Merge branch 'dev'
This commit is contained in:
commit
94748be765
|
@ -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
|
28
CHANGELOG.md
28
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)
|
||||
------------------------
|
||||
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
47
README.md
47
README.md
|
@ -45,19 +45,19 @@ cd $INSTALL_DIR/emqtt
|
|||
### etc/app.config
|
||||
|
||||
```
|
||||
{emqtt, [
|
||||
{auth, {anonymous, []}}, %internal, anonymous
|
||||
{listen, [
|
||||
{mqtt, 1883, [
|
||||
{max_conns, 1024},
|
||||
{acceptor_pool, 4}
|
||||
]},
|
||||
{http, 8883, [
|
||||
{max_conns, 512},
|
||||
{acceptor_pool, 1}
|
||||
]}
|
||||
]}
|
||||
]}
|
||||
{emqtt, [
|
||||
{auth, {anonymous, []}}, %internal, anonymous
|
||||
{listen, [
|
||||
{mqtt, 1883, [
|
||||
{max_conns, 1024},
|
||||
{acceptor_pool, 4}
|
||||
]},
|
||||
{http, 8883, [
|
||||
{max_conns, 512},
|
||||
{acceptor_pool, 1}
|
||||
]}
|
||||
]}
|
||||
]}
|
||||
|
||||
```
|
||||
|
||||
|
@ -97,6 +97,26 @@ on 'host2':
|
|||
|
||||
Run './bin/emqtt_ctl cluster' on 'host1' or 'host2' to check cluster nodes.
|
||||
|
||||
## Cluster
|
||||
|
||||
Suppose we cluster two nodes on 'host1', 'host2', steps:
|
||||
|
||||
on 'host1':
|
||||
|
||||
```
|
||||
./bin/emqtt start
|
||||
```
|
||||
|
||||
on 'host2':
|
||||
|
||||
```
|
||||
./bin/emqtt start
|
||||
|
||||
./bin/emqtt cluster emqtt@host1
|
||||
```
|
||||
|
||||
Run './bin/emqtt cluster' on 'host1' or 'host2' to check cluster nodes.
|
||||
|
||||
## HTTP API
|
||||
|
||||
eMQTT support http to publish message.
|
||||
|
@ -134,5 +154,6 @@ feng at emqtt.io
|
|||
|
||||
## Thanks
|
||||
|
||||
@hejin1026 (260495915 at qq.com)
|
||||
@desoulter (assoulter123 at gmail.com)
|
||||
|
||||
|
|
29
TODO
29
TODO
|
@ -23,3 +23,32 @@ merge pull request#26
|
|||
temporary, 5000, worker, [emqtt_client]}]}}.
|
||||
|
||||
fucking stupid..... esockd locked
|
||||
|
||||
0.2.1
|
||||
=====
|
||||
|
||||
full MQTT 3.1.1 support...
|
||||
|
||||
node cluster....
|
||||
|
||||
one million connections test...
|
||||
|
||||
topic match benchmark tests...
|
||||
|
||||
Dialyzer ...
|
||||
|
||||
full test cases...
|
||||
|
||||
spawn_link to replace 'spawn' and 'link'
|
||||
|
||||
keepalive
|
||||
|
||||
retained
|
||||
|
||||
QOS
|
||||
|
||||
dural sub
|
||||
|
||||
packet dump...
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
%%------------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -20,49 +20,76 @@
|
|||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
%% ---------------------------------
|
||||
%% banner
|
||||
%% ---------------------------------
|
||||
-define(COPYRIGHT, "Copyright (C) 2014, Feng Lee<feng@slimchat.io>").
|
||||
%%------------------------------------------------------------------------------
|
||||
%% Banner
|
||||
%%------------------------------------------------------------------------------
|
||||
-define(COPYRIGHT, "Copyright (C) 2012-2015, Feng Lee <feng@emqtt.io>").
|
||||
|
||||
-define(LICENSE_MESSAGE, "Licensed under MIT").
|
||||
|
||||
-define(PROTOCOL_VERSION, "MQTT/3.1").
|
||||
-define(PROTOCOL_VERSION, "MQTT/3.1.1").
|
||||
|
||||
-define(ERTS_MINIMUM, "6.0").
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% MQTT Qos
|
||||
%% MQTT QoS
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
-define(QOS_0, 0).
|
||||
-define(QOS_1, 1).
|
||||
-define(QOS_2, 2).
|
||||
|
||||
-type qos() :: ?QOS_2 | ?QOS_1 | ?QOS_0.
|
||||
-type mqtt_qos() :: ?QOS_2 | ?QOS_1 | ?QOS_0.
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% MQTT Client
|
||||
%%------------------------------------------------------------------------------
|
||||
-record(mqtt_client, {
|
||||
client_id,
|
||||
username
|
||||
}).
|
||||
|
||||
-type mqtt_client() :: #mqtt_client{}.
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% MQTT Session
|
||||
%%------------------------------------------------------------------------------
|
||||
-record(mqtt_session, {
|
||||
client_id,
|
||||
session_pid,
|
||||
subscriptions = [],
|
||||
awaiting_ack,
|
||||
awaiting_rel
|
||||
}).
|
||||
|
||||
-type mqtt_session() :: #mqtt_session{}.
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% MQTT Message
|
||||
%%------------------------------------------------------------------------------
|
||||
-record(mqtt_msg, {
|
||||
retain,
|
||||
qos,
|
||||
topic,
|
||||
dup,
|
||||
msgid,
|
||||
payload,
|
||||
encoder
|
||||
-record(mqtt_message, {
|
||||
msgid :: integer() | undefined,
|
||||
qos = ?QOS_0 :: mqtt_qos(),
|
||||
retain = false :: boolean(),
|
||||
dup = false :: boolean(),
|
||||
topic :: binary(),
|
||||
payload :: binary()
|
||||
}).
|
||||
|
||||
-type mqtt_msg() :: #mqtt_msg{}.
|
||||
-type mqtt_message() :: #mqtt_message{}.
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% MQTT User Management
|
||||
%%------------------------------------------------------------------------------
|
||||
-record(emqtt_user, {
|
||||
username :: binary(),
|
||||
passwdhash :: binary()
|
||||
-record(mqtt_user, {
|
||||
username :: binary(),
|
||||
passwdhash :: binary()
|
||||
}).
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% MQTT Authorization
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
%%TODO: ClientId | Username --> Pub | Sub --> Topics
|
||||
|
||||
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
%
|
||||
% NOTICE: copy from rabbitmq mqtt-adaper
|
||||
%
|
||||
|
||||
%% The contents of this file are subject to the Mozilla Public License
|
||||
%% Version 1.1 (the "License"); you may not use this file except in
|
||||
%% compliance with the License. You may obtain a copy of the License
|
||||
%% at http://www.mozilla.org/MPL/
|
||||
%%
|
||||
%% Software distributed under the License is distributed on an "AS IS"
|
||||
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
%% the License for the specific language governing rights and
|
||||
%% limitations under the License.
|
||||
%%
|
||||
%% The Original Code is RabbitMQ.
|
||||
%%
|
||||
%% The Initial Developer of the Original Code is VMware, Inc.
|
||||
%% Copyright (c) 2007-2012 VMware, Inc. All rights reserved.
|
||||
%%
|
||||
|
||||
-define(CLIENT_ID_MAXLEN, 1024).
|
||||
|
||||
-define(PROTOCOL_NAMES, [{3, <<"MQIsdp">>}, {4, <<"MQTT">>}]).
|
||||
|
||||
-define(MQTT_PROTO_MAJOR, 3).
|
||||
-define(MQTT_PROTO_MINOR, 1).
|
||||
|
||||
%% frame types
|
||||
|
||||
-define(CONNECT, 1).
|
||||
-define(CONNACK, 2).
|
||||
-define(PUBLISH, 3).
|
||||
-define(PUBACK, 4).
|
||||
-define(PUBREC, 5).
|
||||
-define(PUBREL, 6).
|
||||
-define(PUBCOMP, 7).
|
||||
-define(SUBSCRIBE, 8).
|
||||
-define(SUBACK, 9).
|
||||
-define(UNSUBSCRIBE, 10).
|
||||
-define(UNSUBACK, 11).
|
||||
-define(PINGREQ, 12).
|
||||
-define(PINGRESP, 13).
|
||||
-define(DISCONNECT, 14).
|
||||
|
||||
%% connect return codes
|
||||
|
||||
-define(CONNACK_ACCEPT, 0).
|
||||
-define(CONNACK_PROTO_VER, 1). %% unacceptable protocol version
|
||||
-define(CONNACK_INVALID_ID, 2). %% identifier rejected
|
||||
-define(CONNACK_SERVER, 3). %% server unavailable
|
||||
-define(CONNACK_CREDENTIALS, 4). %% bad user name or password
|
||||
-define(CONNACK_AUTH, 5). %% not authorized
|
||||
|
||||
|
||||
-record(mqtt_frame, {fixed,
|
||||
variable,
|
||||
payload}).
|
||||
|
||||
-record(mqtt_frame_fixed, {type = 0,
|
||||
dup = 0,
|
||||
qos = 0,
|
||||
retain = 0}).
|
||||
|
||||
-record(mqtt_frame_connect, {proto_ver,
|
||||
will_retain,
|
||||
will_qos,
|
||||
will_flag,
|
||||
clean_sess,
|
||||
keep_alive,
|
||||
client_id,
|
||||
will_topic,
|
||||
will_msg,
|
||||
username,
|
||||
password}).
|
||||
|
||||
-record(mqtt_frame_connack, {return_code}).
|
||||
|
||||
-record(mqtt_frame_publish, {topic_name,
|
||||
message_id}).
|
||||
|
||||
-record(mqtt_frame_subscribe,{message_id,
|
||||
topic_table}).
|
||||
|
||||
-record(mqtt_frame_suback, {message_id,
|
||||
qos_table = []}).
|
||||
|
||||
-record(mqtt_topic, {name,
|
||||
qos}).
|
||||
|
||||
-record(mqtt_frame_other, {other}).
|
||||
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% Logging mechanism
|
||||
%%------------------------------------------------------------------------------
|
||||
-define(PRINT(Format, Args),
|
||||
io:format(Format, Args)).
|
||||
|
||||
-define(PRINT_MSG(Msg),
|
||||
io:format(Msg)).
|
||||
|
||||
-define(DEBUG(Format, Args),
|
||||
lager:debug(Format, Args)).
|
||||
|
||||
-define(DEBUG_TRACE(Dest, Format, Args),
|
||||
lager:debug(Dest, Format, Args)).
|
||||
|
||||
-define(DEBUG_MSG(Msg),
|
||||
lager:debug(Msg)).
|
||||
|
||||
-define(INFO(Format, Args),
|
||||
lager:info(Format, Args)).
|
||||
|
||||
-define(INFO_TRACE(Dest, Format, Args),
|
||||
lager:info(Dest, Format, Args)).
|
||||
|
||||
-define(INFO_MSG(Msg),
|
||||
lager:info(Msg)).
|
||||
|
||||
-define(WARN(Format, Args),
|
||||
lager:warning(Format, Args)).
|
||||
|
||||
-define(WARN_TRACE(Dest, Format, Args),
|
||||
lager:warning(Dest, Format, Args)).
|
||||
|
||||
-define(WARN_MSG(Msg),
|
||||
lager:warning(Msg)).
|
||||
|
||||
-define(WARNING(Format, Args),
|
||||
lager:warning(Format, Args)).
|
||||
|
||||
-define(WARNING_TRACE(Dest, Format, Args),
|
||||
lager:warning(Dest, Format, Args)).
|
||||
|
||||
-define(WARNING_MSG(Msg),
|
||||
lager:warning(Msg)).
|
||||
|
||||
-define(ERROR(Format, Args),
|
||||
lager:error(Format, Args)).
|
||||
|
||||
-define(ERROR_TRACE(Dest, Format, Args),
|
||||
lager:error(Dest, Format, Args)).
|
||||
|
||||
-define(ERROR_MSG(Msg),
|
||||
lager:error(Msg)).
|
||||
|
||||
-define(CRITICAL(Format, Args),
|
||||
lager:critical(Format, Args)).
|
||||
|
||||
-define(CRITICAL_TRACE(Dest, Format, Args),
|
||||
lager:critical(Dest, Format, Args)).
|
||||
|
||||
-define(CRITICAL_MSG(Msg),
|
||||
lager:critical(Msg)).
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
%%------------------------------------------------------------------------------
|
||||
%% Copyright (c) 2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
%%
|
||||
%% The Original Code is from RabbitMQ.
|
||||
%%
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% MQTT Protocol Version and Levels
|
||||
%%------------------------------------------------------------------------------
|
||||
-define(MQTT_PROTO_V31, 3).
|
||||
-define(MQTT_PROTO_V311, 4).
|
||||
|
||||
-define(PROTOCOL_NAMES, [{?MQTT_PROTO_V31, <<"MQIsdp">>}, {?MQTT_PROTO_V311, <<"MQTT">>}]).
|
||||
|
||||
-define(MAX_CLIENTID_LEN, 1024).
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% MQTT Control Packet Types
|
||||
%%------------------------------------------------------------------------------
|
||||
-define(RESERVED, 0). %% Reserved
|
||||
-define(CONNECT, 1). %% Client request to connect to Server
|
||||
-define(CONNACK, 2). %% Server to Client: Connect acknowledgment
|
||||
-define(PUBLISH, 3). %% Publish message
|
||||
-define(PUBACK, 4). %% Publish acknowledgment
|
||||
-define(PUBREC, 5). %% Publish received (assured delivery part 1)
|
||||
-define(PUBREL, 6). %% Publish release (assured delivery part 2)
|
||||
-define(PUBCOMP, 7). %% Publish complete (assured delivery part 3)
|
||||
-define(SUBSCRIBE, 8). %% Client subscribe request
|
||||
-define(SUBACK, 9). %% Server Subscribe acknowledgment
|
||||
-define(UNSUBSCRIBE, 10). %% Unsubscribe request
|
||||
-define(UNSUBACK, 11). %% Unsubscribe acknowledgment
|
||||
-define(PINGREQ, 12). %% PING request
|
||||
-define(PINGRESP, 13). %% PING response
|
||||
-define(DISCONNECT, 14). %% Client is disconnecting
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% MQTT Connect Return Codes
|
||||
%%------------------------------------------------------------------------------
|
||||
-define(CONNACK_ACCEPT, 0). %% Connection accepted
|
||||
-define(CONNACK_PROTO_VER, 1). %% Unacceptable protocol version
|
||||
-define(CONNACK_INVALID_ID, 2). %% Client Identifier is correct UTF-8 but not allowed by the Server
|
||||
-define(CONNACK_SERVER, 3). %% Server unavailable
|
||||
-define(CONNACK_CREDENTIALS, 4). %% Username or password is malformed
|
||||
-define(CONNACK_AUTH, 5). %% Client is not authorized to connect
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% MQTT Erlang Types
|
||||
%%------------------------------------------------------------------------------
|
||||
-type mqtt_packet_type() :: ?RESERVED..?DISCONNECT.
|
||||
|
||||
-type mqtt_packet_id() :: 1..16#ffff | undefined.
|
||||
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% MQTT Packet Fixed Header
|
||||
%%------------------------------------------------------------------------------
|
||||
-record(mqtt_packet_header, {
|
||||
type = ?RESERVED :: mqtt_packet_type(),
|
||||
dup = false :: boolean(),
|
||||
qos = 0 :: 0 | 1 | 2,
|
||||
retain = false :: boolean() }).
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% MQTT Packets
|
||||
%%------------------------------------------------------------------------------
|
||||
-record(mqtt_packet_connect, {
|
||||
proto_ver,
|
||||
proto_name,
|
||||
will_retain,
|
||||
will_qos,
|
||||
will_flag,
|
||||
clean_sess,
|
||||
keep_alive,
|
||||
client_id,
|
||||
will_topic,
|
||||
will_msg,
|
||||
username,
|
||||
password }).
|
||||
|
||||
-record(mqtt_packet_connack, {
|
||||
ack_flags = ?RESERVED,
|
||||
return_code }).
|
||||
|
||||
-record(mqtt_packet_publish, {
|
||||
topic_name :: binary(),
|
||||
packet_id :: mqtt_packet_id() }).
|
||||
|
||||
-record(mqtt_packet_puback, {
|
||||
packet_id :: mqtt_packet_id() }).
|
||||
|
||||
-record(mqtt_topic, {
|
||||
name :: binary(),
|
||||
qos :: 0 | 1 | 2 }).
|
||||
|
||||
-record(mqtt_packet_subscribe, {
|
||||
packet_id :: mqtt_packet_id(),
|
||||
topic_table :: list(#mqtt_topic{}) }).
|
||||
|
||||
-record(mqtt_packet_suback, {
|
||||
packet_id :: mqtt_packet_id(),
|
||||
qos_table = [] }).
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% MQTT Control Packet
|
||||
%%------------------------------------------------------------------------------
|
||||
-record(mqtt_packet, {
|
||||
header :: #mqtt_packet_header{},
|
||||
variable :: #mqtt_packet_connect{} | #mqtt_packet_connack{}
|
||||
| #mqtt_packet_publish{} | #mqtt_packet_puback{}
|
||||
| #mqtt_packet_subscribe{} | #mqtt_packet_suback{}
|
||||
| mqtt_packet_id(),
|
||||
payload :: binary() }).
|
||||
|
||||
-type mqtt_packet() :: #mqtt_packet{}.
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%% Copyright (c) 2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -24,15 +24,15 @@
|
|||
%% Core PubSub Topic
|
||||
%%------------------------------------------------------------------------------
|
||||
-record(topic, {
|
||||
name :: binary(),
|
||||
node :: node()
|
||||
name :: binary(),
|
||||
node :: node()
|
||||
}).
|
||||
|
||||
-type topic() :: #topic{}.
|
||||
|
||||
-record(topic_subscriber, {
|
||||
topic :: binary(),
|
||||
qos = 0 :: integer(),
|
||||
qos = 0 :: non_neg_integer(),
|
||||
subpid :: pid()
|
||||
}).
|
||||
|
||||
|
@ -44,7 +44,7 @@
|
|||
|
||||
-record(topic_trie_edge, {
|
||||
node_id :: binary(),
|
||||
word :: binary()
|
||||
word :: binary() | char()
|
||||
}).
|
||||
|
||||
-record(topic_trie, {
|
||||
|
@ -52,3 +52,8 @@
|
|||
node_id :: binary()
|
||||
}).
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% System Topic
|
||||
%%------------------------------------------------------------------------------
|
||||
-define(SYSTOP, <<"$SYS">>).
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -29,7 +29,7 @@
|
|||
{packet, raw},
|
||||
{reuseaddr, true},
|
||||
{backlog, 512},
|
||||
{nodelay, false}
|
||||
{nodelay, true}
|
||||
]).
|
||||
|
||||
listen(Listeners) when is_list(Listeners) ->
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -22,15 +22,17 @@
|
|||
|
||||
-module(emqtt_app).
|
||||
|
||||
-author('feng@slimchat.io').
|
||||
|
||||
-include("emqtt_log.hrl").
|
||||
-author('feng@emqtt.io').
|
||||
|
||||
-behaviour(application).
|
||||
|
||||
%% Application callbacks
|
||||
-export([start/2, stop/1]).
|
||||
|
||||
-define(PRINT_MSG(Msg), io:format(Msg)).
|
||||
|
||||
-define(PRINT(Format, Args), io:format(Format, Args)).
|
||||
|
||||
%% ===================================================================
|
||||
%% Application callbacks
|
||||
%% ===================================================================
|
||||
|
@ -57,38 +59,62 @@ print_vsn() ->
|
|||
?PRINT("~s ~s is running now~n", [Desc, Vsn]).
|
||||
|
||||
start_servers(Sup) ->
|
||||
{ok, SessOpts} = application:get_env(session),
|
||||
{ok, RetainOpts} = application:get_env(retain),
|
||||
lists:foreach(
|
||||
fun({Name, F}) when is_function(F) ->
|
||||
?PRINT("~s is starting...", [Name]),
|
||||
F(),
|
||||
?PRINT_MSG("[done]~n");
|
||||
({Name, Server}) when is_atom(Server) ->
|
||||
({Name, Server}) ->
|
||||
?PRINT("~s is starting...", [Name]),
|
||||
start_child(Sup, Server),
|
||||
?PRINT_MSG("[done]~n");
|
||||
({Name, Server, Opts}) when is_atom(Server) ->
|
||||
({Name, Server, Opts}) ->
|
||||
?PRINT("~s is starting...", [ Name]),
|
||||
start_child(Sup, Server, Opts),
|
||||
?PRINT_MSG("[done]~n")
|
||||
end,
|
||||
[{"emqtt cm", emqtt_cm},
|
||||
[{"emqtt config", emqtt_config},
|
||||
{"emqtt server", emqtt_server, RetainOpts},
|
||||
{"emqtt client manager", emqtt_cm},
|
||||
{"emqtt session manager", emqtt_sm},
|
||||
{"emqtt session supervisor", {supervisor, emqtt_session_sup}, SessOpts},
|
||||
{"emqtt auth", emqtt_auth},
|
||||
{"emqtt retained", emqtt_retained},
|
||||
{"emqtt pubsub", emqtt_pubsub},
|
||||
{"emqtt router", emqtt_router},
|
||||
{"emqtt monitor", emqtt_monitor}
|
||||
]).
|
||||
|
||||
start_child(Sup, Name) ->
|
||||
start_child(Sup, {supervisor, Name}) ->
|
||||
supervisor:start_child(Sup, supervisor_spec(Name));
|
||||
start_child(Sup, Name) when is_atom(Name) ->
|
||||
{ok, _ChiId} = supervisor:start_child(Sup, worker_spec(Name)).
|
||||
start_child(Sup, Name, Opts) ->
|
||||
|
||||
start_child(Sup, {supervisor, Name}, Opts) ->
|
||||
supervisor:start_child(Sup, supervisor_spec(Name, Opts));
|
||||
start_child(Sup, Name, Opts) when is_atom(Name) ->
|
||||
{ok, _ChiId} = supervisor:start_child(Sup, worker_spec(Name, Opts)).
|
||||
|
||||
%%TODO: refactor...
|
||||
supervisor_spec(Name) ->
|
||||
{Name,
|
||||
{Name, start_link, []},
|
||||
permanent, infinity, supervisor, [Name]}.
|
||||
|
||||
supervisor_spec(Name, Opts) ->
|
||||
{Name,
|
||||
{Name, start_link, [Opts]},
|
||||
permanent, infinity, supervisor, [Name]}.
|
||||
|
||||
worker_spec(Name) ->
|
||||
{Name, {Name, start_link, []},
|
||||
permanent, 5000, worker, [Name]}.
|
||||
worker_spec(Name, Opts) ->
|
||||
{Name, {Name, start_link, [Opts]},
|
||||
permanent, 5000, worker, [Name]}.
|
||||
{Name,
|
||||
{Name, start_link, []},
|
||||
permanent, 5000, worker, [Name]}.
|
||||
worker_spec(Name, Opts) ->
|
||||
{Name,
|
||||
{Name, start_link, [Opts]},
|
||||
permanent, 5000, worker, [Name]}.
|
||||
|
||||
%%
|
||||
%% @spec stop(atom) -> 'ok'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -22,12 +22,10 @@
|
|||
|
||||
-module(emqtt_auth).
|
||||
|
||||
-author('feng@slimchat.io').
|
||||
-author('feng@emqtt.io').
|
||||
|
||||
-include("emqtt.hrl").
|
||||
|
||||
-include("emqtt_log.hrl").
|
||||
|
||||
-export([start_link/0,
|
||||
add/2,
|
||||
check/1, check/2,
|
||||
|
@ -73,7 +71,6 @@ init([]) ->
|
|||
ok = AuthMod:init(Opts),
|
||||
ets:new(?TAB, [named_table, protected]),
|
||||
ets:insert(?TAB, {mod, AuthMod}),
|
||||
?PRINT("emqtt authmod is ~p", [AuthMod]),
|
||||
{ok, undefined}.
|
||||
|
||||
authmod(Name) when is_atom(Name) ->
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -22,7 +22,7 @@
|
|||
|
||||
-module(emqtt_auth_anonymous).
|
||||
|
||||
-author('feng@slimchat.io').
|
||||
-author('feng@emqtt.io').
|
||||
|
||||
-export([init/1,
|
||||
add/2,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -22,7 +22,7 @@
|
|||
|
||||
-module(emqtt_auth_internal).
|
||||
|
||||
-author('feng@slimchat.io').
|
||||
-author('feng@emqtt.io').
|
||||
|
||||
-include("emqtt.hrl").
|
||||
|
||||
|
@ -32,10 +32,10 @@
|
|||
delete/1]).
|
||||
|
||||
init(_Opts) ->
|
||||
mnesia:create_table(emqtt_user, [
|
||||
mnesia:create_table(mqtt_user, [
|
||||
{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, emqtt_user)}]),
|
||||
mnesia:add_table_copy(emqtt_user, node(), ram_copies),
|
||||
{attributes, record_info(fields, mqtt_user)}]),
|
||||
mnesia:add_table_copy(mqtt_user, node(), ram_copies),
|
||||
ok.
|
||||
|
||||
check(undefined, _) -> false;
|
||||
|
@ -44,19 +44,19 @@ check(_, undefined) -> false;
|
|||
|
||||
check(Username, Password) when is_binary(Username), is_binary(Password) ->
|
||||
PasswdHash = crypto:hash(md5, Password),
|
||||
case mnesia:dirty_read(emqtt_user, Username) of
|
||||
[#emqtt_user{passwdhash=PasswdHash}] -> true;
|
||||
case mnesia:dirty_read(mqtt_user, Username) of
|
||||
[#mqtt_user{passwdhash=PasswdHash}] -> true;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
add(Username, Password) when is_binary(Username) and is_binary(Password) ->
|
||||
mnesia:dirty_write(
|
||||
#emqtt_user{
|
||||
#mqtt_user{
|
||||
username=Username,
|
||||
passwdhash=crypto:hash(md5, Password)
|
||||
}
|
||||
).
|
||||
|
||||
delete(Username) when is_binary(Username) ->
|
||||
mnesia:dirty_delete(emqtt_user, Username).
|
||||
mnesia:dirty_delete(mqtt_user, Username).
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
-module(emqtt_bridge).
|
||||
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -22,165 +22,141 @@
|
|||
|
||||
-module(emqtt_client).
|
||||
|
||||
-author('feng@slimchat.io').
|
||||
-author('feng@emqtt.io').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
-export([start_link/1,
|
||||
info/1,
|
||||
go/2]).
|
||||
-export([start_link/1, info/1, go/2]).
|
||||
|
||||
-export([init/1,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
handle_info/2,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
handle_info/2,
|
||||
code_change/3,
|
||||
terminate/2]).
|
||||
terminate/2]).
|
||||
|
||||
-include("emqtt.hrl").
|
||||
|
||||
-include("emqtt_log.hrl").
|
||||
-include("emqtt_packet.hrl").
|
||||
|
||||
-include("emqtt_frame.hrl").
|
||||
|
||||
-record(state, {socket,
|
||||
conn_name,
|
||||
await_recv,
|
||||
connection_state,
|
||||
conserve,
|
||||
parse_state,
|
||||
message_id,
|
||||
client_id,
|
||||
clean_sess,
|
||||
will_msg,
|
||||
keep_alive,
|
||||
awaiting_ack,
|
||||
subtopics,
|
||||
awaiting_rel}).
|
||||
|
||||
|
||||
-define(FRAME_TYPE(Frame, Type),
|
||||
Frame = #mqtt_frame{ fixed = #mqtt_frame_fixed{ type = Type }}).
|
||||
%%Client State...
|
||||
-record(state, {
|
||||
socket,
|
||||
peer_name,
|
||||
conn_name,
|
||||
await_recv,
|
||||
conn_state,
|
||||
conserve,
|
||||
parse_state,
|
||||
proto_state,
|
||||
keepalive
|
||||
}).
|
||||
|
||||
start_link(Sock) ->
|
||||
gen_server:start_link(?MODULE, [Sock], []).
|
||||
|
||||
info(Pid) ->
|
||||
gen_server:call(Pid, info).
|
||||
gen_server:call(Pid, info).
|
||||
|
||||
go(Pid, Sock) ->
|
||||
gen_server:call(Pid, {go, Sock}).
|
||||
gen_server:call(Pid, {go, Sock}).
|
||||
|
||||
init([Sock]) ->
|
||||
{ok, #state{socket = Sock}, 1000}.
|
||||
|
||||
handle_call({go, Sock}, _From, State=#state{socket = Sock}) ->
|
||||
handle_call({go, Sock}, _From, #state{socket = Sock}) ->
|
||||
{ok, Peername} = emqtt_net:peer_string(Sock),
|
||||
{ok, ConnStr} = emqtt_net:connection_string(Sock, inbound),
|
||||
lager:info("Connect from ~s", [ConnStr]),
|
||||
{reply, ok,
|
||||
control_throttle(
|
||||
#state{ socket = Sock,
|
||||
conn_name = ConnStr,
|
||||
await_recv = false,
|
||||
connection_state = running,
|
||||
conserve = false,
|
||||
parse_state = emqtt_frame:initial_state(),
|
||||
message_id = 1,
|
||||
subtopics = [],
|
||||
awaiting_ack = gb_trees:empty(),
|
||||
awaiting_rel = gb_trees:empty()})};
|
||||
control_throttle(
|
||||
#state{ socket = Sock,
|
||||
peer_name = Peername,
|
||||
conn_name = ConnStr,
|
||||
await_recv = false,
|
||||
conn_state = running,
|
||||
conserve = false,
|
||||
parse_state = emqtt_packet:initial_state(),
|
||||
proto_state = emqtt_protocol:initial_state(Sock, Peername)}), 10000};
|
||||
|
||||
handle_call(info, _From, State = #state{
|
||||
conn_name=ConnName, proto_state = ProtoState}) ->
|
||||
{reply, [{conn_name, ConnName} | emqtt_protocol:info(ProtoState)], State};
|
||||
|
||||
handle_call(info, _From, #state{conn_name=ConnName,
|
||||
message_id=MsgId, client_id=ClientId} = State) ->
|
||||
Info = [{conn_name, ConnName},
|
||||
{message_id, MsgId},
|
||||
{client_id, ClientId}],
|
||||
{reply, Info, State};
|
||||
|
||||
handle_call(_Req, _From, State) ->
|
||||
{reply, ok, State}.
|
||||
handle_call(Req, _From, State) ->
|
||||
{stop, {badreq, Req}, State}.
|
||||
|
||||
handle_cast(Msg, State) ->
|
||||
{stop, {badmsg, Msg}, State}.
|
||||
{stop, {badmsg, Msg}, State}.
|
||||
|
||||
handle_info(timeout, State) ->
|
||||
stop({shutdown, timeout}, State);
|
||||
stop({shutdown, timeout}, State);
|
||||
|
||||
handle_info({stop, duplicate_id}, State=#state{conn_name=ConnName, client_id=ClientId}) ->
|
||||
?ERROR("Shutdown for duplicate clientid:~s, conn:~s", [ClientId, ConnName]),
|
||||
stop({shutdown, duplicate_id}, State);
|
||||
handle_info({stop, duplicate_id, _NewPid}, State=#state{ proto_state = ProtoState, conn_name=ConnName}) ->
|
||||
%% TODO: to...
|
||||
%% need transfer data???
|
||||
%% emqtt_client:transfer(NewPid, Data),
|
||||
lager:error("Shutdown for duplicate clientid: ~s, conn:~s",
|
||||
[emqtt_protocol:client_id(ProtoState), ConnName]),
|
||||
stop({shutdown, duplicate_id}, State);
|
||||
|
||||
handle_info({dispatch, Msg}, #state{socket = Sock, message_id=MsgId} = State) ->
|
||||
%%TODO: ok??
|
||||
handle_info({dispatch, {From, Message}}, #state{proto_state = ProtoState} = State) ->
|
||||
{ok, ProtoState1} = emqtt_protocol:send_message({From, Message}, ProtoState),
|
||||
{noreply, State#state{proto_state = ProtoState1}};
|
||||
|
||||
#mqtt_msg{retain = Retain,
|
||||
qos = Qos,
|
||||
topic = Topic,
|
||||
dup = Dup,
|
||||
payload = Payload,
|
||||
encoder = Encoder} = Msg,
|
||||
|
||||
Payload1 =
|
||||
if
|
||||
Encoder == undefined -> Payload;
|
||||
true -> Encoder(Payload)
|
||||
end,
|
||||
|
||||
Frame = #mqtt_frame{
|
||||
fixed = #mqtt_frame_fixed{type = ?PUBLISH,
|
||||
qos = Qos,
|
||||
retain = Retain,
|
||||
dup = Dup},
|
||||
variable = #mqtt_frame_publish{topic_name = Topic,
|
||||
message_id = if
|
||||
Qos == ?QOS_0 -> undefined;
|
||||
true -> MsgId
|
||||
end},
|
||||
payload = Payload1},
|
||||
|
||||
send_frame(Sock, Frame),
|
||||
|
||||
if
|
||||
Qos == ?QOS_0 ->
|
||||
{noreply, State};
|
||||
true ->
|
||||
{noreply, next_msg_id(State)}
|
||||
end;
|
||||
handle_info({redeliver, {?PUBREL, PacketId}}, #state{proto_state = ProtoState} = State) ->
|
||||
{ok, ProtoState1} = emqtt_protocol:redeliver({?PUBREL, PacketId}, ProtoState),
|
||||
{noreply, State#state{proto_state = ProtoState1}};
|
||||
|
||||
handle_info({inet_reply, _Ref, ok}, State) ->
|
||||
{noreply, State, hibernate};
|
||||
|
||||
handle_info({inet_async, Sock, _Ref, {ok, Data}}, #state{ socket = Sock}=State) ->
|
||||
handle_info({inet_async, Sock, _Ref, {ok, Data}}, State = #state{ peer_name = PeerName, socket = Sock }) ->
|
||||
lager:debug("RECV from ~s: ~p", [PeerName, Data]),
|
||||
process_received_bytes(
|
||||
Data, control_throttle(State #state{ await_recv = false }));
|
||||
Data, control_throttle(State #state{ await_recv = false }));
|
||||
|
||||
handle_info({inet_async, _Sock, _Ref, {error, Reason}}, State) ->
|
||||
network_error(Reason, State);
|
||||
|
||||
handle_info({inet_reply, _Sock, {error, Reason}}, State) ->
|
||||
{noreply, State};
|
||||
handle_info({inet_reply, _Sock, {error, Reason}}, State = #state{peer_name = PeerName}) ->
|
||||
lager:critical("Client ~s: unexpected inet_reply '~p'", [PeerName, Reason]),
|
||||
{noreply, State};
|
||||
|
||||
handle_info(keep_alive_timeout, #state{keep_alive=KeepAlive}=State) ->
|
||||
case emqtt_keep_alive:state(KeepAlive) of
|
||||
idle ->
|
||||
?INFO("keep_alive timeout: ~p", [State#state.conn_name]),
|
||||
{stop, normal, State};
|
||||
active ->
|
||||
KeepAlive1 = emqtt_keep_alive:reset(KeepAlive),
|
||||
{noreply, State#state{keep_alive=KeepAlive1}}
|
||||
end;
|
||||
handle_info({keepalive, start, TimeoutSec}, State = #state{socket = Socket}) ->
|
||||
lager:info("Client ~s: Start KeepAlive with ~p seconds", [State#state.peer_name, TimeoutSec]),
|
||||
KeepAlive = emqtt_keepalive:new(Socket, TimeoutSec, {keepalive, timeout}),
|
||||
{noreply, State#state{ keepalive = KeepAlive }};
|
||||
|
||||
handle_info(Info, State) ->
|
||||
?ERROR("badinfo :~p",[Info]),
|
||||
{stop, {badinfo, Info}, State}.
|
||||
handle_info({keepalive, timeout}, State = #state { keepalive = KeepAlive }) ->
|
||||
case emqtt_keepalive:resume(KeepAlive) of
|
||||
timeout ->
|
||||
lager:info("Client ~s: Keepalive Timeout!", [State#state.peer_name]),
|
||||
stop({shutdown, keepalive_timeout}, State#state{keepalive = undefined});
|
||||
{resumed, KeepAlive1} ->
|
||||
lager:info("Client ~s: Keepalive Resumed", [State#state.peer_name]),
|
||||
{noreply, State#state{ keepalive = KeepAlive1 }}
|
||||
end;
|
||||
|
||||
terminate(_Reason, #state{keep_alive=KeepAlive}) ->
|
||||
emqtt_keep_alive:cancel(KeepAlive),
|
||||
emqtt_cm:destroy(self()),
|
||||
ok.
|
||||
handle_info(Info, State = #state{peer_name = PeerName}) ->
|
||||
lager:critical("Client ~s: unexpected info ~p",[PeerName, Info]),
|
||||
{stop, {badinfo, Info}, State}.
|
||||
|
||||
terminate(Reason, #state{ peer_name = PeerName, keepalive = KeepAlive, proto_state = ProtoState }) ->
|
||||
lager:info("Client ~s: ~p terminated, reason: ~p~n", [PeerName, self(), Reason]),
|
||||
emqtt_keepalive:cancel(KeepAlive),
|
||||
case {ProtoState, Reason} of
|
||||
{undefined, _} -> ok;
|
||||
{_, {shutdown, Error}} ->
|
||||
emqtt_protocol:shutdown(Error, ProtoState);
|
||||
{_, _} -> ok %TODO:
|
||||
end,
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
|
||||
async_recv(Sock, Length, infinity) when is_port(Sock) ->
|
||||
prim_inet:async_recv(Sock, Length, -1);
|
||||
|
||||
|
@ -191,221 +167,43 @@ async_recv(Sock, Length, Timeout) when is_port(Sock) ->
|
|||
% receive and parse tcp data
|
||||
%-------------------------------------------------------
|
||||
process_received_bytes(<<>>, State) ->
|
||||
{noreply, State};
|
||||
{noreply, State, hibernate};
|
||||
|
||||
process_received_bytes(Bytes,
|
||||
State = #state{ parse_state = ParseState,
|
||||
proto_state = ProtoState,
|
||||
conn_name = ConnStr }) ->
|
||||
case emqtt_frame:parse(Bytes, ParseState) of
|
||||
{more, ParseState1} ->
|
||||
{noreply,
|
||||
control_throttle( State #state{ parse_state = ParseState1 }),
|
||||
hibernate};
|
||||
{ok, Frame, Rest} ->
|
||||
case process_frame(Frame, State) of
|
||||
{ok, State1} ->
|
||||
PS = emqtt_frame:initial_state(),
|
||||
process_received_bytes(
|
||||
Rest,
|
||||
State1 #state{ parse_state = PS});
|
||||
{err, Reason, State1} ->
|
||||
?ERROR("MQTT protocol error ~p for connection ~p~n", [Reason, ConnStr]),
|
||||
stop({shutdown, Reason}, State1);
|
||||
{stop, State1} ->
|
||||
stop(normal, State1)
|
||||
end;
|
||||
{error, Error} ->
|
||||
?ERROR("MQTT detected framing error ~p for connection ~p~n", [ConnStr, Error]),
|
||||
stop({shutdown, Error}, State)
|
||||
case emqtt_packet:parse(Bytes, ParseState) of
|
||||
{more, ParseState1} ->
|
||||
{noreply,
|
||||
control_throttle( State #state{ parse_state = ParseState1 }),
|
||||
hibernate};
|
||||
{ok, Packet, Rest} ->
|
||||
case emqtt_protocol:handle_packet(Packet, ProtoState) of
|
||||
{ok, ProtoState1} ->
|
||||
process_received_bytes(
|
||||
Rest,
|
||||
State#state{ parse_state = emqtt_packet:initial_state(),
|
||||
proto_state = ProtoState1 });
|
||||
{error, Error} ->
|
||||
lager:error("MQTT protocol error ~p for connection ~p~n", [Error, ConnStr]),
|
||||
stop({shutdown, Error}, State);
|
||||
{error, Error, ProtoState1} ->
|
||||
stop({shutdown, Error}, State#state{proto_state = ProtoState1});
|
||||
{stop, Reason, ProtoState1} ->
|
||||
stop(Reason, State#state{proto_state = ProtoState1})
|
||||
end;
|
||||
{error, Error} ->
|
||||
lager:error("MQTT detected framing error ~p for connection ~p~n", [ConnStr, Error]),
|
||||
stop({shutdown, Error}, State)
|
||||
end.
|
||||
|
||||
process_frame(Frame = #mqtt_frame{fixed = #mqtt_frame_fixed{type = Type}},
|
||||
State=#state{client_id=ClientId, keep_alive=KeepAlive}) ->
|
||||
KeepAlive1 = emqtt_keep_alive:activate(KeepAlive),
|
||||
case validate_frame(Type, Frame) of
|
||||
ok ->
|
||||
?INFO("frame from ~s: ~p", [ClientId, Frame]),
|
||||
handle_retained(Type, Frame),
|
||||
process_request(Type, Frame, State#state{keep_alive=KeepAlive1});
|
||||
{error, Reason} ->
|
||||
{err, Reason, State}
|
||||
end.
|
||||
|
||||
process_request(?CONNECT,
|
||||
#mqtt_frame{ variable = #mqtt_frame_connect{
|
||||
username = Username,
|
||||
password = Password,
|
||||
proto_ver = ProtoVersion,
|
||||
clean_sess = CleanSess,
|
||||
keep_alive = AlivePeriod,
|
||||
client_id = ClientId } = Var}, #state{socket = Sock} = State) ->
|
||||
{ReturnCode, State1} =
|
||||
case {lists:member(ProtoVersion, proplists:get_keys(?PROTOCOL_NAMES)),
|
||||
valid_client_id(ClientId)} of
|
||||
{false, _} ->
|
||||
{?CONNACK_PROTO_VER, State};
|
||||
{_, false} ->
|
||||
{?CONNACK_INVALID_ID, State};
|
||||
_ ->
|
||||
case emqtt_auth:check(Username, Password) of
|
||||
false ->
|
||||
?ERROR_MSG("MQTT login failed - no credentials"),
|
||||
{?CONNACK_CREDENTIALS, State};
|
||||
true ->
|
||||
?INFO("connect from clientid: ~p, ~p", [ClientId, AlivePeriod]),
|
||||
emqtt_cm:create(ClientId, self()),
|
||||
KeepAlive = emqtt_keep_alive:new(AlivePeriod*1500, keep_alive_timeout),
|
||||
{?CONNACK_ACCEPT,
|
||||
State #state{ will_msg = make_will_msg(Var),
|
||||
client_id = ClientId,
|
||||
keep_alive = KeepAlive}}
|
||||
end
|
||||
end,
|
||||
?INFO("recv conn...:~p", [ReturnCode]),
|
||||
send_frame(Sock, #mqtt_frame{ fixed = #mqtt_frame_fixed{ type = ?CONNACK},
|
||||
variable = #mqtt_frame_connack{
|
||||
return_code = ReturnCode }}),
|
||||
{ok, State1};
|
||||
|
||||
process_request(?PUBLISH, Frame=#mqtt_frame{
|
||||
fixed = #mqtt_frame_fixed{qos = ?QOS_0}}, State) ->
|
||||
emqtt_pubsub:publish(make_msg(Frame)),
|
||||
{ok, State};
|
||||
|
||||
process_request(?PUBLISH,
|
||||
Frame=#mqtt_frame{
|
||||
fixed = #mqtt_frame_fixed{qos = ?QOS_1},
|
||||
variable = #mqtt_frame_publish{message_id = MsgId}},
|
||||
State=#state{socket=Sock}) ->
|
||||
emqtt_pubsub:publish(make_msg(Frame)),
|
||||
send_frame(Sock, #mqtt_frame{fixed = #mqtt_frame_fixed{ type = ?PUBACK },
|
||||
variable = #mqtt_frame_publish{ message_id = MsgId}}),
|
||||
{ok, State};
|
||||
|
||||
process_request(?PUBLISH,
|
||||
Frame=#mqtt_frame{
|
||||
fixed = #mqtt_frame_fixed{qos = ?QOS_2},
|
||||
variable = #mqtt_frame_publish{message_id = MsgId}},
|
||||
State=#state{socket=Sock}) ->
|
||||
emqtt_pubsub:publish(make_msg(Frame)),
|
||||
put({msg, MsgId}, pubrec),
|
||||
send_frame(Sock, #mqtt_frame{fixed = #mqtt_frame_fixed{type = ?PUBREC},
|
||||
variable = #mqtt_frame_publish{ message_id = MsgId}}),
|
||||
|
||||
{ok, State};
|
||||
|
||||
process_request(?PUBACK, #mqtt_frame{}, State) ->
|
||||
%TODO: fixme later
|
||||
{ok, State};
|
||||
|
||||
process_request(?PUBREC, #mqtt_frame{
|
||||
variable = #mqtt_frame_publish{message_id = MsgId}},
|
||||
State=#state{socket=Sock}) ->
|
||||
%TODO: fixme later
|
||||
send_frame(Sock,
|
||||
#mqtt_frame{fixed = #mqtt_frame_fixed{ type = ?PUBREL},
|
||||
variable = #mqtt_frame_publish{ message_id = MsgId}}),
|
||||
{ok, State};
|
||||
|
||||
process_request(?PUBREL,
|
||||
#mqtt_frame{
|
||||
variable = #mqtt_frame_publish{message_id = MsgId}},
|
||||
State=#state{socket=Sock}) ->
|
||||
erase({msg, MsgId}),
|
||||
send_frame(Sock,
|
||||
#mqtt_frame{fixed = #mqtt_frame_fixed{ type = ?PUBCOMP},
|
||||
variable = #mqtt_frame_publish{ message_id = MsgId}}),
|
||||
{ok, State};
|
||||
|
||||
process_request(?PUBCOMP, #mqtt_frame{
|
||||
variable = #mqtt_frame_publish{message_id = _MsgId}}, State) ->
|
||||
%TODO: fixme later
|
||||
{ok, State};
|
||||
|
||||
process_request(?SUBSCRIBE,
|
||||
#mqtt_frame{
|
||||
variable = #mqtt_frame_subscribe{message_id = MessageId,
|
||||
topic_table = Topics},
|
||||
payload = undefined},
|
||||
#state{socket=Sock} = State) ->
|
||||
|
||||
[emqtt_pubsub:subscribe({Name, Qos}, self()) ||
|
||||
#mqtt_topic{name=Name, qos=Qos} <- Topics],
|
||||
|
||||
GrantedQos = [Qos || #mqtt_topic{qos=Qos} <- Topics],
|
||||
|
||||
send_frame(Sock, #mqtt_frame{fixed = #mqtt_frame_fixed{type = ?SUBACK},
|
||||
variable = #mqtt_frame_suback{
|
||||
message_id = MessageId,
|
||||
qos_table = GrantedQos}}),
|
||||
|
||||
{ok, State};
|
||||
|
||||
process_request(?UNSUBSCRIBE,
|
||||
#mqtt_frame{
|
||||
variable = #mqtt_frame_subscribe{message_id = MessageId,
|
||||
topic_table = Topics },
|
||||
payload = undefined}, #state{socket = Sock, client_id = ClientId} = State) ->
|
||||
|
||||
|
||||
[emqtt_pubsub:unsubscribe(Name, self()) || #mqtt_topic{name=Name} <- Topics],
|
||||
|
||||
send_frame(Sock, #mqtt_frame{fixed = #mqtt_frame_fixed{type = ?UNSUBACK },
|
||||
variable = #mqtt_frame_suback{message_id = MessageId }}),
|
||||
|
||||
{ok, State};
|
||||
|
||||
process_request(?PINGREQ, #mqtt_frame{}, #state{socket=Sock, keep_alive=KeepAlive}=State) ->
|
||||
%Keep alive timer
|
||||
KeepAlive1 = emqtt_keep_alive:reset(KeepAlive),
|
||||
send_frame(Sock, #mqtt_frame{fixed = #mqtt_frame_fixed{ type = ?PINGRESP }}),
|
||||
{ok, State#state{keep_alive=KeepAlive1}};
|
||||
|
||||
process_request(?DISCONNECT, #mqtt_frame{}, State=#state{client_id=ClientId}) ->
|
||||
?INFO("~s disconnected", [ClientId]),
|
||||
{stop, State}.
|
||||
|
||||
next_msg_id(State = #state{ message_id = 16#ffff }) ->
|
||||
State #state{ message_id = 1 };
|
||||
next_msg_id(State = #state{ message_id = MsgId }) ->
|
||||
State #state{ message_id = MsgId + 1 }.
|
||||
|
||||
maybe_clean_sess(false, _Conn, _ClientId) ->
|
||||
% todo: establish subscription to deliver old unacknowledged messages
|
||||
ok.
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
make_will_msg(#mqtt_frame_connect{ will_flag = false }) ->
|
||||
undefined;
|
||||
make_will_msg(#mqtt_frame_connect{ will_retain = Retain,
|
||||
will_qos = Qos,
|
||||
will_topic = Topic,
|
||||
will_msg = Msg }) ->
|
||||
#mqtt_msg{retain = Retain,
|
||||
qos = Qos,
|
||||
topic = Topic,
|
||||
dup = false,
|
||||
payload = Msg }.
|
||||
|
||||
send_will_msg(#state{will_msg = undefined}) ->
|
||||
ignore;
|
||||
send_will_msg(#state{will_msg = WillMsg }) ->
|
||||
emqtt_pubsub:publish(WillMsg).
|
||||
|
||||
send_frame(Sock, Frame) ->
|
||||
?INFO("send frame:~p", [Frame]),
|
||||
erlang:port_command(Sock, emqtt_frame:serialise(Frame)).
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
network_error(Reason,
|
||||
State = #state{ conn_name = ConnStr}) ->
|
||||
?ERROR("MQTT detected network error '~p' for ~p", [Reason, ConnStr]),
|
||||
send_will_msg(State),
|
||||
% todo: flush channel after publish
|
||||
network_error(Reason, State = #state{ peer_name = PeerName }) ->
|
||||
lager:error("Client ~s: MQTT detected network error '~p'", [PeerName, Reason]),
|
||||
stop({shutdown, conn_closed}, State).
|
||||
|
||||
run_socket(State = #state{ connection_state = blocked }) ->
|
||||
run_socket(State = #state{ conn_state = blocked }) ->
|
||||
State;
|
||||
run_socket(State = #state{ await_recv = true }) ->
|
||||
State;
|
||||
|
@ -413,75 +211,16 @@ run_socket(State = #state{ socket = Sock }) ->
|
|||
async_recv(Sock, 0, infinity),
|
||||
State#state{ await_recv = true }.
|
||||
|
||||
control_throttle(State = #state{ connection_state = Flow,
|
||||
control_throttle(State = #state{ conn_state = Flow,
|
||||
conserve = Conserve }) ->
|
||||
case {Flow, Conserve} of
|
||||
{running, true} -> State #state{ connection_state = blocked };
|
||||
{running, true} -> State #state{ conn_state = blocked };
|
||||
{blocked, false} -> run_socket(State #state{
|
||||
connection_state = running });
|
||||
conn_state = running });
|
||||
{_, _} -> run_socket(State)
|
||||
end.
|
||||
|
||||
stop(Reason, State ) ->
|
||||
{stop, Reason, State}.
|
||||
|
||||
valid_client_id(ClientId) ->
|
||||
ClientIdLen = size(ClientId),
|
||||
1 =< ClientIdLen andalso ClientIdLen =< ?CLIENT_ID_MAXLEN.
|
||||
|
||||
handle_retained(?PUBLISH, #mqtt_frame{fixed = #mqtt_frame_fixed{retain = false}}) ->
|
||||
ignore;
|
||||
|
||||
handle_retained(?PUBLISH, #mqtt_frame{
|
||||
fixed = #mqtt_frame_fixed{retain = true},
|
||||
variable = #mqtt_frame_publish{topic_name = Topic},
|
||||
payload= <<>> }) ->
|
||||
emqtt_retained:delete(Topic);
|
||||
|
||||
handle_retained(?PUBLISH, Frame=#mqtt_frame{
|
||||
fixed = #mqtt_frame_fixed{retain = true},
|
||||
variable = #mqtt_frame_publish{topic_name = Topic}}) ->
|
||||
emqtt_retained:insert(Topic, make_msg(Frame));
|
||||
|
||||
handle_retained(_, _) ->
|
||||
ignore.
|
||||
|
||||
validate_frame(?PUBLISH, #mqtt_frame{variable = #mqtt_frame_publish{topic_name = Topic}}) ->
|
||||
case emqtt_topic:validate({publish, Topic}) of
|
||||
true -> ok;
|
||||
false -> {error, badtopic}
|
||||
end;
|
||||
|
||||
validate_frame(?UNSUBSCRIBE, #mqtt_frame{variable = #mqtt_frame_subscribe{topic_table = Topics}}) ->
|
||||
ErrTopics = [Topic || #mqtt_topic{name=Topic, qos=Qos} <- Topics,
|
||||
not emqtt_topic:validate({subscribe, Topic})],
|
||||
case ErrTopics of
|
||||
[] -> ok;
|
||||
_ -> ?ERROR("error topics: ~p", [ErrTopics]), {error, badtopic}
|
||||
end;
|
||||
|
||||
validate_frame(?SUBSCRIBE, #mqtt_frame{variable = #mqtt_frame_subscribe{topic_table = Topics}}) ->
|
||||
ErrTopics = [Topic || #mqtt_topic{name=Topic, qos=Qos} <- Topics,
|
||||
not (emqtt_topic:validate({subscribe, Topic}) and (Qos < 3))],
|
||||
case ErrTopics of
|
||||
[] -> ok;
|
||||
_ -> ?ERROR("error topics: ~p", [ErrTopics]), {error, badtopic}
|
||||
end;
|
||||
|
||||
validate_frame(_Type, _Frame) ->
|
||||
ok.
|
||||
|
||||
make_msg(#mqtt_frame{
|
||||
fixed = #mqtt_frame_fixed{qos = Qos,
|
||||
retain = Retain,
|
||||
dup = Dup},
|
||||
variable = #mqtt_frame_publish{topic_name = Topic,
|
||||
message_id = MessageId},
|
||||
payload = Payload}) ->
|
||||
#mqtt_msg{retain = Retain,
|
||||
qos = Qos,
|
||||
topic = Topic,
|
||||
dup = Dup,
|
||||
msgid = MessageId,
|
||||
payload = Payload}.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -23,21 +23,21 @@
|
|||
%client manager
|
||||
-module(emqtt_cm).
|
||||
|
||||
-author('feng@slimchat.io').
|
||||
-author('feng@emqtt.io').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
-define(TAB, emqtt_client).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% API Function Exports
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
-export([start_link/0]).
|
||||
|
||||
-export([create/2,
|
||||
destroy/1,
|
||||
lookup/1]).
|
||||
-export([lookup/1, register/2, unregister/2]).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% gen_server Function Exports
|
||||
|
@ -56,47 +56,75 @@
|
|||
start_link() ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||
|
||||
%%
|
||||
%% @doc lookup client pid with clientId.
|
||||
%%
|
||||
-spec lookup(ClientId :: binary()) -> pid() | undefined.
|
||||
lookup(ClientId) ->
|
||||
lookup(ClientId) when is_binary(ClientId) ->
|
||||
case ets:lookup(emqtt_client, ClientId) of
|
||||
[{_, Pid}] -> Pid;
|
||||
[{_, Pid, _}] -> Pid;
|
||||
[] -> undefined
|
||||
end.
|
||||
|
||||
-spec create(ClientId :: binary(), Pid :: pid()) -> ok.
|
||||
create(ClientId, Pid) ->
|
||||
case lookup(ClientId) of
|
||||
Pid ->
|
||||
ignore;
|
||||
OldPid when is_pid(OldPid) ->
|
||||
OldPid ! {stop, duplicate_id},
|
||||
ets:insert(emqtt_client, {ClientId, Pid});
|
||||
undefined ->
|
||||
ets:insert(emqtt_client, {ClientId, Pid})
|
||||
end.
|
||||
%%
|
||||
%% @doc register clientId with pid.
|
||||
%%
|
||||
-spec register(ClientId :: binary(), Pid :: pid()) -> ok.
|
||||
register(ClientId, Pid) when is_binary(ClientId), is_pid(Pid) ->
|
||||
gen_server:call(?SERVER, {register, ClientId, Pid}).
|
||||
|
||||
-spec destroy(binary() | pid()) -> ok.
|
||||
destroy(ClientId) when is_binary(ClientId) ->
|
||||
ets:delete(emqtt_client, ClientId);
|
||||
|
||||
destroy(Pid) when is_pid(Pid) ->
|
||||
ets:match_delete(emqtt_client, {{'_', Pid}}).
|
||||
%%
|
||||
%% @doc unregister clientId with pid.
|
||||
%%
|
||||
-spec unregister(ClientId :: binary(), Pid :: pid()) -> ok.
|
||||
unregister(ClientId, Pid) when is_binary(ClientId), is_pid(Pid) ->
|
||||
gen_server:cast(?SERVER, {unregister, ClientId, Pid}).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% gen_server Function Definitions
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
init(Args) ->
|
||||
init([]) ->
|
||||
%on one node
|
||||
ets:new(emqtt_client, [named_table, public]),
|
||||
{ok, Args}.
|
||||
ets:new(?TAB, [set, named_table, protected]),
|
||||
{ok, none}.
|
||||
|
||||
handle_call({register, ClientId, Pid}, _From, State) ->
|
||||
case ets:lookup(?TAB, ClientId) of
|
||||
[{_, Pid, _}] ->
|
||||
lager:error("clientId '~s' has been registered with ~p", [ClientId, Pid]),
|
||||
ignore;
|
||||
[{_, OldPid, MRef}] ->
|
||||
OldPid ! {stop, duplicate_id, Pid},
|
||||
erlang:demonitor(MRef),
|
||||
insert(ClientId, Pid);
|
||||
[] ->
|
||||
insert(ClientId, Pid)
|
||||
end,
|
||||
{reply, ok, State};
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, ok, State}.
|
||||
|
||||
handle_cast({unregister, ClientId, Pid}, State) ->
|
||||
case ets:lookup(?TAB, ClientId) of
|
||||
[{_, Pid, MRef}] ->
|
||||
erlang:demonitor(MRef),
|
||||
ets:delete(?TAB, ClientId);
|
||||
[_] ->
|
||||
ignore;
|
||||
[] ->
|
||||
lager:error("cannot find clientId '~s' with ~p", [ClientId, Pid])
|
||||
end,
|
||||
{noreply, State};
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({'DOWN', MRef, process, DownPid, _Reason}, State) ->
|
||||
ets:match_delete(emqtt_client, {{'_', DownPid, MRef}}),
|
||||
{noreply, State};
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
|
@ -106,4 +134,6 @@ terminate(_Reason, _State) ->
|
|||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
insert(ClientId, Pid) ->
|
||||
ets:insert(emqtt_client, {ClientId, Pid, erlang:monitor(process, Pid)}).
|
||||
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
-module(emqtt_config).
|
||||
|
||||
-export([lookup/1]).
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% API Function Exports
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
-export([start_link/0]).
|
||||
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% gen_server Function Exports
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% API Function Definitions
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||
|
||||
%%TODO: fix later...
|
||||
lookup(Key) -> {ok, Key}.
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% gen_server Function Definitions
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
init(_Args) ->
|
||||
ets:new(?MODULE, [set, protected, named_table]),
|
||||
%%TODO: Load application config.
|
||||
{ok, none}.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, ok, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% Internal Function Definitions
|
||||
%% ------------------------------------------------------------------
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -22,17 +22,20 @@
|
|||
|
||||
-module(emqtt_ctl).
|
||||
|
||||
-author('feng@slimchat.io').
|
||||
-author('feng@emqtt.io').
|
||||
|
||||
-include("emqtt.hrl").
|
||||
|
||||
-include("emqtt_log.hrl").
|
||||
-define(PRINT_MSG(Msg),
|
||||
io:format(Msg)).
|
||||
|
||||
-define(PRINT(Format, Args),
|
||||
io:format(Format, Args)).
|
||||
|
||||
-export([status/1,
|
||||
cluster_info/1,
|
||||
cluster/1,
|
||||
add_user/1,
|
||||
delete_user/1]).
|
||||
useradd/1,
|
||||
userdel/1]).
|
||||
|
||||
status([]) ->
|
||||
{InternalStatus, _ProvidedStatus} = init:get_status(),
|
||||
|
@ -44,26 +47,47 @@ status([]) ->
|
|||
?PRINT_MSG("emqtt is running~n")
|
||||
end.
|
||||
|
||||
cluster_info([]) ->
|
||||
cluster([]) ->
|
||||
Nodes = [node()|nodes()],
|
||||
?PRINT("cluster nodes: ~p~n", [Nodes]).
|
||||
?PRINT("cluster nodes: ~p~n", [Nodes]);
|
||||
|
||||
cluster([SNode]) ->
|
||||
Node = list_to_atom(SNode),
|
||||
Node = node_name(SNode),
|
||||
case net_adm:ping(Node) of
|
||||
pong ->
|
||||
application:stop(emqtt),
|
||||
application:stop(esockd),
|
||||
mnesia:stop(),
|
||||
mnesia:start(),
|
||||
mnesia:change_config(extra_db_nodes, [Node]),
|
||||
application:start(esockd),
|
||||
application:start(emqtt),
|
||||
?PRINT("cluster with ~p successfully.~n", [Node]);
|
||||
pang ->
|
||||
?PRINT("failed to connect to ~p~n", [Node])
|
||||
end.
|
||||
|
||||
add_user([Username, Password]) ->
|
||||
|
||||
useradd([Username, Password]) ->
|
||||
?PRINT("~p", [emqtt_auth:add(list_to_binary(Username), list_to_binary(Password))]).
|
||||
|
||||
delete_user([Username]) ->
|
||||
userdel([Username]) ->
|
||||
?PRINT("~p", [emqtt_auth:delete(list_to_binary(Username))]).
|
||||
|
||||
node_name(SNode) ->
|
||||
SNode1 =
|
||||
case string:tokens(SNode, "@") of
|
||||
[_Node, _Server] ->
|
||||
SNode;
|
||||
_ ->
|
||||
case net_kernel:longnames() of
|
||||
true ->
|
||||
SNode ++ "@" ++ inet_db:gethostname() ++
|
||||
"." ++ inet_db:res_option(domain);
|
||||
false ->
|
||||
SNode ++ "@" ++ inet_db:gethostname();
|
||||
_ ->
|
||||
SNode
|
||||
end
|
||||
end,
|
||||
list_to_atom(SNode1).
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -22,7 +22,7 @@
|
|||
|
||||
-module(emqtt_db).
|
||||
|
||||
-author('feng@slimchat.io').
|
||||
-author('feng@emqtt.io').
|
||||
|
||||
-export([init/0, stop/0]).
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
-module(emqtt_event).
|
||||
|
||||
-export([start_link/0]).
|
||||
|
||||
start_link() ->
|
||||
gen_event:start_link({local, ?MODULE}).
|
||||
|
|
@ -1,266 +0,0 @@
|
|||
%%------------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
%%
|
||||
%% The Original Code is from RabbitMQ.
|
||||
%%
|
||||
|
||||
-module(emqtt_frame).
|
||||
|
||||
-include("emqtt_frame.hrl").
|
||||
|
||||
-export([parse/2, initial_state/0]).
|
||||
-export([serialise/1]).
|
||||
|
||||
-define(RESERVED, 0).
|
||||
-define(MAX_LEN, 16#fffffff).
|
||||
-define(HIGHBIT, 2#10000000).
|
||||
-define(LOWBITS, 2#01111111).
|
||||
|
||||
initial_state() -> none.
|
||||
|
||||
parse(<<>>, none) ->
|
||||
{more, fun(Bin) -> parse(Bin, none) end};
|
||||
parse(<<MessageType:4, Dup:1, QoS:2, Retain:1, Rest/binary>>, none) ->
|
||||
parse_remaining_len(Rest, #mqtt_frame_fixed{ type = MessageType,
|
||||
dup = bool(Dup),
|
||||
qos = QoS,
|
||||
retain = bool(Retain) });
|
||||
parse(Bin, Cont) -> Cont(Bin).
|
||||
|
||||
parse_remaining_len(<<>>, Fixed) ->
|
||||
{more, fun(Bin) -> parse_remaining_len(Bin, Fixed) end};
|
||||
parse_remaining_len(Rest, Fixed) ->
|
||||
parse_remaining_len(Rest, Fixed, 1, 0).
|
||||
|
||||
parse_remaining_len(_Bin, _Fixed, _Multiplier, Length)
|
||||
when Length > ?MAX_LEN ->
|
||||
{error, invalid_mqtt_frame_len};
|
||||
parse_remaining_len(<<>>, Fixed, Multiplier, Length) ->
|
||||
{more, fun(Bin) -> parse_remaining_len(Bin, Fixed, Multiplier, Length) end};
|
||||
parse_remaining_len(<<1:1, Len:7, Rest/binary>>, Fixed, Multiplier, Value) ->
|
||||
parse_remaining_len(Rest, Fixed, Multiplier * ?HIGHBIT, Value + Len * Multiplier);
|
||||
parse_remaining_len(<<0:1, Len:7, Rest/binary>>, Fixed, Multiplier, Value) ->
|
||||
parse_frame(Rest, Fixed, Value + Len * Multiplier).
|
||||
|
||||
parse_frame(Bin, #mqtt_frame_fixed{ type = Type,
|
||||
qos = Qos } = Fixed, Length) ->
|
||||
case {Type, Bin} of
|
||||
{?CONNECT, <<FrameBin:Length/binary, Rest/binary>>} ->
|
||||
{ProtoName, Rest1} = parse_utf(FrameBin),
|
||||
<<ProtoVersion : 8, Rest2/binary>> = Rest1,
|
||||
<<UsernameFlag : 1,
|
||||
PasswordFlag : 1,
|
||||
WillRetain : 1,
|
||||
WillQos : 2,
|
||||
WillFlag : 1,
|
||||
CleanSession : 1,
|
||||
_Reserved : 1,
|
||||
KeepAlive : 16/big,
|
||||
Rest3/binary>> = Rest2,
|
||||
{ClientId, Rest4} = parse_utf(Rest3),
|
||||
{WillTopic, Rest5} = parse_utf(Rest4, WillFlag),
|
||||
{WillMsg, Rest6} = parse_msg(Rest5, WillFlag),
|
||||
{UserName, Rest7} = parse_utf(Rest6, UsernameFlag),
|
||||
{PasssWord, <<>>} = parse_utf(Rest7, PasswordFlag),
|
||||
case protocol_name_approved(ProtoVersion, ProtoName) of
|
||||
true ->
|
||||
wrap(Fixed,
|
||||
#mqtt_frame_connect{
|
||||
proto_ver = ProtoVersion,
|
||||
will_retain = bool(WillRetain),
|
||||
will_qos = WillQos,
|
||||
will_flag = bool(WillFlag),
|
||||
clean_sess = bool(CleanSession),
|
||||
keep_alive = KeepAlive,
|
||||
client_id = ClientId,
|
||||
will_topic = WillTopic,
|
||||
will_msg = WillMsg,
|
||||
username = UserName,
|
||||
password = PasssWord}, Rest);
|
||||
false ->
|
||||
{error, protocol_header_corrupt}
|
||||
end;
|
||||
{?PUBLISH, <<FrameBin:Length/binary, Rest/binary>>} ->
|
||||
{TopicName, Rest1} = parse_utf(FrameBin),
|
||||
{MessageId, Payload} = case Qos of
|
||||
0 -> {undefined, Rest1};
|
||||
_ -> <<M:16/big, R/binary>> = Rest1,
|
||||
{M, R}
|
||||
end,
|
||||
wrap(Fixed, #mqtt_frame_publish {topic_name = TopicName,
|
||||
message_id = MessageId },
|
||||
Payload, Rest);
|
||||
{?PUBACK, <<FrameBin:Length/binary, Rest/binary>>} ->
|
||||
<<MessageId:16/big>> = FrameBin,
|
||||
wrap(Fixed, #mqtt_frame_publish{message_id = MessageId}, Rest);
|
||||
{?PUBREC, <<FrameBin:Length/binary, Rest/binary>>} ->
|
||||
<<MessageId:16/big>> = FrameBin,
|
||||
wrap(Fixed, #mqtt_frame_publish{message_id = MessageId}, Rest);
|
||||
{?PUBREL, <<FrameBin:Length/binary, Rest/binary>>} ->
|
||||
<<MessageId:16/big>> = FrameBin,
|
||||
wrap(Fixed, #mqtt_frame_publish { message_id = MessageId }, Rest);
|
||||
{?PUBCOMP, <<FrameBin:Length/binary, Rest/binary>>} ->
|
||||
<<MessageId:16/big>> = FrameBin,
|
||||
wrap(Fixed, #mqtt_frame_publish { message_id = MessageId }, Rest);
|
||||
{Subs, <<FrameBin:Length/binary, Rest/binary>>}
|
||||
when Subs =:= ?SUBSCRIBE orelse Subs =:= ?UNSUBSCRIBE ->
|
||||
1 = Qos,
|
||||
<<MessageId:16/big, Rest1/binary>> = FrameBin,
|
||||
Topics = parse_topics(Subs, Rest1, []),
|
||||
wrap(Fixed, #mqtt_frame_subscribe { message_id = MessageId,
|
||||
topic_table = Topics }, Rest);
|
||||
{Minimal, Rest}
|
||||
when Minimal =:= ?DISCONNECT orelse Minimal =:= ?PINGREQ ->
|
||||
Length = 0,
|
||||
wrap(Fixed, Rest);
|
||||
{_, TooShortBin} ->
|
||||
{more, fun(BinMore) ->
|
||||
parse_frame(<<TooShortBin/binary, BinMore/binary>>,
|
||||
Fixed, Length)
|
||||
end}
|
||||
end.
|
||||
|
||||
parse_topics(_, <<>>, Topics) ->
|
||||
Topics;
|
||||
parse_topics(?SUBSCRIBE = Sub, Bin, Topics) ->
|
||||
{Name, <<_:6, QoS:2, Rest/binary>>} = parse_utf(Bin),
|
||||
parse_topics(Sub, Rest, [#mqtt_topic { name = Name, qos = QoS } | Topics]);
|
||||
parse_topics(?UNSUBSCRIBE = Sub, Bin, Topics) ->
|
||||
{Name, <<Rest/binary>>} = parse_utf(Bin),
|
||||
parse_topics(Sub, Rest, [#mqtt_topic { name = Name } | Topics]).
|
||||
|
||||
wrap(Fixed, Variable, Payload, Rest) ->
|
||||
{ok, #mqtt_frame { variable = Variable, fixed = Fixed, payload = Payload }, Rest}.
|
||||
wrap(Fixed, Variable, Rest) ->
|
||||
{ok, #mqtt_frame { variable = Variable, fixed = Fixed }, Rest}.
|
||||
wrap(Fixed, Rest) ->
|
||||
{ok, #mqtt_frame { fixed = Fixed }, Rest}.
|
||||
|
||||
parse_utf(Bin, 0) ->
|
||||
{undefined, Bin};
|
||||
parse_utf(Bin, _) ->
|
||||
parse_utf(Bin).
|
||||
|
||||
parse_utf(<<Len:16/big, Str:Len/binary, Rest/binary>>) ->
|
||||
{Str, Rest}.
|
||||
|
||||
parse_msg(Bin, 0) ->
|
||||
{undefined, Bin};
|
||||
parse_msg(<<Len:16/big, Msg:Len/binary, Rest/binary>>, _) ->
|
||||
{Msg, Rest}.
|
||||
|
||||
bool(0) -> false;
|
||||
bool(1) -> true.
|
||||
|
||||
%% serialisation
|
||||
|
||||
serialise(#mqtt_frame{ fixed = Fixed,
|
||||
variable = Variable,
|
||||
payload = Payload }) ->
|
||||
serialise_variable(Fixed, Variable, serialise_payload(Payload)).
|
||||
|
||||
serialise_payload(undefined) -> <<>>;
|
||||
serialise_payload(B) when is_binary(B) -> B.
|
||||
|
||||
serialise_variable(#mqtt_frame_fixed { type = ?CONNACK } = Fixed,
|
||||
#mqtt_frame_connack { return_code = ReturnCode },
|
||||
<<>> = PayloadBin) ->
|
||||
VariableBin = <<?RESERVED:8, ReturnCode:8>>,
|
||||
serialise_fixed(Fixed, VariableBin, PayloadBin);
|
||||
|
||||
serialise_variable(#mqtt_frame_fixed { type = SubAck } = Fixed,
|
||||
#mqtt_frame_suback { message_id = MessageId,
|
||||
qos_table = Qos },
|
||||
<<>> = _PayloadBin)
|
||||
when SubAck =:= ?SUBACK orelse SubAck =:= ?UNSUBACK ->
|
||||
VariableBin = <<MessageId:16/big>>,
|
||||
QosBin = << <<?RESERVED:6, Q:2>> || Q <- Qos >>,
|
||||
serialise_fixed(Fixed, VariableBin, QosBin);
|
||||
|
||||
serialise_variable(#mqtt_frame_fixed { type = ?PUBLISH,
|
||||
qos = Qos } = Fixed,
|
||||
#mqtt_frame_publish { topic_name = TopicName,
|
||||
message_id = MessageId },
|
||||
PayloadBin) ->
|
||||
TopicBin = serialise_utf(TopicName),
|
||||
MessageIdBin = case Qos of
|
||||
0 -> <<>>;
|
||||
1 -> <<MessageId:16/big>>;
|
||||
2 -> <<MessageId:16/big>>
|
||||
end,
|
||||
serialise_fixed(Fixed, <<TopicBin/binary, MessageIdBin/binary>>, PayloadBin);
|
||||
|
||||
serialise_variable(#mqtt_frame_fixed { type = ?PUBACK } = Fixed,
|
||||
#mqtt_frame_publish { message_id = MessageId },
|
||||
PayloadBin) ->
|
||||
MessageIdBin = <<MessageId:16/big>>,
|
||||
serialise_fixed(Fixed, MessageIdBin, PayloadBin);
|
||||
|
||||
serialise_variable(#mqtt_frame_fixed { type = ?PUBREC } = Fixed,
|
||||
#mqtt_frame_publish{ message_id = MsgId},
|
||||
PayloadBin) ->
|
||||
serialise_fixed(Fixed, <<MsgId:16/big>>, PayloadBin);
|
||||
|
||||
serialise_variable(#mqtt_frame_fixed { type = ?PUBREL } = Fixed,
|
||||
#mqtt_frame_publish{ message_id = MsgId},
|
||||
PayloadBin) ->
|
||||
serialise_fixed(Fixed, <<MsgId:16/big>>, PayloadBin);
|
||||
|
||||
serialise_variable(#mqtt_frame_fixed { type = ?PUBCOMP } = Fixed,
|
||||
#mqtt_frame_publish{ message_id = MsgId},
|
||||
PayloadBin) ->
|
||||
serialise_fixed(Fixed, <<MsgId:16/big>>, PayloadBin);
|
||||
|
||||
serialise_variable(#mqtt_frame_fixed {} = Fixed,
|
||||
undefined,
|
||||
<<>> = _PayloadBin) ->
|
||||
serialise_fixed(Fixed, <<>>, <<>>).
|
||||
|
||||
serialise_fixed(#mqtt_frame_fixed{ type = Type,
|
||||
dup = Dup,
|
||||
qos = Qos,
|
||||
retain = Retain }, VariableBin, PayloadBin)
|
||||
when is_integer(Type) andalso ?CONNECT =< Type andalso Type =< ?DISCONNECT ->
|
||||
Len = size(VariableBin) + size(PayloadBin),
|
||||
true = (Len =< ?MAX_LEN),
|
||||
LenBin = serialise_len(Len),
|
||||
<<Type:4, (opt(Dup)):1, (opt(Qos)):2, (opt(Retain)):1,
|
||||
LenBin/binary, VariableBin/binary, PayloadBin/binary>>.
|
||||
|
||||
serialise_utf(String) ->
|
||||
StringBin = unicode:characters_to_binary(String),
|
||||
Len = size(StringBin),
|
||||
true = (Len =< 16#ffff),
|
||||
<<Len:16/big, StringBin/binary>>.
|
||||
|
||||
serialise_len(N) when N =< ?LOWBITS ->
|
||||
<<0:1, N:7>>;
|
||||
serialise_len(N) ->
|
||||
<<1:1, (N rem ?HIGHBIT):7, (serialise_len(N div ?HIGHBIT))/binary>>.
|
||||
|
||||
opt(undefined) -> ?RESERVED;
|
||||
opt(false) -> 0;
|
||||
opt(true) -> 1;
|
||||
opt(X) when is_integer(X) -> X.
|
||||
|
||||
protocol_name_approved(Ver, Name) ->
|
||||
lists:member({Ver, Name}, ?PROTOCOL_NAMES).
|
|
@ -1,5 +1,5 @@
|
|||
%%------------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -22,12 +22,10 @@
|
|||
|
||||
-module(emqtt_http).
|
||||
|
||||
-author('feng@slimchat.io').
|
||||
-author('feng@emqtt.io').
|
||||
|
||||
-include("emqtt.hrl").
|
||||
|
||||
-include("emqtt_log.hrl").
|
||||
|
||||
-import(proplists, [get_value/2, get_value/3]).
|
||||
|
||||
-export([handle/1]).
|
||||
|
@ -45,14 +43,11 @@ handle(Req) ->
|
|||
|
||||
handle('POST', "/mqtt/publish", Req) ->
|
||||
Params = mochiweb_request:parse_post(Req),
|
||||
?INFO("~p~n", [Params]),
|
||||
lager:info("~p~n", [Params]),
|
||||
Topic = list_to_binary(get_value("topic", Params)),
|
||||
Message = list_to_binary(get_value("message", Params)),
|
||||
emqtt_pubsub:publish(#mqtt_msg {
|
||||
retain = 0,
|
||||
qos = ?QOS_0,
|
||||
emqtt_pubsub:publish(#mqtt_message {
|
||||
topic = Topic,
|
||||
dup = 0,
|
||||
payload = Message
|
||||
}),
|
||||
Req:ok({"text/plan", "ok"});
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
-module(emqtt_keepalive).
|
||||
|
||||
-author('feng@emqtt.io').
|
||||
|
||||
-export([new/3, resume/1, cancel/1]).
|
||||
|
||||
-record(keepalive, {socket, recv_oct, timeout_sec, timeout_msg, timer_ref}).
|
||||
|
||||
%%
|
||||
%% @doc create a keepalive.
|
||||
%%
|
||||
new(Socket, TimeoutSec, TimeoutMsg) when TimeoutSec > 0 ->
|
||||
{ok, [{recv_oct, RecvOct}]} = inet:getstat(Socket, [recv_oct]),
|
||||
Ref = erlang:send_after(TimeoutSec*1000, self(), TimeoutMsg),
|
||||
#keepalive { socket = Socket,
|
||||
recv_oct = RecvOct,
|
||||
timeout_sec = TimeoutSec,
|
||||
timeout_msg = TimeoutMsg,
|
||||
timer_ref = Ref }.
|
||||
|
||||
%%
|
||||
%% @doc try to resume keepalive, called when timeout.
|
||||
%%
|
||||
resume(KeepAlive = #keepalive { socket = Socket,
|
||||
recv_oct = RecvOct,
|
||||
timeout_sec = TimeoutSec,
|
||||
timeout_msg = TimeoutMsg,
|
||||
timer_ref = Ref }) ->
|
||||
{ok, [{recv_oct, NewRecvOct}]} = inet:getstat(Socket, [recv_oct]),
|
||||
if
|
||||
NewRecvOct =:= RecvOct ->
|
||||
timeout;
|
||||
true ->
|
||||
%need?
|
||||
cancel(Ref),
|
||||
NewRef = erlang:send_after(TimeoutSec*1000, self(), TimeoutMsg),
|
||||
{resumed, KeepAlive#keepalive { recv_oct = NewRecvOct, timer_ref = NewRef }}
|
||||
end.
|
||||
|
||||
%%
|
||||
%% @doc cancel keepalive
|
||||
%%
|
||||
cancel(#keepalive { timer_ref = Ref }) ->
|
||||
cancel(Ref);
|
||||
cancel(undefined) ->
|
||||
undefined;
|
||||
cancel(Ref) ->
|
||||
catch erlang:cancel_timer(Ref).
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
-module(emqtt_message).
|
||||
|
||||
-author('feng@emqtt.io').
|
||||
|
||||
-include("emqtt.hrl").
|
||||
|
||||
-include("emqtt_packet.hrl").
|
||||
|
||||
-export([from_packet/1, to_packet/1]).
|
||||
|
||||
-export([set_flag/1, set_flag/2, unset_flag/1, unset_flag/2]).
|
||||
|
||||
-export([dump/1]).
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
-ifdef(use_specs).
|
||||
|
||||
-spec( from_packet( mqtt_packet() ) -> mqtt_message() | undefined ).
|
||||
|
||||
-spec( to_packet( mqtt_message() ) -> mqtt_packet() ).
|
||||
|
||||
-sepc( set_flag(atom(), mqtt_message() ) -> mqtt_message().
|
||||
|
||||
-sepc( unset_flag(atom(), mqtt_message() ) -> mqtt_message().
|
||||
|
||||
-endif.
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
%%
|
||||
%% @doc message from packet
|
||||
%%
|
||||
from_packet(#mqtt_packet{ header = #mqtt_packet_header{ type = ?PUBLISH,
|
||||
retain = Retain,
|
||||
qos = Qos,
|
||||
dup = Dup },
|
||||
variable = #mqtt_packet_publish{ topic_name = Topic,
|
||||
packet_id = PacketId },
|
||||
payload = Payload }) ->
|
||||
#mqtt_message{ msgid = PacketId,
|
||||
qos = Qos,
|
||||
retain = Retain,
|
||||
dup = Dup,
|
||||
topic = Topic,
|
||||
payload = Payload };
|
||||
|
||||
from_packet(#mqtt_packet_connect{ will_flag = false }) ->
|
||||
undefined;
|
||||
|
||||
from_packet(#mqtt_packet_connect{ will_retain = Retain,
|
||||
will_qos = Qos,
|
||||
will_topic = Topic,
|
||||
will_msg = Msg }) ->
|
||||
#mqtt_message{ retain = Retain,
|
||||
qos = Qos,
|
||||
topic = Topic,
|
||||
dup = false,
|
||||
payload = Msg }.
|
||||
|
||||
%%
|
||||
%% @doc message to packet
|
||||
%%
|
||||
to_packet(#mqtt_message{ msgid = MsgId,
|
||||
qos = Qos,
|
||||
retain = Retain,
|
||||
dup = Dup,
|
||||
topic = Topic,
|
||||
payload = Payload }) ->
|
||||
|
||||
PacketId = if
|
||||
Qos =:= ?QOS_0 -> undefined;
|
||||
true -> MsgId
|
||||
end,
|
||||
|
||||
#mqtt_packet{ header = #mqtt_packet_header { type = ?PUBLISH,
|
||||
qos = Qos,
|
||||
retain = Retain,
|
||||
dup = Dup },
|
||||
variable = #mqtt_packet_publish { topic_name = Topic,
|
||||
packet_id = PacketId },
|
||||
payload = Payload }.
|
||||
|
||||
%%
|
||||
%% @doc set dup, retain flag
|
||||
%%
|
||||
set_flag(Msg) ->
|
||||
Msg#mqtt_message{dup = true, retain = true}.
|
||||
set_flag(dup, Msg = #mqtt_message{dup = false}) ->
|
||||
Msg#mqtt_message{dup = true};
|
||||
set_flag(retain, Msg = #mqtt_message{retain = false}) ->
|
||||
Msg#mqtt_message{retain = true};
|
||||
set_flag(Flag, Msg) when Flag =:= dup orelse Flag =:= retain -> Msg.
|
||||
|
||||
unset_flag(Msg) ->
|
||||
Msg#mqtt_message{dup = false, retain = false}.
|
||||
unset_flag(dup, Msg = #mqtt_message{dup = true}) ->
|
||||
Msg#mqtt_message{dup = false};
|
||||
unset_flag(retain, Msg = #mqtt_message{retain = true}) ->
|
||||
Msg#mqtt_message{retain = false};
|
||||
unset_flag(Flag, Msg) when Flag =:= dup orelse Flag =:= retain -> Msg.
|
||||
|
||||
|
||||
%%
|
||||
%% @doc dump message
|
||||
%%
|
||||
dump(#mqtt_message{msgid= MsgId, qos = Qos, retain = Retain, dup = Dup, topic = Topic}) ->
|
||||
io_lib:format("Message(MsgId=~p, Qos=~p, Retain=~s, Dup=~s, Topic=~s)",
|
||||
[ MsgId, Qos, Retain, Dup, Topic ]).
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -22,9 +22,7 @@
|
|||
|
||||
-module(emqtt_monitor).
|
||||
|
||||
-author('feng@slimchat.io').
|
||||
|
||||
-include("emqtt_log.hrl").
|
||||
-author('feng@emqtt.io').
|
||||
|
||||
-behavior(gen_server).
|
||||
|
||||
|
@ -50,7 +48,6 @@ start_link() ->
|
|||
%%--------------------------------------------------------------------
|
||||
init([]) ->
|
||||
erlang:system_monitor(self(), [{long_gc, 5000}, {large_heap, 1000000}, busy_port]),
|
||||
?INFO("monitor is started...[ok]", []),
|
||||
{ok, #state{}}.
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
||||
|
@ -62,7 +59,7 @@ init([]) ->
|
|||
%% Description: Handling call messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call(Request, _From, State) ->
|
||||
?ERROR("unexpected request: ~p", [Request]),
|
||||
lager:error("unexpected request: ~p", [Request]),
|
||||
{reply, {error, unexpected_request}, State}.
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
|
@ -71,7 +68,7 @@ handle_call(Request, _From, State) ->
|
|||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast(Msg, State) ->
|
||||
?ERROR("unexpected msg: ~p", [Msg]),
|
||||
lager:error("unexpected msg: ~p", [Msg]),
|
||||
{noreply, State}.
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_info(Info, State) -> {noreply, State} |
|
||||
|
@ -80,22 +77,22 @@ handle_cast(Msg, State) ->
|
|||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_info({monitor, GcPid, long_gc, Info}, State) ->
|
||||
?ERROR("long_gc: gcpid = ~p, ~p ~n ~p", [GcPid, process_info(GcPid,
|
||||
lager:error("long_gc: gcpid = ~p, ~p ~n ~p", [GcPid, process_info(GcPid,
|
||||
[registered_name, memory, message_queue_len,heap_size,total_heap_size]), Info]),
|
||||
{noreply, State};
|
||||
|
||||
handle_info({monitor, GcPid, large_heap, Info}, State) ->
|
||||
?ERROR("large_heap: gcpid = ~p,~p ~n ~p", [GcPid, process_info(GcPid,
|
||||
lager:error("large_heap: gcpid = ~p,~p ~n ~p", [GcPid, process_info(GcPid,
|
||||
[registered_name, memory, message_queue_len,heap_size,total_heap_size]), Info]),
|
||||
{noreply, State};
|
||||
|
||||
handle_info({monitor, SusPid, busy_port, Port}, State) ->
|
||||
?ERROR("busy_port: suspid = ~p, port = ~p", [process_info(SusPid,
|
||||
lager:error("busy_port: suspid = ~p, port = ~p", [process_info(SusPid,
|
||||
[registered_name, memory, message_queue_len,heap_size,total_heap_size]), Port]),
|
||||
{noreply, State};
|
||||
|
||||
handle_info(Info, State) ->
|
||||
?ERROR("unexpected info: ~p", [Info]),
|
||||
lager:error("unexpected info: ~p", [Info]),
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -22,11 +22,11 @@
|
|||
|
||||
-module(emqtt_net).
|
||||
|
||||
-author('feng@slimchat.io').
|
||||
-author('feng@emqtt.io').
|
||||
|
||||
-export([tcp_name/3, tcp_host/1, getopts/2, setopts/2, getaddr/2, port_to_listeners/1]).
|
||||
|
||||
-export([connection_string/2]).
|
||||
-export([peername/1, sockname/1, peer_string/1, connection_string/2]).
|
||||
|
||||
-include_lib("kernel/include/inet.hrl").
|
||||
|
||||
|
@ -196,6 +196,14 @@ setopts(Sock, Options) when is_port(Sock) ->
|
|||
|
||||
sockname(Sock) when is_port(Sock) -> inet:sockname(Sock).
|
||||
|
||||
peer_string(Sock) ->
|
||||
case peername(Sock) of
|
||||
{ok, {Addr, Port}} ->
|
||||
{ok, lists:flatten(io_lib:format("~s:~p", [maybe_ntoab(Addr), Port]))};
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
peername(Sock) when is_port(Sock) -> inet:peername(Sock).
|
||||
|
||||
ntoa({0,0,0,0,0,16#ffff,AB,CD}) ->
|
||||
|
|
|
@ -0,0 +1,353 @@
|
|||
%%------------------------------------------------------------------------------
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
%%
|
||||
%% The Original Code is from RabbitMQ.
|
||||
%%
|
||||
|
||||
-module(emqtt_packet).
|
||||
|
||||
-include("emqtt_packet.hrl").
|
||||
|
||||
-export([initial_state/0]).
|
||||
|
||||
-export([parse/2, serialise/1]).
|
||||
|
||||
-export([dump/1]).
|
||||
|
||||
-define(MAX_LEN, 16#fffffff).
|
||||
-define(HIGHBIT, 2#10000000).
|
||||
-define(LOWBITS, 2#01111111).
|
||||
|
||||
initial_state() -> none.
|
||||
|
||||
parse(<<>>, none) ->
|
||||
{more, fun(Bin) -> parse(Bin, none) end};
|
||||
parse(<<PacketType:4, Dup:1, QoS:2, Retain:1, Rest/binary>>, none) ->
|
||||
parse_remaining_len(Rest, #mqtt_packet_header{type = PacketType,
|
||||
dup = bool(Dup),
|
||||
qos = QoS,
|
||||
retain = bool(Retain) });
|
||||
parse(Bin, Cont) -> Cont(Bin).
|
||||
|
||||
parse_remaining_len(<<>>, Header) ->
|
||||
{more, fun(Bin) -> parse_remaining_len(Bin, Header) end};
|
||||
parse_remaining_len(Rest, Header) ->
|
||||
parse_remaining_len(Rest, Header, 1, 0).
|
||||
|
||||
parse_remaining_len(_Bin, _Header, _Multiplier, Length)
|
||||
when Length > ?MAX_LEN ->
|
||||
{error, invalid_mqtt_frame_len};
|
||||
parse_remaining_len(<<>>, Header, Multiplier, Length) ->
|
||||
{more, fun(Bin) -> parse_remaining_len(Bin, Header, Multiplier, Length) end};
|
||||
parse_remaining_len(<<1:1, Len:7, Rest/binary>>, Header, Multiplier, Value) ->
|
||||
parse_remaining_len(Rest, Header, Multiplier * ?HIGHBIT, Value + Len * Multiplier);
|
||||
parse_remaining_len(<<0:1, Len:7, Rest/binary>>, Header, Multiplier, Value) ->
|
||||
parse_frame(Rest, Header, Value + Len * Multiplier).
|
||||
|
||||
parse_frame(Bin, #mqtt_packet_header{ type = Type,
|
||||
qos = Qos } = Header, Length) ->
|
||||
case {Type, Bin} of
|
||||
{?CONNECT, <<FrameBin:Length/binary, Rest/binary>>} ->
|
||||
{ProtoName, Rest1} = parse_utf(FrameBin),
|
||||
<<ProtoVersion : 8, Rest2/binary>> = Rest1,
|
||||
<<UsernameFlag : 1,
|
||||
PasswordFlag : 1,
|
||||
WillRetain : 1,
|
||||
WillQos : 2,
|
||||
WillFlag : 1,
|
||||
CleanSession : 1,
|
||||
_Reserved : 1,
|
||||
KeepAlive : 16/big,
|
||||
Rest3/binary>> = Rest2,
|
||||
{ClientId, Rest4} = parse_utf(Rest3),
|
||||
{WillTopic, Rest5} = parse_utf(Rest4, WillFlag),
|
||||
{WillMsg, Rest6} = parse_msg(Rest5, WillFlag),
|
||||
{UserName, Rest7} = parse_utf(Rest6, UsernameFlag),
|
||||
{PasssWord, <<>>} = parse_utf(Rest7, PasswordFlag),
|
||||
case protocol_name_approved(ProtoVersion, ProtoName) of
|
||||
true ->
|
||||
wrap(Header,
|
||||
#mqtt_packet_connect{
|
||||
proto_ver = ProtoVersion,
|
||||
proto_name = ProtoName,
|
||||
will_retain = bool(WillRetain),
|
||||
will_qos = WillQos,
|
||||
will_flag = bool(WillFlag),
|
||||
clean_sess = bool(CleanSession),
|
||||
keep_alive = KeepAlive,
|
||||
client_id = ClientId,
|
||||
will_topic = WillTopic,
|
||||
will_msg = WillMsg,
|
||||
username = UserName,
|
||||
password = PasssWord}, Rest);
|
||||
false ->
|
||||
{error, protocol_header_corrupt}
|
||||
end;
|
||||
{?PUBLISH, <<FrameBin:Length/binary, Rest/binary>>} ->
|
||||
{TopicName, Rest1} = parse_utf(FrameBin),
|
||||
{PacketId, Payload} = case Qos of
|
||||
0 -> {undefined, Rest1};
|
||||
_ -> <<Id:16/big, R/binary>> = Rest1,
|
||||
{Id, R}
|
||||
end,
|
||||
wrap(Header, #mqtt_packet_publish {topic_name = TopicName,
|
||||
packet_id = PacketId },
|
||||
Payload, Rest);
|
||||
{?PUBACK, <<FrameBin:Length/binary, Rest/binary>>} ->
|
||||
<<PacketId:16/big>> = FrameBin,
|
||||
wrap(Header, #mqtt_packet_puback{packet_id = PacketId}, Rest);
|
||||
{?PUBREC, <<FrameBin:Length/binary, Rest/binary>>} ->
|
||||
<<PacketId:16/big>> = FrameBin,
|
||||
wrap(Header, #mqtt_packet_puback{packet_id = PacketId}, Rest);
|
||||
{?PUBREL, <<FrameBin:Length/binary, Rest/binary>>} ->
|
||||
1 = Qos,
|
||||
<<PacketId:16/big>> = FrameBin,
|
||||
wrap(Header, #mqtt_packet_puback{ packet_id = PacketId }, Rest);
|
||||
{?PUBCOMP, <<FrameBin:Length/binary, Rest/binary>>} ->
|
||||
<<PacketId:16/big>> = FrameBin,
|
||||
wrap(Header, #mqtt_packet_puback{ packet_id = PacketId }, Rest);
|
||||
{Subs, <<FrameBin:Length/binary, Rest/binary>>}
|
||||
when Subs =:= ?SUBSCRIBE orelse Subs =:= ?UNSUBSCRIBE ->
|
||||
1 = Qos,
|
||||
<<PacketId:16/big, Rest1/binary>> = FrameBin,
|
||||
Topics = parse_topics(Subs, Rest1, []),
|
||||
wrap(Header, #mqtt_packet_subscribe { packet_id = PacketId,
|
||||
topic_table = Topics }, Rest);
|
||||
{Minimal, Rest}
|
||||
when Minimal =:= ?DISCONNECT orelse Minimal =:= ?PINGREQ ->
|
||||
Length = 0,
|
||||
wrap(Header, Rest);
|
||||
{_, TooShortBin} ->
|
||||
{more, fun(BinMore) ->
|
||||
parse_frame(<<TooShortBin/binary, BinMore/binary>>,
|
||||
Header, Length)
|
||||
end}
|
||||
end.
|
||||
|
||||
parse_topics(_, <<>>, Topics) ->
|
||||
Topics;
|
||||
parse_topics(?SUBSCRIBE = Sub, Bin, Topics) ->
|
||||
{Name, <<_:6, QoS:2, Rest/binary>>} = parse_utf(Bin),
|
||||
parse_topics(Sub, Rest, [#mqtt_topic { name = Name, qos = QoS } | Topics]);
|
||||
parse_topics(?UNSUBSCRIBE = Sub, Bin, Topics) ->
|
||||
{Name, <<Rest/binary>>} = parse_utf(Bin),
|
||||
parse_topics(Sub, Rest, [#mqtt_topic { name = Name } | Topics]).
|
||||
|
||||
wrap(Header, Variable, Payload, Rest) ->
|
||||
{ok, #mqtt_packet{ header = Header, variable = Variable, payload = Payload }, Rest}.
|
||||
wrap(Header, Variable, Rest) ->
|
||||
{ok, #mqtt_packet { header = Header, variable = Variable }, Rest}.
|
||||
wrap(Header, Rest) ->
|
||||
{ok, #mqtt_packet { header = Header }, Rest}.
|
||||
|
||||
parse_utf(Bin, 0) ->
|
||||
{undefined, Bin};
|
||||
parse_utf(Bin, _) ->
|
||||
parse_utf(Bin).
|
||||
|
||||
parse_utf(<<Len:16/big, Str:Len/binary, Rest/binary>>) ->
|
||||
{Str, Rest}.
|
||||
|
||||
parse_msg(Bin, 0) ->
|
||||
{undefined, Bin};
|
||||
parse_msg(<<Len:16/big, Msg:Len/binary, Rest/binary>>, _) ->
|
||||
{Msg, Rest}.
|
||||
|
||||
bool(0) -> false;
|
||||
bool(1) -> true.
|
||||
|
||||
%% serialisation
|
||||
|
||||
serialise(#mqtt_packet{ header = Header,
|
||||
variable = Variable,
|
||||
payload = Payload }) ->
|
||||
serialise_header(Header,
|
||||
serialise_variable(Header, Variable,
|
||||
serialise_payload(Payload))).
|
||||
|
||||
serialise_payload(undefined) -> <<>>;
|
||||
serialise_payload(B) when is_binary(B) -> B.
|
||||
|
||||
serialise_variable(#mqtt_packet_header { type = ?CONNACK },
|
||||
#mqtt_packet_connack { ack_flags = AckFlags,
|
||||
return_code = ReturnCode },
|
||||
<<>> = PayloadBin) ->
|
||||
VariableBin = <<AckFlags:8, ReturnCode:8>>,
|
||||
{VariableBin, PayloadBin};
|
||||
|
||||
serialise_variable(#mqtt_packet_header { type = SubAck },
|
||||
#mqtt_packet_suback { packet_id = PacketId,
|
||||
qos_table = Qos },
|
||||
<<>> = _PayloadBin)
|
||||
when SubAck =:= ?SUBACK orelse SubAck =:= ?UNSUBACK ->
|
||||
VariableBin = <<PacketId:16/big>>,
|
||||
QosBin = << <<Q:8>> || Q <- Qos >>,
|
||||
{VariableBin, QosBin};
|
||||
|
||||
serialise_variable(#mqtt_packet_header { type = ?PUBLISH,
|
||||
qos = Qos },
|
||||
#mqtt_packet_publish { topic_name = TopicName,
|
||||
packet_id = PacketId },
|
||||
PayloadBin) ->
|
||||
TopicBin = serialise_utf(TopicName),
|
||||
PacketIdBin = case Qos of
|
||||
0 -> <<>>;
|
||||
1 -> <<PacketId:16/big>>;
|
||||
2 -> <<PacketId:16/big>>
|
||||
end,
|
||||
{<<TopicBin/binary, PacketIdBin/binary>>, PayloadBin};
|
||||
|
||||
serialise_variable(#mqtt_packet_header { type = PubAck },
|
||||
#mqtt_packet_puback { packet_id = PacketId },
|
||||
<<>> = _Payload)
|
||||
when PubAck =:= ?PUBACK; PubAck =:= ?PUBREC;
|
||||
PubAck =:= ?PUBREL; PubAck =:= ?PUBCOMP ->
|
||||
{<<PacketId:16/big>>, <<>>};
|
||||
|
||||
serialise_variable(#mqtt_packet_header { },
|
||||
undefined,
|
||||
<<>> = _PayloadBin) ->
|
||||
{<<>>, <<>>}.
|
||||
|
||||
serialise_header(#mqtt_packet_header{ type = Type,
|
||||
dup = Dup,
|
||||
qos = Qos,
|
||||
retain = Retain },
|
||||
{VariableBin, PayloadBin})
|
||||
when is_integer(Type) andalso ?CONNECT =< Type andalso Type =< ?DISCONNECT ->
|
||||
Len = size(VariableBin) + size(PayloadBin),
|
||||
true = (Len =< ?MAX_LEN),
|
||||
LenBin = serialise_len(Len),
|
||||
<<Type:4, (opt(Dup)):1, (opt(Qos)):2, (opt(Retain)):1,
|
||||
LenBin/binary, VariableBin/binary, PayloadBin/binary>>.
|
||||
|
||||
serialise_utf(String) ->
|
||||
StringBin = unicode:characters_to_binary(String),
|
||||
Len = size(StringBin),
|
||||
true = (Len =< 16#ffff),
|
||||
<<Len:16/big, StringBin/binary>>.
|
||||
|
||||
serialise_len(N) when N =< ?LOWBITS ->
|
||||
<<0:1, N:7>>;
|
||||
serialise_len(N) ->
|
||||
<<1:1, (N rem ?HIGHBIT):7, (serialise_len(N div ?HIGHBIT))/binary>>.
|
||||
|
||||
opt(undefined) -> ?RESERVED;
|
||||
opt(false) -> 0;
|
||||
opt(true) -> 1;
|
||||
opt(X) when is_integer(X) -> X.
|
||||
|
||||
protocol_name_approved(Ver, Name) ->
|
||||
lists:member({Ver, Name}, ?PROTOCOL_NAMES).
|
||||
|
||||
dump(#mqtt_packet{header = Header, variable = Variable, payload = Payload}) when
|
||||
Payload =:= undefined orelse Payload =:= <<>> ->
|
||||
dump_header(Header, dump_variable(Variable));
|
||||
|
||||
dump(#mqtt_packet{header = Header, variable = Variable, payload = Payload}) ->
|
||||
dump_header(Header, dump_variable(Variable, Payload)).
|
||||
|
||||
dump_header(#mqtt_packet_header{type = Type, dup = Dup, qos = QoS, retain = Retain}, S) ->
|
||||
S1 =
|
||||
if
|
||||
S == undefined -> <<>>;
|
||||
true -> [", ", S]
|
||||
end,
|
||||
io_lib:format("~s(Qos=~p, Retain=~s, Dup=~s~s)", [dump_type(Type), QoS, Retain, Dup, S1]).
|
||||
|
||||
dump_variable( #mqtt_packet_connect {
|
||||
proto_ver = ProtoVer,
|
||||
proto_name = ProtoName,
|
||||
will_retain = WillRetain,
|
||||
will_qos = WillQoS,
|
||||
will_flag = WillFlag,
|
||||
clean_sess = CleanSess,
|
||||
keep_alive = KeepAlive,
|
||||
client_id = ClientId,
|
||||
will_topic = WillTopic,
|
||||
will_msg = WillMsg,
|
||||
username = Username,
|
||||
password = Password} ) ->
|
||||
Format = "ClientId=~s, ProtoName=~s, ProtoVsn=~p, CleanSess=~s, KeepAlive=~p, Username=~s, Password=~s",
|
||||
Args = [ClientId, ProtoName, ProtoVer, CleanSess, KeepAlive, Username, dump_password(Password)],
|
||||
{Format1, Args1} = if
|
||||
WillFlag -> { Format ++ ", Will(Qos=~p, Retain=~s, Topic=~s, Msg=~s)",
|
||||
Args ++ [ WillQoS, WillRetain, WillTopic, WillMsg ] };
|
||||
true -> {Format, Args}
|
||||
end,
|
||||
io_lib:format(Format1, Args1);
|
||||
|
||||
dump_variable( #mqtt_packet_connack {
|
||||
ack_flags = AckFlags,
|
||||
return_code = ReturnCode } ) ->
|
||||
io_lib:format("AckFlags=~p, RetainCode=~p", [AckFlags, ReturnCode]);
|
||||
|
||||
dump_variable( #mqtt_packet_publish {
|
||||
topic_name = TopicName,
|
||||
packet_id = PacketId} ) ->
|
||||
io_lib:format("TopicName=~s, PacketId=~p", [TopicName, PacketId]);
|
||||
|
||||
dump_variable( #mqtt_packet_puback {
|
||||
packet_id = PacketId } ) ->
|
||||
io_lib:format("PacketId=~p", [PacketId]);
|
||||
|
||||
dump_variable( #mqtt_packet_subscribe {
|
||||
packet_id = PacketId,
|
||||
topic_table = TopicTable }) ->
|
||||
L = [{Name, QoS} || #mqtt_topic{name = Name, qos = QoS} <- TopicTable],
|
||||
io_lib:format("PacketId=~p, TopicTable=~p", [PacketId, L]);
|
||||
|
||||
dump_variable( #mqtt_packet_suback {
|
||||
packet_id = PacketId,
|
||||
qos_table = QosTable} ) ->
|
||||
io_lib:format("PacketId=~p, QosTable=~p", [PacketId, QosTable]);
|
||||
|
||||
dump_variable(PacketId) when is_integer(PacketId) ->
|
||||
io_lib:format("PacketId=~p", [PacketId]);
|
||||
|
||||
dump_variable(undefined) -> undefined.
|
||||
|
||||
dump_variable(undefined, undefined) ->
|
||||
undefined;
|
||||
dump_variable(undefined, <<>>) ->
|
||||
undefined;
|
||||
dump_variable(Variable, Payload) ->
|
||||
io_lib:format("~s, Payload=~p", [dump_variable(Variable), Payload]).
|
||||
|
||||
dump_password(undefined) -> undefined;
|
||||
dump_password(_) -> <<"******">>.
|
||||
|
||||
dump_type(?CONNECT) -> "CONNECT";
|
||||
dump_type(?CONNACK) -> "CONNACK";
|
||||
dump_type(?PUBLISH) -> "PUBLISH";
|
||||
dump_type(?PUBACK) -> "PUBACK";
|
||||
dump_type(?PUBREC) -> "PUBREC";
|
||||
dump_type(?PUBREL) -> "PUBREL";
|
||||
dump_type(?PUBCOMP) -> "PUBCOMP";
|
||||
dump_type(?SUBSCRIBE) -> "SUBSCRIBE";
|
||||
dump_type(?SUBACK) -> "SUBACK";
|
||||
dump_type(?UNSUBSCRIBE) -> "UNSUBSCRIBE";
|
||||
dump_type(?UNSUBACK) -> "UNSUBACK";
|
||||
dump_type(?PINGREQ) -> "PINGREQ";
|
||||
dump_type(?PINGRESP) -> "PINGRESP";
|
||||
dump_type(?DISCONNECT) -> "DISCONNECT".
|
||||
|
|
@ -1,5 +1,356 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
-module(emqtt_protocol).
|
||||
|
||||
-include("emqtt_frame.hrl").
|
||||
-include("emqtt.hrl").
|
||||
|
||||
-include("emqtt_packet.hrl").
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% API Function Exports
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
-export([initial_state/2, client_id/1]).
|
||||
|
||||
-export([handle_packet/2, send_message/2, send_packet/2, redeliver/2, shutdown/2]).
|
||||
|
||||
-export([info/1]).
|
||||
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% Protocol State
|
||||
%% ------------------------------------------------------------------
|
||||
-record(proto_state, {
|
||||
socket,
|
||||
peer_name,
|
||||
connected = false, %received CONNECT action?
|
||||
proto_vsn,
|
||||
proto_name,
|
||||
%packet_id,
|
||||
client_id,
|
||||
clean_sess,
|
||||
session, %% session state or session pid
|
||||
will_msg
|
||||
}).
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
-ifdef(use_specs).
|
||||
|
||||
-type(proto_state() :: #proto_state{}).
|
||||
|
||||
-spec(send_message({pid() | tuple(), mqtt_message()}, proto_state()) -> {ok, proto_state()}).
|
||||
|
||||
-spec(handle_packet(mqtt_packet(), proto_state()) -> {ok, proto_state()} | {error, any()}).
|
||||
|
||||
-endif.
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
-define(PACKET_TYPE(Packet, Type),
|
||||
Packet = #mqtt_packet { header = #mqtt_packet_header { type = Type }}).
|
||||
|
||||
-define(PUBACK_PACKET(PacketId), #mqtt_packet_puback { packet_id = PacketId }).
|
||||
|
||||
initial_state(Socket, Peername) ->
|
||||
#proto_state{
|
||||
socket = Socket,
|
||||
peer_name = Peername
|
||||
}.
|
||||
|
||||
client_id(#proto_state { client_id = ClientId }) -> ClientId.
|
||||
|
||||
%%SHOULD be registered in emqtt_cm
|
||||
info(#proto_state{ proto_vsn = ProtoVsn,
|
||||
proto_name = ProtoName,
|
||||
client_id = ClientId,
|
||||
clean_sess = CleanSess,
|
||||
will_msg = WillMsg }) ->
|
||||
[ {proto_vsn, ProtoVsn},
|
||||
{proto_name, ProtoName},
|
||||
{client_id, ClientId},
|
||||
{clean_sess, CleanSess},
|
||||
{will_msg, WillMsg} ].
|
||||
|
||||
|
||||
%%CONNECT – Client requests a connection to a Server
|
||||
|
||||
%%A Client can only send the CONNECT Packet once over a Network Connection.
|
||||
handle_packet(?PACKET_TYPE(Packet, ?CONNECT), State = #proto_state{connected = false}) ->
|
||||
handle_packet(?CONNECT, Packet, State#proto_state{connected = true});
|
||||
|
||||
handle_packet(?PACKET_TYPE(_Packet, ?CONNECT), State = #proto_state{connected = true}) ->
|
||||
{error, protocol_bad_connect, State};
|
||||
|
||||
%%Received other packets when CONNECT not arrived.
|
||||
handle_packet(_Packet, State = #proto_state{connected = false}) ->
|
||||
{error, protocol_not_connected, State};
|
||||
|
||||
handle_packet(?PACKET_TYPE(Packet, Type),
|
||||
State = #proto_state { peer_name = PeerName, client_id = ClientId }) ->
|
||||
lager:info("RECV from ~s@~s: ~s", [ClientId, PeerName, emqtt_packet:dump(Packet)]),
|
||||
case validate_packet(Packet) of
|
||||
ok ->
|
||||
handle_packet(Type, Packet, State);
|
||||
{error, Reason} ->
|
||||
{error, Reason, State}
|
||||
end.
|
||||
|
||||
handle_packet(?CONNECT, Packet = #mqtt_packet {
|
||||
variable = #mqtt_packet_connect {
|
||||
username = Username,
|
||||
password = Password,
|
||||
clean_sess = CleanSess,
|
||||
keep_alive = KeepAlive,
|
||||
client_id = ClientId } = Var },
|
||||
State = #proto_state{ peer_name = PeerName } ) ->
|
||||
lager:info("RECV from ~s@~s: ~s", [ClientId, PeerName, emqtt_packet:dump(Packet)]),
|
||||
{ReturnCode1, State1} =
|
||||
case validate_connect(Var) of
|
||||
?CONNACK_ACCEPT ->
|
||||
case emqtt_auth:check(Username, Password) of
|
||||
true ->
|
||||
ClientId1 = clientid(ClientId, State),
|
||||
start_keepalive(KeepAlive),
|
||||
emqtt_cm:register(ClientId1, self()),
|
||||
{?CONNACK_ACCEPT, State#proto_state{ will_msg = willmsg(Var),
|
||||
clean_sess = CleanSess,
|
||||
client_id = ClientId1 }};
|
||||
false ->
|
||||
lager:error("~s@~s: username '~s' login failed - no credentials", [ClientId, PeerName, Username]),
|
||||
{?CONNACK_CREDENTIALS, State#proto_state{client_id = ClientId}}
|
||||
end;
|
||||
ReturnCode ->
|
||||
{ReturnCode, State#proto_state{client_id = ClientId}}
|
||||
end,
|
||||
send_packet( #mqtt_packet {
|
||||
header = #mqtt_packet_header { type = ?CONNACK },
|
||||
variable = #mqtt_packet_connack{ return_code = ReturnCode1 }}, State1 ),
|
||||
%%Starting session
|
||||
{ok, Session} = emqtt_session:start({CleanSess, ClientId, self()}),
|
||||
{ok, State1#proto_state { session = Session }};
|
||||
|
||||
handle_packet(?PUBLISH, Packet = #mqtt_packet {
|
||||
header = #mqtt_packet_header {qos = ?QOS_0}},
|
||||
State = #proto_state{session = Session}) ->
|
||||
emqtt_session:publish(Session, {?QOS_0, emqtt_message:from_packet(Packet)}),
|
||||
{ok, State};
|
||||
|
||||
handle_packet(?PUBLISH, Packet = #mqtt_packet {
|
||||
header = #mqtt_packet_header { qos = ?QOS_1 },
|
||||
variable = #mqtt_packet_publish{packet_id = PacketId }},
|
||||
State = #proto_state { session = Session }) ->
|
||||
emqtt_session:publish(Session, {?QOS_1, emqtt_message:from_packet(Packet)}),
|
||||
send_packet( make_packet(?PUBACK, PacketId), State);
|
||||
|
||||
handle_packet(?PUBLISH, Packet = #mqtt_packet {
|
||||
header = #mqtt_packet_header { qos = ?QOS_2 },
|
||||
variable = #mqtt_packet_publish { packet_id = PacketId } },
|
||||
State = #proto_state { session = Session }) ->
|
||||
NewSession = emqtt_session:publish(Session, {?QOS_2, emqtt_message:from_packet(Packet)}),
|
||||
send_packet( make_packet(?PUBREC, PacketId), State#proto_state {session = NewSession} );
|
||||
|
||||
handle_packet(Puback, #mqtt_packet{variable = ?PUBACK_PACKET(PacketId) },
|
||||
State = #proto_state { session = Session })
|
||||
when Puback >= ?PUBACK andalso Puback =< ?PUBCOMP ->
|
||||
|
||||
NewSession = emqtt_session:puback(Session, {Puback, PacketId}),
|
||||
NewState = State#proto_state {session = NewSession},
|
||||
if
|
||||
Puback =:= ?PUBREC ->
|
||||
send_packet( make_packet(?PUBREL, PacketId), NewState);
|
||||
Puback =:= ?PUBREL ->
|
||||
send_packet( make_packet(?PUBCOMP, PacketId), NewState);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{ok, NewState};
|
||||
|
||||
handle_packet(?SUBSCRIBE, #mqtt_packet {
|
||||
variable = #mqtt_packet_subscribe{
|
||||
packet_id = PacketId,
|
||||
topic_table = TopicTable},
|
||||
payload = undefined},
|
||||
State = #proto_state { session = Session } ) ->
|
||||
|
||||
Topics = [{Name, Qos} || #mqtt_topic{name=Name, qos=Qos} <- TopicTable],
|
||||
{ok, NewSession, GrantedQos} = emqtt_session:subscribe(Session, Topics),
|
||||
send_packet(#mqtt_packet { header = #mqtt_packet_header { type = ?SUBACK },
|
||||
variable = #mqtt_packet_suback{ packet_id = PacketId,
|
||||
qos_table = GrantedQos }},
|
||||
State#proto_state{ session = NewSession });
|
||||
|
||||
handle_packet(?UNSUBSCRIBE, #mqtt_packet {
|
||||
variable = #mqtt_packet_subscribe{
|
||||
packet_id = PacketId,
|
||||
topic_table = Topics },
|
||||
payload = undefined},
|
||||
State = #proto_state{session = Session}) ->
|
||||
{ok, NewSession} = emqtt_session:unsubscribe(Session, [Name || #mqtt_topic{ name = Name } <- Topics]),
|
||||
send_packet(#mqtt_packet { header = #mqtt_packet_header {type = ?UNSUBACK },
|
||||
variable = #mqtt_packet_suback{packet_id = PacketId }},
|
||||
State#proto_state { session = NewSession } );
|
||||
|
||||
handle_packet(?PINGREQ, #mqtt_packet{}, State) ->
|
||||
send_packet(make_packet(?PINGRESP), State);
|
||||
|
||||
handle_packet(?DISCONNECT, #mqtt_packet{}, State) ->
|
||||
%%TODO: how to handle session?
|
||||
% clean willmsg
|
||||
{stop, normal, State#proto_state{will_msg = undefined}}.
|
||||
|
||||
make_packet(Type) when Type >= ?CONNECT andalso Type =< ?DISCONNECT ->
|
||||
#mqtt_packet{ header = #mqtt_packet_header { type = Type } }.
|
||||
|
||||
make_packet(PubAck, PacketId) when PubAck >= ?PUBACK andalso PubAck =< ?PUBCOMP ->
|
||||
#mqtt_packet { header = #mqtt_packet_header { type = PubAck, qos = puback_qos(PubAck) },
|
||||
variable = #mqtt_packet_puback { packet_id = PacketId}}.
|
||||
|
||||
puback_qos(?PUBACK) -> ?QOS_0;
|
||||
puback_qos(?PUBREC) -> ?QOS_0;
|
||||
puback_qos(?PUBREL) -> ?QOS_1;
|
||||
puback_qos(?PUBCOMP) -> ?QOS_0.
|
||||
|
||||
%% qos0 message
|
||||
send_message({_From, Message = #mqtt_message{ qos = ?QOS_0 }}, State) ->
|
||||
send_packet(emqtt_message:to_packet(Message), State);
|
||||
|
||||
%% message from session
|
||||
send_message({_From = SessPid, Message}, State = #proto_state{session = SessPid}) when is_pid(SessPid) ->
|
||||
send_packet(emqtt_message:to_packet(Message), State);
|
||||
|
||||
%% message(qos1, qos2) not from session
|
||||
send_message({_From, Message = #mqtt_message{ qos = Qos }}, State = #proto_state{ session = Session })
|
||||
when (Qos =:= ?QOS_1) orelse (Qos =:= ?QOS_2) ->
|
||||
{Message1, NewSession} = emqtt_session:store(Session, Message),
|
||||
send_packet(emqtt_message:to_packet(Message1), State#proto_state{session = NewSession}).
|
||||
|
||||
send_packet(Packet, State = #proto_state{socket = Sock, peer_name = PeerName, client_id = ClientId}) ->
|
||||
lager:info("SENT to ~s@~s: ~s", [ClientId, PeerName, emqtt_packet:dump(Packet)]),
|
||||
Data = emqtt_packet:serialise(Packet),
|
||||
lager:debug("SENT to ~s: ~p", [PeerName, Data]),
|
||||
%%FIXME Later...
|
||||
erlang:port_command(Sock, Data),
|
||||
{ok, State}.
|
||||
|
||||
%%
|
||||
%% @doc redeliver PUBREL PacketId
|
||||
%%
|
||||
redeliver({?PUBREL, PacketId}, State) ->
|
||||
send_packet( make_packet(?PUBREL, PacketId), State).
|
||||
|
||||
shutdown(Error, #proto_state{peer_name = PeerName, client_id = ClientId, will_msg = WillMsg}) ->
|
||||
send_willmsg(WillMsg),
|
||||
try_unregister(ClientId, self()),
|
||||
lager:info("Protocol ~s@~s Shutdown: ~p", [ClientId, PeerName, Error]),
|
||||
ok.
|
||||
|
||||
willmsg(Packet) when is_record(Packet, mqtt_packet_connect) ->
|
||||
emqtt_message:from_packet(Packet).
|
||||
|
||||
clientid(<<>>, #proto_state{peer_name = PeerName}) ->
|
||||
<<"eMQTT/", (base64:encode(PeerName))/binary>>;
|
||||
|
||||
clientid(ClientId, _State) -> ClientId.
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
send_willmsg(undefined) -> ignore;
|
||||
%%TODO:should call session...
|
||||
send_willmsg(WillMsg) -> emqtt_router:route(WillMsg).
|
||||
|
||||
start_keepalive(0) -> ignore;
|
||||
start_keepalive(Sec) when Sec > 0 ->
|
||||
self() ! {keepalive, start, round(Sec * 1.5)}.
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
%% Validate Packets
|
||||
%%----------------------------------------------------------------------------
|
||||
validate_connect( Connect = #mqtt_packet_connect{} ) ->
|
||||
case validate_protocol(Connect) of
|
||||
true ->
|
||||
case validate_clientid(Connect) of
|
||||
true ->
|
||||
?CONNACK_ACCEPT;
|
||||
false ->
|
||||
?CONNACK_INVALID_ID
|
||||
end;
|
||||
false ->
|
||||
?CONNACK_PROTO_VER
|
||||
end.
|
||||
|
||||
validate_protocol(#mqtt_packet_connect { proto_ver = Ver, proto_name = Name }) ->
|
||||
lists:member({Ver, Name}, ?PROTOCOL_NAMES).
|
||||
|
||||
validate_clientid(#mqtt_packet_connect { client_id = ClientId })
|
||||
when ( size(ClientId) >= 1 ) andalso ( size(ClientId) =< ?MAX_CLIENTID_LEN ) ->
|
||||
true;
|
||||
|
||||
%% MQTT3.1.1 allow null clientId.
|
||||
validate_clientid(#mqtt_packet_connect { proto_ver =?MQTT_PROTO_V311, client_id = ClientId })
|
||||
when size(ClientId) =:= 0 ->
|
||||
true;
|
||||
|
||||
validate_clientid(#mqtt_packet_connect { proto_ver = Ver, clean_sess = CleanSess, client_id = ClientId }) ->
|
||||
lager:warning("Invalid ClientId: ~s, ProtoVer: ~p, CleanSess: ~s", [ClientId, Ver, CleanSess]),
|
||||
false.
|
||||
|
||||
validate_packet(#mqtt_packet { header = #mqtt_packet_header { type = ?PUBLISH },
|
||||
variable = #mqtt_packet_publish{ topic_name = Topic }}) ->
|
||||
case emqtt_topic:validate({publish, Topic}) of
|
||||
true -> ok;
|
||||
false -> lager:warning("Error publish topic: ~p", [Topic]), {error, badtopic}
|
||||
end;
|
||||
|
||||
validate_packet(#mqtt_packet { header = #mqtt_packet_header { type = ?SUBSCRIBE },
|
||||
variable = #mqtt_packet_subscribe{topic_table = Topics }}) ->
|
||||
|
||||
validate_topics(subscribe, Topics);
|
||||
|
||||
validate_packet(#mqtt_packet{ header = #mqtt_packet_header { type = ?UNSUBSCRIBE },
|
||||
variable = #mqtt_packet_subscribe{ topic_table = Topics }}) ->
|
||||
|
||||
validate_topics(unsubscribe, Topics);
|
||||
|
||||
validate_packet(_Packet) ->
|
||||
ok.
|
||||
|
||||
validate_topics(Type, []) when Type =:= subscribe orelse Type =:= unsubscribe ->
|
||||
lager:error("Empty Topics!"),
|
||||
{error, empty_topics};
|
||||
|
||||
validate_topics(Type, Topics) when Type =:= subscribe orelse Type =:= unsubscribe ->
|
||||
ErrTopics = [Topic || #mqtt_topic{name=Topic, qos=Qos} <- Topics,
|
||||
not (emqtt_topic:validate({Type, Topic}) and validate_qos(Qos))],
|
||||
case ErrTopics of
|
||||
[] -> ok;
|
||||
_ -> lager:error("Error Topics: ~p", [ErrTopics]), {error, badtopic}
|
||||
end.
|
||||
|
||||
validate_qos(undefined) -> true;
|
||||
validate_qos(Qos) when Qos =< ?QOS_2 -> true;
|
||||
validate_qos(_) -> false.
|
||||
|
||||
try_unregister(undefined, _) -> ok;
|
||||
try_unregister(ClientId, _) -> emqtt_cm:unregister(ClientId, self()).
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -22,12 +22,10 @@
|
|||
|
||||
-module(emqtt_pubsub).
|
||||
|
||||
-author('feng@slimchat.io').
|
||||
-author('feng@emqtt.io').
|
||||
|
||||
-include("emqtt.hrl").
|
||||
|
||||
-include("emqtt_log.hrl").
|
||||
|
||||
-include("emqtt_topic.hrl").
|
||||
|
||||
-include_lib("stdlib/include/qlc.hrl").
|
||||
|
@ -62,6 +60,20 @@
|
|||
terminate/2,
|
||||
code_change/3]).
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
-ifdef(use_specs).
|
||||
|
||||
-spec topics() -> list(topic()).
|
||||
|
||||
-spec subscribe({binary(), mqtt_qos()} | list(), pid()) -> {ok, list(mqtt_qos())}.
|
||||
|
||||
-spec unsubscribe(binary() | list(binary()), pid()) -> ok.
|
||||
|
||||
-endif.
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
|
@ -77,32 +89,35 @@ start_link() ->
|
|||
%%
|
||||
%% @doc All topics
|
||||
%%
|
||||
-spec topics() -> list(topic()).
|
||||
topics() ->
|
||||
mnesia:dirty_all_keys(topic).
|
||||
|
||||
%%
|
||||
%% @doc Subscribe Topic
|
||||
%% @doc Subscribe Topic or Topics
|
||||
%%
|
||||
-spec subscribe({Topic :: binary(), Qos :: qos()}, SubPid :: pid()) -> any().
|
||||
subscribe({Topic, Qos}, SubPid) when is_binary(Topic) and is_pid(SubPid) ->
|
||||
gen_server:call(?SERVER, {subscribe, {Topic, Qos}, SubPid}).
|
||||
subscribe([{Topic, Qos}], SubPid);
|
||||
|
||||
subscribe(Topics, SubPid) when is_list(Topics) and is_pid(SubPid) ->
|
||||
gen_server:call(?SERVER, {subscribe, Topics, SubPid}).
|
||||
|
||||
%%
|
||||
%% @doc Unsubscribe Topic
|
||||
%% @doc Unsubscribe Topic or Topics
|
||||
%%
|
||||
-spec unsubscribe(Topic :: binary(), SubPid :: pid()) -> any().
|
||||
unsubscribe(Topic, SubPid) when is_binary(Topic) and is_pid(SubPid) ->
|
||||
gen_server:cast(?SERVER, {unsubscribe, Topic, SubPid}).
|
||||
unsubscribe([Topic], SubPid);
|
||||
|
||||
unsubscribe(Topics, SubPid) when is_list(Topics) and is_pid(SubPid) ->
|
||||
gen_server:cast(?SERVER, {unsubscribe, Topics, SubPid}).
|
||||
|
||||
%%
|
||||
%% @doc Publish to cluster node.
|
||||
%%
|
||||
-spec publish(Msg :: mqtt_msg()) -> ok.
|
||||
publish(Msg=#mqtt_msg{topic=Topic}) ->
|
||||
-spec publish(Msg :: mqtt_message()) -> ok.
|
||||
publish(Msg=#mqtt_message{topic=Topic}) ->
|
||||
publish(Topic, Msg).
|
||||
|
||||
-spec publish(Topic :: binary(), Msg :: mqtt_msg()) -> any().
|
||||
-spec publish(Topic :: binary(), Msg :: mqtt_message()) -> any().
|
||||
publish(Topic, Msg) when is_binary(Topic) ->
|
||||
lists:foreach(fun(#topic{name=Name, node=Node}) ->
|
||||
case Node =:= node() of
|
||||
|
@ -112,8 +127,14 @@ publish(Topic, Msg) when is_binary(Topic) ->
|
|||
end, match(Topic)).
|
||||
|
||||
%dispatch locally, should only be called by publish
|
||||
dispatch(Topic, Msg) when is_binary(Topic) ->
|
||||
[SubPid ! {dispatch, Msg} || #topic_subscriber{subpid=SubPid} <- ets:lookup(topic_subscriber, Topic)].
|
||||
dispatch(Topic, Msg = #mqtt_message{qos = Qos}) when is_binary(Topic) ->
|
||||
lists:foreach(fun(#topic_subscriber{qos = SubQos, subpid=SubPid}) ->
|
||||
Msg1 = if
|
||||
Qos > SubQos -> Msg#mqtt_message{qos = SubQos};
|
||||
true -> Msg
|
||||
end,
|
||||
SubPid ! {dispatch, {self(), Msg1}}
|
||||
end, ets:lookup(topic_subscriber, Topic)).
|
||||
|
||||
-spec match(Topic :: binary()) -> [topic()].
|
||||
match(Topic) when is_binary(Topic) ->
|
||||
|
@ -143,29 +164,23 @@ init([]) ->
|
|||
ets:new(topic_subscriber, [bag, named_table, {keypos, 2}]),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call({subscribe, {Topic, Qos}, SubPid}, _From, State) ->
|
||||
case mnesia:transaction(fun trie_add/1, [Topic]) of
|
||||
{atomic, _} ->
|
||||
case get({subscriber, SubPid}) of
|
||||
undefined ->
|
||||
MonRef = erlang:monitor(process, SubPid),
|
||||
put({subcriber, SubPid}, MonRef),
|
||||
put({submon, MonRef}, SubPid);
|
||||
_ ->
|
||||
already_monitored
|
||||
end,
|
||||
ets:insert(topic_subscriber, #topic_subscriber{topic=Topic, qos = Qos, subpid=SubPid}),
|
||||
{reply, ok, State};
|
||||
{aborted, Reason} ->
|
||||
{reply, {error, Reason}, State}
|
||||
end;
|
||||
handle_call({subscribe, Topics, SubPid}, _From, State) ->
|
||||
Result = [subscribe_topic({Topic, Qos}, SubPid) || {Topic, Qos} <- Topics],
|
||||
Reply =
|
||||
case [Err || Err = {error, _} <- Result] of
|
||||
[] -> {ok, [Qos || {ok, Qos} <- Result]};
|
||||
Errors -> hd(Errors)
|
||||
end,
|
||||
{reply, Reply, State};
|
||||
|
||||
handle_call(Req, _From, State) ->
|
||||
{stop, {badreq, Req}, State}.
|
||||
|
||||
handle_cast({unsubscribe, Topic, SubPid}, State) ->
|
||||
ets:match_delete(topic_subscriber, #topic_subscriber{topic=Topic, qos ='_', subpid=SubPid}),
|
||||
try_remove_topic(Topic),
|
||||
handle_cast({unsubscribe, Topics, SubPid}, State) ->
|
||||
lists:foreach(fun(Topic) ->
|
||||
ets:match_delete(topic_subscriber, #topic_subscriber{topic=Topic, qos ='_', subpid=SubPid}),
|
||||
try_remove_topic(Topic)
|
||||
end, Topics),
|
||||
{noreply, State};
|
||||
|
||||
handle_cast(Msg, State) ->
|
||||
|
@ -174,7 +189,7 @@ handle_cast(Msg, State) ->
|
|||
handle_info({'DOWN', Mon, _Type, _Object, _Info}, State) ->
|
||||
case get({submon, Mon}) of
|
||||
undefined ->
|
||||
?ERROR("unexpected 'DOWN': ~p", [Mon]);
|
||||
lager:error("unexpected 'DOWN': ~p", [Mon]);
|
||||
SubPid ->
|
||||
erase({submon, Mon}),
|
||||
erase({subscriber, SubPid}),
|
||||
|
@ -196,6 +211,42 @@ code_change(_OldVsn, State, _Extra) ->
|
|||
%% ------------------------------------------------------------------
|
||||
%% Internal Function Definitions
|
||||
%% ------------------------------------------------------------------
|
||||
subscribe_topic({Topic, Qos}, SubPid) ->
|
||||
case mnesia:transaction(fun trie_add/1, [Topic]) of
|
||||
{atomic, _} ->
|
||||
case get({subscriber, SubPid}) of
|
||||
undefined ->
|
||||
%%TODO: refactor later...
|
||||
MonRef = erlang:monitor(process, SubPid),
|
||||
put({subcriber, SubPid}, MonRef),
|
||||
put({submon, MonRef}, SubPid);
|
||||
_ ->
|
||||
already_monitored
|
||||
end,
|
||||
%% remove duplicated subscribers
|
||||
try_remove_subscriber({Topic, Qos}, SubPid),
|
||||
ets:insert(topic_subscriber, #topic_subscriber{topic=Topic, qos = Qos, subpid=SubPid}),
|
||||
%TODO: GrantedQos??
|
||||
{ok, Qos};
|
||||
{aborted, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
try_remove_subscriber({Topic, Qos}, SubPid) ->
|
||||
case ets:lookup(topic_subscriber, Topic) of
|
||||
[] ->
|
||||
not_found;
|
||||
Subs ->
|
||||
DupSubs = [Sub || Sub = #topic_subscriber{qos = OldQos, subpid = OldPid}
|
||||
<- Subs, Qos =/= OldQos, OldPid =:= SubPid],
|
||||
case DupSubs of
|
||||
[] -> ok;
|
||||
[DupSub] ->
|
||||
lager:warning("PubSub: remove duplicated subscriber ~p", [DupSub]),
|
||||
ets:delete(topic_subscriber, DupSub)
|
||||
end
|
||||
end.
|
||||
|
||||
try_remove_topic(Name) when is_binary(Name) ->
|
||||
case ets:member(topic_subscriber, Name) of
|
||||
false ->
|
||||
|
@ -218,7 +269,7 @@ trie_add(Topic) when is_binary(Topic) ->
|
|||
[TrieNode=#topic_trie_node{topic=undefined}] ->
|
||||
mnesia:write(TrieNode#topic_trie_node{topic=Topic});
|
||||
[#topic_trie_node{topic=Topic}] ->
|
||||
ignore;
|
||||
{atomic, already_exist};
|
||||
[] ->
|
||||
%add trie path
|
||||
[trie_add_path(Triple) || Triple <- emqtt_topic:triples(Topic)],
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
-module(emqtt_queue).
|
||||
|
||||
-include("emqtt.hrl").
|
||||
|
||||
-export([new/1, new/2, in/3, all/1, clear/1]).
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
-ifdef(use_specs).
|
||||
|
||||
-type(mqtt_queue() :: #mqtt_queue_wrapper{}).
|
||||
|
||||
-spec(new(non_neg_intger()) -> mqtt_queue()).
|
||||
|
||||
-spec(in(binary(), mqtt_message(), mqtt_queue()) -> mqtt_queue()).
|
||||
|
||||
-spec(all(mqtt_queue()) -> list()).
|
||||
|
||||
-spec(clear(mqtt_queue()) -> mqtt_queue()).
|
||||
|
||||
-endif.
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
-define(DEFAULT_MAX_LEN, 1000).
|
||||
|
||||
-record(mqtt_queue_wrapper, { queue = queue:new(), max_len = ?DEFAULT_MAX_LEN, store_qos0 = false }).
|
||||
|
||||
new(MaxLen) -> #mqtt_queue_wrapper{ max_len = MaxLen }.
|
||||
|
||||
new(MaxLen, StoreQos0) -> #mqtt_queue_wrapper{ max_len = MaxLen, store_qos0 = StoreQos0 }.
|
||||
|
||||
in(ClientId, Message = #mqtt_message{qos = Qos},
|
||||
Wrapper = #mqtt_queue_wrapper{ queue = Queue, max_len = MaxLen}) ->
|
||||
case queue:len(Queue) < MaxLen of
|
||||
true ->
|
||||
Wrapper#mqtt_queue_wrapper{ queue = queue:in(Message, Queue) };
|
||||
false -> % full
|
||||
if
|
||||
Qos =:= ?QOS_0 ->
|
||||
lager:warning("Queue ~s drop qos0 message: ~p", [ClientId, Message]),
|
||||
Wrapper;
|
||||
true ->
|
||||
{{value, Msg}, Queue1} = queue:drop(Queue),
|
||||
lager:warning("Queue ~s drop message: ~p", [ClientId, Msg]),
|
||||
Wrapper#mqtt_queue_wrapper{ queue = Queue1 }
|
||||
end
|
||||
end.
|
||||
|
||||
all(#mqtt_queue_wrapper { queue = Queue }) -> queue:to_list(Queue).
|
||||
|
||||
clear(Queue) -> Queue#mqtt_queue_wrapper{ queue = queue:new() }.
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
-module(emqtt_queue_sup).
|
||||
|
||||
-author('feng@emqtt.io').
|
||||
|
||||
-behavior(supervisor).
|
||||
|
||||
-export([start_link/0, start_queue/0, init/1]).
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||
|
||||
start_queue() ->
|
||||
supervisor:start_child(?MODULE, []).
|
||||
|
||||
init([]) ->
|
||||
{ok, {{simple_one_for_one, 0, 1},
|
||||
[{queue, {emqtt_queue, start_link, []},
|
||||
transient, 10000, worker, [emqtt_queue]}]}}.
|
||||
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
-module(emqtt_retained).
|
||||
|
||||
-author('feng@slimchat.io').
|
||||
|
||||
%%TODO: FIXME Later...
|
||||
|
||||
%%
|
||||
%% <<MQTT_V3.1_Protocol_Specific>>
|
||||
|
||||
%% RETAIN
|
||||
%% Position: byte 1, bit 0.
|
||||
|
||||
%% This flag is only used on PUBLISH messages. When a client sends a PUBLISH to a server, if the Retain flag is set (1), the server should hold on to the message after it has been delivered to the current subscribers.
|
||||
|
||||
%% When a new subscription is established on a topic, the last retained message on that topic should be sent to the subscriber with the Retain flag set. If there is no retained message, nothing is sent
|
||||
|
||||
%% This is useful where publishers send messages on a "report by exception" basis, where it might be some time between messages. This allows new subscribers to instantly receive data with the retained, or Last Known Good, value.
|
||||
|
||||
%% When a server sends a PUBLISH to a client as a result of a subscription that already existed when the original PUBLISH arrived, the Retain flag should not be set, regardless of the Retain flag of the original PUBLISH. This allows a client to distinguish messages that are being received because they were retained and those that are being received "live".
|
||||
|
||||
%% Retained messages should be kept over restarts of the server.
|
||||
|
||||
%% A server may delete a retained message if it receives a message with a zero-length payload and the Retain flag set on the same topic.
|
||||
|
||||
-include("emqtt.hrl").
|
||||
|
||||
-include("emqtt_log.hrl").
|
||||
|
||||
-export([start_link/0,
|
||||
lookup/1,
|
||||
insert/2,
|
||||
delete/1,
|
||||
send/2]).
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
-export([init/1,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
handle_info/2,
|
||||
terminate/2,
|
||||
code_change/3]).
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
lookup(Topic) ->
|
||||
ets:lookup(retained_msg, Topic).
|
||||
|
||||
insert(Topic, Msg) ->
|
||||
gen_server:cast(?MODULE, {insert, Topic, Msg}).
|
||||
|
||||
delete(Topic) ->
|
||||
gen_server:cast(?MODULE, {delete, Topic}).
|
||||
|
||||
send(Topic, Client) ->
|
||||
[Client ! {dispatch, Msg} ||{_, Msg} <- lookup(Topic)].
|
||||
|
||||
init([]) ->
|
||||
ets:new(retained_msg, [set, protected, named_table]),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call(Req, _From, State) ->
|
||||
{stop, {badreq,Req}, State}.
|
||||
|
||||
handle_cast({insert, Topic, Msg}, State) ->
|
||||
ets:insert(retained_msg, {Topic, Msg}),
|
||||
{noreply, State};
|
||||
|
||||
handle_cast({delete, Topic}, State) ->
|
||||
ets:delete(retained_msg, Topic),
|
||||
{noreply, State};
|
||||
|
||||
handle_cast(Msg, State) ->
|
||||
{stop, {badmsg, Msg}, State}.
|
||||
|
||||
handle_info(Info, State) ->
|
||||
{stop, {badinfo, Info}, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
%%route chain... statistics
|
||||
-module(emqtt_router).
|
||||
|
||||
-include("emqtt.hrl").
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% API Function Exports
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
-export([start_link/0]).
|
||||
|
||||
%%Router Chain-->
|
||||
%%--->In
|
||||
%%Out<---
|
||||
-export([route/1]).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% gen_server Function Exports
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
-ifdef(use_specs).
|
||||
|
||||
-spec(start_link/1 :: () -> {ok, pid()}).
|
||||
|
||||
-spec route(mqtt_message()) -> ok.
|
||||
|
||||
-endif.
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% API Function Definitions
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||
|
||||
route(Msg) ->
|
||||
lager:info("Route message: ~s", [emqtt_message:dump(Msg)]),
|
||||
% need to retain?
|
||||
emqtt_server:retain(Msg),
|
||||
% unset flag and pubsub
|
||||
emqtt_pubsub:publish( emqtt_message:unset_flag(Msg) ).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% gen_server Function Definitions
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
init(Args) ->
|
||||
{ok, Args, hibernate}.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, ok, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% Internal Function Definitions
|
||||
%% ------------------------------------------------------------------
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
-module(emqtt_server).
|
||||
|
||||
-author('feng@slimpp.io').
|
||||
|
||||
-include("emqtt.hrl").
|
||||
|
||||
-include("emqtt_topic.hrl").
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
-define(RETAINED_TAB, mqtt_retained).
|
||||
|
||||
-define(STORE_LIMIT, 100000).
|
||||
|
||||
-record(mqtt_retained, {topic, qos, payload}).
|
||||
|
||||
-record(state, {store_limit}).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% API Function Exports
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
%%TODO: subscribe
|
||||
-export([start_link/1, retain/1, subscribe/2]).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% gen_server Function Exports
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% API Function Definitions
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
start_link(RetainOpts) ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [RetainOpts], []).
|
||||
|
||||
retain(#mqtt_message{ retain = false }) -> ignore;
|
||||
|
||||
%% RETAIN flag set to 1 and payload containing zero bytes
|
||||
retain(#mqtt_message{ retain = true, topic = Topic, payload = <<>> }) ->
|
||||
mnesia:dirty_delete(?RETAINED_TAB, Topic);
|
||||
|
||||
retain(Msg = #mqtt_message{retain = true}) ->
|
||||
gen_server:cast(?SERVER, {retain, Msg}).
|
||||
|
||||
%%
|
||||
subscribe(Topics, CPid) when is_pid(CPid) ->
|
||||
lager:info("Retained Topics: ~p", [match(Topics)]),
|
||||
RetainedMsgs = lists:flatten([mnesia:dirty_read(?RETAINED_TAB, Topic) || Topic <- match(Topics)]),
|
||||
lager:info("Retained Messages: ~p", [RetainedMsgs]),
|
||||
lists:foreach(fun(Msg) ->
|
||||
CPid ! {dispatch, {self(), retained_msg(Msg)}}
|
||||
end, RetainedMsgs).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% gen_server Function Definitions
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
init([RetainOpts]) ->
|
||||
mnesia:create_table(mqtt_retained, [
|
||||
{type, ordered_set},
|
||||
{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, mqtt_retained)}]),
|
||||
mnesia:add_table_copy(mqtt_retained, node(), ram_copies),
|
||||
Limit = proplists:get_value(store_limit, RetainOpts, ?STORE_LIMIT),
|
||||
{ok, #state{store_limit = Limit}}.
|
||||
|
||||
handle_call(Req, _From, State) ->
|
||||
{stop, {badreq, Req}, State}.
|
||||
|
||||
handle_cast({retain, Msg = #mqtt_message{ qos = Qos,
|
||||
topic = Topic,
|
||||
payload = Payload }}, State = #state{store_limit = Limit}) ->
|
||||
case mnesia:table_info(?RETAINED_TAB, size) of
|
||||
Size when Size >= Limit ->
|
||||
lager:error("Server dropped message(retain) for table is full: ~p", [Msg]);
|
||||
_ ->
|
||||
lager:info("Server retained message: ~p", [Msg]),
|
||||
mnesia:dirty_write(#mqtt_retained{ topic = Topic,
|
||||
qos = Qos,
|
||||
payload = Payload })
|
||||
end,
|
||||
{noreply, State};
|
||||
|
||||
handle_cast(Msg, State) ->
|
||||
{stop, {badmsg, Msg}, State}.
|
||||
|
||||
handle_info(Info, State) ->
|
||||
{stop, {badinfo, Info}, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% Internal Function Definitions
|
||||
%% ------------------------------------------------------------------
|
||||
match(Topics) ->
|
||||
RetainedTopics = mnesia:dirty_all_keys(?RETAINED_TAB),
|
||||
lists:flatten([match(Topic, RetainedTopics) || Topic <- Topics]).
|
||||
|
||||
match(Topic, RetainedTopics) ->
|
||||
case emqtt_topic:type(#topic{name=Topic}) of
|
||||
direct -> %% FIXME
|
||||
[Topic];
|
||||
wildcard ->
|
||||
[ T || T <- RetainedTopics, emqtt_topic:match(T, Topic) ]
|
||||
end.
|
||||
|
||||
retained_msg(#mqtt_retained{topic = Topic, qos = Qos, payload = Payload}) ->
|
||||
#mqtt_message { qos = Qos, retain = true, topic = Topic, payload = Payload }.
|
||||
|
|
@ -0,0 +1,341 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
-module(emqtt_session).
|
||||
|
||||
-include("emqtt.hrl").
|
||||
|
||||
-include("emqtt_packet.hrl").
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% API Function Exports
|
||||
%% ------------------------------------------------------------------
|
||||
-export([start/1, resume/3, publish/2, puback/2, subscribe/2, unsubscribe/2, destroy/2]).
|
||||
|
||||
-export([store/2]).
|
||||
|
||||
%%start gen_server
|
||||
-export([start_link/3]).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% gen_server Function Exports
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-record(session_state, {
|
||||
client_id :: binary(),
|
||||
client_pid :: pid(),
|
||||
message_id = 1,
|
||||
submap :: map(),
|
||||
msg_queue, %% do not receive rel
|
||||
awaiting_ack :: map(),
|
||||
awaiting_rel :: map(),
|
||||
awaiting_comp :: map(),
|
||||
expires,
|
||||
expire_timer }).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% Start Session
|
||||
%% ------------------------------------------------------------------
|
||||
start({true = _CleanSess, ClientId, _ClientPid}) ->
|
||||
%%Destroy old session if CleanSess is true before.
|
||||
ok = emqtt_sm:destroy_session(ClientId),
|
||||
{ok, initial_state(ClientId)};
|
||||
|
||||
start({false = _CleanSess, ClientId, ClientPid}) ->
|
||||
{ok, SessPid} = emqtt_sm:start_session(ClientId, ClientPid),
|
||||
{ok, SessPid}.
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% Session API
|
||||
%% ------------------------------------------------------------------
|
||||
resume(SessState = #session_state{}, _ClientId, _ClientPid) ->
|
||||
SessState;
|
||||
resume(SessPid, ClientId, ClientPid) when is_pid(SessPid) ->
|
||||
gen_server:cast(SessPid, {resume, ClientId, ClientPid}),
|
||||
SessPid.
|
||||
|
||||
publish(Session, {?QOS_0, Message}) ->
|
||||
emqtt_router:route(Message), Session;
|
||||
|
||||
publish(Session, {?QOS_1, Message}) ->
|
||||
emqtt_router:route(Message), Session;
|
||||
|
||||
publish(SessState = #session_state{awaiting_rel = AwaitingRel},
|
||||
{?QOS_2, Message = #mqtt_message{ msgid = MsgId }}) ->
|
||||
%% store in awaiting_rel
|
||||
SessState#session_state{awaiting_rel = maps:put(MsgId, Message, AwaitingRel)};
|
||||
|
||||
publish(SessPid, {?QOS_2, Message}) when is_pid(SessPid) ->
|
||||
gen_server:cast(SessPid, {publish, ?QOS_2, Message}),
|
||||
SessPid.
|
||||
|
||||
%% PUBACK
|
||||
puback(SessState = #session_state{client_id = ClientId, awaiting_ack = Awaiting}, {?PUBACK, PacketId}) ->
|
||||
case maps:is_key(PacketId, Awaiting) of
|
||||
true -> ok;
|
||||
false -> lager:warning("Session ~s: PUBACK PacketId '~p' not found!", [ClientId, PacketId])
|
||||
end,
|
||||
SessState#session_state{awaiting_ack = maps:remove(PacketId, Awaiting)};
|
||||
puback(SessPid, {?PUBACK, PacketId}) when is_pid(SessPid) ->
|
||||
gen_server:cast(SessPid, {puback, PacketId}), SessPid;
|
||||
|
||||
%% PUBREC
|
||||
puback(SessState = #session_state{ client_id = ClientId,
|
||||
awaiting_ack = AwaitingAck,
|
||||
awaiting_comp = AwaitingComp }, {?PUBREC, PacketId}) ->
|
||||
case maps:is_key(PacketId, AwaitingAck) of
|
||||
true -> ok;
|
||||
false -> lager:warning("Session ~s: PUBREC PacketId '~p' not found!", [ClientId, PacketId])
|
||||
end,
|
||||
SessState#session_state{ awaiting_ack = maps:remove(PacketId, AwaitingAck),
|
||||
awaiting_comp = maps:put(PacketId, true, AwaitingComp) };
|
||||
|
||||
puback(SessPid, {?PUBREC, PacketId}) when is_pid(SessPid) ->
|
||||
gen_server:cast(SessPid, {pubrec, PacketId}), SessPid;
|
||||
|
||||
%% PUBREL
|
||||
puback(SessState = #session_state{client_id = ClientId, awaiting_rel = Awaiting}, {?PUBREL, PacketId}) ->
|
||||
case maps:find(PacketId, Awaiting) of
|
||||
{ok, Msg} -> emqtt_router:route(Msg);
|
||||
error -> lager:warning("Session ~s: PUBREL PacketId '~p' not found!", [ClientId, PacketId])
|
||||
end,
|
||||
SessState#session_state{awaiting_rel = maps:remove(PacketId, Awaiting)};
|
||||
|
||||
puback(SessPid, {?PUBREL, PacketId}) when is_pid(SessPid) ->
|
||||
gen_server:cast(SessPid, {pubrel, PacketId}), SessPid;
|
||||
|
||||
%% PUBCOMP
|
||||
puback(SessState = #session_state{ client_id = ClientId,
|
||||
awaiting_comp = AwaitingComp}, {?PUBCOMP, PacketId}) ->
|
||||
case maps:is_key(PacketId, AwaitingComp) of
|
||||
true -> ok;
|
||||
false -> lager:warning("Session ~s: PUBREC PacketId '~p' not exist", [ClientId, PacketId])
|
||||
end,
|
||||
SessState#session_state{ awaiting_comp = maps:remove(PacketId, AwaitingComp) };
|
||||
|
||||
puback(SessPid, {?PUBCOMP, PacketId}) when is_pid(SessPid) ->
|
||||
gen_server:cast(SessPid, {pubcomp, PacketId}), SessPid.
|
||||
|
||||
%% SUBSCRIBE
|
||||
subscribe(SessState = #session_state{client_id = ClientId, submap = SubMap}, Topics) ->
|
||||
Resubs = [Topic || {Name, _Qos} = Topic <- Topics, maps:is_key(Name, SubMap)],
|
||||
case Resubs of
|
||||
[] -> ok;
|
||||
_ -> lager:warning("~s resubscribe ~p", [ClientId, Resubs])
|
||||
end,
|
||||
SubMap1 = lists:foldl(fun({Name, Qos}, Acc) -> maps:put(Name, Qos, Acc) end, SubMap, Topics),
|
||||
{ok, GrantedQos} = emqtt_pubsub:subscribe(Topics, self()),
|
||||
%%TODO: should be gen_event and notification...
|
||||
emqtt_server:subscribe([ Name || {Name, _} <- Topics ], self()),
|
||||
{ok, SessState#session_state{submap = SubMap1}, GrantedQos};
|
||||
|
||||
subscribe(SessPid, Topics) when is_pid(SessPid) ->
|
||||
{ok, GrantedQos} = gen_server:call(SessPid, {subscribe, Topics}),
|
||||
{ok, SessPid, GrantedQos}.
|
||||
|
||||
%%
|
||||
%% @doc UNSUBSCRIBE
|
||||
%%
|
||||
unsubscribe(SessState = #session_state{client_id = ClientId, submap = SubMap}, Topics) ->
|
||||
%%TODO: refactor later.
|
||||
case Topics -- maps:keys(SubMap) of
|
||||
[] -> ok;
|
||||
BadUnsubs -> lager:warning("~s should not unsubscribe ~p", [ClientId, BadUnsubs])
|
||||
end,
|
||||
%%unsubscribe from topic tree
|
||||
ok = emqtt_pubsub:unsubscribe(Topics, self()),
|
||||
SubMap1 = lists:foldl(fun(Topic, Acc) -> maps:remove(Topic, Acc) end, SubMap, Topics),
|
||||
{ok, SessState#session_state{submap = SubMap1}};
|
||||
|
||||
unsubscribe(SessPid, Topics) when is_pid(SessPid) ->
|
||||
gen_server:call(SessPid, {unsubscribe, Topics}),
|
||||
{ok, SessPid}.
|
||||
|
||||
destroy(SessPid, ClientId) when is_pid(SessPid) ->
|
||||
gen_server:cast(SessPid, {destroy, ClientId}).
|
||||
|
||||
%store message(qos1) that sent to client
|
||||
store(SessState = #session_state{ message_id = MsgId, awaiting_ack = Awaiting},
|
||||
Message = #mqtt_message{ qos = Qos }) when (Qos =:= ?QOS_1) orelse (Qos =:= ?QOS_2) ->
|
||||
%%assign msgid before send
|
||||
Message1 = Message#mqtt_message{ msgid = MsgId },
|
||||
Message2 =
|
||||
if
|
||||
Qos =:= ?QOS_2 -> Message1#mqtt_message{dup = false};
|
||||
true -> Message1
|
||||
end,
|
||||
Awaiting1 = maps:put(MsgId, Message2, Awaiting),
|
||||
{Message1, next_msg_id(SessState#session_state{ awaiting_ack = Awaiting1 })}.
|
||||
|
||||
initial_state(ClientId) ->
|
||||
#session_state { client_id = ClientId,
|
||||
submap = #{},
|
||||
awaiting_ack = #{},
|
||||
awaiting_rel = #{},
|
||||
awaiting_comp = #{} }.
|
||||
|
||||
initial_state(ClientId, ClientPid) ->
|
||||
State = initial_state(ClientId),
|
||||
State#session_state{client_pid = ClientPid}.
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% gen_server Function Definitions
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
start_link(SessOpts, ClientId, ClientPid) ->
|
||||
gen_server:start_link(?MODULE, [SessOpts, ClientId, ClientPid], []).
|
||||
|
||||
init([SessOpts, ClientId, ClientPid]) ->
|
||||
process_flag(trap_exit, true),
|
||||
%%TODO: Is this OK?
|
||||
true = link(ClientPid),
|
||||
State = initial_state(ClientId, ClientPid),
|
||||
Expires = proplists:get_value(expires, SessOpts, 1) * 3600,
|
||||
MsgQueue = emqtt_queue:new( proplists:get_value(max_queue, SessOpts, 1000),
|
||||
proplists:get_value(store_qos0, SessOpts, false) ),
|
||||
{ok, State#session_state{ expires = Expires,
|
||||
msg_queue = MsgQueue }, hibernate}.
|
||||
|
||||
handle_call({subscribe, Topics}, _From, State) ->
|
||||
{ok, NewState, GrantedQos} = subscribe(State, Topics),
|
||||
{reply, {ok, GrantedQos}, NewState};
|
||||
|
||||
handle_call({unsubscribe, Topics}, _From, State) ->
|
||||
{ok, NewState} = unsubscribe(State, Topics),
|
||||
{reply, ok, NewState};
|
||||
|
||||
handle_call(Req, _From, State) ->
|
||||
{stop, {badreq, Req}, State}.
|
||||
|
||||
handle_cast({resume, ClientId, ClientPid}, State = #session_state {
|
||||
client_id = ClientId,
|
||||
client_pid = undefined,
|
||||
msg_queue = Queue,
|
||||
awaiting_ack = AwaitingAck,
|
||||
awaiting_comp = AwaitingComp,
|
||||
expire_timer = ETimer}) ->
|
||||
lager:info("Session ~s resumed by ~p", [ClientId, ClientPid]),
|
||||
%cancel timeout timer
|
||||
erlang:cancel_timer(ETimer),
|
||||
|
||||
%% redelivery PUBREL
|
||||
lists:foreach(fun(PacketId) ->
|
||||
ClientPid ! {redeliver, {?PUBREL, PacketId}}
|
||||
end, maps:keys(AwaitingComp)),
|
||||
|
||||
%% redelivery messages that awaiting PUBACK or PUBREC
|
||||
Dup = fun(Msg) -> Msg#mqtt_message{ dup = true } end,
|
||||
lists:foreach(fun(Msg) ->
|
||||
ClientPid ! {dispatch, {self(), Dup(Msg)}}
|
||||
end, maps:values(AwaitingAck)),
|
||||
|
||||
%% send offline messages
|
||||
lists:foreach(fun(Msg) ->
|
||||
ClientPid ! {dispatch, {self(), Msg}}
|
||||
end, emqtt_queue:all(Queue)),
|
||||
|
||||
NewState = State#session_state{ client_pid = ClientPid,
|
||||
msg_queue = emqtt_queue:clear(Queue),
|
||||
expire_timer = undefined},
|
||||
{noreply, NewState, hibernate};
|
||||
|
||||
handle_cast({publish, ?QOS_2, Message}, State) ->
|
||||
NewState = publish(State, {?QOS_2, Message}),
|
||||
{noreply, NewState};
|
||||
|
||||
handle_cast({puback, PacketId}, State) ->
|
||||
NewState = puback(State, {?PUBACK, PacketId}),
|
||||
{noreply, NewState};
|
||||
|
||||
handle_cast({pubrec, PacketId}, State) ->
|
||||
NewState = puback(State, {?PUBREC, PacketId}),
|
||||
{noreply, NewState};
|
||||
|
||||
handle_cast({pubrel, PacketId}, State) ->
|
||||
NewState = puback(State, {?PUBREL, PacketId}),
|
||||
{noreply, NewState};
|
||||
|
||||
handle_cast({pubcomp, PacketId}, State) ->
|
||||
NewState = puback(State, {?PUBCOMP, PacketId}),
|
||||
{noreply, NewState};
|
||||
|
||||
handle_cast({destroy, ClientId}, State = #session_state{client_id = ClientId}) ->
|
||||
lager:warning("Session ~s destroyed", [ClientId]),
|
||||
{stop, normal, State};
|
||||
|
||||
handle_cast(Msg, State) ->
|
||||
{stop, {badmsg, Msg}, State}.
|
||||
|
||||
handle_info({dispatch, {_From, Message}}, State) ->
|
||||
{noreply, dispatch(Message, State)};
|
||||
|
||||
handle_info({'EXIT', ClientPid, Reason}, State = #session_state{
|
||||
client_id = ClientId, client_pid = ClientPid, expires = Expires}) ->
|
||||
lager:warning("Session: client ~s@~p exited, caused by ~p", [ClientId, ClientPid, Reason]),
|
||||
Timer = erlang:send_after(Expires * 1000, self(), session_expired),
|
||||
{noreply, State#session_state{ client_pid = undefined, expire_timer = Timer}};
|
||||
|
||||
handle_info(session_expired, State = #session_state{client_id = ClientId}) ->
|
||||
lager:warning("Session ~s expired!", [ClientId]),
|
||||
{stop, {shutdown, expired}, State};
|
||||
|
||||
handle_info(Info, State) ->
|
||||
{stop, {badinfo, Info}, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% Internal Function Definitions
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
dispatch(Message, State = #session_state{ client_id = ClientId,
|
||||
client_pid = undefined }) ->
|
||||
queue(ClientId, Message, State);
|
||||
|
||||
dispatch(Message = #mqtt_message{ qos = ?QOS_0 }, State = #session_state{
|
||||
client_pid = ClientPid }) ->
|
||||
ClientPid ! {dispatch, {self(), Message}},
|
||||
State;
|
||||
|
||||
dispatch(Message = #mqtt_message{ qos = Qos }, State = #session_state{ client_pid = ClientPid })
|
||||
when (Qos =:= ?QOS_1) orelse (Qos =:= ?QOS_2) ->
|
||||
{Message1, NewState} = store(State, Message),
|
||||
ClientPid ! {dispatch, {self(), Message1}},
|
||||
NewState.
|
||||
|
||||
queue(ClientId, Message, State = #session_state{msg_queue = Queue}) ->
|
||||
State#session_state{msg_queue = emqtt_queue:in(ClientId, Message, Queue)}.
|
||||
|
||||
next_msg_id(State = #session_state{ message_id = 16#ffff }) ->
|
||||
State#session_state{ message_id = 1 };
|
||||
|
||||
next_msg_id(State = #session_state{ message_id = MsgId }) ->
|
||||
State#session_state{ message_id = MsgId + 1 }.
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -20,47 +20,38 @@
|
|||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
-module(emqtt_keep_alive).
|
||||
-module(emqtt_session_sup).
|
||||
|
||||
-author('feng@slimchat.io').
|
||||
-author('feng@emqtt.io').
|
||||
|
||||
-export([new/2,
|
||||
state/1,
|
||||
activate/1,
|
||||
reset/1,
|
||||
cancel/1]).
|
||||
-behavior(supervisor).
|
||||
|
||||
-record(keep_alive, {state, period, timer, msg}).
|
||||
-export([start_link/1, start_session/2]).
|
||||
|
||||
new(undefined, _) ->
|
||||
undefined;
|
||||
new(0, _) ->
|
||||
undefined;
|
||||
new(Period, TimeoutMsg) when is_integer(Period) ->
|
||||
Ref = erlang:send_after(Period, self(), TimeoutMsg),
|
||||
#keep_alive{state=idle, period=Period, timer=Ref, msg=TimeoutMsg}.
|
||||
-export([init/1]).
|
||||
|
||||
state(undefined) ->
|
||||
undefined;
|
||||
state(#keep_alive{state=State}) ->
|
||||
State.
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
activate(undefined) ->
|
||||
undefined;
|
||||
activate(KeepAlive) when is_record(KeepAlive, keep_alive) ->
|
||||
KeepAlive#keep_alive{state=active}.
|
||||
-ifdef(use_specs).
|
||||
|
||||
reset(undefined) ->
|
||||
undefined;
|
||||
reset(KeepAlive=#keep_alive{period=Period, timer=Timer, msg=Msg}) ->
|
||||
catch erlang:cancel_timer(Timer),
|
||||
Ref = erlang:send_after(Period, self(), Msg),
|
||||
KeepAlive#keep_alive{state=idle, timer = Ref}.
|
||||
-spec(start_link/1 :: (list(tuple())) -> {ok, pid()}).
|
||||
|
||||
-spec(start_session/2 :: (binary(), pid()) -> {ok, pid()}).
|
||||
|
||||
-endif.
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
start_link(SessOpts) ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, [SessOpts]).
|
||||
|
||||
start_session(ClientId, ClientPid) ->
|
||||
supervisor:start_child(?MODULE, [ClientId, ClientPid]).
|
||||
|
||||
init([SessOpts]) ->
|
||||
{ok, {{simple_one_for_one, 0, 1},
|
||||
[{session, {emqtt_session, start_link, [SessOpts]},
|
||||
transient, 10000, worker, [emqtt_session]}]}}.
|
||||
|
||||
cancel(undefined) ->
|
||||
undefined;
|
||||
cancel(KeepAlive=#keep_alive{timer=Timer}) ->
|
||||
catch erlang:cancel_timer(Timer),
|
||||
KeepAlive#keep_alive{timer=undefined}.
|
||||
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%%
|
||||
%% The Session state in the Server consists of:
|
||||
%% The existence of a Session, even if the rest of the Session state is empty.
|
||||
%% The 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).
|
||||
|
||||
-define(TABLE, emqtt_session).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% API Function Exports
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
-export([start_link/0]).
|
||||
|
||||
-export([lookup_session/1, start_session/2, destroy_session/1]).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% gen_server Function Exports
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
-ifdef(use_specs).
|
||||
|
||||
-spec(start_link/0 :: () -> {ok, pid()}).
|
||||
|
||||
-spec(lookup_session/1 :: (binary()) -> pid() | undefined).
|
||||
|
||||
-spec(start_session/2 :: (binary(), pid()) -> {ok, pid()} | {error, any()}).
|
||||
|
||||
-spec(destroy_session/1 :: (binary()) -> ok).
|
||||
|
||||
-endif.
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% API Function Definitions
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||
|
||||
lookup_session(ClientId) ->
|
||||
case ets:lookup(?TABLE, ClientId) of
|
||||
[{_, SessPid, _}] -> SessPid;
|
||||
[] -> undefined
|
||||
end.
|
||||
|
||||
start_session(ClientId, ClientPid) ->
|
||||
gen_server:call(?SERVER, {start_session, ClientId, ClientPid}).
|
||||
|
||||
destroy_session(ClientId) ->
|
||||
gen_server:call(?SERVER, {destroy_session, ClientId}).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% gen_server Function Definitions
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
init([]) ->
|
||||
process_flag(trap_exit, true),
|
||||
ets:new(?TABLE, [set, protected, named_table]),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call({start_session, ClientId, ClientPid}, _From, State) ->
|
||||
Reply =
|
||||
case ets:lookup(?TABLE, ClientId) of
|
||||
[{_, SessPid, _MRef}] ->
|
||||
emqtt_session:resume(SessPid, ClientId, ClientPid),
|
||||
{ok, SessPid};
|
||||
[] ->
|
||||
case emqtt_session_sup:start_session(ClientId, ClientPid) of
|
||||
{ok, SessPid} ->
|
||||
MRef = erlang:monitor(process, SessPid),
|
||||
ets:insert(?TABLE, {ClientId, SessPid, MRef}),
|
||||
{ok, SessPid};
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
end
|
||||
end,
|
||||
{reply, Reply, State};
|
||||
|
||||
handle_call({destroy_session, ClientId}, _From, State) ->
|
||||
case ets:lookup(?TABLE, ClientId) of
|
||||
[{_, SessPid, MRef}] ->
|
||||
erlang:demonitor(MRef),
|
||||
emqtt_session:destroy(SessPid, ClientId),
|
||||
ets:delete(?TABLE, ClientId);
|
||||
[] ->
|
||||
ignore
|
||||
end,
|
||||
{reply, ok, State};
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, ok, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({'DOWN', MRef, process, DownPid, _Reason}, State) ->
|
||||
ets:match_delete(emqtt_client, {{'_', DownPid, MRef}}),
|
||||
{noreply, State};
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -22,7 +22,7 @@
|
|||
|
||||
-module(emqtt_sup).
|
||||
|
||||
-author('feng@slimchat.io').
|
||||
-author('feng@emqtt.io').
|
||||
|
||||
-include("emqtt.hrl").
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
-module(emqtt_throttle).
|
||||
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -22,7 +22,7 @@
|
|||
|
||||
-module(emqtt_topic).
|
||||
|
||||
-author('feng@slimchat.io').
|
||||
-author('feng@emqtt.io').
|
||||
|
||||
-import(lists, [reverse/1]).
|
||||
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
-module(emqtt_packet_tests).
|
||||
|
||||
-include("emqtt_packet.hrl").
|
||||
|
||||
-import(emqtt_packet, [initial_state/0, parse/2, serialise/1]).
|
||||
|
||||
-ifdef(TEST).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
parse_connect_test() ->
|
||||
State = initial_state(),
|
||||
%% CONNECT(Qos=0, Retain=false, Dup=false, ClientId=mosqpub/10451-iMac.loca, ProtoName=MQIsdp, ProtoVsn=3, CleanSess=true, KeepAlive=60, Username=undefined, Password=undefined)
|
||||
V31ConnBin = <<16,37,0,6,77,81,73,115,100,112,3,2,0,60,0,23,109,111,115,113,112,117,98,47,49,48,52,53,49,45,105,77,97,99,46,108,111,99,97>>,
|
||||
?assertMatch({ok, #mqtt_packet{
|
||||
header = #mqtt_packet_header { type = ?CONNECT,
|
||||
dup = false,
|
||||
qos = 0,
|
||||
retain = false},
|
||||
variable = #mqtt_packet_connect { proto_ver = 3,
|
||||
proto_name = <<"MQIsdp">>,
|
||||
client_id = <<"mosqpub/10451-iMac.loca">>,
|
||||
clean_sess = true,
|
||||
keep_alive = 60 } }, <<>>}, parse(V31ConnBin, State)),
|
||||
%% CONNECT(Qos=0, Retain=false, Dup=false, ClientId=mosqpub/10451-iMac.loca, ProtoName=MQTT, ProtoVsn=4, CleanSess=true, KeepAlive=60, Username=undefined, Password=undefined)
|
||||
V311ConnBin = <<16,35,0,4,77,81,84,84,4,2,0,60,0,23,109,111,115,113,112,117,98,47,49,48,52,53,49,45,105,77,97,99,46,108,111,99,97>>,
|
||||
?assertMatch({ok, #mqtt_packet{
|
||||
header = #mqtt_packet_header { type = ?CONNECT,
|
||||
dup = false,
|
||||
qos = 0,
|
||||
retain = false},
|
||||
variable = #mqtt_packet_connect { proto_ver = 4,
|
||||
proto_name = <<"MQTT">>,
|
||||
client_id = <<"mosqpub/10451-iMac.loca">>,
|
||||
clean_sess = true,
|
||||
keep_alive = 60 } }, <<>>}, parse(V311ConnBin, State)),
|
||||
|
||||
%% CONNECT(Qos=0, Retain=false, Dup=false, ClientId="", ProtoName=MQTT, ProtoVsn=4, CleanSess=true, KeepAlive=60)
|
||||
V311ConnWithoutClientId = <<16,12,0,4,77,81,84,84,4,2,0,60,0,0>>,
|
||||
?assertMatch({ok, #mqtt_packet{
|
||||
header = #mqtt_packet_header { type = ?CONNECT,
|
||||
dup = false,
|
||||
qos = 0,
|
||||
retain = false},
|
||||
variable = #mqtt_packet_connect { proto_ver = 4,
|
||||
proto_name = <<"MQTT">>,
|
||||
client_id = <<>>,
|
||||
clean_sess = true,
|
||||
keep_alive = 60 } }, <<>>}, parse(V311ConnWithoutClientId, State)),
|
||||
%%CONNECT(Qos=0, Retain=false, Dup=false, ClientId=mosqpub/10452-iMac.loca, ProtoName=MQIsdp, ProtoVsn=3, CleanSess=true, KeepAlive=60, Username=test, Password=******, Will(Qos=1, Retain=false, Topic=/will, Msg=willmsg))
|
||||
ConnBinWithWill = <<16,67,0,6,77,81,73,115,100,112,3,206,0,60,0,23,109,111,115,113,112,117,98,47,49,48,52,53,50,45,105,77,97,99,46,108,111,99,97,0,5,47,119,105,108,108,0,7,119,105,108,108,109,115,103,0,4,116,101,115,116,0,6,112,117,98,108,105,99>>,
|
||||
?assertMatch({ok, #mqtt_packet{
|
||||
header = #mqtt_packet_header { type = ?CONNECT,
|
||||
dup = false,
|
||||
qos = 0,
|
||||
retain = false},
|
||||
variable = #mqtt_packet_connect { proto_ver = 3,
|
||||
proto_name = <<"MQIsdp">>,
|
||||
client_id = <<"mosqpub/10452-iMac.loca">>,
|
||||
clean_sess = true,
|
||||
keep_alive = 60,
|
||||
will_retain = false,
|
||||
will_qos = 1,
|
||||
will_flag = true,
|
||||
will_topic = <<"/will">>,
|
||||
will_msg = <<"willmsg">> ,
|
||||
username = <<"test">>,
|
||||
password = <<"public">> } },
|
||||
<<>> }, parse(ConnBinWithWill, State)),
|
||||
ok.
|
||||
|
||||
parse_publish_test() ->
|
||||
State = initial_state(),
|
||||
%%PUBLISH(Qos=1, Retain=false, Dup=false, TopicName=a/b/c, PacketId=1, Payload=<<"hahah">>)
|
||||
PubBin = <<50,14,0,5,97,47,98,47,99,0,1,104,97,104,97,104>>,
|
||||
?assertMatch({ok, #mqtt_packet {
|
||||
header = #mqtt_packet_header { type = ?PUBLISH,
|
||||
dup = false,
|
||||
qos = 1,
|
||||
retain = false},
|
||||
variable = #mqtt_packet_publish { topic_name = <<"a/b/c">>,
|
||||
packet_id = 1 },
|
||||
payload = <<"hahah">> }, <<>>}, parse(PubBin, State)),
|
||||
|
||||
%PUBLISH(Qos=0, Retain=false, Dup=false, TopicName=xxx/yyy, PacketId=undefined, Payload=<<"hello">>)
|
||||
%DISCONNECT(Qos=0, Retain=false, Dup=false)
|
||||
PubBin1 = <<48,14,0,7,120,120,120,47,121,121,121,104,101,108,108,111,224,0>>,
|
||||
?assertMatch({ok, #mqtt_packet {
|
||||
header = #mqtt_packet_header { type = ?PUBLISH,
|
||||
dup = false,
|
||||
qos = 0,
|
||||
retain = false},
|
||||
variable = #mqtt_packet_publish { topic_name = <<"xxx/yyy">>,
|
||||
packet_id = undefined },
|
||||
payload = <<"hello">> }, <<224,0>>}, parse(PubBin1, State)),
|
||||
?assertMatch({ok, #mqtt_packet{
|
||||
header = #mqtt_packet_header { type = ?DISCONNECT,
|
||||
dup = false,
|
||||
qos = 0,
|
||||
retain = false}
|
||||
}, <<>>}, parse(<<224, 0>>, State)).
|
||||
|
||||
parse_puback_test() ->
|
||||
%%PUBACK(Qos=0, Retain=false, Dup=false, PacketId=1)
|
||||
PubAckBin = <<64,2,0,1>>,
|
||||
?assertMatch({ok, #mqtt_packet {
|
||||
header = #mqtt_packet_header { type = ?PUBACK,
|
||||
dup = false,
|
||||
qos = 0,
|
||||
retain = false }
|
||||
}, <<>>}, parse(PubAckBin, initial_state())),
|
||||
ok.
|
||||
|
||||
parse_subscribe_test() ->
|
||||
ok.
|
||||
|
||||
parse_pingreq_test() ->
|
||||
ok.
|
||||
|
||||
parse_disconnect_test() ->
|
||||
%DISCONNECT(Qos=0, Retain=false, Dup=false)
|
||||
Bin = <<224, 0>>,
|
||||
?assertMatch({ok, #mqtt_packet {
|
||||
header = #mqtt_packet_header { type = ?DISCONNECT,
|
||||
dup = false,
|
||||
qos = 0,
|
||||
retain = false }
|
||||
}, <<>>}, parse(Bin, initial_state())).
|
||||
|
||||
serialise_connack_test() ->
|
||||
ConnAck = #mqtt_packet{ header = #mqtt_packet_header { type = ?CONNACK },
|
||||
variable = #mqtt_packet_connack { ack_flags = 0, return_code = 0 } },
|
||||
?assertEqual(<<32,2,0,0>>, emqtt_packet:serialise(ConnAck)).
|
||||
|
||||
serialise_puback_test() ->
|
||||
ok.
|
||||
|
||||
-endif.
|
||||
|
|
@ -1,12 +1,34 @@
|
|||
%%-----------------------------------------------------------------------------
|
||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
||||
%%
|
||||
%% Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
%% of this software and associated documentation files (the "Software"), to deal
|
||||
%% in the Software without restriction, including without limitation the rights
|
||||
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
%% copies of the Software, and to permit persons to whom the Software is
|
||||
%% furnished to do so, subject to the following conditions:
|
||||
%%
|
||||
%% The above copyright notice and this permission notice shall be included in all
|
||||
%% copies or substantial portions of the Software.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
%% SOFTWARE.
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
-module(emqtt_topic_tests).
|
||||
|
||||
-include("emqtt_internal.hrl").
|
||||
-include("emqtt_topic.hrl").
|
||||
|
||||
-import(emqtt_topic, [validate/1, type/1, match/2, triples/1, words/1]).
|
||||
|
||||
-ifdef(TEST).
|
||||
|
||||
-include_lib("enunit/include/enunit.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
validate_test() ->
|
||||
?assert( validate({subscribe, <<"a/b/c">>}) ),
|
||||
|
@ -17,8 +39,8 @@ validate_test() ->
|
|||
|
||||
type_test() ->
|
||||
?assertEqual(direct, type(#topic{name = <<"/a/b/cdkd">>})),
|
||||
?assertEqual(wildcard, type(#type{name = <<"/a/+/d">>})),
|
||||
?assertEqual(wildcard, type(#type{name = <<"/a/b/#">>})).
|
||||
?assertEqual(wildcard, type(#topic{name = <<"/a/+/d">>})),
|
||||
?assertEqual(wildcard, type(#topic{name = <<"/a/b/#">>})).
|
||||
|
||||
-endif.
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
durable queue data...
|
Binary file not shown.
|
@ -0,0 +1,25 @@
|
|||
|
||||
zookeeper
|
||||
|
|
||||
eMQTT1 eMQTT2 eMQTT3
|
||||
|
||||
|
||||
Bridge
|
||||
|
||||
|
||||
eMQTT1 --> eMQTT2
|
||||
|
||||
|
||||
Cluster
|
||||
|
||||
eMQTT1 <--> eMQTT2
|
||||
|
||||
|
||||
Cluster and Bridge
|
||||
|
||||
eMQTT1 eMQTT3
|
||||
---->
|
||||
eMQTT2 eMQTT4
|
||||
|
||||
Mnesia Cluster
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
# MQTT Protocol Guide
|
||||
|
||||
## Server or Broker
|
||||
|
||||
A program or device that acts as an intermediary between Clients which publish Application Messages and Clients which have made Subscriptions.
|
||||
|
||||
A Server Accepts Network Connections from Clients.
|
||||
Accepts Application Messages published by Clients.
|
||||
Processes Subscribe and Unsubscribe requests from Clients.
|
||||
Forwards Application Messages that match Client Subscriptions.
|
||||
|
||||
|
||||
Client ----> Broker(Server) ----> Client
|
||||
|
||||
Publisher ----> Broker -----> Subscriber
|
||||
|
||||
## Subscription and Session
|
||||
|
||||
### Subscription
|
||||
|
||||
A Subscription comprises a Topic Filter and a maximum QoS. A Subscription is associated with a single Session. A Session can contain more than one Subscription. Each Subscription within a session has a different Topic Filter.
|
||||
|
||||
### Session
|
||||
|
||||
A stateful interaction between a Client and a Server. Some Sessions last only as long as the Network
|
||||
|
||||
Connection, others can span multiple consecutive Network Connections between a Client and a Server.
|
||||
|
||||
## Topic Name and Filter
|
||||
|
||||
An expression contained in a Subscription, to indicate an interest in one or more topics. A Topic Filter can include wildcard characters.
|
||||
|
||||
|
||||
## Packet Identifier
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# PubSub
|
||||
|
||||
## Qos
|
||||
|
||||
PubQos | SubQos | In Message | Out Message
|
||||
-------|--------|------------|-------------
|
||||
0 | 0 | - | -
|
||||
0 | 1 | - | -
|
||||
0 | 2 | - | -
|
||||
1 | 0 | - | -
|
||||
1 | 1 | - | -
|
||||
1 | 2 | - | -
|
||||
2 | 0 | - | -
|
||||
2 | 1 | - | -
|
||||
2 | 2 | - | -
|
||||
|
||||
## Publish
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# Retained Message
|
||||
|
||||
## API
|
||||
|
||||
store(
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
|
||||
client state --> parse_state
|
||||
--> proto_state --> session_state
|
|
@ -0,0 +1,14 @@
|
|||
# eMQTT User Guide
|
||||
|
||||
## Introduction
|
||||
|
||||
## Installation
|
||||
|
||||
### Install Requirements
|
||||
|
||||
## Configuration
|
||||
|
||||
## Cluster
|
||||
|
||||
## Bridge
|
||||
|
|
@ -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
|
|
@ -14,7 +14,7 @@
|
|||
{error_logger_redirect, false},
|
||||
{crash_log, "log/emqtt_crash.log"},
|
||||
{handlers, [
|
||||
{lager_console_backend, info},
|
||||
{lager_console_backend, debug},
|
||||
{lager_file_backend, [
|
||||
{file, "log/emqtt_error.log"},
|
||||
{level, error},
|
||||
|
@ -33,12 +33,20 @@
|
|||
]},
|
||||
{emqtt, [
|
||||
{auth, {anonymous, []}}, %internal, anonymous
|
||||
{session, [
|
||||
{expires, 1},
|
||||
{max_queue, 1000},
|
||||
{store_qos0, false}
|
||||
]},
|
||||
{retain, [
|
||||
{store_limit, 100000}
|
||||
]},
|
||||
{listen, [
|
||||
{mqtt, 1883, [
|
||||
{max_conns, 1024},
|
||||
{acceptor_pool, 4}
|
||||
]},
|
||||
{http, 8883, [
|
||||
{http, 8083, [
|
||||
{max_conns, 512},
|
||||
{acceptor_pool, 1}
|
||||
]}
|
||||
|
|
|
@ -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
|
|
@ -97,27 +97,9 @@ case "$1" in
|
|||
$NODETOOL rpc emqtt_ctl status $@
|
||||
;;
|
||||
|
||||
cluster_info)
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "Usage: $SCRIPT cluster_info"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Make sure the local node IS running
|
||||
RES=`$NODETOOL ping`
|
||||
if [ "$RES" != "pong" ]; then
|
||||
echo "Node is not running!"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
|
||||
$NODETOOL rpc emqtt_ctl cluster_info $@
|
||||
;;
|
||||
|
||||
|
||||
cluster)
|
||||
if [ $# -ne 2 ]; then
|
||||
echo "Usage: $SCRIPT cluster <Node>"
|
||||
if [ $# -gt 2 ]; then
|
||||
echo "Usage: $SCRIPT cluster [<Node>]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
@ -132,9 +114,9 @@ case "$1" in
|
|||
$NODETOOL rpc emqtt_ctl cluster $@
|
||||
;;
|
||||
|
||||
add_user)
|
||||
useradd)
|
||||
if [ $# -ne 3 ]; then
|
||||
echo "Usage: $SCRIPT add_user <Username> <Password>"
|
||||
echo "Usage: $SCRIPT useradd <Username> <Password>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
@ -146,12 +128,12 @@ case "$1" in
|
|||
fi
|
||||
shift
|
||||
|
||||
$NODETOOL rpc emqtt_ctl add_user $@
|
||||
$NODETOOL rpc emqtt_ctl useradd $@
|
||||
;;
|
||||
|
||||
delete_user)
|
||||
userdel)
|
||||
if [ $# -ne 2 ]; then
|
||||
echo "Usage: $SCRIPT delete_user <Username>"
|
||||
echo "Usage: $SCRIPT userdel <Username>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
@ -163,16 +145,15 @@ case "$1" in
|
|||
fi
|
||||
shift
|
||||
|
||||
$NODETOOL rpc emqtt_ctl delete_user $@
|
||||
$NODETOOL rpc emqtt_ctl userdel $@
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Usage: $SCRIPT"
|
||||
echo " status #query emqtt status"
|
||||
echo " cluster_info #query cluster nodes"
|
||||
echo " cluster <Node> #cluster node"
|
||||
echo " add_user <Username> <Password> #add user"
|
||||
echo " delete_user <Username> #delete user"
|
||||
echo " status #query emqtt status"
|
||||
echo " cluster [<Node>] #query or cluster nodes"
|
||||
echo " useradd <Username> <Password> #add user"
|
||||
echo " userdel <Username> #delete user"
|
||||
exit 1
|
||||
;;
|
||||
|
||||
|
|
|
@ -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
|
||||
#
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue