Merge pull request #2947 from emqx/master

Auto-pull-request-by-2019-09-30
This commit is contained in:
tigercl 2019-09-30 10:35:28 +08:00 committed by GitHub
commit 4c7ab6a5d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1051 additions and 878 deletions

View File

@ -154,7 +154,7 @@
%% Banned %% Banned
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-type(banned_who() :: {client_id, binary()} -type(banned_who() :: {clientid, binary()}
| {username, binary()} | {username, binary()}
| {ip_address, inet:ip_address()}). | {ip_address, inet:ip_address()}).

View File

@ -219,7 +219,7 @@
will_retain = false, will_retain = false,
keepalive = 0, keepalive = 0,
properties = undefined, properties = undefined,
client_id = <<>>, clientid = <<>>,
will_props = undefined, will_props = undefined,
will_topic = undefined, will_topic = undefined,
will_payload = undefined, will_payload = undefined,

View File

@ -524,10 +524,10 @@ end}.
Formatter = {emqx_logger_formatter, Formatter = {emqx_logger_formatter,
#{template => #{template =>
[time," [",level,"] ", [time," [",level,"] ",
{client_id, {clientid,
[{peername, [{peername,
[client_id,"@",peername," "], [clientid,"@",peername," "],
[client_id, " "]}], [clientid, " "]}],
[{peername, [{peername,
[peername," "], [peername," "],
[]}]}, []}]},

View File

@ -29,7 +29,7 @@
[{test, [{test,
[{deps, [{deps,
[{bbmustache, "1.7.0"}, % hex [{bbmustache, "1.7.0"}, % hex
{emqtt, {git, "https://github.com/emqx/emqtt", {tag, "v1.0.1"}}}, {emqtt, {git, "https://github.com/emqx/emqtt", {branch, "develop"}}},
{emqx_ct_helpers, {git, "https://github.com/emqx/emqx-ct-helpers", {branch, "develop"}}} {emqx_ct_helpers, {git, "https://github.com/emqx/emqx-ct-helpers", {branch, "develop"}}}
]} ]}
]} ]}

View File

@ -28,7 +28,7 @@
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(authenticate(emqx_types:client()) -spec(authenticate(emqx_types:clientinfo())
-> {ok, #{auth_result := emqx_types:auth_result(), -> {ok, #{auth_result := emqx_types:auth_result(),
anonymous := boolean}} | {error, term()}). anonymous := boolean}} | {error, term()}).
authenticate(Client) -> authenticate(Client) ->

View File

@ -86,25 +86,25 @@ bin(B) when is_binary(B) ->
B. B.
%% @doc Match access rule %% @doc Match access rule
-spec(match(emqx_types:client(), emqx_types:topic(), rule()) -spec(match(emqx_types:clientinfo(), emqx_types:topic(), rule())
-> {matched, allow} | {matched, deny} | nomatch). -> {matched, allow} | {matched, deny} | nomatch).
match(_Client, _Topic, {AllowDeny, all}) when ?ALLOW_DENY(AllowDeny) -> match(_ClientInfo, _Topic, {AllowDeny, all}) when ?ALLOW_DENY(AllowDeny) ->
{matched, AllowDeny}; {matched, AllowDeny};
match(Client, Topic, {AllowDeny, Who, _PubSub, TopicFilters}) match(ClientInfo, Topic, {AllowDeny, Who, _PubSub, TopicFilters})
when ?ALLOW_DENY(AllowDeny) -> when ?ALLOW_DENY(AllowDeny) ->
case match_who(Client, Who) case match_who(ClientInfo, Who)
andalso match_topics(Client, Topic, TopicFilters) of andalso match_topics(ClientInfo, Topic, TopicFilters) of
true -> {matched, AllowDeny}; true -> {matched, AllowDeny};
false -> nomatch false -> nomatch
end. end.
match_who(_Client, all) -> match_who(_ClientInfo, all) ->
true; true;
match_who(_Client, {user, all}) -> match_who(_ClientInfo, {user, all}) ->
true; true;
match_who(_Client, {client, all}) -> match_who(_ClientInfo, {client, all}) ->
true; true;
match_who(#{client_id := ClientId}, {client, ClientId}) -> match_who(#{clientid := ClientId}, {client, ClientId}) ->
true; true;
match_who(#{username := Username}, {user, Username}) -> match_who(#{username := Username}, {user, Username}) ->
true; true;
@ -112,44 +112,44 @@ match_who(#{peerhost := undefined}, {ipaddr, _Tup}) ->
false; false;
match_who(#{peerhost := IP}, {ipaddr, CIDR}) -> match_who(#{peerhost := IP}, {ipaddr, CIDR}) ->
esockd_cidr:match(IP, CIDR); esockd_cidr:match(IP, CIDR);
match_who(Client, {'and', Conds}) when is_list(Conds) -> match_who(ClientInfo, {'and', Conds}) when is_list(Conds) ->
lists:foldl(fun(Who, Allow) -> lists:foldl(fun(Who, Allow) ->
match_who(Client, Who) andalso Allow match_who(ClientInfo, Who) andalso Allow
end, true, Conds); end, true, Conds);
match_who(Client, {'or', Conds}) when is_list(Conds) -> match_who(ClientInfo, {'or', Conds}) when is_list(Conds) ->
lists:foldl(fun(Who, Allow) -> lists:foldl(fun(Who, Allow) ->
match_who(Client, Who) orelse Allow match_who(ClientInfo, Who) orelse Allow
end, false, Conds); end, false, Conds);
match_who(_Client, _Who) -> match_who(_ClientInfo, _Who) ->
false. false.
match_topics(_Client, _Topic, []) -> match_topics(_ClientInfo, _Topic, []) ->
false; false;
match_topics(Client, Topic, [{pattern, PatternFilter}|Filters]) -> match_topics(ClientInfo, Topic, [{pattern, PatternFilter}|Filters]) ->
TopicFilter = feed_var(Client, PatternFilter), TopicFilter = feed_var(ClientInfo, PatternFilter),
match_topic(emqx_topic:words(Topic), TopicFilter) match_topic(emqx_topic:words(Topic), TopicFilter)
orelse match_topics(Client, Topic, Filters); orelse match_topics(ClientInfo, Topic, Filters);
match_topics(Client, Topic, [TopicFilter|Filters]) -> match_topics(ClientInfo, Topic, [TopicFilter|Filters]) ->
match_topic(emqx_topic:words(Topic), TopicFilter) match_topic(emqx_topic:words(Topic), TopicFilter)
orelse match_topics(Client, Topic, Filters). orelse match_topics(ClientInfo, Topic, Filters).
match_topic(Topic, {eq, TopicFilter}) -> match_topic(Topic, {eq, TopicFilter}) ->
Topic == TopicFilter; Topic == TopicFilter;
match_topic(Topic, TopicFilter) -> match_topic(Topic, TopicFilter) ->
emqx_topic:match(Topic, TopicFilter). emqx_topic:match(Topic, TopicFilter).
feed_var(Client, Pattern) -> feed_var(ClientInfo, Pattern) ->
feed_var(Client, Pattern, []). feed_var(ClientInfo, Pattern, []).
feed_var(_Client, [], Acc) -> feed_var(_ClientInfo, [], Acc) ->
lists:reverse(Acc); lists:reverse(Acc);
feed_var(Client = #{client_id := undefined}, [<<"%c">>|Words], Acc) -> feed_var(ClientInfo = #{clientid := undefined}, [<<"%c">>|Words], Acc) ->
feed_var(Client, Words, [<<"%c">>|Acc]); feed_var(ClientInfo, Words, [<<"%c">>|Acc]);
feed_var(Client = #{client_id := ClientId}, [<<"%c">>|Words], Acc) -> feed_var(ClientInfo = #{clientid := ClientId}, [<<"%c">>|Words], Acc) ->
feed_var(Client, Words, [ClientId |Acc]); feed_var(ClientInfo, Words, [ClientId |Acc]);
feed_var(Client = #{username := undefined}, [<<"%u">>|Words], Acc) -> feed_var(ClientInfo = #{username := undefined}, [<<"%u">>|Words], Acc) ->
feed_var(Client, Words, [<<"%u">>|Acc]); feed_var(ClientInfo, Words, [<<"%u">>|Acc]);
feed_var(Client = #{username := Username}, [<<"%u">>|Words], Acc) -> feed_var(ClientInfo = #{username := Username}, [<<"%u">>|Words], Acc) ->
feed_var(Client, Words, [Username|Acc]); feed_var(ClientInfo, Words, [Username|Acc]);
feed_var(Client, [W|Words], Acc) -> feed_var(ClientInfo, [W|Words], Acc) ->
feed_var(Client, Words, [W|Acc]). feed_var(ClientInfo, Words, [W|Acc]).

View File

@ -73,11 +73,11 @@ start_link() ->
-spec(stop() -> ok). -spec(stop() -> ok).
stop() -> gen_server:stop(?MODULE). stop() -> gen_server:stop(?MODULE).
-spec(check(emqx_types:client()) -> boolean()). -spec(check(emqx_types:clientinfo()) -> boolean()).
check(#{client_id := ClientId, check(#{clientid := ClientId,
username := Username, username := Username,
peerhost := IPAddr}) -> peerhost := IPAddr}) ->
ets:member(?BANNED_TAB, {client_id, ClientId}) ets:member(?BANNED_TAB, {clientid, ClientId})
orelse ets:member(?BANNED_TAB, {username, Username}) orelse ets:member(?BANNED_TAB, {username, Username})
orelse ets:member(?BANNED_TAB, {ipaddr, IPAddr}). orelse ets:member(?BANNED_TAB, {ipaddr, IPAddr}).
@ -85,7 +85,7 @@ check(#{client_id := ClientId,
add(Banned) when is_record(Banned, banned) -> add(Banned) when is_record(Banned, banned) ->
mnesia:dirty_write(?BANNED_TAB, Banned). mnesia:dirty_write(?BANNED_TAB, Banned).
-spec(delete({client_id, emqx_types:client_id()} -spec(delete({clientid, emqx_types:clientid()}
| {username, emqx_types:username()} | {username, emqx_types:username()}
| {peerhost, emqx_types:peerhost()}) -> ok). | {peerhost, emqx_types:peerhost()}) -> ok).
delete(Key) -> mnesia:dirty_delete(?BANNED_TAB, Key). delete(Key) -> mnesia:dirty_delete(?BANNED_TAB, Key).

View File

@ -38,9 +38,9 @@
, handle_in/2 , handle_in/2
, handle_out/2 , handle_out/2
, handle_call/2 , handle_call/2
, handle_cast/2
, handle_info/2 , handle_info/2
, handle_timeout/3 , handle_timeout/3
, disconnect/2
, terminate/2 , terminate/2
]). ]).
@ -60,7 +60,7 @@
%% MQTT ConnInfo %% MQTT ConnInfo
conninfo :: emqx_types:conninfo(), conninfo :: emqx_types:conninfo(),
%% MQTT ClientInfo %% MQTT ClientInfo
client :: emqx_types:client(), clientinfo :: emqx_types:clientinfo(),
%% MQTT Session %% MQTT Session
session :: emqx_session:session(), session :: emqx_session:session(),
%% Keepalive %% Keepalive
@ -71,18 +71,14 @@
topic_aliases :: maybe(map()), topic_aliases :: maybe(map()),
%% MQTT Topic Alias Maximum %% MQTT Topic Alias Maximum
alias_maximum :: maybe(map()), alias_maximum :: maybe(map()),
%% Publish Stats
pub_stats :: emqx_types:stats(),
%% Timers %% Timers
timers :: #{atom() => disabled | maybe(reference())}, timers :: #{atom() => disabled | maybe(reference())},
%% Fsm State
state :: fsm_state(),
%% GC State %% GC State
gc_state :: maybe(emqx_gc:gc_state()), gc_state :: maybe(emqx_gc:gc_state()),
%% OOM Policy TODO: should be removed from channel.
oom_policy :: maybe(emqx_oom:oom_policy()),
%% Connected
connected :: undefined | boolean(),
%% Connected at
connected_at :: erlang:timestamp(),
%% Disconnected at
disconnected_at :: erlang:timestamp(),
%% Takeover %% Takeover
takeover :: boolean(), takeover :: boolean(),
%% Resume %% Resume
@ -93,6 +89,14 @@
-opaque(channel() :: #channel{}). -opaque(channel() :: #channel{}).
-type(fsm_state() :: #{state_name := initialized
| connecting
| connected
| disconnected,
connected_at := pos_integer(),
disconnected_at := pos_integer()
}).
-define(TIMER_TABLE, #{ -define(TIMER_TABLE, #{
stats_timer => emit_stats, stats_timer => emit_stats,
alive_timer => keepalive, alive_timer => keepalive,
@ -102,8 +106,10 @@
will_timer => will_message will_timer => will_message
}). }).
-define(ATTR_KEYS, [conninfo, client, session, connected, connected_at, disconnected_at]). -define(ATTR_KEYS, [conninfo, clientinfo, state, session]).
-define(INFO_KEYS, ?ATTR_KEYS ++ [keepalive, topic_aliases, alias_maximum, gc_state, disconnected_at]).
-define(INFO_KEYS, ?ATTR_KEYS ++ [keepalive, will_msg, topic_aliases,
alias_maximum, gc_state]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Info, Attrs and Caps %% Info, Attrs and Caps
@ -119,46 +125,43 @@ info(Keys, Channel) when is_list(Keys) ->
[{Key, info(Key, Channel)} || Key <- Keys]; [{Key, info(Key, Channel)} || Key <- Keys];
info(conninfo, #channel{conninfo = ConnInfo}) -> info(conninfo, #channel{conninfo = ConnInfo}) ->
ConnInfo; ConnInfo;
info(client, #channel{client = ClientInfo}) -> info(clientinfo, #channel{clientinfo = ClientInfo}) ->
ClientInfo; ClientInfo;
info(session, #channel{session = Session}) -> info(session, #channel{session = Session}) ->
maybe_apply(fun emqx_session:info/1, Session); maybe_apply(fun emqx_session:info/1, Session);
info(state, #channel{state = State}) ->
State;
info(keepalive, #channel{keepalive = Keepalive}) -> info(keepalive, #channel{keepalive = Keepalive}) ->
maybe_apply(fun emqx_keepalive:info/1, Keepalive); maybe_apply(fun emqx_keepalive:info/1, Keepalive);
info(topic_aliases, #channel{topic_aliases = Aliases}) -> info(topic_aliases, #channel{topic_aliases = Aliases}) ->
Aliases; Aliases;
info(alias_maximum, #channel{alias_maximum = Limits}) -> info(alias_maximum, #channel{alias_maximum = Limits}) ->
Limits; Limits;
info(will_msg, #channel{will_msg = undefined}) ->
undefined;
info(will_msg, #channel{will_msg = WillMsg}) -> info(will_msg, #channel{will_msg = WillMsg}) ->
WillMsg; emqx_message:to_map(WillMsg);
info(pub_stats, #channel{pub_stats = PubStats}) ->
PubStats;
info(gc_state, #channel{gc_state = GcState}) -> info(gc_state, #channel{gc_state = GcState}) ->
maybe_apply(fun emqx_gc:info/1, GcState); maybe_apply(fun emqx_gc:info/1, GcState).
info(oom_policy, #channel{oom_policy = OomPolicy}) ->
maybe_apply(fun emqx_oom:info/1, OomPolicy);
info(connected, #channel{connected = Connected}) ->
Connected;
info(connected_at, #channel{connected_at = ConnectedAt}) ->
ConnectedAt;
info(disconnected_at, #channel{disconnected_at = DisconnectedAt}) ->
DisconnectedAt.
%% @doc Get attrs of the channel. %% @doc Get attrs of the channel.
-spec(attrs(channel()) -> emqx_types:attrs()). -spec(attrs(channel()) -> emqx_types:attrs()).
attrs(Channel) -> attrs(Channel) ->
maps:from_list([{Key, attr(Key, Channel)} || Key <- ?ATTR_KEYS]). Attrs = [{Key, attrs(Key, Channel)} || Key <- ?ATTR_KEYS],
maps:from_list(Attrs).
attr(conninfo, #channel{conninfo = ConnInfo}) -> attrs(session, #channel{session = Session}) ->
ConnInfo;
attr(session, #channel{session = Session}) ->
maybe_apply(fun emqx_session:attrs/1, Session); maybe_apply(fun emqx_session:attrs/1, Session);
attr(Key, Channel) -> info(Key, Channel). attrs(Key, Channel) -> info(Key, Channel).
-spec(stats(channel()) -> emqx_types:stats()). -spec(stats(channel()) -> emqx_types:stats()).
stats(#channel{session = Session}) -> stats(#channel{pub_stats = PubStats, session = Session}) ->
emqx_session:stats(Session). maps:to_list(PubStats) ++ emqx_session:stats(Session).
-spec(caps(channel()) -> emqx_types:caps()). -spec(caps(channel()) -> emqx_types:caps()).
caps(#channel{client = #{zone := Zone}}) -> caps(#channel{clientinfo = #{zone := Zone}}) ->
emqx_mqtt_caps:get_caps(Zone). emqx_mqtt_caps:get_caps(Zone).
%% For tests %% For tests
@ -172,7 +175,7 @@ set_field(Name, Val, Channel) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(init(emqx_types:conninfo(), proplists:proplist()) -> channel()). -spec(init(emqx_types:conninfo(), proplists:proplist()) -> channel()).
init(ConnInfo = #{peername := {PeerHost, _Port}, protocol := Protocol}, Options) -> init(ConnInfo = #{peername := {PeerHost, _Port}}, Options) ->
Zone = proplists:get_value(zone, Options), Zone = proplists:get_value(zone, Options),
Peercert = maps:get(peercert, ConnInfo, undefined), Peercert = maps:get(peercert, ConnInfo, undefined),
Username = case peer_cert_as_username(Options) of Username = case peer_cert_as_username(Options) of
@ -181,12 +184,13 @@ init(ConnInfo = #{peername := {PeerHost, _Port}, protocol := Protocol}, Options)
crt -> Peercert; crt -> Peercert;
_ -> undefined _ -> undefined
end, end,
Protocol = maps:get(protocol, ConnInfo, mqtt),
MountPoint = emqx_zone:get_env(Zone, mountpoint), MountPoint = emqx_zone:get_env(Zone, mountpoint),
ClientInfo = #{zone => Zone, ClientInfo = #{zone => Zone,
protocol => Protocol, protocol => Protocol,
peerhost => PeerHost, peerhost => PeerHost,
peercert => Peercert, peercert => Peercert,
client_id => undefined, clientid => undefined,
username => Username, username => Username,
mountpoint => MountPoint, mountpoint => MountPoint,
is_bridge => false, is_bridge => false,
@ -197,11 +201,11 @@ init(ConnInfo = #{peername := {PeerHost, _Port}, protocol := Protocol}, Options)
false -> disabled false -> disabled
end, end,
#channel{conninfo = ConnInfo, #channel{conninfo = ConnInfo,
client = ClientInfo, clientinfo = ClientInfo,
gc_state = init_gc_state(Zone), pub_stats = #{},
oom_policy = init_oom_policy(Zone),
timers = #{stats_timer => StatsTimer}, timers = #{stats_timer => StatsTimer},
connected = undefined, state = #{state_name => initialized},
gc_state = init_gc_state(Zone),
takeover = false, takeover = false,
resuming = false, resuming = false,
pendings = [] pendings = []
@ -213,9 +217,6 @@ peer_cert_as_username(Options) ->
init_gc_state(Zone) -> init_gc_state(Zone) ->
maybe_apply(fun emqx_gc:init/1, emqx_zone:force_gc_policy(Zone)). maybe_apply(fun emqx_gc:init/1, emqx_zone:force_gc_policy(Zone)).
init_oom_policy(Zone) ->
maybe_apply(fun emqx_oom:init/1, emqx_zone:force_shutdown_policy(Zone)).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Handle incoming packet %% Handle incoming packet
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -224,9 +225,11 @@ init_oom_policy(Zone) ->
-> {ok, channel()} -> {ok, channel()}
| {ok, emqx_types:packet(), channel()} | {ok, emqx_types:packet(), channel()}
| {ok, list(emqx_types:packet()), channel()} | {ok, list(emqx_types:packet()), channel()}
| {close, channel()}
| {close, emqx_types:packet(), channel()}
| {stop, Error :: term(), channel()} | {stop, Error :: term(), channel()}
| {stop, Error :: term(), emqx_types:packet(), channel()}). | {stop, Error :: term(), emqx_types:packet(), channel()}).
handle_in(?CONNECT_PACKET(_), Channel = #channel{connected = true}) -> handle_in(?CONNECT_PACKET(_), Channel = #channel{state = #{state_name := connected}}) ->
handle_out({disconnect, ?RC_PROTOCOL_ERROR}, Channel); handle_out({disconnect, ?RC_PROTOCOL_ERROR}, Channel);
handle_in(?CONNECT_PACKET(ConnPkt), Channel) -> handle_in(?CONNECT_PACKET(ConnPkt), Channel) ->
@ -244,73 +247,77 @@ handle_in(?CONNECT_PACKET(ConnPkt), Channel) ->
end; end;
handle_in(Packet = ?PUBLISH_PACKET(_QoS), Channel) -> handle_in(Packet = ?PUBLISH_PACKET(_QoS), Channel) ->
Channel1 = inc_pub_stats(publish_in, Channel),
case emqx_packet:check(Packet) of case emqx_packet:check(Packet) of
ok -> ok -> handle_publish(Packet, Channel1);
handle_publish(Packet, Channel);
{error, ReasonCode} -> {error, ReasonCode} ->
handle_out({disconnect, ReasonCode}, Channel) handle_out({disconnect, ReasonCode}, Channel1)
end; end;
handle_in(?PUBACK_PACKET(PacketId, _ReasonCode), handle_in(?PUBACK_PACKET(PacketId, _ReasonCode),
Channel = #channel{client = ClientInfo, session = Session}) -> Channel = #channel{clientinfo = ClientInfo, session = Session}) ->
Channel1 = inc_pub_stats(puback_in, Channel),
case emqx_session:puback(PacketId, Session) of case emqx_session:puback(PacketId, Session) of
{ok, Msg, Publishes, NSession} -> {ok, Msg, Publishes, NSession} ->
ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]), ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]),
handle_out({publish, Publishes}, Channel#channel{session = NSession}); handle_out({publish, Publishes}, Channel1#channel{session = NSession});
{ok, Msg, NSession} -> {ok, Msg, NSession} ->
ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]), ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]),
{ok, Channel#channel{session = NSession}}; {ok, Channel1#channel{session = NSession}};
{error, ?RC_PACKET_IDENTIFIER_IN_USE} -> {error, ?RC_PACKET_IDENTIFIER_IN_USE} ->
?LOG(warning, "The PUBACK PacketId ~w is inuse.", [PacketId]), ?LOG(warning, "The PUBACK PacketId ~w is inuse.", [PacketId]),
ok = emqx_metrics:inc('packets.puback.inuse'), ok = emqx_metrics:inc('packets.puback.inuse'),
{ok, Channel}; {ok, Channel1};
{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} -> {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} ->
?LOG(warning, "The PUBACK PacketId ~w is not found", [PacketId]), ?LOG(warning, "The PUBACK PacketId ~w is not found", [PacketId]),
ok = emqx_metrics:inc('packets.puback.missed'), ok = emqx_metrics:inc('packets.puback.missed'),
{ok, Channel} {ok, Channel1}
end; end;
handle_in(?PUBREC_PACKET(PacketId, _ReasonCode), handle_in(?PUBREC_PACKET(PacketId, _ReasonCode),
Channel = #channel{client = ClientInfo, session = Session}) -> Channel = #channel{clientinfo = ClientInfo, session = Session}) ->
Channel1 = inc_pub_stats(pubrec_in, Channel),
case emqx_session:pubrec(PacketId, Session) of case emqx_session:pubrec(PacketId, Session) of
{ok, Msg, NSession} -> {ok, Msg, NSession} ->
ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]), ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]),
NChannel = Channel#channel{session = NSession}, NChannel = Channel1#channel{session = NSession},
handle_out({pubrel, PacketId, ?RC_SUCCESS}, NChannel); handle_out({pubrel, PacketId, ?RC_SUCCESS}, NChannel);
{error, RC = ?RC_PACKET_IDENTIFIER_IN_USE} -> {error, RC = ?RC_PACKET_IDENTIFIER_IN_USE} ->
?LOG(warning, "The PUBREC PacketId ~w is inuse.", [PacketId]), ?LOG(warning, "The PUBREC PacketId ~w is inuse.", [PacketId]),
ok = emqx_metrics:inc('packets.pubrec.inuse'), ok = emqx_metrics:inc('packets.pubrec.inuse'),
handle_out({pubrel, PacketId, RC}, Channel); handle_out({pubrel, PacketId, RC}, Channel1);
{error, RC = ?RC_PACKET_IDENTIFIER_NOT_FOUND} -> {error, RC = ?RC_PACKET_IDENTIFIER_NOT_FOUND} ->
?LOG(warning, "The PUBREC ~w is not found.", [PacketId]), ?LOG(warning, "The PUBREC ~w is not found.", [PacketId]),
ok = emqx_metrics:inc('packets.pubrec.missed'), ok = emqx_metrics:inc('packets.pubrec.missed'),
handle_out({pubrel, PacketId, RC}, Channel) handle_out({pubrel, PacketId, RC}, Channel1)
end; end;
handle_in(?PUBREL_PACKET(PacketId, _ReasonCode), Channel = #channel{session = Session}) -> handle_in(?PUBREL_PACKET(PacketId, _ReasonCode), Channel = #channel{session = Session}) ->
Channel1 = inc_pub_stats(pubrel_in, Channel),
case emqx_session:pubrel(PacketId, Session) of case emqx_session:pubrel(PacketId, Session) of
{ok, NSession} -> {ok, NSession} ->
handle_out({pubcomp, PacketId, ?RC_SUCCESS}, Channel#channel{session = NSession}); handle_out({pubcomp, PacketId, ?RC_SUCCESS}, Channel1#channel{session = NSession});
{error, NotFound} -> {error, NotFound} ->
?LOG(warning, "The PUBREL PacketId ~w is not found", [PacketId]),
ok = emqx_metrics:inc('packets.pubrel.missed'), ok = emqx_metrics:inc('packets.pubrel.missed'),
handle_out({pubcomp, PacketId, NotFound}, Channel) ?LOG(warning, "The PUBREL PacketId ~w is not found", [PacketId]),
handle_out({pubcomp, PacketId, NotFound}, Channel1)
end; end;
handle_in(?PUBCOMP_PACKET(PacketId, _ReasonCode), Channel = #channel{session = Session}) -> handle_in(?PUBCOMP_PACKET(PacketId, _ReasonCode), Channel = #channel{session = Session}) ->
Channel1 = inc_pub_stats(pubcomp_in, Channel),
case emqx_session:pubcomp(PacketId, Session) of case emqx_session:pubcomp(PacketId, Session) of
{ok, Publishes, NSession} -> {ok, Publishes, NSession} ->
handle_out({publish, Publishes}, Channel#channel{session = NSession}); handle_out({publish, Publishes}, Channel1#channel{session = NSession});
{ok, NSession} -> {ok, NSession} ->
{ok, Channel#channel{session = NSession}}; {ok, Channel1#channel{session = NSession}};
{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} -> {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} ->
?LOG(warning, "The PUBCOMP PacketId ~w is not found", [PacketId]), ?LOG(warning, "The PUBCOMP PacketId ~w is not found", [PacketId]),
ok = emqx_metrics:inc('packets.pubcomp.missed'), ok = emqx_metrics:inc('packets.pubcomp.missed'),
{ok, Channel} {ok, Channel1}
end; end;
handle_in(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters), handle_in(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters),
Channel = #channel{client = ClientInfo}) -> Channel = #channel{clientinfo = ClientInfo}) ->
case emqx_packet:check(Packet) of case emqx_packet:check(Packet) of
ok -> TopicFilters1 = emqx_hooks:run_fold('client.subscribe', ok -> TopicFilters1 = emqx_hooks:run_fold('client.subscribe',
[ClientInfo, Properties], [ClientInfo, Properties],
@ -323,7 +330,7 @@ handle_in(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters),
end; end;
handle_in(Packet = ?UNSUBSCRIBE_PACKET(PacketId, Properties, TopicFilters), handle_in(Packet = ?UNSUBSCRIBE_PACKET(PacketId, Properties, TopicFilters),
Channel = #channel{client = ClientInfo}) -> Channel = #channel{clientinfo = ClientInfo}) ->
case emqx_packet:check(Packet) of case emqx_packet:check(Packet) of
ok -> TopicFilters1 = emqx_hooks:run_fold('client.unsubscribe', ok -> TopicFilters1 = emqx_hooks:run_fold('client.unsubscribe',
[ClientInfo, Properties], [ClientInfo, Properties],
@ -339,37 +346,49 @@ handle_in(?PACKET(?PINGREQ), Channel) ->
handle_in(?DISCONNECT_PACKET(ReasonCode, Properties), Channel = #channel{conninfo = ConnInfo}) -> handle_in(?DISCONNECT_PACKET(ReasonCode, Properties), Channel = #channel{conninfo = ConnInfo}) ->
#{proto_ver := ProtoVer, expiry_interval := OldInterval} = ConnInfo, #{proto_ver := ProtoVer, expiry_interval := OldInterval} = ConnInfo,
{ReasonName, Channel1} = case ReasonCode of
?RC_SUCCESS ->
{normal, Channel#channel{will_msg = undefined}};
_Other ->
{emqx_reason_codes:name(ReasonCode, ProtoVer), Channel}
end,
Interval = emqx_mqtt_props:get('Session-Expiry-Interval', Properties, OldInterval), Interval = emqx_mqtt_props:get('Session-Expiry-Interval', Properties, OldInterval),
case OldInterval =:= 0 andalso Interval =/= OldInterval of if
OldInterval == 0 andalso Interval > OldInterval ->
handle_out({disconnect, ?RC_PROTOCOL_ERROR}, Channel1);
Interval == 0 ->
{stop, ReasonName, Channel1};
true -> true ->
handle_out({disconnect, ?RC_PROTOCOL_ERROR}, Channel); Channel2 = Channel1#channel{conninfo = ConnInfo#{expiry_interval => Interval}},
false -> {close, ReasonName, Channel2}
Reason = case ReasonCode of
?RC_SUCCESS -> normal;
_ -> emqx_reason_codes:name(ReasonCode, ProtoVer)
end,
Channel1 = Channel#channel{conninfo = ConnInfo#{expiry_interval := Interval}},
Channel2 = case ReasonCode of
?RC_SUCCESS -> Channel1#channel{will_msg = undefined};
_ -> Channel1
end,
{wait_session_expire, {shutdown, Reason}, Channel2}
end; end;
handle_in(?AUTH_PACKET(), Channel) -> handle_in(?AUTH_PACKET(), Channel) ->
%%TODO: implement later. handle_out({disconnect, ?RC_IMPLEMENTATION_SPECIFIC_ERROR}, Channel);
{ok, Channel};
handle_in({frame_error, Reason}, Channel = #channel{state = FsmState}) ->
case FsmState of
#{state_name := initialized} ->
{stop, {shutdown, Reason}, Channel};
#{state_name := connecting} ->
{stop, {shutdown, Reason}, ?CONNACK_PACKET(?RC_MALFORMED_PACKET), Channel};
#{state_name := connected} ->
handle_out({disconnect, ?RC_MALFORMED_PACKET}, Channel);
#{state_name := disconnected} ->
?LOG(error, "Unexpected frame error: ~p", [Reason]),
{ok, Channel}
end;
handle_in(Packet, Channel) -> handle_in(Packet, Channel) ->
?LOG(error, "Unexpected incoming: ~p", [Packet]), ?LOG(error, "Unexpected incoming: ~p", [Packet]),
handle_out({disconnect, ?RC_MALFORMED_PACKET}, Channel). handle_out({disconnect, ?RC_PROTOCOL_ERROR}, Channel).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Process Connect %% Process Connect
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
process_connect(ConnPkt = #mqtt_packet_connect{clean_start = CleanStart}, process_connect(ConnPkt = #mqtt_packet_connect{clean_start = CleanStart},
Channel = #channel{conninfo = ConnInfo, client = ClientInfo}) -> Channel = #channel{conninfo = ConnInfo, clientinfo = ClientInfo}) ->
case emqx_cm:open_session(CleanStart, ClientInfo, ConnInfo) of case emqx_cm:open_session(CleanStart, ClientInfo, ConnInfo) of
{ok, #{session := Session, present := false}} -> {ok, #{session := Session, present := false}} ->
NChannel = Channel#channel{session = Session}, NChannel = Channel#channel{session = Session},
@ -391,6 +410,11 @@ process_connect(ConnPkt = #mqtt_packet_connect{clean_start = CleanStart},
%% Process Publish %% Process Publish
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
inc_pub_stats(Key, Channel) -> inc_pub_stats(Key, 1, Channel).
inc_pub_stats(Key, I, Channel = #channel{pub_stats = PubStats}) ->
NPubStats = maps:update_with(Key, fun(V) -> V+I end, I, PubStats),
Channel#channel{pub_stats = NPubStats}.
handle_publish(Packet = ?PUBLISH_PACKET(_QoS, Topic, _PacketId), handle_publish(Packet = ?PUBLISH_PACKET(_QoS, Topic, _PacketId),
Channel = #channel{conninfo = #{proto_ver := ProtoVer}}) -> Channel = #channel{conninfo = #{proto_ver := ProtoVer}}) ->
case pipeline([fun process_alias/2, case pipeline([fun process_alias/2,
@ -440,7 +464,7 @@ process_publish(PacketId, Msg = #message{qos = ?QOS_2},
end. end.
publish_to_msg(Packet, #channel{conninfo = #{proto_ver := ProtoVer}, publish_to_msg(Packet, #channel{conninfo = #{proto_ver := ProtoVer},
client = ClientInfo = #{mountpoint := MountPoint}}) -> clientinfo = ClientInfo = #{mountpoint := MountPoint}}) ->
Msg = emqx_packet:to_message(ClientInfo, Packet), Msg = emqx_packet:to_message(ClientInfo, Packet),
Msg1 = emqx_message:set_flag(dup, false, Msg), Msg1 = emqx_message:set_flag(dup, false, Msg),
Msg2 = emqx_message:set_header(proto_ver, ProtoVer, Msg1), Msg2 = emqx_message:set_header(proto_ver, ProtoVer, Msg1),
@ -461,7 +485,7 @@ process_subscribe([{TopicFilter, SubOpts}|More], Acc, Channel) ->
process_subscribe(More, [RC|Acc], NChannel). process_subscribe(More, [RC|Acc], NChannel).
do_subscribe(TopicFilter, SubOpts = #{qos := QoS}, Channel = do_subscribe(TopicFilter, SubOpts = #{qos := QoS}, Channel =
#channel{client = ClientInfo = #{mountpoint := MountPoint}, #channel{clientinfo = ClientInfo = #{mountpoint := MountPoint},
session = Session}) -> session = Session}) ->
case check_subscribe(TopicFilter, SubOpts, Channel) of case check_subscribe(TopicFilter, SubOpts, Channel) of
ok -> ok ->
@ -491,7 +515,7 @@ process_unsubscribe([{TopicFilter, SubOpts}|More], Acc, Channel) ->
process_unsubscribe(More, [RC|Acc], NChannel). process_unsubscribe(More, [RC|Acc], NChannel).
do_unsubscribe(TopicFilter, _SubOpts, Channel = do_unsubscribe(TopicFilter, _SubOpts, Channel =
#channel{client = ClientInfo = #{mountpoint := MountPoint}, #channel{clientinfo = ClientInfo = #{mountpoint := MountPoint},
session = Session}) -> session = Session}) ->
TopicFilter1 = emqx_mountpoint:mount(MountPoint, TopicFilter), TopicFilter1 = emqx_mountpoint:mount(MountPoint, TopicFilter),
case emqx_session:unsubscribe(ClientInfo, TopicFilter1, Session) of case emqx_session:unsubscribe(ClientInfo, TopicFilter1, Session) of
@ -506,22 +530,24 @@ do_unsubscribe(TopicFilter, _SubOpts, Channel =
%%TODO: RunFold or Pipeline %%TODO: RunFold or Pipeline
handle_out({connack, ?RC_SUCCESS, SP, ConnPkt}, handle_out({connack, ?RC_SUCCESS, SP, ConnPkt},
Channel = #channel{conninfo = ConnInfo, client = ClientInfo}) -> Channel = #channel{conninfo = ConnInfo,
clientinfo = ClientInfo,
state = FsmState}) ->
AckProps = run_fold([fun enrich_caps/2, AckProps = run_fold([fun enrich_caps/2,
fun enrich_server_keepalive/2, fun enrich_server_keepalive/2,
fun enrich_assigned_clientid/2 fun enrich_assigned_clientid/2], #{}, Channel),
], #{}, Channel), FsmState1 = FsmState#{state_name => connected,
Channel1 = Channel#channel{will_msg = emqx_packet:will_msg(ConnPkt), connected_at => erlang:system_time(second)
alias_maximum = init_alias_maximum(ConnPkt, ClientInfo), },
connected = true, Channel1 = Channel#channel{state = FsmState1,
connected_at = os:timestamp() will_msg = emqx_packet:will_msg(ConnPkt),
alias_maximum = init_alias_maximum(ConnPkt, ClientInfo)
}, },
Channel2 = ensure_keepalive(AckProps, Channel1), Channel2 = ensure_keepalive(AckProps, Channel1),
ok = emqx_hooks:run('client.connected', [ClientInfo, ?RC_SUCCESS, ConnInfo]), ok = emqx_hooks:run('client.connected', [ClientInfo, ?RC_SUCCESS, ConnInfo]),
AckPacket = ?CONNACK_PACKET(?RC_SUCCESS, SP, AckProps), AckPacket = ?CONNACK_PACKET(?RC_SUCCESS, SP, AckProps),
case maybe_resume_session(Channel2) of case maybe_resume_session(Channel2) of
ignore -> ignore -> {ok, AckPacket, Channel2};
{ok, AckPacket, Channel2};
{ok, Publishes, NSession} -> {ok, Publishes, NSession} ->
Channel3 = Channel2#channel{session = NSession, Channel3 = Channel2#channel{session = NSession,
resuming = false, resuming = false,
@ -531,7 +557,7 @@ handle_out({connack, ?RC_SUCCESS, SP, ConnPkt},
end; end;
handle_out({connack, ReasonCode, _ConnPkt}, Channel = #channel{conninfo = ConnInfo, handle_out({connack, ReasonCode, _ConnPkt}, Channel = #channel{conninfo = ConnInfo,
client = ClientInfo}) -> clientinfo = ClientInfo}) ->
ok = emqx_hooks:run('client.connected', [ClientInfo, ReasonCode, ConnInfo]), ok = emqx_hooks:run('client.connected', [ClientInfo, ReasonCode, ConnInfo]),
ReasonCode1 = case ProtoVer = maps:get(proto_ver, ConnInfo) of ReasonCode1 = case ProtoVer = maps:get(proto_ver, ConnInfo) of
?MQTT_PROTO_V5 -> ReasonCode; ?MQTT_PROTO_V5 -> ReasonCode;
@ -540,8 +566,8 @@ handle_out({connack, ReasonCode, _ConnPkt}, Channel = #channel{conninfo = ConnIn
Reason = emqx_reason_codes:name(ReasonCode1, ProtoVer), Reason = emqx_reason_codes:name(ReasonCode1, ProtoVer),
{stop, {shutdown, Reason}, ?CONNACK_PACKET(ReasonCode1), Channel}; {stop, {shutdown, Reason}, ?CONNACK_PACKET(ReasonCode1), Channel};
handle_out({deliver, Delivers}, Channel = #channel{session = Session, handle_out({deliver, Delivers}, Channel = #channel{state = #{state_name := disconnected},
connected = false}) -> session = Session}) ->
NSession = emqx_session:enqueue(Delivers, Session), NSession = emqx_session:enqueue(Delivers, Session),
{ok, Channel#channel{session = NSession}}; {ok, Channel#channel{session = NSession}};
@ -567,32 +593,33 @@ handle_out({publish, Publishes}, Channel) when is_list(Publishes) ->
{ok, _Ch} -> Acc {ok, _Ch} -> Acc
end end
end, [], Publishes), end, [], Publishes),
{ok, lists:reverse(Packets), Channel}; NChannel = inc_pub_stats(publish_out, length(Packets), Channel),
{ok, lists:reverse(Packets), NChannel};
%% Ignore loop deliver %% Ignore loop deliver
handle_out({publish, _PacketId, #message{from = ClientId, handle_out({publish, _PacketId, #message{from = ClientId,
flags = #{nl := true}}}, flags = #{nl := true}}},
Channel = #channel{client = #{client_id := ClientId}}) -> Channel = #channel{clientinfo = #{clientid := ClientId}}) ->
{ok, Channel}; {ok, Channel};
handle_out({publish, PacketId, Msg}, Channel = handle_out({publish, PacketId, Msg}, Channel =
#channel{client = ClientInfo = #{mountpoint := MountPoint}}) -> #channel{clientinfo = ClientInfo = #{mountpoint := MountPoint}}) ->
Msg1 = emqx_message:update_expiry(Msg), Msg1 = emqx_message:update_expiry(Msg),
Msg2 = emqx_hooks:run_fold('message.delivered', [ClientInfo], Msg1), Msg2 = emqx_hooks:run_fold('message.delivered', [ClientInfo], Msg1),
Msg3 = emqx_mountpoint:unmount(MountPoint, Msg2), Msg3 = emqx_mountpoint:unmount(MountPoint, Msg2),
{ok, emqx_message:to_packet(PacketId, Msg3), Channel}; {ok, emqx_message:to_packet(PacketId, Msg3), Channel};
handle_out({puback, PacketId, ReasonCode}, Channel) -> handle_out({puback, PacketId, ReasonCode}, Channel) ->
{ok, ?PUBACK_PACKET(PacketId, ReasonCode), Channel}; {ok, ?PUBACK_PACKET(PacketId, ReasonCode), inc_pub_stats(puback_out, Channel)};
handle_out({pubrel, PacketId, ReasonCode}, Channel) -> handle_out({pubrel, PacketId, ReasonCode}, Channel) ->
{ok, ?PUBREL_PACKET(PacketId, ReasonCode), Channel}; {ok, ?PUBREL_PACKET(PacketId, ReasonCode), inc_pub_stats(pubrel_out, Channel)};
handle_out({pubrec, PacketId, ReasonCode}, Channel) -> handle_out({pubrec, PacketId, ReasonCode}, Channel) ->
{ok, ?PUBREC_PACKET(PacketId, ReasonCode), Channel}; {ok, ?PUBREC_PACKET(PacketId, ReasonCode), inc_pub_stats(pubrec_out, Channel)};
handle_out({pubcomp, PacketId, ReasonCode}, Channel) -> handle_out({pubcomp, PacketId, ReasonCode}, Channel) ->
{ok, ?PUBCOMP_PACKET(PacketId, ReasonCode), Channel}; {ok, ?PUBCOMP_PACKET(PacketId, ReasonCode), inc_pub_stats(pubcomp_out, Channel)};
handle_out({suback, PacketId, ReasonCodes}, handle_out({suback, PacketId, ReasonCodes},
Channel = #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}}) -> Channel = #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}}) ->
@ -609,15 +636,28 @@ handle_out({unsuback, PacketId, ReasonCodes},
handle_out({unsuback, PacketId, _ReasonCodes}, Channel) -> handle_out({unsuback, PacketId, _ReasonCodes}, Channel) ->
{ok, ?UNSUBACK_PACKET(PacketId), Channel}; {ok, ?UNSUBACK_PACKET(PacketId), Channel};
handle_out({disconnect, ReasonCode}, Channel = #channel{conninfo = ConnInfo}) -> handle_out({disconnect, ReasonCode}, Channel = #channel{conninfo = #{proto_ver := ProtoVer}}) ->
case maps:get(proto_ver, ConnInfo) of ReasonName = emqx_reason_codes:name(ReasonCode, ProtoVer),
?MQTT_PROTO_V5 -> handle_out({disconnect, ReasonCode, ReasonName}, Channel);
Reason = emqx_reason_codes:name(ReasonCode),
Packet = ?DISCONNECT_PACKET(ReasonCode), %%TODO: Improve later...
{wait_session_expire, {shutdown, Reason}, Packet, Channel}; handle_out({disconnect, ReasonCode, ReasonName},
ProtoVer -> Channel = #channel{conninfo = #{proto_ver := ProtoVer,
Reason = emqx_reason_codes:name(ReasonCode, ProtoVer), expiry_interval := ExpiryInterval}}) ->
{wait_session_expire, {shutdown, Reason}, Channel} case {ExpiryInterval, ProtoVer} of
{0, ?MQTT_PROTO_V5} ->
{stop, ReasonName, ?DISCONNECT_PACKET(ReasonCode), Channel};
{0, _Ver} ->
{stop, ReasonName, Channel};
{?UINT_MAX, ?MQTT_PROTO_V5} ->
{close, ReasonName, ?DISCONNECT_PACKET(ReasonCode), Channel};
{?UINT_MAX, _Ver} ->
{close, ReasonName, Channel};
{Interval, ?MQTT_PROTO_V5} ->
NChannel = ensure_timer(expire_timer, Interval, Channel),
{close, ReasonName, ?DISCONNECT_PACKET(ReasonCode), NChannel};
{Interval, _Ver} ->
{close, ReasonName, ensure_timer(expire_timer, Interval, Channel)}
end; end;
handle_out({Type, Data}, Channel) -> handle_out({Type, Data}, Channel) ->
@ -631,10 +671,10 @@ handle_out({Type, Data}, Channel) ->
handle_call(kick, Channel) -> handle_call(kick, Channel) ->
{stop, {shutdown, kicked}, ok, Channel}; {stop, {shutdown, kicked}, ok, Channel};
handle_call(discard, Channel = #channel{connected = true}) -> handle_call(discard, Channel = #channel{state = #{state_name := connected}}) ->
Packet = ?DISCONNECT_PACKET(?RC_SESSION_TAKEN_OVER), Packet = ?DISCONNECT_PACKET(?RC_SESSION_TAKEN_OVER),
{stop, {shutdown, discarded}, Packet, ok, Channel}; {stop, {shutdown, discarded}, Packet, ok, Channel};
handle_call(discard, Channel = #channel{connected = false}) -> handle_call(discard, Channel = #channel{state = #{state_name := disconnected}}) ->
{stop, {shutdown, discarded}, ok, Channel}; {stop, {shutdown, discarded}, ok, Channel};
%% Session Takeover %% Session Takeover
@ -651,49 +691,40 @@ handle_call(Req, Channel) ->
?LOG(error, "Unexpected call: ~p", [Req]), ?LOG(error, "Unexpected call: ~p", [Req]),
{ok, ignored, Channel}. {ok, ignored, Channel}.
%%--------------------------------------------------------------------
%% Handle cast
%%--------------------------------------------------------------------
-spec(handle_cast(Msg :: term(), channel())
-> ok | {ok, channel()} | {stop, Reason :: term(), channel()}).
handle_cast({register, Attrs, Stats}, #channel{client = #{client_id := ClientId}}) ->
ok = emqx_cm:register_channel(ClientId),
emqx_cm:set_chan_attrs(ClientId, Attrs),
emqx_cm:set_chan_stats(ClientId, Stats);
handle_cast(Msg, Channel) ->
?LOG(error, "Unexpected cast: ~p", [Msg]),
{ok, Channel}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Handle Info %% Handle Info
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(handle_info(Info :: term(), channel()) -spec(handle_info(Info :: term(), channel())
-> {ok, channel()} | {stop, Reason :: term(), channel()}). -> ok | {ok, channel()} | {stop, Reason :: term(), channel()}).
handle_info({subscribe, TopicFilters}, Channel = #channel{client = ClientInfo}) -> handle_info({subscribe, TopicFilters}, Channel = #channel{clientinfo = ClientInfo}) ->
TopicFilters1 = emqx_hooks:run_fold('client.subscribe', TopicFilters1 = emqx_hooks:run_fold('client.subscribe',
[ClientInfo, #{'Internal' => true}], [ClientInfo, #{'Internal' => true}],
parse_topic_filters(TopicFilters)), parse_topic_filters(TopicFilters)),
{_ReasonCodes, NChannel} = process_subscribe(TopicFilters1, Channel), {_ReasonCodes, NChannel} = process_subscribe(TopicFilters1, Channel),
{ok, NChannel}; {ok, NChannel};
handle_info({unsubscribe, TopicFilters}, Channel = #channel{client = ClientInfo}) -> handle_info({unsubscribe, TopicFilters}, Channel = #channel{clientinfo = ClientInfo}) ->
TopicFilters1 = emqx_hooks:run_fold('client.unsubscribe', TopicFilters1 = emqx_hooks:run_fold('client.unsubscribe',
[ClientInfo, #{'Internal' => true}], [ClientInfo, #{'Internal' => true}],
parse_topic_filters(TopicFilters)), parse_topic_filters(TopicFilters)),
{_ReasonCodes, NChannel} = process_unsubscribe(TopicFilters1, Channel), {_ReasonCodes, NChannel} = process_unsubscribe(TopicFilters1, Channel),
{ok, NChannel}; {ok, NChannel};
handle_info(disconnected, Channel = #channel{connected = undefined}) -> handle_info({register, Attrs, Stats}, #channel{clientinfo = #{clientid := ClientId}}) ->
shutdown(closed, Channel); ok = emqx_cm:register_channel(ClientId),
emqx_cm:set_chan_attrs(ClientId, Attrs),
emqx_cm:set_chan_stats(ClientId, Stats);
handle_info(disconnected, Channel = #channel{connected = false}) -> %%TODO: Fixme later
%%handle_info(disconnected, Channel = #channel{connected = undefined}) ->
%% shutdown(closed, Channel);
handle_info(disconnected, Channel = #channel{state = #{state_name := disconnected}}) ->
{ok, Channel}; {ok, Channel};
handle_info(disconnected, Channel = #channel{conninfo = #{expiry_interval := ExpiryInterval}, handle_info(disconnected, Channel = #channel{conninfo = #{expiry_interval := ExpiryInterval},
client = ClientInfo = #{zone := Zone}, clientinfo = ClientInfo = #{zone := Zone},
will_msg = WillMsg}) -> will_msg = WillMsg}) ->
emqx_zone:enable_flapping_detect(Zone) andalso emqx_flapping:detect(ClientInfo), emqx_zone:enable_flapping_detect(Zone) andalso emqx_flapping:detect(ClientInfo),
Channel1 = ensure_disconnected(Channel), Channel1 = ensure_disconnected(Channel),
@ -726,7 +757,7 @@ handle_info(Info, Channel) ->
| {ok, Result :: term(), channel()} | {ok, Result :: term(), channel()}
| {stop, Reason :: term(), channel()}). | {stop, Reason :: term(), channel()}).
handle_timeout(TRef, {emit_stats, Stats}, handle_timeout(TRef, {emit_stats, Stats},
Channel = #channel{client = #{client_id := ClientId}, Channel = #channel{clientinfo = #{clientid := ClientId},
timers = #{stats_timer := TRef}}) -> timers = #{stats_timer := TRef}}) ->
ok = emqx_cm:set_chan_stats(ClientId, Stats), ok = emqx_cm:set_chan_stats(ClientId, Stats),
{ok, clean_timer(stats_timer, Channel)}; {ok, clean_timer(stats_timer, Channel)};
@ -739,7 +770,7 @@ handle_timeout(TRef, {keepalive, StatVal},
NChannel = Channel#channel{keepalive = NKeepalive}, NChannel = Channel#channel{keepalive = NKeepalive},
{ok, reset_timer(alive_timer, NChannel)}; {ok, reset_timer(alive_timer, NChannel)};
{error, timeout} -> {error, timeout} ->
{wait_session_expire, {shutdown, keepalive_timeout}, Channel} handle_out({disconnect, ?RC_KEEP_ALIVE_TIMEOUT}, Channel)
end; end;
handle_timeout(TRef, retry_delivery, handle_timeout(TRef, retry_delivery,
@ -810,7 +841,7 @@ reset_timer(Name, Time, Channel) ->
clean_timer(Name, Channel = #channel{timers = Timers}) -> clean_timer(Name, Channel = #channel{timers = Timers}) ->
Channel#channel{timers = maps:remove(Name, Timers)}. Channel#channel{timers = maps:remove(Name, Timers)}.
interval(stats_timer, #channel{client = #{zone := Zone}}) -> interval(stats_timer, #channel{clientinfo = #{zone := Zone}}) ->
emqx_zone:get_env(Zone, idle_timeout, 30000); emqx_zone:get_env(Zone, idle_timeout, 30000);
interval(alive_timer, #channel{keepalive = KeepAlive}) -> interval(alive_timer, #channel{keepalive = KeepAlive}) ->
emqx_keepalive:info(interval, KeepAlive); emqx_keepalive:info(interval, KeepAlive);
@ -828,18 +859,21 @@ will_delay_interval(undefined) -> 0;
will_delay_interval(WillMsg) -> will_delay_interval(WillMsg) ->
emqx_message:get_header('Will-Delay-Interval', WillMsg, 0). emqx_message:get_header('Will-Delay-Interval', WillMsg, 0).
%% TODO: Implement later.
disconnect(_Reason, Channel) -> {ok, Channel}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Terminate %% Terminate
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
terminate(_, #channel{connected = undefined}) -> terminate(_, #channel{state = #{state_name := initialized}}) ->
ok; ok;
terminate(normal, #channel{conninfo = ConnInfo, client = ClientInfo}) -> terminate(normal, #channel{conninfo = ConnInfo, clientinfo = ClientInfo}) ->
ok = emqx_hooks:run('client.disconnected', [ClientInfo, normal, ConnInfo]); ok = emqx_hooks:run('client.disconnected', [ClientInfo, normal, ConnInfo]);
terminate({shutdown, Reason}, #channel{conninfo = ConnInfo, client = ClientInfo}) terminate({shutdown, Reason}, #channel{conninfo = ConnInfo, clientinfo = ClientInfo})
when Reason =:= kicked orelse Reason =:= discarded orelse Reason =:= takeovered -> when Reason =:= kicked orelse Reason =:= discarded orelse Reason =:= takeovered ->
ok = emqx_hooks:run('client.disconnected', [ClientInfo, Reason, ConnInfo]); ok = emqx_hooks:run('client.disconnected', [ClientInfo, Reason, ConnInfo]);
terminate(Reason, #channel{conninfo = ConnInfo, client = ClientInfo, will_msg = WillMsg}) -> terminate(Reason, #channel{conninfo = ConnInfo, clientinfo = ClientInfo, will_msg = WillMsg}) ->
publish_will_msg(WillMsg), publish_will_msg(WillMsg),
ok = emqx_hooks:run('client.disconnected', [ClientInfo, Reason, ConnInfo]). ok = emqx_hooks:run('client.disconnected', [ClientInfo, Reason, ConnInfo]).
@ -864,9 +898,9 @@ enrich_conninfo(#mqtt_packet_connect{
clean_start = CleanStart, clean_start = CleanStart,
keepalive = Keepalive, keepalive = Keepalive,
properties = ConnProps, properties = ConnProps,
client_id = ClientId, clientid = ClientId,
username = Username}, Channel) -> username = Username}, Channel) ->
#channel{conninfo = ConnInfo, client = #{zone := Zone}} = Channel, #channel{conninfo = ConnInfo, clientinfo = #{zone := Zone}} = Channel,
MaxInflight = emqx_mqtt_props:get('Receive-Maximum', MaxInflight = emqx_mqtt_props:get('Receive-Maximum',
ConnProps, emqx_zone:max_inflight(Zone)), ConnProps, emqx_zone:max_inflight(Zone)),
Interval = if ProtoVer == ?MQTT_PROTO_V5 -> Interval = if ProtoVer == ?MQTT_PROTO_V5 ->
@ -880,7 +914,7 @@ enrich_conninfo(#mqtt_packet_connect{
proto_ver => ProtoVer, proto_ver => ProtoVer,
clean_start => CleanStart, clean_start => CleanStart,
keepalive => Keepalive, keepalive => Keepalive,
client_id => ClientId, clientid => ClientId,
username => Username, username => Username,
conn_props => ConnProps, conn_props => ConnProps,
receive_maximum => MaxInflight, receive_maximum => MaxInflight,
@ -889,18 +923,18 @@ enrich_conninfo(#mqtt_packet_connect{
{ok, Channel#channel{conninfo = NConnInfo}}. {ok, Channel#channel{conninfo = NConnInfo}}.
%% @doc Check connect packet. %% @doc Check connect packet.
check_connect(ConnPkt, #channel{client = #{zone := Zone}}) -> check_connect(ConnPkt, #channel{clientinfo = #{zone := Zone}}) ->
emqx_packet:check(ConnPkt, emqx_mqtt_caps:get_caps(Zone)). emqx_packet:check(ConnPkt, emqx_mqtt_caps:get_caps(Zone)).
%% @doc Enrich client %% @doc Enrich client
enrich_client(ConnPkt, Channel = #channel{client = ClientInfo}) -> enrich_client(ConnPkt, Channel = #channel{clientinfo = ClientInfo}) ->
{ok, NConnPkt, NClientInfo} = {ok, NConnPkt, NClientInfo} =
pipeline([fun set_username/2, pipeline([fun set_username/2,
fun set_bridge_mode/2, fun set_bridge_mode/2,
fun maybe_username_as_clientid/2, fun maybe_username_as_clientid/2,
fun maybe_assign_clientid/2, fun maybe_assign_clientid/2,
fun fix_mountpoint/2], ConnPkt, ClientInfo), fun fix_mountpoint/2], ConnPkt, ClientInfo),
{ok, NConnPkt, Channel#channel{client = NClientInfo}}. {ok, NConnPkt, Channel#channel{clientinfo = NClientInfo}}.
set_username(#mqtt_packet_connect{username = Username}, set_username(#mqtt_packet_connect{username = Username},
ClientInfo = #{username := undefined}) -> ClientInfo = #{username := undefined}) ->
@ -916,35 +950,35 @@ maybe_username_as_clientid(_ConnPkt, ClientInfo = #{username := undefined}) ->
{ok, ClientInfo}; {ok, ClientInfo};
maybe_username_as_clientid(_ConnPkt, ClientInfo = #{zone := Zone, username := Username}) -> maybe_username_as_clientid(_ConnPkt, ClientInfo = #{zone := Zone, username := Username}) ->
case emqx_zone:use_username_as_clientid(Zone) of case emqx_zone:use_username_as_clientid(Zone) of
true -> {ok, ClientInfo#{client_id => Username}}; true -> {ok, ClientInfo#{clientid => Username}};
false -> ok false -> ok
end. end.
maybe_assign_clientid(#mqtt_packet_connect{client_id = <<>>}, ClientInfo) -> maybe_assign_clientid(#mqtt_packet_connect{clientid = <<>>}, ClientInfo) ->
%% Generate a rand clientId %% Generate a rand clientId
{ok, ClientInfo#{client_id => emqx_guid:to_base62(emqx_guid:gen())}}; {ok, ClientInfo#{clientid => emqx_guid:to_base62(emqx_guid:gen())}};
maybe_assign_clientid(#mqtt_packet_connect{client_id = ClientId}, ClientInfo) -> maybe_assign_clientid(#mqtt_packet_connect{clientid = ClientId}, ClientInfo) ->
{ok, ClientInfo#{client_id => ClientId}}. {ok, ClientInfo#{clientid => ClientId}}.
fix_mountpoint(_ConnPkt, #{mountpoint := undefined}) -> ok; fix_mountpoint(_ConnPkt, #{mountpoint := undefined}) -> ok;
fix_mountpoint(_ConnPkt, ClientInfo = #{mountpoint := Mountpoint}) -> fix_mountpoint(_ConnPkt, ClientInfo = #{mountpoint := Mountpoint}) ->
{ok, ClientInfo#{mountpoint := emqx_mountpoint:replvar(Mountpoint, ClientInfo)}}. {ok, ClientInfo#{mountpoint := emqx_mountpoint:replvar(Mountpoint, ClientInfo)}}.
%% @doc Set logger metadata. %% @doc Set logger metadata.
set_logger_meta(_ConnPkt, #channel{client = #{client_id := ClientId}}) -> set_logger_meta(_ConnPkt, #channel{clientinfo = #{clientid := ClientId}}) ->
emqx_logger:set_metadata_client_id(ClientId). emqx_logger:set_metadata_clientid(ClientId).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Check banned/flapping %% Check banned/flapping
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
check_banned(_ConnPkt, #channel{client = ClientInfo = #{zone := Zone}}) -> check_banned(_ConnPkt, #channel{clientinfo = ClientInfo = #{zone := Zone}}) ->
case emqx_zone:enable_ban(Zone) andalso emqx_banned:check(ClientInfo) of case emqx_zone:enable_ban(Zone) andalso emqx_banned:check(ClientInfo) of
true -> {error, ?RC_BANNED}; true -> {error, ?RC_BANNED};
false -> ok false -> ok
end. end.
check_flapping(_ConnPkt, #channel{client = ClientInfo = #{zone := Zone}}) -> check_flapping(_ConnPkt, #channel{clientinfo = ClientInfo = #{zone := Zone}}) ->
case emqx_zone:enable_flapping_detect(Zone) case emqx_zone:enable_flapping_detect(Zone)
andalso emqx_flapping:check(ClientInfo) of andalso emqx_flapping:check(ClientInfo) of
true -> {error, ?RC_CONNECTION_RATE_EXCEEDED}; true -> {error, ?RC_CONNECTION_RATE_EXCEEDED};
@ -955,13 +989,13 @@ check_flapping(_ConnPkt, #channel{client = ClientInfo = #{zone := Zone}}) ->
%% Auth Connect %% Auth Connect
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
auth_connect(#mqtt_packet_connect{client_id = ClientId, auth_connect(#mqtt_packet_connect{clientid = ClientId,
username = Username, username = Username,
password = Password}, password = Password},
Channel = #channel{client = ClientInfo}) -> Channel = #channel{clientinfo = ClientInfo}) ->
case emqx_access_control:authenticate(ClientInfo#{password => Password}) of case emqx_access_control:authenticate(ClientInfo#{password => Password}) of
{ok, AuthResult} -> {ok, AuthResult} ->
{ok, Channel#channel{client = maps:merge(ClientInfo, AuthResult)}}; {ok, Channel#channel{clientinfo = maps:merge(ClientInfo, AuthResult)}};
{error, Reason} -> {error, Reason} ->
?LOG(warning, "Client ~s (Username: '~s') login failed for ~0p", ?LOG(warning, "Client ~s (Username: '~s') login failed for ~0p",
[ClientId, Username, Reason]), [ClientId, Username, Reason]),
@ -1004,7 +1038,7 @@ save_alias(AliasId, Topic, Aliases) -> maps:put(AliasId, Topic, Aliases).
%% Check Pub ACL %% Check Pub ACL
check_pub_acl(#mqtt_packet{variable = #mqtt_packet_publish{topic_name = Topic}}, check_pub_acl(#mqtt_packet{variable = #mqtt_packet_publish{topic_name = Topic}},
#channel{client = ClientInfo}) -> #channel{clientinfo = ClientInfo}) ->
case is_acl_enabled(ClientInfo) andalso case is_acl_enabled(ClientInfo) andalso
emqx_access_control:check_acl(ClientInfo, publish, Topic) of emqx_access_control:check_acl(ClientInfo, publish, Topic) of
false -> ok; false -> ok;
@ -1033,7 +1067,7 @@ check_pub_caps(#mqtt_packet{header = #mqtt_packet_header{qos = QoS,
retain = Retain retain = Retain
} }
}, },
#channel{client = #{zone := Zone}}) -> #channel{clientinfo = #{zone := Zone}}) ->
emqx_mqtt_caps:check_pub(Zone, #{qos => QoS, retain => Retain}). emqx_mqtt_caps:check_pub(Zone, #{qos => QoS, retain => Retain}).
%% Check Sub %% Check Sub
@ -1044,7 +1078,7 @@ check_subscribe(TopicFilter, SubOpts, Channel) ->
end. end.
%% Check Sub ACL %% Check Sub ACL
check_sub_acl(TopicFilter, #channel{client = ClientInfo}) -> check_sub_acl(TopicFilter, #channel{clientinfo = ClientInfo}) ->
case is_acl_enabled(ClientInfo) andalso case is_acl_enabled(ClientInfo) andalso
emqx_access_control:check_acl(ClientInfo, subscribe, TopicFilter) of emqx_access_control:check_acl(ClientInfo, subscribe, TopicFilter) of
false -> allow; false -> allow;
@ -1052,7 +1086,7 @@ check_sub_acl(TopicFilter, #channel{client = ClientInfo}) ->
end. end.
%% Check Sub Caps %% Check Sub Caps
check_sub_caps(TopicFilter, SubOpts, #channel{client = #{zone := Zone}}) -> check_sub_caps(TopicFilter, SubOpts, #channel{clientinfo = #{zone := Zone}}) ->
emqx_mqtt_caps:check_sub(Zone, TopicFilter, SubOpts). emqx_mqtt_caps:check_sub(Zone, TopicFilter, SubOpts).
enrich_subid(#{'Subscription-Identifier' := SubId}, TopicFilters) -> enrich_subid(#{'Subscription-Identifier' := SubId}, TopicFilters) ->
@ -1063,12 +1097,12 @@ enrich_subid(_Properties, TopicFilters) ->
enrich_subopts(SubOpts, #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}}) -> enrich_subopts(SubOpts, #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}}) ->
SubOpts; SubOpts;
enrich_subopts(SubOpts, #channel{client = #{zone := Zone, is_bridge := IsBridge}}) -> enrich_subopts(SubOpts, #channel{clientinfo = #{zone := Zone, is_bridge := IsBridge}}) ->
NL = flag(emqx_zone:ignore_loop_deliver(Zone)), NL = flag(emqx_zone:ignore_loop_deliver(Zone)),
SubOpts#{rap => flag(IsBridge), nl => NL}. SubOpts#{rap => flag(IsBridge), nl => NL}.
enrich_caps(AckProps, #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}, enrich_caps(AckProps, #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5},
client = #{zone := Zone}}) -> clientinfo = #{zone := Zone}}) ->
#{max_packet_size := MaxPktSize, #{max_packet_size := MaxPktSize,
max_qos_allowed := MaxQoS, max_qos_allowed := MaxQoS,
retain_available := Retain, retain_available := Retain,
@ -1087,16 +1121,16 @@ enrich_caps(AckProps, #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5},
enrich_caps(AckProps, _Channel) -> enrich_caps(AckProps, _Channel) ->
AckProps. AckProps.
enrich_server_keepalive(AckProps, #channel{client = #{zone := Zone}}) -> enrich_server_keepalive(AckProps, #channel{clientinfo = #{zone := Zone}}) ->
case emqx_zone:server_keepalive(Zone) of case emqx_zone:server_keepalive(Zone) of
undefined -> AckProps; undefined -> AckProps;
Keepalive -> AckProps#{'Server-Keep-Alive' => Keepalive} Keepalive -> AckProps#{'Server-Keep-Alive' => Keepalive}
end. end.
enrich_assigned_clientid(AckProps, #channel{conninfo = ConnInfo, enrich_assigned_clientid(AckProps, #channel{conninfo = ConnInfo,
client = #{client_id := ClientId} clientinfo = #{clientid := ClientId}
}) -> }) ->
case maps:get(client_id, ConnInfo) of case maps:get(clientid, ConnInfo) of
<<>> -> %% Original ClientId is null. <<>> -> %% Original ClientId is null.
AckProps#{'Assigned-Client-Identifier' => ClientId}; AckProps#{'Assigned-Client-Identifier' => ClientId};
_Origin -> AckProps _Origin -> AckProps
@ -1108,8 +1142,10 @@ init_alias_maximum(#mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V5,
inbound => emqx_mqtt_caps:get_caps(Zone, max_topic_alias, 0)}; inbound => emqx_mqtt_caps:get_caps(Zone, max_topic_alias, 0)};
init_alias_maximum(_ConnPkt, _ClientInfo) -> undefined. init_alias_maximum(_ConnPkt, _ClientInfo) -> undefined.
ensure_disconnected(Channel) -> ensure_disconnected(Channel = #channel{state = FsmState}) ->
Channel#channel{connected = false, disconnected_at = os:timestamp()}. Channel#channel{state = FsmState#{state_name := disconnected,
disconnected_at => erlang:system_time(second)
}}.
ensure_keepalive(#{'Server-Keep-Alive' := Interval}, Channel) -> ensure_keepalive(#{'Server-Keep-Alive' := Interval}, Channel) ->
ensure_keepalive_timer(Interval, Channel); ensure_keepalive_timer(Interval, Channel);
@ -1117,7 +1153,7 @@ ensure_keepalive(_AckProps, Channel = #channel{conninfo = ConnInfo}) ->
ensure_keepalive_timer(maps:get(keepalive, ConnInfo), Channel). ensure_keepalive_timer(maps:get(keepalive, ConnInfo), Channel).
ensure_keepalive_timer(0, Channel) -> Channel; ensure_keepalive_timer(0, Channel) -> Channel;
ensure_keepalive_timer(Interval, Channel = #channel{client = #{zone := Zone}}) -> ensure_keepalive_timer(Interval, Channel = #channel{clientinfo = #{zone := Zone}}) ->
Backoff = emqx_zone:get_env(Zone, keepalive_backoff, 0.75), Backoff = emqx_zone:get_env(Zone, keepalive_backoff, 0.75),
Keepalive = emqx_keepalive:init(round(timer:seconds(Interval) * Backoff)), Keepalive = emqx_keepalive:init(round(timer:seconds(Interval) * Backoff)),
ensure_timer(alive_timer, Channel#channel{keepalive = Keepalive}). ensure_timer(alive_timer, Channel#channel{keepalive = Keepalive}).
@ -1150,19 +1186,13 @@ parse_topic_filters(TopicFilters) ->
maybe_gc_and_check_oom(_Oct, Channel = #channel{gc_state = undefined}) -> maybe_gc_and_check_oom(_Oct, Channel = #channel{gc_state = undefined}) ->
Channel; Channel;
maybe_gc_and_check_oom(Oct, Channel = #channel{gc_state = GCSt, maybe_gc_and_check_oom(Oct, Channel = #channel{clientinfo = #{zone := Zone},
oom_policy = OomPolicy}) -> gc_state = GCSt}) ->
{IsGC, GCSt1} = emqx_gc:run(1, Oct, GCSt), {IsGC, GCSt1} = emqx_gc:run(1, Oct, GCSt),
IsGC andalso emqx_metrics:inc('channel.gc.cnt'), IsGC andalso emqx_metrics:inc('channel.gc.cnt'),
IsGC andalso maybe_apply(fun check_oom/1, OomPolicy), IsGC andalso emqx_zone:check_oom(Zone, fun(Shutdown) -> self() ! Shutdown end),
Channel#channel{gc_state = GCSt1}. Channel#channel{gc_state = GCSt1}.
check_oom(OomPolicy) ->
case emqx_oom:check(OomPolicy) of
ok -> ok;
Shutdown -> self() ! Shutdown
end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Helper functions %% Helper functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------

View File

@ -94,19 +94,19 @@ start_link() ->
%% @doc Register a channel. %% @doc Register a channel.
%% Channel will be unregistered automatically when the channel process dies %% Channel will be unregistered automatically when the channel process dies
-spec(register_channel(emqx_types:client_id()) -> ok). -spec(register_channel(emqx_types:clientid()) -> ok).
register_channel(ClientId) when is_binary(ClientId) -> register_channel(ClientId) when is_binary(ClientId) ->
register_channel(ClientId, self()). register_channel(ClientId, self()).
%% @doc Register a channel with pid. %% @doc Register a channel with pid.
-spec(register_channel(emqx_types:client_id(), chan_pid()) -> ok). -spec(register_channel(emqx_types:clientid(), chan_pid()) -> ok).
register_channel(ClientId, ChanPid) -> register_channel(ClientId, ChanPid) ->
Chan = {ClientId, ChanPid}, Chan = {ClientId, ChanPid},
true = ets:insert(?CHAN_TAB, Chan), true = ets:insert(?CHAN_TAB, Chan),
ok = emqx_cm_registry:register_channel(Chan), ok = emqx_cm_registry:register_channel(Chan),
cast({registered, Chan}). cast({registered, Chan}).
-spec(unregister_channel(emqx_types:client_id()) -> ok). -spec(unregister_channel(emqx_types:clientid()) -> ok).
unregister_channel(ClientId) when is_binary(ClientId) -> unregister_channel(ClientId) when is_binary(ClientId) ->
true = do_unregister_channel({ClientId, self()}), true = do_unregister_channel({ClientId, self()}),
ok. ok.
@ -119,31 +119,31 @@ do_unregister_channel(Chan) ->
true = ets:delete(?CHAN_STATS_TAB, Chan), true = ets:delete(?CHAN_STATS_TAB, Chan),
ets:delete_object(?CHAN_TAB, Chan). ets:delete_object(?CHAN_TAB, Chan).
%% @doc Get attrs of a channel. %% @doc Get info of a channel.
-spec(get_chan_attrs(emqx_types:client_id()) -> maybe(emqx_types:attrs())). -spec(get_chan_attrs(emqx_types:clientid()) -> maybe(emqx_types:attrs())).
get_chan_attrs(ClientId) -> get_chan_attrs(ClientId) ->
with_channel(ClientId, fun(ChanPid) -> get_chan_attrs(ClientId, ChanPid) end). with_channel(ClientId, fun(ChanPid) -> get_chan_attrs(ClientId, ChanPid) end).
-spec(get_chan_attrs(emqx_types:client_id(), chan_pid()) -> maybe(emqx_types:attrs())). -spec(get_chan_attrs(emqx_types:clientid(), chan_pid()) -> maybe(emqx_types:attrs())).
get_chan_attrs(ClientId, ChanPid) when node(ChanPid) == node() -> get_chan_attrs(ClientId, ChanPid) when node(ChanPid) == node() ->
Chan = {ClientId, ChanPid}, Chan = {ClientId, ChanPid},
emqx_tables:lookup_value(?CHAN_ATTRS_TAB, Chan); emqx_tables:lookup_value(?CHAN_ATTRS_TAB, Chan);
get_chan_attrs(ClientId, ChanPid) -> get_chan_attrs(ClientId, ChanPid) ->
rpc_call(node(ChanPid), get_chan_attrs, [ClientId, ChanPid]). rpc_call(node(ChanPid), get_chan_attrs, [ClientId, ChanPid]).
%% @doc Set attrs of a channel. %% @doc Set info of a channel.
-spec(set_chan_attrs(emqx_types:client_id(), emqx_types:attrs()) -> ok). -spec(set_chan_attrs(emqx_types:clientid(), emqx_types:attrs()) -> ok).
set_chan_attrs(ClientId, Attrs) when is_binary(ClientId) -> set_chan_attrs(ClientId, Info) when is_binary(ClientId) ->
Chan = {ClientId, self()}, Chan = {ClientId, self()},
true = ets:insert(?CHAN_ATTRS_TAB, {Chan, Attrs}), true = ets:insert(?CHAN_ATTRS_TAB, {Chan, Info}),
ok. ok.
%% @doc Get channel's stats. %% @doc Get channel's stats.
-spec(get_chan_stats(emqx_types:client_id()) -> maybe(emqx_types:stats())). -spec(get_chan_stats(emqx_types:clientid()) -> maybe(emqx_types:stats())).
get_chan_stats(ClientId) -> get_chan_stats(ClientId) ->
with_channel(ClientId, fun(ChanPid) -> get_chan_stats(ClientId, ChanPid) end). with_channel(ClientId, fun(ChanPid) -> get_chan_stats(ClientId, ChanPid) end).
-spec(get_chan_stats(emqx_types:client_id(), chan_pid()) -> maybe(emqx_types:stats())). -spec(get_chan_stats(emqx_types:clientid(), chan_pid()) -> maybe(emqx_types:stats())).
get_chan_stats(ClientId, ChanPid) when node(ChanPid) == node() -> get_chan_stats(ClientId, ChanPid) when node(ChanPid) == node() ->
Chan = {ClientId, ChanPid}, Chan = {ClientId, ChanPid},
emqx_tables:lookup_value(?CHAN_STATS_TAB, Chan); emqx_tables:lookup_value(?CHAN_STATS_TAB, Chan);
@ -151,23 +151,23 @@ get_chan_stats(ClientId, ChanPid) ->
rpc_call(node(ChanPid), get_chan_stats, [ClientId, ChanPid]). rpc_call(node(ChanPid), get_chan_stats, [ClientId, ChanPid]).
%% @doc Set channel's stats. %% @doc Set channel's stats.
-spec(set_chan_stats(emqx_types:client_id(), emqx_types:stats()) -> ok). -spec(set_chan_stats(emqx_types:clientid(), emqx_types:stats()) -> ok).
set_chan_stats(ClientId, Stats) when is_binary(ClientId) -> set_chan_stats(ClientId, Stats) when is_binary(ClientId) ->
set_chan_stats(ClientId, self(), Stats). set_chan_stats(ClientId, self(), Stats).
-spec(set_chan_stats(emqx_types:client_id(), chan_pid(), emqx_types:stats()) -> ok). -spec(set_chan_stats(emqx_types:clientid(), chan_pid(), emqx_types:stats()) -> ok).
set_chan_stats(ClientId, ChanPid, Stats) -> set_chan_stats(ClientId, ChanPid, Stats) ->
Chan = {ClientId, ChanPid}, Chan = {ClientId, ChanPid},
true = ets:insert(?CHAN_STATS_TAB, {Chan, Stats}), true = ets:insert(?CHAN_STATS_TAB, {Chan, Stats}),
ok. ok.
%% @doc Open a session. %% @doc Open a session.
-spec(open_session(boolean(), emqx_types:client(), map()) -spec(open_session(boolean(), emqx_types:clientinfo(), emqx_types:conninfo())
-> {ok, #{session := emqx_session:session(), -> {ok, #{session := emqx_session:session(),
present := boolean(), present := boolean(),
pendings => list()}} pendings => list()}}
| {error, Reason :: term()}). | {error, Reason :: term()}).
open_session(true, ClientInfo = #{client_id := ClientId}, ConnInfo) -> open_session(true, ClientInfo = #{clientid := ClientId}, ConnInfo) ->
CleanStart = fun(_) -> CleanStart = fun(_) ->
ok = discard_session(ClientId), ok = discard_session(ClientId),
Session = emqx_session:init(ClientInfo, ConnInfo), Session = emqx_session:init(ClientInfo, ConnInfo),
@ -175,7 +175,7 @@ open_session(true, ClientInfo = #{client_id := ClientId}, ConnInfo) ->
end, end,
emqx_cm_locker:trans(ClientId, CleanStart); emqx_cm_locker:trans(ClientId, CleanStart);
open_session(false, ClientInfo = #{client_id := ClientId}, ConnInfo) -> open_session(false, ClientInfo = #{clientid := ClientId}, ConnInfo) ->
ResumeStart = fun(_) -> ResumeStart = fun(_) ->
case takeover_session(ClientId) of case takeover_session(ClientId) of
{ok, ConnMod, ChanPid, Session} -> {ok, ConnMod, ChanPid, Session} ->
@ -192,7 +192,7 @@ open_session(false, ClientInfo = #{client_id := ClientId}, ConnInfo) ->
emqx_cm_locker:trans(ClientId, ResumeStart). emqx_cm_locker:trans(ClientId, ResumeStart).
%% @doc Try to takeover a session. %% @doc Try to takeover a session.
-spec(takeover_session(emqx_types:client_id()) -spec(takeover_session(emqx_types:clientid())
-> {ok, emqx_session:session()} | {error, Reason :: term()}). -> {ok, emqx_session:session()} | {error, Reason :: term()}).
takeover_session(ClientId) -> takeover_session(ClientId) ->
case lookup_channels(ClientId) of case lookup_channels(ClientId) of
@ -221,7 +221,7 @@ takeover_session(ClientId, ChanPid) ->
rpc_call(node(ChanPid), takeover_session, [ClientId, ChanPid]). rpc_call(node(ChanPid), takeover_session, [ClientId, ChanPid]).
%% @doc Discard all the sessions identified by the ClientId. %% @doc Discard all the sessions identified by the ClientId.
-spec(discard_session(emqx_types:client_id()) -> ok). -spec(discard_session(emqx_types:clientid()) -> ok).
discard_session(ClientId) when is_binary(ClientId) -> discard_session(ClientId) when is_binary(ClientId) ->
case lookup_channels(ClientId) of case lookup_channels(ClientId) of
[] -> ok; [] -> ok;
@ -259,12 +259,12 @@ with_channel(ClientId, Fun) ->
end. end.
%% @doc Lookup channels. %% @doc Lookup channels.
-spec(lookup_channels(emqx_types:client_id()) -> list(chan_pid())). -spec(lookup_channels(emqx_types:clientid()) -> list(chan_pid())).
lookup_channels(ClientId) -> lookup_channels(ClientId) ->
lookup_channels(global, ClientId). lookup_channels(global, ClientId).
%% @doc Lookup local or global channels. %% @doc Lookup local or global channels.
-spec(lookup_channels(local | global, emqx_types:client_id()) -> list(chan_pid())). -spec(lookup_channels(local | global, emqx_types:clientid()) -> list(chan_pid())).
lookup_channels(global, ClientId) -> lookup_channels(global, ClientId) ->
case emqx_cm_registry:is_enabled() of case emqx_cm_registry:is_enabled() of
true -> true ->

View File

@ -32,11 +32,11 @@
start_link() -> start_link() ->
ekka_locker:start_link(?MODULE). ekka_locker:start_link(?MODULE).
-spec(trans(emqx_types:client_id(), fun(([node()]) -> any())) -> any()). -spec(trans(emqx_types:clientid(), fun(([node()]) -> any())) -> any()).
trans(ClientId, Fun) -> trans(ClientId, Fun) ->
trans(ClientId, Fun, undefined). trans(ClientId, Fun, undefined).
-spec(trans(maybe(emqx_types:client_id()), -spec(trans(maybe(emqx_types:clientid()),
fun(([node()])-> any()), ekka_locker:piggyback()) -> any()). fun(([node()])-> any()), ekka_locker:piggyback()) -> any()).
trans(undefined, Fun, _Piggyback) -> trans(undefined, Fun, _Piggyback) ->
Fun([]); Fun([]);
@ -48,15 +48,15 @@ trans(ClientId, Fun, Piggyback) ->
{error, client_id_unavailable} {error, client_id_unavailable}
end. end.
-spec(lock(emqx_types:client_id()) -> ekka_locker:lock_result()). -spec(lock(emqx_types:clientid()) -> ekka_locker:lock_result()).
lock(ClientId) -> lock(ClientId) ->
ekka_locker:acquire(?MODULE, ClientId, strategy()). ekka_locker:acquire(?MODULE, ClientId, strategy()).
-spec(lock(emqx_types:client_id(), ekka_locker:piggyback()) -> ekka_locker:lock_result()). -spec(lock(emqx_types:clientid(), ekka_locker:piggyback()) -> ekka_locker:lock_result()).
lock(ClientId, Piggyback) -> lock(ClientId, Piggyback) ->
ekka_locker:acquire(?MODULE, ClientId, strategy(), Piggyback). ekka_locker:acquire(?MODULE, ClientId, strategy(), Piggyback).
-spec(unlock(emqx_types:client_id()) -> {boolean(), [node()]}). -spec(unlock(emqx_types:clientid()) -> {boolean(), [node()]}).
unlock(ClientId) -> unlock(ClientId) ->
ekka_locker:release(?MODULE, ClientId, strategy()). ekka_locker:release(?MODULE, ClientId, strategy()).

View File

@ -65,8 +65,8 @@ is_enabled() ->
emqx:get_env(enable_channel_registry, true). emqx:get_env(enable_channel_registry, true).
%% @doc Register a global channel. %% @doc Register a global channel.
-spec(register_channel(emqx_types:client_id() -spec(register_channel(emqx_types:clientid()
| {emqx_types:client_id(), pid()}) -> ok). | {emqx_types:clientid(), pid()}) -> ok).
register_channel(ClientId) when is_binary(ClientId) -> register_channel(ClientId) when is_binary(ClientId) ->
register_channel({ClientId, self()}); register_channel({ClientId, self()});
@ -77,8 +77,8 @@ register_channel({ClientId, ChanPid}) when is_binary(ClientId), is_pid(ChanPid)
end. end.
%% @doc Unregister a global channel. %% @doc Unregister a global channel.
-spec(unregister_channel(emqx_types:client_id() -spec(unregister_channel(emqx_types:clientid()
| {emqx_types:client_id(), pid()}) -> ok). | {emqx_types:clientid(), pid()}) -> ok).
unregister_channel(ClientId) when is_binary(ClientId) -> unregister_channel(ClientId) when is_binary(ClientId) ->
unregister_channel({ClientId, self()}); unregister_channel({ClientId, self()});
@ -89,7 +89,7 @@ unregister_channel({ClientId, ChanPid}) when is_binary(ClientId), is_pid(ChanPid
end. end.
%% @doc Lookup the global channels. %% @doc Lookup the global channels.
-spec(lookup_channels(emqx_types:client_id()) -> list(pid())). -spec(lookup_channels(emqx_types:clientid()) -> list(pid())).
lookup_channels(ClientId) -> lookup_channels(ClientId) ->
[ChanPid || #channel{pid = ChanPid} <- mnesia:dirty_read(?TAB, ClientId)]. [ChanPid || #channel{pid = ChanPid} <- mnesia:dirty_read(?TAB, ClientId)].

View File

@ -30,9 +30,7 @@
%% APIs %% APIs
-export([ info/1 -export([ info/1
, attrs/1
, stats/1 , stats/1
, state/1
]). ]).
-export([call/2]). -export([call/2]).
@ -50,7 +48,7 @@
, terminate/3 , terminate/3
]). ]).
-record(connection, { -record(state, {
%% TCP/TLS Transport %% TCP/TLS Transport
transport :: esockd:transport(), transport :: esockd:transport(),
%% TCP/TLS Socket %% TCP/TLS Socket
@ -63,27 +61,26 @@
active_n :: pos_integer(), active_n :: pos_integer(),
%% The active state %% The active state
active_state :: running | blocked, active_state :: running | blocked,
%% Rate Limit
rate_limit :: maybe(esockd_rate_limit:bucket()),
%% Publish Limit %% Publish Limit
pub_limit :: maybe(esockd_rate_limit:bucket()), pub_limit :: maybe(esockd_rate_limit:bucket()),
%% Rate Limit
rate_limit :: maybe(esockd_rate_limit:bucket()),
%% Limit Timer %% Limit Timer
limit_timer :: maybe(reference()), limit_timer :: maybe(reference()),
%% Parser State %% Parser State
parse_state :: emqx_frame:parse_state(), parse_state :: emqx_frame:parse_state(),
%% Serialize function %% Serialize function
serialize :: fun((emqx_types:packet()) -> iodata()), serialize :: emqx_frame:serialize_fun(),
%% Channel State %% Channel State
chan_state :: emqx_channel:channel() chan_state :: emqx_channel:channel()
}). }).
-type(connection() :: #connection{}). -type(state() :: #state{}).
-define(ACTIVE_N, 100). -define(ACTIVE_N, 100).
-define(HANDLE(T, C, D), handle((T), (C), (D))). -define(HANDLE(T, C, D), handle((T), (C), (D))).
-define(ATTR_KEYS, [socktype, peername, sockname]).
-define(INFO_KEYS, [socktype, peername, sockname, active_n, active_state, -define(INFO_KEYS, [socktype, peername, sockname, active_n, active_state,
rate_limit, pub_limit]). pub_limit, rate_limit]).
-define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]). -define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]).
-define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt, send_pend]). -define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt, send_pend]).
@ -98,64 +95,51 @@ start_link(Transport, Socket, Options) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Get infos of the connection. %% @doc Get infos of the connection.
-spec(info(pid()|connection()) -> emqx_types:infos()). -spec(info(pid()|state()) -> emqx_types:infos()).
info(CPid) when is_pid(CPid) -> info(CPid) when is_pid(CPid) ->
call(CPid, info); call(CPid, info);
info(Conn = #connection{chan_state = ChanState}) -> info(Conn = #state{chan_state = ChanState}) ->
ChanInfo = emqx_channel:info(ChanState), ChanInfo = emqx_channel:info(ChanState),
SockInfo = maps:from_list(info(?INFO_KEYS, Conn)), SockInfo = maps:from_list(info(?INFO_KEYS, Conn)),
maps:merge(ChanInfo, #{sockinfo => SockInfo}). maps:merge(ChanInfo, #{sockinfo => SockInfo}).
info(Keys, Conn) when is_list(Keys) -> info(Keys, Conn) when is_list(Keys) ->
[{Key, info(Key, Conn)} || Key <- Keys]; [{Key, info(Key, Conn)} || Key <- Keys];
info(socktype, #connection{transport = Transport, socket = Socket}) -> info(socktype, #state{transport = Transport, socket = Socket}) ->
Transport:type(Socket); Transport:type(Socket);
info(peername, #connection{peername = Peername}) -> info(peername, #state{peername = Peername}) ->
Peername; Peername;
info(sockname, #connection{sockname = Sockname}) -> info(sockname, #state{sockname = Sockname}) ->
Sockname; Sockname;
info(active_n, #connection{active_n = ActiveN}) -> info(active_n, #state{active_n = ActiveN}) ->
ActiveN; ActiveN;
info(active_state, #connection{active_state = ActiveSt}) -> info(active_state, #state{active_state = ActiveSt}) ->
ActiveSt; ActiveSt;
info(rate_limit, #connection{rate_limit = RateLimit}) -> info(pub_limit, #state{pub_limit = PubLimit}) ->
limit_info(RateLimit);
info(pub_limit, #connection{pub_limit = PubLimit}) ->
limit_info(PubLimit); limit_info(PubLimit);
info(chan_state, #connection{chan_state = ChanState}) -> info(rate_limit, #state{rate_limit = RateLimit}) ->
limit_info(RateLimit);
info(chan_state, #state{chan_state = ChanState}) ->
emqx_channel:info(ChanState). emqx_channel:info(ChanState).
limit_info(Limit) -> limit_info(Limit) ->
emqx_misc:maybe_apply(fun esockd_rate_limit:info/1, Limit). emqx_misc:maybe_apply(fun esockd_rate_limit:info/1, Limit).
%% @doc Get attrs of the connection.
-spec(attrs(pid()|connection()) -> emqx_types:attrs()).
attrs(CPid) when is_pid(CPid) ->
call(CPid, attrs);
attrs(Conn = #connection{chan_state = ChanState}) ->
ChanAttrs = emqx_channel:attrs(ChanState),
SockAttrs = maps:from_list(info(?ATTR_KEYS, Conn)),
maps:merge(ChanAttrs, #{sockinfo => SockAttrs}).
%% @doc Get stats of the channel. %% @doc Get stats of the channel.
-spec(stats(pid()|connection()) -> emqx_types:stats()). -spec(stats(pid()|state()) -> emqx_types:stats()).
stats(CPid) when is_pid(CPid) -> stats(CPid) when is_pid(CPid) ->
call(CPid, stats); call(CPid, stats);
stats(#connection{transport = Transport, stats(#state{transport = Transport,
socket = Socket, socket = Socket,
chan_state = ChanState}) -> chan_state = ChanState}) ->
ProcStats = emqx_misc:proc_stats(),
SockStats = case Transport:getstat(Socket, ?SOCK_STATS) of SockStats = case Transport:getstat(Socket, ?SOCK_STATS) of
{ok, Ss} -> Ss; {ok, Ss} -> Ss;
{error, _} -> [] {error, _} -> []
end, end,
ConnStats = [{Name, emqx_pd:get_counter(Name)} || Name <- ?CONN_STATS], ConnStats = emqx_pd:get_counters(?CONN_STATS),
ChanStats = emqx_channel:stats(ChanState), ChanStats = emqx_channel:stats(ChanState),
lists:append([ProcStats, SockStats, ConnStats, ChanStats]). ProcStats = emqx_misc:proc_stats(),
lists:append([SockStats, ConnStats, ChanStats, ProcStats]).
%% For debug
-spec(state(pid()) -> connection()).
state(CPid) -> call(CPid, state).
%% kick|discard|takeover %% kick|discard|takeover
-spec(call(pid(), Req :: term()) -> Reply :: term()). -spec(call(pid(), Req :: term()) -> Reply :: term()).
@ -170,38 +154,43 @@ init({Transport, RawSocket, Options}) ->
{ok, Peername} = Transport:ensure_ok_or_exit(peername, [Socket]), {ok, Peername} = Transport:ensure_ok_or_exit(peername, [Socket]),
{ok, Sockname} = Transport:ensure_ok_or_exit(sockname, [Socket]), {ok, Sockname} = Transport:ensure_ok_or_exit(sockname, [Socket]),
Peercert = Transport:ensure_ok_or_exit(peercert, [Socket]), Peercert = Transport:ensure_ok_or_exit(peercert, [Socket]),
ConnInfo = #{socktype => Transport:type(Socket),
peername => Peername,
sockname => Sockname,
peercert => Peercert,
conn_mod => ?MODULE
},
emqx_logger:set_metadata_peername(esockd_net:format(Peername)), emqx_logger:set_metadata_peername(esockd_net:format(Peername)),
Zone = proplists:get_value(zone, Options), Zone = proplists:get_value(zone, Options),
RateLimit = init_limiter(proplists:get_value(rate_limit, Options)),
PubLimit = init_limiter(emqx_zone:get_env(Zone, publish_limit)),
ActiveN = proplists:get_value(active_n, Options, ?ACTIVE_N), ActiveN = proplists:get_value(active_n, Options, ?ACTIVE_N),
MaxSize = emqx_zone:get_env(Zone, max_packet_size, ?MAX_PACKET_SIZE), PubLimit = init_limiter(emqx_zone:get_env(Zone, publish_limit)),
ParseState = emqx_frame:initial_parse_state(#{max_size => MaxSize}), RateLimit = init_limiter(proplists:get_value(rate_limit, Options)),
ChanState = emqx_channel:init(#{peername => Peername, FrameOpts = emqx_zone:frame_options(Zone),
sockname => Sockname, ParseState = emqx_frame:initial_parse_state(FrameOpts),
peercert => Peercert, Serialize = emqx_frame:serialize_fun(),
protocol => mqtt, ChanState = emqx_channel:init(ConnInfo, Options),
conn_mod => ?MODULE}, Options), State = #state{transport = Transport,
socket = Socket,
peername = Peername,
sockname = Sockname,
active_n = ActiveN,
active_state = running,
pub_limit = PubLimit,
rate_limit = RateLimit,
parse_state = ParseState,
serialize = Serialize,
chan_state = ChanState
},
IdleTimout = emqx_zone:get_env(Zone, idle_timeout, 30000), IdleTimout = emqx_zone:get_env(Zone, idle_timeout, 30000),
State = #connection{transport = Transport,
socket = Socket,
peername = Peername,
sockname = Sockname,
active_n = ActiveN,
active_state = running,
rate_limit = RateLimit,
pub_limit = PubLimit,
parse_state = ParseState,
chan_state = ChanState,
serialize = serialize_fun(?MQTT_PROTO_V5, undefined)
},
gen_statem:enter_loop(?MODULE, [{hibernate_after, 2 * IdleTimout}], gen_statem:enter_loop(?MODULE, [{hibernate_after, 2 * IdleTimout}],
idle, State, self(), [IdleTimout]). idle, State, self(), [IdleTimout]).
-compile({inline, [init_limiter/1]}).
init_limiter(undefined) -> undefined; init_limiter(undefined) -> undefined;
init_limiter({Rate, Burst}) -> init_limiter({Rate, Burst}) ->
esockd_rate_limit:new(Rate, Burst). esockd_rate_limit:new(Rate, Burst).
-compile({inline, [callback_mode/0]}).
callback_mode() -> callback_mode() ->
[state_functions, state_enter]. [state_functions, state_enter].
@ -219,18 +208,17 @@ idle(timeout, _Timeout, State) ->
shutdown(idle_timeout, State); shutdown(idle_timeout, State);
idle(cast, {incoming, Packet = ?CONNECT_PACKET(ConnPkt)}, State) -> idle(cast, {incoming, Packet = ?CONNECT_PACKET(ConnPkt)}, State) ->
#mqtt_packet_connect{proto_ver = ProtoVer, properties = Properties} = ConnPkt,
MaxPacketSize = emqx_mqtt_props:get('Maximum-Packet-Size', Properties, undefined),
NState = State#connection{serialize = serialize_fun(ProtoVer, MaxPacketSize)},
SuccFun = fun(NewSt) -> {next_state, connected, NewSt} end, SuccFun = fun(NewSt) -> {next_state, connected, NewSt} end,
Serialize = emqx_frame:serialize_fun(ConnPkt),
NState = State#state{serialize = Serialize},
handle_incoming(Packet, SuccFun, NState); handle_incoming(Packet, SuccFun, NState);
idle(cast, {incoming, Packet}, State) when is_record(Packet, mqtt_packet) -> idle(cast, {incoming, Packet}, State) when is_record(Packet, mqtt_packet) ->
?LOG(warning, "Unexpected incoming: ~p", [Packet]), SuccFun = fun(NewSt) -> {next_state, connected, NewSt} end,
shutdown(unexpected_incoming_packet, State); handle_incoming(Packet, SuccFun, State);
idle(cast, {incoming, {error, Reason}}, State) -> idle(cast, {incoming, FrameError = {frame_error, _Reason}}, State) ->
shutdown(Reason, State); handle_incoming(FrameError, State);
idle(EventType, Content, State) -> idle(EventType, Content, State) ->
?HANDLE(EventType, Content, State). ?HANDLE(EventType, Content, State).
@ -245,16 +233,8 @@ connected(enter, _PrevSt, State) ->
connected(cast, {incoming, Packet}, State) when is_record(Packet, mqtt_packet) -> connected(cast, {incoming, Packet}, State) when is_record(Packet, mqtt_packet) ->
handle_incoming(Packet, fun keep_state/1, State); handle_incoming(Packet, fun keep_state/1, State);
connected(cast, {incoming, {error, Reason}}, State = #connection{chan_state = ChanState}) -> connected(cast, {incoming, FrameError = {frame_error, _Reason}}, State) ->
case emqx_channel:handle_out({disconnect, emqx_reason_codes:mqtt_frame_error(Reason)}, ChanState) of handle_incoming(FrameError, State);
{wait_session_expire, _, NChanState} ->
?LOG(debug, "Disconnect and wait for session to expire due to ~p", [Reason]),
{next_state, disconnected, State#connection{chan_state= NChanState}};
{wait_session_expire, _, OutPackets, NChanState} ->
?LOG(debug, "Disconnect and wait for session to expire due to ~p", [Reason]),
NState = State#connection{chan_state= NChanState},
{next_state, disconnected, handle_outgoing(OutPackets, fun(NewSt) -> NewSt end, NState)}
end;
connected(info, Deliver = {deliver, _Topic, _Msg}, State) -> connected(info, Deliver = {deliver, _Topic, _Msg}, State) ->
handle_deliver(emqx_misc:drain_deliver([Deliver]), State); handle_deliver(emqx_misc:drain_deliver([Deliver]), State);
@ -265,13 +245,13 @@ connected(EventType, Content, State) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Disconnected State %% Disconnected State
disconnected(enter, _, State = #connection{chan_state = ChanState}) -> disconnected(enter, _, State = #state{chan_state = ChanState}) ->
case emqx_channel:handle_info(disconnected, ChanState) of case emqx_channel:handle_info(disconnected, ChanState) of
{ok, NChanState} -> {ok, NChanState} ->
ok = register_self(State#connection{chan_state = NChanState}), ok = register_self(State#state{chan_state = NChanState}),
keep_state(State#connection{chan_state = NChanState}); keep_state(State#state{chan_state = NChanState});
{stop, Reason, NChanState} -> {stop, Reason, NChanState} ->
stop(Reason, State#connection{chan_state = NChanState}) stop(Reason, State#state{chan_state = NChanState})
end; end;
disconnected(info, Deliver = {deliver, _Topic, _Msg}, State) -> disconnected(info, Deliver = {deliver, _Topic, _Msg}, State) ->
@ -286,51 +266,49 @@ disconnected(EventType, Content, State) ->
handle({call, From}, info, State) -> handle({call, From}, info, State) ->
reply(From, info(State), State); reply(From, info(State), State);
handle({call, From}, attrs, State) ->
reply(From, attrs(State), State);
handle({call, From}, stats, State) -> handle({call, From}, stats, State) ->
reply(From, stats(State), State); reply(From, stats(State), State);
handle({call, From}, state, State) -> handle({call, From}, state, State) ->
reply(From, State, State); reply(From, State, State);
handle({call, From}, Req, State = #connection{chan_state = ChanState}) -> handle({call, From}, Req, State = #state{chan_state = ChanState}) ->
case emqx_channel:handle_call(Req, ChanState) of case emqx_channel:handle_call(Req, ChanState) of
{ok, Reply, NChanState} -> {ok, Reply, NChanState} ->
reply(From, Reply, State#connection{chan_state = NChanState}); reply(From, Reply, State#state{chan_state = NChanState});
{stop, Reason, Reply, NChanState} -> {stop, Reason, Reply, NChanState} ->
ok = gen_statem:reply(From, Reply), ok = gen_statem:reply(From, Reply),
stop(Reason, State#connection{chan_state = NChanState}); stop(Reason, State#state{chan_state = NChanState});
{stop, Reason, Packet, Reply, NChanState} -> {stop, Reason, Packet, Reply, NChanState} ->
handle_outgoing(Packet, fun (_) -> ok end, State#connection{chan_state = NChanState}), handle_outgoing(Packet, State#state{chan_state = NChanState}),
ok = gen_statem:reply(From, Reply), ok = gen_statem:reply(From, Reply),
stop(Reason, State#connection{chan_state = NChanState}) stop(Reason, State#state{chan_state = NChanState})
end; end;
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Handle cast %% Handle cast
handle(cast, Msg, State = #connection{chan_state = ChanState}) -> handle(cast, Msg, State = #state{chan_state = ChanState}) ->
case emqx_channel:handle_cast(Msg, ChanState) of case emqx_channel:handle_info(Msg, ChanState) of
ok -> {ok, State};
{ok, NChanState} -> {ok, NChanState} ->
keep_state(State#connection{chan_state = NChanState}); keep_state(State#state{chan_state = NChanState});
{stop, Reason, NChanState} -> {stop, Reason, NChanState} ->
stop(Reason, State#connection{chan_state = NChanState}) stop(Reason, State#state{chan_state = NChanState})
end; end;
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Handle info %% Handle info
%% Handle incoming data %% Handle incoming data
handle(info, {Inet, _Sock, Data}, State = #connection{chan_state = ChanState}) handle(info, {Inet, _Sock, Data}, State = #state{chan_state = ChanState})
when Inet == tcp; Inet == ssl -> when Inet == tcp; Inet == ssl ->
?LOG(debug, "RECV ~p", [Data]), ?LOG(debug, "RECV ~p", [Data]),
Oct = iolist_size(Data), Oct = iolist_size(Data),
emqx_pd:update_counter(incoming_bytes, Oct), emqx_pd:update_counter(incoming_bytes, Oct),
ok = emqx_metrics:inc('bytes.received', Oct), ok = emqx_metrics:inc('bytes.received', Oct),
NChanState = emqx_channel:received(Oct, ChanState), NChanState = emqx_channel:received(Oct, ChanState),
NState = State#connection{chan_state = NChanState}, NState = State#state{chan_state = NChanState},
process_incoming(Data, NState); process_incoming(Data, NState);
handle(info, {Error, _Sock, Reason}, State) handle(info, {Error, _Sock, Reason}, State)
@ -353,9 +331,9 @@ handle(info, {Passive, _Sock}, State)
handle(info, activate_socket, State) -> handle(info, activate_socket, State) ->
%% Rate limit timer expired. %% Rate limit timer expired.
NState = State#connection{active_state = running, NState = State#state{active_state = running,
limit_timer = undefined limit_timer = undefined
}, },
case activate_socket(NState) of case activate_socket(NState) of
ok -> keep_state(NState); ok -> keep_state(NState);
{error, Reason} -> {error, Reason} ->
@ -370,7 +348,7 @@ handle(info, {inet_reply, _Sock, {error, Reason}}, State) ->
shutdown(Reason, State); shutdown(Reason, State);
handle(info, {timeout, TRef, keepalive}, handle(info, {timeout, TRef, keepalive},
State = #connection{transport = Transport, socket = Socket}) -> State = #state{transport = Transport, socket = Socket}) ->
case Transport:getstat(Socket, [recv_oct]) of case Transport:getstat(Socket, [recv_oct]) of
{ok, [{recv_oct, RecvOct}]} -> {ok, [{recv_oct, RecvOct}]} ->
handle_timeout(TRef, {keepalive, RecvOct}, State); handle_timeout(TRef, {keepalive, RecvOct}, State);
@ -387,21 +365,21 @@ handle(info, {timeout, TRef, Msg}, State) ->
handle(info, {shutdown, Reason}, State) -> handle(info, {shutdown, Reason}, State) ->
shutdown(Reason, State); shutdown(Reason, State);
handle(info, Info, State = #connection{chan_state = ChanState}) -> handle(info, Info, State = #state{chan_state = ChanState}) ->
case emqx_channel:handle_info(Info, ChanState) of case emqx_channel:handle_info(Info, ChanState) of
{ok, NChanState} -> {ok, NChanState} ->
keep_state(State#connection{chan_state = NChanState}); keep_state(State#state{chan_state = NChanState});
{stop, Reason, NChanState} -> {stop, Reason, NChanState} ->
stop(Reason, State#connection{chan_state = NChanState}) stop(Reason, State#state{chan_state = NChanState})
end. end.
code_change(_Vsn, State, Data, _Extra) -> code_change(_Vsn, State, Data, _Extra) ->
{ok, State, Data}. {ok, State, Data}.
terminate(Reason, _StateName, State) -> terminate(Reason, _StateName, #state{transport = Transport,
#connection{transport = Transport, socket = Socket,
socket = Socket, chan_state = ChanState
chan_state = ChanState} = State, }) ->
?LOG(debug, "Terminated for ~p", [Reason]), ?LOG(debug, "Terminated for ~p", [Reason]),
ok = Transport:fast_close(Socket), ok = Transport:fast_close(Socket),
emqx_channel:terminate(Reason, ChanState). emqx_channel:terminate(Reason, ChanState).
@ -410,8 +388,16 @@ terminate(Reason, _StateName, State) ->
%% Internal functions %% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
register_self(State = #connection{chan_state = ChanState}) -> register_self(State = #state{active_n = ActiveN,
emqx_channel:handle_cast({register, attrs(State), stats(State)}, ChanState). active_state = ActiveSt,
chan_state = ChanState
}) ->
ChanAttrs = emqx_channel:attrs(ChanState),
SockAttrs = #{active_n => ActiveN,
active_state => ActiveSt
},
Attrs = maps:merge(ChanAttrs, #{sockinfo => SockAttrs}),
emqx_channel:handle_info({register, Attrs, stats(State)}, ChanState).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Process incoming data %% Process incoming data
@ -421,107 +407,114 @@ process_incoming(Data, State) ->
process_incoming(Data, [], State). process_incoming(Data, [], State).
process_incoming(<<>>, Packets, State) -> process_incoming(<<>>, Packets, State) ->
{keep_state, State, next_incoming_events(Packets)}; keep_state(State, next_incoming_events(Packets));
process_incoming(Data, Packets, State = #connection{parse_state = ParseState}) -> process_incoming(Data, Packets, State = #state{parse_state = ParseState}) ->
try emqx_frame:parse(Data, ParseState) of try emqx_frame:parse(Data, ParseState) of
{more, NParseState} -> {more, NParseState} ->
NState = State#connection{parse_state = NParseState}, NState = State#state{parse_state = NParseState},
{keep_state, NState, next_incoming_events(Packets)}; keep_state(NState, next_incoming_events(Packets));
{ok, Packet, Rest, NParseState} -> {ok, Packet, Rest, NParseState} ->
NState = State#connection{parse_state = NParseState}, NState = State#state{parse_state = NParseState},
process_incoming(Rest, [Packet|Packets], NState); process_incoming(Rest, [Packet|Packets], NState)
{error, Reason} ->
{keep_state, State, next_incoming_events({error, Reason})}
catch catch
error:Reason:Stk -> error:Reason:Stk ->
?LOG(error, "~nParse failed for ~p~nStacktrace: ~p~nError data:~p", [Reason, Stk, Data]), ?LOG(error, "~nParse failed for ~p~nStacktrace: ~p~nFrame data:~p",
{keep_state, State, next_incoming_events({error, Reason})} [Reason, Stk, Data]),
keep_state(State, next_incoming_events(Packets++[{frame_error, Reason}]))
end. end.
-compile({inline, [next_incoming_events/1]}). -compile({inline, [next_incoming_events/1]}).
next_incoming_events({error, Reason}) -> next_incoming_events([]) -> [];
[next_event(cast, {incoming, {error, Reason}})];
next_incoming_events(Packets) -> next_incoming_events(Packets) ->
[next_event(cast, {incoming, Packet}) || Packet <- Packets]. [next_event(cast, {incoming, Packet}) || Packet <- Packets].
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Handle incoming packet %% Handle incoming packet
handle_incoming(Packet = ?PACKET(Type), SuccFun, handle_incoming(Packet = ?PACKET(Type), SuccFun, State = #state{chan_state = ChanState}) ->
State = #connection{chan_state = ChanState}) ->
_ = inc_incoming_stats(Type), _ = inc_incoming_stats(Type),
ok = emqx_metrics:inc_recv(Packet), _ = emqx_metrics:inc_recv(Packet),
?LOG(debug, "RECV ~s", [emqx_packet:format(Packet)]), ?LOG(debug, "RECV ~s", [emqx_packet:format(Packet)]),
case emqx_channel:handle_in(Packet, ChanState) of case emqx_channel:handle_in(Packet, ChanState) of
{ok, NChanState} -> {ok, NChanState} ->
SuccFun(State#connection{chan_state= NChanState}); SuccFun(State#state{chan_state= NChanState});
{ok, OutPackets, NChanState} -> {ok, OutPackets, NChanState} ->
handle_outgoing(OutPackets, SuccFun, State#connection{chan_state = NChanState}); NState = State#state{chan_state = NChanState},
{wait_session_expire, Reason, NChanState} -> handle_outgoing(OutPackets, SuccFun, NState);
?LOG(debug, "Disconnect and wait for session to expire due to ~p", [Reason]), {close, Reason, NChanState} ->
{next_state, disconnected, State#connection{chan_state = NChanState}}; close(Reason, State#state{chan_state = NChanState});
{wait_session_expire, Reason, OutPackets, NChanState} -> {close, Reason, OutPackets, NChanState} ->
?LOG(debug, "Disconnect and wait for session to expire due to ~p", [Reason]), NState = State#state{chan_state= NChanState},
NState = State#connection{chan_state= NChanState}, close(Reason, handle_outgoing(OutPackets, fun(NewSt) -> NewSt end, NState));
{next_state, disconnected, handle_outgoing(OutPackets, fun(NewSt) -> NewSt end, NState)};
{stop, Reason, NChanState} -> {stop, Reason, NChanState} ->
stop(Reason, State#connection{chan_state = NChanState}); stop(Reason, State#state{chan_state = NChanState});
{stop, Reason, OutPackets, NChanState} -> {stop, Reason, OutPackets, NChanState} ->
NState = State#connection{chan_state= NChanState}, NState = State#state{chan_state= NChanState},
stop(Reason, handle_outgoing(OutPackets, fun(NewSt) -> NewSt end, NState))
end.
handle_incoming(FrameError = {frame_error, _Reason}, State = #state{chan_state = ChanState}) ->
case emqx_channel:handle_in(FrameError, ChanState) of
{close, Reason, NChanState} ->
close(Reason, State#state{chan_state = NChanState});
{close, Reason, OutPackets, NChanState} ->
NState = State#state{chan_state= NChanState},
close(Reason, handle_outgoing(OutPackets, fun(NewSt) -> NewSt end, NState));
{stop, Reason, NChanState} ->
stop(Reason, State#state{chan_state = NChanState});
{stop, Reason, OutPackets, NChanState} ->
NState = State#state{chan_state= NChanState},
stop(Reason, handle_outgoing(OutPackets, fun(NewSt) -> NewSt end, NState)) stop(Reason, handle_outgoing(OutPackets, fun(NewSt) -> NewSt end, NState))
end. end.
%%------------------------------------------------------------------- %%-------------------------------------------------------------------
%% Handle deliver %% Handle deliver
handle_deliver(Delivers, State = #connection{chan_state = ChanState}) -> handle_deliver(Delivers, State = #state{chan_state = ChanState}) ->
case emqx_channel:handle_out({deliver, Delivers}, ChanState) of case emqx_channel:handle_out({deliver, Delivers}, ChanState) of
{ok, NChanState} -> {ok, NChanState} ->
keep_state(State#connection{chan_state = NChanState}); keep_state(State#state{chan_state = NChanState});
{ok, Packets, NChanState} -> {ok, Packets, NChanState} ->
handle_outgoing(Packets, fun keep_state/1, State#connection{chan_state = NChanState}) handle_outgoing(Packets, fun keep_state/1, State#state{chan_state = NChanState})
end. end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Handle outgoing packets %% Handle outgoing packets
handle_outgoing(Packets, SuccFun, State = #connection{serialize = Serialize}) handle_outgoing(Packet, State) ->
when is_list(Packets) -> handle_outgoing(Packet, fun (_) -> ok end, State).
send(lists:map(Serialize, Packets), SuccFun, State);
handle_outgoing(Packet, SuccFun, State = #connection{serialize = Serialize}) -> handle_outgoing(Packets, SuccFun, State) when is_list(Packets) ->
send(Serialize(Packet), SuccFun, State). send(lists:map(serialize_and_inc_stats_fun(State), Packets), SuccFun, State);
%%-------------------------------------------------------------------- handle_outgoing(Packet, SuccFun, State) ->
%% Serialize fun send((serialize_and_inc_stats_fun(State))(Packet), SuccFun, State).
serialize_fun(ProtoVer, MaxPacketSize) -> serialize_and_inc_stats_fun(#state{serialize = Serialize}) ->
fun(Packet = ?PACKET(Type)) -> fun(Packet = ?PACKET(Type)) ->
IoData = emqx_frame:serialize(Packet, ProtoVer), case Serialize(Packet) of
case Type =/= ?PUBLISH orelse MaxPacketSize =:= undefined orelse iolist_size(IoData) =< MaxPacketSize of <<>> -> ?LOG(warning, "~s is discarded due to the frame is too large!",
true -> [emqx_packet:format(Packet)]),
?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]), <<>>;
_ = inc_outgoing_stats(Type), Data -> _ = inc_outgoing_stats(Type),
_ = emqx_metrics:inc_sent(Packet), _ = emqx_metrics:inc_sent(Packet),
IoData; ?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]),
false -> Data
?LOG(warning, "DROP ~s due to oversize packet size", [emqx_packet:format(Packet)]),
<<"">>
end end
end. end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Send data %% Send data
send(IoData, SuccFun, State = #connection{transport = Transport, send(IoData, SuccFun, State = #state{transport = Transport,
socket = Socket, socket = Socket,
chan_state = ChanState}) -> chan_state = ChanState}) ->
Oct = iolist_size(IoData), Oct = iolist_size(IoData),
ok = emqx_metrics:inc('bytes.sent', Oct), ok = emqx_metrics:inc('bytes.sent', Oct),
case Transport:async_send(Socket, IoData) of case Transport:async_send(Socket, IoData) of
ok -> NChanState = emqx_channel:sent(Oct, ChanState), ok -> NChanState = emqx_channel:sent(Oct, ChanState),
SuccFun(State#connection{chan_state = NChanState}); SuccFun(State#state{chan_state = NChanState});
{error, Reason} -> {error, Reason} ->
shutdown(Reason, State) shutdown(Reason, State)
end. end.
@ -529,17 +522,22 @@ send(IoData, SuccFun, State = #connection{transport = Transport,
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Handle timeout %% Handle timeout
handle_timeout(TRef, Msg, State = #connection{chan_state = ChanState}) -> handle_timeout(TRef, Msg, State = #state{chan_state = ChanState}) ->
case emqx_channel:handle_timeout(TRef, Msg, ChanState) of case emqx_channel:handle_timeout(TRef, Msg, ChanState) of
{ok, NChanState} -> {ok, NChanState} ->
keep_state(State#connection{chan_state = NChanState}); keep_state(State#state{chan_state = NChanState});
{ok, Packets, NChanState} -> {ok, Packets, NChanState} ->
handle_outgoing(Packets, fun keep_state/1, State#connection{chan_state = NChanState}); handle_outgoing(Packets, fun keep_state/1, State#state{chan_state = NChanState});
{wait_session_expire, Reason, NChanState} -> {close, Reason, NChanState} ->
?LOG(debug, "Disconnect and wait for session to expire due to ~p", [Reason]), close(Reason, State#state{chan_state = NChanState});
{next_state, disconnected, State#connection{chan_state = NChanState}}; {close, Reason, OutPackets, NChanState} ->
NState = State#state{chan_state= NChanState},
close(Reason, handle_outgoing(OutPackets, fun(NewSt) -> NewSt end, NState));
{stop, Reason, NChanState} -> {stop, Reason, NChanState} ->
stop(Reason, State#connection{chan_state = NChanState}) stop(Reason, State#state{chan_state = NChanState});
{stop, Reason, OutPackets, NChanState} ->
NState = State#state{chan_state= NChanState},
stop(Reason, handle_outgoing(OutPackets, fun(NewSt) -> NewSt end, NState))
end. end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -547,11 +545,11 @@ handle_timeout(TRef, Msg, State = #connection{chan_state = ChanState}) ->
-define(ENABLED(Rl), (Rl =/= undefined)). -define(ENABLED(Rl), (Rl =/= undefined)).
ensure_rate_limit(State = #connection{rate_limit = Rl, pub_limit = Pl}) -> ensure_rate_limit(State = #state{rate_limit = Rl, pub_limit = Pl}) ->
Pubs = emqx_pd:reset_counter(incoming_pubs), Pubs = emqx_pd:reset_counter(incoming_pubs),
Bytes = emqx_pd:reset_counter(incoming_bytes), Bytes = emqx_pd:reset_counter(incoming_bytes),
Limiters = [{Pl, #connection.pub_limit, Pubs} || ?ENABLED(Pl)] ++ Limiters = [{Pl, #state.pub_limit, Pubs} || ?ENABLED(Pl)] ++
[{Rl, #connection.rate_limit, Bytes} || ?ENABLED(Rl)], [{Rl, #state.rate_limit, Bytes} || ?ENABLED(Rl)],
ensure_rate_limit(Limiters, State). ensure_rate_limit(Limiters, State).
ensure_rate_limit([], State) -> ensure_rate_limit([], State) ->
@ -563,30 +561,27 @@ ensure_rate_limit([{Rl, Pos, Cnt}|Limiters], State) ->
{Pause, Rl1} -> {Pause, Rl1} ->
?LOG(debug, "Pause ~pms due to rate limit", [Pause]), ?LOG(debug, "Pause ~pms due to rate limit", [Pause]),
TRef = erlang:send_after(Pause, self(), activate_socket), TRef = erlang:send_after(Pause, self(), activate_socket),
NState = State#connection{active_state = blocked, NState = State#state{active_state = blocked,
limit_timer = TRef}, limit_timer = TRef
},
setelement(Pos, NState, Rl1) setelement(Pos, NState, Rl1)
end. end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Activate Socket %% Activate Socket
activate_socket(#connection{active_state = blocked}) -> -compile({inline, [activate_socket/1]}).
ok; activate_socket(#state{active_state = blocked}) -> ok;
activate_socket(#connection{transport = Transport, activate_socket(#state{transport = Transport,
socket = Socket, socket = Socket,
active_n = N}) -> active_n = N}) ->
Transport:setopts(Socket, [{active, N}]). Transport:setopts(Socket, [{active, N}]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Inc incoming/outgoing stats %% Inc incoming/outgoing stats
-compile({inline, -compile({inline, [inc_incoming_stats/1]}).
[ inc_incoming_stats/1 inc_incoming_stats(Type) when is_integer(Type) ->
, inc_outgoing_stats/1
]}).
inc_incoming_stats(Type) ->
emqx_pd:update_counter(recv_pkt, 1), emqx_pd:update_counter(recv_pkt, 1),
if if
Type == ?PUBLISH -> Type == ?PUBLISH ->
@ -595,6 +590,7 @@ inc_incoming_stats(Type) ->
true -> ok true -> ok
end. end.
-compile({inline, [inc_outgoing_stats/1]}).
inc_outgoing_stats(Type) -> inc_outgoing_stats(Type) ->
emqx_pd:update_counter(send_pkt, 1), emqx_pd:update_counter(send_pkt, 1),
(Type == ?PUBLISH) (Type == ?PUBLISH)
@ -606,6 +602,7 @@ inc_outgoing_stats(Type) ->
-compile({inline, -compile({inline,
[ reply/3 [ reply/3
, keep_state/1 , keep_state/1
, keep_state/2
, next_event/2 , next_event/2
, shutdown/2 , shutdown/2
, stop/2 , stop/2
@ -617,9 +614,17 @@ reply(From, Reply, State) ->
keep_state(State) -> keep_state(State) ->
{keep_state, State}. {keep_state, State}.
keep_state(State, Events) ->
{keep_state, State, Events}.
next_event(Type, Content) -> next_event(Type, Content) ->
{next_event, Type, Content}. {next_event, Type, Content}.
close(Reason, State = #state{transport = Transport, socket = Socket}) ->
?LOG(warning, "Closed for ~p", [Reason]),
ok = Transport:fast_close(Socket),
{next_state, disconnected, State}.
shutdown(Reason, State) -> shutdown(Reason, State) ->
stop({shutdown, Reason}, State). stop({shutdown, Reason}, State).

View File

@ -51,7 +51,7 @@
}). }).
-record(flapping, { -record(flapping, {
client_id :: emqx_types:client_id(), clientid :: emqx_types:clientid(),
peerhost :: emqx_types:peerhost(), peerhost :: emqx_types:peerhost(),
started_at :: pos_integer(), started_at :: pos_integer(),
detect_cnt :: pos_integer(), detect_cnt :: pos_integer(),
@ -69,8 +69,8 @@ start_link() ->
stop() -> gen_server:stop(?MODULE). stop() -> gen_server:stop(?MODULE).
%% @doc Check flapping when a MQTT client connected. %% @doc Check flapping when a MQTT client connected.
-spec(check(emqx_types:client()) -> boolean()). -spec(check(emqx_types:clientinfo()) -> boolean()).
check(#{client_id := ClientId}) -> check(#{clientid := ClientId}) ->
check(ClientId, get_policy()). check(ClientId, get_policy()).
check(ClientId, #{banned_interval := Interval}) -> check(ClientId, #{banned_interval := Interval}) ->
@ -81,10 +81,10 @@ check(ClientId, #{banned_interval := Interval}) ->
end. end.
%% @doc Detect flapping when a MQTT client disconnected. %% @doc Detect flapping when a MQTT client disconnected.
-spec(detect(emqx_types:client()) -> boolean()). -spec(detect(emqx_types:clientinfo()) -> boolean()).
detect(Client) -> detect(Client, get_policy()). detect(Client) -> detect(Client, get_policy()).
detect(#{client_id := ClientId, peerhost := PeerHost}, detect(#{clientid := ClientId, peerhost := PeerHost},
Policy = #{threshold := Threshold}) -> Policy = #{threshold := Threshold}) ->
try ets:update_counter(?FLAPPING_TAB, ClientId, {#flapping.detect_cnt, 1}) of try ets:update_counter(?FLAPPING_TAB, ClientId, {#flapping.detect_cnt, 1}) of
Cnt when Cnt < Threshold -> false; Cnt when Cnt < Threshold -> false;
@ -97,7 +97,7 @@ detect(#{client_id := ClientId, peerhost := PeerHost},
catch catch
error:badarg -> error:badarg ->
%% Create a flapping record. %% Create a flapping record.
Flapping = #flapping{client_id = ClientId, Flapping = #flapping{clientid = ClientId,
peerhost = PeerHost, peerhost = PeerHost,
started_at = emqx_time:now_ms(), started_at = emqx_time:now_ms(),
detect_cnt = 1 detect_cnt = 1
@ -131,7 +131,7 @@ handle_call(Req, _From, State) ->
?LOG(error, "Unexpected call: ~p", [Req]), ?LOG(error, "Unexpected call: ~p", [Req]),
{reply, ignored, State}. {reply, ignored, State}.
handle_cast({detected, Flapping = #flapping{client_id = ClientId, handle_cast({detected, Flapping = #flapping{clientid = ClientId,
peerhost = PeerHost, peerhost = PeerHost,
started_at = StartedAt, started_at = StartedAt,
detect_cnt = DetectCnt}, detect_cnt = DetectCnt},
@ -142,7 +142,7 @@ handle_cast({detected, Flapping = #flapping{client_id = ClientId,
?LOG(error, "Flapping detected: ~s(~s) disconnected ~w times in ~wms", ?LOG(error, "Flapping detected: ~s(~s) disconnected ~w times in ~wms",
[ClientId, esockd_net:ntoa(PeerHost), DetectCnt, Duration]), [ClientId, esockd_net:ntoa(PeerHost), DetectCnt, Duration]),
%% Banned. %% Banned.
BannedFlapping = Flapping#flapping{client_id = {banned, ClientId}, BannedFlapping = Flapping#flapping{clientid = {banned, ClientId},
banned_at = emqx_time:now_ms() banned_at = emqx_time:now_ms()
}, },
alarm_handler:set_alarm({{flapping_detected, ClientId}, BannedFlapping}), alarm_handler:set_alarm({{flapping_detected, ClientId}, BannedFlapping}),
@ -192,11 +192,11 @@ expire_flapping(NowTime, #{duration := Duration, banned_interval := Interval}) -
case ets:select(?FLAPPING_TAB, case ets:select(?FLAPPING_TAB,
[{#flapping{started_at = '$1', banned_at = undefined, _ = '_'}, [{#flapping{started_at = '$1', banned_at = undefined, _ = '_'},
[{'<', '$1', NowTime-Duration}], ['$_']}, [{'<', '$1', NowTime-Duration}], ['$_']},
{#flapping{client_id = {banned, '_'}, banned_at = '$1', _ = '_'}, {#flapping{clientid = {banned, '_'}, banned_at = '$1', _ = '_'},
[{'<', '$1', NowTime-Interval}], ['$_']}]) of [{'<', '$1', NowTime-Interval}], ['$_']}]) of
[] -> ok; [] -> ok;
Flappings -> Flappings ->
lists:foreach(fun(Flapping = #flapping{client_id = {banned, ClientId}}) -> lists:foreach(fun(Flapping = #flapping{clientid = {banned, ClientId}}) ->
ets:delete_object(?FLAPPING_TAB, Flapping), ets:delete_object(?FLAPPING_TAB, Flapping),
alarm_handler:clear_alarm({flapping_detected, ClientId}); alarm_handler:clear_alarm({flapping_detected, ClientId});
(_) -> ok (_) -> ok

View File

@ -25,6 +25,8 @@
-export([ parse/1 -export([ parse/1
, parse/2 , parse/2
, serialize_fun/0
, serialize_fun/1
, serialize/1 , serialize/1
, serialize/2 , serialize/2
]). ]).
@ -32,6 +34,7 @@
-export_type([ options/0 -export_type([ options/0
, parse_state/0 , parse_state/0
, parse_result/0 , parse_result/0
, serialize_fun/0
]). ]).
-type(options() :: #{strict_mode => boolean(), -type(options() :: #{strict_mode => boolean(),
@ -46,7 +49,9 @@
-type(cont_fun() :: fun((binary()) -> parse_result())). -type(cont_fun() :: fun((binary()) -> parse_result())).
-define(none(Opts), {none, Opts}). -type(serialize_fun() :: fun((emqx_types:packet()) -> iodata())).
-define(none(Options), {none, Options}).
-define(DEFAULT_OPTIONS, -define(DEFAULT_OPTIONS,
#{strict_mode => false, #{strict_mode => false,
@ -84,12 +89,12 @@ parse(<<>>, {none, Options}) ->
parse(<<Type:4, Dup:1, QoS:2, Retain:1, Rest/binary>>, parse(<<Type:4, Dup:1, QoS:2, Retain:1, Rest/binary>>,
{none, Options = #{strict_mode := StrictMode}}) -> {none, Options = #{strict_mode := StrictMode}}) ->
%% Validate header if strict mode. %% Validate header if strict mode.
StrictMode andalso validate_header(Type, Dup, QoS, Retain),
Header = #mqtt_packet_header{type = Type, Header = #mqtt_packet_header{type = Type,
dup = bool(Dup), dup = bool(Dup),
qos = QoS, qos = QoS,
retain = bool(Retain) retain = bool(Retain)
}, },
StrictMode andalso validate_header(Type, Dup, QoS, Retain),
Header1 = case fixqos(Type, QoS) of Header1 = case fixqos(Type, QoS) of
QoS -> Header; QoS -> Header;
FixedQoS -> Header#mqtt_packet_header{qos = FixedQoS} FixedQoS -> Header#mqtt_packet_header{qos = FixedQoS}
@ -105,7 +110,7 @@ parse_remaining_len(Rest, Header, Options) ->
parse_remaining_len(_Bin, _Header, _Multiplier, Length, #{max_size := MaxSize}) parse_remaining_len(_Bin, _Header, _Multiplier, Length, #{max_size := MaxSize})
when Length > MaxSize -> when Length > MaxSize ->
error(mqtt_frame_too_large); error(frame_too_large);
parse_remaining_len(<<>>, Header, Multiplier, Length, Options) -> parse_remaining_len(<<>>, Header, Multiplier, Length, Options) ->
{more, fun(Bin) -> parse_remaining_len(Bin, Header, Multiplier, Length, Options) end}; {more, fun(Bin) -> parse_remaining_len(Bin, Header, Multiplier, Length, Options) end};
%% Match DISCONNECT without payload %% Match DISCONNECT without payload
@ -124,7 +129,7 @@ parse_remaining_len(<<0:1, Len:7, Rest/binary>>, Header, Multiplier, Value,
Options = #{max_size := MaxSize}) -> Options = #{max_size := MaxSize}) ->
FrameLen = Value + Len * Multiplier, FrameLen = Value + Len * Multiplier,
if if
FrameLen > MaxSize -> error(mqtt_frame_too_large); FrameLen > MaxSize -> error(frame_too_large);
true -> parse_frame(Rest, Header, FrameLen, Options) true -> parse_frame(Rest, Header, FrameLen, Options)
end. end.
@ -148,6 +153,7 @@ parse_frame(Bin, Header, Length, Options) ->
end} end}
end. end.
-compile({inline, [packet/1, packet/2, packet/3]}).
packet(Header) -> packet(Header) ->
#mqtt_packet{header = Header}. #mqtt_packet{header = Header}.
packet(Header, Variable) -> packet(Header, Variable) ->
@ -180,7 +186,8 @@ parse_packet(#mqtt_packet_header{type = ?CONNECT}, FrameBin, _Options) ->
will_retain = bool(WillRetain), will_retain = bool(WillRetain),
keepalive = KeepAlive, keepalive = KeepAlive,
properties = Properties, properties = Properties,
client_id = ClientId}, clientid = ClientId
},
{ConnPacket1, Rest5} = parse_will_message(ConnPacket, Rest4), {ConnPacket1, Rest5} = parse_will_message(ConnPacket, Rest4),
{Username, Rest6} = parse_utf8_string(Rest5, bool(UsernameFlag)), {Username, Rest6} = parse_utf8_string(Rest5, bool(UsernameFlag)),
{Passsword, <<>>} = parse_utf8_string(Rest6, bool(PasswordFlag)), {Passsword, <<>>} = parse_utf8_string(Rest6, bool(PasswordFlag)),
@ -191,10 +198,10 @@ parse_packet(#mqtt_packet_header{type = ?CONNACK},
{Properties, <<>>} = parse_properties(Rest, Ver), {Properties, <<>>} = parse_properties(Rest, Ver),
#mqtt_packet_connack{ack_flags = AckFlags, #mqtt_packet_connack{ack_flags = AckFlags,
reason_code = ReasonCode, reason_code = ReasonCode,
properties = Properties}; properties = Properties
};
parse_packet(#mqtt_packet_header{type = ?PUBLISH, qos = QoS}, Bin, parse_packet(#mqtt_packet_header{type = ?PUBLISH, qos = QoS}, Bin, #{version := Ver}) ->
#{version := Ver}) ->
{TopicName, Rest} = parse_utf8_string(Bin), {TopicName, Rest} = parse_utf8_string(Bin),
{PacketId, Rest1} = case QoS of {PacketId, Rest1} = case QoS of
?QOS_0 -> {undefined, Rest}; ?QOS_0 -> {undefined, Rest};
@ -202,14 +209,17 @@ parse_packet(#mqtt_packet_header{type = ?PUBLISH, qos = QoS}, Bin,
end, end,
(PacketId =/= undefined) andalso validate_packet_id(PacketId), (PacketId =/= undefined) andalso validate_packet_id(PacketId),
{Properties, Payload} = parse_properties(Rest1, Ver), {Properties, Payload} = parse_properties(Rest1, Ver),
{#mqtt_packet_publish{topic_name = TopicName, Publish = #mqtt_packet_publish{topic_name = TopicName,
packet_id = PacketId, packet_id = PacketId,
properties = Properties}, Payload}; properties = Properties
},
{Publish, Payload};
parse_packet(#mqtt_packet_header{type = PubAck}, <<PacketId:16/big>>, _Options) parse_packet(#mqtt_packet_header{type = PubAck}, <<PacketId:16/big>>, _Options)
when ?PUBACK =< PubAck, PubAck =< ?PUBCOMP -> when ?PUBACK =< PubAck, PubAck =< ?PUBCOMP ->
ok = validate_packet_id(PacketId), ok = validate_packet_id(PacketId),
#mqtt_packet_puback{packet_id = PacketId, reason_code = 0}; #mqtt_packet_puback{packet_id = PacketId, reason_code = 0};
parse_packet(#mqtt_packet_header{type = PubAck}, <<PacketId:16/big, ReasonCode, Rest/binary>>, parse_packet(#mqtt_packet_header{type = PubAck}, <<PacketId:16/big, ReasonCode, Rest/binary>>,
#{version := Ver = ?MQTT_PROTO_V5}) #{version := Ver = ?MQTT_PROTO_V5})
when ?PUBACK =< PubAck, PubAck =< ?PUBCOMP -> when ?PUBACK =< PubAck, PubAck =< ?PUBCOMP ->
@ -217,24 +227,29 @@ parse_packet(#mqtt_packet_header{type = PubAck}, <<PacketId:16/big, ReasonCode,
{Properties, <<>>} = parse_properties(Rest, Ver), {Properties, <<>>} = parse_properties(Rest, Ver),
#mqtt_packet_puback{packet_id = PacketId, #mqtt_packet_puback{packet_id = PacketId,
reason_code = ReasonCode, reason_code = ReasonCode,
properties = Properties}; properties = Properties
};
parse_packet(#mqtt_packet_header{type = ?SUBSCRIBE}, <<PacketId:16/big, Rest/binary>>, parse_packet(#mqtt_packet_header{type = ?SUBSCRIBE}, <<PacketId:16/big, Rest/binary>>,
#{version := Ver}) -> #{version := Ver}) ->
ok = validate_packet_id(PacketId), ok = validate_packet_id(PacketId),
{Properties, Rest1} = parse_properties(Rest, Ver), {Properties, Rest1} = parse_properties(Rest, Ver),
TopicFilters = parse_topic_filters(subscribe, Rest1), TopicFilters = parse_topic_filters(subscribe, Rest1),
ok = validate_subqos([QoS || {_, #{qos := QoS}} <- TopicFilters]),
#mqtt_packet_subscribe{packet_id = PacketId, #mqtt_packet_subscribe{packet_id = PacketId,
properties = Properties, properties = Properties,
topic_filters = TopicFilters}; topic_filters = TopicFilters
};
parse_packet(#mqtt_packet_header{type = ?SUBACK}, <<PacketId:16/big, Rest/binary>>, parse_packet(#mqtt_packet_header{type = ?SUBACK}, <<PacketId:16/big, Rest/binary>>,
#{version := Ver}) -> #{version := Ver}) ->
ok = validate_packet_id(PacketId), ok = validate_packet_id(PacketId),
{Properties, Rest1} = parse_properties(Rest, Ver), {Properties, Rest1} = parse_properties(Rest, Ver),
ReasonCodes = parse_reason_codes(Rest1),
#mqtt_packet_suback{packet_id = PacketId, #mqtt_packet_suback{packet_id = PacketId,
properties = Properties, properties = Properties,
reason_codes = parse_reason_codes(Rest1)}; reason_codes = ReasonCodes
};
parse_packet(#mqtt_packet_header{type = ?UNSUBSCRIBE}, <<PacketId:16/big, Rest/binary>>, parse_packet(#mqtt_packet_header{type = ?UNSUBSCRIBE}, <<PacketId:16/big, Rest/binary>>,
#{version := Ver}) -> #{version := Ver}) ->
@ -243,11 +258,13 @@ parse_packet(#mqtt_packet_header{type = ?UNSUBSCRIBE}, <<PacketId:16/big, Rest/b
TopicFilters = parse_topic_filters(unsubscribe, Rest1), TopicFilters = parse_topic_filters(unsubscribe, Rest1),
#mqtt_packet_unsubscribe{packet_id = PacketId, #mqtt_packet_unsubscribe{packet_id = PacketId,
properties = Properties, properties = Properties,
topic_filters = TopicFilters}; topic_filters = TopicFilters
};
parse_packet(#mqtt_packet_header{type = ?UNSUBACK}, <<PacketId:16/big>>, _Options) -> parse_packet(#mqtt_packet_header{type = ?UNSUBACK}, <<PacketId:16/big>>, _Options) ->
ok = validate_packet_id(PacketId), ok = validate_packet_id(PacketId),
#mqtt_packet_unsuback{packet_id = PacketId}; #mqtt_packet_unsuback{packet_id = PacketId};
parse_packet(#mqtt_packet_header{type = ?UNSUBACK}, <<PacketId:16/big, Rest/binary>>, parse_packet(#mqtt_packet_header{type = ?UNSUBACK}, <<PacketId:16/big, Rest/binary>>,
#{version := Ver}) -> #{version := Ver}) ->
ok = validate_packet_id(PacketId), ok = validate_packet_id(PacketId),
@ -255,13 +272,15 @@ parse_packet(#mqtt_packet_header{type = ?UNSUBACK}, <<PacketId:16/big, Rest/bina
ReasonCodes = parse_reason_codes(Rest1), ReasonCodes = parse_reason_codes(Rest1),
#mqtt_packet_unsuback{packet_id = PacketId, #mqtt_packet_unsuback{packet_id = PacketId,
properties = Properties, properties = Properties,
reason_codes = ReasonCodes}; reason_codes = ReasonCodes
};
parse_packet(#mqtt_packet_header{type = ?DISCONNECT}, <<ReasonCode, Rest/binary>>, parse_packet(#mqtt_packet_header{type = ?DISCONNECT}, <<ReasonCode, Rest/binary>>,
#{version := ?MQTT_PROTO_V5}) -> #{version := ?MQTT_PROTO_V5}) ->
{Properties, <<>>} = parse_properties(Rest, ?MQTT_PROTO_V5), {Properties, <<>>} = parse_properties(Rest, ?MQTT_PROTO_V5),
#mqtt_packet_disconnect{reason_code = ReasonCode, #mqtt_packet_disconnect{reason_code = ReasonCode,
properties = Properties}; properties = Properties
};
parse_packet(#mqtt_packet_header{type = ?AUTH}, <<ReasonCode, Rest/binary>>, parse_packet(#mqtt_packet_header{type = ?AUTH}, <<ReasonCode, Rest/binary>>,
#{version := ?MQTT_PROTO_V5}) -> #{version := ?MQTT_PROTO_V5}) ->
@ -275,16 +294,15 @@ parse_will_message(Packet = #mqtt_packet_connect{will_flag = true,
{Payload, Rest2} = parse_binary_data(Rest1), {Payload, Rest2} = parse_binary_data(Rest1),
{Packet#mqtt_packet_connect{will_props = Props, {Packet#mqtt_packet_connect{will_props = Props,
will_topic = Topic, will_topic = Topic,
will_payload = Payload}, Rest2}; will_payload = Payload
}, Rest2};
parse_will_message(Packet, Bin) -> parse_will_message(Packet, Bin) ->
{Packet, Bin}. {Packet, Bin}.
-compile({inline, [parse_packet_id/1]}).
parse_packet_id(<<PacketId:16/big, Rest/binary>>) -> parse_packet_id(<<PacketId:16/big, Rest/binary>>) ->
{PacketId, Rest}. {PacketId, Rest}.
validate_packet_id(0) -> error(bad_packet_id);
validate_packet_id(_) -> ok.
parse_properties(Bin, Ver) when Ver =/= ?MQTT_PROTO_V5 -> parse_properties(Bin, Ver) when Ver =/= ?MQTT_PROTO_V5 ->
{undefined, Bin}; {undefined, Bin};
%% TODO: version mess? %% TODO: version mess?
@ -377,7 +395,7 @@ parse_variable_byte_integer(<<0:1, Len:7, Rest/binary>>, Multiplier, Value) ->
{Value + Len * Multiplier, Rest}. {Value + Len * Multiplier, Rest}.
parse_topic_filters(subscribe, Bin) -> parse_topic_filters(subscribe, Bin) ->
[{Topic, #{rh => Rh, rap => Rap, nl => Nl, qos => validate_subqos(QoS), rc => 0}} [{Topic, #{rh => Rh, rap => Rap, nl => Nl, qos => QoS}}
|| <<Len:16/big, Topic:Len/binary, _:2, Rh:2, Rap:1, Nl:1, QoS:2>> <= Bin]; || <<Len:16/big, Topic:Len/binary, _:2, Rh:2, Rap:1, Nl:1, QoS:2>> <= Bin];
parse_topic_filters(unsubscribe, Bin) -> parse_topic_filters(unsubscribe, Bin) ->
@ -405,9 +423,23 @@ parse_binary_data(<<Len:16/big, Data:Len/binary, Rest/binary>>) ->
%% Serialize MQTT Packet %% Serialize MQTT Packet
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
serialize_fun() -> serialize_fun(?DEFAULT_OPTIONS).
serialize_fun(#mqtt_packet_connect{proto_ver = ProtoVer, properties = ConnProps}) ->
MaxSize = get_property('Maximum-Packet-Size', ConnProps, ?MAX_PACKET_SIZE),
serialize_fun(#{version => ProtoVer, max_size => MaxSize});
serialize_fun(#{version := Ver, max_size := MaxSize}) ->
fun(Packet) ->
IoData = serialize(Packet, Ver),
case is_too_large(IoData, MaxSize) of
true -> <<>>;
false -> IoData
end
end.
-spec(serialize(emqx_types:packet()) -> iodata()). -spec(serialize(emqx_types:packet()) -> iodata()).
serialize(Packet) -> serialize(Packet) -> serialize(Packet, ?MQTT_PROTO_V4).
serialize(Packet, ?MQTT_PROTO_V4).
-spec(serialize(emqx_types:packet(), emqx_types:version()) -> iodata()). -spec(serialize(emqx_types:packet(), emqx_types:version()) -> iodata()).
serialize(#mqtt_packet{header = Header, serialize(#mqtt_packet{header = Header,
@ -418,10 +450,10 @@ serialize(#mqtt_packet{header = Header,
serialize(#mqtt_packet_header{type = Type, serialize(#mqtt_packet_header{type = Type,
dup = Dup, dup = Dup,
qos = QoS, qos = QoS,
retain = Retain}, VariableBin, PayloadBin) retain = Retain
}, VariableBin, PayloadBin)
when ?CONNECT =< Type andalso Type =< ?AUTH -> when ?CONNECT =< Type andalso Type =< ?AUTH ->
Len = iolist_size(VariableBin) + iolist_size(PayloadBin), Len = iolist_size(VariableBin) + iolist_size(PayloadBin),
(Len =< ?MAX_PACKET_SIZE) orelse error(mqtt_frame_too_large),
[<<Type:4, (flag(Dup)):1, (flag(QoS)):2, (flag(Retain)):1>>, [<<Type:4, (flag(Dup)):1, (flag(QoS)):2, (flag(Retain)):1>>,
serialize_remaining_len(Len), VariableBin, PayloadBin]. serialize_remaining_len(Len), VariableBin, PayloadBin].
@ -435,7 +467,7 @@ serialize_variable(#mqtt_packet_connect{
will_retain = WillRetain, will_retain = WillRetain,
keepalive = KeepAlive, keepalive = KeepAlive,
properties = Properties, properties = Properties,
client_id = ClientId, clientid = ClientId,
will_props = WillProps, will_props = WillProps,
will_topic = WillTopic, will_topic = WillTopic,
will_payload = WillPayload, will_payload = WillPayload,
@ -485,10 +517,11 @@ serialize_variable(#mqtt_packet_puback{packet_id = PacketId}, Ver)
<<PacketId:16/big-unsigned-integer>>; <<PacketId:16/big-unsigned-integer>>;
serialize_variable(#mqtt_packet_puback{packet_id = PacketId, serialize_variable(#mqtt_packet_puback{packet_id = PacketId,
reason_code = ReasonCode, reason_code = ReasonCode,
properties = Properties}, properties = Properties
?MQTT_PROTO_V5) -> },
Ver = ?MQTT_PROTO_V5) ->
[<<PacketId:16/big-unsigned-integer>>, ReasonCode, [<<PacketId:16/big-unsigned-integer>>, ReasonCode,
serialize_properties(Properties, ?MQTT_PROTO_V5)]; serialize_properties(Properties, Ver)];
serialize_variable(#mqtt_packet_subscribe{packet_id = PacketId, serialize_variable(#mqtt_packet_subscribe{packet_id = PacketId,
properties = Properties, properties = Properties,
@ -616,8 +649,7 @@ serialize_property('Shared-Subscription-Available', Val) ->
serialize_topic_filters(subscribe, TopicFilters, ?MQTT_PROTO_V5) -> serialize_topic_filters(subscribe, TopicFilters, ?MQTT_PROTO_V5) ->
<< <<(serialize_utf8_string(Topic))/binary, << <<(serialize_utf8_string(Topic))/binary,
?RESERVED:2, Rh:2, (flag(Rap)):1,(flag(Nl)):1, QoS:2 >> ?RESERVED:2, Rh:2, (flag(Rap)):1,(flag(Nl)):1, QoS:2 >>
|| {Topic, #{rh := Rh, rap := Rap, nl := Nl, qos := QoS}} || {Topic, #{rh := Rh, rap := Rap, nl := Nl, qos := QoS}} <- TopicFilters >>;
<- TopicFilters >>;
serialize_topic_filters(subscribe, TopicFilters, _Ver) -> serialize_topic_filters(subscribe, TopicFilters, _Ver) ->
<< <<(serialize_utf8_string(Topic))/binary, ?RESERVED:6, QoS:2>> << <<(serialize_utf8_string(Topic))/binary, ?RESERVED:6, QoS:2>>
@ -658,6 +690,16 @@ serialize_variable_byte_integer(N) when N =< ?LOWBITS ->
serialize_variable_byte_integer(N) -> serialize_variable_byte_integer(N) ->
<<1:1, (N rem ?HIGHBIT):7, (serialize_variable_byte_integer(N div ?HIGHBIT))/binary>>. <<1:1, (N rem ?HIGHBIT):7, (serialize_variable_byte_integer(N div ?HIGHBIT))/binary>>.
%% Is the frame too large?
-spec(is_too_large(iodata(), pos_integer()) -> boolean()).
is_too_large(IoData, MaxSize) ->
iolist_size(IoData) >= MaxSize.
get_property(_Key, undefined, Default) ->
Default;
get_property(Key, Props, Default) ->
maps:get(Key, Props, Default).
%% Validate header if sctrict mode. See: mqtt-v5.0: 2.1.3 Flags %% Validate header if sctrict mode. See: mqtt-v5.0: 2.1.3 Flags
validate_header(?CONNECT, 0, 0, 0) -> ok; validate_header(?CONNECT, 0, 0, 0) -> ok;
validate_header(?CONNACK, 0, 0, 0) -> ok; validate_header(?CONNACK, 0, 0, 0) -> ok;
@ -678,9 +720,12 @@ validate_header(?DISCONNECT, 0, 0, 0) -> ok;
validate_header(?AUTH, 0, 0, 0) -> ok; validate_header(?AUTH, 0, 0, 0) -> ok;
validate_header(_Type, _Dup, _QoS, _Rt) -> error(bad_frame_header). validate_header(_Type, _Dup, _QoS, _Rt) -> error(bad_frame_header).
validate_subqos(QoS) when ?QOS_0 =< QoS, QoS =< ?QOS_2 -> validate_packet_id(0) -> error(bad_packet_id);
QoS; validate_packet_id(_) -> ok.
validate_subqos(_) -> error(bad_subqos).
validate_subqos([3|_]) -> error(bad_subqos);
validate_subqos([_|T]) -> validate_subqos(T);
validate_subqos([]) -> ok.
bool(0) -> false; bool(0) -> false;
bool(1) -> true. bool(1) -> true.
@ -695,3 +740,4 @@ fixqos(?PUBREL, 0) -> 1;
fixqos(?SUBSCRIBE, 0) -> 1; fixqos(?SUBSCRIBE, 0) -> 1;
fixqos(?UNSUBSCRIBE, 0) -> 1; fixqos(?UNSUBSCRIBE, 0) -> 1;
fixqos(_Type, QoS) -> QoS. fixqos(_Type, QoS) -> QoS.

View File

@ -38,7 +38,7 @@
%% Configs %% Configs
-export([ set_metadata_peername/1 -export([ set_metadata_peername/1
, set_metadata_client_id/1 , set_metadata_clientid/1
, set_proc_metadata/1 , set_proc_metadata/1
, set_primary_log_level/1 , set_primary_log_level/1
, set_log_handler_level/2 , set_log_handler_level/2
@ -121,11 +121,11 @@ critical(Format, Args) ->
critical(Metadata, Format, Args) when is_map(Metadata) -> critical(Metadata, Format, Args) when is_map(Metadata) ->
logger:critical(Format, Args, Metadata). logger:critical(Format, Args, Metadata).
-spec(set_metadata_client_id(emqx_types:client_id()) -> ok). -spec(set_metadata_clientid(emqx_types:clientid()) -> ok).
set_metadata_client_id(<<>>) -> set_metadata_clientid(<<>>) ->
ok; ok;
set_metadata_client_id(ClientId) -> set_metadata_clientid(ClientId) ->
set_proc_metadata(#{client_id => ClientId}). set_proc_metadata(#{clientid => ClientId}).
-spec(set_metadata_peername(peername_str()) -> ok). -spec(set_metadata_peername(peername_str()) -> ok).
set_metadata_peername(Peername) -> set_metadata_peername(Peername) ->

View File

@ -71,13 +71,13 @@
make(Topic, Payload) -> make(Topic, Payload) ->
make(undefined, Topic, Payload). make(undefined, Topic, Payload).
-spec(make(atom() | emqx_types:client_id(), -spec(make(atom() | emqx_types:clientid(),
emqx_topic:topic(), emqx_topic:topic(),
emqx_types:payload()) -> emqx_types:message()). emqx_types:payload()) -> emqx_types:message()).
make(From, Topic, Payload) -> make(From, Topic, Payload) ->
make(From, ?QOS_0, Topic, Payload). make(From, ?QOS_0, Topic, Payload).
-spec(make(atom() | emqx_types:client_id(), -spec(make(atom() | emqx_types:clientid(),
emqx_types:qos(), emqx_types:qos(),
emqx_topic:topic(), emqx_topic:topic(),
emqx_types:payload()) -> emqx_types:message()). emqx_types:payload()) -> emqx_types:message()).

View File

@ -116,17 +116,20 @@ cancel_timer(_) -> ok.
proc_name(Mod, Id) -> proc_name(Mod, Id) ->
list_to_atom(lists:concat([Mod, "_", Id])). list_to_atom(lists:concat([Mod, "_", Id])).
-spec(proc_stats() -> list()). %% Get Proc's Stats.
proc_stats() -> -spec(proc_stats() -> emqx_types:stats()).
proc_stats(self()). proc_stats() -> proc_stats(self()).
-spec(proc_stats(pid()) -> list()). -spec(proc_stats(pid()) -> emqx_types:stats()).
proc_stats(Pid) -> proc_stats(Pid) ->
case process_info(Pid, [message_queue_len, heap_size, case process_info(Pid, [message_queue_len,
total_heap_size, reductions, memory]) of heap_size,
total_heap_size,
reductions,
memory]) of
undefined -> []; undefined -> [];
[{message_queue_len, Len}|Stats] -> [{message_queue_len, Len}|ProcStats] ->
[{mailbox_len, Len}|Stats] [{mailbox_len, Len}|ProcStats]
end. end.
%% @doc Drain delivers from the channel's mailbox. %% @doc Drain delivers from the channel's mailbox.

View File

@ -61,7 +61,7 @@ all_rules() ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Check ACL %% @doc Check ACL
-spec(check_acl(emqx_types:client(), emqx_types:pubsub(), emqx_topic:topic(), -spec(check_acl(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_topic:topic(),
emqx_access_rule:acl_result(), acl_rules()) emqx_access_rule:acl_result(), acl_rules())
-> {ok, allow} | {ok, deny} | ok). -> {ok, allow} | {ok, deny} | ok).
check_acl(Client, PubSub, Topic, _AclResult, Rules) -> check_acl(Client, PubSub, Topic, _AclResult, Rules) ->

View File

@ -88,8 +88,8 @@ on_client_disconnected(ClientInfo, Reason, ConnInfo, Env) ->
?LOG(error, "Failed to encode 'disconnected' presence: ~p", [Presence]) ?LOG(error, "Failed to encode 'disconnected' presence: ~p", [Presence])
end. end.
clientid(#{client_id := undefined}, #{client_id := ClientId}) -> ClientId; clientid(#{clientid := undefined}, #{clientid := ClientId}) -> ClientId;
clientid(#{client_id := ClientId}, _ConnInfo) -> ClientId. clientid(#{clientid := ClientId}, _ConnInfo) -> ClientId.
username(#{username := undefined}, #{username := Username}) -> Username; username(#{username := undefined}, #{username := Username}) -> Username;
username(#{username := Username}, _ConnInfo) -> Username. username(#{username := Username}, _ConnInfo) -> Username.

View File

@ -36,8 +36,8 @@
load(Topics) -> load(Topics) ->
emqx_hooks:add('client.connected', {?MODULE, on_client_connected, [Topics]}). emqx_hooks:add('client.connected', {?MODULE, on_client_connected, [Topics]}).
on_client_connected(#{client_id := ClientId, on_client_connected(#{clientid := ClientId,
username := Username}, ?RC_SUCCESS, _ConnInfo, Topics) -> username := Username}, ?RC_SUCCESS, _ConnInfo, Topics) ->
Replace = fun(Topic) -> Replace = fun(Topic) ->
rep(<<"%u">>, Username, rep(<<"%c">>, ClientId, Topic)) rep(<<"%u">>, Username, rep(<<"%c">>, ClientId, Topic))
end, end,

View File

@ -66,7 +66,7 @@ unmount(MountPoint, Msg = #message{topic = Topic}) ->
-spec(replvar(maybe(mountpoint()), map()) -> maybe(mountpoint())). -spec(replvar(maybe(mountpoint()), map()) -> maybe(mountpoint())).
replvar(undefined, _Vars) -> replvar(undefined, _Vars) ->
undefined; undefined;
replvar(MountPoint, #{client_id := ClientId, username := Username}) -> replvar(MountPoint, #{clientid := ClientId, username := Username}) ->
lists:foldl(fun feed_var/2, MountPoint, lists:foldl(fun feed_var/2, MountPoint,
[{<<"%c">>, ClientId}, {<<"%u">>, Username}]). [{<<"%c">>, ClientId}, {<<"%u">>, Username}]).

View File

@ -181,16 +181,16 @@ check_proto_ver(#mqtt_packet_connect{proto_ver = Ver,
%% MQTT3.1 does not allow null clientId %% MQTT3.1 does not allow null clientId
check_client_id(#mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V3, check_client_id(#mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V3,
client_id = <<>>}, _Opts) -> clientid = <<>>}, _Opts) ->
{error, ?RC_CLIENT_IDENTIFIER_NOT_VALID}; {error, ?RC_CLIENT_IDENTIFIER_NOT_VALID};
%% Issue#599: Null clientId and clean_start = false %% Issue#599: Null clientId and clean_start = false
check_client_id(#mqtt_packet_connect{client_id = <<>>, check_client_id(#mqtt_packet_connect{clientid = <<>>,
clean_start = false}, _Opts) -> clean_start = false}, _Opts) ->
{error, ?RC_CLIENT_IDENTIFIER_NOT_VALID}; {error, ?RC_CLIENT_IDENTIFIER_NOT_VALID};
check_client_id(#mqtt_packet_connect{client_id = <<>>, check_client_id(#mqtt_packet_connect{clientid = <<>>,
clean_start = true}, _Opts) -> clean_start = true}, _Opts) ->
ok; ok;
check_client_id(#mqtt_packet_connect{client_id = ClientId}, check_client_id(#mqtt_packet_connect{clientid = ClientId},
_Opts = #{max_clientid_len := MaxLen}) -> _Opts = #{max_clientid_len := MaxLen}) ->
case (1 =< (Len = byte_size(ClientId))) andalso (Len =< MaxLen) of case (1 =< (Len = byte_size(ClientId))) andalso (Len =< MaxLen) of
true -> ok; true -> ok;
@ -240,8 +240,8 @@ validate_topic_filters(TopicFilters) ->
end, TopicFilters). end, TopicFilters).
%% @doc Publish Packet to Message. %% @doc Publish Packet to Message.
-spec(to_message(emqx_types:client(), emqx_ypes:packet()) -> emqx_types:message()). -spec(to_message(emqx_types:clientinfo(), emqx_ypes:packet()) -> emqx_types:message()).
to_message(#{client_id := ClientId, username := Username, peerhost := PeerHost}, to_message(#{clientid := ClientId, username := Username, peerhost := PeerHost},
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH, #mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH,
retain = Retain, retain = Retain,
qos = QoS, qos = QoS,
@ -257,7 +257,7 @@ to_message(#{client_id := ClientId, username := Username, peerhost := PeerHost},
-spec(will_msg(#mqtt_packet_connect{}) -> emqx_types:message()). -spec(will_msg(#mqtt_packet_connect{}) -> emqx_types:message()).
will_msg(#mqtt_packet_connect{will_flag = false}) -> will_msg(#mqtt_packet_connect{will_flag = false}) ->
undefined; undefined;
will_msg(#mqtt_packet_connect{client_id = ClientId, will_msg(#mqtt_packet_connect{clientid = ClientId,
username = Username, username = Username,
will_retain = Retain, will_retain = Retain,
will_qos = QoS, will_qos = QoS,
@ -304,7 +304,7 @@ format_variable(#mqtt_packet_connect{
will_flag = WillFlag, will_flag = WillFlag,
clean_start = CleanStart, clean_start = CleanStart,
keepalive = KeepAlive, keepalive = KeepAlive,
client_id = ClientId, clientid = ClientId,
will_topic = WillTopic, will_topic = WillTopic,
will_payload = WillPayload, will_payload = WillPayload,
username = Username, username = Username,

View File

@ -19,27 +19,33 @@
-include("types.hrl"). -include("types.hrl").
-export([ update_counter/2 -export([ get_counters/1
, get_counter/1 , get_counter/1
, update_counter/2
, reset_counter/1 , reset_counter/1
]). ]).
-compile({inline, -compile({inline,
[ update_counter/2 [ get_counters/1
, get_counter/1 , get_counter/1
, update_counter/2
, reset_counter/1 , reset_counter/1
]}). ]}).
-type(key() :: term()). -type(key() :: term()).
-spec(update_counter(key(), number()) -> maybe(number())). -spec(get_counters(list(key())) -> list({key(), number()})).
update_counter(Key, Inc) -> get_counters(Keys) when is_list(Keys) ->
put(Key, get_counter(Key) + Inc). [{Key, emqx_pd:get_counter(Key)} || Key <- Keys].
-spec(get_counter(key()) -> number()). -spec(get_counter(key()) -> number()).
get_counter(Key) -> get_counter(Key) ->
case get(Key) of undefined -> 0; Cnt -> Cnt end. case get(Key) of undefined -> 0; Cnt -> Cnt end.
-spec(update_counter(key(), number()) -> maybe(number())).
update_counter(Key, Inc) ->
put(Key, get_counter(Key) + Inc).
-spec(reset_counter(key()) -> number()). -spec(reset_counter(key()) -> number()).
reset_counter(Key) -> reset_counter(Key) ->
case put(Key, 0) of undefined -> 0; Cnt -> Cnt end. case put(Key, 0) of undefined -> 0; Cnt -> Cnt end.

View File

@ -117,7 +117,7 @@
%% Enqueue Count %% Enqueue Count
enqueue_cnt :: non_neg_integer(), enqueue_cnt :: non_neg_integer(),
%% Created at %% Created at
created_at :: erlang:timestamp() created_at :: pos_integer()
}). }).
-opaque(session() :: #session{}). -opaque(session() :: #session{}).
@ -125,22 +125,49 @@
-type(publish() :: {publish, emqx_types:packet_id(), emqx_types:message()}). -type(publish() :: {publish, emqx_types:packet_id(), emqx_types:message()}).
-define(DEFAULT_BATCH_N, 1000). -define(DEFAULT_BATCH_N, 1000).
-define(ATTR_KEYS, [max_inflight, max_mqueue, retry_interval,
max_awaiting_rel, await_rel_timeout, created_at]). -define(ATTR_KEYS, [inflight_max,
-define(INFO_KEYS, [subscriptions, max_subscriptions, upgrade_qos, inflight, mqueue_max,
max_inflight, retry_interval, mqueue_len, max_mqueue, retry_interval,
mqueue_dropped, next_pkt_id, awaiting_rel, max_awaiting_rel, awaiting_rel_max,
await_rel_timeout, created_at]). await_rel_timeout,
-define(STATS_KEYS, [subscriptions_cnt, max_subscriptions, inflight, max_inflight, created_at
mqueue_len, max_mqueue, mqueue_dropped, awaiting_rel, ]).
max_awaiting_rel, enqueue_cnt]).
-define(INFO_KEYS, [subscriptions,
subscriptions_max,
upgrade_qos,
inflight,
inflight_max,
retry_interval,
mqueue_len,
mqueue_max,
mqueue_dropped,
next_pkt_id,
awaiting_rel,
awaiting_rel_max,
await_rel_timeout,
created_at
]).
-define(STATS_KEYS, [subscriptions_cnt,
subscriptions_max,
inflight,
inflight_max,
mqueue_len,
mqueue_max,
mqueue_dropped,
awaiting_rel,
awaiting_rel_max,
enqueue_cnt
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Init a session %% Init a session
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Init a session. %% @doc Init a session.
-spec(init(emqx_types:client(), Options :: map()) -> session()). -spec(init(emqx_types:clientinfo(), emqx_types:conninfo()) -> session()).
init(#{zone := Zone}, #{receive_maximum := MaxInflight}) -> init(#{zone := Zone}, #{receive_maximum := MaxInflight}) ->
#session{max_subscriptions = get_env(Zone, max_subscriptions, 0), #session{max_subscriptions = get_env(Zone, max_subscriptions, 0),
subscriptions = #{}, subscriptions = #{},
@ -153,7 +180,7 @@ init(#{zone := Zone}, #{receive_maximum := MaxInflight}) ->
max_awaiting_rel = get_env(Zone, max_awaiting_rel, 100), max_awaiting_rel = get_env(Zone, max_awaiting_rel, 100),
await_rel_timeout = get_env(Zone, await_rel_timeout, 3600*1000), await_rel_timeout = get_env(Zone, await_rel_timeout, 3600*1000),
enqueue_cnt = 0, enqueue_cnt = 0,
created_at = os:timestamp() created_at = erlang:system_time(second)
}. }.
init_mqueue(Zone) -> init_mqueue(Zone) ->
@ -183,19 +210,19 @@ info(subscriptions, #session{subscriptions = Subs}) ->
Subs; Subs;
info(subscriptions_cnt, #session{subscriptions = Subs}) -> info(subscriptions_cnt, #session{subscriptions = Subs}) ->
maps:size(Subs); maps:size(Subs);
info(max_subscriptions, #session{max_subscriptions = MaxSubs}) -> info(subscriptions_max, #session{max_subscriptions = MaxSubs}) ->
MaxSubs; MaxSubs;
info(upgrade_qos, #session{upgrade_qos = UpgradeQoS}) -> info(upgrade_qos, #session{upgrade_qos = UpgradeQoS}) ->
UpgradeQoS; UpgradeQoS;
info(inflight, #session{inflight = Inflight}) -> info(inflight, #session{inflight = Inflight}) ->
emqx_inflight:size(Inflight); emqx_inflight:size(Inflight);
info(max_inflight, #session{inflight = Inflight}) -> info(inflight_max, #session{inflight = Inflight}) ->
emqx_inflight:max_size(Inflight); emqx_inflight:max_size(Inflight);
info(retry_interval, #session{retry_interval = Interval}) -> info(retry_interval, #session{retry_interval = Interval}) ->
Interval; Interval;
info(mqueue_len, #session{mqueue = MQueue}) -> info(mqueue_len, #session{mqueue = MQueue}) ->
emqx_mqueue:len(MQueue); emqx_mqueue:len(MQueue);
info(max_mqueue, #session{mqueue = MQueue}) -> info(mqueue_max, #session{mqueue = MQueue}) ->
emqx_mqueue:max_len(MQueue); emqx_mqueue:max_len(MQueue);
info(mqueue_dropped, #session{mqueue = MQueue}) -> info(mqueue_dropped, #session{mqueue = MQueue}) ->
emqx_mqueue:dropped(MQueue); emqx_mqueue:dropped(MQueue);
@ -203,7 +230,7 @@ info(next_pkt_id, #session{next_pkt_id = PacketId}) ->
PacketId; PacketId;
info(awaiting_rel, #session{awaiting_rel = AwaitingRel}) -> info(awaiting_rel, #session{awaiting_rel = AwaitingRel}) ->
maps:size(AwaitingRel); maps:size(AwaitingRel);
info(max_awaiting_rel, #session{max_awaiting_rel = MaxAwaitingRel}) -> info(awaiting_rel_max, #session{max_awaiting_rel = MaxAwaitingRel}) ->
MaxAwaitingRel; MaxAwaitingRel;
info(await_rel_timeout, #session{await_rel_timeout = Timeout}) -> info(await_rel_timeout, #session{await_rel_timeout = Timeout}) ->
Timeout; Timeout;
@ -224,14 +251,14 @@ takeover(#session{subscriptions = Subs}) ->
ok = emqx_broker:unsubscribe(TopicFilter) ok = emqx_broker:unsubscribe(TopicFilter)
end, maps:to_list(Subs)). end, maps:to_list(Subs)).
-spec(resume(emqx_types:client_id(), session()) -> ok). -spec(resume(emqx_types:clientid(), session()) -> ok).
resume(ClientId, #session{subscriptions = Subs}) -> resume(ClientId, #session{subscriptions = Subs}) ->
%% 1. Subscribe again. %% 1. Subscribe again.
lists:foreach(fun({TopicFilter, SubOpts}) -> lists:foreach(fun({TopicFilter, SubOpts}) ->
ok = emqx_broker:subscribe(TopicFilter, ClientId, SubOpts) ok = emqx_broker:subscribe(TopicFilter, ClientId, SubOpts)
end, maps:to_list(Subs)). end, maps:to_list(Subs)).
%% 2. Run hooks. %% 2. Run hooks.
%% ok = emqx_hooks:run('session.resumed', [#{client_id => ClientId}, attrs(Session)]), %% ok = emqx_hooks:run('session.resumed', [#{clientid => ClientId}, attrs(Session)]),
%% TODO: 3. Redeliver: Replay delivery and Dequeue pending messages %% TODO: 3. Redeliver: Replay delivery and Dequeue pending messages
%%Session. %%Session.
@ -252,14 +279,14 @@ redeliver(Session = #session{inflight = Inflight}) ->
%% Client -> Broker: SUBSCRIBE %% Client -> Broker: SUBSCRIBE
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(subscribe(emqx_types:client(), emqx_types:topic(), emqx_types:subopts(), session()) -spec(subscribe(emqx_types:clientinfo(), emqx_types:topic(), emqx_types:subopts(), session())
-> {ok, session()} | {error, emqx_types:reason_code()}). -> {ok, session()} | {error, emqx_types:reason_code()}).
subscribe(Client, TopicFilter, SubOpts, Session = #session{subscriptions = Subs}) -> subscribe(ClientInfo, TopicFilter, SubOpts, Session = #session{subscriptions = Subs}) ->
case is_subscriptions_full(Session) case is_subscriptions_full(Session)
andalso (not maps:is_key(TopicFilter, Subs)) of andalso (not maps:is_key(TopicFilter, Subs)) of
true -> {error, ?RC_QUOTA_EXCEEDED}; true -> {error, ?RC_QUOTA_EXCEEDED};
false -> false ->
do_subscribe(Client, TopicFilter, SubOpts, Session) do_subscribe(ClientInfo, TopicFilter, SubOpts, Session)
end. end.
is_subscriptions_full(#session{max_subscriptions = 0}) -> is_subscriptions_full(#session{max_subscriptions = 0}) ->
@ -269,7 +296,7 @@ is_subscriptions_full(#session{max_subscriptions = MaxLimit,
maps:size(Subs) >= MaxLimit. maps:size(Subs) >= MaxLimit.
-compile({inline, [do_subscribe/4]}). -compile({inline, [do_subscribe/4]}).
do_subscribe(Client = #{client_id := ClientId}, TopicFilter, SubOpts, do_subscribe(Client = #{clientid := ClientId}, TopicFilter, SubOpts,
Session = #session{subscriptions = Subs}) -> Session = #session{subscriptions = Subs}) ->
case IsNew = (not maps:is_key(TopicFilter, Subs)) of case IsNew = (not maps:is_key(TopicFilter, Subs)) of
true -> true ->
@ -285,13 +312,13 @@ do_subscribe(Client = #{client_id := ClientId}, TopicFilter, SubOpts,
%% Client -> Broker: UNSUBSCRIBE %% Client -> Broker: UNSUBSCRIBE
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(unsubscribe(emqx_types:client(), emqx_types:topic(), session()) -spec(unsubscribe(emqx_types:clientinfo(), emqx_types:topic(), session())
-> {ok, session()} | {error, emqx_types:reason_code()}). -> {ok, session()} | {error, emqx_types:reason_code()}).
unsubscribe(Client, TopicFilter, Session = #session{subscriptions = Subs}) -> unsubscribe(ClientInfo, TopicFilter, Session = #session{subscriptions = Subs}) ->
case maps:find(TopicFilter, Subs) of case maps:find(TopicFilter, Subs) of
{ok, SubOpts} -> {ok, SubOpts} ->
ok = emqx_broker:unsubscribe(TopicFilter), ok = emqx_broker:unsubscribe(TopicFilter),
ok = emqx_hooks:run('session.unsubscribed', [Client, TopicFilter, SubOpts]), ok = emqx_hooks:run('session.unsubscribed', [ClientInfo, TopicFilter, SubOpts]),
{ok, Session#session{subscriptions = maps:remove(TopicFilter, Subs)}}; {ok, Session#session{subscriptions = maps:remove(TopicFilter, Subs)}};
error -> error ->
{error, ?RC_NO_SUBSCRIPTION_EXISTED} {error, ?RC_NO_SUBSCRIPTION_EXISTED}

View File

@ -37,7 +37,9 @@
, setstat/3 , setstat/3
, statsfun/1 , statsfun/1
, statsfun/2 , statsfun/2
, update_interval/2 ]).
-export([ update_interval/2
, update_interval/3 , update_interval/3
, cancel_update/1 , cancel_update/1
]). ]).

View File

@ -34,15 +34,19 @@
seed() -> seed() ->
rand:seed(exsplus, erlang:timestamp()). rand:seed(exsplus, erlang:timestamp()).
-spec(now_secs() -> pos_integer()).
now_secs() -> now_secs() ->
erlang:system_time(second). erlang:system_time(second).
-spec(now_secs(erlang:timestamp()) -> pos_integer()).
now_secs({MegaSecs, Secs, _MicroSecs}) -> now_secs({MegaSecs, Secs, _MicroSecs}) ->
MegaSecs * 1000000 + Secs. MegaSecs * 1000000 + Secs.
-spec(now_ms() -> pos_integer()).
now_ms() -> now_ms() ->
erlang:system_time(millisecond). erlang:system_time(millisecond).
-spec(now_ms(erlang:timestamp()) -> pos_integer()).
now_ms({MegaSecs, Secs, MicroSecs}) -> now_ms({MegaSecs, Secs, MicroSecs}) ->
(MegaSecs * 1000000 + Secs) * 1000 + round(MicroSecs/1000). (MegaSecs * 1000000 + Secs) * 1000 + round(MicroSecs/1000).

View File

@ -28,16 +28,16 @@
, stop_trace/1 , stop_trace/1
]). ]).
-type(trace_who() :: {client_id | topic, binary() | list()}). -type(trace_who() :: {clientid | topic, binary() | list()}).
-define(TRACER, ?MODULE). -define(TRACER, ?MODULE).
-define(FORMAT, {emqx_logger_formatter, -define(FORMAT, {emqx_logger_formatter,
#{template => #{template =>
[time," [",level,"] ", [time," [",level,"] ",
{client_id, {clientid,
[{peername, [{peername,
[client_id,"@",peername," "], [clientid,"@",peername," "],
[client_id, " "]}], [clientid, " "]}],
[{peername, [{peername,
[peername," "], [peername," "],
[]}]}, []}]},
@ -45,7 +45,7 @@
-define(TOPIC_TRACE_ID(T), "trace_topic_"++T). -define(TOPIC_TRACE_ID(T), "trace_topic_"++T).
-define(CLIENT_TRACE_ID(C), "trace_clientid_"++C). -define(CLIENT_TRACE_ID(C), "trace_clientid_"++C).
-define(TOPIC_TRACE(T), {topic,T}). -define(TOPIC_TRACE(T), {topic,T}).
-define(CLIENT_TRACE(C), {client_id,C}). -define(CLIENT_TRACE(C), {clientid,C}).
-define(is_log_level(L), -define(is_log_level(L),
L =:= emergency orelse L =:= emergency orelse
@ -67,7 +67,7 @@ trace(publish, #message{from = From, topic = Topic, payload = Payload})
when is_binary(From); is_atom(From) -> when is_binary(From); is_atom(From) ->
emqx_logger:info(#{topic => Topic, mfa => {?MODULE, ?FUNCTION_NAME, ?FUNCTION_ARITY} }, "PUBLISH to ~s: ~p", [Topic, Payload]). emqx_logger:info(#{topic => Topic, mfa => {?MODULE, ?FUNCTION_NAME, ?FUNCTION_ARITY} }, "PUBLISH to ~s: ~p", [Topic, Payload]).
%% @doc Start to trace client_id or topic. %% @doc Start to trace clientid or topic.
-spec(start_trace(trace_who(), logger:level(), string()) -> ok | {error, term()}). -spec(start_trace(trace_who(), logger:level(), string()) -> ok | {error, term()}).
start_trace(Who, all, LogFile) -> start_trace(Who, all, LogFile) ->
start_trace(Who, debug, LogFile); start_trace(Who, debug, LogFile);
@ -87,7 +87,7 @@ start_trace(Who, Level, LogFile) ->
false -> {error, {invalid_log_level, Level}} false -> {error, {invalid_log_level, Level}}
end. end.
%% @doc Stop tracing client_id or topic. %% @doc Stop tracing clientid or topic.
-spec(stop_trace(trace_who()) -> ok | {error, term()}). -spec(stop_trace(trace_who()) -> ok | {error, term()}).
stop_trace(Who) -> stop_trace(Who) ->
uninstall_trance_handler(Who). uninstall_trance_handler(Who).

View File

@ -32,8 +32,8 @@
]). ]).
-export_type([ conninfo/0 -export_type([ conninfo/0
, client/0 , clientinfo/0
, client_id/0 , clientid/0
, username/0 , username/0
, password/0 , password/0
, peerhost/0 , peerhost/0
@ -78,8 +78,8 @@
]). ]).
-export_type([ caps/0 -export_type([ caps/0
, infos/0
, attrs/0 , attrs/0
, infos/0
, stats/0 , stats/0
]). ]).
@ -96,28 +96,41 @@
-type(topic() :: emqx_topic:topic()). -type(topic() :: emqx_topic:topic()).
-type(subid() :: binary() | atom()). -type(subid() :: binary() | atom()).
-type(conninfo() :: #{peername := peername(), -type(socktype() :: tcp | udp | ssl | proxy | atom()).
-type(conninfo() :: #{socktype := socktype(),
sockname := peername(), sockname := peername(),
peername := peername(),
peercert := esockd_peercert:peercert(), peercert := esockd_peercert:peercert(),
conn_mod := module(), conn_mod := module(),
atom() => term() proto_name := binary(),
proto_ver := ver(),
clean_start := boolean(),
clientid := clientid(),
username := username(),
conn_props := properties(),
connected := boolean(),
connected_at := erlang:timestamp(),
keepalive := 0..16#FFFF,
receive_maximum := non_neg_integer(),
expiry_interval := non_neg_integer(),
atom() => term()
}). }).
-type(client() :: #{zone := zone(), -type(clientinfo() :: #{zone := zone(),
protocol := protocol(), protocol := protocol(),
peerhost := peerhost(), peerhost := peerhost(),
client_id := client_id(), clientid := clientid(),
username := username(), username := username(),
peercert := esockd_peercert:peercert(), peercert := esockd_peercert:peercert(),
is_bridge := boolean(), is_bridge := boolean(),
is_superuser := boolean(), is_superuser := boolean(),
mountpoint := maybe(binary()), mountpoint := maybe(binary()),
ws_cookie := maybe(list()), ws_cookie := maybe(list()),
password => maybe(binary()), password => maybe(binary()),
auth_result => auth_result(), auth_result => auth_result(),
anonymous => boolean(), anonymous => boolean(),
atom() => term() atom() => term()
}). }).
-type(client_id() :: binary()|atom()). -type(clientid() :: binary()|atom()).
-type(username() :: maybe(binary())). -type(username() :: maybe(binary())).
-type(password() :: maybe(binary())). -type(password() :: maybe(binary())).
-type(peerhost() :: inet:ip_address()). -type(peerhost() :: inet:ip_address()).
@ -166,7 +179,7 @@
-type(command() :: #command{}). -type(command() :: #command{}).
-type(caps() :: emqx_mqtt_caps:caps()). -type(caps() :: emqx_mqtt_caps:caps()).
-type(infos() :: #{atom() => term()}).
-type(attrs() :: #{atom() => term()}). -type(attrs() :: #{atom() => term()}).
-type(stats() :: list({atom(), non_neg_integer()})). -type(infos() :: #{atom() => term()}).
-type(stats() :: #{atom() => non_neg_integer()|stats()}).

View File

@ -25,9 +25,7 @@
-logger_header("[WsConnection]"). -logger_header("[WsConnection]").
-export([ info/1 -export([ info/1
, attrs/1
, stats/1 , stats/1
, state/1
]). ]).
-export([call/2]). -export([call/2]).
@ -40,17 +38,17 @@
, terminate/3 , terminate/3
]). ]).
-record(ws_connection, { -record(state, {
%% Peername of the ws connection. %% Peername of the ws connection.
peername :: emqx_types:peername(), peername :: emqx_types:peername(),
%% Sockname of the ws connection %% Sockname of the ws connection
sockname :: emqx_types:peername(), sockname :: emqx_types:peername(),
%% FSM state %% Conn state
fsm_state :: idle | connected | disconnected, conn_state :: idle | connected | disconnected,
%% Parser State %% Parser State
parse_state :: emqx_frame:parse_state(), parse_state :: emqx_frame:parse_state(),
%% Serialize function %% Serialize function
serialize :: fun((emqx_types:packet()) -> iodata()), serialize :: emqx_frame:serialize_fun(),
%% Channel State %% Channel State
chan_state :: emqx_channel:channel(), chan_state :: emqx_channel:channel(),
%% Out Pending Packets %% Out Pending Packets
@ -59,10 +57,9 @@
stop_reason :: term() stop_reason :: term()
}). }).
-type(ws_connection() :: #ws_connection{}). -type(state() :: #state{}).
-define(INFO_KEYS, [socktype, peername, sockname, active_state]). -define(INFO_KEYS, [socktype, peername, sockname, active_state]).
-define(ATTR_KEYS, [socktype, peername, sockname]).
-define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt]). -define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt]).
-define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]). -define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]).
@ -70,53 +67,36 @@
%% API %% API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(info(pid()|ws_connection()) -> emqx_types:infos()). -spec(info(pid()|state()) -> emqx_types:infos()).
info(WsPid) when is_pid(WsPid) -> info(WsPid) when is_pid(WsPid) ->
call(WsPid, info); call(WsPid, info);
info(WsConn = #ws_connection{chan_state = ChanState}) -> info(WsConn = #state{chan_state = ChanState}) ->
ChanInfo = emqx_channel:info(ChanState), ChanInfo = emqx_channel:info(ChanState),
SockInfo = maps:from_list(info(?INFO_KEYS, WsConn)), SockInfo = maps:from_list(info(?INFO_KEYS, WsConn)),
maps:merge(ChanInfo, #{sockinfo => SockInfo}). maps:merge(ChanInfo, #{sockinfo => SockInfo}).
info(Keys, WsConn) when is_list(Keys) -> info(Keys, WsConn) when is_list(Keys) ->
[{Key, info(Key, WsConn)} || Key <- Keys]; [{Key, info(Key, WsConn)} || Key <- Keys];
info(socktype, #ws_connection{}) -> info(socktype, _State) ->
websocket; ws;
info(peername, #ws_connection{peername = Peername}) -> info(peername, #state{peername = Peername}) ->
Peername; Peername;
info(sockname, #ws_connection{sockname = Sockname}) -> info(sockname, #state{sockname = Sockname}) ->
Sockname; Sockname;
info(active_state, #ws_connection{}) -> info(active_state, _State) ->
running; running;
info(chan_state, #ws_connection{chan_state = ChanState}) -> info(chan_state, #state{chan_state = ChanState}) ->
emqx_channel:info(ChanState). emqx_channel:info(ChanState).
-spec(attrs(pid()|ws_connection()) -> emqx_types:attrs()). -spec(stats(pid()|state()) -> emqx_types:stats()).
attrs(WsPid) when is_pid(WsPid) ->
call(WsPid, attrs);
attrs(WsConn = #ws_connection{chan_state = ChanState}) ->
ChanAttrs = emqx_channel:attrs(ChanState),
SockAttrs = maps:from_list(info(?ATTR_KEYS, WsConn)),
maps:merge(ChanAttrs, #{sockinfo => SockAttrs}).
-spec(stats(pid()|ws_connection()) -> emqx_types:stats()).
stats(WsPid) when is_pid(WsPid) -> stats(WsPid) when is_pid(WsPid) ->
call(WsPid, stats); call(WsPid, stats);
stats(#ws_connection{chan_state = ChanState}) -> stats(#state{chan_state = ChanState}) ->
ProcStats = emqx_misc:proc_stats(), SockStats = emqx_pd:get_counters(?SOCK_STATS),
SockStats = wsock_stats(), ConnStats = emqx_pd:get_counters(?CONN_STATS),
ConnStats = conn_stats(),
ChanStats = emqx_channel:stats(ChanState), ChanStats = emqx_channel:stats(ChanState),
lists:append([ProcStats, SockStats, ConnStats, ChanStats]). ProcStats = emqx_misc:proc_stats(),
lists:append([SockStats, ConnStats, ChanStats, ProcStats]).
wsock_stats() ->
[{Key, emqx_pd:get_counter(Key)} || Key <- ?SOCK_STATS].
conn_stats() ->
[{Name, emqx_pd:get_counter(Name)} || Name <- ?CONN_STATS].
-spec(state(pid()) -> ws_connection()).
state(WsPid) -> call(WsPid, state).
%% kick|discard|takeover %% kick|discard|takeover
-spec(call(pid(), Req :: term()) -> Reply :: term()). -spec(call(pid(), Req :: term()) -> Reply :: term()).
@ -177,44 +157,46 @@ websocket_init([Req, Opts]) ->
[Error, Reason]), [Error, Reason]),
undefined undefined
end, end,
ChanState = emqx_channel:init(#{peername => Peername, ConnInfo = #{socktype => ws,
sockname => Sockname, peername => Peername,
peercert => Peercert, sockname => Sockname,
ws_cookie => WsCookie, peercert => Peercert,
protocol => mqtt, ws_cookie => WsCookie,
conn_mod => ?MODULE conn_mod => ?MODULE
}, Opts), },
Zone = proplists:get_value(zone, Opts), Zone = proplists:get_value(zone, Opts),
MaxSize = emqx_zone:get_env(Zone, max_packet_size, ?MAX_PACKET_SIZE), FrameOpts = emqx_zone:frame_options(Zone),
ParseState = emqx_frame:initial_parse_state(#{max_size => MaxSize}), ParseState = emqx_frame:initial_parse_state(FrameOpts),
Serialize = emqx_frame:serialize_fun(),
ChanState = emqx_channel:init(ConnInfo, Opts),
emqx_logger:set_metadata_peername(esockd_net:format(Peername)), emqx_logger:set_metadata_peername(esockd_net:format(Peername)),
{ok, #ws_connection{peername = Peername, {ok, #state{peername = Peername,
sockname = Sockname, sockname = Sockname,
fsm_state = idle, conn_state = idle,
parse_state = ParseState, parse_state = ParseState,
chan_state = ChanState, serialize = Serialize,
pendings = [], chan_state = ChanState,
serialize = serialize_fun(?MQTT_PROTO_V5, undefined)}}. pendings = []
}}.
websocket_handle({binary, Data}, State) when is_list(Data) -> websocket_handle({binary, Data}, State) when is_list(Data) ->
websocket_handle({binary, iolist_to_binary(Data)}, State); websocket_handle({binary, iolist_to_binary(Data)}, State);
websocket_handle({binary, Data}, State = #ws_connection{chan_state = ChanState}) -> websocket_handle({binary, Data}, State = #state{chan_state = ChanState}) ->
?LOG(debug, "RECV ~p", [Data]), ?LOG(debug, "RECV ~p", [Data]),
Oct = iolist_size(Data), Oct = iolist_size(Data),
ok = inc_recv_stats(1, Oct), ok = inc_recv_stats(1, Oct),
NChanState = emqx_channel:received(Oct, ChanState), NChanState = emqx_channel:received(Oct, ChanState),
NState = State#ws_connection{chan_state = NChanState}, NState = State#state{chan_state = NChanState},
process_incoming(Data, NState); process_incoming(Data, NState);
%% Pings should be replied with pongs, cowboy does it automatically %% Pings should be replied with pongs, cowboy does it automatically
%% Pongs can be safely ignored. Clause here simply prevents crash. %% Pongs can be safely ignored. Clause here simply prevents crash.
websocket_handle(Frame, State) websocket_handle(Frame, State) when Frame =:= ping; Frame =:= pong ->
when Frame =:= ping; Frame =:= pong ->
{ok, State}; {ok, State};
websocket_handle({FrameType, _}, State) websocket_handle({FrameType, _}, State) when FrameType =:= ping;
when FrameType =:= ping; FrameType =:= pong -> FrameType =:= pong ->
{ok, State}; {ok, State};
websocket_handle({FrameType, _}, State) -> websocket_handle({FrameType, _}, State) ->
@ -225,10 +207,6 @@ websocket_info({call, From, info}, State) ->
gen_server:reply(From, info(State)), gen_server:reply(From, info(State)),
{ok, State}; {ok, State};
websocket_info({call, From, attrs}, State) ->
gen_server:reply(From, attrs(State)),
{ok, State};
websocket_info({call, From, stats}, State) -> websocket_info({call, From, stats}, State) ->
gen_server:reply(From, stats(State)), gen_server:reply(From, stats(State)),
{ok, State}; {ok, State};
@ -237,63 +215,43 @@ websocket_info({call, From, state}, State) ->
gen_server:reply(From, State), gen_server:reply(From, State),
{ok, State}; {ok, State};
websocket_info({call, From, Req}, State = #ws_connection{chan_state = ChanState}) -> websocket_info({call, From, Req}, State = #state{chan_state = ChanState}) ->
case emqx_channel:handle_call(Req, ChanState) of case emqx_channel:handle_call(Req, ChanState) of
{ok, Reply, NChanState} -> {ok, Reply, NChanState} ->
_ = gen_server:reply(From, Reply), _ = gen_server:reply(From, Reply),
{ok, State#ws_connection{chan_state = NChanState}}; {ok, State#state{chan_state = NChanState}};
{stop, Reason, Reply, NChanState} -> {stop, Reason, Reply, NChanState} ->
_ = gen_server:reply(From, Reply), _ = gen_server:reply(From, Reply),
stop(Reason, State#ws_connection{chan_state = NChanState}) stop(Reason, State#state{chan_state = NChanState})
end; end;
websocket_info({cast, Msg}, State = #ws_connection{chan_state = ChanState}) -> websocket_info({cast, Msg}, State = #state{chan_state = ChanState}) ->
case emqx_channel:handle_cast(Msg, ChanState) of case emqx_channel:handle_info(Msg, ChanState) of
ok -> {ok, State};
{ok, NChanState} -> {ok, NChanState} ->
{ok, State#ws_connection{chan_state = NChanState}}; {ok, State#state{chan_state = NChanState}};
{stop, Reason, NChanState} -> {stop, Reason, NChanState} ->
stop(Reason, State#ws_connection{chan_state = NChanState}) stop(Reason, State#state{chan_state = NChanState})
end; end;
websocket_info({incoming, {error, Reason}}, State = #ws_connection{fsm_state = idle}) -> websocket_info({incoming, Packet = ?CONNECT_PACKET(ConnPkt)}, State) ->
stop({shutdown, Reason}, State); NState = State#state{serialize = emqx_frame:serialize_fun(ConnPkt)},
websocket_info({incoming, {error, Reason}}, State = #ws_connection{fsm_state = connected, chan_state = ChanState}) ->
case emqx_channel:handle_out({disconnect, emqx_reason_codes:mqtt_frame_error(Reason)}, ChanState) of
{wait_session_expire, _, NChanState} ->
?LOG(debug, "Disconnect and wait for session to expire due to ~p", [Reason]),
disconnected(State#ws_connection{chan_state= NChanState});
{wait_session_expire, _, OutPackets, NChanState} ->
?LOG(debug, "Disconnect and wait for session to expire due to ~p", [Reason]),
disconnected(enqueue(OutPackets, State#ws_connection{chan_state = NChanState}))
end;
websocket_info({incoming, {error, _Reason}}, State = #ws_connection{fsm_state = disconnected}) ->
reply(State);
websocket_info({incoming, Packet = ?CONNECT_PACKET(ConnPkt)},
State = #ws_connection{fsm_state = idle}) ->
#mqtt_packet_connect{proto_ver = ProtoVer, properties = Properties} = ConnPkt,
MaxPacketSize = emqx_mqtt_props:get('Maximum-Packet-Size', Properties, undefined),
NState = State#ws_connection{serialize = serialize_fun(ProtoVer, MaxPacketSize)},
handle_incoming(Packet, fun connected/1, NState); handle_incoming(Packet, fun connected/1, NState);
websocket_info({incoming, Packet}, State = #ws_connection{fsm_state = idle}) -> websocket_info({incoming, Packet}, State) when is_record(Packet, mqtt_packet) ->
?LOG(warning, "Unexpected incoming: ~p", [Packet]),
stop(unexpected_incoming_packet, State);
websocket_info({incoming, Packet}, State = #ws_connection{fsm_state = connected})
when is_record(Packet, mqtt_packet) ->
handle_incoming(Packet, fun reply/1, State); handle_incoming(Packet, fun reply/1, State);
websocket_info({incoming, FrameError = {frame_error, _Reason}}, State) ->
handle_incoming(FrameError, State);
websocket_info(Deliver = {deliver, _Topic, _Msg}, websocket_info(Deliver = {deliver, _Topic, _Msg},
State = #ws_connection{chan_state = ChanState}) -> State = #state{chan_state = ChanState}) ->
Delivers = emqx_misc:drain_deliver([Deliver]), Delivers = emqx_misc:drain_deliver([Deliver]),
case emqx_channel:handle_out({deliver, Delivers}, ChanState) of case emqx_channel:handle_out({deliver, Delivers}, ChanState) of
{ok, NChanState} -> {ok, NChanState} ->
reply(State#ws_connection{chan_state = NChanState}); reply(State#state{chan_state = NChanState});
{ok, Packets, NChanState} -> {ok, Packets, NChanState} ->
reply(enqueue(Packets, State#ws_connection{chan_state = NChanState})) reply(enqueue(Packets, State#state{chan_state = NChanState}))
end; end;
websocket_info({timeout, TRef, keepalive}, State) when is_reference(TRef) -> websocket_info({timeout, TRef, keepalive}, State) when is_reference(TRef) ->
@ -312,47 +270,53 @@ websocket_info({shutdown, Reason}, State) ->
websocket_info({stop, Reason}, State) -> websocket_info({stop, Reason}, State) ->
stop(Reason, State); stop(Reason, State);
websocket_info(Info, State = #ws_connection{chan_state = ChanState}) -> websocket_info(Info, State = #state{chan_state = ChanState}) ->
case emqx_channel:handle_info(Info, ChanState) of case emqx_channel:handle_info(Info, ChanState) of
{ok, NChanState} -> {ok, NChanState} ->
{ok, State#ws_connection{chan_state = NChanState}}; {ok, State#state{chan_state = NChanState}};
{stop, Reason, NChanState} -> {stop, Reason, NChanState} ->
stop(Reason, State#ws_connection{chan_state = NChanState}) stop(Reason, State#state{chan_state = NChanState})
end. end.
terminate(SockError, _Req, #ws_connection{chan_state = ChanState, terminate(SockError, _Req, #state{chan_state = ChanState,
stop_reason = Reason}) -> stop_reason = Reason}) ->
?LOG(debug, "Terminated for ~p, sockerror: ~p", [Reason, SockError]), ?LOG(debug, "Terminated for ~p, sockerror: ~p", [Reason, SockError]),
emqx_channel:terminate(Reason, ChanState). emqx_channel:terminate(Reason, ChanState).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Connected callback %% Connected callback
connected(State = #ws_connection{chan_state = ChanState}) -> connected(State = #state{chan_state = ChanState}) ->
ok = emqx_channel:handle_cast({register, attrs(State), stats(State)}, ChanState), ChanAttrs = emqx_channel:attrs(ChanState),
reply(State#ws_connection{fsm_state = connected}). SockAttrs = #{active_state => running},
Attrs = maps:merge(ChanAttrs, #{sockinfo => SockAttrs}),
ok = emqx_channel:handle_info({register, Attrs, stats(State)}, ChanState),
reply(State#state{conn_state = connected}).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Disconnected callback %% Close
disconnected(State) -> close(Reason, State) ->
reply(State#ws_connection{fsm_state = disconnected}). ?LOG(warning, "Closed for ~p", [Reason]),
reply(State#state{conn_state = disconnected}).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Handle timeout %% Handle timeout
handle_timeout(TRef, Msg, State = #ws_connection{chan_state = ChanState}) -> handle_timeout(TRef, Msg, State = #state{chan_state = ChanState}) ->
case emqx_channel:handle_timeout(TRef, Msg, ChanState) of case emqx_channel:handle_timeout(TRef, Msg, ChanState) of
{ok, NChanState} -> {ok, NChanState} ->
{ok, State#ws_connection{chan_state = NChanState}}; {ok, State#state{chan_state = NChanState}};
{ok, Packets, NChanState} -> {ok, Packets, NChanState} ->
NState = State#ws_connection{chan_state = NChanState}, NState = State#state{chan_state = NChanState},
reply(enqueue(Packets, NState)); reply(enqueue(Packets, NState));
{wait_session_expire, Reason, NChanState} -> {close, Reason, NChanState} ->
?LOG(debug, "Disconnect and wait for session to expire due to ~p", [Reason]), close(Reason, State#state{chan_state = NChanState});
disconnected(State#ws_connection{chan_state = NChanState}); {close, Reason, OutPackets, NChanState} ->
NState = State#state{chan_state= NChanState},
close(Reason, enqueue(OutPackets, NState));
{stop, Reason, NChanState} -> {stop, Reason, NChanState} ->
stop(Reason, State#ws_connection{chan_state = NChanState}) stop(Reason, State#state{chan_state = NChanState})
end. end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -361,20 +325,18 @@ handle_timeout(TRef, Msg, State = #ws_connection{chan_state = ChanState}) ->
process_incoming(<<>>, State) -> process_incoming(<<>>, State) ->
{ok, State}; {ok, State};
process_incoming(Data, State = #ws_connection{parse_state = ParseState}) -> process_incoming(Data, State = #state{parse_state = ParseState}) ->
try emqx_frame:parse(Data, ParseState) of try emqx_frame:parse(Data, ParseState) of
{more, NParseState} -> {more, NParseState} ->
{ok, State#ws_connection{parse_state = NParseState}}; {ok, State#state{parse_state = NParseState}};
{ok, Packet, Rest, NParseState} -> {ok, Packet, Rest, NParseState} ->
self() ! {incoming, Packet}, self() ! {incoming, Packet},
process_incoming(Rest, State#ws_connection{parse_state = NParseState}); process_incoming(Rest, State#state{parse_state = NParseState})
{error, Reason} ->
self() ! {incoming, {error, Reason}},
{ok, State}
catch catch
error:Reason:Stk -> error:Reason:Stk ->
?LOG(error, "~nParse failed for ~p~nStacktrace: ~p~nFrame data: ~p", [Reason, Stk, Data]), ?LOG(error, "~nParse failed for ~p~nStacktrace: ~p~nFrame data: ~p",
self() ! {incoming, {error, Reason}}, [Reason, Stk, Data]),
self() ! {incoming, {frame_error, Reason}},
{ok, State} {ok, State}
end. end.
@ -382,55 +344,59 @@ process_incoming(Data, State = #ws_connection{parse_state = ParseState}) ->
%% Handle incoming packets %% Handle incoming packets
handle_incoming(Packet = ?PACKET(Type), SuccFun, handle_incoming(Packet = ?PACKET(Type), SuccFun,
State = #ws_connection{chan_state = ChanState}) -> State = #state{chan_state = ChanState}) ->
_ = inc_incoming_stats(Type), _ = inc_incoming_stats(Type),
ok = emqx_metrics:inc_recv(Packet), _ = emqx_metrics:inc_recv(Packet),
?LOG(debug, "RECV ~s", [emqx_packet:format(Packet)]), ?LOG(debug, "RECV ~s", [emqx_packet:format(Packet)]),
case emqx_channel:handle_in(Packet, ChanState) of case emqx_channel:handle_in(Packet, ChanState) of
{ok, NChanState} -> {ok, NChanState} ->
SuccFun(State#ws_connection{chan_state= NChanState}); SuccFun(State#state{chan_state= NChanState});
{ok, OutPackets, NChanState} -> {ok, OutPackets, NChanState} ->
NState = State#ws_connection{chan_state= NChanState}, NState = State#state{chan_state= NChanState},
SuccFun(enqueue(OutPackets, NState)); SuccFun(enqueue(OutPackets, NState));
{wait_session_expire, Reason, NChanState} -> {close, Reason, NChanState} ->
?LOG(debug, "Disconnect and wait for session to expire due to ~p", [Reason]), close(Reason, State#state{chan_state = NChanState});
disconnected(State#ws_connection{chan_state = NChanState}); {close, Reason, OutPackets, NChanState} ->
{wait_session_expire, Reason, OutPackets, NChanState} -> NState = State#state{chan_state= NChanState},
?LOG(debug, "Disconnect and wait for session to expire due to ~p", [Reason]), close(Reason, enqueue(OutPackets, NState));
disconnected(enqueue(OutPackets, State#ws_connection{chan_state = NChanState}));
{stop, Reason, NChanState} -> {stop, Reason, NChanState} ->
stop(Reason, State#ws_connection{chan_state = NChanState}); stop(Reason, State#state{chan_state = NChanState});
{stop, Reason, OutPackets, NChanState} -> {stop, Reason, OutPackets, NChanState} ->
NState = State#ws_connection{chan_state= NChanState}, NState = State#state{chan_state= NChanState},
stop(Reason, enqueue(OutPackets, NState))
end.
handle_incoming(FrameError = {frame_error, _Reason},
State = #state{chan_state = ChanState}) ->
case emqx_channel:handle_in(FrameError, ChanState) of
{stop, Reason, NChanState} ->
stop(Reason, State#state{chan_state = NChanState});
{stop, Reason, OutPackets, NChanState} ->
NState = State#state{chan_state = NChanState},
stop(Reason, enqueue(OutPackets, NState)) stop(Reason, enqueue(OutPackets, NState))
end. end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Handle outgoing packets %% Handle outgoing packets
handle_outgoing(Packets, State = #ws_connection{serialize = Serialize, handle_outgoing(Packets, State = #state{chan_state = ChanState}) ->
chan_state = ChanState}) -> IoData = lists:map(serialize_and_inc_stats_fun(State), Packets),
Data = lists:map(Serialize, Packets), Oct = iolist_size(IoData),
Oct = iolist_size(Data),
ok = inc_sent_stats(length(Packets), Oct), ok = inc_sent_stats(length(Packets), Oct),
NChanState = emqx_channel:sent(Oct, ChanState), NChanState = emqx_channel:sent(Oct, ChanState),
{{binary, Data}, State#ws_connection{chan_state = NChanState}}. {{binary, IoData}, State#state{chan_state = NChanState}}.
%%-------------------------------------------------------------------- %% TODO: Duplicated with emqx_channel:serialize_and_inc_stats_fun/1
%% Serialize fun serialize_and_inc_stats_fun(#state{serialize = Serialize}) ->
serialize_fun(ProtoVer, MaxPacketSize) ->
fun(Packet = ?PACKET(Type)) -> fun(Packet = ?PACKET(Type)) ->
IoData = emqx_frame:serialize(Packet, ProtoVer), case Serialize(Packet) of
case Type =/= ?PUBLISH orelse MaxPacketSize =:= undefined orelse iolist_size(IoData) =< MaxPacketSize of <<>> -> ?LOG(warning, "~s is discarded due to the frame is too large!",
true -> [emqx_packet:format(Packet)]),
?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]), <<>>;
_ = inc_outgoing_stats(Type), Data -> _ = inc_outgoing_stats(Type),
_ = emqx_metrics:inc_sent(Packet), _ = emqx_metrics:inc_sent(Packet),
IoData; ?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]),
false -> Data
?LOG(warning, "DROP ~s due to oversize packet size", [emqx_packet:format(Packet)]),
<<"">>
end end
end. end.
@ -469,21 +435,21 @@ inc_sent_stats(Cnt, Oct) ->
-compile({inline, [reply/1]}). -compile({inline, [reply/1]}).
reply(State = #ws_connection{pendings = []}) -> reply(State = #state{pendings = []}) ->
{ok, State}; {ok, State};
reply(State = #ws_connection{pendings = Pendings}) -> reply(State = #state{pendings = Pendings}) ->
{Reply, NState} = handle_outgoing(Pendings, State), {Reply, NState} = handle_outgoing(Pendings, State),
{reply, Reply, NState#ws_connection{pendings = []}}. {reply, Reply, NState#state{pendings = []}}.
stop(Reason, State = #ws_connection{pendings = []}) -> stop(Reason, State = #state{pendings = []}) ->
{stop, State#ws_connection{stop_reason = Reason}}; {stop, State#state{stop_reason = Reason}};
stop(Reason, State = #ws_connection{pendings = Pendings}) -> stop(Reason, State = #state{pendings = Pendings}) ->
{Reply, State1} = handle_outgoing(Pendings, State), {Reply, State1} = handle_outgoing(Pendings, State),
State2 = State1#ws_connection{pendings = [], stop_reason = Reason}, State2 = State1#state{pendings = [], stop_reason = Reason},
{reply, [Reply, close], State2}. {reply, [Reply, close], State2}.
enqueue(Packet, State) when is_record(Packet, mqtt_packet) -> enqueue(Packet, State) when is_record(Packet, mqtt_packet) ->
enqueue([Packet], State); enqueue([Packet], State);
enqueue(Packets, State = #ws_connection{pendings = Pendings}) -> enqueue(Packets, State = #state{pendings = Pendings}) ->
State#ws_connection{pendings = lists:append(Pendings, Packets)}. State#state{pendings = lists:append(Pendings, Packets)}.

View File

@ -19,6 +19,7 @@
-behaviour(gen_server). -behaviour(gen_server).
-include("emqx.hrl"). -include("emqx.hrl").
-include("emqx_mqtt.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("types.hrl"). -include("types.hrl").
@ -27,7 +28,10 @@
%% APIs %% APIs
-export([start_link/0, stop/0]). -export([start_link/0, stop/0]).
-export([ use_username_as_clientid/1 -export([ frame_options/1
, mqtt_strict_mode/1
, max_packet_size/1
, use_username_as_clientid/1
, enable_stats/1 , enable_stats/1
, enable_acl/1 , enable_acl/1
, enable_ban/1 , enable_ban/1
@ -40,6 +44,8 @@
, force_shutdown_policy/1 , force_shutdown_policy/1
]). ]).
-export([check_oom/2]).
-export([ get_env/2 -export([ get_env/2
, get_env/3 , get_env/3
, set_env/3 , set_env/3
@ -76,6 +82,20 @@
start_link() -> start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
-spec(frame_options(zone()) -> emqx_frame:options()).
frame_options(Zone) ->
#{strict_mode => mqtt_strict_mode(Zone),
max_size => max_packet_size(Zone)
}.
-spec(mqtt_strict_mode(zone()) -> boolean()).
mqtt_strict_mode(Zone) ->
get_env(Zone, mqtt_strict_mode, false).
-spec(max_packet_size(zone()) -> integer()).
max_packet_size(Zone) ->
get_env(Zone, max_packet_size, ?MAX_PACKET_SIZE).
-spec(use_username_as_clientid(zone()) -> boolean()). -spec(use_username_as_clientid(zone()) -> boolean()).
use_username_as_clientid(Zone) -> use_username_as_clientid(Zone) ->
get_env(Zone, use_username_as_clientid, false). get_env(Zone, use_username_as_clientid, false).
@ -120,6 +140,19 @@ force_gc_policy(Zone) ->
force_shutdown_policy(Zone) -> force_shutdown_policy(Zone) ->
get_env(Zone, force_shutdown_policy). get_env(Zone, force_shutdown_policy).
-spec(check_oom(zone(), fun()) -> ok | term()).
check_oom(Zone, Action) ->
case emqx_zone:force_shutdown_policy(Zone) of
undefined -> ok;
Policy -> do_check_oom(emqx_oom:init(Policy), Action)
end.
do_check_oom(OomPolicy, Action) ->
case emqx_oom:check(OomPolicy) of
ok -> ok;
Shutdown -> Action(Shutdown)
end.
-spec(get_env(maybe(zone()), atom()) -> maybe(term())). -spec(get_env(maybe(zone()), atom()) -> maybe(term())).
get_env(undefined, Key) -> emqx:get_env(Key); get_env(undefined, Key) -> emqx:get_env(Key);
get_env(Zone, Key) -> get_env(Zone, Key) ->

View File

@ -43,7 +43,7 @@ t_get_env(_) ->
t_emqx_pubsub_api(_) -> t_emqx_pubsub_api(_) ->
emqx:start(), emqx:start(),
true = emqx:is_running(node()), true = emqx:is_running(node()),
{ok, C} = emqtt:start_link([{host, "localhost"}, {client_id, "myclient"}]), {ok, C} = emqtt:start_link([{host, "localhost"}, {clientid, "myclient"}]),
{ok, _} = emqtt:connect(C), {ok, _} = emqtt:connect(C),
ClientId = <<"myclient">>, ClientId = <<"myclient">>,
Topic = <<"mytopic">>, Topic = <<"mytopic">>,

View File

@ -113,7 +113,7 @@ t_reload_acl(_) ->
t_check_acl_1(_) -> t_check_acl_1(_) ->
Client = #{zone => external, Client = #{zone => external,
client_id => <<"client1">>, clientid => <<"client1">>,
username => <<"testuser">> username => <<"testuser">>
}, },
allow = ?AC:check_acl(Client, subscribe, <<"users/testuser/1">>), allow = ?AC:check_acl(Client, subscribe, <<"users/testuser/1">>),
@ -124,14 +124,14 @@ t_check_acl_1(_) ->
t_check_acl_2(_) -> t_check_acl_2(_) ->
Client = #{zone => external, Client = #{zone => external,
client_id => <<"client2">>, clientid => <<"client2">>,
username => <<"xyz">> username => <<"xyz">>
}, },
deny = ?AC:check_acl(Client, subscribe, <<"a/b/c">>). deny = ?AC:check_acl(Client, subscribe, <<"a/b/c">>).
t_acl_cache_basic(_) -> t_acl_cache_basic(_) ->
Client = #{zone => external, Client = #{zone => external,
client_id => <<"client1">>, clientid => <<"client1">>,
username => <<"testuser">> username => <<"testuser">>
}, },
not_found = ?CACHE:get_acl_cache(subscribe, <<"users/testuser/1">>), not_found = ?CACHE:get_acl_cache(subscribe, <<"users/testuser/1">>),
@ -146,7 +146,7 @@ t_acl_cache_basic(_) ->
t_acl_cache_expiry(_) -> t_acl_cache_expiry(_) ->
application:set_env(emqx, acl_cache_ttl, 100), application:set_env(emqx, acl_cache_ttl, 100),
Client = #{zone => external, Client = #{zone => external,
client_id => <<"client1">>, clientid => <<"client1">>,
username => <<"testuser">> username => <<"testuser">>
}, },
allow = ?AC:check_acl(Client, subscribe, <<"clients/client1">>), allow = ?AC:check_acl(Client, subscribe, <<"clients/client1">>),
@ -157,7 +157,7 @@ t_acl_cache_expiry(_) ->
t_acl_cache_full(_) -> t_acl_cache_full(_) ->
application:set_env(emqx, acl_cache_max_size, 1), application:set_env(emqx, acl_cache_max_size, 1),
Client = #{zone => external, Client = #{zone => external,
client_id => <<"client1">>, clientid => <<"client1">>,
username => <<"testuser">> username => <<"testuser">>
}, },
allow = ?AC:check_acl(Client, subscribe, <<"users/testuser/1">>), allow = ?AC:check_acl(Client, subscribe, <<"users/testuser/1">>),
@ -173,7 +173,7 @@ t_acl_cache_cleanup(_) ->
application:set_env(emqx, acl_cache_ttl, 100), application:set_env(emqx, acl_cache_ttl, 100),
application:set_env(emqx, acl_cache_max_size, 2), application:set_env(emqx, acl_cache_max_size, 2),
Client = #{zone => external, Client = #{zone => external,
client_id => <<"client1">>, clientid => <<"client1">>,
username => <<"testuser">> username => <<"testuser">>
}, },
allow = ?AC:check_acl(Client, subscribe, <<"users/testuser/1">>), allow = ?AC:check_acl(Client, subscribe, <<"users/testuser/1">>),
@ -345,12 +345,12 @@ t_compile_rule(_) ->
t_match_rule(_) -> t_match_rule(_) ->
ClientInfo1 = #{zone => external, ClientInfo1 = #{zone => external,
client_id => <<"testClient">>, clientid => <<"testClient">>,
username => <<"TestUser">>, username => <<"TestUser">>,
peerhost => {127,0,0,1} peerhost => {127,0,0,1}
}, },
ClientInfo2 = #{zone => external, ClientInfo2 = #{zone => external,
client_id => <<"testClient">>, clientid => <<"testClient">>,
username => <<"TestUser">>, username => <<"TestUser">>,
peerhost => {192,168,0,10} peerhost => {192,168,0,10}
}, },

View File

@ -37,7 +37,7 @@ end_per_suite(_Config) ->
ekka_mnesia:delete_schema(). ekka_mnesia:delete_schema().
t_add_delete(_) -> t_add_delete(_) ->
Banned = #banned{who = {client_id, <<"TestClient">>}, Banned = #banned{who = {clientid, <<"TestClient">>},
reason = <<"test">>, reason = <<"test">>,
by = <<"banned suite">>, by = <<"banned suite">>,
desc = <<"test">>, desc = <<"test">>,
@ -45,27 +45,27 @@ t_add_delete(_) ->
}, },
ok = emqx_banned:add(Banned), ok = emqx_banned:add(Banned),
?assertEqual(1, emqx_banned:info(size)), ?assertEqual(1, emqx_banned:info(size)),
ok = emqx_banned:delete({client_id, <<"TestClient">>}), ok = emqx_banned:delete({clientid, <<"TestClient">>}),
?assertEqual(0, emqx_banned:info(size)). ?assertEqual(0, emqx_banned:info(size)).
t_check(_) -> t_check(_) ->
ok = emqx_banned:add(#banned{who = {client_id, <<"BannedClient">>}}), ok = emqx_banned:add(#banned{who = {clientid, <<"BannedClient">>}}),
ok = emqx_banned:add(#banned{who = {username, <<"BannedUser">>}}), ok = emqx_banned:add(#banned{who = {username, <<"BannedUser">>}}),
ok = emqx_banned:add(#banned{who = {ipaddr, {192,168,0,1}}}), ok = emqx_banned:add(#banned{who = {ipaddr, {192,168,0,1}}}),
?assertEqual(3, emqx_banned:info(size)), ?assertEqual(3, emqx_banned:info(size)),
ClientInfo1 = #{client_id => <<"BannedClient">>, ClientInfo1 = #{clientid => <<"BannedClient">>,
username => <<"user">>, username => <<"user">>,
peerhost => {127,0,0,1} peerhost => {127,0,0,1}
}, },
ClientInfo2 = #{client_id => <<"client">>, ClientInfo2 = #{clientid => <<"client">>,
username => <<"BannedUser">>, username => <<"BannedUser">>,
peerhost => {127,0,0,1} peerhost => {127,0,0,1}
}, },
ClientInfo3 = #{client_id => <<"client">>, ClientInfo3 = #{clientid => <<"client">>,
username => <<"user">>, username => <<"user">>,
peerhost => {192,168,0,1} peerhost => {192,168,0,1}
}, },
ClientInfo4 = #{client_id => <<"client">>, ClientInfo4 = #{clientid => <<"client">>,
username => <<"user">>, username => <<"user">>,
peerhost => {127,0,0,1} peerhost => {127,0,0,1}
}, },
@ -73,7 +73,7 @@ t_check(_) ->
?assert(emqx_banned:check(ClientInfo2)), ?assert(emqx_banned:check(ClientInfo2)),
?assert(emqx_banned:check(ClientInfo3)), ?assert(emqx_banned:check(ClientInfo3)),
?assertNot(emqx_banned:check(ClientInfo4)), ?assertNot(emqx_banned:check(ClientInfo4)),
ok = emqx_banned:delete({client_id, <<"BannedClient">>}), ok = emqx_banned:delete({clientid, <<"BannedClient">>}),
ok = emqx_banned:delete({username, <<"BannedUser">>}), ok = emqx_banned:delete({username, <<"BannedUser">>}),
ok = emqx_banned:delete({ipaddr, {192,168,0,1}}), ok = emqx_banned:delete({ipaddr, {192,168,0,1}}),
?assertNot(emqx_banned:check(ClientInfo1)), ?assertNot(emqx_banned:check(ClientInfo1)),
@ -84,7 +84,7 @@ t_check(_) ->
t_unused(_) -> t_unused(_) ->
{ok, Banned} = emqx_banned:start_link(), {ok, Banned} = emqx_banned:start_link(),
ok = emqx_banned:add(#banned{who = {client_id, <<"BannedClient">>}, ok = emqx_banned:add(#banned{who = {clientid, <<"BannedClient">>},
until = erlang:system_time(second) until = erlang:system_time(second)
}), }),
?assertEqual(ignored, gen_server:call(Banned, unexpected_req)), ?assertEqual(ignored, gen_server:call(Banned, unexpected_req)),

View File

@ -28,6 +28,21 @@
-include("emqx_mqtt.hrl"). -include("emqx_mqtt.hrl").
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-define(DEFAULT_CONNINFO,
#{peername => {{127,0,0,1}, 3456},
sockname => {{127,0,0,1}, 1883},
conn_mod => emqx_connection,
proto_name => <<"MQTT">>,
proto_ver => ?MQTT_PROTO_V5,
clean_start => true,
keepalive => 30,
clientid => <<"clientid">>,
username => <<"username">>,
conn_props => #{},
receive_maximum => 100,
expiry_interval => 0
}).
all() -> emqx_ct:all(?MODULE). all() -> emqx_ct:all(?MODULE).
init_per_suite(Config) -> init_per_suite(Config) ->
@ -50,7 +65,7 @@ t_handle_connect(_) ->
clean_start = true, clean_start = true,
keepalive = 30, keepalive = 30,
properties = #{}, properties = #{},
client_id = <<"clientid">>, clientid = <<"clientid">>,
username = <<"username">>, username = <<"username">>,
password = <<"passwd">> password = <<"passwd">>
}, },
@ -58,20 +73,21 @@ t_handle_connect(_) ->
fun(Channel) -> fun(Channel) ->
{ok, ?CONNACK_PACKET(?RC_SUCCESS), Channel1} {ok, ?CONNACK_PACKET(?RC_SUCCESS), Channel1}
= handle_in(?CONNECT_PACKET(ConnPkt), Channel), = handle_in(?CONNECT_PACKET(ConnPkt), Channel),
#{client_id := ClientId, username := Username} #{clientid := ClientId, username := Username}
= emqx_channel:info(client, Channel1), = emqx_channel:info(clientinfo, Channel1),
?assertEqual(<<"clientid">>, ClientId), ?assertEqual(<<"clientid">>, ClientId),
?assertEqual(<<"username">>, Username) ?assertEqual(<<"username">>, Username)
end). end).
t_handle_publish_qos0(_) -> t_handle_in_publish_qos0(_) ->
with_channel( with_channel(
fun(Channel) -> fun(Channel) ->
Publish = ?PUBLISH_PACKET(?QOS_0, <<"topic">>, undefined, <<"payload">>), Publish = ?PUBLISH_PACKET(?QOS_0, <<"topic">>, undefined, <<"payload">>),
{ok, Channel} = handle_in(Publish, Channel) {ok, Channel1} = handle_in(Publish, Channel),
?assertEqual(#{publish_in => 1}, emqx_channel:info(pub_stats, Channel1))
end). end).
t_handle_publish_qos1(_) -> t_handle_in_publish_qos1(_) ->
with_channel( with_channel(
fun(Channel) -> fun(Channel) ->
Publish = ?PUBLISH_PACKET(?QOS_1, <<"topic">>, 1, <<"payload">>), Publish = ?PUBLISH_PACKET(?QOS_1, <<"topic">>, 1, <<"payload">>),
@ -91,30 +107,34 @@ t_handle_publish_qos2(_) ->
?assertEqual(2, AwaitingRel) ?assertEqual(2, AwaitingRel)
end). end).
t_handle_puback(_) -> t_handle_in_puback(_) ->
with_channel( with_channel(
fun(Channel) -> fun(Channel) ->
{ok, Channel} = handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), Channel) {ok, Channel1} = handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), Channel),
?assertEqual(#{puback_in => 1}, emqx_channel:info(pub_stats, Channel1))
end). end).
t_handle_pubrec(_) -> t_handle_in_pubrec(_) ->
with_channel( with_channel(
fun(Channel) -> fun(Channel) ->
{ok, ?PUBREL_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel} {ok, ?PUBREL_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel1}
= handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), Channel) = handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), Channel),
?assertEqual(#{pubrec_in => 1, pubrel_out => 1}, emqx_channel:info(pub_stats, Channel1))
end). end).
t_handle_pubrel(_) -> t_handle_in_pubrel(_) ->
with_channel( with_channel(
fun(Channel) -> fun(Channel) ->
{ok, ?PUBCOMP_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel} {ok, ?PUBCOMP_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel1}
= handle_in(?PUBREL_PACKET(1, ?RC_SUCCESS), Channel) = handle_in(?PUBREL_PACKET(1, ?RC_SUCCESS), Channel),
?assertEqual(#{pubrel_in => 1, pubcomp_out => 1}, emqx_channel:info(pub_stats, Channel1))
end). end).
t_handle_pubcomp(_) -> t_handle_in_pubcomp(_) ->
with_channel( with_channel(
fun(Channel) -> fun(Channel) ->
{ok, Channel} = handle_in(?PUBCOMP_PACKET(1, ?RC_SUCCESS), Channel) {ok, Channel1} = handle_in(?PUBCOMP_PACKET(1, ?RC_SUCCESS), Channel),
?assertEqual(#{pubcomp_in => 1}, emqx_channel:info(pub_stats, Channel1))
end). end).
t_handle_subscribe(_) -> t_handle_subscribe(_) ->
@ -144,14 +164,15 @@ t_handle_pingreq(_) ->
t_handle_disconnect(_) -> t_handle_disconnect(_) ->
with_channel( with_channel(
fun(Channel) -> fun(Channel) ->
{wait_session_expire, {shutdown, normal}, Channel1} = handle_in(?DISCONNECT_PACKET(?RC_SUCCESS), Channel), {stop, normal, Channel1} = handle_in(?DISCONNECT_PACKET(?RC_SUCCESS), Channel),
?assertEqual(undefined, emqx_channel:info(will_msg, Channel1)) ?assertEqual(undefined, emqx_channel:info(will_msg, Channel1))
end). end).
t_handle_auth(_) -> t_handle_in_auth(_) ->
with_channel( with_channel(
fun(Channel) -> fun(Channel) ->
{ok, Channel} = handle_in(?AUTH_PACKET(), Channel) Packet = ?DISCONNECT_PACKET(?RC_IMPLEMENTATION_SPECIFIC_ERROR),
{stop, implementation_specific_error, Packet, Channel} = handle_in(?AUTH_PACKET(), Channel)
end). end).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -175,13 +196,13 @@ t_handle_deliver(_) ->
%% Test cases for handle_out %% Test cases for handle_out
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
t_handle_connack(_) -> t_handle_out_connack(_) ->
ConnPkt = #mqtt_packet_connect{ ConnPkt = #mqtt_packet_connect{
proto_name = <<"MQTT">>, proto_name = <<"MQTT">>,
proto_ver = ?MQTT_PROTO_V4, proto_ver = ?MQTT_PROTO_V4,
clean_start = true, clean_start = true,
properties = #{}, properties = #{},
client_id = <<"clientid">> clientid = <<"clientid">>
}, },
with_channel( with_channel(
fun(Channel) -> fun(Channel) ->
@ -199,39 +220,44 @@ t_handle_out_publish(_) ->
Pub1 = {publish, 1, emqx_message:make(<<"c">>, ?QOS_1, <<"t">>, <<"qos1">>)}, Pub1 = {publish, 1, emqx_message:make(<<"c">>, ?QOS_1, <<"t">>, <<"qos1">>)},
{ok, ?PUBLISH_PACKET(?QOS_0), Channel} = handle_out(Pub0, Channel), {ok, ?PUBLISH_PACKET(?QOS_0), Channel} = handle_out(Pub0, Channel),
{ok, ?PUBLISH_PACKET(?QOS_1), Channel} = handle_out(Pub1, Channel), {ok, ?PUBLISH_PACKET(?QOS_1), Channel} = handle_out(Pub1, Channel),
{ok, Packets, Channel} = handle_out({publish, [Pub0, Pub1]}, Channel), {ok, Packets, Channel1} = handle_out({publish, [Pub0, Pub1]}, Channel),
?assertEqual(2, length(Packets)) ?assertEqual(2, length(Packets)),
?assertEqual(#{publish_out => 2}, emqx_channel:info(pub_stats, Channel1))
end). end).
t_handle_out_puback(_) -> t_handle_out_puback(_) ->
with_channel( with_channel(
fun(Channel) -> fun(Channel) ->
{ok, Channel} = handle_out({puberr, ?RC_NOT_AUTHORIZED}, Channel), {ok, Channel} = handle_out({puberr, ?RC_NOT_AUTHORIZED}, Channel),
{ok, ?PUBACK_PACKET(1, ?RC_SUCCESS), Channel} {ok, ?PUBACK_PACKET(1, ?RC_SUCCESS), Channel1}
= handle_out({puback, 1, ?RC_SUCCESS}, Channel) = handle_out({puback, 1, ?RC_SUCCESS}, Channel),
?assertEqual(#{puback_out => 1}, emqx_channel:info(pub_stats, Channel1))
end). end).
t_handle_out_pubrec(_) -> t_handle_out_pubrec(_) ->
with_channel( with_channel(
fun(Channel) -> fun(Channel) ->
{ok, ?PUBREC_PACKET(4, ?RC_SUCCESS), Channel} {ok, ?PUBREC_PACKET(4, ?RC_SUCCESS), Channel1}
= handle_out({pubrec, 4, ?RC_SUCCESS}, Channel) = handle_out({pubrec, 4, ?RC_SUCCESS}, Channel),
?assertEqual(#{pubrec_out => 1}, emqx_channel:info(pub_stats, Channel1))
end). end).
t_handle_out_pubrel(_) -> t_handle_out_pubrel(_) ->
with_channel( with_channel(
fun(Channel) -> fun(Channel) ->
{ok, ?PUBREL_PACKET(2), Channel} {ok, ?PUBREL_PACKET(2), Channel1}
= handle_out({pubrel, 2, ?RC_SUCCESS}, Channel), = handle_out({pubrel, 2, ?RC_SUCCESS}, Channel),
{ok, ?PUBREL_PACKET(3, ?RC_SUCCESS), Channel} {ok, ?PUBREL_PACKET(3, ?RC_SUCCESS), Channel2}
= handle_out({pubrel, 3, ?RC_SUCCESS}, Channel) = handle_out({pubrel, 3, ?RC_SUCCESS}, Channel1),
?assertEqual(#{pubrel_out => 2}, emqx_channel:info(pub_stats, Channel2))
end). end).
t_handle_out_pubcomp(_) -> t_handle_out_pubcomp(_) ->
with_channel( with_channel(
fun(Channel) -> fun(Channel) ->
{ok, ?PUBCOMP_PACKET(5, ?RC_SUCCESS), Channel} {ok, ?PUBCOMP_PACKET(5, ?RC_SUCCESS), Channel1}
= handle_out({pubcomp, 5, ?RC_SUCCESS}, Channel) = handle_out({pubcomp, 5, ?RC_SUCCESS}, Channel),
?assertEqual(#{pubcomp_out => 1}, emqx_channel:info(pub_stats, Channel1))
end). end).
t_handle_out_suback(_) -> t_handle_out_suback(_) ->
@ -279,32 +305,22 @@ t_terminate(_) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
with_channel(TestFun) -> with_channel(TestFun) ->
ConnInfo = #{peername => {{127,0,0,1}, 3456}, with_channel(#{}, TestFun).
sockname => {{127,0,0,1}, 1883},
protocol => mqtt, with_channel(ConnInfo, TestFun) ->
conn_mod => emqx_connection, ConnInfo1 = maps:merge(?DEFAULT_CONNINFO, ConnInfo),
proto_name => <<"MQTT">>,
proto_ver => ?MQTT_PROTO_V5,
clean_start => true,
keepalive => 30,
client_id => <<"clientid">>,
username => <<"username">>,
conn_props => #{},
receive_maximum => 100,
expiry_interval => 60
},
ClientInfo = #{zone => <<"external">>, ClientInfo = #{zone => <<"external">>,
protocol => mqtt, protocol => mqtt,
peerhost => {127,0,0,1}, peerhost => {127,0,0,1},
client_id => <<"clientid">>, clientid => <<"clientid">>,
username => <<"username">>, username => <<"username">>,
peercert => undefined, peercert => undefined,
is_bridge => false, is_bridge => false,
is_superuser => false, is_superuser => false,
mountpoint => undefined mountpoint => undefined
}, },
Channel = emqx_channel:init(ConnInfo, [{zone, testing}]), Channel = emqx_channel:init(ConnInfo1, [{zone, testing}]),
Session = emqx_session:init(ClientInfo, ConnInfo), Session = emqx_session:init(ClientInfo, ConnInfo1),
Channel1 = emqx_channel:set_field(client, ClientInfo, Channel), Channel1 = emqx_channel:set_field(clientinfo, ClientInfo, Channel),
TestFun(emqx_channel:set_field(session, Session, Channel1)). TestFun(emqx_channel:set_field(session, Session, Channel1)).

View File

@ -95,10 +95,10 @@ t_cm(_) ->
IdleTimeout = emqx_zone:get_env(external, idle_timeout, 30000), IdleTimeout = emqx_zone:get_env(external, idle_timeout, 30000),
emqx_zone:set_env(external, idle_timeout, 1000), emqx_zone:set_env(external, idle_timeout, 1000),
ClientId = <<"myclient">>, ClientId = <<"myclient">>,
{ok, C} = emqtt:start_link([{client_id, ClientId}]), {ok, C} = emqtt:start_link([{clientid, ClientId}]),
{ok, _} = emqtt:connect(C), {ok, _} = emqtt:connect(C),
ct:sleep(50), ct:sleep(50),
#{client := #{client_id := ClientId}} = emqx_cm:get_chan_attrs(ClientId), #{clientinfo := #{clientid := ClientId}} = emqx_cm:get_chan_attrs(ClientId),
emqtt:subscribe(C, <<"mytopic">>, 0), emqtt:subscribe(C, <<"mytopic">>, 0),
ct:sleep(1200), ct:sleep(1200),
Stats = emqx_cm:get_chan_stats(ClientId), Stats = emqx_cm:get_chan_stats(ClientId),
@ -135,13 +135,13 @@ t_will_message(_Config) ->
t_offline_message_queueing(_) -> t_offline_message_queueing(_) ->
{ok, C1} = emqtt:start_link([{clean_start, false}, {ok, C1} = emqtt:start_link([{clean_start, false},
{client_id, <<"c1">>}]), {clientid, <<"c1">>}]),
{ok, _} = emqtt:connect(C1), {ok, _} = emqtt:connect(C1),
{ok, _, [2]} = emqtt:subscribe(C1, nth(6, ?WILD_TOPICS), 2), {ok, _, [2]} = emqtt:subscribe(C1, nth(6, ?WILD_TOPICS), 2),
ok = emqtt:disconnect(C1), ok = emqtt:disconnect(C1),
{ok, C2} = emqtt:start_link([{clean_start, true}, {ok, C2} = emqtt:start_link([{clean_start, true},
{client_id, <<"c2">>}]), {clientid, <<"c2">>}]),
{ok, _} = emqtt:connect(C2), {ok, _} = emqtt:connect(C2),
ok = emqtt:publish(C2, nth(2, ?TOPICS), <<"qos 0">>, 0), ok = emqtt:publish(C2, nth(2, ?TOPICS), <<"qos 0">>, 0),
@ -149,8 +149,7 @@ t_offline_message_queueing(_) ->
{ok, _} = emqtt:publish(C2, nth(4, ?TOPICS), <<"qos 2">>, 2), {ok, _} = emqtt:publish(C2, nth(4, ?TOPICS), <<"qos 2">>, 2),
timer:sleep(10), timer:sleep(10),
emqtt:disconnect(C2), emqtt:disconnect(C2),
{ok, C3} = emqtt:start_link([{clean_start, false}, {ok, C3} = emqtt:start_link([{clean_start, false}, {clientid, <<"c1">>}]),
{client_id, <<"c1">>}]),
{ok, _} = emqtt:connect(C3), {ok, _} = emqtt:connect(C3),
timer:sleep(10), timer:sleep(10),
@ -198,8 +197,7 @@ t_overlapping_subscriptions(_) ->
t_redelivery_on_reconnect(_) -> t_redelivery_on_reconnect(_) ->
ct:pal("Redelivery on reconnect test starting"), ct:pal("Redelivery on reconnect test starting"),
{ok, C1} = emqtt:start_link([{clean_start, false}, {ok, C1} = emqtt:start_link([{clean_start, false}, {clientid, <<"c">>}]),
{client_id, <<"c">>}]),
{ok, _} = emqtt:connect(C1), {ok, _} = emqtt:connect(C1),
{ok, _, [2]} = emqtt:subscribe(C1, nth(7, ?WILD_TOPICS), 2), {ok, _, [2]} = emqtt:subscribe(C1, nth(7, ?WILD_TOPICS), 2),
@ -212,8 +210,7 @@ t_redelivery_on_reconnect(_) ->
timer:sleep(10), timer:sleep(10),
ok = emqtt:disconnect(C1), ok = emqtt:disconnect(C1),
?assertEqual(0, length(recv_msgs(2))), ?assertEqual(0, length(recv_msgs(2))),
{ok, C2} = emqtt:start_link([{clean_start, false}, {ok, C2} = emqtt:start_link([{clean_start, false}, {clientid, <<"c">>}]),
{client_id, <<"c">>}]),
{ok, _} = emqtt:connect(C2), {ok, _} = emqtt:connect(C2),
timer:sleep(10), timer:sleep(10),

View File

@ -64,17 +64,17 @@ t_get_set_chan_stats(_) ->
t_open_session(_) -> t_open_session(_) ->
ClientInfo = #{zone => external, ClientInfo = #{zone => external,
client_id => <<"clientid">>, clientid => <<"clientid">>,
username => <<"username">>, username => <<"username">>,
peerhost => {127,0,0,1}}, peerhost => {127,0,0,1}},
ConnInfo = #{peername => {{127,0,0,1}, 5000}, ConnInfo = #{peername => {{127,0,0,1}, 5000},
receive_maximum => 100}, receive_maximum => 100},
{ok, #{session := Session1, present := false}} {ok, #{session := Session1, present := false}}
= emqx_cm:open_session(true, ClientInfo, ConnInfo), = emqx_cm:open_session(true, ClientInfo, ConnInfo),
?assertEqual(100, emqx_session:info(max_inflight, Session1)), ?assertEqual(100, emqx_session:info(inflight_max, Session1)),
{ok, #{session := Session2, present := false}} {ok, #{session := Session2, present := false}}
= emqx_cm:open_session(false, ClientInfo, ConnInfo), = emqx_cm:open_session(false, ClientInfo, ConnInfo),
?assertEqual(100, emqx_session:info(max_inflight, Session2)). ?assertEqual(100, emqx_session:info(inflight_max, Session2)).
t_discard_session(_) -> t_discard_session(_) ->
ok = emqx_cm:discard_session(<<"clientid">>). ok = emqx_cm:discard_session(<<"clientid">>).

View File

@ -33,7 +33,7 @@ end_per_suite(_Config) ->
t_basic(_) -> t_basic(_) ->
Topic = <<"TopicA">>, Topic = <<"TopicA">>,
{ok, C} = emqtt:start_link([{port, 1883}, {client_id, <<"hello">>}]), {ok, C} = emqtt:start_link([{port, 1883}, {clientid, <<"hello">>}]),
{ok, _} = emqtt:connect(C), {ok, _} = emqtt:connect(C),
{ok, _, [1]} = emqtt:subscribe(C, Topic, qos1), {ok, _, [1]} = emqtt:subscribe(C, Topic, qos1),
{ok, _, [2]} = emqtt:subscribe(C, Topic, qos2), {ok, _, [2]} = emqtt:subscribe(C, Topic, qos2),

View File

@ -41,7 +41,7 @@ end_per_suite(_Config) ->
t_detect_check(_) -> t_detect_check(_) ->
ClientInfo = #{zone => external, ClientInfo = #{zone => external,
client_id => <<"clientid">>, clientid => <<"clientid">>,
peerhost => {127,0,0,1} peerhost => {127,0,0,1}
}, },
false = emqx_flapping:detect(ClientInfo), false = emqx_flapping:detect(ClientInfo),

View File

@ -129,8 +129,8 @@ t_parse_cont(_) ->
t_parse_frame_too_large(_) -> t_parse_frame_too_large(_) ->
Packet = ?PUBLISH_PACKET(?QOS_1, <<"t">>, 1, payload(1000)), Packet = ?PUBLISH_PACKET(?QOS_1, <<"t">>, 1, payload(1000)),
?catch_error(mqtt_frame_too_large, parse_serialize(Packet, #{max_size => 256})), ?catch_error(frame_too_large, parse_serialize(Packet, #{max_size => 256})),
?catch_error(mqtt_frame_too_large, parse_serialize(Packet, #{max_size => 512})), ?catch_error(frame_too_large, parse_serialize(Packet, #{max_size => 512})),
?assertEqual(Packet, parse_serialize(Packet, #{max_size => 2048, version => ?MQTT_PROTO_V4})). ?assertEqual(Packet, parse_serialize(Packet, #{max_size => 2048, version => ?MQTT_PROTO_V4})).
t_serialize_parse_connect(_) -> t_serialize_parse_connect(_) ->
@ -147,7 +147,7 @@ prop_serialize_parse_connect() ->
Packet = ?CONNECT_PACKET(#mqtt_packet_connect{ Packet = ?CONNECT_PACKET(#mqtt_packet_connect{
proto_name = ProtoName, proto_name = ProtoName,
proto_ver = ProtoVer, proto_ver = ProtoVer,
client_id = <<"clientId">>, clientid = <<"clientId">>,
will_qos = ?QOS_1, will_qos = ?QOS_1,
will_flag = true, will_flag = true,
will_retain = true, will_retain = true,
@ -167,7 +167,7 @@ t_serialize_parse_v3_connect(_) ->
Packet = ?CONNECT_PACKET( Packet = ?CONNECT_PACKET(
#mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V3, #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V3,
proto_name = <<"MQIsdp">>, proto_name = <<"MQIsdp">>,
client_id = <<"mosqpub/10451-iMac.loca">>, clientid = <<"mosqpub/10451-iMac.loca">>,
clean_start = true, clean_start = true,
keepalive = 60 keepalive = 60
}), }),
@ -180,7 +180,7 @@ t_serialize_parse_v4_connect(_) ->
Packet = ?CONNECT_PACKET( Packet = ?CONNECT_PACKET(
#mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V4, #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V4,
proto_name = <<"MQTT">>, proto_name = <<"MQTT">>,
client_id = <<"mosqpub/10451-iMac.loca">>, clientid = <<"mosqpub/10451-iMac.loca">>,
clean_start = true, clean_start = true,
keepalive = 60 keepalive = 60
}), }),
@ -213,7 +213,7 @@ t_serialize_parse_v5_connect(_) ->
proto_ver = ?MQTT_PROTO_V5, proto_ver = ?MQTT_PROTO_V5,
is_bridge = false, is_bridge = false,
clean_start = true, clean_start = true,
client_id = <<>>, clientid = <<>>,
will_flag = true, will_flag = true,
will_qos = ?QOS_1, will_qos = ?QOS_1,
will_retain = false, will_retain = false,
@ -231,7 +231,7 @@ t_serialize_parse_connect_without_clientid(_) ->
Bin = <<16,12,0,4,77,81,84,84,4,2,0,60,0,0>>, Bin = <<16,12,0,4,77,81,84,84,4,2,0,60,0,0>>,
Packet = ?CONNECT_PACKET(#mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V4, Packet = ?CONNECT_PACKET(#mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V4,
proto_name = <<"MQTT">>, proto_name = <<"MQTT">>,
client_id = <<>>, clientid = <<>>,
clean_start = true, clean_start = true,
keepalive = 60 keepalive = 60
}), }),
@ -246,7 +246,7 @@ t_serialize_parse_connect_with_will(_) ->
Packet = #mqtt_packet{header = #mqtt_packet_header{type = ?CONNECT}, Packet = #mqtt_packet{header = #mqtt_packet_header{type = ?CONNECT},
variable = #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V3, variable = #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V3,
proto_name = <<"MQIsdp">>, proto_name = <<"MQIsdp">>,
client_id = <<"mosqpub/10452-iMac.loca">>, clientid = <<"mosqpub/10452-iMac.loca">>,
clean_start = true, clean_start = true,
keepalive = 60, keepalive = 60,
will_retain = false, will_retain = false,
@ -267,7 +267,7 @@ t_serialize_parse_bridge_connect(_) ->
67,58,50,57,58,50,66,58,55,55,58,53,50,47,115,116,97,116,101,0,1,48>>, 67,58,50,57,58,50,66,58,55,55,58,53,50,47,115,116,97,116,101,0,1,48>>,
Topic = <<"$SYS/broker/connection/C_00:0C:29:2B:77:52/state">>, Topic = <<"$SYS/broker/connection/C_00:0C:29:2B:77:52/state">>,
Packet = #mqtt_packet{header = #mqtt_packet_header{type = ?CONNECT}, Packet = #mqtt_packet{header = #mqtt_packet_header{type = ?CONNECT},
variable = #mqtt_packet_connect{client_id = <<"C_00:0C:29:2B:77:52">>, variable = #mqtt_packet_connect{clientid = <<"C_00:0C:29:2B:77:52">>,
proto_ver = 16#03, proto_ver = 16#03,
proto_name = <<"MQIsdp">>, proto_name = <<"MQIsdp">>,
is_bridge = true, is_bridge = true,
@ -411,7 +411,7 @@ t_serialize_parse_pubcomp_v5(_) ->
t_serialize_parse_subscribe(_) -> t_serialize_parse_subscribe(_) ->
%% SUBSCRIBE(Q1, R0, D0, PacketId=2, TopicTable=[{<<"TopicA">>,2}]) %% SUBSCRIBE(Q1, R0, D0, PacketId=2, TopicTable=[{<<"TopicA">>,2}])
Bin = <<?SUBSCRIBE:4,2:4,11,0,2,0,6,84,111,112,105,99,65,2>>, Bin = <<?SUBSCRIBE:4,2:4,11,0,2,0,6,84,111,112,105,99,65,2>>,
TopicOpts = #{nl => 0 , rap => 0, rc => 0, rh => 0, qos => 2}, TopicOpts = #{nl => 0 , rap => 0, rh => 0, qos => 2},
TopicFilters = [{<<"TopicA">>, TopicOpts}], TopicFilters = [{<<"TopicA">>, TopicOpts}],
Packet = ?SUBSCRIBE_PACKET(2, TopicFilters), Packet = ?SUBSCRIBE_PACKET(2, TopicFilters),
?assertEqual(Bin, serialize_to_binary(Packet)), ?assertEqual(Bin, serialize_to_binary(Packet)),
@ -424,8 +424,8 @@ t_serialize_parse_subscribe(_) ->
?catch_error(bad_subqos, parse_serialize(?SUBSCRIBE_PACKET(1, [{<<"t">>, #{qos => 3}}]))). ?catch_error(bad_subqos, parse_serialize(?SUBSCRIBE_PACKET(1, [{<<"t">>, #{qos => 3}}]))).
t_serialize_parse_subscribe_v5(_) -> t_serialize_parse_subscribe_v5(_) ->
TopicFilters = [{<<"TopicQos0">>, #{rh => 1, qos => ?QOS_2, rap => 0, nl => 0, rc => 0}}, TopicFilters = [{<<"TopicQos0">>, #{rh => 1, qos => ?QOS_2, rap => 0, nl => 0}},
{<<"TopicQos1">>, #{rh => 1, qos => ?QOS_2, rap => 0, nl => 0, rc => 0}}], {<<"TopicQos1">>, #{rh => 1, qos => ?QOS_2, rap => 0, nl => 0}}],
Packet = ?SUBSCRIBE_PACKET(3, #{'Subscription-Identifier' => 16#FFFFFFF}, TopicFilters), Packet = ?SUBSCRIBE_PACKET(3, #{'Subscription-Identifier' => 16#FFFFFFF}, TopicFilters),
?assertEqual(Packet, parse_serialize(Packet, #{version => ?MQTT_PROTO_V5})). ?assertEqual(Packet, parse_serialize(Packet, #{version => ?MQTT_PROTO_V5})).

View File

@ -24,6 +24,30 @@
all() -> emqx_ct:all(?MODULE). all() -> emqx_ct:all(?MODULE).
t_new(_) ->
with_metrics_server(
fun() ->
ok = emqx_metrics:new('metrics.test'),
0 = emqx_metrics:val('metrics.test'),
ok = emqx_metrics:inc('metrics.test'),
1 = emqx_metrics:val('metrics.test'),
ok = emqx_metrics:new(counter, 'metrics.test.cnt'),
0 = emqx_metrics:val('metrics.test.cnt'),
ok = emqx_metrics:inc('metrics.test.cnt'),
1 = emqx_metrics:val('metrics.test.cnt'),
ok = emqx_metrics:new(gauge, 'metrics.test.total'),
0 = emqx_metrics:val('metrics.test.total'),
ok = emqx_metrics:inc('metrics.test.total'),
1 = emqx_metrics:val('metrics.test.total')
end).
t_all(_) ->
with_metrics_server(
fun() ->
Metrics = emqx_metrics:all(),
?assert(length(Metrics) > 50)
end).
t_inc_dec(_) -> t_inc_dec(_) ->
with_metrics_server( with_metrics_server(
fun() -> fun() ->

View File

@ -55,11 +55,11 @@ end_per_suite(_Config) ->
%% Test case for emqx_mod_presence %% Test case for emqx_mod_presence
t_mod_presence(_) -> t_mod_presence(_) ->
ok = emqx_mod_presence:load([{qos, ?QOS_1}]), ok = emqx_mod_presence:load([{qos, ?QOS_1}]),
{ok, C1} = emqtt:start_link([{client_id, <<"monsys">>}]), {ok, C1} = emqtt:start_link([{clientid, <<"monsys">>}]),
{ok, _} = emqtt:connect(C1), {ok, _} = emqtt:connect(C1),
{ok, _Props, [?QOS_1]} = emqtt:subscribe(C1, <<"$SYS/brokers/+/clients/#">>, qos1), {ok, _Props, [?QOS_1]} = emqtt:subscribe(C1, <<"$SYS/brokers/+/clients/#">>, qos1),
%% Connected Presence %% Connected Presence
{ok, C2} = emqtt:start_link([{client_id, <<"clientid">>}, {ok, C2} = emqtt:start_link([{clientid, <<"clientid">>},
{username, <<"username">>}]), {username, <<"username">>}]),
{ok, _} = emqtt:connect(C2), {ok, _} = emqtt:connect(C2),
ok = recv_and_check_presence(<<"clientid">>, <<"connected">>), ok = recv_and_check_presence(<<"clientid">>, <<"connected">>),
@ -91,14 +91,14 @@ recv_and_check_presence(ClientId, Presence) ->
<<"disconnected">> -> <<"disconnected">> ->
?assertMatch(#{clientid := <<"clientid">>, ?assertMatch(#{clientid := <<"clientid">>,
username := <<"username">>, username := <<"username">>,
reason := <<"closed">>}, emqx_json:decode(Payload, [{labels, atom}, return_maps])) reason := <<"normal">>}, emqx_json:decode(Payload, [{labels, atom}, return_maps]))
end. end.
%% Test case for emqx_mod_subscription %% Test case for emqx_mod_subscription
t_mod_subscription(_) -> t_mod_subscription(_) ->
emqx_mod_subscription:load([{<<"connected/%c/%u">>, ?QOS_0}]), emqx_mod_subscription:load([{<<"connected/%c/%u">>, ?QOS_0}]),
{ok, C} = emqtt:start_link([{host, "localhost"}, {ok, C} = emqtt:start_link([{host, "localhost"},
{client_id, "myclient"}, {clientid, "myclient"},
{username, "admin"}]), {username, "admin"}]),
{ok, _} = emqtt:connect(C), {ok, _} = emqtt:connect(C),
emqtt:publish(C, <<"connected/myclient/admin">>, <<"Hello world">>, ?QOS_0), emqtt:publish(C, <<"connected/myclient/admin">>, <<"Hello world">>, ?QOS_0),
@ -111,7 +111,7 @@ t_mod_subscription(_) ->
%% Test case for emqx_mod_write %% Test case for emqx_mod_write
t_mod_rewrite(_Config) -> t_mod_rewrite(_Config) ->
ok = emqx_mod_rewrite:load(?RULES), ok = emqx_mod_rewrite:load(?RULES),
{ok, C} = emqtt:start_link([{client_id, <<"rewrite_client">>}]), {ok, C} = emqtt:start_link([{clientid, <<"rewrite_client">>}]),
{ok, _} = emqtt:connect(C), {ok, _} = emqtt:connect(C),
OrigTopics = [<<"x/y/2">>, <<"x/1/2">>, <<"y/a/z/b">>, <<"y/def">>], OrigTopics = [<<"x/y/2">>, <<"x/1/2">>, <<"y/a/z/b">>, <<"y/def">>],
DestTopics = [<<"z/y/2">>, <<"x/1/2">>, <<"y/z/b">>, <<"y/def">>], DestTopics = [<<"z/y/2">>, <<"x/1/2">>, <<"y/z/b">>, <<"y/def">>],

View File

@ -56,12 +56,12 @@ t_replvar(_) ->
?assertEqual(undefined, replvar(undefined, #{})), ?assertEqual(undefined, replvar(undefined, #{})),
?assertEqual(<<"mount/user/clientid/">>, ?assertEqual(<<"mount/user/clientid/">>,
replvar(<<"mount/%u/%c/">>, replvar(<<"mount/%u/%c/">>,
#{client_id => <<"clientid">>, #{clientid => <<"clientid">>,
username => <<"user">> username => <<"user">>
})), })),
?assertEqual(<<"mount/%u/clientid/">>, ?assertEqual(<<"mount/%u/clientid/">>,
replvar(<<"mount/%u/%c/">>, replvar(<<"mount/%u/%c/">>,
#{client_id => <<"clientid">>, #{clientid => <<"clientid">>,
username => undefined username => undefined
})). })).

View File

@ -42,8 +42,8 @@ t_message_expiry_interval_2(_) ->
emqtt:stop(ClientA). emqtt:stop(ClientA).
message_expiry_interval_init() -> message_expiry_interval_init() ->
{ok, ClientA} = emqtt:start_link([{proto_ver,v5}, {client_id, <<"client-a">>}, {clean_start, false},{properties, #{'Session-Expiry-Interval' => 360}}]), {ok, ClientA} = emqtt:start_link([{proto_ver,v5}, {clientid, <<"client-a">>}, {clean_start, false},{properties, #{'Session-Expiry-Interval' => 360}}]),
{ok, ClientB} = emqtt:start_link([{proto_ver,v5}, {client_id, <<"client-b">>}, {clean_start, false},{properties, #{'Session-Expiry-Interval' => 360}}]), {ok, ClientB} = emqtt:start_link([{proto_ver,v5}, {clientid, <<"client-b">>}, {clean_start, false},{properties, #{'Session-Expiry-Interval' => 360}}]),
{ok, _} = emqtt:connect(ClientA), {ok, _} = emqtt:connect(ClientA),
{ok, _} = emqtt:connect(ClientB), {ok, _} = emqtt:connect(ClientB),
%% subscribe and disconnect client-b %% subscribe and disconnect client-b
@ -58,7 +58,7 @@ message_expiry_interval_exipred(ClientA, QoS) ->
ct:sleep(1500), ct:sleep(1500),
%% resume the session for client-b %% resume the session for client-b
{ok, ClientB1} = emqtt:start_link([{proto_ver,v5}, {client_id, <<"client-b">>}, {clean_start, false},{properties, #{'Session-Expiry-Interval' => 360}}]), {ok, ClientB1} = emqtt:start_link([{proto_ver,v5}, {clientid, <<"client-b">>}, {clean_start, false},{properties, #{'Session-Expiry-Interval' => 360}}]),
{ok, _} = emqtt:connect(ClientB1), {ok, _} = emqtt:connect(ClientB1),
%% verify client-b could not receive the publish message %% verify client-b could not receive the publish message
@ -78,7 +78,7 @@ message_expiry_interval_not_exipred(ClientA, QoS) ->
%% wait for 1s and then resume the session for client-b, the message should not expires %% wait for 1s and then resume the session for client-b, the message should not expires
%% as Message-Expiry-Interval = 20s %% as Message-Expiry-Interval = 20s
ct:sleep(1000), ct:sleep(1000),
{ok, ClientB1} = emqtt:start_link([{proto_ver,v5}, {client_id, <<"client-b">>}, {clean_start, false},{properties, #{'Session-Expiry-Interval' => 360}}]), {ok, ClientB1} = emqtt:start_link([{proto_ver,v5}, {clientid, <<"client-b">>}, {clean_start, false},{properties, #{'Session-Expiry-Interval' => 360}}]),
{ok, _} = emqtt:connect(ClientB1), {ok, _} = emqtt:connect(ClientB1),
%% verify client-b could receive the publish message and the Message-Expiry-Interval is set %% verify client-b could receive the publish message and the Message-Expiry-Interval is set

View File

@ -112,11 +112,11 @@ t_check_connect(_) ->
ConnPkt2 = #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V3, ConnPkt2 = #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V3,
proto_name = <<"MQIsdp">>, proto_name = <<"MQIsdp">>,
client_id = <<>> clientid = <<>>
}, },
{error, ?RC_CLIENT_IDENTIFIER_NOT_VALID} = emqx_packet:check(ConnPkt2, Opts), {error, ?RC_CLIENT_IDENTIFIER_NOT_VALID} = emqx_packet:check(ConnPkt2, Opts),
ConnPkt3 = #mqtt_packet_connect{client_id = <<"123456">>}, ConnPkt3 = #mqtt_packet_connect{clientid = <<"123456">>},
{error, ?RC_CLIENT_IDENTIFIER_NOT_VALID} = emqx_packet:check(ConnPkt3, Opts), {error, ?RC_CLIENT_IDENTIFIER_NOT_VALID} = emqx_packet:check(ConnPkt3, Opts),
ConnPkt4 = #mqtt_packet_connect{will_flag = true, ConnPkt4 = #mqtt_packet_connect{will_flag = true,
@ -152,7 +152,7 @@ t_from_to_message(_) ->
packet_id = 10, packet_id = 10,
properties = #{}}, properties = #{}},
payload = <<"payload">>}, payload = <<"payload">>},
MsgFromPkt = emqx_packet:to_message(#{client_id => <<"clientid">>, MsgFromPkt = emqx_packet:to_message(#{clientid => <<"clientid">>,
username => <<"test">>, username => <<"test">>,
peerhost => {127,0,0,1}}, Pkt), peerhost => {127,0,0,1}}, Pkt),
?assertEqual(ExpectedMsg2, MsgFromPkt#message{id = emqx_message:id(ExpectedMsg), ?assertEqual(ExpectedMsg2, MsgFromPkt#message{id = emqx_message:id(ExpectedMsg),
@ -161,7 +161,7 @@ t_from_to_message(_) ->
t_will_msg(_) -> t_will_msg(_) ->
Pkt = #mqtt_packet_connect{will_flag = true, Pkt = #mqtt_packet_connect{will_flag = true,
client_id = <<"clientid">>, clientid = <<"clientid">>,
username = "test", username = "test",
will_retain = true, will_retain = true,
will_qos = ?QOS_2, will_qos = ?QOS_2,

View File

@ -42,7 +42,7 @@ request_response_per_qos(QoS) ->
RspTopic = <<"response_topic">>, RspTopic = <<"response_topic">>,
{ok, Requester} = emqx_request_sender:start_link(RspTopic, QoS, {ok, Requester} = emqx_request_sender:start_link(RspTopic, QoS,
[{proto_ver, v5}, [{proto_ver, v5},
{client_id, <<"requester">>}, {clientid, <<"requester">>},
{properties, #{ 'Request-Response-Information' => 1}}]), {properties, #{ 'Request-Response-Information' => 1}}]),
%% This is a square service %% This is a square service
Square = fun(_CorrData, ReqBin) -> Square = fun(_CorrData, ReqBin) ->
@ -51,7 +51,7 @@ request_response_per_qos(QoS) ->
end, end,
{ok, Responser} = emqx_request_handler:start_link(ReqTopic, QoS, Square, {ok, Responser} = emqx_request_handler:start_link(ReqTopic, QoS, Square,
[{proto_ver, v5}, [{proto_ver, v5},
{client_id, <<"responser">>} {clientid, <<"responser">>}
]), ]),
ok = emqx_request_sender:send(Requester, ReqTopic, RspTopic, <<"corr-1">>, <<"2">>, QoS), ok = emqx_request_sender:send(Requester, ReqTopic, RspTopic, <<"corr-1">>, <<"2">>, QoS),
receive receive

View File

@ -170,17 +170,17 @@ deliver_args() ->
info_args() -> info_args() ->
oneof([subscriptions, oneof([subscriptions,
max_subscriptions, subscriptions_max,
upgrade_qos, upgrade_qos,
inflight, inflight,
max_inflight, inflight_max,
retry_interval, retry_interval,
mqueue_len, mqueue_len,
max_mqueue, mqueue_max,
mqueue_dropped, mqueue_dropped,
next_pkt_id, next_pkt_id,
awaiting_rel, awaiting_rel,
max_awaiting_rel, awaiting_rel_max,
await_rel_timeout, await_rel_timeout,
created_at created_at
]). ]).
@ -188,12 +188,12 @@ info_args() ->
sub_args() -> sub_args() ->
?LET({ClientId, TopicFilter, SubOpts}, ?LET({ClientId, TopicFilter, SubOpts},
{clientid(), topic(), sub_opts()}, {clientid(), topic(), sub_opts()},
{#{client_id => ClientId}, TopicFilter, SubOpts}). {#{clientid => ClientId}, TopicFilter, SubOpts}).
unsub_args() -> unsub_args() ->
?LET({ClientId, TopicFilter}, ?LET({ClientId, TopicFilter},
{clientid(), topic()}, {clientid(), topic()},
{#{client_id => ClientId}, TopicFilter}). {#{clientid => ClientId}, TopicFilter}).
publish_args() -> publish_args() ->
?LET({PacketId, Message}, ?LET({PacketId, Message},

View File

@ -80,9 +80,9 @@ t_no_connection_nack(_) ->
ShareTopic = <<"$share/", Group/binary, $/, Topic/binary>>, ShareTopic = <<"$share/", Group/binary, $/, Topic/binary>>,
ExpProp = [{properties, #{'Session-Expiry-Interval' => timer:seconds(30)}}], ExpProp = [{properties, #{'Session-Expiry-Interval' => timer:seconds(30)}}],
{ok, SubConnPid1} = emqtt:start_link([{client_id, Subscriber1}] ++ ExpProp), {ok, SubConnPid1} = emqtt:start_link([{clientid, Subscriber1}] ++ ExpProp),
{ok, _Props} = emqtt:connect(SubConnPid1), {ok, _Props} = emqtt:connect(SubConnPid1),
{ok, SubConnPid2} = emqtt:start_link([{client_id, Subscriber2}] ++ ExpProp), {ok, SubConnPid2} = emqtt:start_link([{clientid, Subscriber2}] ++ ExpProp),
{ok, _Props} = emqtt:connect(SubConnPid2), {ok, _Props} = emqtt:connect(SubConnPid2),
emqtt:subscribe(SubConnPid1, ShareTopic, QoS), emqtt:subscribe(SubConnPid1, ShareTopic, QoS),
emqtt:subscribe(SubConnPid1, ShareTopic, QoS), emqtt:subscribe(SubConnPid1, ShareTopic, QoS),
@ -151,9 +151,9 @@ t_not_so_sticky(_) ->
ok = ensure_config(sticky), ok = ensure_config(sticky),
ClientId1 = <<"ClientId1">>, ClientId1 = <<"ClientId1">>,
ClientId2 = <<"ClientId2">>, ClientId2 = <<"ClientId2">>,
{ok, C1} = emqtt:start_link([{client_id, ClientId1}]), {ok, C1} = emqtt:start_link([{clientid, ClientId1}]),
{ok, _} = emqtt:connect(C1), {ok, _} = emqtt:connect(C1),
{ok, C2} = emqtt:start_link([{client_id, ClientId2}]), {ok, C2} = emqtt:start_link([{clientid, ClientId2}]),
{ok, _} = emqtt:connect(C2), {ok, _} = emqtt:connect(C2),
emqtt:subscribe(C1, {<<"$share/group1/foo/bar">>, 0}), emqtt:subscribe(C1, {<<"$share/group1/foo/bar">>, 0}),
@ -179,9 +179,9 @@ test_two_messages(Strategy, WithAck) ->
Topic = <<"foo/bar">>, Topic = <<"foo/bar">>,
ClientId1 = <<"ClientId1">>, ClientId1 = <<"ClientId1">>,
ClientId2 = <<"ClientId2">>, ClientId2 = <<"ClientId2">>,
{ok, ConnPid1} = emqtt:start_link([{client_id, ClientId1}]), {ok, ConnPid1} = emqtt:start_link([{clientid, ClientId1}]),
{ok, _} = emqtt:connect(ConnPid1), {ok, _} = emqtt:connect(ConnPid1),
{ok, ConnPid2} = emqtt:start_link([{client_id, ClientId2}]), {ok, ConnPid2} = emqtt:start_link([{clientid, ClientId2}]),
{ok, _} = emqtt:connect(ConnPid2), {ok, _} = emqtt:connect(ConnPid2),
Message1 = emqx_message:make(ClientId1, 0, Topic, <<"hello1">>), Message1 = emqx_message:make(ClientId1, 0, Topic, <<"hello1">>),

View File

@ -35,18 +35,19 @@ end_per_suite(_Config) ->
t_start_traces(_Config) -> t_start_traces(_Config) ->
{ok, T} = emqtt:start_link([{host, "localhost"}, {ok, T} = emqtt:start_link([{host, "localhost"},
{client_id, <<"client">>}, {clientid, <<"client">>},
{username, <<"testuser">>}, {username, <<"testuser">>},
{password, <<"pass">>}]), {password, <<"pass">>}
]),
emqtt:connect(T), emqtt:connect(T),
%% Start tracing %% Start tracing
emqx_logger:set_log_level(error), emqx_logger:set_log_level(error),
{error, _} = emqx_tracer:start_trace({client_id, <<"client">>}, debug, "tmp/client.log"), {error, _} = emqx_tracer:start_trace({clientid, <<"client">>}, debug, "tmp/client.log"),
emqx_logger:set_log_level(debug), emqx_logger:set_log_level(debug),
ok = emqx_tracer:start_trace({client_id, <<"client">>}, debug, "tmp/client.log"), ok = emqx_tracer:start_trace({clientid, <<"client">>}, debug, "tmp/client.log"),
ok = emqx_tracer:start_trace({client_id, <<"client2">>}, all, "tmp/client2.log"), ok = emqx_tracer:start_trace({clientid, <<"client2">>}, all, "tmp/client2.log"),
{error, {invalid_log_level, bad_level}} = emqx_tracer:start_trace({client_id, <<"client3">>}, bad_level, "tmp/client3.log"), {error, {invalid_log_level, bad_level}} = emqx_tracer:start_trace({clientid, <<"client3">>}, bad_level, "tmp/client3.log"),
ok = emqx_tracer:start_trace({topic, <<"a/#">>}, all, "tmp/topic_trace.log"), ok = emqx_tracer:start_trace({topic, <<"a/#">>}, all, "tmp/topic_trace.log"),
ct:sleep(100), ct:sleep(100),
@ -56,8 +57,8 @@ t_start_traces(_Config) ->
?assert(filelib:is_regular("tmp/topic_trace.log")), ?assert(filelib:is_regular("tmp/topic_trace.log")),
%% Get current traces %% Get current traces
?assertEqual([{{client_id,"client"},{debug,"tmp/client.log"}}, ?assertEqual([{{clientid,"client"},{debug,"tmp/client.log"}},
{{client_id,"client2"},{debug,"tmp/client2.log"}}, {{clientid,"client2"},{debug,"tmp/client2.log"}},
{{topic,"a/#"},{debug,"tmp/topic_trace.log"}}], emqx_tracer:lookup_traces()), {{topic,"a/#"},{debug,"tmp/topic_trace.log"}}], emqx_tracer:lookup_traces()),
%% set the overall log level to debug %% set the overall log level to debug
@ -73,8 +74,8 @@ t_start_traces(_Config) ->
?assert(filelib:file_size("tmp/client2.log") == 0), ?assert(filelib:file_size("tmp/client2.log") == 0),
%% Stop tracing %% Stop tracing
ok = emqx_tracer:stop_trace({client_id, <<"client">>}), ok = emqx_tracer:stop_trace({clientid, <<"client">>}),
ok = emqx_tracer:stop_trace({client_id, <<"client2">>}), ok = emqx_tracer:stop_trace({clientid, <<"client2">>}),
ok = emqx_tracer:stop_trace({topic, <<"a/#">>}), ok = emqx_tracer:stop_trace({topic, <<"a/#">>}),
emqtt:disconnect(T), emqtt:disconnect(T),