From 29a8a0f28370d7e6803b031d44d29ff1e5f9fa5e Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Thu, 11 Dec 2014 15:50:18 +0800 Subject: [PATCH 01/84] move 'process_request' to emqtt_protocol --- apps/emqtt/include/emqtt_frame.hrl | 17 +++ apps/emqtt/src/emqtt_client.erl | 152 --------------------------- apps/emqtt/src/emqtt_protocol.erl | 162 +++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 152 deletions(-) diff --git a/apps/emqtt/include/emqtt_frame.hrl b/apps/emqtt/include/emqtt_frame.hrl index e00191261..1aca3243c 100644 --- a/apps/emqtt/include/emqtt_frame.hrl +++ b/apps/emqtt/include/emqtt_frame.hrl @@ -23,6 +23,8 @@ -define(MQTT_PROTO_MAJOR, 3). -define(MQTT_PROTO_MINOR, 1). +-define(CLIENT_ID_MAXLEN, 23). + %% frame types -define(CONNECT, 1). @@ -49,6 +51,21 @@ -define(CONNACK_CREDENTIALS, 4). %% bad user name or password -define(CONNACK_AUTH, 5). %% not authorized +-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}). + -record(mqtt_frame, {fixed, variable, diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index daa322fa7..500c66d6b 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -43,24 +43,6 @@ -include("emqtt_frame.hrl"). --define(CLIENT_ID_MAXLEN, 23). - --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 }}). @@ -233,140 +215,6 @@ process_frame(Frame = #mqtt_frame{fixed = #mqtt_frame_fixed{type = Type}}, {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 }) -> diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 468b70a22..66af58d4f 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -1,5 +1,167 @@ +%%----------------------------------------------------------------------------- +%% Copyright (c) 2014, Feng Lee +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in all +%% copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. +%%------------------------------------------------------------------------------ -module(emqtt_protocol). +-include("emqtt.hrl"). + +-include("emqtt_log.hrl"). + -include("emqtt_frame.hrl"). +-export([process_request/3]). + +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}. + + From 8569718c2309f8d0669e54dad96f6edee21370c0 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Mon, 29 Dec 2014 10:58:45 +0800 Subject: [PATCH 02/84] rm empty line --- apps/emqtt/include/emqtt_frame.hrl | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/emqtt/include/emqtt_frame.hrl b/apps/emqtt/include/emqtt_frame.hrl index 82c7d196c..ef68345de 100644 --- a/apps/emqtt/include/emqtt_frame.hrl +++ b/apps/emqtt/include/emqtt_frame.hrl @@ -87,4 +87,3 @@ -record(mqtt_frame_other, {other}). - From 930e9f4f9f6a0ab3cf1a0229f53a1821fb373526 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Tue, 30 Dec 2014 13:12:17 +0800 Subject: [PATCH 03/84] protocol and router --- apps/emqtt/include/emqtt_frame.hrl | 4 +-- apps/emqtt/src/emqtt_client.erl | 31 ++++++++++----------- apps/emqtt/src/emqtt_ctl.erl | 36 +++++++++++++++++++------ apps/emqtt/src/emqtt_protocol.erl | 35 ++++++++++++++++++++++++ apps/emqtt/src/emqtt_router.erl | 23 ++++++++++++++++ rel/files/emqtt_ctl | 43 +++++++++--------------------- 6 files changed, 115 insertions(+), 57 deletions(-) create mode 100644 apps/emqtt/src/emqtt_router.erl diff --git a/apps/emqtt/include/emqtt_frame.hrl b/apps/emqtt/include/emqtt_frame.hrl index 9c233c6e5..5ea69a335 100644 --- a/apps/emqtt/include/emqtt_frame.hrl +++ b/apps/emqtt/include/emqtt_frame.hrl @@ -18,14 +18,12 @@ %% 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). --define(CLIENT_ID_MAXLEN, 23). +-define(CLIENT_ID_MAXLEN, 1024). %% frame types diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index ef7a01d2d..a5f3c0941 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -43,20 +43,21 @@ -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}). +%% +%-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), @@ -226,7 +227,7 @@ process_frame(Frame = #mqtt_frame{fixed = #mqtt_frame_fixed{type = Type}}, ok -> ?INFO("frame from ~s: ~p", [ClientId, Frame]), handle_retained(Type, Frame), - process_request(Type, Frame, State#state{keep_alive=KeepAlive1}); + emqtt_protocol:process_request(Type, Frame, State#state{keep_alive=KeepAlive1}); {error, Reason} -> {err, Reason, State} end. diff --git a/apps/emqtt/src/emqtt_ctl.erl b/apps/emqtt/src/emqtt_ctl.erl index 9962ed73d..e24587914 100644 --- a/apps/emqtt/src/emqtt_ctl.erl +++ b/apps/emqtt/src/emqtt_ctl.erl @@ -29,10 +29,9 @@ -include("emqtt_log.hrl"). -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 +43,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). diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 66af58d4f..39eec70e5 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -164,4 +164,39 @@ process_request(?DISCONNECT, #mqtt_frame{}, State=#state{client_id=ClientId}) -> ?INFO("~s disconnected", [ClientId]), {stop, State}. +send_frame(Sock, Frame) -> + ?INFO("send frame:~p", [Frame]), + erlang:port_command(Sock, emqtt_frame:serialise(Frame)). +valid_client_id(ClientId) -> + ClientIdLen = size(ClientId), + 1 =< ClientIdLen andalso ClientIdLen =< ?CLIENT_ID_MAXLEN. + +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}. + +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 }. diff --git a/apps/emqtt/src/emqtt_router.erl b/apps/emqtt/src/emqtt_router.erl new file mode 100644 index 000000000..d8b9492c0 --- /dev/null +++ b/apps/emqtt/src/emqtt_router.erl @@ -0,0 +1,23 @@ +%%----------------------------------------------------------------------------- +%% Copyright (c) 2014, Feng Lee +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in all +%% copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. +%%------------------------------------------------------------------------------ + +-module(emqtt_router). diff --git a/rel/files/emqtt_ctl b/rel/files/emqtt_ctl index becdee431..444439801 100755 --- a/rel/files/emqtt_ctl +++ b/rel/files/emqtt_ctl @@ -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 " + if [ $# -gt 2 ]; then + echo "Usage: $SCRIPT cluster []" 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 " + echo "Usage: $SCRIPT useradd " 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 " + echo "Usage: $SCRIPT userdel " 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 #cluster node" - echo " add_user #add user" - echo " delete_user #delete user" + echo " status #query emqtt status" + echo " cluster [] #query or cluster nodes" + echo " useradd #add user" + echo " userdel #delete user" exit 1 ;; From a5d3581b40c8dccdd92ddbcc71b9bdca0f03b91c Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Tue, 30 Dec 2014 13:21:47 +0800 Subject: [PATCH 04/84] 0.3 todo --- TODO | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/TODO b/TODO index 8c5142381..e7722c346 100644 --- a/TODO +++ b/TODO @@ -17,3 +17,12 @@ temporary, 5000, worker, [emqtt_client]}]}}. fucking stupid..... esockd locked + +0.2.1 +===== + +full MQTT 3.1.1 support... + +node cluster.... + +one million connections test... From 5beb38cd6877cf0b6b6f1ff5a36d0994f9046056 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Mon, 5 Jan 2015 13:04:53 +0800 Subject: [PATCH 05/84] misc fix --- apps/emqtt/include/emqtt_frame.hrl | 2 - apps/emqtt/src/emqtt.erl | 2 +- apps/emqtt/src/emqtt_client.erl | 4 +- apps/emqtt/src/emqtt_cm.erl | 59 ++++++++++++++++++++---------- apps/emqtt/src/emqtt_router.erl | 28 ++++++++++++++ 5 files changed, 70 insertions(+), 25 deletions(-) create mode 100644 apps/emqtt/src/emqtt_router.erl diff --git a/apps/emqtt/include/emqtt_frame.hrl b/apps/emqtt/include/emqtt_frame.hrl index bdaa087b7..f1ae3e7d5 100644 --- a/apps/emqtt/include/emqtt_frame.hrl +++ b/apps/emqtt/include/emqtt_frame.hrl @@ -25,8 +25,6 @@ -define(MQTT_PROTO_MAJOR, 3). -define(MQTT_PROTO_MINOR, 1). --define(CLIENT_ID_MAXLEN, 23). - %% frame types -define(CONNECT, 1). diff --git a/apps/emqtt/src/emqtt.erl b/apps/emqtt/src/emqtt.erl index d881106ad..0b57307e0 100644 --- a/apps/emqtt/src/emqtt.erl +++ b/apps/emqtt/src/emqtt.erl @@ -29,7 +29,7 @@ {packet, raw}, {reuseaddr, true}, {backlog, 512}, - {nodelay, false} + {nodelay, true} ]). listen(Listeners) when is_list(Listeners) -> diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index ef7a01d2d..46a97f807 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -173,9 +173,9 @@ handle_info(Info, State) -> ?ERROR("badinfo :~p",[Info]), {stop, {badinfo, Info}, State}. -terminate(_Reason, #state{keep_alive=KeepAlive}) -> +terminate(_Reason, #state{client_id = ClientId, keep_alive=KeepAlive}) -> emqtt_keep_alive:cancel(KeepAlive), - emqtt_cm:destroy(self()), + emqtt_cm:destroy(ClientId, self()), ok. code_change(_OldVsn, State, _Extra) -> diff --git a/apps/emqtt/src/emqtt_cm.erl b/apps/emqtt/src/emqtt_cm.erl index 9061a6800..d6809f92e 100644 --- a/apps/emqtt/src/emqtt_cm.erl +++ b/apps/emqtt/src/emqtt_cm.erl @@ -29,15 +29,15 @@ -define(SERVER, ?MODULE). +-define(TAB, emqtt_client). + %% ------------------------------------------------------------------ %% API Function Exports %% ------------------------------------------------------------------ -export([start_link/0]). --export([create/2, - destroy/1, - lookup/1]). +-export([lookup/1, create/2, destroy/2]). %% ------------------------------------------------------------------ %% gen_server Function Exports @@ -59,28 +59,17 @@ start_link() -> -spec lookup(ClientId :: binary()) -> pid() | undefined. lookup(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. + gen_server:call(?SERVER, {create, 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}}). +-spec destroy(ClientId :: binary(), Pid :: pid()) -> ok. +destroy(ClientId, Pid) when is_binary(ClientId) -> + gen_server:cast(?SERVER, {destroy, ClientId, Pid}); %% ------------------------------------------------------------------ %% gen_server Function Definitions @@ -88,15 +77,45 @@ destroy(Pid) when is_pid(Pid) -> init(Args) -> %on one node - ets:new(emqtt_client, [named_table, public]), + ets:new(?TAB, [set, named_table, protected]), {ok, Args}. +handle_call({create, ClientId, Pid}, _From, State) -> + case ets:lookup(?TAB, ClientId) of + [{_, Pid, _}] -> + ?ERROR("client '~s' has been registered with ~p", [ClientId, Pid]), + ignore; + [{_, OldPid, MRef}] -> + OldPid ! {stop, duplicate_id}, + erlang:demonitor(MRef), + ets:insert(emqtt_client, {ClientId, Pid, erlang:monitor(process, Pid)}); + [] -> + ets:insert(emqtt_client, {ClientId, Pid, erlang:monitor(process, Pid)}) + end. + {reply, ok, State}; + handle_call(_Request, _From, State) -> {reply, ok, State}. +handle_cast({destroy, ClientId, Pid}, State) when is_binary(ClientId) -> + case ets:lookup(?TAB, ClientId) of + [{_, Pid, MRef}] -> + erlang:demonitor(MRef), + ets:delete(?TAB, ClientId); + [_] -> + ignore; + [] -> + ?ERROR("cannot find client '~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}. diff --git a/apps/emqtt/src/emqtt_router.erl b/apps/emqtt/src/emqtt_router.erl new file mode 100644 index 000000000..98c7329d0 --- /dev/null +++ b/apps/emqtt/src/emqtt_router.erl @@ -0,0 +1,28 @@ +%%----------------------------------------------------------------------------- +%% Copyright (c) 2014, Feng Lee +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in all +%% copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. +%%------------------------------------------------------------------------------ + +-module(emqtt_router). + +%%Router Chain--> +%%--->In +%%Out<--- + From 3e6b17146a770108db231af6779f817329a78155 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Mon, 5 Jan 2015 23:23:08 +0800 Subject: [PATCH 06/84] seperate emqtt_protol from emqtt_client --- apps/emqtt/include/emqtt_frame.hrl | 15 -- apps/emqtt/src/emqtt_client.erl | 238 ++++++----------------------- apps/emqtt/src/emqtt_cm.erl | 10 +- apps/emqtt/src/emqtt_protocol.erl | 202 ++++++++++++++++++++---- apps/emqtt/src/emqtt_router.erl | 15 ++ 5 files changed, 236 insertions(+), 244 deletions(-) diff --git a/apps/emqtt/include/emqtt_frame.hrl b/apps/emqtt/include/emqtt_frame.hrl index 5ea69a335..e878681cd 100644 --- a/apps/emqtt/include/emqtt_frame.hrl +++ b/apps/emqtt/include/emqtt_frame.hrl @@ -51,21 +51,6 @@ -define(CONNACK_CREDENTIALS, 4). %% bad user name or password -define(CONNACK_AUTH, 5). %% not authorized --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}). - -record(mqtt_frame, {fixed, variable, diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index b0a47d74d..cd3132774 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -26,9 +26,7 @@ -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, @@ -43,25 +41,17 @@ -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, + conn_name, + await_recv, + connection_state, + conserve, + parse_state, + proto_state, + keep_alive +}). start_link(Sock) -> gen_server:start_link(?MODULE, [Sock], []). @@ -73,9 +63,9 @@ go(Pid, Sock) -> gen_server:call(Pid, {go, Sock}). init([Sock]) -> - {ok, #state{socket = Sock}, 1000}. + {ok, #state{socket = Sock}, hibernate}. -handle_call({go, Sock}, _From, State=#state{socket = Sock}) -> +handle_call({go, Sock}, _From, State = #state{socket = Sock}) -> {ok, ConnStr} = emqtt_net:connection_string(Sock, inbound), {reply, ok, control_throttle( @@ -85,21 +75,13 @@ handle_call({go, Sock}, _From, State=#state{socket = Sock}) -> 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()})}; + proto_state = emqtt_protocol:initial_state()})}; +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}. @@ -107,45 +89,15 @@ handle_cast(Msg, State) -> handle_info(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]), +handle_info({stop, duplicate_id}, State=#state{conn_name=ConnName}) -> + %%TODO: + %?ERROR("Shutdown for duplicate clientid:~s, conn:~s", [ClientId, ConnName]), stop({shutdown, duplicate_id}, State); -handle_info({dispatch, Msg}, #state{socket = Sock, message_id=MsgId} = State) -> - - #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; +%%TODO: ok?? +handle_info({dispatch, Msg}, #state{proto_state = ProtoState} = State) -> + {ok, ProtoState1} = emqtt_protocol:send_message(Msg, ProtoState), + {noreply, State#state{proto_state = ProtoState1}}; handle_info({inet_reply, _Ref, ok}, State) -> {noreply, State, hibernate}; @@ -157,6 +109,7 @@ handle_info({inet_async, Sock, _Ref, {ok, Data}}, #state{ socket = Sock}=State) handle_info({inet_async, _Sock, _Ref, {error, Reason}}, State) -> network_error(Reason, State); +%%TODO: HOW TO HANDLE THIS?? handle_info({inet_reply, _Sock, {error, Reason}}, State) -> {noreply, State}; @@ -174,9 +127,10 @@ handle_info(Info, State) -> ?ERROR("badinfo :~p",[Info]), {stop, {badinfo, Info}, State}. -terminate(_Reason, #state{client_id = ClientId, keep_alive=KeepAlive}) -> - emqtt_keep_alive:cancel(KeepAlive), - emqtt_cm:destroy(ClientId, self()), +terminate(_Reason, #state{proto_state = ProtoState}) -> + %%TODO: fix keep_alive... + %%emqtt_keep_alive:cancel(KeepAlive), + emqtt_protocol:client_terminated(ProtoState), ok. code_change(_OldVsn, State, _Extra) -> @@ -192,10 +146,11 @@ 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} -> @@ -203,72 +158,31 @@ process_received_bytes(Bytes, control_throttle( State #state{ parse_state = ParseState1 }), hibernate}; {ok, Frame, Rest} -> - case process_frame(Frame, State) of - {ok, State1} -> - PS = emqtt_frame:initial_state(), + case emqtt_protol:handle_frame(Frame, ProtoState) of + {ok, ProtoState1} -> 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) + State#state{ parse_state = emqtt_frame:initial_state(), + proto_state = ProtoState1 }); + {error, Error} -> + ?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, ProtoState1} -> + stop(normal, State#state{proto_state = ProtoState1}) end; {error, Error} -> ?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), - emqtt_protocol:process_request(Type, Frame, State#state{keep_alive=KeepAlive1}); - {error, Reason} -> - {err, Reason, State} - end. - -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: where to SEND WILL MSG?? + %%send_will_msg(State), % todo: flush channel after publish stop({shutdown, conn_closed}, State). @@ -292,63 +206,3 @@ control_throttle(State = #state{ connection_state = Flow, 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}. - diff --git a/apps/emqtt/src/emqtt_cm.erl b/apps/emqtt/src/emqtt_cm.erl index d6809f92e..94e5424e0 100644 --- a/apps/emqtt/src/emqtt_cm.erl +++ b/apps/emqtt/src/emqtt_cm.erl @@ -25,6 +25,8 @@ -author('feng@slimchat.io'). +-include("emqtt_log.hrl"). + -behaviour(gen_server). -define(SERVER, ?MODULE). @@ -69,7 +71,7 @@ create(ClientId, Pid) -> -spec destroy(ClientId :: binary(), Pid :: pid()) -> ok. destroy(ClientId, Pid) when is_binary(ClientId) -> - gen_server:cast(?SERVER, {destroy, ClientId, Pid}); + gen_server:cast(?SERVER, {destroy, ClientId, Pid}). %% ------------------------------------------------------------------ %% gen_server Function Definitions @@ -91,7 +93,7 @@ handle_call({create, ClientId, Pid}, _From, State) -> ets:insert(emqtt_client, {ClientId, Pid, erlang:monitor(process, Pid)}); [] -> ets:insert(emqtt_client, {ClientId, Pid, erlang:monitor(process, Pid)}) - end. + end, {reply, ok, State}; handle_call(_Request, _From, State) -> @@ -106,7 +108,7 @@ handle_cast({destroy, ClientId, Pid}, State) when is_binary(ClientId) -> ignore; [] -> ?ERROR("cannot find client '~s' with ~p", [ClientId, Pid]) - end + end, {noreply, State}; handle_cast(_Msg, State) -> @@ -114,7 +116,7 @@ handle_cast(_Msg, State) -> handle_info({'DOWN', MRef, process, DownPid, _Reason}, State) -> ets:match_delete(emqtt_client, {{'_', DownPid, MRef}}), - {noreply, State}. + {noreply, State}; handle_info(_Info, State) -> {noreply, State}. diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 39eec70e5..5438f4e64 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -28,16 +28,69 @@ -include("emqtt_frame.hrl"). --export([process_request/3]). +-record(proto_state, { + socket, + message_id, + client_id, + clean_sess, + will_msg, + awaiting_ack, + subtopics, + awaiting_rel +}). -process_request(?CONNECT, +-type proto_state() :: #proto_state{}. + +-export([initial_state/1, handle_frame/2, send_message/2, client_terminated/1]). + +-export([info/1]). + +-define(FRAME_TYPE(Frame, Type), + Frame = #mqtt_frame{ fixed = #mqtt_frame_fixed{ type = Type }}). + +initial_state(Socket) -> + #proto_state{ + socket = Socket, + message_id = 1, + awaiting_ack = gb_trees:empty(), + subtopics = [], + awaiting_rel = gb_trees:empty() + }. + +info(#proto_state{ message_id = MsgId, + client_id = ClientId, + clean_sess = CleanSess, + will_msg = WillMsg, + subtopics = SubTopics}) -> + [ {message_id, MsgId}, + {client_id, ClientId}, + {clean_sess, CleanSess}, + {will_msg, WillMsg}, + {subtopics, SubTopics} ]. + +-spec handle_frame(Frame, State) -> {ok, NewState} | {error, any()} when + Frame :: #mqtt_frame{}, + State :: proto_state(), + NewState :: proto_state(). + +handle_frame(Frame = #mqtt_frame{ fixed = #mqtt_frame_fixed{ type = Type }}, + State = #proto_state{client_id = ClientId}) -> + ?INFO("frame from ~s: ~p", [ClientId, Frame]), + case validate_frame(Type, Frame) of + ok -> + handle_request(Type, Frame, State); + {error, Reason} -> + {error, Reason, State} + end. + +handle_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) -> + client_id = ClientId } = Var}, State = #proto_state{socket = Sock}) -> {ReturnCode, State1} = case {lists:member(ProtoVersion, proplists:get_keys(?PROTOCOL_NAMES)), valid_client_id(ClientId)} of @@ -52,12 +105,12 @@ process_request(?CONNECT, {?CONNACK_CREDENTIALS, State}; true -> ?INFO("connect from clientid: ~p, ~p", [ClientId, AlivePeriod]), + %%TODO: + %%KeepAlive = emqtt_keep_alive:new(AlivePeriod*1500, keep_alive_timeout), 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}} + State #proto_state{ will_msg = make_will_msg(Var), + client_id = ClientId }} end end, ?INFO("recv conn...:~p", [ReturnCode]), @@ -66,26 +119,26 @@ process_request(?CONNECT, return_code = ReturnCode }}), {ok, State1}; -process_request(?PUBLISH, Frame=#mqtt_frame{ +handle_request(?PUBLISH, Frame=#mqtt_frame{ fixed = #mqtt_frame_fixed{qos = ?QOS_0}}, State) -> - emqtt_pubsub:publish(make_msg(Frame)), + emqtt_router:route(make_msg(Frame)), {ok, State}; -process_request(?PUBLISH, +handle_request(?PUBLISH, Frame=#mqtt_frame{ fixed = #mqtt_frame_fixed{qos = ?QOS_1}, variable = #mqtt_frame_publish{message_id = MsgId}}, - State=#state{socket=Sock}) -> + State=#proto_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, +handle_request(?PUBLISH, Frame=#mqtt_frame{ fixed = #mqtt_frame_fixed{qos = ?QOS_2}, variable = #mqtt_frame_publish{message_id = MsgId}}, - State=#state{socket=Sock}) -> + State=#proto_state{socket=Sock}) -> emqtt_pubsub:publish(make_msg(Frame)), put({msg, MsgId}, pubrec), send_frame(Sock, #mqtt_frame{fixed = #mqtt_frame_fixed{type = ?PUBREC}, @@ -93,40 +146,40 @@ process_request(?PUBLISH, {ok, State}; -process_request(?PUBACK, #mqtt_frame{}, State) -> +handle_request(?PUBACK, #mqtt_frame{}, State) -> %TODO: fixme later {ok, State}; -process_request(?PUBREC, #mqtt_frame{ +handle_request(?PUBREC, #mqtt_frame{ variable = #mqtt_frame_publish{message_id = MsgId}}, - State=#state{socket=Sock}) -> + State=#proto_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, +handle_request(?PUBREL, #mqtt_frame{ variable = #mqtt_frame_publish{message_id = MsgId}}, - State=#state{socket=Sock}) -> + State=#proto_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{ +handle_request(?PUBCOMP, #mqtt_frame{ variable = #mqtt_frame_publish{message_id = _MsgId}}, State) -> %TODO: fixme later {ok, State}; -process_request(?SUBSCRIBE, +handle_request(?SUBSCRIBE, #mqtt_frame{ variable = #mqtt_frame_subscribe{message_id = MessageId, topic_table = Topics}, payload = undefined}, - #state{socket=Sock} = State) -> + #proto_state{socket=Sock} = State) -> [emqtt_pubsub:subscribe({Name, Qos}, self()) || #mqtt_topic{name=Name, qos=Qos} <- Topics], @@ -140,11 +193,11 @@ process_request(?SUBSCRIBE, {ok, State}; -process_request(?UNSUBSCRIBE, +handle_request(?UNSUBSCRIBE, #mqtt_frame{ variable = #mqtt_frame_subscribe{message_id = MessageId, topic_table = Topics }, - payload = undefined}, #state{socket = Sock, client_id = ClientId} = State) -> + payload = undefined}, #proto_state{socket = Sock, client_id = ClientId} = State) -> [emqtt_pubsub:unsubscribe(Name, self()) || #mqtt_topic{name=Name} <- Topics], @@ -154,26 +207,64 @@ process_request(?UNSUBSCRIBE, {ok, State}; -process_request(?PINGREQ, #mqtt_frame{}, #state{socket=Sock, keep_alive=KeepAlive}=State) -> +%, keep_alive=KeepAlive +handle_request(?PINGREQ, #mqtt_frame{}, #proto_state{socket=Sock}=State) -> %Keep alive timer - KeepAlive1 = emqtt_keep_alive:reset(KeepAlive), + %%TODO:... + %%KeepAlive1 = emqtt_keep_alive:reset(KeepAlive), send_frame(Sock, #mqtt_frame{fixed = #mqtt_frame_fixed{ type = ?PINGRESP }}), - {ok, State#state{keep_alive=KeepAlive1}}; + {ok, State}; -process_request(?DISCONNECT, #mqtt_frame{}, State=#state{client_id=ClientId}) -> +handle_request(?DISCONNECT, #mqtt_frame{}, State=#proto_state{client_id=ClientId}) -> ?INFO("~s disconnected", [ClientId]), {stop, State}. +-spec send_message(Message, State) -> {ok, NewState} when + Message :: mqtt_msg(), + State :: proto_state(), + NewState :: proto_state(). + +send_message(Message, State = #proto_state{socket = Sock, message_id = MsgId}) -> + + #mqtt_msg{retain = Retain, + qos = Qos, + topic = Topic, + dup = Dup, + payload = Payload, + encoder = Encoder} = Message, + + 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 -> + {ok, State}; + true -> + {ok, next_msg_id(State)} + end. + send_frame(Sock, Frame) -> ?INFO("send frame:~p", [Frame]), erlang:port_command(Sock, emqtt_frame:serialise(Frame)). -valid_client_id(ClientId) -> - ClientIdLen = size(ClientId), - 1 =< ClientIdLen andalso ClientIdLen =< ?CLIENT_ID_MAXLEN. - -validate_frame(_Type, _Frame) -> - ok. +%%TODO: fix me later... +client_terminated(#proto_state{client_id = ClientId} = State) -> + emqtt_cm:destroy(ClientId, self()). make_msg(#mqtt_frame{ fixed = #mqtt_frame_fixed{qos = Qos, @@ -200,3 +291,48 @@ make_will_msg(#mqtt_frame_connect{ will_retain = Retain, topic = Topic, dup = false, payload = Msg }. + +next_msg_id(State = #proto_state{ message_id = 16#ffff }) -> + State #proto_state{ message_id = 1 }; +next_msg_id(State = #proto_state{ message_id = MsgId }) -> + State #proto_state{ message_id = MsgId + 1 }. + +valid_client_id(ClientId) -> + ClientIdLen = size(ClientId), + 1 =< ClientIdLen andalso ClientIdLen =< ?CLIENT_ID_MAXLEN. + +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. + +maybe_clean_sess(false, _Conn, _ClientId) -> + % todo: establish subscription to deliver old unacknowledged messages + ok. + +%%---------------------------------------------------------------------------- + +send_will_msg(#proto_state{will_msg = undefined}) -> + ignore; +send_will_msg(#proto_state{will_msg = WillMsg }) -> + emqtt_pubsub:publish(WillMsg). diff --git a/apps/emqtt/src/emqtt_router.erl b/apps/emqtt/src/emqtt_router.erl index a33a5446e..215e10522 100644 --- a/apps/emqtt/src/emqtt_router.erl +++ b/apps/emqtt/src/emqtt_router.erl @@ -20,8 +20,23 @@ %% SOFTWARE. %%------------------------------------------------------------------------------ +%%route chain... statistics -module(emqtt_router). +-include("emqtt.hrl"). + +-include("emqtt_frame.hrl"). + +-export([route/1]). + %%Router Chain--> %%--->In %%Out<--- + +-spec route(Msg :: mqtt_msg()) -> any(). +route(Msg) -> + emqtt_pubsub:publish(retained(Msg)). + +retained(Msg = #mqtt_msg{retain = true, topic = Topic}) -> + emqtt_retained:insert(Topic, Msg), Msg. + From a8dcb2bfe39954fb55cdf83c50931539bbe2e712 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Tue, 6 Jan 2015 10:46:53 +0800 Subject: [PATCH 07/84] rm emqtt_log.hrl --- apps/emqtt/include/emqtt_log.hrl | 85 ------------------------------- apps/emqtt/src/emqtt_app.erl | 6 ++- apps/emqtt/src/emqtt_auth.erl | 3 -- apps/emqtt/src/emqtt_client.erl | 14 +++-- apps/emqtt/src/emqtt_cm.erl | 6 +-- apps/emqtt/src/emqtt_ctl.erl | 6 ++- apps/emqtt/src/emqtt_http.erl | 4 +- apps/emqtt/src/emqtt_monitor.erl | 15 +++--- apps/emqtt/src/emqtt_protocol.erl | 18 +++---- apps/emqtt/src/emqtt_pubsub.erl | 4 +- apps/emqtt/src/emqtt_retained.erl | 2 - 11 files changed, 33 insertions(+), 130 deletions(-) delete mode 100644 apps/emqtt/include/emqtt_log.hrl diff --git a/apps/emqtt/include/emqtt_log.hrl b/apps/emqtt/include/emqtt_log.hrl deleted file mode 100644 index cb9215ebc..000000000 --- a/apps/emqtt/include/emqtt_log.hrl +++ /dev/null @@ -1,85 +0,0 @@ -%%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee -%% -%% Permission is hereby granted, free of charge, to any person obtaining a copy -%% of this software and associated documentation files (the "Software"), to deal -%% in the Software without restriction, including without limitation the rights -%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%% copies of the Software, and to permit persons to whom the Software is -%% furnished to do so, subject to the following conditions: -%% -%% The above copyright notice and this permission notice shall be included in all -%% copies or substantial portions of the Software. -%% -%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%% SOFTWARE. -%%------------------------------------------------------------------------------ - -%%------------------------------------------------------------------------------ -%% 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)). - diff --git a/apps/emqtt/src/emqtt_app.erl b/apps/emqtt/src/emqtt_app.erl index b8fac497f..3b9aeff9d 100644 --- a/apps/emqtt/src/emqtt_app.erl +++ b/apps/emqtt/src/emqtt_app.erl @@ -24,13 +24,15 @@ -author('feng@slimchat.io'). --include("emqtt_log.hrl"). - -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 %% =================================================================== diff --git a/apps/emqtt/src/emqtt_auth.erl b/apps/emqtt/src/emqtt_auth.erl index 0905ef8ee..ba3202e0a 100644 --- a/apps/emqtt/src/emqtt_auth.erl +++ b/apps/emqtt/src/emqtt_auth.erl @@ -26,8 +26,6 @@ -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) -> diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index cd3132774..8ed84a55e 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -37,8 +37,6 @@ -include("emqtt.hrl"). --include("emqtt_log.hrl"). - -include("emqtt_frame.hrl"). %%Client State... @@ -91,7 +89,7 @@ handle_info(timeout, State) -> handle_info({stop, duplicate_id}, State=#state{conn_name=ConnName}) -> %%TODO: - %?ERROR("Shutdown for duplicate clientid:~s, conn:~s", [ClientId, ConnName]), + %lager:error("Shutdown for duplicate clientid:~s, conn:~s", [ClientId, ConnName]), stop({shutdown, duplicate_id}, State); %%TODO: ok?? @@ -116,7 +114,7 @@ handle_info({inet_reply, _Sock, {error, Reason}}, 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]), + lager:info("keep_alive timeout: ~p", [State#state.conn_name]), {stop, normal, State}; active -> KeepAlive1 = emqtt_keep_alive:reset(KeepAlive), @@ -124,7 +122,7 @@ handle_info(keep_alive_timeout, #state{keep_alive=KeepAlive}=State) -> end; handle_info(Info, State) -> - ?ERROR("badinfo :~p",[Info]), + lager:error("badinfo :~p",[Info]), {stop, {badinfo, Info}, State}. terminate(_Reason, #state{proto_state = ProtoState}) -> @@ -165,7 +163,7 @@ process_received_bytes(Bytes, State#state{ parse_state = emqtt_frame:initial_state(), proto_state = ProtoState1 }); {error, Error} -> - ?ERROR("MQTT protocol error ~p for connection ~p~n", [Error, ConnStr]), + 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}); @@ -173,14 +171,14 @@ process_received_bytes(Bytes, stop(normal, State#state{proto_state = ProtoState1}) end; {error, Error} -> - ?ERROR("MQTT detected framing error ~p for connection ~p~n", [ConnStr, Error]), + lager:error("MQTT detected framing error ~p for connection ~p~n", [ConnStr, Error]), stop({shutdown, Error}, State) end. %%---------------------------------------------------------------------------- network_error(Reason, State = #state{ conn_name = ConnStr}) -> - ?ERROR("MQTT detected network error '~p' for ~p", [Reason, ConnStr]), + lager:error("MQTT detected network error '~p' for ~p", [Reason, ConnStr]), %%TODO: where to SEND WILL MSG?? %%send_will_msg(State), % todo: flush channel after publish diff --git a/apps/emqtt/src/emqtt_cm.erl b/apps/emqtt/src/emqtt_cm.erl index 94e5424e0..bd5562aba 100644 --- a/apps/emqtt/src/emqtt_cm.erl +++ b/apps/emqtt/src/emqtt_cm.erl @@ -25,8 +25,6 @@ -author('feng@slimchat.io'). --include("emqtt_log.hrl"). - -behaviour(gen_server). -define(SERVER, ?MODULE). @@ -85,7 +83,7 @@ init(Args) -> handle_call({create, ClientId, Pid}, _From, State) -> case ets:lookup(?TAB, ClientId) of [{_, Pid, _}] -> - ?ERROR("client '~s' has been registered with ~p", [ClientId, Pid]), + lager:error("client '~s' has been registered with ~p", [ClientId, Pid]), ignore; [{_, OldPid, MRef}] -> OldPid ! {stop, duplicate_id}, @@ -107,7 +105,7 @@ handle_cast({destroy, ClientId, Pid}, State) when is_binary(ClientId) -> [_] -> ignore; [] -> - ?ERROR("cannot find client '~s' with ~p", [ClientId, Pid]) + lager:error("cannot find client '~s' with ~p", [ClientId, Pid]) end, {noreply, State}; diff --git a/apps/emqtt/src/emqtt_ctl.erl b/apps/emqtt/src/emqtt_ctl.erl index e24587914..e5f747552 100644 --- a/apps/emqtt/src/emqtt_ctl.erl +++ b/apps/emqtt/src/emqtt_ctl.erl @@ -26,7 +26,11 @@ -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/1, diff --git a/apps/emqtt/src/emqtt_http.erl b/apps/emqtt/src/emqtt_http.erl index 2458c4241..e3bdc4f90 100644 --- a/apps/emqtt/src/emqtt_http.erl +++ b/apps/emqtt/src/emqtt_http.erl @@ -26,8 +26,6 @@ -include("emqtt.hrl"). --include("emqtt_log.hrl"). - -import(proplists, [get_value/2, get_value/3]). -export([handle/1]). @@ -45,7 +43,7 @@ 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 { diff --git a/apps/emqtt/src/emqtt_monitor.erl b/apps/emqtt/src/emqtt_monitor.erl index 608dc2701..996600707 100644 --- a/apps/emqtt/src/emqtt_monitor.erl +++ b/apps/emqtt/src/emqtt_monitor.erl @@ -24,8 +24,6 @@ -author('feng@slimchat.io'). --include("emqtt_log.hrl"). - -behavior(gen_server). -export([start_link/0]). @@ -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}. %%-------------------------------------------------------------------- diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 5438f4e64..34da96b7c 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -24,8 +24,6 @@ -include("emqtt.hrl"). --include("emqtt_log.hrl"). - -include("emqtt_frame.hrl"). -record(proto_state, { @@ -75,7 +73,7 @@ info(#proto_state{ message_id = MsgId, handle_frame(Frame = #mqtt_frame{ fixed = #mqtt_frame_fixed{ type = Type }}, State = #proto_state{client_id = ClientId}) -> - ?INFO("frame from ~s: ~p", [ClientId, Frame]), + lager:info("frame from ~s: ~p", [ClientId, Frame]), case validate_frame(Type, Frame) of ok -> handle_request(Type, Frame, State); @@ -101,10 +99,10 @@ handle_request(?CONNECT, _ -> case emqtt_auth:check(Username, Password) of false -> - ?ERROR_MSG("MQTT login failed - no credentials"), + lager:error_MSG("MQTT login failed - no credentials"), {?CONNACK_CREDENTIALS, State}; true -> - ?INFO("connect from clientid: ~p, ~p", [ClientId, AlivePeriod]), + lager:info("connect from clientid: ~p, ~p", [ClientId, AlivePeriod]), %%TODO: %%KeepAlive = emqtt_keep_alive:new(AlivePeriod*1500, keep_alive_timeout), emqtt_cm:create(ClientId, self()), @@ -113,7 +111,7 @@ handle_request(?CONNECT, client_id = ClientId }} end end, - ?INFO("recv conn...:~p", [ReturnCode]), + lager:info("recv conn...:~p", [ReturnCode]), send_frame(Sock, #mqtt_frame{ fixed = #mqtt_frame_fixed{ type = ?CONNACK}, variable = #mqtt_frame_connack{ return_code = ReturnCode }}), @@ -216,7 +214,7 @@ handle_request(?PINGREQ, #mqtt_frame{}, #proto_state{socket=Sock}=State) -> {ok, State}; handle_request(?DISCONNECT, #mqtt_frame{}, State=#proto_state{client_id=ClientId}) -> - ?INFO("~s disconnected", [ClientId]), + lager:info("~s disconnected", [ClientId]), {stop, State}. -spec send_message(Message, State) -> {ok, NewState} when @@ -259,7 +257,7 @@ send_message(Message, State = #proto_state{socket = Sock, message_id = MsgId}) - end. send_frame(Sock, Frame) -> - ?INFO("send frame:~p", [Frame]), + lager:info("send frame:~p", [Frame]), erlang:port_command(Sock, emqtt_frame:serialise(Frame)). %%TODO: fix me later... @@ -312,7 +310,7 @@ validate_frame(?UNSUBSCRIBE, #mqtt_frame{variable = #mqtt_frame_subscribe{topic_ not emqtt_topic:validate({subscribe, Topic})], case ErrTopics of [] -> ok; - _ -> ?ERROR("error topics: ~p", [ErrTopics]), {error, badtopic} + _ -> lager:error("error topics: ~p", [ErrTopics]), {error, badtopic} end; validate_frame(?SUBSCRIBE, #mqtt_frame{variable = #mqtt_frame_subscribe{topic_table = Topics}}) -> @@ -320,7 +318,7 @@ validate_frame(?SUBSCRIBE, #mqtt_frame{variable = #mqtt_frame_subscribe{topic_ta not (emqtt_topic:validate({subscribe, Topic}) and (Qos < 3))], case ErrTopics of [] -> ok; - _ -> ?ERROR("error topics: ~p", [ErrTopics]), {error, badtopic} + _ -> lager:error("error topics: ~p", [ErrTopics]), {error, badtopic} end; validate_frame(_Type, _Frame) -> diff --git a/apps/emqtt/src/emqtt_pubsub.erl b/apps/emqtt/src/emqtt_pubsub.erl index 22d47a90e..a9785deee 100644 --- a/apps/emqtt/src/emqtt_pubsub.erl +++ b/apps/emqtt/src/emqtt_pubsub.erl @@ -26,8 +26,6 @@ -include("emqtt.hrl"). --include("emqtt_log.hrl"). - -include("emqtt_topic.hrl"). -include_lib("stdlib/include/qlc.hrl"). @@ -174,7 +172,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}), diff --git a/apps/emqtt/src/emqtt_retained.erl b/apps/emqtt/src/emqtt_retained.erl index fb57d223a..69d643761 100644 --- a/apps/emqtt/src/emqtt_retained.erl +++ b/apps/emqtt/src/emqtt_retained.erl @@ -46,8 +46,6 @@ -include("emqtt.hrl"). --include("emqtt_log.hrl"). - -export([start_link/0, lookup/1, insert/2, From fdc69f6ea67933892639ae45dcf9bffcc8216f84 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Tue, 6 Jan 2015 10:47:06 +0800 Subject: [PATCH 08/84] fix deps --- Makefile | 6 +++--- rebar.config | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 17970a877..72e436459 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ -all: deps compile +all: dep compile -compile: deps +compile: dep ./rebar compile -deps: +dep: ./rebar get-deps clean: diff --git a/rebar.config b/rebar.config index 7c558a08e..50069b6c4 100644 --- a/rebar.config +++ b/rebar.config @@ -12,5 +12,5 @@ {deps, [ {lager, ".*", {git, "git://github.com/basho/lager.git", {branch, "master"}}}, {esockd, ".*", {git, "git://github.com/slimpp/esockd.git", {branch, "master"}}}, - {mochiweb, ".*", {git, "git@github.com:slimpp/mochiweb.git", {branch, "master"}}} + {mochiweb, ".*", {git, "git://github.com/slimpp/mochiweb.git", {branch, "master"}}} ]}. From 668b39768c4e258a3eb9ecefdd0ccc0bfb17c962 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 6 Jan 2015 03:39:31 +0000 Subject: [PATCH 09/84] pubsut test --- apps/emqtt/src/emqtt_client.erl | 11 +++++++++-- apps/emqtt/src/emqtt_protocol.erl | 6 ++++-- apps/emqtt/src/emqtt_router.erl | 4 +++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index 8ed84a55e..22eb1a728 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -65,6 +65,7 @@ init([Sock]) -> handle_call({go, Sock}, _From, State = #state{socket = Sock}) -> {ok, ConnStr} = emqtt_net:connection_string(Sock, inbound), + lager:debug("conn from ~s", [ConnStr]), {reply, ok, control_throttle( #state{ socket = Sock, @@ -73,7 +74,7 @@ handle_call({go, Sock}, _From, State = #state{socket = Sock}) -> connection_state = running, conserve = false, parse_state = emqtt_frame:initial_state(), - proto_state = emqtt_protocol:initial_state()})}; + proto_state = emqtt_protocol:initial_state(Sock)})}; handle_call(info, _From, State = #state{conn_name=ConnName, proto_state = ProtoState}) -> {reply, [{conn_name, ConnName} | emqtt_protocol:info(ProtoState)], State}; @@ -125,6 +126,12 @@ handle_info(Info, State) -> lager:error("badinfo :~p",[Info]), {stop, {badinfo, Info}, State}. +terminate(_Reason, #state{proto_state = unefined}) -> + %%TODO: fix keep_alive... + %%emqtt_keep_alive:cancel(KeepAlive), + %emqtt_protocol:client_terminated(ProtoState), + ok; + terminate(_Reason, #state{proto_state = ProtoState}) -> %%TODO: fix keep_alive... %%emqtt_keep_alive:cancel(KeepAlive), @@ -156,7 +163,7 @@ process_received_bytes(Bytes, control_throttle( State #state{ parse_state = ParseState1 }), hibernate}; {ok, Frame, Rest} -> - case emqtt_protol:handle_frame(Frame, ProtoState) of + case emqtt_protocol:handle_frame(Frame, ProtoState) of {ok, ProtoState1} -> process_received_bytes( Rest, diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 34da96b7c..9e004c8bd 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -88,7 +88,8 @@ handle_request(?CONNECT, proto_ver = ProtoVersion, clean_sess = CleanSess, keep_alive = AlivePeriod, - client_id = ClientId } = Var}, State = #proto_state{socket = Sock}) -> + client_id = ClientId } = Var}, State0 = #proto_state{socket = Sock}) -> + State = State0#proto_state{client_id = ClientId}, {ReturnCode, State1} = case {lists:member(ProtoVersion, proplists:get_keys(?PROTOCOL_NAMES)), valid_client_id(ClientId)} of @@ -262,7 +263,8 @@ send_frame(Sock, Frame) -> %%TODO: fix me later... client_terminated(#proto_state{client_id = ClientId} = State) -> - emqtt_cm:destroy(ClientId, self()). + ok. + %emqtt_cm:destroy(ClientId, self()). make_msg(#mqtt_frame{ fixed = #mqtt_frame_fixed{qos = Qos, diff --git a/apps/emqtt/src/emqtt_router.erl b/apps/emqtt/src/emqtt_router.erl index 215e10522..6b5c48c9b 100644 --- a/apps/emqtt/src/emqtt_router.erl +++ b/apps/emqtt/src/emqtt_router.erl @@ -38,5 +38,7 @@ route(Msg) -> emqtt_pubsub:publish(retained(Msg)). retained(Msg = #mqtt_msg{retain = true, topic = Topic}) -> - emqtt_retained:insert(Topic, Msg), Msg. + emqtt_retained:insert(Topic, Msg), Msg; + +retained(Msg) -> Msg. From 1c8ac49b1272f1286c9a30412091968fff867fd9 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Tue, 6 Jan 2015 11:50:53 +0800 Subject: [PATCH 10/84] misc fix --- apps/emqtt/src/emqtt_protocol.erl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 34da96b7c..1dbb3e8da 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -112,9 +112,10 @@ handle_request(?CONNECT, end end, lager:info("recv conn...:~p", [ReturnCode]), - send_frame(Sock, #mqtt_frame{ fixed = #mqtt_frame_fixed{ type = ?CONNACK}, - variable = #mqtt_frame_connack{ - return_code = ReturnCode }}), + send_frame(Sock, #mqtt_frame{ + fixed = #mqtt_frame_fixed{ type = ?CONNACK }, + variable = #mqtt_frame_connack{ + return_code = ReturnCode }}), {ok, State1}; handle_request(?PUBLISH, Frame=#mqtt_frame{ From f1c7185f5244b5ea20541787d1c7723424a7151e Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Tue, 6 Jan 2015 12:17:25 +0800 Subject: [PATCH 11/84] state -> conn_state --- apps/emqtt/src/emqtt_client.erl | 56 +++++++++++++++---------------- apps/emqtt/src/emqtt_cm.erl | 47 ++++++++++++++++---------- apps/emqtt/src/emqtt_protocol.erl | 6 ++-- 3 files changed, 60 insertions(+), 49 deletions(-) diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index 22eb1a728..da692bbc2 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -40,7 +40,7 @@ -include("emqtt_frame.hrl"). %%Client State... --record(state, { +-record(conn_state, { socket, conn_name, await_recv, @@ -61,14 +61,14 @@ go(Pid, Sock) -> gen_server:call(Pid, {go, Sock}). init([Sock]) -> - {ok, #state{socket = Sock}, hibernate}. + {ok, #conn_state{socket = Sock}, hibernate}. -handle_call({go, Sock}, _From, State = #state{socket = Sock}) -> +handle_call({go, Sock}, _From, State = #conn_state{socket = Sock}) -> {ok, ConnStr} = emqtt_net:connection_string(Sock, inbound), lager:debug("conn from ~s", [ConnStr]), {reply, ok, control_throttle( - #state{ socket = Sock, + #conn_state{ socket = Sock, conn_name = ConnStr, await_recv = false, connection_state = running, @@ -76,7 +76,7 @@ handle_call({go, Sock}, _From, State = #state{socket = Sock}) -> parse_state = emqtt_frame:initial_state(), proto_state = emqtt_protocol:initial_state(Sock)})}; -handle_call(info, _From, State = #state{conn_name=ConnName, proto_state = ProtoState}) -> +handle_call(info, _From, State = #conn_state{conn_name=ConnName, proto_state = ProtoState}) -> {reply, [{conn_name, ConnName} | emqtt_protocol:info(ProtoState)], State}; handle_call(Req, _From, State) -> @@ -88,22 +88,22 @@ handle_cast(Msg, State) -> handle_info(timeout, State) -> stop({shutdown, timeout}, State); -handle_info({stop, duplicate_id}, State=#state{conn_name=ConnName}) -> +handle_info({stop, duplicate_id}, State=#conn_state{conn_name=ConnName}) -> %%TODO: %lager:error("Shutdown for duplicate clientid:~s, conn:~s", [ClientId, ConnName]), stop({shutdown, duplicate_id}, State); %%TODO: ok?? -handle_info({dispatch, Msg}, #state{proto_state = ProtoState} = State) -> +handle_info({dispatch, Msg}, #conn_state{proto_state = ProtoState} = State) -> {ok, ProtoState1} = emqtt_protocol:send_message(Msg, ProtoState), - {noreply, State#state{proto_state = ProtoState1}}; + {noreply, State#conn_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}}, #conn_state{ socket = Sock}=State) -> process_received_bytes( - Data, control_throttle(State #state{ await_recv = false })); + Data, control_throttle(State #conn_state{ await_recv = false })); handle_info({inet_async, _Sock, _Ref, {error, Reason}}, State) -> network_error(Reason, State); @@ -112,27 +112,27 @@ handle_info({inet_async, _Sock, _Ref, {error, Reason}}, State) -> handle_info({inet_reply, _Sock, {error, Reason}}, State) -> {noreply, State}; -handle_info(keep_alive_timeout, #state{keep_alive=KeepAlive}=State) -> +handle_info(keep_alive_timeout, #conn_state{keep_alive=KeepAlive}=State) -> case emqtt_keep_alive:state(KeepAlive) of idle -> - lager:info("keep_alive timeout: ~p", [State#state.conn_name]), + lager:info("keep_alive timeout: ~p", [State#conn_state.conn_name]), {stop, normal, State}; active -> KeepAlive1 = emqtt_keep_alive:reset(KeepAlive), - {noreply, State#state{keep_alive=KeepAlive1}} + {noreply, State#conn_state{keep_alive=KeepAlive1}} end; handle_info(Info, State) -> lager:error("badinfo :~p",[Info]), {stop, {badinfo, Info}, State}. -terminate(_Reason, #state{proto_state = unefined}) -> +terminate(_Reason, #conn_state{proto_state = unefined}) -> %%TODO: fix keep_alive... %%emqtt_keep_alive:cancel(KeepAlive), %emqtt_protocol:client_terminated(ProtoState), ok; -terminate(_Reason, #state{proto_state = ProtoState}) -> +terminate(_Reason, #conn_state{proto_state = ProtoState}) -> %%TODO: fix keep_alive... %%emqtt_keep_alive:cancel(KeepAlive), emqtt_protocol:client_terminated(ProtoState), @@ -154,28 +154,28 @@ process_received_bytes(<<>>, State) -> {noreply, State, hibernate}; process_received_bytes(Bytes, - State = #state{ parse_state = ParseState, + State = #conn_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 }), + control_throttle( State #conn_state{ parse_state = ParseState1 }), hibernate}; {ok, Frame, Rest} -> case emqtt_protocol:handle_frame(Frame, ProtoState) of {ok, ProtoState1} -> process_received_bytes( Rest, - State#state{ parse_state = emqtt_frame:initial_state(), + State#conn_state{ parse_state = emqtt_frame: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({shutdown, Error}, State#conn_state{proto_state = ProtoState1}); {stop, ProtoState1} -> - stop(normal, State#state{proto_state = ProtoState1}) + stop(normal, State#conn_state{proto_state = ProtoState1}) end; {error, Error} -> lager:error("MQTT detected framing error ~p for connection ~p~n", [ConnStr, Error]), @@ -184,26 +184,26 @@ process_received_bytes(Bytes, %%---------------------------------------------------------------------------- network_error(Reason, - State = #state{ conn_name = ConnStr}) -> + State = #conn_state{ conn_name = ConnStr}) -> lager:error("MQTT detected network error '~p' for ~p", [Reason, ConnStr]), %%TODO: where to SEND WILL MSG?? %%send_will_msg(State), % todo: flush channel after publish stop({shutdown, conn_closed}, State). -run_socket(State = #state{ connection_state = blocked }) -> +run_socket(State = #conn_state{ connection_state = blocked }) -> State; -run_socket(State = #state{ await_recv = true }) -> +run_socket(State = #conn_state{ await_recv = true }) -> State; -run_socket(State = #state{ socket = Sock }) -> +run_socket(State = #conn_state{ socket = Sock }) -> async_recv(Sock, 0, infinity), - State#state{ await_recv = true }. + State#conn_state{ await_recv = true }. -control_throttle(State = #state{ connection_state = Flow, +control_throttle(State = #conn_state{ connection_state = Flow, conserve = Conserve }) -> case {Flow, Conserve} of - {running, true} -> State #state{ connection_state = blocked }; - {blocked, false} -> run_socket(State #state{ + {running, true} -> State #conn_state{ connection_state = blocked }; + {blocked, false} -> run_socket(State #conn_state{ connection_state = running }); {_, _} -> run_socket(State) end. diff --git a/apps/emqtt/src/emqtt_cm.erl b/apps/emqtt/src/emqtt_cm.erl index bd5562aba..0182f71e7 100644 --- a/apps/emqtt/src/emqtt_cm.erl +++ b/apps/emqtt/src/emqtt_cm.erl @@ -37,7 +37,7 @@ -export([start_link/0]). --export([lookup/1, create/2, destroy/2]). +-export([lookup/1, register/2, unregister/2]). %% ------------------------------------------------------------------ %% gen_server Function Exports @@ -56,56 +56,65 @@ 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; [] -> undefined end. --spec create(ClientId :: binary(), Pid :: pid()) -> ok. -create(ClientId, Pid) -> - gen_server:call(?SERVER, {create, ClientId, Pid}). +%% +%% @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(ClientId :: binary(), Pid :: pid()) -> ok. -destroy(ClientId, Pid) when is_binary(ClientId) -> - gen_server:cast(?SERVER, {destroy, ClientId, 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(?TAB, [set, named_table, protected]), - {ok, Args}. + {ok, none}. -handle_call({create, ClientId, Pid}, _From, State) -> +handle_call({register, ClientId, Pid}, _From, State) -> case ets:lookup(?TAB, ClientId) of [{_, Pid, _}] -> - lager:error("client '~s' has been registered with ~p", [ClientId, Pid]), + lager:error("clientId '~s' has been registered with ~p", [ClientId, Pid]), ignore; [{_, OldPid, MRef}] -> - OldPid ! {stop, duplicate_id}, + OldPid ! {stop, duplicate_id, Pid}, erlang:demonitor(MRef), - ets:insert(emqtt_client, {ClientId, Pid, erlang:monitor(process, Pid)}); + insert(ClientId, Pid); [] -> - ets:insert(emqtt_client, {ClientId, Pid, erlang:monitor(process, Pid)}) + insert(ClientId, Pid) end, {reply, ok, State}; handle_call(_Request, _From, State) -> {reply, ok, State}. -handle_cast({destroy, ClientId, Pid}, State) when is_binary(ClientId) -> +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 client '~s' with ~p", [ClientId, Pid]) + lager:error("cannot find clientId '~s' with ~p", [ClientId, Pid]) end, {noreply, State}; @@ -125,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)}). diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 9528b8096..f2504ace5 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -106,7 +106,7 @@ handle_request(?CONNECT, lager:info("connect from clientid: ~p, ~p", [ClientId, AlivePeriod]), %%TODO: %%KeepAlive = emqtt_keep_alive:new(AlivePeriod*1500, keep_alive_timeout), - emqtt_cm:create(ClientId, self()), + emqtt_cm:register(ClientId, self()), {?CONNACK_ACCEPT, State #proto_state{ will_msg = make_will_msg(Var), client_id = ClientId }} @@ -265,7 +265,7 @@ send_frame(Sock, Frame) -> %%TODO: fix me later... client_terminated(#proto_state{client_id = ClientId} = State) -> ok. - %emqtt_cm:destroy(ClientId, self()). + %emqtt_cm:unregister(ClientId, self()). make_msg(#mqtt_frame{ fixed = #mqtt_frame_fixed{qos = Qos, @@ -336,4 +336,4 @@ maybe_clean_sess(false, _Conn, _ClientId) -> send_will_msg(#proto_state{will_msg = undefined}) -> ignore; send_will_msg(#proto_state{will_msg = WillMsg }) -> - emqtt_pubsub:publish(WillMsg). + emqtt_router:route(WillMsg). From b44511af50f157413b8c3d4ab7486d6beba6d4cd Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Tue, 6 Jan 2015 21:46:12 +0800 Subject: [PATCH 12/84] queue, router, sm --- apps/emqtt/src/emqtt_app.erl | 8 ++++ apps/emqtt/src/emqtt_client.erl | 6 ++- apps/emqtt/src/emqtt_queue.erl | 73 ++++++++++++++++++++++++++++++ apps/emqtt/src/emqtt_queue_sup.erl | 41 +++++++++++++++++ apps/emqtt/src/emqtt_router.erl | 51 ++++++++++++++++++++- apps/emqtt/src/emqtt_sm.erl | 5 ++ doc/design.md | 30 ++++++++++++ 7 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 apps/emqtt/src/emqtt_queue.erl create mode 100644 apps/emqtt/src/emqtt_queue_sup.erl create mode 100644 apps/emqtt/src/emqtt_sm.erl create mode 100644 doc/design.md diff --git a/apps/emqtt/src/emqtt_app.erl b/apps/emqtt/src/emqtt_app.erl index 3b9aeff9d..14c3bf29e 100644 --- a/apps/emqtt/src/emqtt_app.erl +++ b/apps/emqtt/src/emqtt_app.erl @@ -77,6 +77,14 @@ start_servers(Sup) -> {"emqtt auth", emqtt_auth}, {"emqtt retained", emqtt_retained}, {"emqtt pubsub", emqtt_pubsub}, + {"emqtt router", emqtt_router}, + {"emqtt queue supervisor", fun() -> + Mod = emqtt_queue_sup, + supervisor:start_child(Sup, + {Mod, + {Mod, start_link, []}, + permanent, 1000, supervisor, [Mod]}) + end}, {"emqtt monitor", emqtt_monitor} ]). diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index da692bbc2..e3e18d1b6 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -61,11 +61,12 @@ go(Pid, Sock) -> gen_server:call(Pid, {go, Sock}). init([Sock]) -> + io:format("client is created: ~p~n", [self()]), {ok, #conn_state{socket = Sock}, hibernate}. handle_call({go, Sock}, _From, State = #conn_state{socket = Sock}) -> {ok, ConnStr} = emqtt_net:connection_string(Sock, inbound), - lager:debug("conn from ~s", [ConnStr]), + io:format("conn from ~s~n", [ConnStr]), {reply, ok, control_throttle( #conn_state{ socket = Sock, @@ -126,7 +127,8 @@ handle_info(Info, State) -> lager:error("badinfo :~p",[Info]), {stop, {badinfo, Info}, State}. -terminate(_Reason, #conn_state{proto_state = unefined}) -> +terminate(Reason, #conn_state{proto_state = unefined}) -> + io:format("client terminated: ~p, reason: ~p~n", [self(), Reason]), %%TODO: fix keep_alive... %%emqtt_keep_alive:cancel(KeepAlive), %emqtt_protocol:client_terminated(ProtoState), diff --git a/apps/emqtt/src/emqtt_queue.erl b/apps/emqtt/src/emqtt_queue.erl new file mode 100644 index 000000000..da715a542 --- /dev/null +++ b/apps/emqtt/src/emqtt_queue.erl @@ -0,0 +1,73 @@ +%%----------------------------------------------------------------------------- +%% Copyright (c) 2014, Feng Lee +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in all +%% copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. +%%------------------------------------------------------------------------------ +-module(emqtt_queue). + +-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(?MODULE, [], []). + +%% ------------------------------------------------------------------ +%% gen_server Function Definitions +%% ------------------------------------------------------------------ + +init(Args) -> + {ok, Args}. + +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 +%% ------------------------------------------------------------------ + diff --git a/apps/emqtt/src/emqtt_queue_sup.erl b/apps/emqtt/src/emqtt_queue_sup.erl new file mode 100644 index 000000000..3cfa25383 --- /dev/null +++ b/apps/emqtt/src/emqtt_queue_sup.erl @@ -0,0 +1,41 @@ +%%----------------------------------------------------------------------------- +%% Copyright (c) 2014, Feng Lee +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in all +%% copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. +%%------------------------------------------------------------------------------ +-module(emqtt_queue_sup). + +-author('feng@slimchat.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]}]}}. + + diff --git a/apps/emqtt/src/emqtt_router.erl b/apps/emqtt/src/emqtt_router.erl index 6b5c48c9b..43cc16268 100644 --- a/apps/emqtt/src/emqtt_router.erl +++ b/apps/emqtt/src/emqtt_router.erl @@ -27,18 +27,65 @@ -include("emqtt_frame.hrl"). --export([route/1]). +-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]). + +%% ------------------------------------------------------------------ +%% API Function Definitions +%% ------------------------------------------------------------------ + +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). -spec route(Msg :: mqtt_msg()) -> any(). route(Msg) -> emqtt_pubsub:publish(retained(Msg)). +%% ------------------------------------------------------------------ +%% gen_server Function Definitions +%% ------------------------------------------------------------------ + +init(Args) -> + {ok, Args}. + +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 +%% ------------------------------------------------------------------ retained(Msg = #mqtt_msg{retain = true, topic = Topic}) -> emqtt_retained:insert(Topic, Msg), Msg; retained(Msg) -> Msg. - diff --git a/apps/emqtt/src/emqtt_sm.erl b/apps/emqtt/src/emqtt_sm.erl new file mode 100644 index 000000000..cbcb477d9 --- /dev/null +++ b/apps/emqtt/src/emqtt_sm.erl @@ -0,0 +1,5 @@ +-module(emqtt_sm). + +%%emqtt session manager... + +%%cleanSess: true | false diff --git a/doc/design.md b/doc/design.md new file mode 100644 index 000000000..9e775a095 --- /dev/null +++ b/doc/design.md @@ -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 + From 5ab4db7caa4d3c567f77ebd24068ca67fad4cb72 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Tue, 6 Jan 2015 21:54:08 +0800 Subject: [PATCH 13/84] tests --- apps/emqtt/test/emqtt_topic_tests.erl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/emqtt/test/emqtt_topic_tests.erl b/apps/emqtt/test/emqtt_topic_tests.erl index 89e2eeb20..7b949c6b7 100644 --- a/apps/emqtt/test/emqtt_topic_tests.erl +++ b/apps/emqtt/test/emqtt_topic_tests.erl @@ -1,12 +1,13 @@ + -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 +18,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. From 034b1a789f6269387967aafd3519e69c47762dd7 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Wed, 7 Jan 2015 14:22:26 +0800 Subject: [PATCH 14/84] configuration, cluster --- README.md | 55 ++++++++++++++++++++++++++++++++++-- apps/emqtt/include/emqtt.hrl | 7 +++++ doc/protocol.md | 32 +++++++++++++++++++++ 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 doc/protocol.md diff --git a/README.md b/README.md index de4686df3..44bedc77d 100644 --- a/README.md +++ b/README.md @@ -42,12 +42,63 @@ eMQTT requires Erlang R17+. ## Configuration -...... +### etc/app.config -## Admin and Cluster +``` + {emqtt, [ + {auth, {anonymous, []}}, %internal, anonymous + {listen, [ + {mqtt, 1883, [ + {max_conns, 1024}, + {acceptor_pool, 4} + ]}, + {http, 8883, [ + {max_conns, 512}, + {acceptor_pool, 1} + ]} + ]} + ]} + +``` + +### etc/vm.args + +``` + +-sname emqtt + +-setcookie emqtt + +``` + +When nodes clustered, vm.args should be configured as below: + +``` +-name emqtt@host1 +``` ...... +## 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. diff --git a/apps/emqtt/include/emqtt.hrl b/apps/emqtt/include/emqtt.hrl index dbffbe0e4..375705483 100644 --- a/apps/emqtt/include/emqtt.hrl +++ b/apps/emqtt/include/emqtt.hrl @@ -41,6 +41,13 @@ -type qos() :: ?QOS_2 | ?QOS_1 | ?QOS_0. +%%------------------------------------------------------------------------------ +%% MQTT Client +%%------------------------------------------------------------------------------ +-record(mqtt_client, { + client_id +}). + %%------------------------------------------------------------------------------ %% MQTT Message %%------------------------------------------------------------------------------ diff --git a/doc/protocol.md b/doc/protocol.md new file mode 100644 index 000000000..2043706b9 --- /dev/null +++ b/doc/protocol.md @@ -0,0 +1,32 @@ +# 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. + From becd527c2d89b163d2566207c728c12db41b3afa Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Wed, 7 Jan 2015 14:26:27 +0800 Subject: [PATCH 15/84] add TODO --- TODO | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/TODO b/TODO index e7722c346..0b16f9bc4 100644 --- a/TODO +++ b/TODO @@ -26,3 +26,23 @@ 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... + + From c9d3e2d29170a8f4579b56891623249f50f5cc79 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Thu, 8 Jan 2015 14:25:38 +0800 Subject: [PATCH 16/84] mqtt 3.1.1 protocol --- CHANGELOG.md | 5 ++ apps/emqtt/include/emqtt.hrl | 4 +- apps/emqtt/include/emqtt_topic.hrl | 2 +- apps/emqtt/src/emqtt.erl | 2 +- apps/emqtt/src/emqtt_app.erl | 4 +- apps/emqtt/src/emqtt_auth.erl | 4 +- apps/emqtt/src/emqtt_auth_anonymous.erl | 4 +- apps/emqtt/src/emqtt_auth_internal.erl | 4 +- apps/emqtt/src/emqtt_client.erl | 4 +- apps/emqtt/src/emqtt_cm.erl | 4 +- apps/emqtt/src/emqtt_ctl.erl | 4 +- apps/emqtt/src/emqtt_db.erl | 4 +- apps/emqtt/src/emqtt_frame.erl | 2 +- apps/emqtt/src/emqtt_http.erl | 4 +- apps/emqtt/src/emqtt_keep_alive.erl | 4 +- apps/emqtt/src/emqtt_monitor.erl | 4 +- apps/emqtt/src/emqtt_net.erl | 4 +- apps/emqtt/src/emqtt_protocol.erl | 40 +++++++---- apps/emqtt/src/emqtt_pubsub.erl | 4 +- apps/emqtt/src/emqtt_queue.erl | 2 +- apps/emqtt/src/emqtt_queue_sup.erl | 4 +- apps/emqtt/src/emqtt_retained.erl | 4 +- apps/emqtt/src/emqtt_router.erl | 3 +- apps/emqtt/src/emqtt_session.erl | 24 +++++++ apps/emqtt/src/emqtt_sm.erl | 94 +++++++++++++++++++++++++ apps/emqtt/src/emqtt_sup.erl | 4 +- apps/emqtt/src/emqtt_topic.erl | 4 +- data/.placeholder | 1 + doc/protocol.md | 4 ++ 29 files changed, 198 insertions(+), 53 deletions(-) create mode 100644 apps/emqtt/src/emqtt_session.erl create mode 100644 data/.placeholder diff --git a/CHANGELOG.md b/CHANGELOG.md index d2607c88e..dccdaa143 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ eMQTT ChangeLog ================== +TODO: 0.2.1 (2014-12-31) +------------------- + +Pass MQTT 3.1.1 Tests + 0.2.0 (2014-12-07) ------------------- diff --git a/apps/emqtt/include/emqtt.hrl b/apps/emqtt/include/emqtt.hrl index 375705483..6dbe2b20e 100644 --- a/apps/emqtt/include/emqtt.hrl +++ b/apps/emqtt/include/emqtt.hrl @@ -1,5 +1,5 @@ %%------------------------------------------------------------------------------ -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ %% --------------------------------- %% banner %% --------------------------------- --define(COPYRIGHT, "Copyright (C) 2014, Feng Lee"). +-define(COPYRIGHT, "Copyright (C) 2014, Feng Lee"). -define(LICENSE_MESSAGE, "Licensed under MIT"). diff --git a/apps/emqtt/include/emqtt_topic.hrl b/apps/emqtt/include/emqtt_topic.hrl index c31ea8556..8ac0663e9 100644 --- a/apps/emqtt/include/emqtt_topic.hrl +++ b/apps/emqtt/include/emqtt_topic.hrl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt.erl b/apps/emqtt/src/emqtt.erl index 0b57307e0..651f5cad0 100644 --- a/apps/emqtt/src/emqtt.erl +++ b/apps/emqtt/src/emqtt.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_app.erl b/apps/emqtt/src/emqtt_app.erl index 14c3bf29e..e0cb71f80 100644 --- a/apps/emqtt/src/emqtt_app.erl +++ b/apps/emqtt/src/emqtt_app.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ -module(emqtt_app). --author('feng@slimchat.io'). +-author('feng@emqtt.io'). -behaviour(application). diff --git a/apps/emqtt/src/emqtt_auth.erl b/apps/emqtt/src/emqtt_auth.erl index ba3202e0a..10d4513d1 100644 --- a/apps/emqtt/src/emqtt_auth.erl +++ b/apps/emqtt/src/emqtt_auth.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ -module(emqtt_auth). --author('feng@slimchat.io'). +-author('feng@emqtt.io'). -include("emqtt.hrl"). diff --git a/apps/emqtt/src/emqtt_auth_anonymous.erl b/apps/emqtt/src/emqtt_auth_anonymous.erl index a823eeef3..243cb85d8 100644 --- a/apps/emqtt/src/emqtt_auth_anonymous.erl +++ b/apps/emqtt/src/emqtt_auth_anonymous.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ -module(emqtt_auth_anonymous). --author('feng@slimchat.io'). +-author('feng@emqtt.io'). -export([init/1, add/2, diff --git a/apps/emqtt/src/emqtt_auth_internal.erl b/apps/emqtt/src/emqtt_auth_internal.erl index f2dcef1dd..be51159b2 100644 --- a/apps/emqtt/src/emqtt_auth_internal.erl +++ b/apps/emqtt/src/emqtt_auth_internal.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ -module(emqtt_auth_internal). --author('feng@slimchat.io'). +-author('feng@emqtt.io'). -include("emqtt.hrl"). diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index e3e18d1b6..13f91b8ee 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ -module(emqtt_client). --author('feng@slimchat.io'). +-author('feng@emqtt.io'). -behaviour(gen_server). diff --git a/apps/emqtt/src/emqtt_cm.erl b/apps/emqtt/src/emqtt_cm.erl index 0182f71e7..afbc180c6 100644 --- a/apps/emqtt/src/emqtt_cm.erl +++ b/apps/emqtt/src/emqtt_cm.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ %client manager -module(emqtt_cm). --author('feng@slimchat.io'). +-author('feng@emqtt.io'). -behaviour(gen_server). diff --git a/apps/emqtt/src/emqtt_ctl.erl b/apps/emqtt/src/emqtt_ctl.erl index e5f747552..116456923 100644 --- a/apps/emqtt/src/emqtt_ctl.erl +++ b/apps/emqtt/src/emqtt_ctl.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ -module(emqtt_ctl). --author('feng@slimchat.io'). +-author('feng@emqtt.io'). -include("emqtt.hrl"). diff --git a/apps/emqtt/src/emqtt_db.erl b/apps/emqtt/src/emqtt_db.erl index 0b5f508ad..f2316630a 100644 --- a/apps/emqtt/src/emqtt_db.erl +++ b/apps/emqtt/src/emqtt_db.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ -module(emqtt_db). --author('feng@slimchat.io'). +-author('feng@emqtt.io'). -export([init/0, stop/0]). diff --git a/apps/emqtt/src/emqtt_frame.erl b/apps/emqtt/src/emqtt_frame.erl index 660e2c6f8..bd31065db 100644 --- a/apps/emqtt/src/emqtt_frame.erl +++ b/apps/emqtt/src/emqtt_frame.erl @@ -1,5 +1,5 @@ %%------------------------------------------------------------------------------ -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_http.erl b/apps/emqtt/src/emqtt_http.erl index e3bdc4f90..0d2a37b92 100644 --- a/apps/emqtt/src/emqtt_http.erl +++ b/apps/emqtt/src/emqtt_http.erl @@ -1,5 +1,5 @@ %%------------------------------------------------------------------------------ -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ -module(emqtt_http). --author('feng@slimchat.io'). +-author('feng@emqtt.io'). -include("emqtt.hrl"). diff --git a/apps/emqtt/src/emqtt_keep_alive.erl b/apps/emqtt/src/emqtt_keep_alive.erl index 873608fb9..5c360d3c9 100644 --- a/apps/emqtt/src/emqtt_keep_alive.erl +++ b/apps/emqtt/src/emqtt_keep_alive.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ -module(emqtt_keep_alive). --author('feng@slimchat.io'). +-author('feng@emqtt.io'). -export([new/2, state/1, diff --git a/apps/emqtt/src/emqtt_monitor.erl b/apps/emqtt/src/emqtt_monitor.erl index 996600707..d1b18e040 100644 --- a/apps/emqtt/src/emqtt_monitor.erl +++ b/apps/emqtt/src/emqtt_monitor.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ -module(emqtt_monitor). --author('feng@slimchat.io'). +-author('feng@emqtt.io'). -behavior(gen_server). diff --git a/apps/emqtt/src/emqtt_net.erl b/apps/emqtt/src/emqtt_net.erl index e6620c3b7..061956cf6 100644 --- a/apps/emqtt/src/emqtt_net.erl +++ b/apps/emqtt/src/emqtt_net.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ -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]). diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index f2504ace5..d26cf2eac 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -28,6 +28,7 @@ -record(proto_state, { socket, + connected = false, %received CONNECT action? message_id, client_id, clean_sess, @@ -43,8 +44,7 @@ -export([info/1]). --define(FRAME_TYPE(Frame, Type), - Frame = #mqtt_frame{ fixed = #mqtt_frame_fixed{ type = Type }}). +-define(FRAME_TYPE(Type), #mqtt_frame{ fixed = #mqtt_frame_fixed{ type = Type }}). initial_state(Socket) -> #proto_state{ @@ -71,6 +71,23 @@ info(#proto_state{ message_id = MsgId, State :: proto_state(), NewState :: proto_state(). + +%%CONNECT – Client requests a connection to a Server + +%%A Client can only send the CONNECT Packet once over a Network Connection. 369 + +%%First CONNECT +handle_frame(Frame = ?FRAME_TYPE(?CONNECT), State = #proto_state{connected = false}) -> + handle_connect(Frame, State#proto_state{connected = true}); + +%%Sencond CONNECT +handle_frame(?FRAME_TYPE(?CONNECT), State = #proto_state{connected = true}) -> + {error, bad_connect, State}; + +%%Received other packets when CONNECT not arrived. +handle_frame(_Frame, State = #proto_state{connected = false}) -> + {error, no_connected, State}; + handle_frame(Frame = #mqtt_frame{ fixed = #mqtt_frame_fixed{ type = Type }}, State = #proto_state{client_id = ClientId}) -> lager:info("frame from ~s: ~p", [ClientId, Frame]), @@ -81,14 +98,13 @@ handle_frame(Frame = #mqtt_frame{ fixed = #mqtt_frame_fixed{ type = Type }}, {error, Reason, State} end. -handle_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}, State0 = #proto_state{socket = Sock}) -> +handle_connect(#mqtt_frame{ variable = #mqtt_frame_connect{ + username = Username, + password = Password, + proto_ver = ProtoVersion, + clean_sess = CleanSess, + keep_alive = AlivePeriod, + client_id = ClientId } = Var}, State0 = #proto_state{socket = Sock}) -> State = State0#proto_state{client_id = ClientId}, {ReturnCode, State1} = case {lists:member(ProtoVersion, proplists:get_keys(?PROTOCOL_NAMES)), @@ -117,7 +133,7 @@ handle_request(?CONNECT, fixed = #mqtt_frame_fixed{ type = ?CONNACK }, variable = #mqtt_frame_connack{ return_code = ReturnCode }}), - {ok, State1}; + {ok, State1}. handle_request(?PUBLISH, Frame=#mqtt_frame{ fixed = #mqtt_frame_fixed{qos = ?QOS_0}}, State) -> diff --git a/apps/emqtt/src/emqtt_pubsub.erl b/apps/emqtt/src/emqtt_pubsub.erl index a9785deee..0b8c5f372 100644 --- a/apps/emqtt/src/emqtt_pubsub.erl +++ b/apps/emqtt/src/emqtt_pubsub.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ -module(emqtt_pubsub). --author('feng@slimchat.io'). +-author('feng@emqtt.io'). -include("emqtt.hrl"). diff --git a/apps/emqtt/src/emqtt_queue.erl b/apps/emqtt/src/emqtt_queue.erl index da715a542..9f960618d 100644 --- a/apps/emqtt/src/emqtt_queue.erl +++ b/apps/emqtt/src/emqtt_queue.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_queue_sup.erl b/apps/emqtt/src/emqtt_queue_sup.erl index 3cfa25383..9b7b79e10 100644 --- a/apps/emqtt/src/emqtt_queue_sup.erl +++ b/apps/emqtt/src/emqtt_queue_sup.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,7 @@ %%------------------------------------------------------------------------------ -module(emqtt_queue_sup). --author('feng@slimchat.io'). +-author('feng@emqtt.io'). -behavior(supervisor). diff --git a/apps/emqtt/src/emqtt_retained.erl b/apps/emqtt/src/emqtt_retained.erl index 69d643761..fa0d9b7d2 100644 --- a/apps/emqtt/src/emqtt_retained.erl +++ b/apps/emqtt/src/emqtt_retained.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ -module(emqtt_retained). --author('feng@slimchat.io'). +-author('feng@emqtt.io'). %%TODO: FIXME Later... diff --git a/apps/emqtt/src/emqtt_router.erl b/apps/emqtt/src/emqtt_router.erl index 43cc16268..636837370 100644 --- a/apps/emqtt/src/emqtt_router.erl +++ b/apps/emqtt/src/emqtt_router.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -89,3 +89,4 @@ retained(Msg = #mqtt_msg{retain = true, topic = Topic}) -> emqtt_retained:insert(Topic, Msg), Msg; retained(Msg) -> Msg. + diff --git a/apps/emqtt/src/emqtt_session.erl b/apps/emqtt/src/emqtt_session.erl new file mode 100644 index 000000000..35ade693e --- /dev/null +++ b/apps/emqtt/src/emqtt_session.erl @@ -0,0 +1,24 @@ +%%----------------------------------------------------------------------------- +%% Copyright (c) 2015, Feng Lee +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in all +%% copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. +%%------------------------------------------------------------------------------ + +-module(emqtt_session). + diff --git a/apps/emqtt/src/emqtt_sm.erl b/apps/emqtt/src/emqtt_sm.erl index cbcb477d9..92fa1c9d3 100644 --- a/apps/emqtt/src/emqtt_sm.erl +++ b/apps/emqtt/src/emqtt_sm.erl @@ -1,5 +1,99 @@ +%%----------------------------------------------------------------------------- +%% Copyright (c) 2014, Feng Lee +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in all +%% copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. +%%------------------------------------------------------------------------------ + + +%%------------------------------------------------------------------------------ +%% +%% The Session state in the Server consists of: +%% The existence of a Session, even if the rest of the Session state is empty. +%% The Client’s 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). + +%% ------------------------------------------------------------------ +%% API Function Exports +%% ------------------------------------------------------------------ + +-export([start_link/0]). + +-export([create/2, resume/2, destroy/1]). + +%% ------------------------------------------------------------------ +%% 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, [], []). + +create(ClientId, Pid) -> ok. + +resume(ClientId, Pid) -> ok. + +destroy(ClientId) -> ok. + +%% ------------------------------------------------------------------ +%% gen_server Function Definitions +%% ------------------------------------------------------------------ + +init(Args) -> + {ok, Args}. + +handle_call(_Request, _From, State) -> + {reply, ok, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + diff --git a/apps/emqtt/src/emqtt_sup.erl b/apps/emqtt/src/emqtt_sup.erl index de3d053b3..fa3785b52 100644 --- a/apps/emqtt/src/emqtt_sup.erl +++ b/apps/emqtt/src/emqtt_sup.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ -module(emqtt_sup). --author('feng@slimchat.io'). +-author('feng@emqtt.io'). -include("emqtt.hrl"). diff --git a/apps/emqtt/src/emqtt_topic.erl b/apps/emqtt/src/emqtt_topic.erl index 29d9d865d..794a1f8c1 100644 --- a/apps/emqtt/src/emqtt_topic.erl +++ b/apps/emqtt/src/emqtt_topic.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2014, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,7 @@ -module(emqtt_topic). --author('feng@slimchat.io'). +-author('feng@emqtt.io'). -import(lists, [reverse/1]). diff --git a/data/.placeholder b/data/.placeholder new file mode 100644 index 000000000..0fec8a3ed --- /dev/null +++ b/data/.placeholder @@ -0,0 +1 @@ +durable queue data... diff --git a/doc/protocol.md b/doc/protocol.md index 2043706b9..77ba565b3 100644 --- a/doc/protocol.md +++ b/doc/protocol.md @@ -30,3 +30,7 @@ Connection, others can span multiple consecutive Network Connections between a C An expression contained in a Subscription, to indicate an interest in one or more topics. A Topic Filter can include wildcard characters. + +## Packet Identifier + + From 6ff4f59a25121359af0c211241a842d6e057504a Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Sat, 10 Jan 2015 00:00:16 +0800 Subject: [PATCH 17/84] frame -> packet --- LICENSE | 2 +- apps/emqtt/include/emqtt.hrl | 54 ++- apps/emqtt/include/emqtt_frame.hrl | 91 ----- apps/emqtt/include/emqtt_packet.hrl | 134 +++++++ apps/emqtt/include/emqtt_topic.hrl | 15 +- apps/emqtt/src/emqtt.erl | 2 +- apps/emqtt/src/emqtt_app.erl | 2 +- apps/emqtt/src/emqtt_auth.erl | 2 +- apps/emqtt/src/emqtt_auth_anonymous.erl | 2 +- apps/emqtt/src/emqtt_auth_internal.erl | 16 +- apps/emqtt/src/emqtt_client.erl | 86 ++--- apps/emqtt/src/emqtt_cm.erl | 2 +- apps/emqtt/src/emqtt_ctl.erl | 2 +- apps/emqtt/src/emqtt_db.erl | 2 +- apps/emqtt/src/emqtt_http.erl | 7 +- apps/emqtt/src/emqtt_keep_alive.erl | 2 +- apps/emqtt/src/emqtt_monitor.erl | 2 +- apps/emqtt/src/emqtt_net.erl | 2 +- .../src/{emqtt_frame.erl => emqtt_packet.erl} | 185 +++++---- apps/emqtt/src/emqtt_protocol.erl | 357 +++++++++--------- apps/emqtt/src/emqtt_pubsub.erl | 10 +- apps/emqtt/src/emqtt_queue.erl | 2 +- apps/emqtt/src/emqtt_queue_sup.erl | 2 +- apps/emqtt/src/emqtt_retained.erl | 2 +- apps/emqtt/src/emqtt_router.erl | 8 +- apps/emqtt/src/emqtt_sm.erl | 2 +- apps/emqtt/src/emqtt_sup.erl | 2 +- apps/emqtt/src/emqtt_topic.erl | 2 +- 28 files changed, 526 insertions(+), 471 deletions(-) delete mode 100644 apps/emqtt/include/emqtt_frame.hrl create mode 100644 apps/emqtt/include/emqtt_packet.hrl rename apps/emqtt/src/{emqtt_frame.erl => emqtt_packet.erl} (55%) diff --git a/LICENSE b/LICENSE index fe58b384a..287c4750f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014, Feng Lee +Copyright (c) 2012-2015, Feng Lee Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/include/emqtt.hrl b/apps/emqtt/include/emqtt.hrl index 6dbe2b20e..19485c7da 100644 --- a/apps/emqtt/include/emqtt.hrl +++ b/apps/emqtt/include/emqtt.hrl @@ -1,5 +1,5 @@ %%------------------------------------------------------------------------------ -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -20,56 +20,72 @@ %% SOFTWARE. %%------------------------------------------------------------------------------ -%% --------------------------------- -%% banner -%% --------------------------------- --define(COPYRIGHT, "Copyright (C) 2014, Feng Lee"). +%%------------------------------------------------------------------------------ +%% Banner +%%------------------------------------------------------------------------------ +-define(COPYRIGHT, "Copyright (C) 2012-2015, Feng Lee "). -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 + client_id, + username }). +-type mqtt_client() :: #mqtt_client{}. + +%%------------------------------------------------------------------------------ +%% MQTT Session +%%------------------------------------------------------------------------------ +-record(mqtt_session, { + client_id +}). + +-type mqtt_session() :: #mqtt_session{}. + %%------------------------------------------------------------------------------ %% MQTT Message %%------------------------------------------------------------------------------ --record(mqtt_msg, { - retain, - qos, - topic, - dup, - msgid, - payload, - encoder +-record(mqtt_message, { + qos = ?QOS_0 :: mqtt_qos(), + retain = false :: boolean(), + dup = false :: boolean(), + msgid :: integer(), + topic :: binary(), + payload :: binary() }). --type mqtt_msg() :: #mqtt_msg{}. +-type mqtt_message() :: #mqtt_message{}. %%------------------------------------------------------------------------------ %% MQTT User Management %%------------------------------------------------------------------------------ --record(emqtt_user, { +-record(mqtt_user, { username :: binary(), passwdhash :: binary() }). +%%------------------------------------------------------------------------------ +%% MQTT Authorization +%%------------------------------------------------------------------------------ + +%%TODO: ClientId | Username --> Pub | Sub --> Topics diff --git a/apps/emqtt/include/emqtt_frame.hrl b/apps/emqtt/include/emqtt_frame.hrl deleted file mode 100644 index e878681cd..000000000 --- a/apps/emqtt/include/emqtt_frame.hrl +++ /dev/null @@ -1,91 +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(PROTOCOL_NAMES, [{3, <<"MQIsdp">>}, {4, <<"MQTT">>}]). - --define(MQTT_PROTO_MAJOR, 3). --define(MQTT_PROTO_MINOR, 1). - --define(CLIENT_ID_MAXLEN, 1024). - -%% 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}). - diff --git a/apps/emqtt/include/emqtt_packet.hrl b/apps/emqtt/include/emqtt_packet.hrl new file mode 100644 index 000000000..2bbcebc29 --- /dev/null +++ b/apps/emqtt/include/emqtt_packet.hrl @@ -0,0 +1,134 @@ +%%------------------------------------------------------------------------------ +%% Copyright (c) 2015, Feng Lee +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in all +%% copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. +%%------------------------------------------------------------------------------ +%% +%% 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 = fasle :: boolean(), + qos = 0 :: 0 | 1 | 2, + retain = false :: boolean() }). + +%%------------------------------------------------------------------------------ +%% MQTT Packets +%%------------------------------------------------------------------------------ +-record(mqtt_packet_connect, { + proto_ver, + 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{}. + + diff --git a/apps/emqtt/include/emqtt_topic.hrl b/apps/emqtt/include/emqtt_topic.hrl index 8ac0663e9..4cb9e4dda 100644 --- a/apps/emqtt/include/emqtt_topic.hrl +++ b/apps/emqtt/include/emqtt_topic.hrl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -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">>). + diff --git a/apps/emqtt/src/emqtt.erl b/apps/emqtt/src/emqtt.erl index 651f5cad0..06d4b7d96 100644 --- a/apps/emqtt/src/emqtt.erl +++ b/apps/emqtt/src/emqtt.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_app.erl b/apps/emqtt/src/emqtt_app.erl index e0cb71f80..ff3400a56 100644 --- a/apps/emqtt/src/emqtt_app.erl +++ b/apps/emqtt/src/emqtt_app.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_auth.erl b/apps/emqtt/src/emqtt_auth.erl index 10d4513d1..4045c5c1a 100644 --- a/apps/emqtt/src/emqtt_auth.erl +++ b/apps/emqtt/src/emqtt_auth.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_auth_anonymous.erl b/apps/emqtt/src/emqtt_auth_anonymous.erl index 243cb85d8..8bd5e4ad6 100644 --- a/apps/emqtt/src/emqtt_auth_anonymous.erl +++ b/apps/emqtt/src/emqtt_auth_anonymous.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_auth_internal.erl b/apps/emqtt/src/emqtt_auth_internal.erl index be51159b2..de6df9ab1 100644 --- a/apps/emqtt/src/emqtt_auth_internal.erl +++ b/apps/emqtt/src/emqtt_auth_internal.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -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). diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index 13f91b8ee..cc87e8cf2 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -37,14 +37,12 @@ -include("emqtt.hrl"). --include("emqtt_frame.hrl"). - %%Client State... --record(conn_state, { +-record(state, { socket, conn_name, await_recv, - connection_state, + conn_state, conserve, parse_state, proto_state, @@ -62,22 +60,24 @@ go(Pid, Sock) -> init([Sock]) -> io:format("client is created: ~p~n", [self()]), - {ok, #conn_state{socket = Sock}, hibernate}. + {ok, #state{socket = Sock}, hibernate}. -handle_call({go, Sock}, _From, State = #conn_state{socket = Sock}) -> +handle_call({go, Sock}, _From, #state{socket = Sock}) -> {ok, ConnStr} = emqtt_net:connection_string(Sock, inbound), io:format("conn from ~s~n", [ConnStr]), {reply, ok, - control_throttle( - #conn_state{ socket = Sock, - conn_name = ConnStr, - await_recv = false, - connection_state = running, - conserve = false, - parse_state = emqtt_frame:initial_state(), - proto_state = emqtt_protocol:initial_state(Sock)})}; + control_throttle( + #state{ socket = Sock, + conn_name = ConnStr, + await_recv = false, + conn_state = running, + conserve = false, + parse_state = emqtt_packet:initial_state(), + proto_state = emqtt_protocol:initial_state(Sock)})}; -handle_call(info, _From, State = #conn_state{conn_name=ConnName, proto_state = ProtoState}) -> +handle_call(info, _From, State = #state{ + conn_name=ConnName, + proto_state = ProtoState}) -> {reply, [{conn_name, ConnName} | emqtt_protocol:info(ProtoState)], State}; handle_call(Req, _From, State) -> @@ -89,22 +89,22 @@ handle_cast(Msg, State) -> handle_info(timeout, State) -> stop({shutdown, timeout}, State); -handle_info({stop, duplicate_id}, State=#conn_state{conn_name=ConnName}) -> +handle_info({stop, duplicate_id}, State=#state{conn_name=ConnName}) -> %%TODO: %lager:error("Shutdown for duplicate clientid:~s, conn:~s", [ClientId, ConnName]), stop({shutdown, duplicate_id}, State); %%TODO: ok?? -handle_info({dispatch, Msg}, #conn_state{proto_state = ProtoState} = State) -> +handle_info({dispatch, Msg}, #state{proto_state = ProtoState} = State) -> {ok, ProtoState1} = emqtt_protocol:send_message(Msg, ProtoState), - {noreply, State#conn_state{proto_state = ProtoState1}}; + {noreply, State#state{proto_state = ProtoState1}}; handle_info({inet_reply, _Ref, ok}, State) -> {noreply, State, hibernate}; -handle_info({inet_async, Sock, _Ref, {ok, Data}}, #conn_state{ socket = Sock}=State) -> +handle_info({inet_async, Sock, _Ref, {ok, Data}}, #state{ socket = Sock}=State) -> process_received_bytes( - Data, control_throttle(State #conn_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); @@ -113,28 +113,28 @@ handle_info({inet_async, _Sock, _Ref, {error, Reason}}, State) -> handle_info({inet_reply, _Sock, {error, Reason}}, State) -> {noreply, State}; -handle_info(keep_alive_timeout, #conn_state{keep_alive=KeepAlive}=State) -> +handle_info(keep_alive_timeout, #state{keep_alive=KeepAlive}=State) -> case emqtt_keep_alive:state(KeepAlive) of idle -> - lager:info("keep_alive timeout: ~p", [State#conn_state.conn_name]), + lager:info("keep_alive timeout: ~p", [State#state.conn_name]), {stop, normal, State}; active -> KeepAlive1 = emqtt_keep_alive:reset(KeepAlive), - {noreply, State#conn_state{keep_alive=KeepAlive1}} + {noreply, State#state{keep_alive=KeepAlive1}} end; handle_info(Info, State) -> lager:error("badinfo :~p",[Info]), {stop, {badinfo, Info}, State}. -terminate(Reason, #conn_state{proto_state = unefined}) -> +terminate(Reason, #state{proto_state = unefined}) -> io:format("client terminated: ~p, reason: ~p~n", [self(), Reason]), %%TODO: fix keep_alive... %%emqtt_keep_alive:cancel(KeepAlive), %emqtt_protocol:client_terminated(ProtoState), ok; -terminate(_Reason, #conn_state{proto_state = ProtoState}) -> +terminate(_Reason, #state{proto_state = ProtoState}) -> %%TODO: fix keep_alive... %%emqtt_keep_alive:cancel(KeepAlive), emqtt_protocol:client_terminated(ProtoState), @@ -156,28 +156,28 @@ process_received_bytes(<<>>, State) -> {noreply, State, hibernate}; process_received_bytes(Bytes, - State = #conn_state{ parse_state = ParseState, + State = #state{ parse_state = ParseState, proto_state = ProtoState, conn_name = ConnStr }) -> - case emqtt_frame:parse(Bytes, ParseState) of + case emqtt_packet:parse(Bytes, ParseState) of {more, ParseState1} -> {noreply, - control_throttle( State #conn_state{ parse_state = ParseState1 }), + control_throttle( State #state{ parse_state = ParseState1 }), hibernate}; - {ok, Frame, Rest} -> - case emqtt_protocol:handle_frame(Frame, ProtoState) of + {ok, Packet, Rest} -> + case emqtt_protocol:handle_packet(Packet, ProtoState) of {ok, ProtoState1} -> process_received_bytes( Rest, - State#conn_state{ parse_state = emqtt_frame:initial_state(), + 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#conn_state{proto_state = ProtoState1}); + stop({shutdown, Error}, State#state{proto_state = ProtoState1}); {stop, ProtoState1} -> - stop(normal, State#conn_state{proto_state = ProtoState1}) + stop(normal, State#state{proto_state = ProtoState1}) end; {error, Error} -> lager:error("MQTT detected framing error ~p for connection ~p~n", [ConnStr, Error]), @@ -186,27 +186,27 @@ process_received_bytes(Bytes, %%---------------------------------------------------------------------------- network_error(Reason, - State = #conn_state{ conn_name = ConnStr}) -> + State = #state{ conn_name = ConnStr}) -> lager:error("MQTT detected network error '~p' for ~p", [Reason, ConnStr]), %%TODO: where to SEND WILL MSG?? %%send_will_msg(State), % todo: flush channel after publish stop({shutdown, conn_closed}, State). -run_socket(State = #conn_state{ connection_state = blocked }) -> +run_socket(State = #state{ conn_state = blocked }) -> State; -run_socket(State = #conn_state{ await_recv = true }) -> +run_socket(State = #state{ await_recv = true }) -> State; -run_socket(State = #conn_state{ socket = Sock }) -> +run_socket(State = #state{ socket = Sock }) -> async_recv(Sock, 0, infinity), - State#conn_state{ await_recv = true }. + State#state{ await_recv = true }. -control_throttle(State = #conn_state{ connection_state = Flow, +control_throttle(State = #state{ conn_state = Flow, conserve = Conserve }) -> case {Flow, Conserve} of - {running, true} -> State #conn_state{ connection_state = blocked }; - {blocked, false} -> run_socket(State #conn_state{ - connection_state = running }); + {running, true} -> State #state{ conn_state = blocked }; + {blocked, false} -> run_socket(State #state{ + conn_state = running }); {_, _} -> run_socket(State) end. diff --git a/apps/emqtt/src/emqtt_cm.erl b/apps/emqtt/src/emqtt_cm.erl index afbc180c6..64cd451f4 100644 --- a/apps/emqtt/src/emqtt_cm.erl +++ b/apps/emqtt/src/emqtt_cm.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_ctl.erl b/apps/emqtt/src/emqtt_ctl.erl index 116456923..bee4caf5c 100644 --- a/apps/emqtt/src/emqtt_ctl.erl +++ b/apps/emqtt/src/emqtt_ctl.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_db.erl b/apps/emqtt/src/emqtt_db.erl index f2316630a..961556666 100644 --- a/apps/emqtt/src/emqtt_db.erl +++ b/apps/emqtt/src/emqtt_db.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_http.erl b/apps/emqtt/src/emqtt_http.erl index 0d2a37b92..e6aa3ccec 100644 --- a/apps/emqtt/src/emqtt_http.erl +++ b/apps/emqtt/src/emqtt_http.erl @@ -1,5 +1,5 @@ %%------------------------------------------------------------------------------ -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -46,11 +46,8 @@ handle('POST', "/mqtt/publish", Req) -> 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"}); diff --git a/apps/emqtt/src/emqtt_keep_alive.erl b/apps/emqtt/src/emqtt_keep_alive.erl index 5c360d3c9..048f0dde7 100644 --- a/apps/emqtt/src/emqtt_keep_alive.erl +++ b/apps/emqtt/src/emqtt_keep_alive.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_monitor.erl b/apps/emqtt/src/emqtt_monitor.erl index d1b18e040..934aff4fb 100644 --- a/apps/emqtt/src/emqtt_monitor.erl +++ b/apps/emqtt/src/emqtt_monitor.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_net.erl b/apps/emqtt/src/emqtt_net.erl index 061956cf6..949f731cf 100644 --- a/apps/emqtt/src/emqtt_net.erl +++ b/apps/emqtt/src/emqtt_net.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_frame.erl b/apps/emqtt/src/emqtt_packet.erl similarity index 55% rename from apps/emqtt/src/emqtt_frame.erl rename to apps/emqtt/src/emqtt_packet.erl index bd31065db..f5b58e0a7 100644 --- a/apps/emqtt/src/emqtt_frame.erl +++ b/apps/emqtt/src/emqtt_packet.erl @@ -1,5 +1,5 @@ %%------------------------------------------------------------------------------ -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -23,14 +23,14 @@ %% The Original Code is from RabbitMQ. %% --module(emqtt_frame). +-module(emqtt_packet). --include("emqtt_frame.hrl"). +-include("emqtt_packet.hrl"). --export([parse/2, initial_state/0]). --export([serialise/1]). +-export([initial_state/0]). + +-export([parse/2, serialise/1]). --define(RESERVED, 0). -define(MAX_LEN, 16#fffffff). -define(HIGHBIT, 2#10000000). -define(LOWBITS, 2#01111111). @@ -39,30 +39,30 @@ initial_state() -> none. parse(<<>>, none) -> {more, fun(Bin) -> parse(Bin, none) end}; -parse(<>, none) -> - parse_remaining_len(Rest, #mqtt_frame_fixed{ type = MessageType, - dup = bool(Dup), - qos = QoS, - retain = bool(Retain) }); +parse(<>, 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(<<>>, 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(<<>>, 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, _Fixed, _Multiplier, Length) +parse_remaining_len(_Bin, _Header, _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_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_frame_fixed{ type = Type, - qos = Qos } = Fixed, Length) -> +parse_frame(Bin, #mqtt_packet_header{ type = Type, + qos = Qos } = Header, Length) -> case {Type, Bin} of {?CONNECT, <>} -> {ProtoName, Rest1} = parse_utf(FrameBin), @@ -83,8 +83,8 @@ parse_frame(Bin, #mqtt_frame_fixed{ type = Type, {PasssWord, <<>>} = parse_utf(Rest7, PasswordFlag), case protocol_name_approved(ProtoVersion, ProtoName) of true -> - wrap(Fixed, - #mqtt_frame_connect{ + wrap(Header, + #mqtt_packet_connect{ proto_ver = ProtoVersion, will_retain = bool(WillRetain), will_qos = WillQos, @@ -101,41 +101,41 @@ parse_frame(Bin, #mqtt_frame_fixed{ type = Type, end; {?PUBLISH, <>} -> {TopicName, Rest1} = parse_utf(FrameBin), - {MessageId, Payload} = case Qos of + {PacketId, Payload} = case Qos of 0 -> {undefined, Rest1}; - _ -> <> = Rest1, - {M, R} + _ -> <> = Rest1, + {Id, R} end, - wrap(Fixed, #mqtt_frame_publish {topic_name = TopicName, - message_id = MessageId }, + wrap(Header, #mqtt_packet_publish {topic_name = TopicName, + packet_id = PacketId }, Payload, Rest); {?PUBACK, <>} -> - <> = FrameBin, - wrap(Fixed, #mqtt_frame_publish{message_id = MessageId}, Rest); + <> = FrameBin, + wrap(Header, #mqtt_packet_puback{packet_id = PacketId}, Rest); {?PUBREC, <>} -> - <> = FrameBin, - wrap(Fixed, #mqtt_frame_publish{message_id = MessageId}, Rest); + <> = FrameBin, + wrap(Header, #mqtt_packet_puback{packet_id = PacketId}, Rest); {?PUBREL, <>} -> - <> = FrameBin, - wrap(Fixed, #mqtt_frame_publish { message_id = MessageId }, Rest); + <> = FrameBin, + wrap(Header, #mqtt_packet_puback{ packet_id = PacketId }, Rest); {?PUBCOMP, <>} -> - <> = FrameBin, - wrap(Fixed, #mqtt_frame_publish { message_id = MessageId }, Rest); + <> = FrameBin, + wrap(Header, #mqtt_packet_puback{ packet_id = PacketId }, Rest); {Subs, <>} when Subs =:= ?SUBSCRIBE orelse Subs =:= ?UNSUBSCRIBE -> 1 = Qos, - <> = FrameBin, + <> = FrameBin, Topics = parse_topics(Subs, Rest1, []), - wrap(Fixed, #mqtt_frame_subscribe { message_id = MessageId, + wrap(Header, #mqtt_packet_subscribe { packet_id = PacketId, topic_table = Topics }, Rest); {Minimal, Rest} when Minimal =:= ?DISCONNECT orelse Minimal =:= ?PINGREQ -> Length = 0, - wrap(Fixed, Rest); + wrap(Header, Rest); {_, TooShortBin} -> {more, fun(BinMore) -> parse_frame(<>, - Fixed, Length) + Header, Length) end} end. @@ -148,12 +148,12 @@ parse_topics(?UNSUBSCRIBE = Sub, Bin, Topics) -> {Name, <>} = 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}. +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}; @@ -173,72 +173,62 @@ bool(1) -> true. %% serialisation -serialise(#mqtt_frame{ fixed = Fixed, - variable = Variable, - payload = Payload }) -> - serialise_variable(Fixed, Variable, serialise_payload(Payload)). +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_frame_fixed { type = ?CONNACK } = Fixed, - #mqtt_frame_connack { return_code = ReturnCode }, +serialise_variable(#mqtt_packet_header { type = ?CONNACK }, + #mqtt_packet_connack { ack_flags = AckFlags, + return_code = ReturnCode }, <<>> = PayloadBin) -> - VariableBin = <>, - serialise_fixed(Fixed, VariableBin, PayloadBin); + VariableBin = <>, + {VariableBin, PayloadBin}; -serialise_variable(#mqtt_frame_fixed { type = SubAck } = Fixed, - #mqtt_frame_suback { message_id = MessageId, - qos_table = Qos }, +serialise_variable(#mqtt_packet_header { type = SubAck }, + #mqtt_packet_suback { packet_id = PacketId, + qos_table = Qos }, <<>> = _PayloadBin) when SubAck =:= ?SUBACK orelse SubAck =:= ?UNSUBACK -> - VariableBin = <>, - QosBin = << <> || Q <- Qos >>, - serialise_fixed(Fixed, VariableBin, QosBin); + VariableBin = <>, + QosBin = << <> || Q <- Qos >>, + {VariableBin, QosBin}; -serialise_variable(#mqtt_frame_fixed { type = ?PUBLISH, - qos = Qos } = Fixed, - #mqtt_frame_publish { topic_name = TopicName, - message_id = MessageId }, +serialise_variable(#mqtt_packet_header { type = ?PUBLISH, + qos = Qos }, + #mqtt_packet_publish { topic_name = TopicName, + packet_id = PacketId }, PayloadBin) -> TopicBin = serialise_utf(TopicName), - MessageIdBin = case Qos of + PacketIdBin = case Qos of 0 -> <<>>; - 1 -> <>; - 2 -> <> + 1 -> <>; + 2 -> <> end, - serialise_fixed(Fixed, <>, PayloadBin); + {<>, PayloadBin}; -serialise_variable(#mqtt_frame_fixed { type = ?PUBACK } = Fixed, - #mqtt_frame_publish { message_id = MessageId }, - PayloadBin) -> - MessageIdBin = <>, - serialise_fixed(Fixed, MessageIdBin, PayloadBin); +serialise_variable(#mqtt_packet_header { type = PubAck }, + #mqtt_packet_puback { packet_id = PacketId }, + <<>> = _Payload) + when PubAck =:= ?PUBACK; PubAck =:= ?PUBREC; + PubAck =:= ?PUBREL; PubAck =:= ?PUBCOMP -> + {PacketIdBin = <>, <<>>}; -serialise_variable(#mqtt_frame_fixed { type = ?PUBREC } = Fixed, - #mqtt_frame_publish{ message_id = MsgId}, - PayloadBin) -> - serialise_fixed(Fixed, <>, PayloadBin); - -serialise_variable(#mqtt_frame_fixed { type = ?PUBREL } = Fixed, - #mqtt_frame_publish{ message_id = MsgId}, - PayloadBin) -> - serialise_fixed(Fixed, <>, PayloadBin); - -serialise_variable(#mqtt_frame_fixed { type = ?PUBCOMP } = Fixed, - #mqtt_frame_publish{ message_id = MsgId}, - PayloadBin) -> - serialise_fixed(Fixed, <>, PayloadBin); - -serialise_variable(#mqtt_frame_fixed {} = Fixed, +serialise_variable(#mqtt_packet_header { }, undefined, <<>> = _PayloadBin) -> - serialise_fixed(Fixed, <<>>, <<>>). + {<<>>, <<>>}. -serialise_fixed(#mqtt_frame_fixed{ type = Type, - dup = Dup, - qos = Qos, - retain = Retain }, VariableBin, 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), @@ -264,3 +254,4 @@ opt(X) when is_integer(X) -> X. protocol_name_approved(Ver, Name) -> lists:member({Ver, Name}, ?PROTOCOL_NAMES). + diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index d26cf2eac..2f2cd4e76 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -24,87 +24,89 @@ -include("emqtt.hrl"). --include("emqtt_frame.hrl"). +-include("emqtt_packet.hrl"). -record(proto_state, { socket, connected = false, %received CONNECT action? - message_id, + packet_id, client_id, clean_sess, will_msg, + subscriptions, awaiting_ack, - subtopics, awaiting_rel }). -type proto_state() :: #proto_state{}. --export([initial_state/1, handle_frame/2, send_message/2, client_terminated/1]). +-export([initial_state/1]). + +-export([handle_packet/2, send_packet/2, client_terminated/1]). -export([info/1]). --define(FRAME_TYPE(Type), #mqtt_frame{ fixed = #mqtt_frame_fixed{ type = Type }}). +-define(PACKET_TYPE(Packet, Type), + Packet = #mqtt_packet { header = #mqtt_packet_header { type = Type }}). initial_state(Socket) -> #proto_state{ socket = Socket, - message_id = 1, + packet_id = 1, + subscriptions = [], awaiting_ack = gb_trees:empty(), - subtopics = [], awaiting_rel = gb_trees:empty() }. -info(#proto_state{ message_id = MsgId, +info(#proto_state{ packet_id = PacketId, client_id = ClientId, clean_sess = CleanSess, will_msg = WillMsg, - subtopics = SubTopics}) -> - [ {message_id, MsgId}, - {client_id, ClientId}, - {clean_sess, CleanSess}, - {will_msg, WillMsg}, - {subtopics, SubTopics} ]. - --spec handle_frame(Frame, State) -> {ok, NewState} | {error, any()} when - Frame :: #mqtt_frame{}, - State :: proto_state(), - NewState :: proto_state(). + subscriptions= Subs }) -> + [ {packet_id, PacketId}, + {client_id, ClientId}, + {clean_sess, CleanSess}, + {will_msg, WillMsg}, + {subscriptions, Subs} ]. +-spec handle_packet(Packet, State) -> {ok, NewState} | {error, any()} when + Packet :: mqtt_packet(), + State :: proto_state(), + NewState :: proto_state(). %%CONNECT – Client requests a connection to a Server %%A Client can only send the CONNECT Packet once over a Network Connection. 369 +handle_packet(?PACKET_TYPE(Packet, ?CONNECT), State = #proto_state{connected = false}) -> + handle_packet(?CONNECT, Packet, State#proto_state{connected = true}); -%%First CONNECT -handle_frame(Frame = ?FRAME_TYPE(?CONNECT), State = #proto_state{connected = false}) -> - handle_connect(Frame, State#proto_state{connected = true}); - -%%Sencond CONNECT -handle_frame(?FRAME_TYPE(?CONNECT), State = #proto_state{connected = true}) -> - {error, bad_connect, State}; +handle_packet(?PACKET_TYPE(Packet, ?CONNECT), State = #proto_state{connected = true}) -> + {error, protocol_bad_connect, State}; %%Received other packets when CONNECT not arrived. -handle_frame(_Frame, State = #proto_state{connected = false}) -> - {error, no_connected, State}; +handle_packet(_Packet, State = #proto_state{connected = false}) -> + {error, protocol_not_connected, State}; -handle_frame(Frame = #mqtt_frame{ fixed = #mqtt_frame_fixed{ type = Type }}, +handle_packet(?PACKET_TYPE(Packet, Type), State = #proto_state{client_id = ClientId}) -> - lager:info("frame from ~s: ~p", [ClientId, Frame]), - case validate_frame(Type, Frame) of + lager:info("packet from ~s: ~p", [ClientId, Packet]), + case validate_packet(Type, Packet) of ok -> - handle_request(Type, Frame, State); + handle_packet(Type, Packet, State); {error, Reason} -> {error, Reason, State} end. -handle_connect(#mqtt_frame{ variable = #mqtt_frame_connect{ - username = Username, - password = Password, - proto_ver = ProtoVersion, - clean_sess = CleanSess, - keep_alive = AlivePeriod, - client_id = ClientId } = Var}, State0 = #proto_state{socket = Sock}) -> +handle_packet(?CONNECT, #mqtt_packet { + variable = #mqtt_packet_connect { + username = Username, + password = Password, + proto_ver = ProtoVersion, + clean_sess = CleanSess, + keep_alive = AlivePeriod, + client_id = ClientId } = Var }, + State0 = #proto_state{socket = Sock}) -> + State = State0#proto_state{client_id = ClientId}, {ReturnCode, State1} = case {lists:member(ProtoVersion, proplists:get_keys(?PROTOCOL_NAMES)), @@ -129,202 +131,204 @@ handle_connect(#mqtt_frame{ variable = #mqtt_frame_connect{ end end, lager: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}. + send_packet(Sock, #mqtt_packet { + header = #mqtt_packet_header { type = ?CONNACK }, + variable = #mqtt_packet_connack{ return_code = ReturnCode }}), + {ok, State1}; -handle_request(?PUBLISH, Frame=#mqtt_frame{ - fixed = #mqtt_frame_fixed{qos = ?QOS_0}}, State) -> - emqtt_router:route(make_msg(Frame)), +handle_packet(?PUBLISH, Packet = #mqtt_packet { + header = #mqtt_packet_header {qos = ?QOS_0}}, State) -> + emqtt_router:route(make_message(Packet)), {ok, State}; -handle_request(?PUBLISH, - Frame=#mqtt_frame{ - fixed = #mqtt_frame_fixed{qos = ?QOS_1}, - variable = #mqtt_frame_publish{message_id = MsgId}}, +handle_packet(?PUBLISH, Packet = #mqtt_packet { + header = #mqtt_packet_header { qos = ?QOS_1 }, + variable = #mqtt_packet_publish{packet_id = PacketId}}, State=#proto_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}}), + emqtt_router:route(make_message(Packet)), + send_packet(Sock, #mqtt_packet { header = #mqtt_packet_header{ type = ?PUBACK }, + variable = #mqtt_packet_puback { packet_id = PacketId}}), {ok, State}; -handle_request(?PUBLISH, - Frame=#mqtt_frame{ - fixed = #mqtt_frame_fixed{qos = ?QOS_2}, - variable = #mqtt_frame_publish{message_id = MsgId}}, +handle_packet(?PUBLISH, Packet = #mqtt_packet { + header = #mqtt_packet_header { qos = ?QOS_2 }, + variable = #mqtt_packet_publish{packet_id = PacketId}}, State=#proto_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}}), - + %%FIXME: this is not right...should store it first... + emqtt_router:route(make_message(Packet)), + put({msg, PacketId}, pubrec), + send_packet(Sock, #mqtt_packet { header = #mqtt_packet_header{ type = ?PUBREC }, + variable = #mqtt_packet_puback { packet_id = PacketId}}), {ok, State}; -handle_request(?PUBACK, #mqtt_frame{}, State) -> +handle_packet(?PUBACK, #mqtt_packet {}, State) -> + %FIXME Later + {ok, State}; + +handle_packet(?PUBREC, #mqtt_packet { + variable = #mqtt_packet_puback { packet_id = PktId }}, + State=#proto_state{socket=Sock}) -> + %FIXME Later: should release the message here + send_packet(Sock, #mqtt_packet { + header = #mqtt_packet_header { type = ?PUBREL}, + variable = #mqtt_packet_puback { packet_id = PktId}}), + {ok, State}; + +handle_packet(?PUBREL, #mqtt_packet { + variable = #mqtt_packet_puback { packet_id = PktId}}, + State=#proto_state{socket=Sock}) -> + %%FIXME: not right... + erase({msg, PktId}), + send_packet(Sock, #mqtt_packet { header = #mqtt_packet_header { type = ?PUBCOMP}, + variable = #mqtt_packet_puback { packet_id = PktId}}), + {ok, State}; + +handle_packet(?PUBCOMP, #mqtt_packet { + variable = #mqtt_packet_puback{packet_id = _PktId}}, State) -> %TODO: fixme later {ok, State}; -handle_request(?PUBREC, #mqtt_frame{ - variable = #mqtt_frame_publish{message_id = MsgId}}, - State=#proto_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}; - -handle_request(?PUBREL, - #mqtt_frame{ - variable = #mqtt_frame_publish{message_id = MsgId}}, - State=#proto_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}; - -handle_request(?PUBCOMP, #mqtt_frame{ - variable = #mqtt_frame_publish{message_id = _MsgId}}, State) -> - %TODO: fixme later - {ok, State}; - -handle_request(?SUBSCRIBE, - #mqtt_frame{ - variable = #mqtt_frame_subscribe{message_id = MessageId, - topic_table = Topics}, - payload = undefined}, - #proto_state{socket=Sock} = State) -> +handle_packet(?SUBSCRIBE, #mqtt_packet { + variable = #mqtt_packet_subscribe{ + packet_id = PacketId, + topic_table = Topics}, + payload = undefined}, + State = #proto_state{socket=Sock}) -> + %%FIXME: this is not right... [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}}), + send_packet(Sock, #mqtt_packet { header = #mqtt_packet_header { type = ?SUBACK }, + variable = #mqtt_packet_suback{ + packet_id = PacketId, + qos_table = GrantedQos }}), {ok, State}; -handle_request(?UNSUBSCRIBE, - #mqtt_frame{ - variable = #mqtt_frame_subscribe{message_id = MessageId, - topic_table = Topics }, - payload = undefined}, #proto_state{socket = Sock, client_id = ClientId} = State) -> - +handle_packet(?UNSUBSCRIBE, #mqtt_packet { + variable = #mqtt_packet_subscribe{ + packet_id = PacketId, + topic_table = Topics }, + payload = undefined}, + State = #proto_state{socket = Sock, client_id = ClientId}) -> [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 }}), + send_packet(Sock, #mqtt_packet { header = #mqtt_packet_header {type = ?UNSUBACK }, + variable = #mqtt_packet_suback{packet_id = PacketId }}), {ok, State}; -%, keep_alive=KeepAlive -handle_request(?PINGREQ, #mqtt_frame{}, #proto_state{socket=Sock}=State) -> - %Keep alive timer - %%TODO:... - %%KeepAlive1 = emqtt_keep_alive:reset(KeepAlive), - send_frame(Sock, #mqtt_frame{fixed = #mqtt_frame_fixed{ type = ?PINGRESP }}), +handle_packet(?PINGREQ, #mqtt_packet{}, #proto_state{socket=Sock}=State) -> + send_packet(Sock, make_packet(?PINGRESP)), {ok, State}; -handle_request(?DISCONNECT, #mqtt_frame{}, State=#proto_state{client_id=ClientId}) -> +handle_packet(?DISCONNECT, #mqtt_packet{}, State=#proto_state{client_id=ClientId}) -> lager:info("~s disconnected", [ClientId]), {stop, State}. + +make_packet(Type) when Type >= ?CONNECT andalso Type =< ?DISCONNECT -> + #mqtt_packet{ header = #mqtt_packet_header { type = Type } }. + -spec send_message(Message, State) -> {ok, NewState} when - Message :: mqtt_msg(), + Message :: mqtt_message(), State :: proto_state(), NewState :: proto_state(). -send_message(Message, State = #proto_state{socket = Sock, message_id = MsgId}) -> +send_message(Message = #mqtt_message{ + retain = Retain, + qos = Qos, + topic = Topic, + dup = Dup, + payload = Payload}, + State = #proto_state{socket = Sock, packet_id = PacketId}) -> - #mqtt_msg{retain = Retain, - qos = Qos, - topic = Topic, - dup = Dup, - payload = Payload, - encoder = Encoder} = Message, - - 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}, + Packet = #mqtt_packet { + header = #mqtt_packet_header { + type = ?PUBLISH, + qos = Qos, + retain = Retain, + dup = Dup }, + variable = #mqtt_packet_publish { + topic_name = Topic, + packet_id = if + Qos == ?QOS_0 -> undefined; + true -> PacketId + end }, + payload = Payload}, - send_frame(Sock, Frame), + send_packet(Sock, Packet), if Qos == ?QOS_0 -> {ok, State}; true -> - {ok, next_msg_id(State)} + {ok, next_packet_id(State)} end. -send_frame(Sock, Frame) -> - lager:info("send frame:~p", [Frame]), - erlang:port_command(Sock, emqtt_frame:serialise(Frame)). +send_packet(Sock, Packet) -> + lager:info("send packet:~p", [Packet]), + %%FIXME Later... + erlang:port_command(Sock, emqtt_packet:serialise(Packet)). %%TODO: fix me later... client_terminated(#proto_state{client_id = ClientId} = State) -> ok. %emqtt_cm:unregister(ClientId, self()). -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}. +make_message(#mqtt_packet { + header = #mqtt_packet_header{ + qos = Qos, + retain = Retain, + dup = Dup }, + variable = #mqtt_packet_publish{ + topic_name = Topic, + packet_id = PacketId }, + payload = Payload }) -> -make_will_msg(#mqtt_frame_connect{ will_flag = false }) -> + #mqtt_message{ retain = Retain, + qos = Qos, + topic = Topic, + dup = Dup, + msgid = PacketId, + payload = Payload}. + +make_will_msg(#mqtt_packet_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 }. -next_msg_id(State = #proto_state{ message_id = 16#ffff }) -> - State #proto_state{ message_id = 1 }; -next_msg_id(State = #proto_state{ message_id = MsgId }) -> - State #proto_state{ message_id = MsgId + 1 }. +make_will_msg(#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 }. + +next_packet_id(State = #proto_state{ packet_id = 16#ffff }) -> + State #proto_state{ packet_id = 1 }; +next_packet_id(State = #proto_state{ packet_id = PacketId }) -> + State #proto_state{ packet_id = PacketId + 1 }. valid_client_id(ClientId) -> ClientIdLen = size(ClientId), - 1 =< ClientIdLen andalso ClientIdLen =< ?CLIENT_ID_MAXLEN. + 1 =< ClientIdLen andalso ClientIdLen =< ?MAX_CLIENTID_LEN. -validate_frame(?PUBLISH, #mqtt_frame{variable = #mqtt_frame_publish{topic_name = Topic}}) -> +validate_packet(?PUBLISH, #mqtt_packet { + variable = #mqtt_packet_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}}) -> +validate_packet(?UNSUBSCRIBE, #mqtt_packet { + variable = #mqtt_packet_subscribe{ + topic_table = Topics }}) -> ErrTopics = [Topic || #mqtt_topic{name=Topic, qos=Qos} <- Topics, not emqtt_topic:validate({subscribe, Topic})], case ErrTopics of @@ -332,7 +336,7 @@ validate_frame(?UNSUBSCRIBE, #mqtt_frame{variable = #mqtt_frame_subscribe{topic_ _ -> lager:error("error topics: ~p", [ErrTopics]), {error, badtopic} end; -validate_frame(?SUBSCRIBE, #mqtt_frame{variable = #mqtt_frame_subscribe{topic_table = Topics}}) -> +validate_packet(?SUBSCRIBE, #mqtt_packet{variable = #mqtt_packet_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 @@ -340,7 +344,7 @@ validate_frame(?SUBSCRIBE, #mqtt_frame{variable = #mqtt_frame_subscribe{topic_ta _ -> lager:error("error topics: ~p", [ErrTopics]), {error, badtopic} end; -validate_frame(_Type, _Frame) -> +validate_packet(_Type, _Frame) -> ok. maybe_clean_sess(false, _Conn, _ClientId) -> @@ -353,3 +357,4 @@ send_will_msg(#proto_state{will_msg = undefined}) -> ignore; send_will_msg(#proto_state{will_msg = WillMsg }) -> emqtt_router:route(WillMsg). + diff --git a/apps/emqtt/src/emqtt_pubsub.erl b/apps/emqtt/src/emqtt_pubsub.erl index 44be24bbb..f687b7527 100644 --- a/apps/emqtt/src/emqtt_pubsub.erl +++ b/apps/emqtt/src/emqtt_pubsub.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -82,7 +82,7 @@ topics() -> %% %% @doc Subscribe Topic %% --spec subscribe({Topic :: binary(), Qos :: qos()}, SubPid :: pid()) -> any(). +-spec subscribe({Topic :: binary(), Qos :: mqtt_qos()}, SubPid :: pid()) -> any(). subscribe({Topic, Qos}, SubPid) when is_binary(Topic) and is_pid(SubPid) -> gen_server:call(?SERVER, {subscribe, {Topic, Qos}, SubPid}). @@ -96,11 +96,11 @@ unsubscribe(Topic, SubPid) when is_binary(Topic) and is_pid(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 diff --git a/apps/emqtt/src/emqtt_queue.erl b/apps/emqtt/src/emqtt_queue.erl index 9f960618d..bb6e3cb77 100644 --- a/apps/emqtt/src/emqtt_queue.erl +++ b/apps/emqtt/src/emqtt_queue.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_queue_sup.erl b/apps/emqtt/src/emqtt_queue_sup.erl index 9b7b79e10..c49e2e1a8 100644 --- a/apps/emqtt/src/emqtt_queue_sup.erl +++ b/apps/emqtt/src/emqtt_queue_sup.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_retained.erl b/apps/emqtt/src/emqtt_retained.erl index fa0d9b7d2..2db923316 100644 --- a/apps/emqtt/src/emqtt_retained.erl +++ b/apps/emqtt/src/emqtt_retained.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_router.erl b/apps/emqtt/src/emqtt_router.erl index 636837370..9b552e8d3 100644 --- a/apps/emqtt/src/emqtt_router.erl +++ b/apps/emqtt/src/emqtt_router.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -25,8 +25,6 @@ -include("emqtt.hrl"). --include("emqtt_frame.hrl"). - -behaviour(gen_server). -define(SERVER, ?MODULE). @@ -56,7 +54,7 @@ start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). --spec route(Msg :: mqtt_msg()) -> any(). +-spec route(Msg :: mqtt_message()) -> any(). route(Msg) -> emqtt_pubsub:publish(retained(Msg)). @@ -85,7 +83,7 @@ code_change(_OldVsn, State, _Extra) -> %% ------------------------------------------------------------------ %% Internal Function Definitions %% ------------------------------------------------------------------ -retained(Msg = #mqtt_msg{retain = true, topic = Topic}) -> +retained(Msg = #mqtt_message{retain = true, topic = Topic}) -> emqtt_retained:insert(Topic, Msg), Msg; retained(Msg) -> Msg. diff --git a/apps/emqtt/src/emqtt_sm.erl b/apps/emqtt/src/emqtt_sm.erl index 92fa1c9d3..508947725 100644 --- a/apps/emqtt/src/emqtt_sm.erl +++ b/apps/emqtt/src/emqtt_sm.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_sup.erl b/apps/emqtt/src/emqtt_sup.erl index fa3785b52..567f19a58 100644 --- a/apps/emqtt/src/emqtt_sup.erl +++ b/apps/emqtt/src/emqtt_sup.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_topic.erl b/apps/emqtt/src/emqtt_topic.erl index 794a1f8c1..d9ba5d41b 100644 --- a/apps/emqtt/src/emqtt_topic.erl +++ b/apps/emqtt/src/emqtt_topic.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2014, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal From 663646f11313a4f91cd11a98b602a1a9e3880a44 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Sat, 10 Jan 2015 00:00:40 +0800 Subject: [PATCH 18/84] packet tests --- apps/emqtt/test/emqtt_packet_tests.erl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 apps/emqtt/test/emqtt_packet_tests.erl diff --git a/apps/emqtt/test/emqtt_packet_tests.erl b/apps/emqtt/test/emqtt_packet_tests.erl new file mode 100644 index 000000000..de520f777 --- /dev/null +++ b/apps/emqtt/test/emqtt_packet_tests.erl @@ -0,0 +1,14 @@ +-module(emqtt_packet_tests). + +-ifdef(TEST). + +-include_lib("eunit/include/eunit.hrl"). + +decode_test() -> + ok. + +encode_test() -> + ok. + +-endif. + From c6568447627e40c93d2b66ffa71aaa94678d661a Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sat, 10 Jan 2015 00:17:12 +0800 Subject: [PATCH 19/84] comment --- apps/emqtt/test/emqtt_packet_tests.erl | 21 +++++++++++++++++++++ apps/emqtt/test/emqtt_topic_tests.erl | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/apps/emqtt/test/emqtt_packet_tests.erl b/apps/emqtt/test/emqtt_packet_tests.erl index de520f777..f561a86b4 100644 --- a/apps/emqtt/test/emqtt_packet_tests.erl +++ b/apps/emqtt/test/emqtt_packet_tests.erl @@ -1,3 +1,24 @@ +%%----------------------------------------------------------------------------- +%% Copyright (c) 2012-2015, Feng Lee +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in all +%% copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. +%%------------------------------------------------------------------------------ -module(emqtt_packet_tests). -ifdef(TEST). diff --git a/apps/emqtt/test/emqtt_topic_tests.erl b/apps/emqtt/test/emqtt_topic_tests.erl index 7b949c6b7..6c353c4d4 100644 --- a/apps/emqtt/test/emqtt_topic_tests.erl +++ b/apps/emqtt/test/emqtt_topic_tests.erl @@ -1,3 +1,24 @@ +%%----------------------------------------------------------------------------- +%% Copyright (c) 2012-2015, Feng Lee +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in all +%% copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. +%%------------------------------------------------------------------------------ -module(emqtt_topic_tests). From 24d9b46836f691bd68decc9654bb1bbd07703555 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sat, 10 Jan 2015 15:12:42 +0800 Subject: [PATCH 20/84] add throttle, bridge --- apps/emqtt/src/emqtt_auth_internal.erl | 2 +- apps/emqtt/src/emqtt_bridge.erl | 26 +++++++++++++++++++ apps/emqtt/src/emqtt_client.erl | 1 + ...qtt_keep_alive.erl => emqtt_keepalive.erl} | 2 +- apps/emqtt/src/emqtt_packet.erl | 2 +- apps/emqtt/src/emqtt_session.erl | 2 +- apps/emqtt/src/emqtt_throttle.erl | 26 +++++++++++++++++++ 7 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 apps/emqtt/src/emqtt_bridge.erl rename apps/emqtt/src/{emqtt_keep_alive.erl => emqtt_keepalive.erl} (98%) create mode 100644 apps/emqtt/src/emqtt_throttle.erl diff --git a/apps/emqtt/src/emqtt_auth_internal.erl b/apps/emqtt/src/emqtt_auth_internal.erl index de6df9ab1..c0d5a6d10 100644 --- a/apps/emqtt/src/emqtt_auth_internal.erl +++ b/apps/emqtt/src/emqtt_auth_internal.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2015, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_bridge.erl b/apps/emqtt/src/emqtt_bridge.erl new file mode 100644 index 000000000..04df2955d --- /dev/null +++ b/apps/emqtt/src/emqtt_bridge.erl @@ -0,0 +1,26 @@ +%%----------------------------------------------------------------------------- +%% Copyright (c) 2012-2015, Feng Lee +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in all +%% copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. +%%------------------------------------------------------------------------------ + +-module(emqtt_bridge). + + + diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index cc87e8cf2..ade78783c 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -213,3 +213,4 @@ control_throttle(State = #state{ conn_state = Flow, stop(Reason, State ) -> {stop, Reason, State}. + diff --git a/apps/emqtt/src/emqtt_keep_alive.erl b/apps/emqtt/src/emqtt_keepalive.erl similarity index 98% rename from apps/emqtt/src/emqtt_keep_alive.erl rename to apps/emqtt/src/emqtt_keepalive.erl index 048f0dde7..1106f7460 100644 --- a/apps/emqtt/src/emqtt_keep_alive.erl +++ b/apps/emqtt/src/emqtt_keepalive.erl @@ -20,7 +20,7 @@ %% SOFTWARE. %%------------------------------------------------------------------------------ --module(emqtt_keep_alive). +-module(emqtt_keepalive). -author('feng@emqtt.io'). diff --git a/apps/emqtt/src/emqtt_packet.erl b/apps/emqtt/src/emqtt_packet.erl index f5b58e0a7..81d07351a 100644 --- a/apps/emqtt/src/emqtt_packet.erl +++ b/apps/emqtt/src/emqtt_packet.erl @@ -1,5 +1,5 @@ %%------------------------------------------------------------------------------ -%% Copyright (c) 2015, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_session.erl b/apps/emqtt/src/emqtt_session.erl index 35ade693e..145d7fb18 100644 --- a/apps/emqtt/src/emqtt_session.erl +++ b/apps/emqtt/src/emqtt_session.erl @@ -1,5 +1,5 @@ %%----------------------------------------------------------------------------- -%% Copyright (c) 2015, Feng Lee +%% Copyright (c) 2012-2015, Feng Lee %% %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal diff --git a/apps/emqtt/src/emqtt_throttle.erl b/apps/emqtt/src/emqtt_throttle.erl new file mode 100644 index 000000000..1e56cda52 --- /dev/null +++ b/apps/emqtt/src/emqtt_throttle.erl @@ -0,0 +1,26 @@ +%%----------------------------------------------------------------------------- +%% Copyright (c) 2012-2015, Feng Lee +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in all +%% copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. +%%------------------------------------------------------------------------------ + +-module(emqtt_throttle). + + + From 52c3bc962827a21a43c9e74ffa6ab5c3f87b4d5f Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sat, 10 Jan 2015 16:46:11 +0800 Subject: [PATCH 21/84] KeepAlive --- apps/emqtt/src/emqtt_client.erl | 160 +++++++++++++++-------------- apps/emqtt/src/emqtt_keepalive.erl | 71 +++++++------ apps/emqtt/src/emqtt_net.erl | 10 +- apps/emqtt/src/emqtt_packet.erl | 2 +- apps/emqtt/src/emqtt_protocol.erl | 15 +-- 5 files changed, 140 insertions(+), 118 deletions(-) diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index ade78783c..7561c1915 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -29,120 +29,126 @@ -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"). %%Client State... -record(state, { - socket, - conn_name, - await_recv, - conn_state, - conserve, - parse_state, - proto_state, - keep_alive + 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]) -> - io:format("client is created: ~p~n", [self()]), - {ok, #state{socket = Sock}, hibernate}. + {ok, #state{socket = Sock}, 1000}. handle_call({go, Sock}, _From, #state{socket = Sock}) -> + {ok, Peername} = emqtt_net:peer_string(Sock), {ok, ConnStr} = emqtt_net:connection_string(Sock, inbound), - io:format("conn from ~s~n", [ConnStr]), + lager:debug("connection: ~s~n", [ConnStr]), {reply, ok, 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)})}; + proto_state = emqtt_protocol:initial_state(Sock)}), 10000}; handle_call(info, _From, State = #state{ - conn_name=ConnName, - proto_state = ProtoState}) -> - {reply, [{conn_name, ConnName} | emqtt_protocol:info(ProtoState)], State}; + conn_name=ConnName, proto_state = ProtoState}) -> + {reply, [{conn_name, ConnName} | emqtt_protocol:info(ProtoState)], 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}) -> - %%TODO: - %lager:error("Shutdown for duplicate clientid:~s, conn:~s", [ClientId, ConnName]), - stop({shutdown, duplicate_id}, State); + %%TODO: + %lager:error("Shutdown for duplicate clientid:~s, conn:~s", [ClientId, ConnName]), + stop({shutdown, duplicate_id}, State); %%TODO: ok?? -handle_info({dispatch, Msg}, #state{proto_state = ProtoState} = State) -> - {ok, ProtoState1} = emqtt_protocol:send_message(Msg, ProtoState), - {noreply, State#state{proto_state = ProtoState1}}; +handle_info({dispatch, Message}, #state{proto_state = ProtoState} = State) -> + {ok, ProtoState1} = emqtt_protocol:send_message(Message, 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) -> 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); -%%TODO: HOW TO HANDLE THIS?? handle_info({inet_reply, _Sock, {error, Reason}}, State) -> - {noreply, State}; + lager:critical("unexpected inet_reply '~p'", [Reason]), + {noreply, State}; -handle_info(keep_alive_timeout, #state{keep_alive=KeepAlive}=State) -> - case emqtt_keep_alive:state(KeepAlive) of - idle -> - lager: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("~s keepalive started: ~p", [State#state.peer_name, TimeoutSec]), + KeepAlive = emqtt_keepalive:new(Socket, TimeoutSec, {keepalive, timeout}), + {noreply, State#state{ keepalive = KeepAlive }}; + +handle_info({keepalive, timeout}, State = #state { keepalive = KeepAlive }) -> + case emqtt_keepalive:resume(KeepAlive) of + timeout -> + lager:info("~s keepalive timeout!", [State#state.peer_name]), + {stop, normal, State}; + {resumed, KeepAlive1} -> + lager:info("~s keepalive resumed.", [State#state.peer_name]), + {noreply, State#state{ keepalive = KeepAlive1 }} + end; handle_info(Info, State) -> - lager:error("badinfo :~p",[Info]), - {stop, {badinfo, Info}, State}. + lager:error("badinfo :~p",[Info]), + {stop, {badinfo, Info}, State}. terminate(Reason, #state{proto_state = unefined}) -> io:format("client terminated: ~p, reason: ~p~n", [self(), Reason]), - %%TODO: fix keep_alive... - %%emqtt_keep_alive:cancel(KeepAlive), - %emqtt_protocol:client_terminated(ProtoState), - ok; + %%TODO: fix keep_alive... + %%emqtt_keep_alive:cancel(KeepAlive), + %emqtt_protocol:client_terminated(ProtoState), + ok; -terminate(_Reason, #state{proto_state = ProtoState}) -> - %%TODO: fix keep_alive... - %%emqtt_keep_alive:cancel(KeepAlive), - emqtt_protocol:client_terminated(ProtoState), - ok. +terminate(_Reason, #state { keepalive = KeepAlive, proto_state = ProtoState }) -> + %%TODO: fix keep_alive... + emqtt_keepalive:cancel(KeepAlive), + emqtt_protocol:client_terminated(ProtoState), + ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. - + async_recv(Sock, Length, infinity) when is_port(Sock) -> prim_inet:async_recv(Sock, Length, -1); @@ -157,38 +163,38 @@ process_received_bytes(<<>>, State) -> process_received_bytes(Bytes, State = #state{ parse_state = ParseState, - proto_state = ProtoState, + proto_state = ProtoState, conn_name = ConnStr }) -> 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, ProtoState1} -> - stop(normal, 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) + {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, ProtoState1} -> + stop(normal, 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. %%---------------------------------------------------------------------------- network_error(Reason, State = #state{ conn_name = ConnStr}) -> lager:error("MQTT detected network error '~p' for ~p", [Reason, ConnStr]), - %%TODO: where to SEND WILL MSG?? + %%TODO: where to SEND WILL MSG?? %%send_will_msg(State), % todo: flush channel after publish stop({shutdown, conn_closed}, State). diff --git a/apps/emqtt/src/emqtt_keepalive.erl b/apps/emqtt/src/emqtt_keepalive.erl index 1106f7460..eef049a6c 100644 --- a/apps/emqtt/src/emqtt_keepalive.erl +++ b/apps/emqtt/src/emqtt_keepalive.erl @@ -24,43 +24,48 @@ -author('feng@emqtt.io'). --export([new/2, - state/1, - activate/1, - reset/1, - cancel/1]). +-export([new/3, resume/1, cancel/1]). --record(keep_alive, {state, period, timer, msg}). +-record(keepalive, {socket, recv_oct, timeout_sec, timeout_msg, timer_ref}). -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}. +%% +%% @doc create a keepalive. +%% +new(Socket, TimeoutSec, TimeoutMsg) when TimeoutSec > 0 -> + {ok, [{recv_oct, RecvOct}]} = inet:getstate(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 }. -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}. - -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}. +%% +%% @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:getstate(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(KeepAlive=#keep_alive{timer=Timer}) -> - catch erlang:cancel_timer(Timer), - KeepAlive#keep_alive{timer=undefined}. - +cancel(Ref) -> + catch erlang:cancel_timer(Ref). diff --git a/apps/emqtt/src/emqtt_net.erl b/apps/emqtt/src/emqtt_net.erl index 949f731cf..21d98d704 100644 --- a/apps/emqtt/src/emqtt_net.erl +++ b/apps/emqtt/src/emqtt_net.erl @@ -26,7 +26,7 @@ -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}) -> diff --git a/apps/emqtt/src/emqtt_packet.erl b/apps/emqtt/src/emqtt_packet.erl index 81d07351a..820cf50b2 100644 --- a/apps/emqtt/src/emqtt_packet.erl +++ b/apps/emqtt/src/emqtt_packet.erl @@ -217,7 +217,7 @@ serialise_variable(#mqtt_packet_header { type = PubAck }, <<>> = _Payload) when PubAck =:= ?PUBACK; PubAck =:= ?PUBREC; PubAck =:= ?PUBREL; PubAck =:= ?PUBCOMP -> - {PacketIdBin = <>, <<>>}; + {<>, <<>>}; serialise_variable(#mqtt_packet_header { }, undefined, diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 2f2cd4e76..2bc27b39c 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -103,7 +103,7 @@ handle_packet(?CONNECT, #mqtt_packet { password = Password, proto_ver = ProtoVersion, clean_sess = CleanSess, - keep_alive = AlivePeriod, + keep_alive = KeepAlive, client_id = ClientId } = Var }, State0 = #proto_state{socket = Sock}) -> @@ -118,19 +118,18 @@ handle_packet(?CONNECT, #mqtt_packet { _ -> case emqtt_auth:check(Username, Password) of false -> - lager:error_MSG("MQTT login failed - no credentials"), + lager:error("MQTT login failed - no credentials"), {?CONNACK_CREDENTIALS, State}; true -> - lager:info("connect from clientid: ~p, ~p", [ClientId, AlivePeriod]), - %%TODO: - %%KeepAlive = emqtt_keep_alive:new(AlivePeriod*1500, keep_alive_timeout), + lager:info("connect from clientid: ~p, keepalive: ", [ClientId, KeepAlive]), + start_keepalive(KeepAlive), emqtt_cm:register(ClientId, self()), {?CONNACK_ACCEPT, State #proto_state{ will_msg = make_will_msg(Var), client_id = ClientId }} end end, - lager:info("recv conn...:~p", [ReturnCode]), + lager:info("[SENT] MQTT CONNACK: ~p", [ReturnCode]), send_packet(Sock, #mqtt_packet { header = #mqtt_packet_header { type = ?CONNACK }, variable = #mqtt_packet_connack{ return_code = ReturnCode }}), @@ -358,3 +357,7 @@ send_will_msg(#proto_state{will_msg = undefined}) -> send_will_msg(#proto_state{will_msg = WillMsg }) -> emqtt_router:route(WillMsg). +start_keepalive(0) -> ignore; +start_keepalive(Sec) when Sec > 0 -> + self() ! {keepalive, start, Sec * 1.5}. + From b351f7c452cfa6da0cf57507b3a4e14eeecea704 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sat, 10 Jan 2015 16:50:00 +0800 Subject: [PATCH 22/84] fasle -> false --- apps/emqtt/include/emqtt_packet.hrl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqtt/include/emqtt_packet.hrl b/apps/emqtt/include/emqtt_packet.hrl index 2bbcebc29..1f1ea2f3c 100644 --- a/apps/emqtt/include/emqtt_packet.hrl +++ b/apps/emqtt/include/emqtt_packet.hrl @@ -75,7 +75,7 @@ %%------------------------------------------------------------------------------ -record(mqtt_packet_header, { type = ?RESERVED :: mqtt_packet_type(), - dup = fasle :: boolean(), + dup = false :: boolean(), qos = 0 :: 0 | 1 | 2, retain = false :: boolean() }). From 5f0c0df45834eeb0c275e5e021ee1e1405dabdf5 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sat, 10 Jan 2015 16:54:04 +0800 Subject: [PATCH 23/84] getstate -> getstat --- apps/emqtt/src/emqtt_keepalive.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/emqtt/src/emqtt_keepalive.erl b/apps/emqtt/src/emqtt_keepalive.erl index eef049a6c..7dd7be0f8 100644 --- a/apps/emqtt/src/emqtt_keepalive.erl +++ b/apps/emqtt/src/emqtt_keepalive.erl @@ -32,7 +32,7 @@ %% @doc create a keepalive. %% new(Socket, TimeoutSec, TimeoutMsg) when TimeoutSec > 0 -> - {ok, [{recv_oct, RecvOct}]} = inet:getstate(Socket, [recv_oct]), + {ok, [{recv_oct, RecvOct}]} = inet:getstat(Socket, [recv_oct]), Ref = erlang:send_after(TimeoutSec*1000, self(), TimeoutMsg), #keepalive { socket = Socket, recv_oct = RecvOct, @@ -48,7 +48,7 @@ resume(KeepAlive = #keepalive { socket = Socket, timeout_sec = TimeoutSec, timeout_msg = TimeoutMsg, timer_ref = Ref }) -> - {ok, [{recv_oct, NewRecvOct}]} = inet:getstate(Socket, [recv_oct]), + {ok, [{recv_oct, NewRecvOct}]} = inet:getstat(Socket, [recv_oct]), if NewRecvOct =:= RecvOct -> timeout; From ae2c5a4fab96dced6ad0e5af1152f1f1f03be56f Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sat, 10 Jan 2015 16:57:28 +0800 Subject: [PATCH 24/84] round --- apps/emqtt/src/emqtt_protocol.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 2bc27b39c..0e108da8f 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -359,5 +359,5 @@ send_will_msg(#proto_state{will_msg = WillMsg }) -> start_keepalive(0) -> ignore; start_keepalive(Sec) when Sec > 0 -> - self() ! {keepalive, start, Sec * 1.5}. + self() ! {keepalive, start, round(Sec * 1.5)}. From 899569dd34f4a4b67724e459adce7c6538796214 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sat, 10 Jan 2015 17:00:31 +0800 Subject: [PATCH 25/84] fix FORMAT ERROR --- apps/emqtt/src/emqtt_protocol.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 0e108da8f..f7c78a1cc 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -121,7 +121,7 @@ handle_packet(?CONNECT, #mqtt_packet { lager:error("MQTT login failed - no credentials"), {?CONNACK_CREDENTIALS, State}; true -> - lager:info("connect from clientid: ~p, keepalive: ", [ClientId, KeepAlive]), + lager:info("connect from clientid: ~s, keepalive: ~p", [ClientId, KeepAlive]), start_keepalive(KeepAlive), emqtt_cm:register(ClientId, self()), {?CONNACK_ACCEPT, From 8bbce8d0b7981970adebe881633032bb4c8f72fe Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Sat, 10 Jan 2015 23:20:55 +0800 Subject: [PATCH 26/84] dump --- apps/emqtt/include/emqtt_packet.hrl | 1 + apps/emqtt/src/emqtt_client.erl | 2 +- apps/emqtt/src/emqtt_packet.erl | 78 ++++++++++++++++++ apps/emqtt/src/emqtt_protocol.erl | 120 ++++++++++++++-------------- 4 files changed, 141 insertions(+), 60 deletions(-) diff --git a/apps/emqtt/include/emqtt_packet.hrl b/apps/emqtt/include/emqtt_packet.hrl index 1f1ea2f3c..4dbc2c007 100644 --- a/apps/emqtt/include/emqtt_packet.hrl +++ b/apps/emqtt/include/emqtt_packet.hrl @@ -84,6 +84,7 @@ %%------------------------------------------------------------------------------ -record(mqtt_packet_connect, { proto_ver, + proto_name, will_retain, will_qos, will_flag, diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index 7561c1915..a5ceba6f6 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -75,7 +75,7 @@ handle_call({go, Sock}, _From, #state{socket = Sock}) -> conn_state = running, conserve = false, parse_state = emqtt_packet:initial_state(), - proto_state = emqtt_protocol:initial_state(Sock)}), 10000}; + proto_state = emqtt_protocol:initial_state(Sock, Peername)}), 10000}; handle_call(info, _From, State = #state{ conn_name=ConnName, proto_state = ProtoState}) -> diff --git a/apps/emqtt/src/emqtt_packet.erl b/apps/emqtt/src/emqtt_packet.erl index 820cf50b2..d9135843a 100644 --- a/apps/emqtt/src/emqtt_packet.erl +++ b/apps/emqtt/src/emqtt_packet.erl @@ -31,6 +31,8 @@ -export([parse/2, serialise/1]). +-export([dump/1]). + -define(MAX_LEN, 16#fffffff). -define(HIGHBIT, 2#10000000). -define(LOWBITS, 2#01111111). @@ -86,6 +88,7 @@ parse_frame(Bin, #mqtt_packet_header{ type = Type, wrap(Header, #mqtt_packet_connect{ proto_ver = ProtoVersion, + proto_name = ProtoName, will_retain = bool(WillRetain), will_qos = WillQos, will_flag = bool(WillFlag), @@ -255,3 +258,78 @@ 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) -> + io_lib:format("~s(Qos=~p, Retain=~s, Dup=~s, ~s)", [dump_type(Type), QoS, Retain, Dup, S]). + +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} ) -> + io_lib:format("ClientId=~s, ProtoName=~s, ProtoVsn=~p, CleanSess=~s, KeepAlive=~p, Username=~s, Password=~s", + [ClientId, ProtoName, ProtoVer, CleanSess, KeepAlive, Username, Password]); %%TODO: Will + +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]); + +%%TODO: not right... +dump_variable(undefined) -> "". + +dump_variable(Variable, Payload) -> + dump_variable(Variable). + +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". + diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index f7c78a1cc..88a3ff7f6 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -26,9 +26,12 @@ -include("emqtt_packet.hrl"). --record(proto_state, { - socket, - connected = false, %received CONNECT action? +-record(proto_state, { + socket, + peer_name, + connected = false, %received CONNECT action? + proto_vsn, + proto_name, packet_id, client_id, clean_sess, @@ -40,7 +43,7 @@ -type proto_state() :: #proto_state{}. --export([initial_state/1]). +-export([initial_state/2]). -export([handle_packet/2, send_packet/2, client_terminated/1]). @@ -49,24 +52,29 @@ -define(PACKET_TYPE(Packet, Type), Packet = #mqtt_packet { header = #mqtt_packet_header { type = Type }}). -initial_state(Socket) -> +initial_state(Socket, Peername) -> #proto_state{ socket = Socket, + peer_name = Peername, packet_id = 1, subscriptions = [], awaiting_ack = gb_trees:empty(), awaiting_rel = gb_trees:empty() }. -info(#proto_state{ packet_id = PacketId, +info(#proto_state{ proto_vsn = ProtoVsn, + proto_name = ProtoName, + packet_id = PacketId, client_id = ClientId, clean_sess = CleanSess, will_msg = WillMsg, subscriptions= Subs }) -> [ {packet_id, PacketId}, - {client_id, ClientId}, - {clean_sess, CleanSess}, - {will_msg, WillMsg}, + {proto_vsn, ProtoVsn}, + {proto_name, ProtoName}, + {client_id, ClientId}, + {clean_sess, CleanSess}, + {will_msg, WillMsg}, {subscriptions, Subs} ]. -spec handle_packet(Packet, State) -> {ok, NewState} | {error, any()} when @@ -80,7 +88,7 @@ info(#proto_state{ packet_id = PacketId, 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}) -> +handle_packet(?PACKET_TYPE(_Packet, ?CONNECT), State = #proto_state{connected = true}) -> {error, protocol_bad_connect, State}; %%Received other packets when CONNECT not arrived. @@ -88,8 +96,8 @@ handle_packet(_Packet, State = #proto_state{connected = false}) -> {error, protocol_not_connected, State}; handle_packet(?PACKET_TYPE(Packet, Type), - State = #proto_state{client_id = ClientId}) -> - lager:info("packet from ~s: ~p", [ClientId, Packet]), + State = #proto_state { peer_name = PeerName, client_id = ClientId }) -> + lager:info("RECV from ~s/~s: ~s", [PeerName, ClientId, emqtt_packet:dump(Packet)]), case validate_packet(Type, Packet) of ok -> handle_packet(Type, Packet, State); @@ -97,29 +105,28 @@ handle_packet(?PACKET_TYPE(Packet, Type), {error, Reason, State} end. -handle_packet(?CONNECT, #mqtt_packet { - variable = #mqtt_packet_connect { +handle_packet(?CONNECT, Packet = #mqtt_packet { + variable = #mqtt_packet_connect { username = Username, password = Password, proto_ver = ProtoVersion, clean_sess = CleanSess, keep_alive = KeepAlive, client_id = ClientId } = Var }, - State0 = #proto_state{socket = Sock}) -> - - State = State0#proto_state{client_id = ClientId}, + State = #proto_state{ peer_name = PeerName} ) -> + lager:info("RECV from ~s/~s: ~s", [PeerName, ClientId, emqtt_packet:dump(Packet)]), {ReturnCode, State1} = case {lists:member(ProtoVersion, proplists:get_keys(?PROTOCOL_NAMES)), valid_client_id(ClientId)} of {false, _} -> - {?CONNACK_PROTO_VER, State}; + {?CONNACK_PROTO_VER, State#proto_state{client_id = ClientId}}; {_, false} -> - {?CONNACK_INVALID_ID, State}; + {?CONNACK_INVALID_ID, State#proto_state{client_id = ClientId}}; _ -> case emqtt_auth:check(Username, Password) of false -> lager:error("MQTT login failed - no credentials"), - {?CONNACK_CREDENTIALS, State}; + {?CONNACK_CREDENTIALS, State#proto_state{client_id = ClientId}}; true -> lager:info("connect from clientid: ~s, keepalive: ~p", [ClientId, KeepAlive]), start_keepalive(KeepAlive), @@ -129,10 +136,9 @@ handle_packet(?CONNECT, #mqtt_packet { client_id = ClientId }} end end, - lager:info("[SENT] MQTT CONNACK: ~p", [ReturnCode]), - send_packet(Sock, #mqtt_packet { - header = #mqtt_packet_header { type = ?CONNACK }, - variable = #mqtt_packet_connack{ return_code = ReturnCode }}), + send_packet( #mqtt_packet { + header = #mqtt_packet_header { type = ?CONNACK }, + variable = #mqtt_packet_connack{ return_code = ReturnCode }}, State ), {ok, State1}; handle_packet(?PUBLISH, Packet = #mqtt_packet { @@ -143,21 +149,19 @@ handle_packet(?PUBLISH, Packet = #mqtt_packet { handle_packet(?PUBLISH, Packet = #mqtt_packet { header = #mqtt_packet_header { qos = ?QOS_1 }, variable = #mqtt_packet_publish{packet_id = PacketId}}, - State=#proto_state{socket=Sock}) -> + State) -> emqtt_router:route(make_message(Packet)), - send_packet(Sock, #mqtt_packet { header = #mqtt_packet_header{ type = ?PUBACK }, - variable = #mqtt_packet_puback { packet_id = PacketId}}), + send_packet( make_packet(?PUBACK, PacketId), State ), {ok, State}; handle_packet(?PUBLISH, Packet = #mqtt_packet { header = #mqtt_packet_header { qos = ?QOS_2 }, variable = #mqtt_packet_publish{packet_id = PacketId}}, - State=#proto_state{socket=Sock}) -> + State) -> %%FIXME: this is not right...should store it first... emqtt_router:route(make_message(Packet)), put({msg, PacketId}, pubrec), - send_packet(Sock, #mqtt_packet { header = #mqtt_packet_header{ type = ?PUBREC }, - variable = #mqtt_packet_puback { packet_id = PacketId}}), + send_packet( make_packet(?PUBREC, PacketId), State ), {ok, State}; handle_packet(?PUBACK, #mqtt_packet {}, State) -> @@ -165,25 +169,20 @@ handle_packet(?PUBACK, #mqtt_packet {}, State) -> {ok, State}; handle_packet(?PUBREC, #mqtt_packet { - variable = #mqtt_packet_puback { packet_id = PktId }}, - State=#proto_state{socket=Sock}) -> + variable = #mqtt_packet_puback { packet_id = PacketId }}, + State) -> %FIXME Later: should release the message here - send_packet(Sock, #mqtt_packet { - header = #mqtt_packet_header { type = ?PUBREL}, - variable = #mqtt_packet_puback { packet_id = PktId}}), + send_packet( make_packet(?PUBREL, PacketId), State ), {ok, State}; -handle_packet(?PUBREL, #mqtt_packet { - variable = #mqtt_packet_puback { packet_id = PktId}}, - State=#proto_state{socket=Sock}) -> +handle_packet(?PUBREL, #mqtt_packet { variable = #mqtt_packet_puback { packet_id = PacketId}}, State) -> %%FIXME: not right... - erase({msg, PktId}), - send_packet(Sock, #mqtt_packet { header = #mqtt_packet_header { type = ?PUBCOMP}, - variable = #mqtt_packet_puback { packet_id = PktId}}), + erase({msg, PacketId}), + send_packet( make_packet(?PUBCOMP, PacketId), State ), {ok, State}; handle_packet(?PUBCOMP, #mqtt_packet { - variable = #mqtt_packet_puback{packet_id = _PktId}}, State) -> + variable = #mqtt_packet_puback{packet_id = _PacketId}}, State) -> %TODO: fixme later {ok, State}; @@ -192,7 +191,7 @@ handle_packet(?SUBSCRIBE, #mqtt_packet { packet_id = PacketId, topic_table = Topics}, payload = undefined}, - State = #proto_state{socket=Sock}) -> + State) -> %%FIXME: this is not right... [emqtt_pubsub:subscribe({Name, Qos}, self()) || @@ -200,10 +199,10 @@ handle_packet(?SUBSCRIBE, #mqtt_packet { GrantedQos = [Qos || #mqtt_topic{qos=Qos} <- Topics], - send_packet(Sock, #mqtt_packet { header = #mqtt_packet_header { type = ?SUBACK }, - variable = #mqtt_packet_suback{ - packet_id = PacketId, - qos_table = GrantedQos }}), + send_packet(#mqtt_packet { header = #mqtt_packet_header { type = ?SUBACK }, + variable = #mqtt_packet_suback{ + packet_id = PacketId, + qos_table = GrantedQos }}, State), {ok, State}; @@ -212,27 +211,30 @@ handle_packet(?UNSUBSCRIBE, #mqtt_packet { packet_id = PacketId, topic_table = Topics }, payload = undefined}, - State = #proto_state{socket = Sock, client_id = ClientId}) -> + State = #proto_state{client_id = ClientId}) -> [emqtt_pubsub:unsubscribe(Name, self()) || #mqtt_topic{name=Name} <- Topics], - send_packet(Sock, #mqtt_packet { header = #mqtt_packet_header {type = ?UNSUBACK }, - variable = #mqtt_packet_suback{packet_id = PacketId }}), + send_packet(#mqtt_packet { header = #mqtt_packet_header {type = ?UNSUBACK }, + variable = #mqtt_packet_suback{packet_id = PacketId }}, State), {ok, State}; -handle_packet(?PINGREQ, #mqtt_packet{}, #proto_state{socket=Sock}=State) -> - send_packet(Sock, make_packet(?PINGRESP)), +handle_packet(?PINGREQ, #mqtt_packet{}, State) -> + send_packet(make_packet(?PINGRESP), State), {ok, State}; -handle_packet(?DISCONNECT, #mqtt_packet{}, State=#proto_state{client_id=ClientId}) -> - lager:info("~s disconnected", [ClientId]), +handle_packet(?DISCONNECT, #mqtt_packet{}, State=#proto_state{peer_name = PeerName, client_id = ClientId}) -> + lager:info("RECV from ~s/~s: DISCONNECT", [PeerName, ClientId]), {stop, State}. - 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 =< ?PUBREC -> + #mqtt_packet { header = #mqtt_packet_header { type = PubAck}, + variable = #mqtt_packet_puback { packet_id = PacketId}}. + -spec send_message(Message, State) -> {ok, NewState} when Message :: mqtt_message(), State :: proto_state(), @@ -244,7 +246,7 @@ send_message(Message = #mqtt_message{ topic = Topic, dup = Dup, payload = Payload}, - State = #proto_state{socket = Sock, packet_id = PacketId}) -> + State = #proto_state{packet_id = PacketId}) -> Packet = #mqtt_packet { header = #mqtt_packet_header { @@ -260,7 +262,7 @@ send_message(Message = #mqtt_message{ end }, payload = Payload}, - send_packet(Sock, Packet), + send_packet(Packet, State), if Qos == ?QOS_0 -> {ok, State}; @@ -268,8 +270,8 @@ send_message(Message = #mqtt_message{ {ok, next_packet_id(State)} end. -send_packet(Sock, Packet) -> - lager:info("send packet:~p", [Packet]), +send_packet(Packet, #proto_state{socket = Sock, peer_name = PeerName, client_id = ClientId}) -> + lager:info("SENT to ~s/~s: ~p", [PeerName, ClientId, emqtt_packet:dump(Packet)]), %%FIXME Later... erlang:port_command(Sock, emqtt_packet:serialise(Packet)). From 4f0178ab91cdcabe0287e9c21d5010a8a6849114 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sun, 11 Jan 2015 00:19:33 +0800 Subject: [PATCH 27/84] fix dump --- apps/emqtt/src/emqtt_packet.erl | 16 ++++++++++++---- apps/emqtt/src/emqtt_protocol.erl | 8 +++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/apps/emqtt/src/emqtt_packet.erl b/apps/emqtt/src/emqtt_packet.erl index d9135843a..f00f3cd29 100644 --- a/apps/emqtt/src/emqtt_packet.erl +++ b/apps/emqtt/src/emqtt_packet.erl @@ -266,7 +266,12 @@ 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) -> - io_lib:format("~s(Qos=~p, Retain=~s, Dup=~s, ~s)", [dump_type(Type), QoS, Retain, Dup, 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, @@ -312,11 +317,14 @@ dump_variable( #mqtt_packet_suback { dump_variable(PacketId) when is_integer(PacketId) -> io_lib:format("PacketId=~p", [PacketId]); -%%TODO: not right... -dump_variable(undefined) -> "". +dump_variable(undefined) -> undefined. +dump_variable(undefined, undefined) -> + undefined; +dump_variable(undefined, <<>>) -> + undefined; dump_variable(Variable, Payload) -> - dump_variable(Variable). + io_lib:format("~s, Payload=~p", [dump_variable(Variable), Payload]). dump_type(?CONNECT) -> "CONNECT"; dump_type(?CONNACK) -> "CONNACK"; diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 88a3ff7f6..432fc1bc4 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -97,7 +97,7 @@ handle_packet(_Packet, State = #proto_state{connected = false}) -> handle_packet(?PACKET_TYPE(Packet, Type), State = #proto_state { peer_name = PeerName, client_id = ClientId }) -> - lager:info("RECV from ~s/~s: ~s", [PeerName, ClientId, emqtt_packet:dump(Packet)]), + lager:info("RECV from ~s@~s: ~s", [ClientId, PeerName, emqtt_packet:dump(Packet)]), case validate_packet(Type, Packet) of ok -> handle_packet(Type, Packet, State); @@ -114,7 +114,7 @@ handle_packet(?CONNECT, Packet = #mqtt_packet { keep_alive = KeepAlive, client_id = ClientId } = Var }, State = #proto_state{ peer_name = PeerName} ) -> - lager:info("RECV from ~s/~s: ~s", [PeerName, ClientId, emqtt_packet:dump(Packet)]), + lager:info("RECV from ~s@~s: ~s", [ClientId, PeerName, emqtt_packet:dump(Packet)]), {ReturnCode, State1} = case {lists:member(ProtoVersion, proplists:get_keys(?PROTOCOL_NAMES)), valid_client_id(ClientId)} of @@ -128,7 +128,6 @@ handle_packet(?CONNECT, Packet = #mqtt_packet { lager:error("MQTT login failed - no credentials"), {?CONNACK_CREDENTIALS, State#proto_state{client_id = ClientId}}; true -> - lager:info("connect from clientid: ~s, keepalive: ~p", [ClientId, KeepAlive]), start_keepalive(KeepAlive), emqtt_cm:register(ClientId, self()), {?CONNACK_ACCEPT, @@ -225,7 +224,6 @@ handle_packet(?PINGREQ, #mqtt_packet{}, State) -> {ok, State}; handle_packet(?DISCONNECT, #mqtt_packet{}, State=#proto_state{peer_name = PeerName, client_id = ClientId}) -> - lager:info("RECV from ~s/~s: DISCONNECT", [PeerName, ClientId]), {stop, State}. make_packet(Type) when Type >= ?CONNECT andalso Type =< ?DISCONNECT -> @@ -271,7 +269,7 @@ send_message(Message = #mqtt_message{ end. send_packet(Packet, #proto_state{socket = Sock, peer_name = PeerName, client_id = ClientId}) -> - lager:info("SENT to ~s/~s: ~p", [PeerName, ClientId, emqtt_packet:dump(Packet)]), + lager:info("SENT to ~s@~s: ~s", [ClientId, PeerName, emqtt_packet:dump(Packet)]), %%FIXME Later... erlang:port_command(Sock, emqtt_packet:serialise(Packet)). From a46b0f6a75b8163f2bbfa7675997faae6dc7cf54 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sun, 11 Jan 2015 00:20:34 +0800 Subject: [PATCH 28/84] info conn --- apps/emqtt/src/emqtt_client.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index a5ceba6f6..eccfa8f3d 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -65,7 +65,7 @@ init([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:debug("connection: ~s~n", [ConnStr]), + lager:info("Connect from ~s", [ConnStr]), {reply, ok, control_throttle( #state{ socket = Sock, From 5d6a70afb2feff677b09a54c508e5a6d8cea6711 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sun, 11 Jan 2015 00:26:58 +0800 Subject: [PATCH 29/84] send connack with State1 --- apps/emqtt/src/emqtt_protocol.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 432fc1bc4..6d512438a 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -137,7 +137,7 @@ handle_packet(?CONNECT, Packet = #mqtt_packet { end, send_packet( #mqtt_packet { header = #mqtt_packet_header { type = ?CONNACK }, - variable = #mqtt_packet_connack{ return_code = ReturnCode }}, State ), + variable = #mqtt_packet_connack{ return_code = ReturnCode }}, State1 ), {ok, State1}; handle_packet(?PUBLISH, Packet = #mqtt_packet { From 2deed75c26f843c0fa593c481cd860ba7b3f91aa Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sun, 11 Jan 2015 00:34:27 +0800 Subject: [PATCH 30/84] dump keepalive --- apps/emqtt/src/emqtt_client.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index eccfa8f3d..390f66926 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -115,14 +115,14 @@ handle_info({inet_reply, _Sock, {error, Reason}}, State) -> {noreply, State}; handle_info({keepalive, start, TimeoutSec}, State = #state{socket = Socket}) -> - lager:info("~s keepalive started: ~p", [State#state.peer_name, TimeoutSec]), + 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({keepalive, timeout}, State = #state { keepalive = KeepAlive }) -> case emqtt_keepalive:resume(KeepAlive) of timeout -> - lager:info("~s keepalive timeout!", [State#state.peer_name]), + lager:info("Client ~s: Keepalive Timeout!", [State#state.peer_name]), {stop, normal, State}; {resumed, KeepAlive1} -> lager:info("~s keepalive resumed.", [State#state.peer_name]), From efa1ecc5f4ab0121d13ee64e94b44e1e3e17263b Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sun, 11 Jan 2015 00:40:59 +0800 Subject: [PATCH 31/84] export send_message --- apps/emqtt/src/emqtt_protocol.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 6d512438a..b794bb9fe 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -45,7 +45,7 @@ -export([initial_state/2]). --export([handle_packet/2, send_packet/2, client_terminated/1]). +-export([handle_packet/2, send_message/2, send_packet/2, client_terminated/1]). -export([info/1]). From b746e57cdbbb5a8a3e65b76213778d4975178afb Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sun, 11 Jan 2015 00:44:28 +0800 Subject: [PATCH 32/84] keepalive resumed --- apps/emqtt/src/emqtt_client.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index 390f66926..c74d1c606 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -125,7 +125,7 @@ handle_info({keepalive, timeout}, State = #state { keepalive = KeepAlive }) -> lager:info("Client ~s: Keepalive Timeout!", [State#state.peer_name]), {stop, normal, State}; {resumed, KeepAlive1} -> - lager:info("~s keepalive resumed.", [State#state.peer_name]), + lager:info("Client ~s: Keepalive Resumed", [State#state.peer_name]), {noreply, State#state{ keepalive = KeepAlive1 }} end; From 6c2a5c0b96d4eb44aed1b08efc2714c2393c3a25 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Sun, 11 Jan 2015 10:57:46 +0800 Subject: [PATCH 33/84] dump will --- apps/emqtt/src/emqtt_packet.erl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/emqtt/src/emqtt_packet.erl b/apps/emqtt/src/emqtt_packet.erl index f00f3cd29..76e388a78 100644 --- a/apps/emqtt/src/emqtt_packet.erl +++ b/apps/emqtt/src/emqtt_packet.erl @@ -286,8 +286,14 @@ dump_variable( #mqtt_packet_connect { will_msg = WillMsg, username = Username, password = Password} ) -> - io_lib:format("ClientId=~s, ProtoName=~s, ProtoVsn=~p, CleanSess=~s, KeepAlive=~p, Username=~s, Password=~s", - [ClientId, ProtoName, ProtoVer, CleanSess, KeepAlive, Username, Password]); %%TODO: Will + 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, @@ -326,6 +332,9 @@ dump_variable(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"; From 3e7452dac274777f49c69b875ddd6182ab873839 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Sun, 11 Jan 2015 13:37:55 +0800 Subject: [PATCH 34/84] improve dump --- apps/emqtt/src/emqtt_app.erl | 4 +- apps/emqtt/src/emqtt_client.erl | 3 +- apps/emqtt/src/emqtt_config.erl | 82 ++++++++++++++++++++++++++ apps/emqtt/src/emqtt_protocol.erl | 4 +- apps/emqtt/test/emqtt_packet_tests.erl | 48 ++++++++++++++- 5 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 apps/emqtt/src/emqtt_config.erl diff --git a/apps/emqtt/src/emqtt_app.erl b/apps/emqtt/src/emqtt_app.erl index ff3400a56..abf59a323 100644 --- a/apps/emqtt/src/emqtt_app.erl +++ b/apps/emqtt/src/emqtt_app.erl @@ -73,7 +73,9 @@ start_servers(Sup) -> start_child(Sup, Server, Opts), ?PRINT_MSG("[done]~n") end, - [{"emqtt cm", emqtt_cm}, + [{"emqtt config", emqtt_config}, + {"emqtt client manager", emqtt_cm}, + {"emqtt session manager", emqtt_sm}, {"emqtt auth", emqtt_auth}, {"emqtt retained", emqtt_retained}, {"emqtt pubsub", emqtt_pubsub}, diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index c74d1c606..a00a52e85 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -103,7 +103,8 @@ handle_info({dispatch, Message}, #state{proto_state = ProtoState} = State) -> 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{ peer_name = PeerName, socket = Sock } = State) -> + lager:debug("RECV from ~s: ~p", [State#state.peer_name, Data]), process_received_bytes( Data, control_throttle(State #state{ await_recv = false })); diff --git a/apps/emqtt/src/emqtt_config.erl b/apps/emqtt/src/emqtt_config.erl new file mode 100644 index 000000000..0a18e922f --- /dev/null +++ b/apps/emqtt/src/emqtt_config.erl @@ -0,0 +1,82 @@ +%%----------------------------------------------------------------------------- +%% Copyright (c) 2012-2015, Feng Lee +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in all +%% copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. +%%------------------------------------------------------------------------------ + +-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 +%% ------------------------------------------------------------------ + diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index b794bb9fe..51c255990 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -270,8 +270,10 @@ send_message(Message = #mqtt_message{ send_packet(Packet, #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, emqtt_packet:serialise(Packet)). + erlang:port_command(Sock, Data). %%TODO: fix me later... client_terminated(#proto_state{client_id = ClientId} = State) -> diff --git a/apps/emqtt/test/emqtt_packet_tests.erl b/apps/emqtt/test/emqtt_packet_tests.erl index f561a86b4..b9e516c91 100644 --- a/apps/emqtt/test/emqtt_packet_tests.erl +++ b/apps/emqtt/test/emqtt_packet_tests.erl @@ -21,14 +21,58 @@ %%------------------------------------------------------------------------------ -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"). -decode_test() -> +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>>, + %% 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{}, <<>>}, parse(V31ConnBin, State)), + ?assertMatch({ok, #mqtt_packet{}, <<>>}, parse(V311ConnBin, 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>>, ok. -encode_test() -> +parse_publish_test() -> + %%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>>, + %PUBLISH(Qos=0, Retain=false, Dup=false, TopicName=xxx/yyy, PacketId=undefined, Payload=<<"hello">>) + %DISCONNECT(Qos=0, Retain=false, Dup=false) + PubBin1 = <<48,14,0,7,120,120,120,47,121,121,121,104,101,108,108,111,224,0>>, + ok. + +parse_puback_test() -> + %%PUBACK(Qos=0, Retain=false, Dup=false, PacketId=1) + PubAckBin = <<64,2,0,1>>, + ok. + +parse_subscribe_test() -> + ok. + +parse_pingreq_test() -> + ok. + +parse_disconnect_test() -> + %DISCONNECT(Qos=0, Retain=false, Dup=false) + Bin = <<224, 0>>, + ok. + +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. From 68eb2259810dbc4c5cbf32e79379b74e60849904 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sun, 11 Jan 2015 23:27:20 +0800 Subject: [PATCH 35/84] add tests --- .gitmodules | 3 +++ tests/benchmarks/.placeholder | 0 tests/org.eclipse.paho.mqtt.testing | 1 + 3 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 100644 tests/benchmarks/.placeholder create mode 160000 tests/org.eclipse.paho.mqtt.testing diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..61767a9fb --- /dev/null +++ b/.gitmodules @@ -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 diff --git a/tests/benchmarks/.placeholder b/tests/benchmarks/.placeholder new file mode 100644 index 000000000..e69de29bb diff --git a/tests/org.eclipse.paho.mqtt.testing b/tests/org.eclipse.paho.mqtt.testing new file mode 160000 index 000000000..17afdf7fb --- /dev/null +++ b/tests/org.eclipse.paho.mqtt.testing @@ -0,0 +1 @@ +Subproject commit 17afdf7fb8e148f376d63592bbf3dd4ccdf19e84 From 3ca3552452862fa34847c2035143d4ccb4bf37f9 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sun, 11 Jan 2015 23:29:50 +0800 Subject: [PATCH 36/84] fix {function_clause,[{emqtt_protocol,make_packet, [7,6] --- apps/emqtt/src/emqtt_protocol.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index b794bb9fe..e0eab9001 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -229,7 +229,7 @@ handle_packet(?DISCONNECT, #mqtt_packet{}, State=#proto_state{peer_name = PeerNa 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 =< ?PUBREC -> +make_packet(PubAck, PacketId) when PubAck >= ?PUBACK andalso PubAck =< ?PUBCOMP -> #mqtt_packet { header = #mqtt_packet_header { type = PubAck}, variable = #mqtt_packet_puback { packet_id = PacketId}}. From 161754871f0c77f10840067e60cab9bf3a803050 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sun, 11 Jan 2015 23:53:14 +0800 Subject: [PATCH 37/84] issue#33: QoS of PUBREL packet should be 1 --- apps/emqtt/src/emqtt_protocol.erl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index e0eab9001..170234791 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -230,9 +230,14 @@ 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}, + #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; + -spec send_message(Message, State) -> {ok, NewState} when Message :: mqtt_message(), State :: proto_state(), From 28c0aeabd7a758e7c96ce3143624fb542fbe65cb Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sun, 11 Jan 2015 23:54:41 +0800 Subject: [PATCH 38/84] compile error --- apps/emqtt/src/emqtt_client.erl | 8 +++++--- apps/emqtt/src/emqtt_protocol.erl | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index c74d1c606..da9ec98a4 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -90,9 +90,11 @@ handle_cast(Msg, State) -> handle_info(timeout, State) -> stop({shutdown, timeout}, State); -handle_info({stop, duplicate_id}, State=#state{conn_name=ConnName}) -> - %%TODO: - %lager:error("Shutdown for duplicate clientid:~s, conn:~s", [ClientId, ConnName]), +handle_info({stop, duplicate_id, NewPid}, State=#state{conn_name=ConnName}) -> + %% TODO: to... + %% need transfer data??? + %% emqtt_client:transfer(NewPid, Data), + %% lager:error("Shutdown for duplicate clientid:~s, conn:~s", [ClientId, ConnName]), stop({shutdown, duplicate_id}, State); %%TODO: ok?? diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 170234791..5230bf596 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -236,7 +236,7 @@ make_packet(PubAck, PacketId) when PubAck >= ?PUBACK andalso PubAck =< ?PUBCOMP puback_qos(?PUBACK) -> ?QOS_0; puback_qos(?PUBREC) -> ?QOS_0; puback_qos(?PUBREL) -> ?QOS_1; -puback_qos(?PUBCOMP) -> ?QOS_0; +puback_qos(?PUBCOMP) -> ?QOS_0. -spec send_message(Message, State) -> {ok, NewState} when Message :: mqtt_message(), From 4e14d326e2d503ce475a02e07d7bda8b56c0130a Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Mon, 12 Jan 2015 01:26:17 +0800 Subject: [PATCH 39/84] 1 = Qos when PUBREL received --- apps/emqtt/src/emqtt_packet.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/emqtt/src/emqtt_packet.erl b/apps/emqtt/src/emqtt_packet.erl index f00f3cd29..723064688 100644 --- a/apps/emqtt/src/emqtt_packet.erl +++ b/apps/emqtt/src/emqtt_packet.erl @@ -119,6 +119,7 @@ parse_frame(Bin, #mqtt_packet_header{ type = Type, <> = FrameBin, wrap(Header, #mqtt_packet_puback{packet_id = PacketId}, Rest); {?PUBREL, <>} -> + 1 = Qos, <> = FrameBin, wrap(Header, #mqtt_packet_puback{ packet_id = PacketId }, Rest); {?PUBCOMP, <>} -> From 66fab96e1a75bad23095811f1866941be5ed263d Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Mon, 12 Jan 2015 12:58:04 +0800 Subject: [PATCH 40/84] packet tests --- apps/emqtt/test/emqtt_packet_tests.erl | 69 ++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/apps/emqtt/test/emqtt_packet_tests.erl b/apps/emqtt/test/emqtt_packet_tests.erl index b9e516c91..e1ade7ce0 100644 --- a/apps/emqtt/test/emqtt_packet_tests.erl +++ b/apps/emqtt/test/emqtt_packet_tests.erl @@ -33,27 +33,90 @@ 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{}, <<>>}, parse(V31ConnBin, State)), - ?assertMatch({ok, #mqtt_packet{}, <<>>}, parse(V311ConnBin, State)), + ?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=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 = 4, + proto_name = <<"MQTT">>, + client_id = <<"mosqpub/10451-iMac.loca">>, + clean_sess = true, + keep_alive = 60, + will_retain = false, + will_qos = 1, + will_flag = true, + will_topic = <<"/will">>, + will_msg = <<"willMsg">> } }, <<>>}, 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">> }, Rest}, parse(PubBin1, State)), + ?assertMatch({ok, #mqtt_packet{ + header = #mqtt_packet_header { type = ?DISCONNECT, + dup = false, + qos = 0, + retain = false} + }, <<>>}, parse(Rest, State)), + ok. 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() -> From 8cc8046a02fc26e5ce51ed04143b49ae03264777 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Mon, 12 Jan 2015 12:58:16 +0800 Subject: [PATCH 41/84] add --- go | 5 +++++ 1 file changed, 5 insertions(+) create mode 100755 go diff --git a/go b/go new file mode 100755 index 000000000..cd18c5893 --- /dev/null +++ b/go @@ -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 From 7bdacc6395743f02f2e2081ec0886fe027927722 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Mon, 12 Jan 2015 15:12:13 +0800 Subject: [PATCH 42/84] packet tests --- apps/emqtt/test/emqtt_packet_tests.erl | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/apps/emqtt/test/emqtt_packet_tests.erl b/apps/emqtt/test/emqtt_packet_tests.erl index e1ade7ce0..41a8c5298 100644 --- a/apps/emqtt/test/emqtt_packet_tests.erl +++ b/apps/emqtt/test/emqtt_packet_tests.erl @@ -63,16 +63,19 @@ parse_connect_test() -> dup = false, qos = 0, retain = false}, - variable = #mqtt_packet_connect { proto_ver = 4, - proto_name = <<"MQTT">>, - client_id = <<"mosqpub/10451-iMac.loca">>, + 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">> } }, <<>>}, parse(ConnBinWithWill, State)), + will_msg = <<"willmsg">> , + username = <<"test">>, + password = <<"public">> } }, + <<>> }, parse(ConnBinWithWill, State)), ok. parse_publish_test() -> @@ -86,7 +89,7 @@ parse_publish_test() -> retain = false}, variable = #mqtt_packet_publish { topic_name = <<"a/b/c">>, packet_id = 1 }, - payload = <<"hahah">> }, <<>>}, parse(PubBin, State)) + 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) @@ -98,15 +101,13 @@ parse_publish_test() -> retain = false}, variable = #mqtt_packet_publish { topic_name = <<"xxx/yyy">>, packet_id = undefined }, - payload = <<"hello">> }, Rest}, parse(PubBin1, State)), + payload = <<"hello">> }, <<224,0>>}, parse(PubBin1, State)), ?assertMatch({ok, #mqtt_packet{ header = #mqtt_packet_header { type = ?DISCONNECT, dup = false, qos = 0, retain = false} - }, <<>>}, parse(Rest, State)), - - ok. + }, <<>>}, parse(<<224, 0>>, State)). parse_puback_test() -> %%PUBACK(Qos=0, Retain=false, Dup=false, PacketId=1) @@ -128,7 +129,12 @@ parse_pingreq_test() -> parse_disconnect_test() -> %DISCONNECT(Qos=0, Retain=false, Dup=false) Bin = <<224, 0>>, - ok. + ?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 }, From dccbee29055dd1d71ec64754b1c3a63a468941b8 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Mon, 12 Jan 2015 16:50:16 +0800 Subject: [PATCH 43/84] support empty clientid --- apps/emqtt/src/emqtt_packet.erl | 17 ++++++- apps/emqtt/src/emqtt_protocol.erl | 70 +++++++++++++++----------- apps/emqtt/test/emqtt_packet_tests.erl | 12 +++++ 3 files changed, 70 insertions(+), 29 deletions(-) diff --git a/apps/emqtt/src/emqtt_packet.erl b/apps/emqtt/src/emqtt_packet.erl index 13bc32d61..80e0f3bfc 100644 --- a/apps/emqtt/src/emqtt_packet.erl +++ b/apps/emqtt/src/emqtt_packet.erl @@ -31,7 +31,7 @@ -export([parse/2, serialise/1]). --export([dump/1]). +-export([validate/2, dump/1]). -define(MAX_LEN, 16#fffffff). -define(HIGHBIT, 2#10000000). @@ -259,6 +259,21 @@ opt(X) when is_integer(X) -> X. protocol_name_approved(Ver, Name) -> lists:member({Ver, Name}, ?PROTOCOL_NAMES). +validate(protocol, {Ver, Name}) -> + protocol_name_approved(Ver, Name); + +validate(clientid, {_, ClientId}) when ( size(ClientId) >= 1 ) + andalso ( size(ClientId) >= ?MAX_CLIENTID_LEN ) -> + true; + +%% MQTT3.1.1 allow null clientId. +validate(clientid, {?MQTT_PROTO_V311, ClientId}) + when size(ClientId) =:= 0 -> + true; + +validate(clientid, {_, _}) -> + false. + dump(#mqtt_packet{header = Header, variable = Variable, payload = Payload}) when Payload =:= undefined orelse Payload =:= <<>> -> dump_header(Header, dump_variable(Variable)); diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index d4e44df3c..1f799b4b3 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -109,35 +109,31 @@ handle_packet(?CONNECT, Packet = #mqtt_packet { variable = #mqtt_packet_connect { username = Username, password = Password, - proto_ver = ProtoVersion, clean_sess = CleanSess, keep_alive = KeepAlive, client_id = ClientId } = Var }, - State = #proto_state{ peer_name = PeerName} ) -> + State = #proto_state{ peer_name = PeerName } ) -> lager:info("RECV from ~s@~s: ~s", [ClientId, PeerName, emqtt_packet:dump(Packet)]), - {ReturnCode, State1} = - case {lists:member(ProtoVersion, proplists:get_keys(?PROTOCOL_NAMES)), - valid_client_id(ClientId)} of - {false, _} -> - {?CONNACK_PROTO_VER, State#proto_state{client_id = ClientId}}; - {_, false} -> - {?CONNACK_INVALID_ID, State#proto_state{client_id = ClientId}}; - _ -> - case emqtt_auth:check(Username, Password) of - false -> - lager:error("MQTT login failed - no credentials"), - {?CONNACK_CREDENTIALS, State#proto_state{client_id = ClientId}}; - true -> - start_keepalive(KeepAlive), - emqtt_cm:register(ClientId, self()), - {?CONNACK_ACCEPT, - State #proto_state{ will_msg = make_will_msg(Var), - client_id = ClientId }} - end - end, - send_packet( #mqtt_packet { - header = #mqtt_packet_header { type = ?CONNACK }, - variable = #mqtt_packet_connack{ return_code = ReturnCode }}, State1 ), + {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 = make_will_msg(Var), 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 ), {ok, State1}; handle_packet(?PUBLISH, Packet = #mqtt_packet { @@ -320,9 +316,22 @@ next_packet_id(State = #proto_state{ packet_id = 16#ffff }) -> next_packet_id(State = #proto_state{ packet_id = PacketId }) -> State #proto_state{ packet_id = PacketId + 1 }. -valid_client_id(ClientId) -> - ClientIdLen = size(ClientId), - 1 =< ClientIdLen andalso ClientIdLen =< ?MAX_CLIENTID_LEN. + +validate_connect( #mqtt_packet_connect { + proto_ver = Ver, + proto_name = Name, + client_id = ClientId } ) -> + case emqtt_packet:validate(protocol, {Ver, Name}) of + true -> + case emqtt_packet:validate(clientid, {Ver, ClientId}) of + true -> + ?CONNACK_ACCEPT; + false -> + ?CONNACK_INVALID_ID + end; + false -> + ?CONNACK_PROTO_VER + end. validate_packet(?PUBLISH, #mqtt_packet { variable = #mqtt_packet_publish{ @@ -353,6 +362,11 @@ validate_packet(?SUBSCRIBE, #mqtt_packet{variable = #mqtt_packet_subscribe{topic validate_packet(_Type, _Frame) -> ok. +clientid(<<>>, #proto_state{peer_name = PeerName}) -> + <<"eMQTT/", (base64:encode(PeerName))/binary>>; + +clientid(ClientId, _State) -> ClientId. + maybe_clean_sess(false, _Conn, _ClientId) -> % todo: establish subscription to deliver old unacknowledged messages ok. diff --git a/apps/emqtt/test/emqtt_packet_tests.erl b/apps/emqtt/test/emqtt_packet_tests.erl index 41a8c5298..f1571e0a0 100644 --- a/apps/emqtt/test/emqtt_packet_tests.erl +++ b/apps/emqtt/test/emqtt_packet_tests.erl @@ -56,6 +56,18 @@ parse_connect_test() -> 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{ From fedb5c209a14e4df5475db71fa5c4e0b65b0f3cf Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Mon, 12 Jan 2015 19:24:44 +0800 Subject: [PATCH 44/84] session --- apps/emqtt/src/emqtt_client.erl | 4 ++-- apps/emqtt/src/emqtt_protocol.erl | 4 ++-- apps/emqtt/src/emqtt_sm.erl | 20 +++++++++++++++----- rel/files/app.config | 10 +++++++++- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index 4b96ad2d4..ca75dd7f0 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -140,13 +140,13 @@ terminate(Reason, #state{proto_state = unefined}) -> io:format("client terminated: ~p, reason: ~p~n", [self(), Reason]), %%TODO: fix keep_alive... %%emqtt_keep_alive:cancel(KeepAlive), - %emqtt_protocol:client_terminated(ProtoState), + %emqtt_protocol:connection_lost(ProtoState), ok; terminate(_Reason, #state { keepalive = KeepAlive, proto_state = ProtoState }) -> %%TODO: fix keep_alive... emqtt_keepalive:cancel(KeepAlive), - emqtt_protocol:client_terminated(ProtoState), + emqtt_protocol:connection_lost(ProtoState), ok. code_change(_OldVsn, State, _Extra) -> diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 1f799b4b3..c0a6d29c8 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -45,7 +45,7 @@ -export([initial_state/2]). --export([handle_packet/2, send_message/2, send_packet/2, client_terminated/1]). +-export([handle_packet/2, send_message/2, send_packet/2, connection_lost/1]). -export([info/1]). @@ -277,7 +277,7 @@ send_packet(Packet, #proto_state{socket = Sock, peer_name = PeerName, client_id erlang:port_command(Sock, Data). %%TODO: fix me later... -client_terminated(#proto_state{client_id = ClientId} = State) -> +connection_lost(#proto_state{client_id = ClientId} = State) -> ok. %emqtt_cm:unregister(ClientId, self()). diff --git a/apps/emqtt/src/emqtt_sm.erl b/apps/emqtt/src/emqtt_sm.erl index 508947725..bb711d581 100644 --- a/apps/emqtt/src/emqtt_sm.erl +++ b/apps/emqtt/src/emqtt_sm.erl @@ -53,7 +53,7 @@ -export([start_link/0]). --export([create/2, resume/2, destroy/1]). +-export([lookup/1, create/2, resume/2, destroy/1]). %% ------------------------------------------------------------------ %% gen_server Function Exports @@ -62,12 +62,18 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +-record(state, { expires = 24, %hours + max_queue = 1000 }). + + %% ------------------------------------------------------------------ %% API Function Definitions %% ------------------------------------------------------------------ -start_link() -> - gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). +start_link(SessOpts) -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [SessOpts], []). + +lookup(ClientId) -> ok. create(ClientId, Pid) -> ok. @@ -79,8 +85,12 @@ destroy(ClientId) -> ok. %% gen_server Function Definitions %% ------------------------------------------------------------------ -init(Args) -> - {ok, Args}. +init(SessOpts) -> + {ok, SessOpts} = application:get_env(session), + State = #state{ expires = proplists:get_value(expires, SessOpts, 24) * 3600, + max_queue = proplists:get_value(max_queue, SessOpts, 1000) }, + {ok, State}. + handle_call(_Request, _From, State) -> {reply, ok, State}. diff --git a/rel/files/app.config b/rel/files/app.config index 3521665f8..aa3b51d5b 100644 --- a/rel/files/app.config +++ b/rel/files/app.config @@ -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,6 +33,14 @@ ]}, {emqtt, [ {auth, {anonymous, []}}, %internal, anonymous + {session, [ + {expires, 24}, + {max_queue, 1000}, + {qos0, false} + ]}, + {retain, [ + + ]}, {listen, [ {mqtt, 1883, [ {max_conns, 1024}, From 3c9c2610814f0fe1ec9d0e8400d201bdeaf7e60c Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Mon, 12 Jan 2015 19:32:59 +0800 Subject: [PATCH 45/84] debug, 8083 --- rel/files/app.config | 4 ++-- rel/files/emqtt.cfg | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 rel/files/emqtt.cfg diff --git a/rel/files/app.config b/rel/files/app.config index 3521665f8..95e306140 100644 --- a/rel/files/app.config +++ b/rel/files/app.config @@ -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}, @@ -38,7 +38,7 @@ {max_conns, 1024}, {acceptor_pool, 4} ]}, - {http, 8883, [ + {http, 8083, [ {max_conns, 512}, {acceptor_pool, 1} ]} diff --git a/rel/files/emqtt.cfg b/rel/files/emqtt.cfg new file mode 100644 index 000000000..8b1d8dc3d --- /dev/null +++ b/rel/files/emqtt.cfg @@ -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 From 8c3558b7ec8875943c4ca3095c96e30cd4a31e8c Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Tue, 13 Jan 2015 00:05:16 +0800 Subject: [PATCH 46/84] session management --- apps/emqtt/src/emqtt_app.erl | 3 +- apps/emqtt/src/emqtt_packet.erl | 17 +-- apps/emqtt/src/emqtt_protocol.erl | 198 ++++++++++++++++-------------- apps/emqtt/src/emqtt_session.erl | 94 ++++++++++++++ apps/emqtt/src/emqtt_sm.erl | 7 +- 5 files changed, 209 insertions(+), 110 deletions(-) diff --git a/apps/emqtt/src/emqtt_app.erl b/apps/emqtt/src/emqtt_app.erl index abf59a323..f742a0167 100644 --- a/apps/emqtt/src/emqtt_app.erl +++ b/apps/emqtt/src/emqtt_app.erl @@ -59,6 +59,7 @@ print_vsn() -> ?PRINT("~s ~s is running now~n", [Desc, Vsn]). start_servers(Sup) -> + {ok, SessOpts} = application:get_env(session), lists:foreach( fun({Name, F}) when is_function(F) -> ?PRINT("~s is starting...", [Name]), @@ -75,7 +76,7 @@ start_servers(Sup) -> end, [{"emqtt config", emqtt_config}, {"emqtt client manager", emqtt_cm}, - {"emqtt session manager", emqtt_sm}, + {"emqtt session manager", emqtt_sm, SessOpts}, {"emqtt auth", emqtt_auth}, {"emqtt retained", emqtt_retained}, {"emqtt pubsub", emqtt_pubsub}, diff --git a/apps/emqtt/src/emqtt_packet.erl b/apps/emqtt/src/emqtt_packet.erl index 80e0f3bfc..13bc32d61 100644 --- a/apps/emqtt/src/emqtt_packet.erl +++ b/apps/emqtt/src/emqtt_packet.erl @@ -31,7 +31,7 @@ -export([parse/2, serialise/1]). --export([validate/2, dump/1]). +-export([dump/1]). -define(MAX_LEN, 16#fffffff). -define(HIGHBIT, 2#10000000). @@ -259,21 +259,6 @@ opt(X) when is_integer(X) -> X. protocol_name_approved(Ver, Name) -> lists:member({Ver, Name}, ?PROTOCOL_NAMES). -validate(protocol, {Ver, Name}) -> - protocol_name_approved(Ver, Name); - -validate(clientid, {_, ClientId}) when ( size(ClientId) >= 1 ) - andalso ( size(ClientId) >= ?MAX_CLIENTID_LEN ) -> - true; - -%% MQTT3.1.1 allow null clientId. -validate(clientid, {?MQTT_PROTO_V311, ClientId}) - when size(ClientId) =:= 0 -> - true; - -validate(clientid, {_, _}) -> - false. - dump(#mqtt_packet{header = Header, variable = Variable, payload = Payload}) when Payload =:= undefined orelse Payload =:= <<>> -> dump_header(Header, dump_variable(Variable)); diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index c0a6d29c8..bd58c8f7e 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -26,6 +26,19 @@ -include("emqtt_packet.hrl"). +%% ------------------------------------------------------------------ +%% API Function Exports +%% ------------------------------------------------------------------ + +-export([initial_state/2]). + +-export([handle_packet/2, send_message/2, send_packet/2, connection_lost/1]). + +-export([info/1]). + +%% ------------------------------------------------------------------ +%% Protocol State +%% ------------------------------------------------------------------ -record(proto_state, { socket, peer_name, @@ -35,20 +48,12 @@ packet_id, client_id, clean_sess, - will_msg, - subscriptions, - awaiting_ack, - awaiting_rel + session, %% session state or session pid + will_msg }). -type proto_state() :: #proto_state{}. --export([initial_state/2]). - --export([handle_packet/2, send_message/2, send_packet/2, connection_lost/1]). - --export([info/1]). - -define(PACKET_TYPE(Packet, Type), Packet = #mqtt_packet { header = #mqtt_packet_header { type = Type }}). @@ -56,26 +61,22 @@ initial_state(Socket, Peername) -> #proto_state{ socket = Socket, peer_name = Peername, - packet_id = 1, - subscriptions = [], - awaiting_ack = gb_trees:empty(), - awaiting_rel = gb_trees:empty() + packet_id = 1 }. +%%SHOULD be registered in emqtt_cm info(#proto_state{ proto_vsn = ProtoVsn, proto_name = ProtoName, packet_id = PacketId, client_id = ClientId, clean_sess = CleanSess, - will_msg = WillMsg, - subscriptions= Subs }) -> + will_msg = WillMsg }) -> [ {packet_id, PacketId}, {proto_vsn, ProtoVsn}, {proto_name, ProtoName}, {client_id, ClientId}, {clean_sess, CleanSess}, - {will_msg, WillMsg}, - {subscriptions, Subs} ]. + {will_msg, WillMsg} ]. -spec handle_packet(Packet, State) -> {ok, NewState} | {error, any()} when Packet :: mqtt_packet(), @@ -84,7 +85,7 @@ info(#proto_state{ proto_vsn = ProtoVsn, %%CONNECT – Client requests a connection to a Server -%%A Client can only send the CONNECT Packet once over a Network Connection. 369 +%%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}); @@ -98,7 +99,7 @@ handle_packet(_Packet, State = #proto_state{connected = false}) -> 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(Type, Packet) of + case validate_packet(Packet) of ok -> handle_packet(Type, Packet, State); {error, Reason} -> @@ -122,8 +123,9 @@ handle_packet(?CONNECT, Packet = #mqtt_packet { ClientId1 = clientid(ClientId, State), start_keepalive(KeepAlive), emqtt_cm:register(ClientId1, self()), - {?CONNACK_ACCEPT, - State#proto_state{ will_msg = make_will_msg(Var), client_id = ClientId1 }}; + {?CONNACK_ACCEPT, State#proto_state{ will_msg = make_will_msg(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}} @@ -134,30 +136,31 @@ handle_packet(?CONNECT, Packet = #mqtt_packet { send_packet( #mqtt_packet { header = #mqtt_packet_header { type = ?CONNACK }, variable = #mqtt_packet_connack{ return_code = ReturnCode1 }}, State1 ), - {ok, State1}; + %% + {ok, Session} = emqtt_session:start({CleanSess, ClientId, self()}), + emqtt_session:resume(Session), + %%TODO: Resume session + {ok, State1#proto_state { session = Session }}; handle_packet(?PUBLISH, Packet = #mqtt_packet { header = #mqtt_packet_header {qos = ?QOS_0}}, State) -> - emqtt_router:route(make_message(Packet)), + emqtt_session:publish(Session, ?QOS_0, make_message(Packet)), {ok, State}; handle_packet(?PUBLISH, Packet = #mqtt_packet { - header = #mqtt_packet_header { qos = ?QOS_1 }, + header = #mqtt_packet_header { qos = ?QOS_1 }, variable = #mqtt_packet_publish{packet_id = PacketId}}, - State) -> - emqtt_router:route(make_message(Packet)), - send_packet( make_packet(?PUBACK, PacketId), State ), - {ok, State}; + State = #proto_state { session = Session }) -> + emqtt_session:publish(Session, {?QOS_1, make_message(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}}, + header = #mqtt_packet_header { qos = ?QOS_2 }, + variable = #mqtt_packet_publish { packet_id = PacketId } }, State) -> %%FIXME: this is not right...should store it first... - emqtt_router:route(make_message(Packet)), - put({msg, PacketId}, pubrec), - send_packet( make_packet(?PUBREC, PacketId), State ), - {ok, State}; + NewSession = emqtt_session:publish(Session, {?QOS_2, make_message(Packet)}), + send_packet( make_packet(?PUBREC, PacketId), State#proto_state {session = NewSession} ); handle_packet(?PUBACK, #mqtt_packet {}, State) -> %FIXME Later @@ -167,14 +170,12 @@ handle_packet(?PUBREC, #mqtt_packet { variable = #mqtt_packet_puback { packet_id = PacketId }}, State) -> %FIXME Later: should release the message here - send_packet( make_packet(?PUBREL, PacketId), State ), - {ok, State}; + send_packet( make_packet(?PUBREL, PacketId), State ); handle_packet(?PUBREL, #mqtt_packet { variable = #mqtt_packet_puback { packet_id = PacketId}}, State) -> %%FIXME: not right... erase({msg, PacketId}), - send_packet( make_packet(?PUBCOMP, PacketId), State ), - {ok, State}; + send_packet( make_packet(?PUBCOMP, PacketId), State ); handle_packet(?PUBCOMP, #mqtt_packet { variable = #mqtt_packet_puback{packet_id = _PacketId}}, State) -> @@ -197,9 +198,8 @@ handle_packet(?SUBSCRIBE, #mqtt_packet { send_packet(#mqtt_packet { header = #mqtt_packet_header { type = ?SUBACK }, variable = #mqtt_packet_suback{ packet_id = PacketId, - qos_table = GrantedQos }}, State), + qos_table = GrantedQos }}, State); - {ok, State}; handle_packet(?UNSUBSCRIBE, #mqtt_packet { variable = #mqtt_packet_subscribe{ @@ -211,13 +211,10 @@ handle_packet(?UNSUBSCRIBE, #mqtt_packet { [emqtt_pubsub:unsubscribe(Name, self()) || #mqtt_topic{name=Name} <- Topics], send_packet(#mqtt_packet { header = #mqtt_packet_header {type = ?UNSUBACK }, - variable = #mqtt_packet_suback{packet_id = PacketId }}, State), - - {ok, State}; + variable = #mqtt_packet_suback{packet_id = PacketId }}, State); handle_packet(?PINGREQ, #mqtt_packet{}, State) -> - send_packet(make_packet(?PINGRESP), State), - {ok, State}; + send_packet(make_packet(?PINGRESP), State); handle_packet(?DISCONNECT, #mqtt_packet{}, State=#proto_state{peer_name = PeerName, client_id = ClientId}) -> {stop, State}. @@ -275,6 +272,7 @@ send_packet(Packet, #proto_state{socket = Sock, peer_name = PeerName, client_id lager:debug("SENT to ~s: ~p", [PeerName, Data]), %%FIXME Later... erlang:port_command(Sock, Data). + {ok, State}; %%TODO: fix me later... connection_lost(#proto_state{client_id = ClientId} = State) -> @@ -317,50 +315,7 @@ next_packet_id(State = #proto_state{ packet_id = PacketId }) -> State #proto_state{ packet_id = PacketId + 1 }. -validate_connect( #mqtt_packet_connect { - proto_ver = Ver, - proto_name = Name, - client_id = ClientId } ) -> - case emqtt_packet:validate(protocol, {Ver, Name}) of - true -> - case emqtt_packet:validate(clientid, {Ver, ClientId}) of - true -> - ?CONNACK_ACCEPT; - false -> - ?CONNACK_INVALID_ID - end; - false -> - ?CONNACK_PROTO_VER - end. -validate_packet(?PUBLISH, #mqtt_packet { - variable = #mqtt_packet_publish{ - topic_name = Topic }}) -> - case emqtt_topic:validate({publish, Topic}) of - true -> ok; - false -> {error, badtopic} - end; - -validate_packet(?UNSUBSCRIBE, #mqtt_packet { - variable = #mqtt_packet_subscribe{ - topic_table = Topics }}) -> - ErrTopics = [Topic || #mqtt_topic{name=Topic, qos=Qos} <- Topics, - not emqtt_topic:validate({subscribe, Topic})], - case ErrTopics of - [] -> ok; - _ -> lager:error("error topics: ~p", [ErrTopics]), {error, badtopic} - end; - -validate_packet(?SUBSCRIBE, #mqtt_packet{variable = #mqtt_packet_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; - _ -> lager:error("error topics: ~p", [ErrTopics]), {error, badtopic} - end; - -validate_packet(_Type, _Frame) -> - ok. clientid(<<>>, #proto_state{peer_name = PeerName}) -> <<"eMQTT/", (base64:encode(PeerName))/binary>>; @@ -382,3 +337,68 @@ 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:error("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, 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. + diff --git a/apps/emqtt/src/emqtt_session.erl b/apps/emqtt/src/emqtt_session.erl index 145d7fb18..da52036aa 100644 --- a/apps/emqtt/src/emqtt_session.erl +++ b/apps/emqtt/src/emqtt_session.erl @@ -22,3 +22,97 @@ -module(emqtt_session). +-record(session_state, { + client_id, + client_pid, + packet_id = 1, + subscriptions = [], + messages = [], %% do not receive rel + awaiting_ack, + awaiting_rel }). + +%% ------------------------------------------------------------------ +%% API Function Exports +%% ------------------------------------------------------------------ +-export([start/1, resume/1, publish/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({true = CleanSess, ClientId, ClientPid}) -> + %%destroy old session + %%TODO: emqtt_sm:destory_session(ClientId), + {ok, initial_state(ClientId)}; + +start({false = CleanSess, ClientId, ClientPid}) -> + %%TODO: emqtt_sm:start_session({ClientId, ClientPid}) + gen_server:start_link(?MODULE, [ClientId, ClientPid], []). + +resume(#session_state {}) -> 'TODO'; +resume(SessPid) when is_pid(SessPid) -> 'TODO'. + +publish(_, {?QOS_0, Message}) -> + emqtt_router:route(Message); + +%%TODO: +publish(_, {?QOS_1, Message}) -> + emqtt_router:route(Message), + +%%TODO: +publish(Session = #session_state{awaiting_rel = Awaiting}, {?QOS_2, Message}) -> + %% store gb_tree: + Session#session_state{awaiting_rel = Awaiting}; + +publish(_, {?QOS_2, Message}) -> + %TODO: + put({msg, PacketId}, pubrec), + emqtt_router:route(Message), + +initial_state(ClientId) -> + #session_state { client_id = ClientId, + packet_id = 1, + subscriptions = [], + awaiting_ack = gb_trees:empty(), + awaiting_rel = gb_trees:empty() }. + +initial_state(ClientId, ClientPid) -> + State = initial_state(ClientId), + State#session_state{client_pid = ClientPid}. + +%% ------------------------------------------------------------------ +%% gen_server Function Definitions +%% ------------------------------------------------------------------ + +init([ClientId, ClientPid]) -> + process_flag(trap_exit, true), + State = initial_state(ClientId, ClientPid), + {ok, State}. + +handle_call(_Request, _From, State) -> + {reply, ok, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%% ------------------------------------------------------------------ +%% Internal Function Definitions +%% ------------------------------------------------------------------ + + + diff --git a/apps/emqtt/src/emqtt_sm.erl b/apps/emqtt/src/emqtt_sm.erl index bb711d581..791d60ad7 100644 --- a/apps/emqtt/src/emqtt_sm.erl +++ b/apps/emqtt/src/emqtt_sm.erl @@ -51,9 +51,9 @@ %% API Function Exports %% ------------------------------------------------------------------ --export([start_link/0]). +-export([start_link/1]). --export([lookup/1, create/2, resume/2, destroy/1]). +-export([lookup/1, register/2, resume/2, destroy/1]). %% ------------------------------------------------------------------ %% gen_server Function Exports @@ -75,7 +75,7 @@ start_link(SessOpts) -> lookup(ClientId) -> ok. -create(ClientId, Pid) -> ok. +register(ClientId, Pid) -> ok. resume(ClientId, Pid) -> ok. @@ -86,7 +86,6 @@ destroy(ClientId) -> ok. %% ------------------------------------------------------------------ init(SessOpts) -> - {ok, SessOpts} = application:get_env(session), State = #state{ expires = proplists:get_value(expires, SessOpts, 24) * 3600, max_queue = proplists:get_value(max_queue, SessOpts, 1000) }, {ok, State}. From 35ff84a8b7c74eaf76acf41a7fa8a59f72e43240 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Tue, 13 Jan 2015 00:08:28 +0800 Subject: [PATCH 47/84] doc --- apps/emqtt/include/emqtt.hrl | 6 ++++- doc/cluster.md | 25 ++++++++++++++++++ doc/session.md | 50 ++++++++++++++++++++++++++++++++++++ doc/state_design.md | 4 +++ 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 doc/cluster.md create mode 100644 doc/session.md create mode 100644 doc/state_design.md diff --git a/apps/emqtt/include/emqtt.hrl b/apps/emqtt/include/emqtt.hrl index 19485c7da..2d172945d 100644 --- a/apps/emqtt/include/emqtt.hrl +++ b/apps/emqtt/include/emqtt.hrl @@ -55,7 +55,11 @@ %% MQTT Session %%------------------------------------------------------------------------------ -record(mqtt_session, { - client_id + client_id, + session_pid, + subscriptions = [], + awaiting_ack, + awaiting_rel }). -type mqtt_session() :: #mqtt_session{}. diff --git a/doc/cluster.md b/doc/cluster.md new file mode 100644 index 000000000..076ba2689 --- /dev/null +++ b/doc/cluster.md @@ -0,0 +1,25 @@ + + zookeeper + | + eMQTT1 eMQTT2 eMQTT3 + + +Bridge + + + eMQTT1 --> eMQTT2 + + +Cluster + + eMQTT1 <--> eMQTT2 + + +Cluster and Bridge + + eMQTT1 eMQTT3 + ----> + eMQTT2 eMQTT4 + +Mnesia Cluster + diff --git a/doc/session.md b/doc/session.md new file mode 100644 index 000000000..fc9211a9b --- /dev/null +++ b/doc/session.md @@ -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) + + diff --git a/doc/state_design.md b/doc/state_design.md new file mode 100644 index 000000000..74a484e37 --- /dev/null +++ b/doc/state_design.md @@ -0,0 +1,4 @@ + + +client state --> parse_state + --> proto_state --> session_state From cf37428c9152693d40ae40cec91be64831721841 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Tue, 13 Jan 2015 11:50:10 +0800 Subject: [PATCH 48/84] session --- apps/emqtt/src/emqtt_client.erl | 28 +++++------ apps/emqtt/src/emqtt_protocol.erl | 83 ++++++++++++++----------------- apps/emqtt/src/emqtt_pubsub.erl | 2 +- apps/emqtt/src/emqtt_retained.erl | 2 +- apps/emqtt/src/emqtt_session.erl | 53 ++++++++++++++------ 5 files changed, 92 insertions(+), 76 deletions(-) diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index ca75dd7f0..a60c19100 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -38,16 +38,16 @@ -include("emqtt.hrl"). %%Client State... --record(state, { - socket, - peer_name, - conn_name, - await_recv, - conn_state, - conserve, - parse_state, - proto_state, - keepalive +-record(state, { + socket, + peer_name, + conn_name, + await_recv, + conn_state, + conserve, + parse_state, + proto_state, + keepalive }). start_link(Sock) -> @@ -98,8 +98,8 @@ handle_info({stop, duplicate_id, NewPid}, State=#state{conn_name=ConnName}) -> stop({shutdown, duplicate_id}, State); %%TODO: ok?? -handle_info({dispatch, Message}, #state{proto_state = ProtoState} = State) -> - {ok, ProtoState1} = emqtt_protocol:send_message(Message, ProtoState), +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}}; handle_info({inet_reply, _Ref, ok}, State) -> @@ -185,8 +185,8 @@ process_received_bytes(Bytes, stop({shutdown, Error}, State); {error, Error, ProtoState1} -> stop({shutdown, Error}, State#state{proto_state = ProtoState1}); - {stop, ProtoState1} -> - stop(normal, 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]), diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index bd58c8f7e..6900924bb 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -57,6 +57,8 @@ -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, @@ -143,13 +145,14 @@ handle_packet(?CONNECT, Packet = #mqtt_packet { {ok, State1#proto_state { session = Session }}; handle_packet(?PUBLISH, Packet = #mqtt_packet { - header = #mqtt_packet_header {qos = ?QOS_0}}, State) -> - emqtt_session:publish(Session, ?QOS_0, make_message(Packet)), + header = #mqtt_packet_header {qos = ?QOS_0}}, + State = #proto_state{session = Session}) -> + emqtt_session:publish(Session, {?QOS_0, make_message(Packet)}), {ok, State}; handle_packet(?PUBLISH, Packet = #mqtt_packet { header = #mqtt_packet_header { qos = ?QOS_1 }, - variable = #mqtt_packet_publish{packet_id = PacketId}}, + variable = #mqtt_packet_publish{packet_id = PacketId }}, State = #proto_state { session = Session }) -> emqtt_session:publish(Session, {?QOS_1, make_message(Packet)}), send_packet( make_packet(?PUBACK, PacketId), State); @@ -157,67 +160,57 @@ handle_packet(?PUBLISH, Packet = #mqtt_packet { handle_packet(?PUBLISH, Packet = #mqtt_packet { header = #mqtt_packet_header { qos = ?QOS_2 }, variable = #mqtt_packet_publish { packet_id = PacketId } }, - State) -> - %%FIXME: this is not right...should store it first... + State = #proto_state { session = Session }) -> NewSession = emqtt_session:publish(Session, {?QOS_2, make_message(Packet)}), send_packet( make_packet(?PUBREC, PacketId), State#proto_state {session = NewSession} ); -handle_packet(?PUBACK, #mqtt_packet {}, State) -> - %FIXME Later - {ok, State}; +handle_packet(Puback, #mqtt_packet{variable = ?PUBACK_PACKET(PacketId) }, + State = #proto_state { session = Session }) + when Puback >= ?PUBACK andalso Puback =< ?PUBCOMP -> -handle_packet(?PUBREC, #mqtt_packet { - variable = #mqtt_packet_puback { packet_id = PacketId }}, - State) -> - %FIXME Later: should release the message here - send_packet( make_packet(?PUBREL, PacketId), State ); - -handle_packet(?PUBREL, #mqtt_packet { variable = #mqtt_packet_puback { packet_id = PacketId}}, State) -> - %%FIXME: not right... - erase({msg, PacketId}), - send_packet( make_packet(?PUBCOMP, PacketId), State ); - -handle_packet(?PUBCOMP, #mqtt_packet { - variable = #mqtt_packet_puback{packet_id = _PacketId}}, State) -> - %TODO: fixme later - {ok, State}; + 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 = Topics}, + topic_table = TopicTable}, payload = undefined}, - State) -> - - %%FIXME: this is not right... - [emqtt_pubsub:subscribe({Name, Qos}, self()) || - #mqtt_topic{name=Name, qos=Qos} <- Topics], - - GrantedQos = [Qos || #mqtt_topic{qos=Qos} <- Topics], + 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); - handle_packet(?UNSUBSCRIBE, #mqtt_packet { variable = #mqtt_packet_subscribe{ packet_id = PacketId, topic_table = Topics }, payload = undefined}, - State = #proto_state{client_id = ClientId}) -> - - [emqtt_pubsub:unsubscribe(Name, self()) || #mqtt_topic{name=Name} <- Topics], - + 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); + 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=#proto_state{peer_name = PeerName, client_id = ClientId}) -> - {stop, State}. +handle_packet(?DISCONNECT, #mqtt_packet{}, State) -> + %%how to handle session? + {stop, normal, State}. make_packet(Type) when Type >= ?CONNECT andalso Type =< ?DISCONNECT -> #mqtt_packet{ header = #mqtt_packet_header { type = Type } }. @@ -232,16 +225,16 @@ puback_qos(?PUBREL) -> ?QOS_1; puback_qos(?PUBCOMP) -> ?QOS_0. -spec send_message(Message, State) -> {ok, NewState} when - Message :: mqtt_message(), + Message :: {pid(), mqtt_message()}, State :: proto_state(), NewState :: proto_state(). -send_message(Message = #mqtt_message{ +send_message({From, Message = #mqtt_message{ retain = Retain, qos = Qos, topic = Topic, dup = Dup, - payload = Payload}, + payload = Payload}}, State = #proto_state{packet_id = PacketId}) -> Packet = #mqtt_packet { @@ -266,13 +259,13 @@ send_message(Message = #mqtt_message{ {ok, next_packet_id(State)} end. -send_packet(Packet, #proto_state{socket = Sock, peer_name = PeerName, client_id = ClientId}) -> +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}; + erlang:port_command(Sock, Data), + {ok, State}. %%TODO: fix me later... connection_lost(#proto_state{client_id = ClientId} = State) -> diff --git a/apps/emqtt/src/emqtt_pubsub.erl b/apps/emqtt/src/emqtt_pubsub.erl index f687b7527..84f162fdf 100644 --- a/apps/emqtt/src/emqtt_pubsub.erl +++ b/apps/emqtt/src/emqtt_pubsub.erl @@ -111,7 +111,7 @@ publish(Topic, Msg) when is_binary(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)]. + [SubPid ! {dispatch, {self(), Msg}} || #topic_subscriber{subpid=SubPid} <- ets:lookup(topic_subscriber, Topic)]. -spec match(Topic :: binary()) -> [topic()]. match(Topic) when is_binary(Topic) -> diff --git a/apps/emqtt/src/emqtt_retained.erl b/apps/emqtt/src/emqtt_retained.erl index 2db923316..96c605537 100644 --- a/apps/emqtt/src/emqtt_retained.erl +++ b/apps/emqtt/src/emqtt_retained.erl @@ -76,7 +76,7 @@ delete(Topic) -> gen_server:cast(?MODULE, {delete, Topic}). send(Topic, Client) -> - [Client ! {dispatch, Msg} ||{_, Msg} <- lookup(Topic)]. + [Client ! {dispatch, {self(), Msg}} ||{_, Msg} <- lookup(Topic)]. init([]) -> ets:new(retained_msg, [set, protected, named_table]), diff --git a/apps/emqtt/src/emqtt_session.erl b/apps/emqtt/src/emqtt_session.erl index da52036aa..ae6271098 100644 --- a/apps/emqtt/src/emqtt_session.erl +++ b/apps/emqtt/src/emqtt_session.erl @@ -22,6 +22,22 @@ -module(emqtt_session). +-include("emqtt.hrl"). + +-include("emqtt_packet.hrl"). + +%% ------------------------------------------------------------------ +%% API Function Exports +%% ------------------------------------------------------------------ +-export([start/1, resume/1, publish/2, puback/2]). + +%% ------------------------------------------------------------------ +%% gen_server Function Exports +%% ------------------------------------------------------------------ + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + -record(session_state, { client_id, client_pid, @@ -31,18 +47,6 @@ awaiting_ack, awaiting_rel }). -%% ------------------------------------------------------------------ -%% API Function Exports -%% ------------------------------------------------------------------ --export([start/1, resume/1, publish/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 %% ------------------------------------------------------------------ @@ -63,7 +67,7 @@ publish(_, {?QOS_0, Message}) -> %%TODO: publish(_, {?QOS_1, Message}) -> - emqtt_router:route(Message), + emqtt_router:route(Message); %%TODO: publish(Session = #session_state{awaiting_rel = Awaiting}, {?QOS_2, Message}) -> @@ -72,8 +76,27 @@ publish(Session = #session_state{awaiting_rel = Awaiting}, {?QOS_2, Message}) -> publish(_, {?QOS_2, Message}) -> %TODO: - put({msg, PacketId}, pubrec), - emqtt_router:route(Message), + %put({msg, PacketId}, pubrec), + emqtt_router:route(Message). + +puback(_, {?PUBACK, PacketId}) -> + 'TODO'; +puback(_, {?PUBREC, PacketId}) -> + 'TODO'; +puback(_, {?PUBREL, PacketId}) -> + %FIXME Later: should release the message here + erase({msg, PacketId}), + 'TODO'; +puback(_, {?PUBCOMP, PacketId}) -> + 'TODO'. + +subscribe(Session, Topics) -> + %%TODO. + {ok, Session, [Qos || {_Name, Qos} <- Topics]}. + +unsubscribe(Session, Topics) -> + %%TODO. + {ok, Session}. initial_state(ClientId) -> #session_state { client_id = ClientId, From 0301644793e8046fd32e336348fd6498e34d8fd5 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Tue, 13 Jan 2015 16:45:53 +0800 Subject: [PATCH 49/84] session --- apps/emqtt/src/emqtt_protocol.erl | 4 +- apps/emqtt/src/emqtt_session.erl | 38 +++++++++---- apps/emqtt/src/emqtt_session_sup.erl | 57 ++++++++++++++++++++ apps/emqtt/src/emqtt_sm.erl | 81 +++++++++++++++++++++++----- 4 files changed, 153 insertions(+), 27 deletions(-) create mode 100644 apps/emqtt/src/emqtt_session_sup.erl diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 6900924bb..0e2a22354 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -138,10 +138,8 @@ handle_packet(?CONNECT, Packet = #mqtt_packet { 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()}), - emqtt_session:resume(Session), - %%TODO: Resume session {ok, State1#proto_state { session = Session }}; handle_packet(?PUBLISH, Packet = #mqtt_packet { diff --git a/apps/emqtt/src/emqtt_session.erl b/apps/emqtt/src/emqtt_session.erl index ae6271098..059a70e34 100644 --- a/apps/emqtt/src/emqtt_session.erl +++ b/apps/emqtt/src/emqtt_session.erl @@ -31,6 +31,8 @@ %% ------------------------------------------------------------------ -export([start/1, resume/1, publish/2, puback/2]). +%%start gen_server +-export([start_link/3]). %% ------------------------------------------------------------------ %% gen_server Function Exports %% ------------------------------------------------------------------ @@ -45,22 +47,30 @@ subscriptions = [], messages = [], %% do not receive rel awaiting_ack, - awaiting_rel }). + awaiting_rel, + expires, + max_queue }). %% ------------------------------------------------------------------ -%% API Function Definitions +%% Start Session %% ------------------------------------------------------------------ -start({true = CleanSess, ClientId, ClientPid}) -> - %%destroy old session - %%TODO: emqtt_sm:destory_session(ClientId), +start({true = CleanSess, ClientId, _ClientPid}) -> + %%Destroy old session if CleanSess is true before. + ok = emqtt_sm:destory_session(ClientId), {ok, initial_state(ClientId)}; start({false = CleanSess, ClientId, ClientPid}) -> - %%TODO: emqtt_sm:start_session({ClientId, ClientPid}) - gen_server:start_link(?MODULE, [ClientId, ClientPid], []). + {ok, SessPid} = emqtt_sm:start_session(ClientId, ClientPid), + {ok, SessPid}. -resume(#session_state {}) -> 'TODO'; -resume(SessPid) when is_pid(SessPid) -> 'TODO'. +%% ------------------------------------------------------------------ +%% Session API +%% ------------------------------------------------------------------ +resume(SessState = #session_state{}, _ClientPid) -> + SessState; +resume(SessPid, ClientPid) when is_pid(SessPid) -> + gen_server:cast(SessPid, {resume, ClientPid}), + SessPid. publish(_, {?QOS_0, Message}) -> emqtt_router:route(Message); @@ -113,10 +123,16 @@ initial_state(ClientId, ClientPid) -> %% gen_server Function Definitions %% ------------------------------------------------------------------ -init([ClientId, ClientPid]) -> +start_link(SessOpts, ClientId, ClientPid) -> + gen_server:start_link(?MODULE, [SessOpts, ClientId, ClientPid], []). + +init([SessOpts, ClientId, ClientPid]) -> process_flag(trap_exit, true), + %%TODO: OK? + true = link(ClientPid), State = initial_state(ClientId, ClientPid), - {ok, State}. + {ok, State#state{ expires = proplists:get_value(expires, SessOpts, 24) * 3600, + max_queue = proplists:get_value(max_queue, SessOpts, 1000) } }. handle_call(_Request, _From, State) -> {reply, ok, State}. diff --git a/apps/emqtt/src/emqtt_session_sup.erl b/apps/emqtt/src/emqtt_session_sup.erl new file mode 100644 index 000000000..d4a118bbf --- /dev/null +++ b/apps/emqtt/src/emqtt_session_sup.erl @@ -0,0 +1,57 @@ +%%----------------------------------------------------------------------------- +%% Copyright (c) 2012-2015, Feng Lee +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in all +%% copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. +%%------------------------------------------------------------------------------ + +-module(emqtt_session_sup). + +-author('feng@emqtt.io'). + +-behavior(supervisor). + +-export([start_link/0, start_session/2]). + +-export([init/1]). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-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]}]}}. + + + diff --git a/apps/emqtt/src/emqtt_sm.erl b/apps/emqtt/src/emqtt_sm.erl index 791d60ad7..7714034ef 100644 --- a/apps/emqtt/src/emqtt_sm.erl +++ b/apps/emqtt/src/emqtt_sm.erl @@ -47,13 +47,15 @@ -define(SERVER, ?MODULE). +-define(TABLE, emqtt_session). + %% ------------------------------------------------------------------ %% API Function Exports %% ------------------------------------------------------------------ --export([start_link/1]). +-export([start_link/0]). --export([lookup/1, register/2, resume/2, destroy/1]). +-export([lookup_session/1, start_session/2, destroy_session/1]). %% ------------------------------------------------------------------ %% gen_server Function Exports @@ -62,34 +64,82 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --record(state, { expires = 24, %hours - max_queue = 1000 }). +%%---------------------------------------------------------------------------- + +-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(SessOpts) -> - gen_server:start_link({local, ?SERVER}, ?MODULE, [SessOpts], []). -lookup(ClientId) -> ok. +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). -register(ClientId, Pid) -> ok. +lookup_session(ClientId) -> + case ets:lookup(?TABLE, ClientId) of + [{_, SessPid, _}] -> SessPid; + [] -> undefined + end. -resume(ClientId, Pid) -> ok. +start_session(ClientId, ClientPid) -> + gen_server:call(?SERVER, {start_session, ClientId, ClientPid}). -destroy(ClientId) -> ok. +destory_session(ClientId) -> + gen_server:call(?SERVER, {destory_session, ClientId}). %% ------------------------------------------------------------------ %% gen_server Function Definitions %% ------------------------------------------------------------------ -init(SessOpts) -> - State = #state{ expires = proplists:get_value(expires, SessOpts, 24) * 3600, - max_queue = proplists:get_value(max_queue, SessOpts, 1000) }, +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, 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({destory_session, ClientId}, _From, State) -> + case ets:lookup(?TABLE, ClientId) of + [{_, SessPid, MRef}] -> + erlang:demonitor(MRef), + emqtt_session:destory(SessPid), + ets:delete(?TABLE, ClientId); + [] -> + ignore + end, + {reply, ok, State}; handle_call(_Request, _From, State) -> {reply, ok, State}. @@ -97,6 +147,10 @@ handle_call(_Request, _From, 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,3 +160,4 @@ terminate(_Reason, _State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. + From 600a3b0e2c9a404da39d4a033aa0b97b3ae8675c Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Wed, 14 Jan 2015 12:51:41 +0800 Subject: [PATCH 50/84] session --- apps/emqtt/src/emqtt_app.erl | 10 +- apps/emqtt/src/emqtt_pubsub.erl | 2 + apps/emqtt/src/emqtt_session.erl | 156 +++++++++++++++++++-------- apps/emqtt/src/emqtt_session_sup.erl | 2 +- apps/emqtt/src/emqtt_sm.erl | 10 +- 5 files changed, 130 insertions(+), 50 deletions(-) diff --git a/apps/emqtt/src/emqtt_app.erl b/apps/emqtt/src/emqtt_app.erl index f742a0167..ce65a92ec 100644 --- a/apps/emqtt/src/emqtt_app.erl +++ b/apps/emqtt/src/emqtt_app.erl @@ -76,7 +76,15 @@ start_servers(Sup) -> end, [{"emqtt config", emqtt_config}, {"emqtt client manager", emqtt_cm}, - {"emqtt session manager", emqtt_sm, SessOpts}, + {"emqtt session manager", emqtt_sm}, + %%TODO: fixme + {"emqtt session supervisor", fun() -> + Mod = emqtt_session_sup, + supervisor:start_child(Sup, + {Mod, + {Mod, start_link, [SessOpts]}, + permanent, 1000, supervisor, [Mod]}) + end}, {"emqtt auth", emqtt_auth}, {"emqtt retained", emqtt_retained}, {"emqtt pubsub", emqtt_pubsub}, diff --git a/apps/emqtt/src/emqtt_pubsub.erl b/apps/emqtt/src/emqtt_pubsub.erl index 84f162fdf..a3c73a9bb 100644 --- a/apps/emqtt/src/emqtt_pubsub.erl +++ b/apps/emqtt/src/emqtt_pubsub.erl @@ -86,6 +86,8 @@ topics() -> subscribe({Topic, Qos}, SubPid) when is_binary(Topic) and is_pid(SubPid) -> gen_server:call(?SERVER, {subscribe, {Topic, Qos}, SubPid}). + + %% %% @doc Unsubscribe Topic %% diff --git a/apps/emqtt/src/emqtt_session.erl b/apps/emqtt/src/emqtt_session.erl index 059a70e34..20334ac93 100644 --- a/apps/emqtt/src/emqtt_session.erl +++ b/apps/emqtt/src/emqtt_session.erl @@ -29,10 +29,11 @@ %% ------------------------------------------------------------------ %% API Function Exports %% ------------------------------------------------------------------ --export([start/1, resume/1, publish/2, puback/2]). +-export([start/1, resume/2, publish/2, puback/2, subscribe/2, unsubscribe/2]). %%start gen_server -export([start_link/3]). + %% ------------------------------------------------------------------ %% gen_server Function Exports %% ------------------------------------------------------------------ @@ -41,13 +42,13 @@ terminate/2, code_change/3]). -record(session_state, { - client_id, - client_pid, - packet_id = 1, - subscriptions = [], - messages = [], %% do not receive rel - awaiting_ack, - awaiting_rel, + client_id :: binary(), + client_pid :: pid(), + packet_id = 1, + submap :: map(), + messages = [], %% do not receive rel + awaiting_ack :: map(), + awaiting_rel :: map(), expires, max_queue }). @@ -66,54 +67,94 @@ start({false = CleanSess, ClientId, ClientPid}) -> %% ------------------------------------------------------------------ %% Session API %% ------------------------------------------------------------------ -resume(SessState = #session_state{}, _ClientPid) -> +resume(SessState = #session_state{}, _ClientPid) -> SessState; -resume(SessPid, ClientPid) when is_pid(SessPid) -> +resume(SessPid, ClientPid) when is_pid(SessPid) -> gen_server:cast(SessPid, {resume, ClientPid}), SessPid. publish(_, {?QOS_0, Message}) -> emqtt_router:route(Message); - %%TODO: publish(_, {?QOS_1, Message}) -> emqtt_router:route(Message); - %%TODO: -publish(Session = #session_state{awaiting_rel = Awaiting}, {?QOS_2, Message}) -> - %% store gb_tree: - Session#session_state{awaiting_rel = Awaiting}; +publish(SessState = #session_state{awaiting_rel = Awaiting}, + {?QOS_2, Message = #mqtt_message{ msgid = MsgId }}) -> + %% store in awaiting map + %%TODO: TIMEOUT + Awaiting1 = maps:put(MsgId, Message, Awaiting), + SessState#session_state{awaiting_rel = Awaiting1}; -publish(_, {?QOS_2, Message}) -> - %TODO: - %put({msg, PacketId}, pubrec), - emqtt_router:route(Message). +publish(SessPid, {?QOS_2, Message}) when is_pid(SessPid) -> + gen_server:cast(SessPid, {publish, ?QOS_2, Message}), + SessPid. -puback(_, {?PUBACK, PacketId}) -> - 'TODO'; -puback(_, {?PUBREC, PacketId}) -> - 'TODO'; -puback(_, {?PUBREL, PacketId}) -> +puback(SessState = #session_state{client_id = ClientId, awaiting_ack = Awaiting}, {?PUBACK, PacketId}) -> + Awaiting1 = + case maps:is_key(PacketId, Awaiting) of + true -> maps:remove(PacketId, Awaiting); + false -> lager:warning("~s puback packetid '~p' not exist", [ClientId, PacketId]) + end, + SessState#session_state{awaiting_ack= Awaiting1}; +puback(SessPid, {?PUBACK, PacketId}) when is_pid(SessPid) -> + gen_server:cast(SessPid, {puback, PacketId}), SessPid; + +puback(SessState = #session_state{}, {?PUBREC, PacketId}) -> + %%TODO' + SessState; +puback(SessPid, {?PUBREC, PacketId}) when is_pid(SessPid) -> + gen_server:cast(SessPid, {pubrec, PacketId}), SessPid; + +puback(SessState = #session_state{}, {?PUBREL, PacketId}) -> %FIXME Later: should release the message here - erase({msg, PacketId}), - 'TODO'; -puback(_, {?PUBCOMP, PacketId}) -> - 'TODO'. + %%emqtt_router:route(Message). + 'TODO', erase({msg, PacketId}), SessState; +puback(SessPid, {?PUBREL, PacketId}) when is_pid(SessPid) -> + gen_server:cast(SessPid, {pubrel, PacketId}), SessPid; -subscribe(Session, Topics) -> - %%TODO. - {ok, Session, [Qos || {_Name, Qos} <- Topics]}. +puback(SessState = #session_state{}, {?PUBCOMP, PacketId}) -> + 'TODO', SessState; +puback(SessPid, {?PUBCOMP, PacketId}) when is_pid(SessPid) -> + gen_server:cast(SessPid, {pubcomp, PacketId}), SessPid. -unsubscribe(Session, Topics) -> - %%TODO. - {ok, Session}. +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 = emqtt_pubsub:subscribe({Topic, Qos}, self()) || {Topic, Qos} <- Topics], + %%TODO: granted all? + GrantedQos = [Qos || {_Name, Qos} <- Topics], + {ok, SessState#session_state{submap = SubMap1}, GrantedQos}; + +subscribe(SessPid, Topics) when is_pid(SessPid) -> + {ok, GrantedQos} = gen_server:call(SessPid, {subscribe, Topics}), + {ok, SessPid, GrantedQos}. + +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(Topic, self()) || Topic <- Topics], + SubMap1 = lists:foldl(fun(Topic, Acc) -> maps:remove(Topic, Acc) end, SubMap, Topics), + {ok, SessState#session_state{submap = SubMap1}}; + +unsubscribe(SessPid, Topics) -> + gen_server:call(SessPid, {unsubscribe, Topics}), + {ok, SessPid}. initial_state(ClientId) -> - #session_state { client_id = ClientId, - packet_id = 1, - subscriptions = [], - awaiting_ack = gb_trees:empty(), - awaiting_rel = gb_trees:empty() }. + #session_state { client_id = ClientId, + packet_id = 1, + submap = #{}, + awaiting_ack = #{}, + awaiting_rel = #{} }. initial_state(ClientId, ClientPid) -> State = initial_state(ClientId), @@ -131,14 +172,43 @@ init([SessOpts, ClientId, ClientPid]) -> %%TODO: OK? true = link(ClientPid), State = initial_state(ClientId, ClientPid), - {ok, State#state{ expires = proplists:get_value(expires, SessOpts, 24) * 3600, - max_queue = proplists:get_value(max_queue, SessOpts, 1000) } }. + {ok, State#session_state{ + expires = proplists:get_value(expires, SessOpts, 24) * 3600, + max_queue = proplists:get_value(max_queue, SessOpts, 1000) } }. + +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(_Request, _From, State) -> {reply, ok, State}. -handle_cast(_Msg, State) -> - {noreply, State}. +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(Msg, State) -> + {stop, {badmsg, Msg}, State}. handle_info(_Info, State) -> {noreply, State}. diff --git a/apps/emqtt/src/emqtt_session_sup.erl b/apps/emqtt/src/emqtt_session_sup.erl index d4a118bbf..01ea4db7e 100644 --- a/apps/emqtt/src/emqtt_session_sup.erl +++ b/apps/emqtt/src/emqtt_session_sup.erl @@ -26,7 +26,7 @@ -behavior(supervisor). --export([start_link/0, start_session/2]). +-export([start_link/1, start_session/2]). -export([init/1]). diff --git a/apps/emqtt/src/emqtt_sm.erl b/apps/emqtt/src/emqtt_sm.erl index 7714034ef..e473e057c 100644 --- a/apps/emqtt/src/emqtt_sm.erl +++ b/apps/emqtt/src/emqtt_sm.erl @@ -55,7 +55,7 @@ -export([start_link/0]). --export([lookup_session/1, start_session/2, destroy_session/1]). +-export([lookup_session/1, start_session/2, destory_session/1]). %% ------------------------------------------------------------------ %% gen_server Function Exports @@ -75,7 +75,7 @@ -spec(start_session/2 :: (binary(), pid()) -> {ok, pid()} | {error, any()}). --spec(destroy_session/1 :: (binary()) -> ok). +-spec(destory_session/1 :: (binary()) -> ok). -endif. @@ -107,15 +107,15 @@ destory_session(ClientId) -> %% gen_server Function Definitions %% ------------------------------------------------------------------ -init() -> +init([]) -> process_flag(trap_exit, true), ets:new(?TABLE, [set, protected, named_table]), - {ok, State}. + {ok, #state{}}. handle_call({start_session, ClientId, ClientPid}, _From, State) -> Reply = case ets:lookup(?TABLE, ClientId) of - [{_, SessPid, MRef}] -> + [{_, SessPid, _MRef}] -> emqtt_session:resume(SessPid, ClientPid), {ok, SessPid}; [] -> From 946003737c663409f2a54139ebca306c20dcd475 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Wed, 14 Jan 2015 13:05:09 +0800 Subject: [PATCH 51/84] fix clietid validator --- apps/emqtt/src/emqtt_protocol.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 0e2a22354..451a73c27 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -349,7 +349,7 @@ 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 ) -> + when ( size(ClientId) >= 1 ) andalso ( size(ClientId) > ?MAX_CLIENTID_LEN ) -> true; %% MQTT3.1.1 allow null clientId. From eda40bf23256d70a8cceba1160270e0716a31528 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Wed, 14 Jan 2015 13:09:27 +0800 Subject: [PATCH 52/84] fix clientid --- apps/emqtt/src/emqtt_protocol.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 451a73c27..b43e240ae 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -349,7 +349,7 @@ 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 ) -> + when ( size(ClientId) >= 1 ) andalso ( size(ClientId) =< ?MAX_CLIENTID_LEN ) -> true; %% MQTT3.1.1 allow null clientId. From 22797172a4498a7e8ad0007766bbab874b8ccec7 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Wed, 14 Jan 2015 13:19:34 +0800 Subject: [PATCH 53/84] fix dispatch --- apps/emqtt/src/emqtt_client.erl | 2 +- apps/emqtt/src/emqtt_protocol.erl | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index a60c19100..4263242f6 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -98,7 +98,7 @@ handle_info({stop, duplicate_id, NewPid}, State=#state{conn_name=ConnName}) -> stop({shutdown, duplicate_id}, State); %%TODO: ok?? -handle_info({dispatch, From, Message}, #state{proto_state = ProtoState} = State) -> +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}}; diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index b43e240ae..889ca5ee6 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -222,8 +222,9 @@ puback_qos(?PUBREC) -> ?QOS_0; puback_qos(?PUBREL) -> ?QOS_1; puback_qos(?PUBCOMP) -> ?QOS_0. --spec send_message(Message, State) -> {ok, NewState} when - Message :: {pid(), mqtt_message()}, +-spec send_message({From, Message}, State) -> {ok, NewState} when + From :: pid(), + Message :: mqtt_message(), State :: proto_state(), NewState :: proto_state(). From 85be3eef4924dd82e0d052f62251d7b4bf9ef1d3 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Wed, 14 Jan 2015 13:34:07 +0800 Subject: [PATCH 54/84] stop when badmsg, badinfo --- apps/emqtt/src/emqtt_session.erl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/emqtt/src/emqtt_session.erl b/apps/emqtt/src/emqtt_session.erl index 20334ac93..b25667b0a 100644 --- a/apps/emqtt/src/emqtt_session.erl +++ b/apps/emqtt/src/emqtt_session.erl @@ -184,8 +184,8 @@ handle_call({unsubscribe, Topics}, _From, State) -> {ok, NewState} = unsubscribe(State, Topics), {reply, ok, NewState}; -handle_call(_Request, _From, State) -> - {reply, ok, State}. +handle_call(Req, _From, State) -> + {stop, {badreq, Req}, State}. handle_cast({publish, ?QOS_2, Message}, State) -> NewState = publish(State, {?QOS_2, Message}), @@ -210,8 +210,8 @@ handle_cast({pubcomp, PacketId}, State) -> handle_cast(Msg, State) -> {stop, {badmsg, Msg}, State}. -handle_info(_Info, State) -> - {noreply, State}. +handle_info(Info, State) -> + {stop, {badinfo, Info}, State}. terminate(_Reason, _State) -> ok. @@ -224,4 +224,3 @@ code_change(_OldVsn, State, _Extra) -> %% ------------------------------------------------------------------ - From fb56eee21de828d293a9c1dcab4a55a20eab332b Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Wed, 14 Jan 2015 14:08:39 +0800 Subject: [PATCH 55/84] session resume and expired --- apps/emqtt/src/emqtt_session.erl | 41 ++++++++++++++++++++++++++++---- apps/emqtt/src/emqtt_sm.erl | 2 +- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/apps/emqtt/src/emqtt_session.erl b/apps/emqtt/src/emqtt_session.erl index b25667b0a..373bbf551 100644 --- a/apps/emqtt/src/emqtt_session.erl +++ b/apps/emqtt/src/emqtt_session.erl @@ -29,7 +29,7 @@ %% ------------------------------------------------------------------ %% API Function Exports %% ------------------------------------------------------------------ --export([start/1, resume/2, publish/2, puback/2, subscribe/2, unsubscribe/2]). +-export([start/1, resume/3, publish/2, puback/2, subscribe/2, unsubscribe/2]). %%start gen_server -export([start_link/3]). @@ -50,6 +50,7 @@ awaiting_ack :: map(), awaiting_rel :: map(), expires, + expire_timer, max_queue }). %% ------------------------------------------------------------------ @@ -67,10 +68,10 @@ start({false = CleanSess, ClientId, ClientPid}) -> %% ------------------------------------------------------------------ %% Session API %% ------------------------------------------------------------------ -resume(SessState = #session_state{}, _ClientPid) -> +resume(SessState = #session_state{}, _ClientId, _ClientPid) -> SessState; -resume(SessPid, ClientPid) when is_pid(SessPid) -> - gen_server:cast(SessPid, {resume, ClientPid}), +resume(SessPid, ClientId, ClientPid) when is_pid(SessPid) -> + gen_server:cast(SessPid, {resume, ClientId, ClientPid}), SessPid. publish(_, {?QOS_0, Message}) -> @@ -187,6 +188,17 @@ handle_call({unsubscribe, Topics}, _From, State) -> handle_call(Req, _From, State) -> {stop, {badreq, Req}, State}. +handle_cast({resume, ClientId, ClientPid}, State = #session_state { + client_id = ClientId, + client_pid = undefined, + messages = Messages, + expire_timer = ETimer}) -> + lager:info("Session: client ~s resumed by ~p", [ClientId, ClientPid]), + erlang:cancel_timer(ETimer), + [ClientPid ! {dispatch, {self(), Message}} || Message <- Messages], + NewState = State#session_state{ client_pid = ClientPid, messages = [], expire_timer = undefined}, + {noreply, NewState}; + handle_cast({publish, ?QOS_2, Message}, State) -> NewState = publish(State, {?QOS_2, Message}), {noreply, NewState}; @@ -210,6 +222,27 @@ handle_cast({pubcomp, PacketId}, State) -> handle_cast(Msg, State) -> {stop, {badmsg, Msg}, State}. +handle_info({dispatch, {_From, Message}}, State = #session_state{ + client_pid = undefined, messages = Messages}) -> + %%TODO: queue len + NewState = State#session_state{messages = [Message | Messages]}, + {noreply, NewState}; + +handle_info({dispatch, {_From, Message}}, State = #session_state{client_pid = ClientPid}) -> + %%TODO: replace From with self(), ok? + ClientPid ! {dispatch, {self(), Message}}, + {noreply, 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 session expired!", [ClientId]), + {stop, {shutdown, expired}, State}; + handle_info(Info, State) -> {stop, {badinfo, Info}, State}. diff --git a/apps/emqtt/src/emqtt_sm.erl b/apps/emqtt/src/emqtt_sm.erl index e473e057c..406c699be 100644 --- a/apps/emqtt/src/emqtt_sm.erl +++ b/apps/emqtt/src/emqtt_sm.erl @@ -116,7 +116,7 @@ handle_call({start_session, ClientId, ClientPid}, _From, State) -> Reply = case ets:lookup(?TABLE, ClientId) of [{_, SessPid, _MRef}] -> - emqtt_session:resume(SessPid, ClientPid), + emqtt_session:resume(SessPid, ClientId, ClientPid), {ok, SessPid}; [] -> case emqtt_session_sup:start_session(ClientId, ClientPid) of From b10c9c7dfec87a668d90e75f247dd14892a3a28f Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Wed, 14 Jan 2015 14:09:21 +0800 Subject: [PATCH 56/84] session expired by 1 hour --- rel/files/app.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rel/files/app.config b/rel/files/app.config index 1a3b1ca69..e64fda7cf 100644 --- a/rel/files/app.config +++ b/rel/files/app.config @@ -34,7 +34,7 @@ {emqtt, [ {auth, {anonymous, []}}, %internal, anonymous {session, [ - {expires, 24}, + {expires, 1}, {max_queue, 1000}, {qos0, false} ]}, From 40faeddd0e7748c88314a3202c622cfcdd3ba415 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Wed, 14 Jan 2015 14:15:06 +0800 Subject: [PATCH 57/84] session: reverse cached masseges --- apps/emqtt/src/emqtt_session.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqtt/src/emqtt_session.erl b/apps/emqtt/src/emqtt_session.erl index 373bbf551..72c2757fb 100644 --- a/apps/emqtt/src/emqtt_session.erl +++ b/apps/emqtt/src/emqtt_session.erl @@ -195,7 +195,7 @@ handle_cast({resume, ClientId, ClientPid}, State = #session_state { expire_timer = ETimer}) -> lager:info("Session: client ~s resumed by ~p", [ClientId, ClientPid]), erlang:cancel_timer(ETimer), - [ClientPid ! {dispatch, {self(), Message}} || Message <- Messages], + [ClientPid ! {dispatch, {self(), Message}} || Message <- lists:reverse(Messages)], NewState = State#session_state{ client_pid = ClientPid, messages = [], expire_timer = undefined}, {noreply, NewState}; From f54986dd0dc710865acbc8cd8cf174761c07b1a4 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Wed, 14 Jan 2015 17:54:27 +0800 Subject: [PATCH 58/84] session destroy --- apps/emqtt/src/emqtt_session.erl | 17 ++++++++++++----- apps/emqtt/src/emqtt_sm.erl | 12 ++++++------ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/apps/emqtt/src/emqtt_session.erl b/apps/emqtt/src/emqtt_session.erl index 72c2757fb..2c1304e03 100644 --- a/apps/emqtt/src/emqtt_session.erl +++ b/apps/emqtt/src/emqtt_session.erl @@ -29,7 +29,7 @@ %% ------------------------------------------------------------------ %% API Function Exports %% ------------------------------------------------------------------ --export([start/1, resume/3, publish/2, puback/2, subscribe/2, unsubscribe/2]). +-export([start/1, resume/3, publish/2, puback/2, subscribe/2, unsubscribe/2, destroy/2]). %%start gen_server -export([start_link/3]). @@ -56,12 +56,12 @@ %% ------------------------------------------------------------------ %% Start Session %% ------------------------------------------------------------------ -start({true = CleanSess, ClientId, _ClientPid}) -> +start({true = _CleanSess, ClientId, _ClientPid}) -> %%Destroy old session if CleanSess is true before. - ok = emqtt_sm:destory_session(ClientId), + ok = emqtt_sm:destroy_session(ClientId), {ok, initial_state(ClientId)}; -start({false = CleanSess, ClientId, ClientPid}) -> +start({false = _CleanSess, ClientId, ClientPid}) -> {ok, SessPid} = emqtt_sm:start_session(ClientId, ClientPid), {ok, SessPid}. @@ -146,10 +146,13 @@ unsubscribe(SessState = #session_state{client_id = ClientId, submap = SubMap}, T SubMap1 = lists:foldl(fun(Topic, Acc) -> maps:remove(Topic, Acc) end, SubMap, Topics), {ok, SessState#session_state{submap = SubMap1}}; -unsubscribe(SessPid, Topics) -> +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}). + initial_state(ClientId) -> #session_state { client_id = ClientId, packet_id = 1, @@ -219,6 +222,10 @@ 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}. diff --git a/apps/emqtt/src/emqtt_sm.erl b/apps/emqtt/src/emqtt_sm.erl index 406c699be..f98dc27d3 100644 --- a/apps/emqtt/src/emqtt_sm.erl +++ b/apps/emqtt/src/emqtt_sm.erl @@ -55,7 +55,7 @@ -export([start_link/0]). --export([lookup_session/1, start_session/2, destory_session/1]). +-export([lookup_session/1, start_session/2, destroy_session/1]). %% ------------------------------------------------------------------ %% gen_server Function Exports @@ -75,7 +75,7 @@ -spec(start_session/2 :: (binary(), pid()) -> {ok, pid()} | {error, any()}). --spec(destory_session/1 :: (binary()) -> ok). +-spec(destroy_session/1 :: (binary()) -> ok). -endif. @@ -100,8 +100,8 @@ lookup_session(ClientId) -> start_session(ClientId, ClientPid) -> gen_server:call(?SERVER, {start_session, ClientId, ClientPid}). -destory_session(ClientId) -> - gen_server:call(?SERVER, {destory_session, ClientId}). +destroy_session(ClientId) -> + gen_server:call(?SERVER, {destroy_session, ClientId}). %% ------------------------------------------------------------------ %% gen_server Function Definitions @@ -130,11 +130,11 @@ handle_call({start_session, ClientId, ClientPid}, _From, State) -> end, {reply, Reply, State}; -handle_call({destory_session, ClientId}, _From, State) -> +handle_call({destroy_session, ClientId}, _From, State) -> case ets:lookup(?TABLE, ClientId) of [{_, SessPid, MRef}] -> erlang:demonitor(MRef), - emqtt_session:destory(SessPid), + emqtt_session:destory(SessPid, ClientId), ets:delete(?TABLE, ClientId); [] -> ignore From 8391eeb1ddf7962cb3e5ff216726cd84594427b6 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Wed, 14 Jan 2015 17:55:58 +0800 Subject: [PATCH 59/84] destory -> destroy --- apps/emqtt/src/emqtt_sm.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqtt/src/emqtt_sm.erl b/apps/emqtt/src/emqtt_sm.erl index f98dc27d3..439ce3a30 100644 --- a/apps/emqtt/src/emqtt_sm.erl +++ b/apps/emqtt/src/emqtt_sm.erl @@ -134,7 +134,7 @@ handle_call({destroy_session, ClientId}, _From, State) -> case ets:lookup(?TABLE, ClientId) of [{_, SessPid, MRef}] -> erlang:demonitor(MRef), - emqtt_session:destory(SessPid, ClientId), + emqtt_session:destroy(SessPid, ClientId), ets:delete(?TABLE, ClientId); [] -> ignore From 890b429faded2058cee42441f52d9a7e8061d321 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Wed, 14 Jan 2015 18:25:59 +0800 Subject: [PATCH 60/84] shutdown, keepalive_timeout --- apps/emqtt/src/emqtt_client.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index 4263242f6..01a728b8d 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -126,7 +126,7 @@ 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, normal, State}; + stop({shutdown, keepalive_timeout}, State); {resumed, KeepAlive1} -> lager:info("Client ~s: Keepalive Resumed", [State#state.peer_name]), {noreply, State#state{ keepalive = KeepAlive1 }} From 525a1049766f7885843dff8f60d02e558309b59f Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Wed, 14 Jan 2015 19:38:35 +0800 Subject: [PATCH 61/84] will message --- apps/emqtt/src/emqtt_client.erl | 38 +++++++++++++------------------ apps/emqtt/src/emqtt_protocol.erl | 32 ++++++++++++++------------ 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index 01a728b8d..f438e16ef 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -113,12 +113,12 @@ handle_info({inet_async, Sock, _Ref, {ok, Data}}, #state{ peer_name = PeerName, handle_info({inet_async, _Sock, _Ref, {error, Reason}}, State) -> network_error(Reason, State); -handle_info({inet_reply, _Sock, {error, Reason}}, State) -> - lager:critical("unexpected inet_reply '~p'", [Reason]), +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({keepalive, start, TimeoutSec}, State = #state{socket = Socket}) -> - lager:info("Client: ~s: Start KeepAlive with ~p seconds", [State#state.peer_name, TimeoutSec]), + 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 }}; @@ -126,27 +126,25 @@ 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); + 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; -handle_info(Info, State) -> - lager:error("badinfo :~p",[Info]), +handle_info(Info, State = #state{peer_name = PeerName}) -> + lager:critical("Client ~s: unexpected info ~p",[PeerName, Info]), {stop, {badinfo, Info}, State}. -terminate(Reason, #state{proto_state = unefined}) -> - io:format("client terminated: ~p, reason: ~p~n", [self(), Reason]), - %%TODO: fix keep_alive... - %%emqtt_keep_alive:cancel(KeepAlive), - %emqtt_protocol:connection_lost(ProtoState), - ok; - -terminate(_Reason, #state { keepalive = KeepAlive, proto_state = ProtoState }) -> - %%TODO: fix keep_alive... +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), - emqtt_protocol:connection_lost(ProtoState), + case {ProtoState, Reason} of + {undefined, _} -> ok; + {_, {shutdown, Error}} -> + emqtt_protocol:shutdown(Error, ProtoState); + {_, _} -> ok %TODO: + end, ok. code_change(_OldVsn, State, _Extra) -> @@ -194,12 +192,8 @@ process_received_bytes(Bytes, end. %%---------------------------------------------------------------------------- -network_error(Reason, - State = #state{ conn_name = ConnStr}) -> - lager:error("MQTT detected network error '~p' for ~p", [Reason, ConnStr]), - %%TODO: where to SEND WILL MSG?? - %%send_will_msg(State), - % todo: flush channel after publish +network_error(Reason, State = #state{ peer_name = PeerName, conn_name = ConnStr }) -> + lager:error("Client ~s: MQTT detected network error '~p'", [PeerName, Reason]), stop({shutdown, conn_closed}, State). run_socket(State = #state{ conn_state = blocked }) -> diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 889ca5ee6..a99f062fe 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -32,7 +32,7 @@ -export([initial_state/2]). --export([handle_packet/2, send_message/2, send_packet/2, connection_lost/1]). +-export([handle_packet/2, send_message/2, send_packet/2, shutdown/2]). -export([info/1]). @@ -125,7 +125,7 @@ handle_packet(?CONNECT, Packet = #mqtt_packet { ClientId1 = clientid(ClientId, State), start_keepalive(KeepAlive), emqtt_cm:register(ClientId1, self()), - {?CONNACK_ACCEPT, State#proto_state{ will_msg = make_will_msg(Var), + {?CONNACK_ACCEPT, State#proto_state{ will_msg = make_willmsg(Var), clean_sess = CleanSess, client_id = ClientId1 }}; false -> @@ -207,8 +207,9 @@ handle_packet(?PINGREQ, #mqtt_packet{}, State) -> send_packet(make_packet(?PINGRESP), State); handle_packet(?DISCONNECT, #mqtt_packet{}, State) -> - %%how to handle session? - {stop, normal, 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 } }. @@ -266,10 +267,11 @@ send_packet(Packet, State = #proto_state{socket = Sock, peer_name = PeerName, cl erlang:port_command(Sock, Data), {ok, State}. -%%TODO: fix me later... -connection_lost(#proto_state{client_id = ClientId} = State) -> +shutdown(Error, State = #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. - %emqtt_cm:unregister(ClientId, self()). make_message(#mqtt_packet { header = #mqtt_packet_header{ @@ -288,10 +290,10 @@ make_message(#mqtt_packet { msgid = PacketId, payload = Payload}. -make_will_msg(#mqtt_packet_connect{ will_flag = false }) -> +make_willmsg(#mqtt_packet_connect{ will_flag = false }) -> undefined; -make_will_msg(#mqtt_packet_connect{ will_retain = Retain, +make_willmsg(#mqtt_packet_connect{ will_retain = Retain, will_qos = Qos, will_topic = Topic, will_msg = Msg }) -> @@ -307,8 +309,6 @@ next_packet_id(State = #proto_state{ packet_id = PacketId }) -> State #proto_state{ packet_id = PacketId + 1 }. - - clientid(<<>>, #proto_state{peer_name = PeerName}) -> <<"eMQTT/", (base64:encode(PeerName))/binary>>; @@ -320,10 +320,9 @@ maybe_clean_sess(false, _Conn, _ClientId) -> %%---------------------------------------------------------------------------- -send_will_msg(#proto_state{will_msg = undefined}) -> - ignore; -send_will_msg(#proto_state{will_msg = WillMsg }) -> - emqtt_router:route(WillMsg). +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 -> @@ -394,3 +393,6 @@ 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()). + From d1ac732b8c08599a8b1b99e9ac63584ba078c895 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Wed, 14 Jan 2015 23:57:17 +0800 Subject: [PATCH 62/84] validate empty topics --- apps/emqtt/src/emqtt_protocol.erl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index a99f062fe..72e9813d4 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -381,6 +381,10 @@ validate_packet(#mqtt_packet{ header = #mqtt_packet_header { type = ?UNSUBSCRIB 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))], From 0ae43e316c0947b97492b8ac92bedb5739840015 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Thu, 15 Jan 2015 00:02:05 +0800 Subject: [PATCH 63/84] subscribe, unsubscribe topics --- apps/emqtt/src/emqtt_session.erl | 8 ++++---- apps/emqtt/src/emqtt_sm.erl | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/emqtt/src/emqtt_session.erl b/apps/emqtt/src/emqtt_session.erl index 2c1304e03..42e47e27e 100644 --- a/apps/emqtt/src/emqtt_session.erl +++ b/apps/emqtt/src/emqtt_session.erl @@ -126,9 +126,9 @@ subscribe(SessState = #session_state{client_id = ClientId, submap = SubMap}, Top _ -> lager:warning("~s resubscribe ~p", [ClientId, Resubs]) end, SubMap1 = lists:foldl(fun({Name, Qos}, Acc) -> maps:put(Name, Qos, Acc) end, SubMap, Topics), - [ok = emqtt_pubsub:subscribe({Topic, Qos}, self()) || {Topic, Qos} <- Topics], - %%TODO: granted all? - GrantedQos = [Qos || {_Name, Qos} <- Topics], + {ok, GrantedQos} = emqtt_pubsub:subscribe(Topics, self()), + %[ok = emqtt_pubsub:subscribe({Topic, Qos}, self()) || {Topic, Qos} <- Topics], + %GrantedQos = [Qos || {_Name, Qos} <- Topics], {ok, SessState#session_state{submap = SubMap1}, GrantedQos}; subscribe(SessPid, Topics) when is_pid(SessPid) -> @@ -142,7 +142,7 @@ unsubscribe(SessState = #session_state{client_id = ClientId, submap = SubMap}, T BadUnsubs -> lager:warning("~s should not unsubscribe ~p", [ClientId, BadUnsubs]) end, %%unsubscribe from topic tree - [ok = emqtt_pubsub:unsubscribe(Topic, self()) || Topic <- Topics], + 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}}; diff --git a/apps/emqtt/src/emqtt_sm.erl b/apps/emqtt/src/emqtt_sm.erl index 439ce3a30..c9db9c710 100644 --- a/apps/emqtt/src/emqtt_sm.erl +++ b/apps/emqtt/src/emqtt_sm.erl @@ -87,7 +87,6 @@ %% API Function Definitions %% ------------------------------------------------------------------ - start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). From 45b63a6b13a41915c70d309d4d0afe2a8a0d8023 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Thu, 15 Jan 2015 00:03:10 +0800 Subject: [PATCH 64/84] fix issue#39 remove old subscription with different Qos #39 --- apps/emqtt/src/emqtt_pubsub.erl | 101 +++++++++++++++++++++++--------- 1 file changed, 73 insertions(+), 28 deletions(-) diff --git a/apps/emqtt/src/emqtt_pubsub.erl b/apps/emqtt/src/emqtt_pubsub.erl index a3c73a9bb..51274128d 100644 --- a/apps/emqtt/src/emqtt_pubsub.erl +++ b/apps/emqtt/src/emqtt_pubsub.erl @@ -60,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, {}). %% ------------------------------------------------------------------ @@ -75,25 +89,26 @@ 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 :: mqtt_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. @@ -143,29 +158,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) -> @@ -196,6 +205,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 +263,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)], From 999c2b5ebdbcd2512fde3a8486d0ecc91bfab6e3 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Thu, 15 Jan 2015 23:50:37 +0800 Subject: [PATCH 65/84] supprot qos0, qos1, qos2 --- apps/emqtt/include/emqtt.hrl | 2 +- apps/emqtt/src/emqtt_client.erl | 11 ++- apps/emqtt/src/emqtt_message.erl | 89 +++++++++++++++++ apps/emqtt/src/emqtt_protocol.erl | 142 +++++++++------------------ apps/emqtt/src/emqtt_pubsub.erl | 10 +- apps/emqtt/src/emqtt_queue.erl | 69 ++++++------- apps/emqtt/src/emqtt_router.erl | 15 ++- apps/emqtt/src/emqtt_session.erl | 156 ++++++++++++++++++++---------- 8 files changed, 300 insertions(+), 194 deletions(-) create mode 100644 apps/emqtt/src/emqtt_message.erl diff --git a/apps/emqtt/include/emqtt.hrl b/apps/emqtt/include/emqtt.hrl index 2d172945d..c73fc8f71 100644 --- a/apps/emqtt/include/emqtt.hrl +++ b/apps/emqtt/include/emqtt.hrl @@ -68,10 +68,10 @@ %% MQTT Message %%------------------------------------------------------------------------------ -record(mqtt_message, { + msgid :: integer() | undefined, qos = ?QOS_0 :: mqtt_qos(), retain = false :: boolean(), dup = false :: boolean(), - msgid :: integer(), topic :: binary(), payload :: binary() }). diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index f438e16ef..d0b4774f2 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -90,11 +90,12 @@ handle_cast(Msg, State) -> handle_info(timeout, State) -> stop({shutdown, timeout}, State); -handle_info({stop, duplicate_id, NewPid}, State=#state{conn_name=ConnName}) -> +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", [ClientId, ConnName]), + lager:error("Shutdown for duplicate clientid: ~s, conn:~s", + [emqtt_protocol:client_id(ProtoState), ConnName]), stop({shutdown, duplicate_id}, State); %%TODO: ok?? @@ -105,8 +106,8 @@ handle_info({dispatch, {From, Message}}, #state{proto_state = ProtoState} = Stat handle_info({inet_reply, _Ref, ok}, State) -> {noreply, State, hibernate}; -handle_info({inet_async, Sock, _Ref, {ok, Data}}, #state{ peer_name = PeerName, socket = Sock } = State) -> - lager:debug("RECV from ~s: ~p", [State#state.peer_name, Data]), +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 })); @@ -192,7 +193,7 @@ process_received_bytes(Bytes, end. %%---------------------------------------------------------------------------- -network_error(Reason, State = #state{ peer_name = PeerName, conn_name = ConnStr }) -> +network_error(Reason, State = #state{ peer_name = PeerName }) -> lager:error("Client ~s: MQTT detected network error '~p'", [PeerName, Reason]), stop({shutdown, conn_closed}, State). diff --git a/apps/emqtt/src/emqtt_message.erl b/apps/emqtt/src/emqtt_message.erl new file mode 100644 index 000000000..5ea06405d --- /dev/null +++ b/apps/emqtt/src/emqtt_message.erl @@ -0,0 +1,89 @@ +%%----------------------------------------------------------------------------- +%% Copyright (c) 2012-2015, Feng Lee +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in all +%% copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. +%%------------------------------------------------------------------------------ + +-module(emqtt_message). + +-include("emqtt.hrl"). + +-include("emqtt_packet.hrl"). + +-export([to_packet/1]). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-spec( from_packet( mqtt_packet() ) -> mqtt_message() | undefined ). + +-spec( to_packet( mqtt_message() ) -> mqtt_packet() ). + +-endif + +%%---------------------------------------------------------------------------- + +from_packet(#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 }) -> + #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 }. + +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 }. + diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 72e9813d4..364d60398 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -30,29 +30,42 @@ %% API Function Exports %% ------------------------------------------------------------------ --export([initial_state/2]). +-export([initial_state/2, client_id/1]). -export([handle_packet/2, send_message/2, send_packet/2, shutdown/2]). -export([info/1]). + %% ------------------------------------------------------------------ %% Protocol State %% ------------------------------------------------------------------ --record(proto_state, { - socket, +-record(proto_state, { + socket, peer_name, connected = false, %received CONNECT action? - proto_vsn, + proto_vsn, proto_name, - packet_id, + %packet_id, client_id, clean_sess, session, %% session state or session pid will_msg }). --type proto_state() :: #proto_state{}. +%%---------------------------------------------------------------------------- + +-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 }}). @@ -62,28 +75,23 @@ initial_state(Socket, Peername) -> #proto_state{ socket = Socket, - peer_name = Peername, - packet_id = 1 + 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, - packet_id = PacketId, client_id = ClientId, clean_sess = CleanSess, will_msg = WillMsg }) -> - [ {packet_id, PacketId}, - {proto_vsn, ProtoVsn}, + [ {proto_vsn, ProtoVsn}, {proto_name, ProtoName}, {client_id, ClientId}, {clean_sess, CleanSess}, {will_msg, WillMsg} ]. --spec handle_packet(Packet, State) -> {ok, NewState} | {error, any()} when - Packet :: mqtt_packet(), - State :: proto_state(), - NewState :: proto_state(). %%CONNECT – Client requests a connection to a Server @@ -125,7 +133,7 @@ handle_packet(?CONNECT, Packet = #mqtt_packet { ClientId1 = clientid(ClientId, State), start_keepalive(KeepAlive), emqtt_cm:register(ClientId1, self()), - {?CONNACK_ACCEPT, State#proto_state{ will_msg = make_willmsg(Var), + {?CONNACK_ACCEPT, State#proto_state{ will_msg = willmsg(Var), clean_sess = CleanSess, client_id = ClientId1 }}; false -> @@ -145,21 +153,21 @@ handle_packet(?CONNECT, Packet = #mqtt_packet { handle_packet(?PUBLISH, Packet = #mqtt_packet { header = #mqtt_packet_header {qos = ?QOS_0}}, State = #proto_state{session = Session}) -> - emqtt_session:publish(Session, {?QOS_0, make_message(Packet)}), + emqtt_session:publish(Session, {?QOS_0, emqtt_messsage: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, make_message(Packet)}), + emqtt_session:publish(Session, {?QOS_1, emqtt_messsage: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, make_message(Packet)}), + 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) }, @@ -188,9 +196,9 @@ handle_packet(?SUBSCRIBE, #mqtt_packet { 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); + variable = #mqtt_packet_suback{ packet_id = PacketId, + qos_table = GrantedQos }}, + State#proto_state{ session = NewSession }); handle_packet(?UNSUBSCRIBE, #mqtt_packet { variable = #mqtt_packet_subscribe{ @@ -223,41 +231,19 @@ puback_qos(?PUBREC) -> ?QOS_0; puback_qos(?PUBREL) -> ?QOS_1; puback_qos(?PUBCOMP) -> ?QOS_0. --spec send_message({From, Message}, State) -> {ok, NewState} when - From :: pid(), - Message :: mqtt_message(), - State :: proto_state(), - NewState :: proto_state(). +%% qos0 message +send_message({_From, Message = #mqtt_message{ qos = ?QOS_0 }}, State) -> + send_packet(emqtt_message:to_packet(Message), State); -send_message({From, Message = #mqtt_message{ - retain = Retain, - qos = Qos, - topic = Topic, - dup = Dup, - payload = Payload}}, - State = #proto_state{packet_id = PacketId}) -> +%% 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); - Packet = #mqtt_packet { - header = #mqtt_packet_header { - type = ?PUBLISH, - qos = Qos, - retain = Retain, - dup = Dup }, - variable = #mqtt_packet_publish { - topic_name = Topic, - packet_id = if - Qos == ?QOS_0 -> undefined; - true -> PacketId - end }, - payload = Payload}, - - send_packet(Packet, State), - if - Qos == ?QOS_0 -> - {ok, State}; - true -> - {ok, next_packet_id(State)} - end. +%% 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)]), @@ -267,57 +253,20 @@ send_packet(Packet, State = #proto_state{socket = Sock, peer_name = PeerName, cl erlang:port_command(Sock, Data), {ok, State}. -shutdown(Error, State = #proto_state{peer_name = PeerName, client_id = ClientId, will_msg = WillMsg}) -> +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. -make_message(#mqtt_packet { - header = #mqtt_packet_header{ - qos = Qos, - retain = Retain, - dup = Dup }, - variable = #mqtt_packet_publish{ - topic_name = Topic, - packet_id = PacketId }, - payload = Payload }) -> - - #mqtt_message{ retain = Retain, - qos = Qos, - topic = Topic, - dup = Dup, - msgid = PacketId, - payload = Payload}. - -make_willmsg(#mqtt_packet_connect{ will_flag = false }) -> - undefined; - -make_willmsg(#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 }. - -next_packet_id(State = #proto_state{ packet_id = 16#ffff }) -> - State #proto_state{ packet_id = 1 }; -next_packet_id(State = #proto_state{ packet_id = PacketId }) -> - State #proto_state{ packet_id = PacketId + 1 }. - +willmsg(Packet) when is_record(Packet, mqtt_packet_connect) -> + emqtt_packet:from_packet(Packet). clientid(<<>>, #proto_state{peer_name = PeerName}) -> <<"eMQTT/", (base64:encode(PeerName))/binary>>; clientid(ClientId, _State) -> ClientId. -maybe_clean_sess(false, _Conn, _ClientId) -> - % todo: establish subscription to deliver old unacknowledged messages - ok. - %%---------------------------------------------------------------------------- send_willmsg(undefined) -> ignore; @@ -328,7 +277,6 @@ start_keepalive(0) -> ignore; start_keepalive(Sec) when Sec > 0 -> self() ! {keepalive, start, round(Sec * 1.5)}. - %%---------------------------------------------------------------------------- %% Validate Packets %%---------------------------------------------------------------------------- @@ -365,7 +313,7 @@ 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:error("Error Publish Topic: ~p", [Topic]), {error, badtopic} + false -> lager:warning("Error publish topic: ~p", [Topic]), {error, badtopic} end; validate_packet(#mqtt_packet { header = #mqtt_packet_header { type = ?SUBSCRIBE }, diff --git a/apps/emqtt/src/emqtt_pubsub.erl b/apps/emqtt/src/emqtt_pubsub.erl index 51274128d..c05e344f1 100644 --- a/apps/emqtt/src/emqtt_pubsub.erl +++ b/apps/emqtt/src/emqtt_pubsub.erl @@ -127,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, {self(), 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) -> diff --git a/apps/emqtt/src/emqtt_queue.erl b/apps/emqtt/src/emqtt_queue.erl index bb6e3cb77..c025be04f 100644 --- a/apps/emqtt/src/emqtt_queue.erl +++ b/apps/emqtt/src/emqtt_queue.erl @@ -21,53 +21,54 @@ %%------------------------------------------------------------------------------ -module(emqtt_queue). --behaviour(gen_server). +-include("emqtt.hrl"). --define(SERVER, ?MODULE). +-export([new/1, new/2, in/3, all/1, clear/1]). -%% ------------------------------------------------------------------ -%% API Function Exports -%% ------------------------------------------------------------------ +%%---------------------------------------------------------------------------- --export([start_link/0]). +-ifdef(use_specs). -%% ------------------------------------------------------------------ -%% gen_server Function Exports -%% ------------------------------------------------------------------ +-type(mqtt_queue() :: #mqtt_queue_wrapper{}). --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). +-spec(new(non_neg_intger()) -> mqtt_queue()). -%% ------------------------------------------------------------------ -%% API Function Definitions -%% ------------------------------------------------------------------ +-spec(in(binary(), mqtt_message(), mqtt_queue()) -> mqtt_queue()). -start_link() -> - gen_server:start_link(?MODULE, [], []). +-spec(all(mqtt_queue()) -> list()). -%% ------------------------------------------------------------------ -%% gen_server Function Definitions -%% ------------------------------------------------------------------ +-spec(clear(mqtt_queue()) -> mqtt_queue()). -init(Args) -> - {ok, Args}. +-endif. -handle_call(_Request, _From, State) -> - {reply, ok, State}. +%%---------------------------------------------------------------------------- -handle_cast(_Msg, State) -> - {noreply, State}. +-define(DEFAULT_MAX_LEN, 1000). -handle_info(_Info, State) -> - {noreply, State}. +-record(mqtt_queue_wrapper, { queue = queue:new(), max_len = ?DEFAULT_MAX_LEN, store_qos0 = false }). -terminate(_Reason, _State) -> - ok. +new(MaxLen) -> #mqtt_queue_wrapper{ max_len = MaxLen }. -code_change(_OldVsn, State, _Extra) -> - {ok, State}. +new(MaxLen, StoreQos0) -> #mqtt_queue_wrapper{ max_len = MaxLen, store_qos0 = StoreQos0 }. -%% ------------------------------------------------------------------ -%% Internal Function Definitions -%% ------------------------------------------------------------------ +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() }. diff --git a/apps/emqtt/src/emqtt_router.erl b/apps/emqtt/src/emqtt_router.erl index 9b552e8d3..e7e6bf033 100644 --- a/apps/emqtt/src/emqtt_router.erl +++ b/apps/emqtt/src/emqtt_router.erl @@ -47,6 +47,16 @@ -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 %% ------------------------------------------------------------------ @@ -54,9 +64,8 @@ start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). --spec route(Msg :: mqtt_message()) -> any(). -route(Msg) -> - emqtt_pubsub:publish(retained(Msg)). +route(Message) -> + emqtt_pubsub:publish(retained(Message)). %% ------------------------------------------------------------------ %% gen_server Function Definitions diff --git a/apps/emqtt/src/emqtt_session.erl b/apps/emqtt/src/emqtt_session.erl index 42e47e27e..c4abd8e3f 100644 --- a/apps/emqtt/src/emqtt_session.erl +++ b/apps/emqtt/src/emqtt_session.erl @@ -31,6 +31,8 @@ %% ------------------------------------------------------------------ -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]). @@ -44,14 +46,14 @@ -record(session_state, { client_id :: binary(), client_pid :: pid(), - packet_id = 1, + message_id = 1, submap :: map(), - messages = [], %% do not receive rel + msg_queue, %% do not receive rel awaiting_ack :: map(), awaiting_rel :: map(), + awaiting_comp :: map(), expires, - expire_timer, - max_queue }). + expire_timer }). %% ------------------------------------------------------------------ %% Start Session @@ -74,51 +76,69 @@ resume(SessPid, ClientId, ClientPid) when is_pid(SessPid) -> gen_server:cast(SessPid, {resume, ClientId, ClientPid}), SessPid. -publish(_, {?QOS_0, Message}) -> - emqtt_router:route(Message); -%%TODO: -publish(_, {?QOS_1, Message}) -> - emqtt_router:route(Message); -%%TODO: -publish(SessState = #session_state{awaiting_rel = Awaiting}, +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 map - %%TODO: TIMEOUT - Awaiting1 = maps:put(MsgId, Message, Awaiting), - SessState#session_state{awaiting_rel = Awaiting1}; + %% 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}) -> - Awaiting1 = case maps:is_key(PacketId, Awaiting) of - true -> maps:remove(PacketId, Awaiting); - false -> lager:warning("~s puback packetid '~p' not exist", [ClientId, PacketId]) + true -> ok; + false -> lager:warning("Session ~s: PUBACK PacketId '~p' not found!", [ClientId, PacketId]) end, - SessState#session_state{awaiting_ack= Awaiting1}; + SessState#session_state{awaiting_ack = maps:remove(PacketId, Awaiting)}; puback(SessPid, {?PUBACK, PacketId}) when is_pid(SessPid) -> gen_server:cast(SessPid, {puback, PacketId}), SessPid; -puback(SessState = #session_state{}, {?PUBREC, PacketId}) -> - %%TODO' - SessState; +%% 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; -puback(SessState = #session_state{}, {?PUBREL, PacketId}) -> - %FIXME Later: should release the message here - %%emqtt_router:route(Message). - 'TODO', erase({msg, PacketId}), SessState; +%% 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; -puback(SessState = #session_state{}, {?PUBCOMP, PacketId}) -> - 'TODO', SessState; +%% 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 @@ -127,14 +147,15 @@ subscribe(SessState = #session_state{client_id = ClientId, submap = SubMap}, Top end, SubMap1 = lists:foldl(fun({Name, Qos}, Acc) -> maps:put(Name, Qos, Acc) end, SubMap, Topics), {ok, GrantedQos} = emqtt_pubsub:subscribe(Topics, self()), - %[ok = emqtt_pubsub:subscribe({Topic, Qos}, self()) || {Topic, Qos} <- Topics], - %GrantedQos = [Qos || {_Name, Qos} <- Topics], {ok, SessState#session_state{submap = SubMap1}, GrantedQos}; subscribe(SessPid, Topics) when is_pid(SessPid) -> {ok, GrantedQos} = gen_server:call(SessPid, {subscribe, Topics}), {ok, SessPid, GrantedQos}. +%% +%% @doc UNSUBSCRIBE +%% unsubscribe(SessState = #session_state{client_id = ClientId, submap = SubMap}, Topics) -> %%TODO: refactor later. case Topics -- maps:keys(SubMap) of @@ -153,12 +174,25 @@ unsubscribe(SessPid, Topics) when is_pid(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, - packet_id = 1, submap = #{}, awaiting_ack = #{}, - awaiting_rel = #{} }. + awaiting_rel = #{}, + awaiting_comp = #{} }. initial_state(ClientId, ClientPid) -> State = initial_state(ClientId), @@ -173,12 +207,14 @@ start_link(SessOpts, ClientId, ClientPid) -> init([SessOpts, ClientId, ClientPid]) -> process_flag(trap_exit, true), - %%TODO: OK? + %%TODO: Is this OK? true = link(ClientPid), State = initial_state(ClientId, ClientPid), - {ok, State#session_state{ - expires = proplists:get_value(expires, SessOpts, 24) * 3600, - max_queue = proplists:get_value(max_queue, SessOpts, 1000) } }. + 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), @@ -194,13 +230,13 @@ handle_call(Req, _From, State) -> handle_cast({resume, ClientId, ClientPid}, State = #session_state { client_id = ClientId, client_pid = undefined, - messages = Messages, + msg_queue = Queue, expire_timer = ETimer}) -> lager:info("Session: client ~s resumed by ~p", [ClientId, ClientPid]), erlang:cancel_timer(ETimer), - [ClientPid ! {dispatch, {self(), Message}} || Message <- lists:reverse(Messages)], - NewState = State#session_state{ client_pid = ClientPid, messages = [], expire_timer = undefined}, - {noreply, NewState}; + [ClientPid ! {dispatch, {self(), Message}} || Message <- 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}), @@ -223,22 +259,14 @@ handle_cast({pubcomp, PacketId}, State) -> {noreply, NewState}; handle_cast({destroy, ClientId}, State = #session_state{client_id = ClientId}) -> - lager:warning("Session: ~s destroyed", [ClientId]), + lager:warning("Session ~s destroyed", [ClientId]), {stop, normal, State}; handle_cast(Msg, State) -> {stop, {badmsg, Msg}, State}. -handle_info({dispatch, {_From, Message}}, State = #session_state{ - client_pid = undefined, messages = Messages}) -> - %%TODO: queue len - NewState = State#session_state{messages = [Message | Messages]}, - {noreply, NewState}; - -handle_info({dispatch, {_From, Message}}, State = #session_state{client_pid = ClientPid}) -> - %%TODO: replace From with self(), ok? - ClientPid ! {dispatch, {self(), Message}}, - {noreply, 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}) -> @@ -247,7 +275,7 @@ handle_info({'EXIT', ClientPid, Reason}, State = #session_state{ {noreply, State#session_state{ client_pid = undefined, expire_timer = Timer}}; handle_info(session_expired, State = #session_state{client_id = ClientId}) -> - lager:warning("Session: ~s session expired!", [ClientId]), + lager:warning("Session ~s expired!", [ClientId]), {stop, {shutdown, expired}, State}; handle_info(Info, State) -> @@ -263,4 +291,28 @@ code_change(_OldVsn, State, _Extra) -> %% 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 }. + + From b4528e46e7c97d230ed62d421c51a601feaf7f3a Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Thu, 15 Jan 2015 23:59:31 +0800 Subject: [PATCH 66/84] store_qos0 --- rel/files/app.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rel/files/app.config b/rel/files/app.config index e64fda7cf..3102ebb27 100644 --- a/rel/files/app.config +++ b/rel/files/app.config @@ -36,7 +36,7 @@ {session, [ {expires, 1}, {max_queue, 1000}, - {qos0, false} + {store_qos0, false} ]}, {retain, [ From efd1fbf4fea6cff9f9600c6f11757e599f75b18f Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Fri, 16 Jan 2015 00:20:49 +0800 Subject: [PATCH 67/84] fix emqtt_packet:from_packet --- apps/emqtt/src/emqtt_protocol.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 364d60398..d5d851d61 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -260,7 +260,7 @@ shutdown(Error, #proto_state{peer_name = PeerName, client_id = ClientId, will_ms ok. willmsg(Packet) when is_record(Packet, mqtt_packet_connect) -> - emqtt_packet:from_packet(Packet). + emqtt_message:from_packet(Packet). clientid(<<>>, #proto_state{peer_name = PeerName}) -> <<"eMQTT/", (base64:encode(PeerName))/binary>>; From 69568cfb61e2b710042caf6be84dba9e258d4b35 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Fri, 16 Jan 2015 00:25:31 +0800 Subject: [PATCH 68/84] from_packet --- apps/emqtt/src/emqtt_message.erl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/emqtt/src/emqtt_message.erl b/apps/emqtt/src/emqtt_message.erl index 5ea06405d..27ad5982d 100644 --- a/apps/emqtt/src/emqtt_message.erl +++ b/apps/emqtt/src/emqtt_message.erl @@ -26,7 +26,7 @@ -include("emqtt_packet.hrl"). --export([to_packet/1]). +-export([from_packet/1, to_packet/1]). %%---------------------------------------------------------------------------- @@ -36,10 +36,13 @@ -spec( to_packet( mqtt_message() ) -> mqtt_packet() ). --endif +-endif. %%---------------------------------------------------------------------------- +%% +%% @doc message from packet +%% from_packet(#mqtt_packet{ header = #mqtt_packet_header{ type = ?PUBLISH, qos = Qos, retain = Retain, @@ -67,6 +70,9 @@ from_packet(#mqtt_packet_connect{ will_retain = Retain, dup = false, payload = Msg }. +%% +%% @doc message to packet +%% to_packet(#mqtt_message{ msgid = MsgId, qos = Qos, retain = Retain, From 46d174912095d78287a830804d7fe3fe58a7bae0 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Fri, 16 Jan 2015 00:38:30 +0800 Subject: [PATCH 69/84] fix emqtt_messsage --- apps/emqtt/src/emqtt_message.erl | 4 +++- apps/emqtt/src/emqtt_protocol.erl | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/emqtt/src/emqtt_message.erl b/apps/emqtt/src/emqtt_message.erl index 27ad5982d..3ecbd54da 100644 --- a/apps/emqtt/src/emqtt_message.erl +++ b/apps/emqtt/src/emqtt_message.erl @@ -22,6 +22,8 @@ -module(emqtt_message). +-author('feng@emqtt.io'). + -include("emqtt.hrl"). -include("emqtt_packet.hrl"). @@ -44,8 +46,8 @@ %% @doc message from packet %% from_packet(#mqtt_packet{ header = #mqtt_packet_header{ type = ?PUBLISH, - qos = Qos, retain = Retain, + qos = Qos, dup = Dup }, variable = #mqtt_packet_publish{ topic_name = Topic, packet_id = PacketId }, diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index d5d851d61..6481a2e81 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -153,14 +153,14 @@ handle_packet(?CONNECT, Packet = #mqtt_packet { 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_messsage:from_packet(Packet)}), + 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_messsage:from_packet(Packet)}), + emqtt_session:publish(Session, {?QOS_1, emqtt_message:from_packet(Packet)}), send_packet( make_packet(?PUBACK, PacketId), State); handle_packet(?PUBLISH, Packet = #mqtt_packet { From e31068787b4ba5810f78f082f119122b9301a656 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Fri, 16 Jan 2015 01:13:02 +0800 Subject: [PATCH 70/84] fix issue#42: Redelivery on reconnect --- apps/emqtt/src/emqtt_client.erl | 6 ++++++ apps/emqtt/src/emqtt_protocol.erl | 8 +++++++- apps/emqtt/src/emqtt_session.erl | 26 ++++++++++++++++++++------ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/apps/emqtt/src/emqtt_client.erl b/apps/emqtt/src/emqtt_client.erl index d0b4774f2..751f29d17 100644 --- a/apps/emqtt/src/emqtt_client.erl +++ b/apps/emqtt/src/emqtt_client.erl @@ -37,6 +37,8 @@ -include("emqtt.hrl"). +-include("emqtt_packet.hrl"). + %%Client State... -record(state, { socket, @@ -103,6 +105,10 @@ handle_info({dispatch, {From, Message}}, #state{proto_state = ProtoState} = Stat {ok, ProtoState1} = emqtt_protocol:send_message({From, Message}, ProtoState), {noreply, State#state{proto_state = ProtoState1}}; +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}; diff --git a/apps/emqtt/src/emqtt_protocol.erl b/apps/emqtt/src/emqtt_protocol.erl index 6481a2e81..d0857c554 100644 --- a/apps/emqtt/src/emqtt_protocol.erl +++ b/apps/emqtt/src/emqtt_protocol.erl @@ -32,7 +32,7 @@ -export([initial_state/2, client_id/1]). --export([handle_packet/2, send_message/2, send_packet/2, shutdown/2]). +-export([handle_packet/2, send_message/2, send_packet/2, redeliver/2, shutdown/2]). -export([info/1]). @@ -253,6 +253,12 @@ send_packet(Packet, State = #proto_state{socket = Sock, peer_name = PeerName, cl 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()), diff --git a/apps/emqtt/src/emqtt_session.erl b/apps/emqtt/src/emqtt_session.erl index c4abd8e3f..e4d1e04c7 100644 --- a/apps/emqtt/src/emqtt_session.erl +++ b/apps/emqtt/src/emqtt_session.erl @@ -228,14 +228,28 @@ 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, - expire_timer = ETimer}) -> - lager:info("Session: client ~s resumed by ~p", [ClientId, ClientPid]), + 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]), erlang:cancel_timer(ETimer), + + %% redelivery PUBREL + [ ClientPid ! {redeliver, {?PUBREL, PacketId}} || PacketId <- maps:keys(AwaitingComp) ], + + %% redelivery messages that awaiting PUBACK or PUBREC + Dup = fun(Msg) -> Msg#mqtt_message{ dup = true } end, + [ ClientPid ! {dispatch, {self(), Dup(Message)}} || Message <- maps:values(AwaitingAck) ], + + %% send offline messages [ClientPid ! {dispatch, {self(), Message}} || Message <- emqtt_queue:all(Queue)], - NewState = State#session_state{ client_pid = ClientPid, msg_queue = emqtt_queue:clear(Queue), expire_timer = undefined}, + + 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) -> From 760d6954b37a70c8d33a678d3d40fb645dd0f7a5 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Fri, 16 Jan 2015 01:54:33 +0800 Subject: [PATCH 71/84] 3.3.1.1 dup: reset dup when route message --- apps/emqtt/src/emqtt_router.erl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/emqtt/src/emqtt_router.erl b/apps/emqtt/src/emqtt_router.erl index e7e6bf033..be4fbfc06 100644 --- a/apps/emqtt/src/emqtt_router.erl +++ b/apps/emqtt/src/emqtt_router.erl @@ -65,7 +65,7 @@ start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). route(Message) -> - emqtt_pubsub:publish(retained(Message)). + emqtt_pubsub:publish(retained(reset_dup(Message))). %% ------------------------------------------------------------------ %% gen_server Function Definitions @@ -97,3 +97,6 @@ retained(Msg = #mqtt_message{retain = true, topic = Topic}) -> retained(Msg) -> Msg. +reset_dup(Msg = #mqtt_message{dup = true}) -> Msg#mqtt_message{dup = false}; +reset_dup(Msg) -> Msg. + From 75d7e656727da7422320c1ead4a73e996be09f3c Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Fri, 16 Jan 2015 12:35:12 +0800 Subject: [PATCH 72/84] set_flag, unset_flag --- apps/emqtt/src/emqtt_message.erl | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/apps/emqtt/src/emqtt_message.erl b/apps/emqtt/src/emqtt_message.erl index 3ecbd54da..aa4c415f0 100644 --- a/apps/emqtt/src/emqtt_message.erl +++ b/apps/emqtt/src/emqtt_message.erl @@ -30,6 +30,8 @@ -export([from_packet/1, to_packet/1]). +-export([set_flag/1, set_flag/2, unset_flag/1, unset_flag/2]). + %%---------------------------------------------------------------------------- -ifdef(use_specs). @@ -38,6 +40,10 @@ -spec( to_packet( mqtt_message() ) -> mqtt_packet() ). +-sepc( set_flag(atom(), mqtt_message() ) -> mqtt_message(). + +-sepc( unset_flag(atom(), mqtt_message() ) -> mqtt_message(). + -endif. %%---------------------------------------------------------------------------- @@ -95,3 +101,22 @@ to_packet(#mqtt_message{ msgid = MsgId, 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. + From b68a3253019129729d693835a9f4d8b3a6d3dbbd Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Fri, 16 Jan 2015 12:36:27 +0800 Subject: [PATCH 73/84] retained --- apps/emqtt/include/emqtt.hrl | 20 ++++++++++---------- apps/emqtt/src/emqtt_retained.erl | 6 ++++++ apps/emqtt/src/emqtt_router.erl | 12 ++++-------- apps/emqtt/src/emqtt_session.erl | 13 ++++++++++--- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/apps/emqtt/include/emqtt.hrl b/apps/emqtt/include/emqtt.hrl index c73fc8f71..ff67fafde 100644 --- a/apps/emqtt/include/emqtt.hrl +++ b/apps/emqtt/include/emqtt.hrl @@ -45,8 +45,8 @@ %% MQTT Client %%------------------------------------------------------------------------------ -record(mqtt_client, { - client_id, - username + client_id, + username }). -type mqtt_client() :: #mqtt_client{}. @@ -68,12 +68,12 @@ %% MQTT Message %%------------------------------------------------------------------------------ -record(mqtt_message, { - msgid :: integer() | undefined, - qos = ?QOS_0 :: mqtt_qos(), - retain = false :: boolean(), - dup = false :: boolean(), - topic :: binary(), - payload :: binary() + msgid :: integer() | undefined, + qos = ?QOS_0 :: mqtt_qos(), + retain = false :: boolean(), + dup = false :: boolean(), + topic :: binary(), + payload :: binary() }). -type mqtt_message() :: #mqtt_message{}. @@ -82,8 +82,8 @@ %% MQTT User Management %%------------------------------------------------------------------------------ -record(mqtt_user, { - username :: binary(), - passwdhash :: binary() + username :: binary(), + passwdhash :: binary() }). %%------------------------------------------------------------------------------ diff --git a/apps/emqtt/src/emqtt_retained.erl b/apps/emqtt/src/emqtt_retained.erl index 96c605537..f7edd79f1 100644 --- a/apps/emqtt/src/emqtt_retained.erl +++ b/apps/emqtt/src/emqtt_retained.erl @@ -47,6 +47,7 @@ -include("emqtt.hrl"). -export([start_link/0, + retain/1, lookup/1, insert/2, delete/1, @@ -66,6 +67,11 @@ start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). +retain(Msg = #mqtt_message{retain = true}) -> + Msg; + +retain(Msg) -> Msg. + lookup(Topic) -> ets:lookup(retained_msg, Topic). diff --git a/apps/emqtt/src/emqtt_router.erl b/apps/emqtt/src/emqtt_router.erl index be4fbfc06..09eeaf0ce 100644 --- a/apps/emqtt/src/emqtt_router.erl +++ b/apps/emqtt/src/emqtt_router.erl @@ -65,7 +65,10 @@ start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). route(Message) -> - emqtt_pubsub:publish(retained(reset_dup(Message))). + %%TODO: hooks later. + emqtt_pubsub:publish( + emqtt_message:unset_flag( + emqtt_retained:retain(Message))). %% ------------------------------------------------------------------ %% gen_server Function Definitions @@ -92,11 +95,4 @@ code_change(_OldVsn, State, _Extra) -> %% ------------------------------------------------------------------ %% Internal Function Definitions %% ------------------------------------------------------------------ -retained(Msg = #mqtt_message{retain = true, topic = Topic}) -> - emqtt_retained:insert(Topic, Msg), Msg; - -retained(Msg) -> Msg. - -reset_dup(Msg = #mqtt_message{dup = true}) -> Msg#mqtt_message{dup = false}; -reset_dup(Msg) -> Msg. diff --git a/apps/emqtt/src/emqtt_session.erl b/apps/emqtt/src/emqtt_session.erl index e4d1e04c7..430f82ae9 100644 --- a/apps/emqtt/src/emqtt_session.erl +++ b/apps/emqtt/src/emqtt_session.erl @@ -235,17 +235,24 @@ handle_cast({resume, ClientId, ClientPid}, State = #session_state { awaiting_comp = AwaitingComp, expire_timer = ETimer}) -> lager:info("Session ~s resumed by ~p", [ClientId, ClientPid]), + %cancel timeout timer erlang:cancel_timer(ETimer), %% redelivery PUBREL - [ ClientPid ! {redeliver, {?PUBREL, PacketId}} || PacketId <- maps:keys(AwaitingComp) ], + 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, - [ ClientPid ! {dispatch, {self(), Dup(Message)}} || Message <- maps:values(AwaitingAck) ], + lists:foreach(fun(Msg) -> + ClientPid ! {dispatch, {self(), Dup(Msg)}} + end, maps:values(AwaitingAck)), %% send offline messages - [ClientPid ! {dispatch, {self(), Message}} || Message <- emqtt_queue:all(Queue)], + 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), From 953df5f9f71218a08fe3f321637b8b144a2e2123 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Fri, 16 Jan 2015 15:45:15 +0800 Subject: [PATCH 74/84] retained messages --- apps/emqtt/src/emqtt_router.erl | 12 +-- apps/emqtt/src/emqtt_server.erl | 127 +++++++++++++++++++++++++++++++ apps/emqtt/src/emqtt_session.erl | 2 + apps/emqtt/src/x.erl | 50 ++++++++++++ rel/files/app.config | 2 +- 5 files changed, 186 insertions(+), 7 deletions(-) create mode 100644 apps/emqtt/src/emqtt_server.erl create mode 100644 apps/emqtt/src/x.erl diff --git a/apps/emqtt/src/emqtt_router.erl b/apps/emqtt/src/emqtt_router.erl index 09eeaf0ce..9da53ceb7 100644 --- a/apps/emqtt/src/emqtt_router.erl +++ b/apps/emqtt/src/emqtt_router.erl @@ -64,18 +64,18 @@ start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). -route(Message) -> - %%TODO: hooks later. - emqtt_pubsub:publish( - emqtt_message:unset_flag( - emqtt_retained:retain(Message))). +route(Msg) -> + % need to retain? + emqtt_retained:retain(Message), + % unset flag and pubsub + emqtt_pubsub:publish( emqtt_message:unset_flag(Msg) ). %% ------------------------------------------------------------------ %% gen_server Function Definitions %% ------------------------------------------------------------------ init(Args) -> - {ok, Args}. + {ok, Args, hibernate}. handle_call(_Request, _From, State) -> {reply, ok, State}. diff --git a/apps/emqtt/src/emqtt_server.erl b/apps/emqtt/src/emqtt_server.erl new file mode 100644 index 000000000..b4cf8ada4 --- /dev/null +++ b/apps/emqtt/src/emqtt_server.erl @@ -0,0 +1,127 @@ +%%----------------------------------------------------------------------------- +%% Copyright (c) 2012-2015, Feng Lee +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in all +%% copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. +%%------------------------------------------------------------------------------ + +-module(emqtt_server). + +-author('feng@slimpp.io'). + +-include("emqtt.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(Msg = #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}), Msg; + +%% +subscribe(Topics, CPid) when is_pid(CPid) -> + RetainedMsgs = lists:flatten([mnesia:dirty_read(?RETAINED_TAB, Topic) || Topic <- match(Topics)]) + 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(_Request, _From, State) -> + {reply, ok, State}. + +handle_cast({retain, Msg}, State = #state{store_limit = Limit}) -> + case mnesia:table_info(?RETAINED_TAB, size) of + Size >= Limit -> + lager:error("Server dropped message(retain) for table is full: ~p", [Msg]); + true -> + 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) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%% ------------------------------------------------------------------ +%% Internal Function Definitions +%% ------------------------------------------------------------------ +match(Topics) -> + %%TODO: dirty_all_keys.... + Topics. + +retained_msg(#mqtt_retained{topic = Topic, qos = Qos, payload = Payload}) -> + #mqtt_message { qos = Qos, retain = true, topic = Topic, payload = Payload }. + diff --git a/apps/emqtt/src/emqtt_session.erl b/apps/emqtt/src/emqtt_session.erl index 430f82ae9..e52890a1a 100644 --- a/apps/emqtt/src/emqtt_session.erl +++ b/apps/emqtt/src/emqtt_session.erl @@ -147,6 +147,8 @@ subscribe(SessState = #session_state{client_id = ClientId, submap = SubMap}, Top 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) -> diff --git a/apps/emqtt/src/x.erl b/apps/emqtt/src/x.erl new file mode 100644 index 000000000..06e3e515d --- /dev/null +++ b/apps/emqtt/src/x.erl @@ -0,0 +1,50 @@ +-module(x). +-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, [], []). + +%% ------------------------------------------------------------------ +%% gen_server Function Definitions +%% ------------------------------------------------------------------ + +init(Args) -> + {ok, Args}. + +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 +%% ------------------------------------------------------------------ + diff --git a/rel/files/app.config b/rel/files/app.config index 3102ebb27..f6ae0f077 100644 --- a/rel/files/app.config +++ b/rel/files/app.config @@ -39,7 +39,7 @@ {store_qos0, false} ]}, {retain, [ - + {store_limit, 100000} ]}, {listen, [ {mqtt, 1883, [ From eedfd41a4562bb8410388aea2b665503afd16b02 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Fri, 16 Jan 2015 15:48:33 +0800 Subject: [PATCH 75/84] retained messages --- apps/emqtt/src/emqtt_router.erl | 2 +- apps/emqtt/src/emqtt_server.erl | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/emqtt/src/emqtt_router.erl b/apps/emqtt/src/emqtt_router.erl index 9da53ceb7..3026f98fa 100644 --- a/apps/emqtt/src/emqtt_router.erl +++ b/apps/emqtt/src/emqtt_router.erl @@ -66,7 +66,7 @@ start_link() -> route(Msg) -> % need to retain? - emqtt_retained:retain(Message), + emqtt_retained:retain(Msg), % unset flag and pubsub emqtt_pubsub:publish( emqtt_message:unset_flag(Msg) ). diff --git a/apps/emqtt/src/emqtt_server.erl b/apps/emqtt/src/emqtt_server.erl index b4cf8ada4..f61993784 100644 --- a/apps/emqtt/src/emqtt_server.erl +++ b/apps/emqtt/src/emqtt_server.erl @@ -62,17 +62,17 @@ start_link(RetainOpts) -> retain(#mqtt_message{ retain = false }) -> ignore; %% RETAIN flag set to 1 and payload containing zero bytes -retain(Msg = #mqtt_message{ retain = true, topic = Topic, payload = <<>> }) -> +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}), Msg; + gen_server:cast(?SERVER, {retain, Msg}). %% subscribe(Topics, CPid) when is_pid(CPid) -> - RetainedMsgs = lists:flatten([mnesia:dirty_read(?RETAINED_TAB, Topic) || Topic <- match(Topics)]) + RetainedMsgs = lists:flatten([mnesia:dirty_read(?RETAINED_TAB, Topic) || Topic <- match(Topics)]), lists:foreach(fun(Msg) -> - CPid ! {dispatch, {self(), retained_msg(Msg}} + CPid ! {dispatch, {self(), retained_msg(Msg)}} end, RetainedMsgs). %% ------------------------------------------------------------------ @@ -91,11 +91,13 @@ init([RetainOpts]) -> handle_call(_Request, _From, State) -> {reply, ok, State}. -handle_cast({retain, Msg}, State = #state{store_limit = Limit}) -> +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 >= Limit -> + Size when Size >= Limit -> lager:error("Server dropped message(retain) for table is full: ~p", [Msg]); - true -> + _ -> lager:info("Server retained message: ~p", [Msg]), mnesia:dirty_write(#mqtt_retained{ topic = Topic, qos = Qos, From 56e1f20ef92ad76ef1d6cf9a99151640cc952840 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sat, 17 Jan 2015 22:43:39 +0800 Subject: [PATCH 76/84] doc --- doc/.retain.md.swp | Bin 0 -> 12288 bytes doc/pubsub.md | 19 +++++++++++++++++++ doc/retain.md | 6 ++++++ doc/user-guide.md | 14 ++++++++++++++ 4 files changed, 39 insertions(+) create mode 100644 doc/.retain.md.swp create mode 100644 doc/pubsub.md create mode 100644 doc/retain.md create mode 100644 doc/user-guide.md diff --git a/doc/.retain.md.swp b/doc/.retain.md.swp new file mode 100644 index 0000000000000000000000000000000000000000..648c9731dc496c528162ce136a590152afb2b1ac GIT binary patch literal 12288 zcmeI&KQ9D97{~EB5|M}y-oU!8?H*UqDzCWYB6s?+``~2HGCK+ll{cYLD|KE8Dis~? z!Lw&IiY=~N`A+i7lga$a?5Eqz9Ci+R{a`2R8f~k_oZLPQ7w#YDAC`;}ca@2h1NF_0 zoz9E0Jh55b%G Date: Sat, 17 Jan 2015 22:48:44 +0800 Subject: [PATCH 77/84] add gen_event --- apps/emqtt/src/emqtt_event.erl | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 apps/emqtt/src/emqtt_event.erl diff --git a/apps/emqtt/src/emqtt_event.erl b/apps/emqtt/src/emqtt_event.erl new file mode 100644 index 000000000..ec0ff0f62 --- /dev/null +++ b/apps/emqtt/src/emqtt_event.erl @@ -0,0 +1,29 @@ +%%----------------------------------------------------------------------------- +%% Copyright (c) 2012-2015, Feng Lee +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in all +%% copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. +%%------------------------------------------------------------------------------ + +-module(emqtt_event). + +-export([start_link/0]). + +start_link() -> + gen_event:start_link({local, ?MODULE}). + From 0ae4a0f1af6a916f7836d57c18cd6befdd038ca3 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sat, 17 Jan 2015 23:08:59 +0800 Subject: [PATCH 78/84] supervisor_spec --- apps/emqtt/src/emqtt_app.erl | 55 ++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/apps/emqtt/src/emqtt_app.erl b/apps/emqtt/src/emqtt_app.erl index ce65a92ec..82f4bd366 100644 --- a/apps/emqtt/src/emqtt_app.erl +++ b/apps/emqtt/src/emqtt_app.erl @@ -60,56 +60,61 @@ print_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 config", emqtt_config}, + {"emqtt server", emqtt_server, RetainOpts}, {"emqtt client manager", emqtt_cm}, {"emqtt session manager", emqtt_sm}, - %%TODO: fixme - {"emqtt session supervisor", fun() -> - Mod = emqtt_session_sup, - supervisor:start_child(Sup, - {Mod, - {Mod, start_link, [SessOpts]}, - permanent, 1000, supervisor, [Mod]}) - end}, + {"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 queue supervisor", fun() -> - Mod = emqtt_queue_sup, - supervisor:start_child(Sup, - {Mod, - {Mod, start_link, []}, - permanent, 1000, supervisor, [Mod]}) - end}, {"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' From fdfdf494f55b43bc1fc95c2895db909ed80150b7 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sat, 17 Jan 2015 23:21:54 +0800 Subject: [PATCH 79/84] match retained topics --- apps/emqtt/src/emqtt_server.erl | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/emqtt/src/emqtt_server.erl b/apps/emqtt/src/emqtt_server.erl index f61993784..2292d7994 100644 --- a/apps/emqtt/src/emqtt_server.erl +++ b/apps/emqtt/src/emqtt_server.erl @@ -121,8 +121,16 @@ code_change(_OldVsn, State, _Extra) -> %% Internal Function Definitions %% ------------------------------------------------------------------ match(Topics) -> - %%TODO: dirty_all_keys.... - Topics. + RetainedTopics = mnesia:dirty_all_keys(?RETAINED_TAB), + lists:flatten([match(Topic, RetainedTopics) || Topic <- Topics]). + +match(Topic, RetainedTopics) -> + case emqtt_topic:type(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 }. From c803ce0d7a5da87d62ecdf2f3ffdb39bb07b328d Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Sat, 17 Jan 2015 23:24:53 +0800 Subject: [PATCH 80/84] fix function clause --- apps/emqtt/src/emqtt_server.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/emqtt/src/emqtt_server.erl b/apps/emqtt/src/emqtt_server.erl index 2292d7994..c464656e3 100644 --- a/apps/emqtt/src/emqtt_server.erl +++ b/apps/emqtt/src/emqtt_server.erl @@ -25,6 +25,7 @@ -author('feng@slimpp.io'). -include("emqtt.hrl"). +-include("emqtt_topic.hrl"). -behaviour(gen_server). @@ -125,7 +126,7 @@ match(Topics) -> lists:flatten([match(Topic, RetainedTopics) || Topic <- Topics]). match(Topic, RetainedTopics) -> - case emqtt_topic:type(Topic) of + case emqtt_topic:type(#topic{name=Topic}) of direct -> %% FIXME [Topic]; wildcard -> From f16d56c8b911e5af4881e376008e6d00918a5ee9 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Sun, 18 Jan 2015 11:36:21 +0800 Subject: [PATCH 81/84] retained messages --- apps/emqtt/src/emqtt_server.erl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/emqtt/src/emqtt_server.erl b/apps/emqtt/src/emqtt_server.erl index c464656e3..6cc58a14d 100644 --- a/apps/emqtt/src/emqtt_server.erl +++ b/apps/emqtt/src/emqtt_server.erl @@ -71,7 +71,9 @@ retain(Msg = #mqtt_message{retain = true}) -> %% 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). From 52abcef341b48caaeb6ed37519d9a6ceacc9edc7 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Sun, 18 Jan 2015 12:12:52 +0800 Subject: [PATCH 82/84] fix retained --- apps/emqtt/src/emqtt_message.erl | 11 +++ apps/emqtt/src/emqtt_retained.erl | 114 ------------------------------ apps/emqtt/src/emqtt_router.erl | 3 +- apps/emqtt/src/emqtt_server.erl | 13 ++-- 4 files changed, 20 insertions(+), 121 deletions(-) delete mode 100644 apps/emqtt/src/emqtt_retained.erl diff --git a/apps/emqtt/src/emqtt_message.erl b/apps/emqtt/src/emqtt_message.erl index aa4c415f0..d8dde9814 100644 --- a/apps/emqtt/src/emqtt_message.erl +++ b/apps/emqtt/src/emqtt_message.erl @@ -32,6 +32,8 @@ -export([set_flag/1, set_flag/2, unset_flag/1, unset_flag/2]). +-export([dump/1]). + %%---------------------------------------------------------------------------- -ifdef(use_specs). @@ -120,3 +122,12 @@ 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 ]). + + diff --git a/apps/emqtt/src/emqtt_retained.erl b/apps/emqtt/src/emqtt_retained.erl deleted file mode 100644 index f7edd79f1..000000000 --- a/apps/emqtt/src/emqtt_retained.erl +++ /dev/null @@ -1,114 +0,0 @@ -%%----------------------------------------------------------------------------- -%% Copyright (c) 2012-2015, Feng Lee -%% -%% Permission is hereby granted, free of charge, to any person obtaining a copy -%% of this software and associated documentation files (the "Software"), to deal -%% in the Software without restriction, including without limitation the rights -%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%% copies of the Software, and to permit persons to whom the Software is -%% furnished to do so, subject to the following conditions: -%% -%% The above copyright notice and this permission notice shall be included in all -%% copies or substantial portions of the Software. -%% -%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%% SOFTWARE. -%%------------------------------------------------------------------------------ - --module(emqtt_retained). - --author('feng@emqtt.io'). - -%%TODO: FIXME Later... - -%% -%% <> - -%% 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"). - --export([start_link/0, - retain/1, - 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, [], []). - -retain(Msg = #mqtt_message{retain = true}) -> - Msg; - -retain(Msg) -> Msg. - -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, {self(), 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}. - - diff --git a/apps/emqtt/src/emqtt_router.erl b/apps/emqtt/src/emqtt_router.erl index 3026f98fa..43e6bd419 100644 --- a/apps/emqtt/src/emqtt_router.erl +++ b/apps/emqtt/src/emqtt_router.erl @@ -65,8 +65,9 @@ start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). route(Msg) -> + lager:info("Route message: ~s", [emqtt_message:dump(Msg)]), % need to retain? - emqtt_retained:retain(Msg), + emqtt_server:retain(Msg), % unset flag and pubsub emqtt_pubsub:publish( emqtt_message:unset_flag(Msg) ). diff --git a/apps/emqtt/src/emqtt_server.erl b/apps/emqtt/src/emqtt_server.erl index 6cc58a14d..9360eaa8f 100644 --- a/apps/emqtt/src/emqtt_server.erl +++ b/apps/emqtt/src/emqtt_server.erl @@ -25,6 +25,7 @@ -author('feng@slimpp.io'). -include("emqtt.hrl"). + -include("emqtt_topic.hrl"). -behaviour(gen_server). @@ -91,8 +92,8 @@ init([RetainOpts]) -> Limit = proplists:get_value(store_limit, RetainOpts, ?STORE_LIMIT), {ok, #state{store_limit = Limit}}. -handle_call(_Request, _From, State) -> - {reply, ok, State}. +handle_call(Req, _From, State) -> + {stop, {badreq, Req}, State}. handle_cast({retain, Msg = #mqtt_message{ qos = Qos, topic = Topic, @@ -108,11 +109,11 @@ handle_cast({retain, Msg = #mqtt_message{ qos = Qos, end, {noreply, State}; -handle_cast(_Msg, State) -> - {noreply, State}. +handle_cast(Msg, State) -> + {stop, {badmsg, Msg}, State}. -handle_info(_Info, State) -> - {noreply, State}. +handle_info(Info, State) -> + {stop, {badinfo, Info}, State}. terminate(_Reason, _State) -> ok. From f3057c08e4ac75cf2eaf4dcb970482b923629d38 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Sun, 18 Jan 2015 12:57:33 +0800 Subject: [PATCH 83/84] 0.3.0 changes --- CHANGELOG.md | 28 +++++++++++++++++++++++ apps/emqtt/src/x.erl | 50 ------------------------------------------ scripts/mosquitto_test | 19 ++++++++++++++++ 3 files changed, 47 insertions(+), 50 deletions(-) delete mode 100644 apps/emqtt/src/x.erl create mode 100755 scripts/mosquitto_test diff --git a/CHANGELOG.md b/CHANGELOG.md index 9011e9dc0..d582ef4f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) ------------------------ diff --git a/apps/emqtt/src/x.erl b/apps/emqtt/src/x.erl deleted file mode 100644 index 06e3e515d..000000000 --- a/apps/emqtt/src/x.erl +++ /dev/null @@ -1,50 +0,0 @@ --module(x). --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, [], []). - -%% ------------------------------------------------------------------ -%% gen_server Function Definitions -%% ------------------------------------------------------------------ - -init(Args) -> - {ok, Args}. - -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 -%% ------------------------------------------------------------------ - diff --git a/scripts/mosquitto_test b/scripts/mosquitto_test new file mode 100755 index 000000000..d5a504134 --- /dev/null +++ b/scripts/mosquitto_test @@ -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 From ed3048232ad0142c9c09eb67cc569a293e31c040 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Sun, 18 Jan 2015 13:25:02 +0800 Subject: [PATCH 84/84] name, setcookie max processes --- rel/files/vm.args | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rel/files/vm.args b/rel/files/vm.args index cf869a472..1c790f3a1 100644 --- a/rel/files/vm.args +++ b/rel/files/vm.args @@ -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 +#