From d945ee4972d4b25d1d4efe070a51c6836142f591 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Fri, 20 Sep 2019 23:25:19 +0800 Subject: [PATCH 01/14] Add test cases for new/1, all/0 functions --- test/emqx_metrics_SUITE.erl | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/emqx_metrics_SUITE.erl b/test/emqx_metrics_SUITE.erl index cc7db5ad2..7e547259e 100644 --- a/test/emqx_metrics_SUITE.erl +++ b/test/emqx_metrics_SUITE.erl @@ -24,6 +24,30 @@ 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(_) -> with_metrics_server( fun() -> From 98b7e3d9484c112d921374ee5bc897a4a94ff4e2 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Sat, 21 Sep 2019 15:27:05 +0800 Subject: [PATCH 02/14] Rename the type 'client()' to 'client_info()' - Rename tye type 'client()' to 'client_info()' - Rename the 'client' field of channel to 'client_info' - Fix the function specs --- src/emqx_access_control.erl | 2 +- src/emqx_access_rule.erl | 64 ++++++++++----------- src/emqx_banned.erl | 2 +- src/emqx_channel.erl | 104 +++++++++++++++++----------------- src/emqx_cm.erl | 2 +- src/emqx_flapping.erl | 4 +- src/emqx_mod_acl_internal.erl | 2 +- src/emqx_packet.erl | 2 +- src/emqx_session.erl | 14 ++--- src/emqx_types.erl | 40 ++++++------- 10 files changed, 118 insertions(+), 118 deletions(-) diff --git a/src/emqx_access_control.erl b/src/emqx_access_control.erl index 06e8f5ed8..ff5ad9d09 100644 --- a/src/emqx_access_control.erl +++ b/src/emqx_access_control.erl @@ -28,7 +28,7 @@ %% APIs %%-------------------------------------------------------------------- --spec(authenticate(emqx_types:client()) +-spec(authenticate(emqx_types:client_info()) -> {ok, #{auth_result := emqx_types:auth_result(), anonymous := boolean}} | {error, term()}). authenticate(Client) -> diff --git a/src/emqx_access_rule.erl b/src/emqx_access_rule.erl index e48caa5c0..690b45998 100644 --- a/src/emqx_access_rule.erl +++ b/src/emqx_access_rule.erl @@ -86,23 +86,23 @@ bin(B) when is_binary(B) -> B. %% @doc Match access rule --spec(match(emqx_types:client(), emqx_types:topic(), rule()) +-spec(match(emqx_types:client_info(), emqx_types:topic(), rule()) -> {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}; -match(Client, Topic, {AllowDeny, Who, _PubSub, TopicFilters}) +match(ClientInfo, Topic, {AllowDeny, Who, _PubSub, TopicFilters}) when ?ALLOW_DENY(AllowDeny) -> - case match_who(Client, Who) - andalso match_topics(Client, Topic, TopicFilters) of + case match_who(ClientInfo, Who) + andalso match_topics(ClientInfo, Topic, TopicFilters) of true -> {matched, AllowDeny}; false -> nomatch end. -match_who(_Client, all) -> +match_who(_ClientInfo, all) -> true; -match_who(_Client, {user, all}) -> +match_who(_ClientInfo, {user, all}) -> true; -match_who(_Client, {client, all}) -> +match_who(_ClientInfo, {client, all}) -> true; match_who(#{client_id := ClientId}, {client, ClientId}) -> true; @@ -112,44 +112,44 @@ match_who(#{peerhost := undefined}, {ipaddr, _Tup}) -> false; match_who(#{peerhost := IP}, {ipaddr, 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) -> - match_who(Client, Who) andalso Allow + match_who(ClientInfo, Who) andalso Allow 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) -> - match_who(Client, Who) orelse Allow + match_who(ClientInfo, Who) orelse Allow end, false, Conds); -match_who(_Client, _Who) -> +match_who(_ClientInfo, _Who) -> false. -match_topics(_Client, _Topic, []) -> +match_topics(_ClientInfo, _Topic, []) -> false; -match_topics(Client, Topic, [{pattern, PatternFilter}|Filters]) -> - TopicFilter = feed_var(Client, PatternFilter), +match_topics(ClientInfo, Topic, [{pattern, PatternFilter}|Filters]) -> + TopicFilter = feed_var(ClientInfo, PatternFilter), match_topic(emqx_topic:words(Topic), TopicFilter) - orelse match_topics(Client, Topic, Filters); -match_topics(Client, Topic, [TopicFilter|Filters]) -> + orelse match_topics(ClientInfo, Topic, Filters); +match_topics(ClientInfo, Topic, [TopicFilter|Filters]) -> match_topic(emqx_topic:words(Topic), TopicFilter) - orelse match_topics(Client, Topic, Filters). + orelse match_topics(ClientInfo, Topic, Filters). match_topic(Topic, {eq, TopicFilter}) -> Topic == TopicFilter; match_topic(Topic, TopicFilter) -> emqx_topic:match(Topic, TopicFilter). -feed_var(Client, Pattern) -> - feed_var(Client, Pattern, []). -feed_var(_Client, [], Acc) -> +feed_var(ClientInfo, Pattern) -> + feed_var(ClientInfo, Pattern, []). +feed_var(_ClientInfo, [], Acc) -> lists:reverse(Acc); -feed_var(Client = #{client_id := undefined}, [<<"%c">>|Words], Acc) -> - feed_var(Client, Words, [<<"%c">>|Acc]); -feed_var(Client = #{client_id := ClientId}, [<<"%c">>|Words], Acc) -> - feed_var(Client, Words, [ClientId |Acc]); -feed_var(Client = #{username := undefined}, [<<"%u">>|Words], Acc) -> - feed_var(Client, Words, [<<"%u">>|Acc]); -feed_var(Client = #{username := Username}, [<<"%u">>|Words], Acc) -> - feed_var(Client, Words, [Username|Acc]); -feed_var(Client, [W|Words], Acc) -> - feed_var(Client, Words, [W|Acc]). +feed_var(ClientInfo = #{client_id := undefined}, [<<"%c">>|Words], Acc) -> + feed_var(ClientInfo, Words, [<<"%c">>|Acc]); +feed_var(ClientInfo = #{client_id := ClientId}, [<<"%c">>|Words], Acc) -> + feed_var(ClientInfo, Words, [ClientId |Acc]); +feed_var(ClientInfo = #{username := undefined}, [<<"%u">>|Words], Acc) -> + feed_var(ClientInfo, Words, [<<"%u">>|Acc]); +feed_var(ClientInfo = #{username := Username}, [<<"%u">>|Words], Acc) -> + feed_var(ClientInfo, Words, [Username|Acc]); +feed_var(ClientInfo, [W|Words], Acc) -> + feed_var(ClientInfo, Words, [W|Acc]). diff --git a/src/emqx_banned.erl b/src/emqx_banned.erl index 7e3e959e3..d41f32ee0 100644 --- a/src/emqx_banned.erl +++ b/src/emqx_banned.erl @@ -73,7 +73,7 @@ start_link() -> -spec(stop() -> ok). stop() -> gen_server:stop(?MODULE). --spec(check(emqx_types:client()) -> boolean()). +-spec(check(emqx_types:client_info()) -> boolean()). check(#{client_id := ClientId, username := Username, peerhost := IPAddr}) -> diff --git a/src/emqx_channel.erl b/src/emqx_channel.erl index edd08baa6..5d76f8414 100644 --- a/src/emqx_channel.erl +++ b/src/emqx_channel.erl @@ -60,7 +60,7 @@ %% MQTT ConnInfo conninfo :: emqx_types:conninfo(), %% MQTT ClientInfo - client :: emqx_types:client(), + client_info :: emqx_types:client_info(), %% MQTT Session session :: emqx_session:session(), %% Keepalive @@ -102,7 +102,7 @@ will_timer => will_message }). --define(ATTR_KEYS, [conninfo, client, session, connected, connected_at, disconnected_at]). +-define(ATTR_KEYS, [conninfo, client_info, session, connected, connected_at, disconnected_at]). -define(INFO_KEYS, ?ATTR_KEYS ++ [keepalive, topic_aliases, alias_maximum, gc_state, disconnected_at]). %%-------------------------------------------------------------------- @@ -119,7 +119,7 @@ info(Keys, Channel) when is_list(Keys) -> [{Key, info(Key, Channel)} || Key <- Keys]; info(conninfo, #channel{conninfo = ConnInfo}) -> ConnInfo; -info(client, #channel{client = ClientInfo}) -> +info(client_info, #channel{client_info = ClientInfo}) -> ClientInfo; info(session, #channel{session = Session}) -> maybe_apply(fun emqx_session:info/1, Session); @@ -158,7 +158,7 @@ stats(#channel{session = Session}) -> emqx_session:stats(Session). -spec(caps(channel()) -> emqx_types:caps()). -caps(#channel{client = #{zone := Zone}}) -> +caps(#channel{client_info = #{zone := Zone}}) -> emqx_mqtt_caps:get_caps(Zone). %% For tests @@ -196,15 +196,15 @@ init(ConnInfo = #{peername := {PeerHost, _Port}, protocol := Protocol}, Options) true -> undefined; false -> disabled end, - #channel{conninfo = ConnInfo, - client = ClientInfo, - gc_state = init_gc_state(Zone), - oom_policy = init_oom_policy(Zone), - timers = #{stats_timer => StatsTimer}, - connected = undefined, - takeover = false, - resuming = false, - pendings = [] + #channel{conninfo = ConnInfo, + client_info = ClientInfo, + gc_state = init_gc_state(Zone), + oom_policy = init_oom_policy(Zone), + timers = #{stats_timer => StatsTimer}, + connected = undefined, + takeover = false, + resuming = false, + pendings = [] }. peer_cert_as_username(Options) -> @@ -252,7 +252,7 @@ handle_in(Packet = ?PUBLISH_PACKET(_QoS), Channel) -> end; handle_in(?PUBACK_PACKET(PacketId, _ReasonCode), - Channel = #channel{client = ClientInfo, session = Session}) -> + Channel = #channel{client_info = ClientInfo, session = Session}) -> case emqx_session:puback(PacketId, Session) of {ok, Msg, Publishes, NSession} -> ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]), @@ -271,7 +271,7 @@ handle_in(?PUBACK_PACKET(PacketId, _ReasonCode), end; handle_in(?PUBREC_PACKET(PacketId, _ReasonCode), - Channel = #channel{client = ClientInfo, session = Session}) -> + Channel = #channel{client_info = ClientInfo, session = Session}) -> case emqx_session:pubrec(PacketId, Session) of {ok, Msg, NSession} -> ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]), @@ -310,7 +310,7 @@ handle_in(?PUBCOMP_PACKET(PacketId, _ReasonCode), Channel = #channel{session = S end; handle_in(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters), - Channel = #channel{client = ClientInfo}) -> + Channel = #channel{client_info = ClientInfo}) -> case emqx_packet:check(Packet) of ok -> TopicFilters1 = emqx_hooks:run_fold('client.subscribe', [ClientInfo, Properties], @@ -323,7 +323,7 @@ handle_in(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters), end; handle_in(Packet = ?UNSUBSCRIBE_PACKET(PacketId, Properties, TopicFilters), - Channel = #channel{client = ClientInfo}) -> + Channel = #channel{client_info = ClientInfo}) -> case emqx_packet:check(Packet) of ok -> TopicFilters1 = emqx_hooks:run_fold('client.unsubscribe', [ClientInfo, Properties], @@ -369,7 +369,7 @@ handle_in(Packet, Channel) -> %%-------------------------------------------------------------------- process_connect(ConnPkt = #mqtt_packet_connect{clean_start = CleanStart}, - Channel = #channel{conninfo = ConnInfo, client = ClientInfo}) -> + Channel = #channel{conninfo = ConnInfo, client_info = ClientInfo}) -> case emqx_cm:open_session(CleanStart, ClientInfo, ConnInfo) of {ok, #{session := Session, present := false}} -> NChannel = Channel#channel{session = Session}, @@ -440,7 +440,7 @@ process_publish(PacketId, Msg = #message{qos = ?QOS_2}, end. publish_to_msg(Packet, #channel{conninfo = #{proto_ver := ProtoVer}, - client = ClientInfo = #{mountpoint := MountPoint}}) -> + client_info = ClientInfo = #{mountpoint := MountPoint}}) -> Msg = emqx_packet:to_message(ClientInfo, Packet), Msg1 = emqx_message:set_flag(dup, false, Msg), Msg2 = emqx_message:set_header(proto_ver, ProtoVer, Msg1), @@ -461,7 +461,7 @@ process_subscribe([{TopicFilter, SubOpts}|More], Acc, Channel) -> process_subscribe(More, [RC|Acc], NChannel). do_subscribe(TopicFilter, SubOpts = #{qos := QoS}, Channel = - #channel{client = ClientInfo = #{mountpoint := MountPoint}, + #channel{client_info = ClientInfo = #{mountpoint := MountPoint}, session = Session}) -> case check_subscribe(TopicFilter, SubOpts, Channel) of ok -> @@ -491,7 +491,7 @@ process_unsubscribe([{TopicFilter, SubOpts}|More], Acc, Channel) -> process_unsubscribe(More, [RC|Acc], NChannel). do_unsubscribe(TopicFilter, _SubOpts, Channel = - #channel{client = ClientInfo = #{mountpoint := MountPoint}, + #channel{client_info = ClientInfo = #{mountpoint := MountPoint}, session = Session}) -> TopicFilter1 = emqx_mountpoint:mount(MountPoint, TopicFilter), case emqx_session:unsubscribe(ClientInfo, TopicFilter1, Session) of @@ -506,7 +506,7 @@ do_unsubscribe(TopicFilter, _SubOpts, Channel = %%TODO: RunFold or Pipeline handle_out({connack, ?RC_SUCCESS, SP, ConnPkt}, - Channel = #channel{conninfo = ConnInfo, client = ClientInfo}) -> + Channel = #channel{conninfo = ConnInfo, client_info = ClientInfo}) -> AckProps = run_fold([fun enrich_caps/2, fun enrich_server_keepalive/2, fun enrich_assigned_clientid/2 @@ -531,7 +531,7 @@ handle_out({connack, ?RC_SUCCESS, SP, ConnPkt}, end; handle_out({connack, ReasonCode, _ConnPkt}, Channel = #channel{conninfo = ConnInfo, - client = ClientInfo}) -> + client_info = ClientInfo}) -> ok = emqx_hooks:run('client.connected', [ClientInfo, ReasonCode, ConnInfo]), ReasonCode1 = case ProtoVer = maps:get(proto_ver, ConnInfo) of ?MQTT_PROTO_V5 -> ReasonCode; @@ -572,11 +572,11 @@ handle_out({publish, Publishes}, Channel) when is_list(Publishes) -> %% Ignore loop deliver handle_out({publish, _PacketId, #message{from = ClientId, flags = #{nl := true}}}, - Channel = #channel{client = #{client_id := ClientId}}) -> + Channel = #channel{client_info = #{client_id := ClientId}}) -> {ok, Channel}; handle_out({publish, PacketId, Msg}, Channel = - #channel{client = ClientInfo = #{mountpoint := MountPoint}}) -> + #channel{client_info = ClientInfo = #{mountpoint := MountPoint}}) -> Msg1 = emqx_message:update_expiry(Msg), Msg2 = emqx_hooks:run_fold('message.delivered', [ClientInfo], Msg1), Msg3 = emqx_mountpoint:unmount(MountPoint, Msg2), @@ -657,7 +657,7 @@ handle_call(Req, Channel) -> -spec(handle_cast(Msg :: term(), channel()) -> ok | {ok, channel()} | {stop, Reason :: term(), channel()}). -handle_cast({register, Attrs, Stats}, #channel{client = #{client_id := ClientId}}) -> +handle_cast({register, Attrs, Stats}, #channel{client_info = #{client_id := ClientId}}) -> ok = emqx_cm:register_channel(ClientId), emqx_cm:set_chan_attrs(ClientId, Attrs), emqx_cm:set_chan_stats(ClientId, Stats); @@ -672,14 +672,14 @@ handle_cast(Msg, Channel) -> -spec(handle_info(Info :: term(), channel()) -> {ok, channel()} | {stop, Reason :: term(), channel()}). -handle_info({subscribe, TopicFilters}, Channel = #channel{client = ClientInfo}) -> +handle_info({subscribe, TopicFilters}, Channel = #channel{client_info = ClientInfo}) -> TopicFilters1 = emqx_hooks:run_fold('client.subscribe', [ClientInfo, #{'Internal' => true}], parse_topic_filters(TopicFilters)), {_ReasonCodes, NChannel} = process_subscribe(TopicFilters1, Channel), {ok, NChannel}; -handle_info({unsubscribe, TopicFilters}, Channel = #channel{client = ClientInfo}) -> +handle_info({unsubscribe, TopicFilters}, Channel = #channel{client_info = ClientInfo}) -> TopicFilters1 = emqx_hooks:run_fold('client.unsubscribe', [ClientInfo, #{'Internal' => true}], parse_topic_filters(TopicFilters)), @@ -693,7 +693,7 @@ handle_info(disconnected, Channel = #channel{connected = false}) -> {ok, Channel}; handle_info(disconnected, Channel = #channel{conninfo = #{expiry_interval := ExpiryInterval}, - client = ClientInfo = #{zone := Zone}, + client_info = ClientInfo = #{zone := Zone}, will_msg = WillMsg}) -> emqx_zone:enable_flapping_detect(Zone) andalso emqx_flapping:detect(ClientInfo), Channel1 = ensure_disconnected(Channel), @@ -726,7 +726,7 @@ handle_info(Info, Channel) -> | {ok, Result :: term(), channel()} | {stop, Reason :: term(), channel()}). handle_timeout(TRef, {emit_stats, Stats}, - Channel = #channel{client = #{client_id := ClientId}, + Channel = #channel{client_info = #{client_id := ClientId}, timers = #{stats_timer := TRef}}) -> ok = emqx_cm:set_chan_stats(ClientId, Stats), {ok, clean_timer(stats_timer, Channel)}; @@ -810,7 +810,7 @@ reset_timer(Name, Time, Channel) -> clean_timer(Name, Channel = #channel{timers = Timers}) -> Channel#channel{timers = maps:remove(Name, Timers)}. -interval(stats_timer, #channel{client = #{zone := Zone}}) -> +interval(stats_timer, #channel{client_info = #{zone := Zone}}) -> emqx_zone:get_env(Zone, idle_timeout, 30000); interval(alive_timer, #channel{keepalive = KeepAlive}) -> emqx_keepalive:info(interval, KeepAlive); @@ -834,12 +834,12 @@ will_delay_interval(WillMsg) -> terminate(_, #channel{connected = undefined}) -> ok; -terminate(normal, #channel{conninfo = ConnInfo, client = ClientInfo}) -> +terminate(normal, #channel{conninfo = ConnInfo, client_info = ClientInfo}) -> ok = emqx_hooks:run('client.disconnected', [ClientInfo, normal, ConnInfo]); -terminate({shutdown, Reason}, #channel{conninfo = ConnInfo, client = ClientInfo}) +terminate({shutdown, Reason}, #channel{conninfo = ConnInfo, client_info = ClientInfo}) when Reason =:= kicked orelse Reason =:= discarded orelse Reason =:= takeovered -> ok = emqx_hooks:run('client.disconnected', [ClientInfo, Reason, ConnInfo]); -terminate(Reason, #channel{conninfo = ConnInfo, client = ClientInfo, will_msg = WillMsg}) -> +terminate(Reason, #channel{conninfo = ConnInfo, client_info = ClientInfo, will_msg = WillMsg}) -> publish_will_msg(WillMsg), ok = emqx_hooks:run('client.disconnected', [ClientInfo, Reason, ConnInfo]). @@ -866,7 +866,7 @@ enrich_conninfo(#mqtt_packet_connect{ properties = ConnProps, client_id = ClientId, username = Username}, Channel) -> - #channel{conninfo = ConnInfo, client = #{zone := Zone}} = Channel, + #channel{conninfo = ConnInfo, client_info = #{zone := Zone}} = Channel, MaxInflight = emqx_mqtt_props:get('Receive-Maximum', ConnProps, emqx_zone:max_inflight(Zone)), Interval = if ProtoVer == ?MQTT_PROTO_V5 -> @@ -889,18 +889,18 @@ enrich_conninfo(#mqtt_packet_connect{ {ok, Channel#channel{conninfo = NConnInfo}}. %% @doc Check connect packet. -check_connect(ConnPkt, #channel{client = #{zone := Zone}}) -> +check_connect(ConnPkt, #channel{client_info = #{zone := Zone}}) -> emqx_packet:check(ConnPkt, emqx_mqtt_caps:get_caps(Zone)). %% @doc Enrich client -enrich_client(ConnPkt, Channel = #channel{client = ClientInfo}) -> +enrich_client(ConnPkt, Channel = #channel{client_info = ClientInfo}) -> {ok, NConnPkt, NClientInfo} = pipeline([fun set_username/2, fun set_bridge_mode/2, fun maybe_username_as_clientid/2, fun maybe_assign_clientid/2, fun fix_mountpoint/2], ConnPkt, ClientInfo), - {ok, NConnPkt, Channel#channel{client = NClientInfo}}. + {ok, NConnPkt, Channel#channel{client_info = NClientInfo}}. set_username(#mqtt_packet_connect{username = Username}, ClientInfo = #{username := undefined}) -> @@ -931,20 +931,20 @@ fix_mountpoint(_ConnPkt, ClientInfo = #{mountpoint := Mountpoint}) -> {ok, ClientInfo#{mountpoint := emqx_mountpoint:replvar(Mountpoint, ClientInfo)}}. %% @doc Set logger metadata. -set_logger_meta(_ConnPkt, #channel{client = #{client_id := ClientId}}) -> +set_logger_meta(_ConnPkt, #channel{client_info = #{client_id := ClientId}}) -> emqx_logger:set_metadata_client_id(ClientId). %%-------------------------------------------------------------------- %% Check banned/flapping %%-------------------------------------------------------------------- -check_banned(_ConnPkt, #channel{client = ClientInfo = #{zone := Zone}}) -> +check_banned(_ConnPkt, #channel{client_info = ClientInfo = #{zone := Zone}}) -> case emqx_zone:enable_ban(Zone) andalso emqx_banned:check(ClientInfo) of true -> {error, ?RC_BANNED}; false -> ok end. -check_flapping(_ConnPkt, #channel{client = ClientInfo = #{zone := Zone}}) -> +check_flapping(_ConnPkt, #channel{client_info = ClientInfo = #{zone := Zone}}) -> case emqx_zone:enable_flapping_detect(Zone) andalso emqx_flapping:check(ClientInfo) of true -> {error, ?RC_CONNECTION_RATE_EXCEEDED}; @@ -958,10 +958,10 @@ check_flapping(_ConnPkt, #channel{client = ClientInfo = #{zone := Zone}}) -> auth_connect(#mqtt_packet_connect{client_id = ClientId, username = Username, password = Password}, - Channel = #channel{client = ClientInfo}) -> + Channel = #channel{client_info = ClientInfo}) -> case emqx_access_control:authenticate(ClientInfo#{password => Password}) of {ok, AuthResult} -> - {ok, Channel#channel{client = maps:merge(ClientInfo, AuthResult)}}; + {ok, Channel#channel{client_info = maps:merge(ClientInfo, AuthResult)}}; {error, Reason} -> ?LOG(warning, "Client ~s (Username: '~s') login failed for ~0p", [ClientId, Username, Reason]), @@ -1004,7 +1004,7 @@ save_alias(AliasId, Topic, Aliases) -> maps:put(AliasId, Topic, Aliases). %% Check Pub ACL check_pub_acl(#mqtt_packet{variable = #mqtt_packet_publish{topic_name = Topic}}, - #channel{client = ClientInfo}) -> + #channel{client_info = ClientInfo}) -> case is_acl_enabled(ClientInfo) andalso emqx_access_control:check_acl(ClientInfo, publish, Topic) of false -> ok; @@ -1033,7 +1033,7 @@ check_pub_caps(#mqtt_packet{header = #mqtt_packet_header{qos = QoS, retain = Retain } }, - #channel{client = #{zone := Zone}}) -> + #channel{client_info = #{zone := Zone}}) -> emqx_mqtt_caps:check_pub(Zone, #{qos => QoS, retain => Retain}). %% Check Sub @@ -1044,7 +1044,7 @@ check_subscribe(TopicFilter, SubOpts, Channel) -> end. %% Check Sub ACL -check_sub_acl(TopicFilter, #channel{client = ClientInfo}) -> +check_sub_acl(TopicFilter, #channel{client_info = ClientInfo}) -> case is_acl_enabled(ClientInfo) andalso emqx_access_control:check_acl(ClientInfo, subscribe, TopicFilter) of false -> allow; @@ -1052,7 +1052,7 @@ check_sub_acl(TopicFilter, #channel{client = ClientInfo}) -> end. %% Check Sub Caps -check_sub_caps(TopicFilter, SubOpts, #channel{client = #{zone := Zone}}) -> +check_sub_caps(TopicFilter, SubOpts, #channel{client_info = #{zone := Zone}}) -> emqx_mqtt_caps:check_sub(Zone, TopicFilter, SubOpts). enrich_subid(#{'Subscription-Identifier' := SubId}, TopicFilters) -> @@ -1063,12 +1063,12 @@ enrich_subid(_Properties, TopicFilters) -> enrich_subopts(SubOpts, #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}}) -> SubOpts; -enrich_subopts(SubOpts, #channel{client = #{zone := Zone, is_bridge := IsBridge}}) -> +enrich_subopts(SubOpts, #channel{client_info = #{zone := Zone, is_bridge := IsBridge}}) -> NL = flag(emqx_zone:ignore_loop_deliver(Zone)), SubOpts#{rap => flag(IsBridge), nl => NL}. enrich_caps(AckProps, #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}, - client = #{zone := Zone}}) -> + client_info = #{zone := Zone}}) -> #{max_packet_size := MaxPktSize, max_qos_allowed := MaxQoS, retain_available := Retain, @@ -1087,14 +1087,14 @@ enrich_caps(AckProps, #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}, enrich_caps(AckProps, _Channel) -> AckProps. -enrich_server_keepalive(AckProps, #channel{client = #{zone := Zone}}) -> +enrich_server_keepalive(AckProps, #channel{client_info = #{zone := Zone}}) -> case emqx_zone:server_keepalive(Zone) of undefined -> AckProps; Keepalive -> AckProps#{'Server-Keep-Alive' => Keepalive} end. enrich_assigned_clientid(AckProps, #channel{conninfo = ConnInfo, - client = #{client_id := ClientId} + client_info = #{client_id := ClientId} }) -> case maps:get(client_id, ConnInfo) of <<>> -> %% Original ClientId is null. @@ -1117,7 +1117,7 @@ ensure_keepalive(_AckProps, Channel = #channel{conninfo = ConnInfo}) -> ensure_keepalive_timer(maps:get(keepalive, ConnInfo), Channel). ensure_keepalive_timer(0, Channel) -> Channel; -ensure_keepalive_timer(Interval, Channel = #channel{client = #{zone := Zone}}) -> +ensure_keepalive_timer(Interval, Channel = #channel{client_info = #{zone := Zone}}) -> Backoff = emqx_zone:get_env(Zone, keepalive_backoff, 0.75), Keepalive = emqx_keepalive:init(round(timer:seconds(Interval) * Backoff)), ensure_timer(alive_timer, Channel#channel{keepalive = Keepalive}). diff --git a/src/emqx_cm.erl b/src/emqx_cm.erl index 4da131269..80d44f4fc 100644 --- a/src/emqx_cm.erl +++ b/src/emqx_cm.erl @@ -162,7 +162,7 @@ set_chan_stats(ClientId, ChanPid, Stats) -> ok. %% @doc Open a session. --spec(open_session(boolean(), emqx_types:client(), map()) +-spec(open_session(boolean(), emqx_types:client_info(), emqx_types:conninfo()) -> {ok, #{session := emqx_session:session(), present := boolean(), pendings => list()}} diff --git a/src/emqx_flapping.erl b/src/emqx_flapping.erl index ca0e411a0..c2d2f97e2 100644 --- a/src/emqx_flapping.erl +++ b/src/emqx_flapping.erl @@ -69,7 +69,7 @@ start_link() -> stop() -> gen_server:stop(?MODULE). %% @doc Check flapping when a MQTT client connected. --spec(check(emqx_types:client()) -> boolean()). +-spec(check(emqx_types:client_info()) -> boolean()). check(#{client_id := ClientId}) -> check(ClientId, get_policy()). @@ -81,7 +81,7 @@ check(ClientId, #{banned_interval := Interval}) -> end. %% @doc Detect flapping when a MQTT client disconnected. --spec(detect(emqx_types:client()) -> boolean()). +-spec(detect(emqx_types:client_info()) -> boolean()). detect(Client) -> detect(Client, get_policy()). detect(#{client_id := ClientId, peerhost := PeerHost}, diff --git a/src/emqx_mod_acl_internal.erl b/src/emqx_mod_acl_internal.erl index 36e4f67ff..14c8d9999 100644 --- a/src/emqx_mod_acl_internal.erl +++ b/src/emqx_mod_acl_internal.erl @@ -61,7 +61,7 @@ all_rules() -> %%-------------------------------------------------------------------- %% @doc Check ACL --spec(check_acl(emqx_types:client(), emqx_types:pubsub(), emqx_topic:topic(), +-spec(check_acl(emqx_types:client_info(), emqx_types:pubsub(), emqx_topic:topic(), emqx_access_rule:acl_result(), acl_rules()) -> {ok, allow} | {ok, deny} | ok). check_acl(Client, PubSub, Topic, _AclResult, Rules) -> diff --git a/src/emqx_packet.erl b/src/emqx_packet.erl index 6b7663b75..f1509137f 100644 --- a/src/emqx_packet.erl +++ b/src/emqx_packet.erl @@ -240,7 +240,7 @@ validate_topic_filters(TopicFilters) -> end, TopicFilters). %% @doc Publish Packet to Message. --spec(to_message(emqx_types:client(), emqx_ypes:packet()) -> emqx_types:message()). +-spec(to_message(emqx_types:client_info(), emqx_ypes:packet()) -> emqx_types:message()). to_message(#{client_id := ClientId, username := Username, peerhost := PeerHost}, #mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH, retain = Retain, diff --git a/src/emqx_session.erl b/src/emqx_session.erl index 434f5f5ed..7b3de773c 100644 --- a/src/emqx_session.erl +++ b/src/emqx_session.erl @@ -140,7 +140,7 @@ %%-------------------------------------------------------------------- %% @doc Init a session. --spec(init(emqx_types:client(), Options :: map()) -> session()). +-spec(init(emqx_types:client_info(), emqx_types:conninfo()) -> session()). init(#{zone := Zone}, #{receive_maximum := MaxInflight}) -> #session{max_subscriptions = get_env(Zone, max_subscriptions, 0), subscriptions = #{}, @@ -252,14 +252,14 @@ redeliver(Session = #session{inflight = Inflight}) -> %% Client -> Broker: SUBSCRIBE %%-------------------------------------------------------------------- --spec(subscribe(emqx_types:client(), emqx_types:topic(), emqx_types:subopts(), session()) +-spec(subscribe(emqx_types:client_info(), emqx_types:topic(), emqx_types:subopts(), session()) -> {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) andalso (not maps:is_key(TopicFilter, Subs)) of true -> {error, ?RC_QUOTA_EXCEEDED}; false -> - do_subscribe(Client, TopicFilter, SubOpts, Session) + do_subscribe(ClientInfo, TopicFilter, SubOpts, Session) end. is_subscriptions_full(#session{max_subscriptions = 0}) -> @@ -285,13 +285,13 @@ do_subscribe(Client = #{client_id := ClientId}, TopicFilter, SubOpts, %% Client -> Broker: UNSUBSCRIBE %%-------------------------------------------------------------------- --spec(unsubscribe(emqx_types:client(), emqx_types:topic(), session()) +-spec(unsubscribe(emqx_types:client_info(), emqx_types:topic(), session()) -> {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 {ok, SubOpts} -> 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)}}; error -> {error, ?RC_NO_SUBSCRIPTION_EXISTED} diff --git a/src/emqx_types.erl b/src/emqx_types.erl index 27f3c7b2e..b4e1520bf 100644 --- a/src/emqx_types.erl +++ b/src/emqx_types.erl @@ -32,7 +32,7 @@ ]). -export_type([ conninfo/0 - , client/0 + , client_info/0 , client_id/0 , username/0 , password/0 @@ -79,7 +79,6 @@ -export_type([ caps/0 , infos/0 - , attrs/0 , stats/0 ]). @@ -96,27 +95,29 @@ -type(topic() :: emqx_topic:topic()). -type(subid() :: binary() | atom()). --type(conninfo() :: #{peername := peername(), +-type(socktype() :: tcp | udp | ssl | proxy | atom()). +-type(conninfo() :: #{socktype := socktype(), + peername := peername(), sockname := peername(), peercert := esockd_peercert:peercert(), conn_mod := module(), atom() => term() }). --type(client() :: #{zone := zone(), - protocol := protocol(), - peerhost := peerhost(), - client_id := client_id(), - username := username(), - peercert := esockd_peercert:peercert(), - is_bridge := boolean(), - is_superuser := boolean(), - mountpoint := maybe(binary()), - ws_cookie := maybe(list()), - password => maybe(binary()), - auth_result => auth_result(), - anonymous => boolean(), - atom() => term() - }). +-type(client_info() :: #{zone := zone(), + protocol := protocol(), + peerhost := peerhost(), + client_id := client_id(), + username := username(), + peercert := esockd_peercert:peercert(), + is_bridge := boolean(), + is_superuser := boolean(), + mountpoint := maybe(binary()), + ws_cookie := maybe(list()), + password => maybe(binary()), + auth_result => auth_result(), + anonymous => boolean(), + atom() => term() + }). -type(client_id() :: binary()|atom()). -type(username() :: maybe(binary())). -type(password() :: maybe(binary())). @@ -167,6 +168,5 @@ -type(caps() :: emqx_mqtt_caps:caps()). -type(infos() :: #{atom() => term()}). --type(attrs() :: #{atom() => term()}). --type(stats() :: list({atom(), non_neg_integer()})). +-type(stats() :: #{atom() => non_neg_integer()|stats()}). From 2790ab318ddf6ecd1699cae5b8bbf7b237e788a1 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Tue, 24 Sep 2019 13:53:46 +0800 Subject: [PATCH 03/14] Rename 'client_id' field to 'client' --- include/emqx_mqtt.hrl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/emqx_mqtt.hrl b/include/emqx_mqtt.hrl index 3a138f393..30bd932c3 100644 --- a/include/emqx_mqtt.hrl +++ b/include/emqx_mqtt.hrl @@ -219,7 +219,7 @@ will_retain = false, keepalive = 0, properties = undefined, - client_id = <<>>, + clientid = <<>>, will_props = undefined, will_topic = undefined, will_payload = undefined, From 1a5c10bd6f91221bd9e05652f1ed24e54db43b67 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Tue, 24 Sep 2019 15:47:36 +0800 Subject: [PATCH 04/14] Depends on 'develop' branch of emqtt --- rebar.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebar.config b/rebar.config index 2a9e6225f..368666ff3 100644 --- a/rebar.config +++ b/rebar.config @@ -29,7 +29,7 @@ [{test, [{deps, [{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"}}} ]} ]} From 20ddd498fcfa2198c71990e590e5892d49ab3ae9 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Tue, 24 Sep 2019 17:06:25 +0800 Subject: [PATCH 05/14] Rename the 'client_id' field to 'clientid' --- src/emqx_access_control.erl | 2 +- src/emqx_access_rule.erl | 8 ++-- src/emqx_banned.erl | 12 +++--- src/emqx_cm.erl | 42 ++++++++++---------- src/emqx_cm_locker.erl | 10 ++--- src/emqx_cm_registry.erl | 10 ++--- src/emqx_flapping.erl | 20 +++++----- src/emqx_frame.erl | 4 +- src/emqx_logger.erl | 10 ++--- src/emqx_message.erl | 4 +- src/emqx_mod_acl_internal.erl | 2 +- src/emqx_mod_presence.erl | 4 +- src/emqx_mod_subscription.erl | 4 +- src/emqx_mountpoint.erl | 2 +- src/emqx_packet.erl | 16 ++++---- src/emqx_tracer.erl | 14 +++---- src/emqx_types.erl | 53 +++++++++++++++---------- test/emqx_access_SUITE.erl | 16 ++++---- test/emqx_banned_SUITE.erl | 18 ++++----- test/emqx_cm_SUITE.erl | 6 +-- test/emqx_flapping_SUITE.erl | 2 +- test/emqx_frame_SUITE.erl | 14 +++---- test/emqx_mountpoint_SUITE.erl | 4 +- test/emqx_msg_expiry_interval_SUITE.erl | 8 ++-- test/emqx_packet_SUITE.erl | 8 ++-- test/emqx_tracer_SUITE.erl | 23 ++++++----- 26 files changed, 165 insertions(+), 151 deletions(-) diff --git a/src/emqx_access_control.erl b/src/emqx_access_control.erl index ff5ad9d09..68773f95f 100644 --- a/src/emqx_access_control.erl +++ b/src/emqx_access_control.erl @@ -28,7 +28,7 @@ %% APIs %%-------------------------------------------------------------------- --spec(authenticate(emqx_types:client_info()) +-spec(authenticate(emqx_types:clientinfo()) -> {ok, #{auth_result := emqx_types:auth_result(), anonymous := boolean}} | {error, term()}). authenticate(Client) -> diff --git a/src/emqx_access_rule.erl b/src/emqx_access_rule.erl index 690b45998..b9ba7ef05 100644 --- a/src/emqx_access_rule.erl +++ b/src/emqx_access_rule.erl @@ -86,7 +86,7 @@ bin(B) when is_binary(B) -> B. %% @doc Match access rule --spec(match(emqx_types:client_info(), emqx_types:topic(), rule()) +-spec(match(emqx_types:clientinfo(), emqx_types:topic(), rule()) -> {matched, allow} | {matched, deny} | nomatch). match(_ClientInfo, _Topic, {AllowDeny, all}) when ?ALLOW_DENY(AllowDeny) -> {matched, AllowDeny}; @@ -104,7 +104,7 @@ match_who(_ClientInfo, {user, all}) -> true; match_who(_ClientInfo, {client, all}) -> true; -match_who(#{client_id := ClientId}, {client, ClientId}) -> +match_who(#{clientid := ClientId}, {client, ClientId}) -> true; match_who(#{username := Username}, {user, Username}) -> true; @@ -142,9 +142,9 @@ feed_var(ClientInfo, Pattern) -> feed_var(ClientInfo, Pattern, []). feed_var(_ClientInfo, [], Acc) -> lists:reverse(Acc); -feed_var(ClientInfo = #{client_id := undefined}, [<<"%c">>|Words], Acc) -> +feed_var(ClientInfo = #{clientid := undefined}, [<<"%c">>|Words], Acc) -> feed_var(ClientInfo, Words, [<<"%c">>|Acc]); -feed_var(ClientInfo = #{client_id := ClientId}, [<<"%c">>|Words], Acc) -> +feed_var(ClientInfo = #{clientid := ClientId}, [<<"%c">>|Words], Acc) -> feed_var(ClientInfo, Words, [ClientId |Acc]); feed_var(ClientInfo = #{username := undefined}, [<<"%u">>|Words], Acc) -> feed_var(ClientInfo, Words, [<<"%u">>|Acc]); diff --git a/src/emqx_banned.erl b/src/emqx_banned.erl index d41f32ee0..e9044c8c1 100644 --- a/src/emqx_banned.erl +++ b/src/emqx_banned.erl @@ -73,11 +73,11 @@ start_link() -> -spec(stop() -> ok). stop() -> gen_server:stop(?MODULE). --spec(check(emqx_types:client_info()) -> boolean()). -check(#{client_id := ClientId, - username := Username, - peerhost := IPAddr}) -> - ets:member(?BANNED_TAB, {client_id, ClientId}) +-spec(check(emqx_types:clientinfo()) -> boolean()). +check(#{clientid := ClientId, + username := Username, + peerhost := IPAddr}) -> + ets:member(?BANNED_TAB, {clientid, ClientId}) orelse ets:member(?BANNED_TAB, {username, Username}) orelse ets:member(?BANNED_TAB, {ipaddr, IPAddr}). @@ -85,7 +85,7 @@ check(#{client_id := ClientId, add(Banned) when is_record(Banned, 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()} | {peerhost, emqx_types:peerhost()}) -> ok). delete(Key) -> mnesia:dirty_delete(?BANNED_TAB, Key). diff --git a/src/emqx_cm.erl b/src/emqx_cm.erl index 80d44f4fc..d61b56b44 100644 --- a/src/emqx_cm.erl +++ b/src/emqx_cm.erl @@ -94,19 +94,19 @@ start_link() -> %% @doc Register a channel. %% 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, self()). %% @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) -> Chan = {ClientId, ChanPid}, true = ets:insert(?CHAN_TAB, Chan), ok = emqx_cm_registry:register_channel(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) -> true = do_unregister_channel({ClientId, self()}), ok. @@ -119,31 +119,31 @@ do_unregister_channel(Chan) -> true = ets:delete(?CHAN_STATS_TAB, Chan), ets:delete_object(?CHAN_TAB, Chan). -%% @doc Get attrs of a channel. --spec(get_chan_attrs(emqx_types:client_id()) -> maybe(emqx_types:attrs())). +%% @doc Get info of a channel. +-spec(get_chan_attrs(emqx_types:clientid()) -> maybe(emqx_types:attrs())). get_chan_attrs(ClientId) -> 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() -> Chan = {ClientId, ChanPid}, emqx_tables:lookup_value(?CHAN_ATTRS_TAB, Chan); get_chan_attrs(ClientId, ChanPid) -> rpc_call(node(ChanPid), get_chan_attrs, [ClientId, ChanPid]). -%% @doc Set attrs of a channel. --spec(set_chan_attrs(emqx_types:client_id(), emqx_types:attrs()) -> ok). -set_chan_attrs(ClientId, Attrs) when is_binary(ClientId) -> +%% @doc Set info of a channel. +-spec(set_chan_attrs(emqx_types:clientid(), emqx_types:attrs()) -> ok). +set_chan_attrs(ClientId, Info) when is_binary(ClientId) -> Chan = {ClientId, self()}, - true = ets:insert(?CHAN_ATTRS_TAB, {Chan, Attrs}), + true = ets:insert(?CHAN_ATTRS_TAB, {Chan, Info}), ok. %% @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) -> 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() -> Chan = {ClientId, ChanPid}, 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]). %% @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, 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) -> Chan = {ClientId, ChanPid}, true = ets:insert(?CHAN_STATS_TAB, {Chan, Stats}), ok. %% @doc Open a session. --spec(open_session(boolean(), emqx_types:client_info(), emqx_types:conninfo()) +-spec(open_session(boolean(), emqx_types:clientinfo(), emqx_types:conninfo()) -> {ok, #{session := emqx_session:session(), present := boolean(), pendings => list()}} | {error, Reason :: term()}). -open_session(true, ClientInfo = #{client_id := ClientId}, ConnInfo) -> +open_session(true, ClientInfo = #{clientid := ClientId}, ConnInfo) -> CleanStart = fun(_) -> ok = discard_session(ClientId), Session = emqx_session:init(ClientInfo, ConnInfo), @@ -175,7 +175,7 @@ open_session(true, ClientInfo = #{client_id := ClientId}, ConnInfo) -> end, emqx_cm_locker:trans(ClientId, CleanStart); -open_session(false, ClientInfo = #{client_id := ClientId}, ConnInfo) -> +open_session(false, ClientInfo = #{clientid := ClientId}, ConnInfo) -> ResumeStart = fun(_) -> case takeover_session(ClientId) of {ok, ConnMod, ChanPid, Session} -> @@ -192,7 +192,7 @@ open_session(false, ClientInfo = #{client_id := ClientId}, ConnInfo) -> emqx_cm_locker:trans(ClientId, ResumeStart). %% @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()}). takeover_session(ClientId) -> case lookup_channels(ClientId) of @@ -221,7 +221,7 @@ takeover_session(ClientId, ChanPid) -> rpc_call(node(ChanPid), takeover_session, [ClientId, ChanPid]). %% @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) -> case lookup_channels(ClientId) of [] -> ok; @@ -259,12 +259,12 @@ with_channel(ClientId, Fun) -> end. %% @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(global, ClientId). %% @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) -> case emqx_cm_registry:is_enabled() of true -> diff --git a/src/emqx_cm_locker.erl b/src/emqx_cm_locker.erl index 874863b63..18310a70a 100644 --- a/src/emqx_cm_locker.erl +++ b/src/emqx_cm_locker.erl @@ -32,11 +32,11 @@ start_link() -> 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, undefined). --spec(trans(maybe(emqx_types:client_id()), +-spec(trans(maybe(emqx_types:clientid()), fun(([node()])-> any()), ekka_locker:piggyback()) -> any()). trans(undefined, Fun, _Piggyback) -> Fun([]); @@ -48,15 +48,15 @@ trans(ClientId, Fun, Piggyback) -> {error, client_id_unavailable} end. --spec(lock(emqx_types:client_id()) -> ekka_locker:lock_result()). +-spec(lock(emqx_types:clientid()) -> ekka_locker:lock_result()). lock(ClientId) -> 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) -> ekka_locker:acquire(?MODULE, ClientId, strategy(), Piggyback). --spec(unlock(emqx_types:client_id()) -> {boolean(), [node()]}). +-spec(unlock(emqx_types:clientid()) -> {boolean(), [node()]}). unlock(ClientId) -> ekka_locker:release(?MODULE, ClientId, strategy()). diff --git a/src/emqx_cm_registry.erl b/src/emqx_cm_registry.erl index 04dd04e80..4f2562478 100644 --- a/src/emqx_cm_registry.erl +++ b/src/emqx_cm_registry.erl @@ -65,8 +65,8 @@ is_enabled() -> emqx:get_env(enable_channel_registry, true). %% @doc Register a global channel. --spec(register_channel(emqx_types:client_id() - | {emqx_types:client_id(), pid()}) -> ok). +-spec(register_channel(emqx_types:clientid() + | {emqx_types:clientid(), pid()}) -> ok). register_channel(ClientId) when is_binary(ClientId) -> register_channel({ClientId, self()}); @@ -77,8 +77,8 @@ register_channel({ClientId, ChanPid}) when is_binary(ClientId), is_pid(ChanPid) end. %% @doc Unregister a global channel. --spec(unregister_channel(emqx_types:client_id() - | {emqx_types:client_id(), pid()}) -> ok). +-spec(unregister_channel(emqx_types:clientid() + | {emqx_types:clientid(), pid()}) -> ok). unregister_channel(ClientId) when is_binary(ClientId) -> unregister_channel({ClientId, self()}); @@ -89,7 +89,7 @@ unregister_channel({ClientId, ChanPid}) when is_binary(ClientId), is_pid(ChanPid end. %% @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) -> [ChanPid || #channel{pid = ChanPid} <- mnesia:dirty_read(?TAB, ClientId)]. diff --git a/src/emqx_flapping.erl b/src/emqx_flapping.erl index c2d2f97e2..e058f0cda 100644 --- a/src/emqx_flapping.erl +++ b/src/emqx_flapping.erl @@ -51,7 +51,7 @@ }). -record(flapping, { - client_id :: emqx_types:client_id(), + clientid :: emqx_types:clientid(), peerhost :: emqx_types:peerhost(), started_at :: pos_integer(), detect_cnt :: pos_integer(), @@ -69,8 +69,8 @@ start_link() -> stop() -> gen_server:stop(?MODULE). %% @doc Check flapping when a MQTT client connected. --spec(check(emqx_types:client_info()) -> boolean()). -check(#{client_id := ClientId}) -> +-spec(check(emqx_types:clientinfo()) -> boolean()). +check(#{clientid := ClientId}) -> check(ClientId, get_policy()). check(ClientId, #{banned_interval := Interval}) -> @@ -81,10 +81,10 @@ check(ClientId, #{banned_interval := Interval}) -> end. %% @doc Detect flapping when a MQTT client disconnected. --spec(detect(emqx_types:client_info()) -> boolean()). +-spec(detect(emqx_types:clientinfo()) -> boolean()). detect(Client) -> detect(Client, get_policy()). -detect(#{client_id := ClientId, peerhost := PeerHost}, +detect(#{clientid := ClientId, peerhost := PeerHost}, Policy = #{threshold := Threshold}) -> try ets:update_counter(?FLAPPING_TAB, ClientId, {#flapping.detect_cnt, 1}) of Cnt when Cnt < Threshold -> false; @@ -97,7 +97,7 @@ detect(#{client_id := ClientId, peerhost := PeerHost}, catch error:badarg -> %% Create a flapping record. - Flapping = #flapping{client_id = ClientId, + Flapping = #flapping{clientid = ClientId, peerhost = PeerHost, started_at = emqx_time:now_ms(), detect_cnt = 1 @@ -131,7 +131,7 @@ handle_call(Req, _From, State) -> ?LOG(error, "Unexpected call: ~p", [Req]), {reply, ignored, State}. -handle_cast({detected, Flapping = #flapping{client_id = ClientId, +handle_cast({detected, Flapping = #flapping{clientid = ClientId, peerhost = PeerHost, started_at = StartedAt, 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", [ClientId, esockd_net:ntoa(PeerHost), DetectCnt, Duration]), %% Banned. - BannedFlapping = Flapping#flapping{client_id = {banned, ClientId}, + BannedFlapping = Flapping#flapping{clientid = {banned, ClientId}, banned_at = emqx_time:now_ms() }, 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, [{#flapping{started_at = '$1', banned_at = undefined, _ = '_'}, [{'<', '$1', NowTime-Duration}], ['$_']}, - {#flapping{client_id = {banned, '_'}, banned_at = '$1', _ = '_'}, + {#flapping{clientid = {banned, '_'}, banned_at = '$1', _ = '_'}, [{'<', '$1', NowTime-Interval}], ['$_']}]) of [] -> ok; Flappings -> - lists:foreach(fun(Flapping = #flapping{client_id = {banned, ClientId}}) -> + lists:foreach(fun(Flapping = #flapping{clientid = {banned, ClientId}}) -> ets:delete_object(?FLAPPING_TAB, Flapping), alarm_handler:clear_alarm({flapping_detected, ClientId}); (_) -> ok diff --git a/src/emqx_frame.erl b/src/emqx_frame.erl index 9795e60a3..e65f5d7c4 100644 --- a/src/emqx_frame.erl +++ b/src/emqx_frame.erl @@ -180,7 +180,7 @@ parse_packet(#mqtt_packet_header{type = ?CONNECT}, FrameBin, _Options) -> will_retain = bool(WillRetain), keepalive = KeepAlive, properties = Properties, - client_id = ClientId}, + clientid = ClientId}, {ConnPacket1, Rest5} = parse_will_message(ConnPacket, Rest4), {Username, Rest6} = parse_utf8_string(Rest5, bool(UsernameFlag)), {Passsword, <<>>} = parse_utf8_string(Rest6, bool(PasswordFlag)), @@ -435,7 +435,7 @@ serialize_variable(#mqtt_packet_connect{ will_retain = WillRetain, keepalive = KeepAlive, properties = Properties, - client_id = ClientId, + clientid = ClientId, will_props = WillProps, will_topic = WillTopic, will_payload = WillPayload, diff --git a/src/emqx_logger.erl b/src/emqx_logger.erl index 0b3038523..d3216a539 100644 --- a/src/emqx_logger.erl +++ b/src/emqx_logger.erl @@ -38,7 +38,7 @@ %% Configs -export([ set_metadata_peername/1 - , set_metadata_client_id/1 + , set_metadata_clientid/1 , set_proc_metadata/1 , set_primary_log_level/1 , set_log_handler_level/2 @@ -121,11 +121,11 @@ critical(Format, Args) -> critical(Metadata, Format, Args) when is_map(Metadata) -> logger:critical(Format, Args, Metadata). --spec(set_metadata_client_id(emqx_types:client_id()) -> ok). -set_metadata_client_id(<<>>) -> +-spec(set_metadata_clientid(emqx_types:clientid()) -> ok). +set_metadata_clientid(<<>>) -> ok; -set_metadata_client_id(ClientId) -> - set_proc_metadata(#{client_id => ClientId}). +set_metadata_clientid(ClientId) -> + set_proc_metadata(#{clientid => ClientId}). -spec(set_metadata_peername(peername_str()) -> ok). set_metadata_peername(Peername) -> diff --git a/src/emqx_message.erl b/src/emqx_message.erl index ca8a433d0..8d99e961d 100644 --- a/src/emqx_message.erl +++ b/src/emqx_message.erl @@ -71,13 +71,13 @@ make(Topic, Payload) -> make(undefined, Topic, Payload). --spec(make(atom() | emqx_types:client_id(), +-spec(make(atom() | emqx_types:clientid(), emqx_topic:topic(), emqx_types:payload()) -> emqx_types:message()). make(From, 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_topic:topic(), emqx_types:payload()) -> emqx_types:message()). diff --git a/src/emqx_mod_acl_internal.erl b/src/emqx_mod_acl_internal.erl index 14c8d9999..15c7a489e 100644 --- a/src/emqx_mod_acl_internal.erl +++ b/src/emqx_mod_acl_internal.erl @@ -61,7 +61,7 @@ all_rules() -> %%-------------------------------------------------------------------- %% @doc Check ACL --spec(check_acl(emqx_types:client_info(), 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()) -> {ok, allow} | {ok, deny} | ok). check_acl(Client, PubSub, Topic, _AclResult, Rules) -> diff --git a/src/emqx_mod_presence.erl b/src/emqx_mod_presence.erl index 84d69b48b..d9f6d5c75 100644 --- a/src/emqx_mod_presence.erl +++ b/src/emqx_mod_presence.erl @@ -88,8 +88,8 @@ on_client_disconnected(ClientInfo, Reason, ConnInfo, Env) -> ?LOG(error, "Failed to encode 'disconnected' presence: ~p", [Presence]) end. -clientid(#{client_id := undefined}, #{client_id := ClientId}) -> ClientId; -clientid(#{client_id := ClientId}, _ConnInfo) -> ClientId. +clientid(#{clientid := undefined}, #{clientid := ClientId}) -> ClientId; +clientid(#{clientid := ClientId}, _ConnInfo) -> ClientId. username(#{username := undefined}, #{username := Username}) -> Username; username(#{username := Username}, _ConnInfo) -> Username. diff --git a/src/emqx_mod_subscription.erl b/src/emqx_mod_subscription.erl index a42234856..47b9ab337 100644 --- a/src/emqx_mod_subscription.erl +++ b/src/emqx_mod_subscription.erl @@ -36,8 +36,8 @@ load(Topics) -> emqx_hooks:add('client.connected', {?MODULE, on_client_connected, [Topics]}). -on_client_connected(#{client_id := ClientId, - username := Username}, ?RC_SUCCESS, _ConnInfo, Topics) -> +on_client_connected(#{clientid := ClientId, + username := Username}, ?RC_SUCCESS, _ConnInfo, Topics) -> Replace = fun(Topic) -> rep(<<"%u">>, Username, rep(<<"%c">>, ClientId, Topic)) end, diff --git a/src/emqx_mountpoint.erl b/src/emqx_mountpoint.erl index 986bbd048..da7b712f3 100644 --- a/src/emqx_mountpoint.erl +++ b/src/emqx_mountpoint.erl @@ -66,7 +66,7 @@ unmount(MountPoint, Msg = #message{topic = Topic}) -> -spec(replvar(maybe(mountpoint()), map()) -> maybe(mountpoint())). replvar(undefined, _Vars) -> undefined; -replvar(MountPoint, #{client_id := ClientId, username := Username}) -> +replvar(MountPoint, #{clientid := ClientId, username := Username}) -> lists:foldl(fun feed_var/2, MountPoint, [{<<"%c">>, ClientId}, {<<"%u">>, Username}]). diff --git a/src/emqx_packet.erl b/src/emqx_packet.erl index f1509137f..6f3039978 100644 --- a/src/emqx_packet.erl +++ b/src/emqx_packet.erl @@ -181,16 +181,16 @@ check_proto_ver(#mqtt_packet_connect{proto_ver = Ver, %% MQTT3.1 does not allow null clientId check_client_id(#mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V3, - client_id = <<>>}, _Opts) -> + clientid = <<>>}, _Opts) -> {error, ?RC_CLIENT_IDENTIFIER_NOT_VALID}; %% 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) -> {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) -> ok; -check_client_id(#mqtt_packet_connect{client_id = ClientId}, +check_client_id(#mqtt_packet_connect{clientid = ClientId}, _Opts = #{max_clientid_len := MaxLen}) -> case (1 =< (Len = byte_size(ClientId))) andalso (Len =< MaxLen) of true -> ok; @@ -240,8 +240,8 @@ validate_topic_filters(TopicFilters) -> end, TopicFilters). %% @doc Publish Packet to Message. --spec(to_message(emqx_types:client_info(), emqx_ypes:packet()) -> emqx_types:message()). -to_message(#{client_id := ClientId, username := Username, peerhost := PeerHost}, +-spec(to_message(emqx_types:clientinfo(), emqx_ypes:packet()) -> emqx_types:message()). +to_message(#{clientid := ClientId, username := Username, peerhost := PeerHost}, #mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH, retain = Retain, qos = QoS, @@ -257,7 +257,7 @@ to_message(#{client_id := ClientId, username := Username, peerhost := PeerHost}, -spec(will_msg(#mqtt_packet_connect{}) -> emqx_types:message()). will_msg(#mqtt_packet_connect{will_flag = false}) -> undefined; -will_msg(#mqtt_packet_connect{client_id = ClientId, +will_msg(#mqtt_packet_connect{clientid = ClientId, username = Username, will_retain = Retain, will_qos = QoS, @@ -304,7 +304,7 @@ format_variable(#mqtt_packet_connect{ will_flag = WillFlag, clean_start = CleanStart, keepalive = KeepAlive, - client_id = ClientId, + clientid = ClientId, will_topic = WillTopic, will_payload = WillPayload, username = Username, diff --git a/src/emqx_tracer.erl b/src/emqx_tracer.erl index ab102d6bb..36daa9f50 100644 --- a/src/emqx_tracer.erl +++ b/src/emqx_tracer.erl @@ -28,16 +28,16 @@ , stop_trace/1 ]). --type(trace_who() :: {client_id | topic, binary() | list()}). +-type(trace_who() :: {clientid | topic, binary() | list()}). -define(TRACER, ?MODULE). -define(FORMAT, {emqx_logger_formatter, #{template => [time," [",level,"] ", - {client_id, + {clientid, [{peername, - [client_id,"@",peername," "], - [client_id, " "]}], + [clientid,"@",peername," "], + [clientid, " "]}], [{peername, [peername," "], []}]}, @@ -45,7 +45,7 @@ -define(TOPIC_TRACE_ID(T), "trace_topic_"++T). -define(CLIENT_TRACE_ID(C), "trace_clientid_"++C). -define(TOPIC_TRACE(T), {topic,T}). --define(CLIENT_TRACE(C), {client_id,C}). +-define(CLIENT_TRACE(C), {clientid,C}). -define(is_log_level(L), L =:= emergency orelse @@ -67,7 +67,7 @@ trace(publish, #message{from = From, topic = Topic, payload = Payload}) when is_binary(From); is_atom(From) -> 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()}). start_trace(Who, all, LogFile) -> start_trace(Who, debug, LogFile); @@ -87,7 +87,7 @@ start_trace(Who, Level, LogFile) -> false -> {error, {invalid_log_level, Level}} end. -%% @doc Stop tracing client_id or topic. +%% @doc Stop tracing clientid or topic. -spec(stop_trace(trace_who()) -> ok | {error, term()}). stop_trace(Who) -> uninstall_trance_handler(Who). diff --git a/src/emqx_types.erl b/src/emqx_types.erl index b4e1520bf..c2744b3c6 100644 --- a/src/emqx_types.erl +++ b/src/emqx_types.erl @@ -32,8 +32,8 @@ ]). -export_type([ conninfo/0 - , client_info/0 - , client_id/0 + , clientinfo/0 + , clientid/0 , username/0 , password/0 , peerhost/0 @@ -78,6 +78,7 @@ ]). -export_type([ caps/0 + , attrs/0 , infos/0 , stats/0 ]). @@ -97,28 +98,39 @@ -type(socktype() :: tcp | udp | ssl | proxy | atom()). -type(conninfo() :: #{socktype := socktype(), - peername := peername(), sockname := peername(), + peername := peername(), peercert := esockd_peercert:peercert(), 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_info() :: #{zone := zone(), - protocol := protocol(), - peerhost := peerhost(), - client_id := client_id(), - username := username(), - peercert := esockd_peercert:peercert(), - is_bridge := boolean(), - is_superuser := boolean(), - mountpoint := maybe(binary()), - ws_cookie := maybe(list()), - password => maybe(binary()), - auth_result => auth_result(), - anonymous => boolean(), - atom() => term() - }). --type(client_id() :: binary()|atom()). +-type(clientinfo() :: #{zone := zone(), + protocol := protocol(), + peerhost := peerhost(), + clientid := clientid(), + username := username(), + peercert := esockd_peercert:peercert(), + is_bridge := boolean(), + is_superuser := boolean(), + mountpoint := maybe(binary()), + ws_cookie := maybe(list()), + password => maybe(binary()), + auth_result => auth_result(), + anonymous => boolean(), + atom() => term() + }). +-type(clientid() :: binary()|atom()). -type(username() :: maybe(binary())). -type(password() :: maybe(binary())). -type(peerhost() :: inet:ip_address()). @@ -167,6 +179,7 @@ -type(command() :: #command{}). -type(caps() :: emqx_mqtt_caps:caps()). +-type(attrs() :: #{atom() => term()}). -type(infos() :: #{atom() => term()}). -type(stats() :: #{atom() => non_neg_integer()|stats()}). diff --git a/test/emqx_access_SUITE.erl b/test/emqx_access_SUITE.erl index 5b1694e74..23f3844b4 100644 --- a/test/emqx_access_SUITE.erl +++ b/test/emqx_access_SUITE.erl @@ -113,7 +113,7 @@ t_reload_acl(_) -> t_check_acl_1(_) -> Client = #{zone => external, - client_id => <<"client1">>, + clientid => <<"client1">>, username => <<"testuser">> }, allow = ?AC:check_acl(Client, subscribe, <<"users/testuser/1">>), @@ -124,14 +124,14 @@ t_check_acl_1(_) -> t_check_acl_2(_) -> Client = #{zone => external, - client_id => <<"client2">>, + clientid => <<"client2">>, username => <<"xyz">> }, deny = ?AC:check_acl(Client, subscribe, <<"a/b/c">>). t_acl_cache_basic(_) -> Client = #{zone => external, - client_id => <<"client1">>, + clientid => <<"client1">>, username => <<"testuser">> }, not_found = ?CACHE:get_acl_cache(subscribe, <<"users/testuser/1">>), @@ -146,7 +146,7 @@ t_acl_cache_basic(_) -> t_acl_cache_expiry(_) -> application:set_env(emqx, acl_cache_ttl, 100), Client = #{zone => external, - client_id => <<"client1">>, + clientid => <<"client1">>, username => <<"testuser">> }, allow = ?AC:check_acl(Client, subscribe, <<"clients/client1">>), @@ -157,7 +157,7 @@ t_acl_cache_expiry(_) -> t_acl_cache_full(_) -> application:set_env(emqx, acl_cache_max_size, 1), Client = #{zone => external, - client_id => <<"client1">>, + clientid => <<"client1">>, username => <<"testuser">> }, 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_max_size, 2), Client = #{zone => external, - client_id => <<"client1">>, + clientid => <<"client1">>, username => <<"testuser">> }, allow = ?AC:check_acl(Client, subscribe, <<"users/testuser/1">>), @@ -345,12 +345,12 @@ t_compile_rule(_) -> t_match_rule(_) -> ClientInfo1 = #{zone => external, - client_id => <<"testClient">>, + clientid => <<"testClient">>, username => <<"TestUser">>, peerhost => {127,0,0,1} }, ClientInfo2 = #{zone => external, - client_id => <<"testClient">>, + clientid => <<"testClient">>, username => <<"TestUser">>, peerhost => {192,168,0,10} }, diff --git a/test/emqx_banned_SUITE.erl b/test/emqx_banned_SUITE.erl index 99c5df3d3..7801aea9a 100644 --- a/test/emqx_banned_SUITE.erl +++ b/test/emqx_banned_SUITE.erl @@ -37,7 +37,7 @@ end_per_suite(_Config) -> ekka_mnesia:delete_schema(). t_add_delete(_) -> - Banned = #banned{who = {client_id, <<"TestClient">>}, + Banned = #banned{who = {clientid, <<"TestClient">>}, reason = <<"test">>, by = <<"banned suite">>, desc = <<"test">>, @@ -45,27 +45,27 @@ t_add_delete(_) -> }, ok = emqx_banned:add(Banned), ?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)). 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 = {ipaddr, {192,168,0,1}}}), ?assertEqual(3, emqx_banned:info(size)), - ClientInfo1 = #{client_id => <<"BannedClient">>, + ClientInfo1 = #{clientid => <<"BannedClient">>, username => <<"user">>, peerhost => {127,0,0,1} }, - ClientInfo2 = #{client_id => <<"client">>, + ClientInfo2 = #{clientid => <<"client">>, username => <<"BannedUser">>, peerhost => {127,0,0,1} }, - ClientInfo3 = #{client_id => <<"client">>, + ClientInfo3 = #{clientid => <<"client">>, username => <<"user">>, peerhost => {192,168,0,1} }, - ClientInfo4 = #{client_id => <<"client">>, + ClientInfo4 = #{clientid => <<"client">>, username => <<"user">>, peerhost => {127,0,0,1} }, @@ -73,7 +73,7 @@ t_check(_) -> ?assert(emqx_banned:check(ClientInfo2)), ?assert(emqx_banned:check(ClientInfo3)), ?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({ipaddr, {192,168,0,1}}), ?assertNot(emqx_banned:check(ClientInfo1)), @@ -84,7 +84,7 @@ t_check(_) -> t_unused(_) -> {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) }), ?assertEqual(ignored, gen_server:call(Banned, unexpected_req)), diff --git a/test/emqx_cm_SUITE.erl b/test/emqx_cm_SUITE.erl index 66c793eed..644ffc1a8 100644 --- a/test/emqx_cm_SUITE.erl +++ b/test/emqx_cm_SUITE.erl @@ -63,17 +63,17 @@ t_get_set_chan_stats(_) -> t_open_session(_) -> ClientInfo = #{zone => external, - client_id => <<"clientid">>, + clientid => <<"clientid">>, username => <<"username">>, peerhost => {127,0,0,1}}, ConnInfo = #{peername => {{127,0,0,1}, 5000}, receive_maximum => 100}, {ok, #{session := Session1, present := false}} = 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}} = 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(_) -> ok = emqx_cm:discard_session(<<"clientid">>). diff --git a/test/emqx_flapping_SUITE.erl b/test/emqx_flapping_SUITE.erl index 4476b2957..a9234c541 100644 --- a/test/emqx_flapping_SUITE.erl +++ b/test/emqx_flapping_SUITE.erl @@ -41,7 +41,7 @@ end_per_suite(_Config) -> t_detect_check(_) -> ClientInfo = #{zone => external, - client_id => <<"clientid">>, + clientid => <<"clientid">>, peerhost => {127,0,0,1} }, false = emqx_flapping:detect(ClientInfo), diff --git a/test/emqx_frame_SUITE.erl b/test/emqx_frame_SUITE.erl index e1991c359..09ef7d901 100644 --- a/test/emqx_frame_SUITE.erl +++ b/test/emqx_frame_SUITE.erl @@ -147,7 +147,7 @@ prop_serialize_parse_connect() -> Packet = ?CONNECT_PACKET(#mqtt_packet_connect{ proto_name = ProtoName, proto_ver = ProtoVer, - client_id = <<"clientId">>, + clientid = <<"clientId">>, will_qos = ?QOS_1, will_flag = true, will_retain = true, @@ -167,7 +167,7 @@ t_serialize_parse_v3_connect(_) -> Packet = ?CONNECT_PACKET( #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V3, proto_name = <<"MQIsdp">>, - client_id = <<"mosqpub/10451-iMac.loca">>, + clientid = <<"mosqpub/10451-iMac.loca">>, clean_start = true, keepalive = 60 }), @@ -180,7 +180,7 @@ t_serialize_parse_v4_connect(_) -> Packet = ?CONNECT_PACKET( #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V4, proto_name = <<"MQTT">>, - client_id = <<"mosqpub/10451-iMac.loca">>, + clientid = <<"mosqpub/10451-iMac.loca">>, clean_start = true, keepalive = 60 }), @@ -213,7 +213,7 @@ t_serialize_parse_v5_connect(_) -> proto_ver = ?MQTT_PROTO_V5, is_bridge = false, clean_start = true, - client_id = <<>>, + clientid = <<>>, will_flag = true, will_qos = ?QOS_1, 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>>, Packet = ?CONNECT_PACKET(#mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V4, proto_name = <<"MQTT">>, - client_id = <<>>, + clientid = <<>>, clean_start = true, keepalive = 60 }), @@ -246,7 +246,7 @@ t_serialize_parse_connect_with_will(_) -> Packet = #mqtt_packet{header = #mqtt_packet_header{type = ?CONNECT}, variable = #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V3, proto_name = <<"MQIsdp">>, - client_id = <<"mosqpub/10452-iMac.loca">>, + clientid = <<"mosqpub/10452-iMac.loca">>, clean_start = true, keepalive = 60, 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>>, Topic = <<"$SYS/broker/connection/C_00:0C:29:2B:77:52/state">>, 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_name = <<"MQIsdp">>, is_bridge = true, diff --git a/test/emqx_mountpoint_SUITE.erl b/test/emqx_mountpoint_SUITE.erl index bb1f40db4..d78bfe5e7 100644 --- a/test/emqx_mountpoint_SUITE.erl +++ b/test/emqx_mountpoint_SUITE.erl @@ -56,12 +56,12 @@ t_replvar(_) -> ?assertEqual(undefined, replvar(undefined, #{})), ?assertEqual(<<"mount/user/clientid/">>, replvar(<<"mount/%u/%c/">>, - #{client_id => <<"clientid">>, + #{clientid => <<"clientid">>, username => <<"user">> })), ?assertEqual(<<"mount/%u/clientid/">>, replvar(<<"mount/%u/%c/">>, - #{client_id => <<"clientid">>, + #{clientid => <<"clientid">>, username => undefined })). diff --git a/test/emqx_msg_expiry_interval_SUITE.erl b/test/emqx_msg_expiry_interval_SUITE.erl index 6fe18ff5a..063cf19dc 100644 --- a/test/emqx_msg_expiry_interval_SUITE.erl +++ b/test/emqx_msg_expiry_interval_SUITE.erl @@ -42,8 +42,8 @@ t_message_expiry_interval_2(_) -> emqtt:stop(ClientA). 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, ClientB} = emqtt:start_link([{proto_ver,v5}, {client_id, <<"client-b">>}, {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}, {clientid, <<"client-b">>}, {clean_start, false},{properties, #{'Session-Expiry-Interval' => 360}}]), {ok, _} = emqtt:connect(ClientA), {ok, _} = emqtt:connect(ClientB), %% subscribe and disconnect client-b @@ -58,7 +58,7 @@ message_expiry_interval_exipred(ClientA, QoS) -> ct:sleep(1500), %% 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), %% 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 %% as Message-Expiry-Interval = 20s 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), %% verify client-b could receive the publish message and the Message-Expiry-Interval is set diff --git a/test/emqx_packet_SUITE.erl b/test/emqx_packet_SUITE.erl index 95ad2d305..5b9845231 100644 --- a/test/emqx_packet_SUITE.erl +++ b/test/emqx_packet_SUITE.erl @@ -112,11 +112,11 @@ t_check_connect(_) -> ConnPkt2 = #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V3, proto_name = <<"MQIsdp">>, - client_id = <<>> + clientid = <<>> }, {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), ConnPkt4 = #mqtt_packet_connect{will_flag = true, @@ -152,7 +152,7 @@ t_from_to_message(_) -> packet_id = 10, properties = #{}}, payload = <<"payload">>}, - MsgFromPkt = emqx_packet:to_message(#{client_id => <<"clientid">>, + MsgFromPkt = emqx_packet:to_message(#{clientid => <<"clientid">>, username => <<"test">>, peerhost => {127,0,0,1}}, Pkt), ?assertEqual(ExpectedMsg2, MsgFromPkt#message{id = emqx_message:id(ExpectedMsg), @@ -161,7 +161,7 @@ t_from_to_message(_) -> t_will_msg(_) -> Pkt = #mqtt_packet_connect{will_flag = true, - client_id = <<"clientid">>, + clientid = <<"clientid">>, username = "test", will_retain = true, will_qos = ?QOS_2, diff --git a/test/emqx_tracer_SUITE.erl b/test/emqx_tracer_SUITE.erl index 1d9afe028..ece0f5799 100644 --- a/test/emqx_tracer_SUITE.erl +++ b/test/emqx_tracer_SUITE.erl @@ -35,18 +35,19 @@ end_per_suite(_Config) -> t_start_traces(_Config) -> {ok, T} = emqtt:start_link([{host, "localhost"}, - {client_id, <<"client">>}, - {username, <<"testuser">>}, - {password, <<"pass">>}]), + {clientid, <<"client">>}, + {username, <<"testuser">>}, + {password, <<"pass">>} + ]), emqtt:connect(T), %% Start tracing 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), - ok = emqx_tracer:start_trace({client_id, <<"client">>}, debug, "tmp/client.log"), - ok = emqx_tracer:start_trace({client_id, <<"client2">>}, all, "tmp/client2.log"), - {error, {invalid_log_level, bad_level}} = emqx_tracer:start_trace({client_id, <<"client3">>}, bad_level, "tmp/client3.log"), + ok = emqx_tracer:start_trace({clientid, <<"client">>}, debug, "tmp/client.log"), + ok = emqx_tracer:start_trace({clientid, <<"client2">>}, all, "tmp/client2.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"), ct:sleep(100), @@ -56,8 +57,8 @@ t_start_traces(_Config) -> ?assert(filelib:is_regular("tmp/topic_trace.log")), %% Get current traces - ?assertEqual([{{client_id,"client"},{debug,"tmp/client.log"}}, - {{client_id,"client2"},{debug,"tmp/client2.log"}}, + ?assertEqual([{{clientid,"client"},{debug,"tmp/client.log"}}, + {{clientid,"client2"},{debug,"tmp/client2.log"}}, {{topic,"a/#"},{debug,"tmp/topic_trace.log"}}], emqx_tracer:lookup_traces()), %% set the overall log level to debug @@ -73,8 +74,8 @@ t_start_traces(_Config) -> ?assert(filelib:file_size("tmp/client2.log") == 0), %% Stop tracing - ok = emqx_tracer:stop_trace({client_id, <<"client">>}), - ok = emqx_tracer:stop_trace({client_id, <<"client2">>}), + ok = emqx_tracer:stop_trace({clientid, <<"client">>}), + ok = emqx_tracer:stop_trace({clientid, <<"client2">>}), ok = emqx_tracer:stop_trace({topic, <<"a/#">>}), emqtt:disconnect(T), From 609f442ea9fc313fc12049f816c8302f1f8962a0 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Tue, 24 Sep 2019 17:07:32 +0800 Subject: [PATCH 06/14] Add function 'get_counters/1' --- src/emqx_pd.erl | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/emqx_pd.erl b/src/emqx_pd.erl index 9800d9d57..db33927d0 100644 --- a/src/emqx_pd.erl +++ b/src/emqx_pd.erl @@ -19,27 +19,33 @@ -include("types.hrl"). --export([ update_counter/2 +-export([ get_counters/1 , get_counter/1 + , update_counter/2 , reset_counter/1 ]). -compile({inline, - [ update_counter/2 + [ get_counters/1 , get_counter/1 + , update_counter/2 , reset_counter/1 ]}). -type(key() :: term()). --spec(update_counter(key(), number()) -> maybe(number())). -update_counter(Key, Inc) -> - put(Key, get_counter(Key) + Inc). +-spec(get_counters(list(key())) -> list({key(), number()})). +get_counters(Keys) when is_list(Keys) -> + [{Key, emqx_pd:get_counter(Key)} || Key <- Keys]. -spec(get_counter(key()) -> number()). get_counter(Key) -> 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()). reset_counter(Key) -> case put(Key, 0) of undefined -> 0; Cnt -> Cnt end. From 89b03eb9a6a9dfb14046b54f077d71d7f110cae9 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Tue, 24 Sep 2019 17:08:06 +0800 Subject: [PATCH 07/14] Add function specs --- src/emqx_time.erl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/emqx_time.erl b/src/emqx_time.erl index 16508cdda..073ee7067 100644 --- a/src/emqx_time.erl +++ b/src/emqx_time.erl @@ -34,15 +34,19 @@ seed() -> rand:seed(exsplus, erlang:timestamp()). +-spec(now_secs() -> pos_integer()). now_secs() -> erlang:system_time(second). +-spec(now_secs(erlang:timestamp()) -> pos_integer()). now_secs({MegaSecs, Secs, _MicroSecs}) -> MegaSecs * 1000000 + Secs. +-spec(now_ms() -> pos_integer()). now_ms() -> erlang:system_time(millisecond). +-spec(now_ms(erlang:timestamp()) -> pos_integer()). now_ms({MegaSecs, Secs, MicroSecs}) -> (MegaSecs * 1000000 + Secs) * 1000 + round(MicroSecs/1000). From 7b1a80c265bca62c163c5de9a2bd22391c328b3b Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Fri, 27 Sep 2019 20:26:54 +0800 Subject: [PATCH 08/14] Rename 'mailbox_len' to 'message_queue_len' --- src/emqx_misc.erl | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/emqx_misc.erl b/src/emqx_misc.erl index 8a7dda032..b69b06c55 100644 --- a/src/emqx_misc.erl +++ b/src/emqx_misc.erl @@ -116,17 +116,19 @@ cancel_timer(_) -> ok. proc_name(Mod, Id) -> list_to_atom(lists:concat([Mod, "_", Id])). --spec(proc_stats() -> list()). -proc_stats() -> - proc_stats(self()). +%% Get Proc's Stats. +-spec(proc_stats() -> emqx_types:stats()). +proc_stats() -> proc_stats(self()). --spec(proc_stats(pid()) -> list()). +-spec(proc_stats(pid()) -> emqx_types:stats()). proc_stats(Pid) -> - case process_info(Pid, [message_queue_len, heap_size, - total_heap_size, reductions, memory]) of + case process_info(Pid, [message_queue_len, + heap_size, + total_heap_size, + reductions, + memory]) of undefined -> []; - [{message_queue_len, Len}|Stats] -> - [{mailbox_len, Len}|Stats] + ProcStats -> ProcStats end. %% @doc Drain delivers from the channel's mailbox. From fcb1b83495c5b4aba807334ccc8b8e71a14d3bba Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Sat, 28 Sep 2019 10:06:16 +0800 Subject: [PATCH 09/14] Rename the 'client_id' option to 'clientid' --- test/emqx_modules_SUITE.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/emqx_modules_SUITE.erl b/test/emqx_modules_SUITE.erl index 550deacb7..401cc2895 100644 --- a/test/emqx_modules_SUITE.erl +++ b/test/emqx_modules_SUITE.erl @@ -55,11 +55,11 @@ end_per_suite(_Config) -> %% Test case for emqx_mod_presence t_mod_presence(_) -> 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, _Props, [?QOS_1]} = emqtt:subscribe(C1, <<"$SYS/brokers/+/clients/#">>, qos1), %% Connected Presence - {ok, C2} = emqtt:start_link([{client_id, <<"clientid">>}, + {ok, C2} = emqtt:start_link([{clientid, <<"clientid">>}, {username, <<"username">>}]), {ok, _} = emqtt:connect(C2), ok = recv_and_check_presence(<<"clientid">>, <<"connected">>), @@ -98,7 +98,7 @@ recv_and_check_presence(ClientId, Presence) -> t_mod_subscription(_) -> emqx_mod_subscription:load([{<<"connected/%c/%u">>, ?QOS_0}]), {ok, C} = emqtt:start_link([{host, "localhost"}, - {client_id, "myclient"}, + {clientid, "myclient"}, {username, "admin"}]), {ok, _} = emqtt:connect(C), emqtt:publish(C, <<"connected/myclient/admin">>, <<"Hello world">>, ?QOS_0), @@ -111,7 +111,7 @@ t_mod_subscription(_) -> %% Test case for emqx_mod_write t_mod_rewrite(_Config) -> 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), 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">>], From 8ab682151d675d4b68b3a2dd3b394bc985d45fcd Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Sun, 29 Sep 2019 10:22:02 +0800 Subject: [PATCH 10/14] Improve the connection and channel modules - Rename the 'client_id' field to 'clientid' - Support publish stats in channel module - Update test cases for frame and channel modules --- src/emqx_channel.erl | 396 ++++++++++++++++++--------------- src/emqx_connection.erl | 367 +++++++++++++++--------------- src/emqx_frame.erl | 114 +++++++--- src/emqx_session.erl | 69 ++++-- src/emqx_stats.erl | 4 +- src/emqx_ws_connection.erl | 301 +++++++++++-------------- src/emqx_zone.erl | 35 ++- test/emqx_channel_SUITE.erl | 114 ++++++---- test/emqx_client_SUITE.erl | 17 +- test/emqx_connection_SUITE.erl | 2 +- test/emqx_frame_SUITE.erl | 10 +- test/emqx_misc_SUITE.erl | 2 +- test/emqx_session_SUITE.erl | 4 +- test/emqx_shared_sub_SUITE.erl | 12 +- 14 files changed, 781 insertions(+), 666 deletions(-) diff --git a/src/emqx_channel.erl b/src/emqx_channel.erl index 5d76f8414..cad4111d4 100644 --- a/src/emqx_channel.erl +++ b/src/emqx_channel.erl @@ -38,9 +38,9 @@ , handle_in/2 , handle_out/2 , handle_call/2 - , handle_cast/2 , handle_info/2 , handle_timeout/3 + , disconnect/2 , terminate/2 ]). @@ -60,7 +60,7 @@ %% MQTT ConnInfo conninfo :: emqx_types:conninfo(), %% MQTT ClientInfo - client_info :: emqx_types:client_info(), + clientinfo :: emqx_types:clientinfo(), %% MQTT Session session :: emqx_session:session(), %% Keepalive @@ -71,18 +71,14 @@ topic_aliases :: maybe(map()), %% MQTT Topic Alias Maximum alias_maximum :: maybe(map()), + %% Publish Stats + pub_stats :: emqx_types:stats(), %% Timers timers :: #{atom() => disabled | maybe(reference())}, + %% Fsm State + fsm_state :: fsm_state(), %% 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 :: boolean(), %% Resume @@ -93,6 +89,14 @@ -opaque(channel() :: #channel{}). +-type(fsm_state() :: #{state_name := initialized + | connecting + | connected + | disconnected, + connected_at := pos_integer(), + disconnected := pos_integer() + }). + -define(TIMER_TABLE, #{ stats_timer => emit_stats, alive_timer => keepalive, @@ -102,8 +106,10 @@ will_timer => will_message }). --define(ATTR_KEYS, [conninfo, client_info, session, connected, connected_at, disconnected_at]). --define(INFO_KEYS, ?ATTR_KEYS ++ [keepalive, topic_aliases, alias_maximum, gc_state, disconnected_at]). +-define(ATTR_KEYS, [conninfo, client, session]). + +-define(INFO_KEYS, ?ATTR_KEYS ++ [conninfo, client, session, keepalive, + will_msg, topic_aliases, alias_maximum, gc_state]). %%-------------------------------------------------------------------- %% Info, Attrs and Caps @@ -119,7 +125,7 @@ info(Keys, Channel) when is_list(Keys) -> [{Key, info(Key, Channel)} || Key <- Keys]; info(conninfo, #channel{conninfo = ConnInfo}) -> ConnInfo; -info(client_info, #channel{client_info = ClientInfo}) -> +info(client, #channel{clientinfo = ClientInfo}) -> ClientInfo; info(session, #channel{session = Session}) -> maybe_apply(fun emqx_session:info/1, Session); @@ -129,36 +135,31 @@ info(topic_aliases, #channel{topic_aliases = Aliases}) -> Aliases; info(alias_maximum, #channel{alias_maximum = Limits}) -> Limits; +info(will_msg, #channel{will_msg = undefined}) -> + undefined; 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}) -> - 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. + maybe_apply(fun emqx_gc:info/1, GcState). %% @doc Get attrs of the channel. -spec(attrs(channel()) -> emqx_types:attrs()). 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}) -> - ConnInfo; -attr(session, #channel{session = Session}) -> +attrs(session, #channel{session = 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()). -stats(#channel{session = Session}) -> - emqx_session:stats(Session). +stats(#channel{pub_stats = PubStats, session = Session}) -> + maps:to_list(PubStats) ++ emqx_session:stats(Session). -spec(caps(channel()) -> emqx_types:caps()). -caps(#channel{client_info = #{zone := Zone}}) -> +caps(#channel{clientinfo = #{zone := Zone}}) -> emqx_mqtt_caps:get_caps(Zone). %% For tests @@ -172,7 +173,7 @@ set_field(Name, Val, 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), Peercert = maps:get(peercert, ConnInfo, undefined), Username = case peer_cert_as_username(Options) of @@ -181,12 +182,13 @@ init(ConnInfo = #{peername := {PeerHost, _Port}, protocol := Protocol}, Options) crt -> Peercert; _ -> undefined end, + Protocol = maps:get(protocol, ConnInfo, mqtt), MountPoint = emqx_zone:get_env(Zone, mountpoint), ClientInfo = #{zone => Zone, protocol => Protocol, peerhost => PeerHost, peercert => Peercert, - client_id => undefined, + clientid => undefined, username => Username, mountpoint => MountPoint, is_bridge => false, @@ -196,15 +198,15 @@ init(ConnInfo = #{peername := {PeerHost, _Port}, protocol := Protocol}, Options) true -> undefined; false -> disabled end, - #channel{conninfo = ConnInfo, - client_info = ClientInfo, - gc_state = init_gc_state(Zone), - oom_policy = init_oom_policy(Zone), - timers = #{stats_timer => StatsTimer}, - connected = undefined, - takeover = false, - resuming = false, - pendings = [] + #channel{conninfo = ConnInfo, + clientinfo = ClientInfo, + pub_stats = #{}, + timers = #{stats_timer => StatsTimer}, + fsm_state = #{state_name => initialized}, + gc_state = init_gc_state(Zone), + takeover = false, + resuming = false, + pendings = [] }. peer_cert_as_username(Options) -> @@ -213,9 +215,6 @@ peer_cert_as_username(Options) -> init_gc_state(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 %%-------------------------------------------------------------------- @@ -224,9 +223,11 @@ init_oom_policy(Zone) -> -> {ok, channel()} | {ok, emqx_types:packet(), channel()} | {ok, list(emqx_types:packet()), channel()} + | {close, channel()} + | {close, emqx_types:packet(), channel()} | {stop, Error :: term(), channel()} | {stop, Error :: term(), emqx_types:packet(), channel()}). -handle_in(?CONNECT_PACKET(_), Channel = #channel{connected = true}) -> +handle_in(?CONNECT_PACKET(_), Channel = #channel{fsm_state = #{state_name := connected}}) -> handle_out({disconnect, ?RC_PROTOCOL_ERROR}, Channel); handle_in(?CONNECT_PACKET(ConnPkt), Channel) -> @@ -244,73 +245,77 @@ handle_in(?CONNECT_PACKET(ConnPkt), Channel) -> end; handle_in(Packet = ?PUBLISH_PACKET(_QoS), Channel) -> + Channel1 = inc_pub_stats(publish_in, Channel), case emqx_packet:check(Packet) of - ok -> - handle_publish(Packet, Channel); + ok -> handle_publish(Packet, Channel1); {error, ReasonCode} -> - handle_out({disconnect, ReasonCode}, Channel) + handle_out({disconnect, ReasonCode}, Channel1) end; handle_in(?PUBACK_PACKET(PacketId, _ReasonCode), - Channel = #channel{client_info = ClientInfo, session = Session}) -> + Channel = #channel{clientinfo = ClientInfo, session = Session}) -> + Channel1 = inc_pub_stats(puback_in, Channel), case emqx_session:puback(PacketId, Session) of {ok, Msg, Publishes, NSession} -> 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 = emqx_hooks:run('message.acked', [ClientInfo, Msg]), - {ok, Channel#channel{session = NSession}}; + {ok, Channel1#channel{session = NSession}}; {error, ?RC_PACKET_IDENTIFIER_IN_USE} -> ?LOG(warning, "The PUBACK PacketId ~w is inuse.", [PacketId]), ok = emqx_metrics:inc('packets.puback.inuse'), - {ok, Channel}; + {ok, Channel1}; {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} -> ?LOG(warning, "The PUBACK PacketId ~w is not found", [PacketId]), ok = emqx_metrics:inc('packets.puback.missed'), - {ok, Channel} + {ok, Channel1} end; handle_in(?PUBREC_PACKET(PacketId, _ReasonCode), - Channel = #channel{client_info = ClientInfo, session = Session}) -> + Channel = #channel{clientinfo = ClientInfo, session = Session}) -> + Channel1 = inc_pub_stats(pubrec_in, Channel), case emqx_session:pubrec(PacketId, Session) of {ok, Msg, NSession} -> 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); {error, RC = ?RC_PACKET_IDENTIFIER_IN_USE} -> ?LOG(warning, "The PUBREC PacketId ~w is inuse.", [PacketId]), 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} -> ?LOG(warning, "The PUBREC ~w is not found.", [PacketId]), ok = emqx_metrics:inc('packets.pubrec.missed'), - handle_out({pubrel, PacketId, RC}, Channel) + handle_out({pubrel, PacketId, RC}, Channel1) end; handle_in(?PUBREL_PACKET(PacketId, _ReasonCode), Channel = #channel{session = Session}) -> + Channel1 = inc_pub_stats(pubrel_in, Channel), case emqx_session:pubrel(PacketId, Session) of {ok, NSession} -> - handle_out({pubcomp, PacketId, ?RC_SUCCESS}, Channel#channel{session = NSession}); + handle_out({pubcomp, PacketId, ?RC_SUCCESS}, Channel1#channel{session = NSession}); {error, NotFound} -> - ?LOG(warning, "The PUBREL PacketId ~w is not found", [PacketId]), 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; handle_in(?PUBCOMP_PACKET(PacketId, _ReasonCode), Channel = #channel{session = Session}) -> + Channel1 = inc_pub_stats(pubcomp_in, Channel), case emqx_session:pubcomp(PacketId, Session) of {ok, Publishes, NSession} -> - handle_out({publish, Publishes}, Channel#channel{session = NSession}); + handle_out({publish, Publishes}, Channel1#channel{session = NSession}); {ok, NSession} -> - {ok, Channel#channel{session = NSession}}; + {ok, Channel1#channel{session = NSession}}; {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} -> ?LOG(warning, "The PUBCOMP PacketId ~w is not found", [PacketId]), ok = emqx_metrics:inc('packets.pubcomp.missed'), - {ok, Channel} + {ok, Channel1} end; handle_in(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters), - Channel = #channel{client_info = ClientInfo}) -> + Channel = #channel{clientinfo = ClientInfo}) -> case emqx_packet:check(Packet) of ok -> TopicFilters1 = emqx_hooks:run_fold('client.subscribe', [ClientInfo, Properties], @@ -323,7 +328,7 @@ handle_in(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters), end; handle_in(Packet = ?UNSUBSCRIBE_PACKET(PacketId, Properties, TopicFilters), - Channel = #channel{client_info = ClientInfo}) -> + Channel = #channel{clientinfo = ClientInfo}) -> case emqx_packet:check(Packet) of ok -> TopicFilters1 = emqx_hooks:run_fold('client.unsubscribe', [ClientInfo, Properties], @@ -339,37 +344,49 @@ handle_in(?PACKET(?PINGREQ), Channel) -> handle_in(?DISCONNECT_PACKET(ReasonCode, Properties), Channel = #channel{conninfo = 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), - 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 -> - handle_out({disconnect, ?RC_PROTOCOL_ERROR}, Channel); - false -> - 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} + Channel2 = Channel1#channel{conninfo = ConnInfo#{expiry_interval => Interval}}, + {close, ReasonName, Channel2} end; handle_in(?AUTH_PACKET(), Channel) -> - %%TODO: implement later. - {ok, Channel}; + handle_out({disconnect, ?RC_IMPLEMENTATION_SPECIFIC_ERROR}, Channel); + +handle_in({frame_error, Reason}, Channel = #channel{fsm_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) -> ?LOG(error, "Unexpected incoming: ~p", [Packet]), - handle_out({disconnect, ?RC_MALFORMED_PACKET}, Channel). + handle_out({disconnect, ?RC_PROTOCOL_ERROR}, Channel). %%-------------------------------------------------------------------- %% Process Connect %%-------------------------------------------------------------------- process_connect(ConnPkt = #mqtt_packet_connect{clean_start = CleanStart}, - Channel = #channel{conninfo = ConnInfo, client_info = ClientInfo}) -> + Channel = #channel{conninfo = ConnInfo, clientinfo = ClientInfo}) -> case emqx_cm:open_session(CleanStart, ClientInfo, ConnInfo) of {ok, #{session := Session, present := false}} -> NChannel = Channel#channel{session = Session}, @@ -391,6 +408,11 @@ process_connect(ConnPkt = #mqtt_packet_connect{clean_start = CleanStart}, %% 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), Channel = #channel{conninfo = #{proto_ver := ProtoVer}}) -> case pipeline([fun process_alias/2, @@ -440,7 +462,7 @@ process_publish(PacketId, Msg = #message{qos = ?QOS_2}, end. publish_to_msg(Packet, #channel{conninfo = #{proto_ver := ProtoVer}, - client_info = ClientInfo = #{mountpoint := MountPoint}}) -> + clientinfo = ClientInfo = #{mountpoint := MountPoint}}) -> Msg = emqx_packet:to_message(ClientInfo, Packet), Msg1 = emqx_message:set_flag(dup, false, Msg), Msg2 = emqx_message:set_header(proto_ver, ProtoVer, Msg1), @@ -461,7 +483,7 @@ process_subscribe([{TopicFilter, SubOpts}|More], Acc, Channel) -> process_subscribe(More, [RC|Acc], NChannel). do_subscribe(TopicFilter, SubOpts = #{qos := QoS}, Channel = - #channel{client_info = ClientInfo = #{mountpoint := MountPoint}, + #channel{clientinfo = ClientInfo = #{mountpoint := MountPoint}, session = Session}) -> case check_subscribe(TopicFilter, SubOpts, Channel) of ok -> @@ -491,7 +513,7 @@ process_unsubscribe([{TopicFilter, SubOpts}|More], Acc, Channel) -> process_unsubscribe(More, [RC|Acc], NChannel). do_unsubscribe(TopicFilter, _SubOpts, Channel = - #channel{client_info = ClientInfo = #{mountpoint := MountPoint}, + #channel{clientinfo = ClientInfo = #{mountpoint := MountPoint}, session = Session}) -> TopicFilter1 = emqx_mountpoint:mount(MountPoint, TopicFilter), case emqx_session:unsubscribe(ClientInfo, TopicFilter1, Session) of @@ -506,22 +528,24 @@ do_unsubscribe(TopicFilter, _SubOpts, Channel = %%TODO: RunFold or Pipeline handle_out({connack, ?RC_SUCCESS, SP, ConnPkt}, - Channel = #channel{conninfo = ConnInfo, client_info = ClientInfo}) -> + Channel = #channel{conninfo = ConnInfo, + clientinfo = ClientInfo, + fsm_state = FsmState}) -> AckProps = run_fold([fun enrich_caps/2, fun enrich_server_keepalive/2, - fun enrich_assigned_clientid/2 - ], #{}, Channel), - Channel1 = Channel#channel{will_msg = emqx_packet:will_msg(ConnPkt), - alias_maximum = init_alias_maximum(ConnPkt, ClientInfo), - connected = true, - connected_at = os:timestamp() + fun enrich_assigned_clientid/2], #{}, Channel), + FsmState1 = FsmState#{state_name => connected, + connected_at => erlang:system_time(second) + }, + Channel1 = Channel#channel{fsm_state = FsmState1, + will_msg = emqx_packet:will_msg(ConnPkt), + alias_maximum = init_alias_maximum(ConnPkt, ClientInfo) }, Channel2 = ensure_keepalive(AckProps, Channel1), ok = emqx_hooks:run('client.connected', [ClientInfo, ?RC_SUCCESS, ConnInfo]), AckPacket = ?CONNACK_PACKET(?RC_SUCCESS, SP, AckProps), case maybe_resume_session(Channel2) of - ignore -> - {ok, AckPacket, Channel2}; + ignore -> {ok, AckPacket, Channel2}; {ok, Publishes, NSession} -> Channel3 = Channel2#channel{session = NSession, resuming = false, @@ -531,7 +555,7 @@ handle_out({connack, ?RC_SUCCESS, SP, ConnPkt}, end; handle_out({connack, ReasonCode, _ConnPkt}, Channel = #channel{conninfo = ConnInfo, - client_info = ClientInfo}) -> + clientinfo = ClientInfo}) -> ok = emqx_hooks:run('client.connected', [ClientInfo, ReasonCode, ConnInfo]), ReasonCode1 = case ProtoVer = maps:get(proto_ver, ConnInfo) of ?MQTT_PROTO_V5 -> ReasonCode; @@ -540,8 +564,8 @@ handle_out({connack, ReasonCode, _ConnPkt}, Channel = #channel{conninfo = ConnIn Reason = emqx_reason_codes:name(ReasonCode1, ProtoVer), {stop, {shutdown, Reason}, ?CONNACK_PACKET(ReasonCode1), Channel}; -handle_out({deliver, Delivers}, Channel = #channel{session = Session, - connected = false}) -> +handle_out({deliver, Delivers}, Channel = #channel{fsm_state = #{state_name := disconnected}, + session = Session}) -> NSession = emqx_session:enqueue(Delivers, Session), {ok, Channel#channel{session = NSession}}; @@ -567,32 +591,33 @@ handle_out({publish, Publishes}, Channel) when is_list(Publishes) -> {ok, _Ch} -> Acc end end, [], Publishes), - {ok, lists:reverse(Packets), Channel}; + NChannel = inc_pub_stats(publish_out, length(Packets), Channel), + {ok, lists:reverse(Packets), NChannel}; %% Ignore loop deliver handle_out({publish, _PacketId, #message{from = ClientId, flags = #{nl := true}}}, - Channel = #channel{client_info = #{client_id := ClientId}}) -> + Channel = #channel{clientinfo = #{clientid := ClientId}}) -> {ok, Channel}; handle_out({publish, PacketId, Msg}, Channel = - #channel{client_info = ClientInfo = #{mountpoint := MountPoint}}) -> + #channel{clientinfo = ClientInfo = #{mountpoint := MountPoint}}) -> Msg1 = emqx_message:update_expiry(Msg), Msg2 = emqx_hooks:run_fold('message.delivered', [ClientInfo], Msg1), Msg3 = emqx_mountpoint:unmount(MountPoint, Msg2), {ok, emqx_message:to_packet(PacketId, Msg3), 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) -> - {ok, ?PUBREL_PACKET(PacketId, ReasonCode), Channel}; + {ok, ?PUBREL_PACKET(PacketId, ReasonCode), inc_pub_stats(pubrel_out, 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) -> - {ok, ?PUBCOMP_PACKET(PacketId, ReasonCode), Channel}; + {ok, ?PUBCOMP_PACKET(PacketId, ReasonCode), inc_pub_stats(pubcomp_out, Channel)}; handle_out({suback, PacketId, ReasonCodes}, Channel = #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}}) -> @@ -609,15 +634,28 @@ handle_out({unsuback, PacketId, ReasonCodes}, handle_out({unsuback, PacketId, _ReasonCodes}, Channel) -> {ok, ?UNSUBACK_PACKET(PacketId), Channel}; -handle_out({disconnect, ReasonCode}, Channel = #channel{conninfo = ConnInfo}) -> - case maps:get(proto_ver, ConnInfo) of - ?MQTT_PROTO_V5 -> - Reason = emqx_reason_codes:name(ReasonCode), - Packet = ?DISCONNECT_PACKET(ReasonCode), - {wait_session_expire, {shutdown, Reason}, Packet, Channel}; - ProtoVer -> - Reason = emqx_reason_codes:name(ReasonCode, ProtoVer), - {wait_session_expire, {shutdown, Reason}, Channel} +handle_out({disconnect, ReasonCode}, Channel = #channel{conninfo = #{proto_ver := ProtoVer}}) -> + ReasonName = emqx_reason_codes:name(ReasonCode, ProtoVer), + handle_out({disconnect, ReasonCode, ReasonName}, Channel); + +%%TODO: Improve later... +handle_out({disconnect, ReasonCode, ReasonName}, + Channel = #channel{conninfo = #{proto_ver := ProtoVer, + expiry_interval := ExpiryInterval}}) -> + 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; handle_out({Type, Data}, Channel) -> @@ -631,10 +669,10 @@ handle_out({Type, Data}, Channel) -> handle_call(kick, Channel) -> {stop, {shutdown, kicked}, ok, Channel}; -handle_call(discard, Channel = #channel{connected = true}) -> +handle_call(discard, Channel = #channel{fsm_state = #{state_name := connected}}) -> Packet = ?DISCONNECT_PACKET(?RC_SESSION_TAKEN_OVER), {stop, {shutdown, discarded}, Packet, ok, Channel}; -handle_call(discard, Channel = #channel{connected = false}) -> +handle_call(discard, Channel = #channel{fsm_state = #{state_name := disconnected}}) -> {stop, {shutdown, discarded}, ok, Channel}; %% Session Takeover @@ -651,49 +689,40 @@ handle_call(Req, Channel) -> ?LOG(error, "Unexpected call: ~p", [Req]), {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_info = #{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 %%-------------------------------------------------------------------- -spec(handle_info(Info :: term(), channel()) - -> {ok, channel()} | {stop, Reason :: term(), channel()}). -handle_info({subscribe, TopicFilters}, Channel = #channel{client_info = ClientInfo}) -> + -> ok | {ok, channel()} | {stop, Reason :: term(), channel()}). +handle_info({subscribe, TopicFilters}, Channel = #channel{clientinfo = ClientInfo}) -> TopicFilters1 = emqx_hooks:run_fold('client.subscribe', [ClientInfo, #{'Internal' => true}], parse_topic_filters(TopicFilters)), {_ReasonCodes, NChannel} = process_subscribe(TopicFilters1, Channel), {ok, NChannel}; -handle_info({unsubscribe, TopicFilters}, Channel = #channel{client_info = ClientInfo}) -> +handle_info({unsubscribe, TopicFilters}, Channel = #channel{clientinfo = ClientInfo}) -> TopicFilters1 = emqx_hooks:run_fold('client.unsubscribe', [ClientInfo, #{'Internal' => true}], parse_topic_filters(TopicFilters)), {_ReasonCodes, NChannel} = process_unsubscribe(TopicFilters1, Channel), {ok, NChannel}; -handle_info(disconnected, Channel = #channel{connected = undefined}) -> - shutdown(closed, Channel); +handle_info({register, Attrs, Stats}, #channel{clientinfo = #{clientid := ClientId}}) -> + 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{fsm_state = #{state_name := disconnected}}) -> {ok, Channel}; handle_info(disconnected, Channel = #channel{conninfo = #{expiry_interval := ExpiryInterval}, - client_info = ClientInfo = #{zone := Zone}, + clientinfo = ClientInfo = #{zone := Zone}, will_msg = WillMsg}) -> emqx_zone:enable_flapping_detect(Zone) andalso emqx_flapping:detect(ClientInfo), Channel1 = ensure_disconnected(Channel), @@ -726,7 +755,7 @@ handle_info(Info, Channel) -> | {ok, Result :: term(), channel()} | {stop, Reason :: term(), channel()}). handle_timeout(TRef, {emit_stats, Stats}, - Channel = #channel{client_info = #{client_id := ClientId}, + Channel = #channel{clientinfo = #{clientid := ClientId}, timers = #{stats_timer := TRef}}) -> ok = emqx_cm:set_chan_stats(ClientId, Stats), {ok, clean_timer(stats_timer, Channel)}; @@ -739,7 +768,7 @@ handle_timeout(TRef, {keepalive, StatVal}, NChannel = Channel#channel{keepalive = NKeepalive}, {ok, reset_timer(alive_timer, NChannel)}; {error, timeout} -> - {wait_session_expire, {shutdown, keepalive_timeout}, Channel} + handle_out({disconnect, ?RC_KEEP_ALIVE_TIMEOUT}, Channel) end; handle_timeout(TRef, retry_delivery, @@ -810,7 +839,7 @@ reset_timer(Name, Time, Channel) -> clean_timer(Name, Channel = #channel{timers = Timers}) -> Channel#channel{timers = maps:remove(Name, Timers)}. -interval(stats_timer, #channel{client_info = #{zone := Zone}}) -> +interval(stats_timer, #channel{clientinfo = #{zone := Zone}}) -> emqx_zone:get_env(Zone, idle_timeout, 30000); interval(alive_timer, #channel{keepalive = KeepAlive}) -> emqx_keepalive:info(interval, KeepAlive); @@ -828,18 +857,21 @@ will_delay_interval(undefined) -> 0; will_delay_interval(WillMsg) -> emqx_message:get_header('Will-Delay-Interval', WillMsg, 0). +%% TODO: Implement later. +disconnect(_Reason, Channel) -> {ok, Channel}. + %%-------------------------------------------------------------------- %% Terminate %%-------------------------------------------------------------------- -terminate(_, #channel{connected = undefined}) -> +terminate(_, #channel{fsm_state = #{state_name := initialized}}) -> ok; -terminate(normal, #channel{conninfo = ConnInfo, client_info = ClientInfo}) -> +terminate(normal, #channel{conninfo = ConnInfo, clientinfo = ClientInfo}) -> ok = emqx_hooks:run('client.disconnected', [ClientInfo, normal, ConnInfo]); -terminate({shutdown, Reason}, #channel{conninfo = ConnInfo, client_info = ClientInfo}) +terminate({shutdown, Reason}, #channel{conninfo = ConnInfo, clientinfo = ClientInfo}) when Reason =:= kicked orelse Reason =:= discarded orelse Reason =:= takeovered -> ok = emqx_hooks:run('client.disconnected', [ClientInfo, Reason, ConnInfo]); -terminate(Reason, #channel{conninfo = ConnInfo, client_info = ClientInfo, will_msg = WillMsg}) -> +terminate(Reason, #channel{conninfo = ConnInfo, clientinfo = ClientInfo, will_msg = WillMsg}) -> publish_will_msg(WillMsg), ok = emqx_hooks:run('client.disconnected', [ClientInfo, Reason, ConnInfo]). @@ -864,9 +896,9 @@ enrich_conninfo(#mqtt_packet_connect{ clean_start = CleanStart, keepalive = Keepalive, properties = ConnProps, - client_id = ClientId, + clientid = ClientId, username = Username}, Channel) -> - #channel{conninfo = ConnInfo, client_info = #{zone := Zone}} = Channel, + #channel{conninfo = ConnInfo, clientinfo = #{zone := Zone}} = Channel, MaxInflight = emqx_mqtt_props:get('Receive-Maximum', ConnProps, emqx_zone:max_inflight(Zone)), Interval = if ProtoVer == ?MQTT_PROTO_V5 -> @@ -880,7 +912,7 @@ enrich_conninfo(#mqtt_packet_connect{ proto_ver => ProtoVer, clean_start => CleanStart, keepalive => Keepalive, - client_id => ClientId, + clientid => ClientId, username => Username, conn_props => ConnProps, receive_maximum => MaxInflight, @@ -889,18 +921,18 @@ enrich_conninfo(#mqtt_packet_connect{ {ok, Channel#channel{conninfo = NConnInfo}}. %% @doc Check connect packet. -check_connect(ConnPkt, #channel{client_info = #{zone := Zone}}) -> +check_connect(ConnPkt, #channel{clientinfo = #{zone := Zone}}) -> emqx_packet:check(ConnPkt, emqx_mqtt_caps:get_caps(Zone)). %% @doc Enrich client -enrich_client(ConnPkt, Channel = #channel{client_info = ClientInfo}) -> +enrich_client(ConnPkt, Channel = #channel{clientinfo = ClientInfo}) -> {ok, NConnPkt, NClientInfo} = pipeline([fun set_username/2, fun set_bridge_mode/2, fun maybe_username_as_clientid/2, fun maybe_assign_clientid/2, fun fix_mountpoint/2], ConnPkt, ClientInfo), - {ok, NConnPkt, Channel#channel{client_info = NClientInfo}}. + {ok, NConnPkt, Channel#channel{clientinfo = NClientInfo}}. set_username(#mqtt_packet_connect{username = Username}, ClientInfo = #{username := undefined}) -> @@ -916,35 +948,35 @@ maybe_username_as_clientid(_ConnPkt, ClientInfo = #{username := undefined}) -> {ok, ClientInfo}; maybe_username_as_clientid(_ConnPkt, ClientInfo = #{zone := Zone, username := Username}) -> case emqx_zone:use_username_as_clientid(Zone) of - true -> {ok, ClientInfo#{client_id => Username}}; + true -> {ok, ClientInfo#{clientid => Username}}; false -> ok end. -maybe_assign_clientid(#mqtt_packet_connect{client_id = <<>>}, ClientInfo) -> +maybe_assign_clientid(#mqtt_packet_connect{clientid = <<>>}, ClientInfo) -> %% Generate a rand clientId - {ok, ClientInfo#{client_id => emqx_guid:to_base62(emqx_guid:gen())}}; -maybe_assign_clientid(#mqtt_packet_connect{client_id = ClientId}, ClientInfo) -> - {ok, ClientInfo#{client_id => ClientId}}. + {ok, ClientInfo#{clientid => emqx_guid:to_base62(emqx_guid:gen())}}; +maybe_assign_clientid(#mqtt_packet_connect{clientid = ClientId}, ClientInfo) -> + {ok, ClientInfo#{clientid => ClientId}}. fix_mountpoint(_ConnPkt, #{mountpoint := undefined}) -> ok; fix_mountpoint(_ConnPkt, ClientInfo = #{mountpoint := Mountpoint}) -> {ok, ClientInfo#{mountpoint := emqx_mountpoint:replvar(Mountpoint, ClientInfo)}}. %% @doc Set logger metadata. -set_logger_meta(_ConnPkt, #channel{client_info = #{client_id := ClientId}}) -> - emqx_logger:set_metadata_client_id(ClientId). +set_logger_meta(_ConnPkt, #channel{clientinfo = #{clientid := ClientId}}) -> + emqx_logger:set_metadata_clientid(ClientId). %%-------------------------------------------------------------------- %% Check banned/flapping %%-------------------------------------------------------------------- -check_banned(_ConnPkt, #channel{client_info = ClientInfo = #{zone := Zone}}) -> +check_banned(_ConnPkt, #channel{clientinfo = ClientInfo = #{zone := Zone}}) -> case emqx_zone:enable_ban(Zone) andalso emqx_banned:check(ClientInfo) of true -> {error, ?RC_BANNED}; false -> ok end. -check_flapping(_ConnPkt, #channel{client_info = ClientInfo = #{zone := Zone}}) -> +check_flapping(_ConnPkt, #channel{clientinfo = ClientInfo = #{zone := Zone}}) -> case emqx_zone:enable_flapping_detect(Zone) andalso emqx_flapping:check(ClientInfo) of true -> {error, ?RC_CONNECTION_RATE_EXCEEDED}; @@ -955,13 +987,13 @@ check_flapping(_ConnPkt, #channel{client_info = ClientInfo = #{zone := Zone}}) - %% Auth Connect %%-------------------------------------------------------------------- -auth_connect(#mqtt_packet_connect{client_id = ClientId, +auth_connect(#mqtt_packet_connect{clientid = ClientId, username = Username, password = Password}, - Channel = #channel{client_info = ClientInfo}) -> + Channel = #channel{clientinfo = ClientInfo}) -> case emqx_access_control:authenticate(ClientInfo#{password => Password}) of {ok, AuthResult} -> - {ok, Channel#channel{client_info = maps:merge(ClientInfo, AuthResult)}}; + {ok, Channel#channel{clientinfo = maps:merge(ClientInfo, AuthResult)}}; {error, Reason} -> ?LOG(warning, "Client ~s (Username: '~s') login failed for ~0p", [ClientId, Username, Reason]), @@ -1004,7 +1036,7 @@ save_alias(AliasId, Topic, Aliases) -> maps:put(AliasId, Topic, Aliases). %% Check Pub ACL check_pub_acl(#mqtt_packet{variable = #mqtt_packet_publish{topic_name = Topic}}, - #channel{client_info = ClientInfo}) -> + #channel{clientinfo = ClientInfo}) -> case is_acl_enabled(ClientInfo) andalso emqx_access_control:check_acl(ClientInfo, publish, Topic) of false -> ok; @@ -1033,7 +1065,7 @@ check_pub_caps(#mqtt_packet{header = #mqtt_packet_header{qos = QoS, retain = Retain } }, - #channel{client_info = #{zone := Zone}}) -> + #channel{clientinfo = #{zone := Zone}}) -> emqx_mqtt_caps:check_pub(Zone, #{qos => QoS, retain => Retain}). %% Check Sub @@ -1044,7 +1076,7 @@ check_subscribe(TopicFilter, SubOpts, Channel) -> end. %% Check Sub ACL -check_sub_acl(TopicFilter, #channel{client_info = ClientInfo}) -> +check_sub_acl(TopicFilter, #channel{clientinfo = ClientInfo}) -> case is_acl_enabled(ClientInfo) andalso emqx_access_control:check_acl(ClientInfo, subscribe, TopicFilter) of false -> allow; @@ -1052,7 +1084,7 @@ check_sub_acl(TopicFilter, #channel{client_info = ClientInfo}) -> end. %% Check Sub Caps -check_sub_caps(TopicFilter, SubOpts, #channel{client_info = #{zone := Zone}}) -> +check_sub_caps(TopicFilter, SubOpts, #channel{clientinfo = #{zone := Zone}}) -> emqx_mqtt_caps:check_sub(Zone, TopicFilter, SubOpts). enrich_subid(#{'Subscription-Identifier' := SubId}, TopicFilters) -> @@ -1063,12 +1095,12 @@ enrich_subid(_Properties, TopicFilters) -> enrich_subopts(SubOpts, #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}}) -> SubOpts; -enrich_subopts(SubOpts, #channel{client_info = #{zone := Zone, is_bridge := IsBridge}}) -> +enrich_subopts(SubOpts, #channel{clientinfo = #{zone := Zone, is_bridge := IsBridge}}) -> NL = flag(emqx_zone:ignore_loop_deliver(Zone)), SubOpts#{rap => flag(IsBridge), nl => NL}. enrich_caps(AckProps, #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}, - client_info = #{zone := Zone}}) -> + clientinfo = #{zone := Zone}}) -> #{max_packet_size := MaxPktSize, max_qos_allowed := MaxQoS, retain_available := Retain, @@ -1087,16 +1119,16 @@ enrich_caps(AckProps, #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}, enrich_caps(AckProps, _Channel) -> AckProps. -enrich_server_keepalive(AckProps, #channel{client_info = #{zone := Zone}}) -> +enrich_server_keepalive(AckProps, #channel{clientinfo = #{zone := Zone}}) -> case emqx_zone:server_keepalive(Zone) of undefined -> AckProps; Keepalive -> AckProps#{'Server-Keep-Alive' => Keepalive} end. enrich_assigned_clientid(AckProps, #channel{conninfo = ConnInfo, - client_info = #{client_id := ClientId} + clientinfo = #{clientid := ClientId} }) -> - case maps:get(client_id, ConnInfo) of + case maps:get(clientid, ConnInfo) of <<>> -> %% Original ClientId is null. AckProps#{'Assigned-Client-Identifier' => ClientId}; _Origin -> AckProps @@ -1108,8 +1140,10 @@ init_alias_maximum(#mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V5, inbound => emqx_mqtt_caps:get_caps(Zone, max_topic_alias, 0)}; init_alias_maximum(_ConnPkt, _ClientInfo) -> undefined. -ensure_disconnected(Channel) -> - Channel#channel{connected = false, disconnected_at = os:timestamp()}. +ensure_disconnected(Channel = #channel{fsm_state = FsmState}) -> + Channel#channel{fsm_state = FsmState#{state_name := disconnected, + disconnected_at => erlang:system_time(second) + }}. ensure_keepalive(#{'Server-Keep-Alive' := Interval}, Channel) -> ensure_keepalive_timer(Interval, Channel); @@ -1117,7 +1151,7 @@ ensure_keepalive(_AckProps, Channel = #channel{conninfo = ConnInfo}) -> ensure_keepalive_timer(maps:get(keepalive, ConnInfo), Channel). ensure_keepalive_timer(0, Channel) -> Channel; -ensure_keepalive_timer(Interval, Channel = #channel{client_info = #{zone := Zone}}) -> +ensure_keepalive_timer(Interval, Channel = #channel{clientinfo = #{zone := Zone}}) -> Backoff = emqx_zone:get_env(Zone, keepalive_backoff, 0.75), Keepalive = emqx_keepalive:init(round(timer:seconds(Interval) * Backoff)), ensure_timer(alive_timer, Channel#channel{keepalive = Keepalive}). @@ -1150,19 +1184,13 @@ parse_topic_filters(TopicFilters) -> maybe_gc_and_check_oom(_Oct, Channel = #channel{gc_state = undefined}) -> Channel; -maybe_gc_and_check_oom(Oct, Channel = #channel{gc_state = GCSt, - oom_policy = OomPolicy}) -> +maybe_gc_and_check_oom(Oct, Channel = #channel{clientinfo = #{zone := Zone}, + gc_state = GCSt}) -> {IsGC, GCSt1} = emqx_gc:run(1, Oct, GCSt), 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}. -check_oom(OomPolicy) -> - case emqx_oom:check(OomPolicy) of - ok -> ok; - Shutdown -> self() ! Shutdown - end. - %%-------------------------------------------------------------------- %% Helper functions %%-------------------------------------------------------------------- diff --git a/src/emqx_connection.erl b/src/emqx_connection.erl index 3adee23f5..a0408dea6 100644 --- a/src/emqx_connection.erl +++ b/src/emqx_connection.erl @@ -30,9 +30,7 @@ %% APIs -export([ info/1 - , attrs/1 , stats/1 - , state/1 ]). -export([call/2]). @@ -50,7 +48,7 @@ , terminate/3 ]). --record(connection, { +-record(state, { %% TCP/TLS Transport transport :: esockd:transport(), %% TCP/TLS Socket @@ -63,27 +61,26 @@ active_n :: pos_integer(), %% The active state active_state :: running | blocked, - %% Rate Limit - rate_limit :: maybe(esockd_rate_limit:bucket()), %% Publish Limit pub_limit :: maybe(esockd_rate_limit:bucket()), + %% Rate Limit + rate_limit :: maybe(esockd_rate_limit:bucket()), %% Limit Timer limit_timer :: maybe(reference()), %% Parser State parse_state :: emqx_frame:parse_state(), %% Serialize function - serialize :: fun((emqx_types:packet()) -> iodata()), + serialize :: emqx_frame:serialize_fun(), %% Channel State chan_state :: emqx_channel:channel() }). --type(connection() :: #connection{}). +-type(state() :: #state{}). -define(ACTIVE_N, 100). -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, - rate_limit, pub_limit]). + pub_limit, rate_limit]). -define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]). -define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt, send_pend]). @@ -98,64 +95,52 @@ start_link(Transport, Socket, Options) -> %%-------------------------------------------------------------------- %% @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) -> call(CPid, info); -info(Conn = #connection{chan_state = ChanState}) -> +info(Conn = #state{chan_state = ChanState}) -> ChanInfo = emqx_channel:info(ChanState), SockInfo = maps:from_list(info(?INFO_KEYS, Conn)), maps:merge(ChanInfo, #{sockinfo => SockInfo}). info(Keys, Conn) when is_list(Keys) -> [{Key, info(Key, Conn)} || Key <- Keys]; -info(socktype, #connection{transport = Transport, socket = Socket}) -> +info(socktype, #state{transport = Transport, socket = Socket}) -> Transport:type(Socket); -info(peername, #connection{peername = Peername}) -> +info(peername, #state{peername = Peername}) -> Peername; -info(sockname, #connection{sockname = Sockname}) -> +info(sockname, #state{sockname = Sockname}) -> Sockname; -info(active_n, #connection{active_n = ActiveN}) -> +info(active_n, #state{active_n = ActiveN}) -> ActiveN; -info(active_state, #connection{active_state = ActiveSt}) -> +info(active_state, #state{active_state = ActiveSt}) -> ActiveSt; -info(rate_limit, #connection{rate_limit = RateLimit}) -> - limit_info(RateLimit); -info(pub_limit, #connection{pub_limit = PubLimit}) -> +info(pub_limit, #state{pub_limit = 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). limit_info(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. --spec(stats(pid()|connection()) -> emqx_types:stats()). +-spec(stats(pid()|state()) -> emqx_types:stats()). stats(CPid) when is_pid(CPid) -> call(CPid, stats); -stats(#connection{transport = Transport, - socket = Socket, - chan_state = ChanState}) -> - ProcStats = emqx_misc:proc_stats(), +stats(#state{transport = Transport, + socket = Socket, + chan_state = ChanState}) -> SockStats = case Transport:getstat(Socket, ?SOCK_STATS) of {ok, Ss} -> Ss; {error, _} -> [] end, - ConnStats = [{Name, emqx_pd:get_counter(Name)} || Name <- ?CONN_STATS], + ConnStats = emqx_pd:get_counters(?CONN_STATS), ChanStats = emqx_channel:stats(ChanState), - lists:append([ProcStats, SockStats, ConnStats, ChanStats]). - -%% For debug --spec(state(pid()) -> connection()). -state(CPid) -> call(CPid, state). + ProcStats = emqx_misc:proc_stats(), + [{sock_stats, SockStats}, {conn_stats, ConnStats}, + {chan_stats, ChanStats}, {proc_stats, ProcStats}]. %% kick|discard|takeover -spec(call(pid(), Req :: term()) -> Reply :: term()). @@ -170,38 +155,43 @@ init({Transport, RawSocket, Options}) -> {ok, Peername} = Transport:ensure_ok_or_exit(peername, [Socket]), {ok, Sockname} = Transport:ensure_ok_or_exit(sockname, [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)), 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), - MaxSize = emqx_zone:get_env(Zone, max_packet_size, ?MAX_PACKET_SIZE), - ParseState = emqx_frame:initial_parse_state(#{max_size => MaxSize}), - ChanState = emqx_channel:init(#{peername => Peername, - sockname => Sockname, - peercert => Peercert, - protocol => mqtt, - conn_mod => ?MODULE}, Options), + PubLimit = init_limiter(emqx_zone:get_env(Zone, publish_limit)), + RateLimit = init_limiter(proplists:get_value(rate_limit, Options)), + FrameOpts = emqx_zone:frame_options(Zone), + ParseState = emqx_frame:initial_parse_state(FrameOpts), + Serialize = emqx_frame:serialize_fun(), + ChanState = emqx_channel:init(ConnInfo, 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), - 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}], idle, State, self(), [IdleTimout]). +-compile({inline, [init_limiter/1]}). init_limiter(undefined) -> undefined; init_limiter({Rate, Burst}) -> esockd_rate_limit:new(Rate, Burst). +-compile({inline, [callback_mode/0]}). callback_mode() -> [state_functions, state_enter]. @@ -219,18 +209,17 @@ idle(timeout, _Timeout, State) -> shutdown(idle_timeout, 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, + Serialize = emqx_frame:serialize_fun(ConnPkt), + NState = State#state{serialize = Serialize}, handle_incoming(Packet, SuccFun, NState); idle(cast, {incoming, Packet}, State) when is_record(Packet, mqtt_packet) -> - ?LOG(warning, "Unexpected incoming: ~p", [Packet]), - shutdown(unexpected_incoming_packet, State); + SuccFun = fun(NewSt) -> {next_state, connected, NewSt} end, + handle_incoming(Packet, SuccFun, State); -idle(cast, {incoming, {error, Reason}}, State) -> - shutdown(Reason, State); +idle(cast, {incoming, FrameError = {frame_error, _Reason}}, State) -> + handle_incoming(FrameError, State); idle(EventType, Content, State) -> ?HANDLE(EventType, Content, State). @@ -245,16 +234,8 @@ connected(enter, _PrevSt, State) -> connected(cast, {incoming, Packet}, State) when is_record(Packet, mqtt_packet) -> handle_incoming(Packet, fun keep_state/1, State); -connected(cast, {incoming, {error, Reason}}, State = #connection{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]), - {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(cast, {incoming, FrameError = {frame_error, _Reason}}, State) -> + handle_incoming(FrameError, State); connected(info, Deliver = {deliver, _Topic, _Msg}, State) -> handle_deliver(emqx_misc:drain_deliver([Deliver]), State); @@ -265,13 +246,13 @@ connected(EventType, Content, 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 {ok, NChanState} -> - ok = register_self(State#connection{chan_state = NChanState}), - keep_state(State#connection{chan_state = NChanState}); + ok = register_self(State#state{chan_state = NChanState}), + keep_state(State#state{chan_state = NChanState}); {stop, Reason, NChanState} -> - stop(Reason, State#connection{chan_state = NChanState}) + stop(Reason, State#state{chan_state = NChanState}) end; disconnected(info, Deliver = {deliver, _Topic, _Msg}, State) -> @@ -286,51 +267,49 @@ disconnected(EventType, Content, State) -> handle({call, From}, info, State) -> reply(From, info(State), State); -handle({call, From}, attrs, State) -> - reply(From, attrs(State), State); - handle({call, From}, stats, State) -> reply(From, stats(State), State); handle({call, 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 {ok, Reply, NChanState} -> - reply(From, Reply, State#connection{chan_state = NChanState}); + reply(From, Reply, State#state{chan_state = NChanState}); {stop, Reason, Reply, NChanState} -> 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} -> - 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), - stop(Reason, State#connection{chan_state = NChanState}) + stop(Reason, State#state{chan_state = NChanState}) end; %%-------------------------------------------------------------------- %% Handle cast -handle(cast, Msg, State = #connection{chan_state = ChanState}) -> - case emqx_channel:handle_cast(Msg, ChanState) of +handle(cast, Msg, State = #state{chan_state = ChanState}) -> + case emqx_channel:handle_info(Msg, ChanState) of + ok -> {ok, State}; {ok, NChanState} -> - keep_state(State#connection{chan_state = NChanState}); + keep_state(State#state{chan_state = NChanState}); {stop, Reason, NChanState} -> - stop(Reason, State#connection{chan_state = NChanState}) + stop(Reason, State#state{chan_state = NChanState}) end; %%-------------------------------------------------------------------- %% Handle info %% 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 -> ?LOG(debug, "RECV ~p", [Data]), Oct = iolist_size(Data), emqx_pd:update_counter(incoming_bytes, Oct), ok = emqx_metrics:inc('bytes.received', Oct), NChanState = emqx_channel:received(Oct, ChanState), - NState = State#connection{chan_state = NChanState}, + NState = State#state{chan_state = NChanState}, process_incoming(Data, NState); handle(info, {Error, _Sock, Reason}, State) @@ -353,9 +332,9 @@ handle(info, {Passive, _Sock}, State) handle(info, activate_socket, State) -> %% Rate limit timer expired. - NState = State#connection{active_state = running, - limit_timer = undefined - }, + NState = State#state{active_state = running, + limit_timer = undefined + }, case activate_socket(NState) of ok -> keep_state(NState); {error, Reason} -> @@ -370,7 +349,7 @@ handle(info, {inet_reply, _Sock, {error, Reason}}, State) -> shutdown(Reason, State); handle(info, {timeout, TRef, keepalive}, - State = #connection{transport = Transport, socket = Socket}) -> + State = #state{transport = Transport, socket = Socket}) -> case Transport:getstat(Socket, [recv_oct]) of {ok, [{recv_oct, RecvOct}]} -> handle_timeout(TRef, {keepalive, RecvOct}, State); @@ -387,21 +366,21 @@ handle(info, {timeout, TRef, Msg}, State) -> handle(info, {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 {ok, NChanState} -> - keep_state(State#connection{chan_state = NChanState}); + keep_state(State#state{chan_state = NChanState}); {stop, Reason, NChanState} -> - stop(Reason, State#connection{chan_state = NChanState}) + stop(Reason, State#state{chan_state = NChanState}) end. code_change(_Vsn, State, Data, _Extra) -> {ok, State, Data}. -terminate(Reason, _StateName, State) -> - #connection{transport = Transport, - socket = Socket, - chan_state = ChanState} = State, +terminate(Reason, _StateName, #state{transport = Transport, + socket = Socket, + chan_state = ChanState + }) -> ?LOG(debug, "Terminated for ~p", [Reason]), ok = Transport:fast_close(Socket), emqx_channel:terminate(Reason, ChanState). @@ -410,8 +389,16 @@ terminate(Reason, _StateName, State) -> %% Internal functions %%-------------------------------------------------------------------- -register_self(State = #connection{chan_state = ChanState}) -> - emqx_channel:handle_cast({register, attrs(State), stats(State)}, ChanState). +register_self(State = #state{active_n = ActiveN, + 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 @@ -421,107 +408,109 @@ process_incoming(Data, State) -> process_incoming(Data, [], 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 {more, NParseState} -> - NState = State#connection{parse_state = NParseState}, - {keep_state, NState, next_incoming_events(Packets)}; + NState = State#state{parse_state = NParseState}, + keep_state(NState, next_incoming_events(Packets)); {ok, Packet, Rest, NParseState} -> - NState = State#connection{parse_state = NParseState}, - process_incoming(Rest, [Packet|Packets], NState); - {error, Reason} -> - {keep_state, State, next_incoming_events({error, Reason})} + NState = State#state{parse_state = NParseState}, + process_incoming(Rest, [Packet|Packets], NState) catch error:Reason:Stk -> - ?LOG(error, "~nParse failed for ~p~nStacktrace: ~p~nError data:~p", [Reason, Stk, Data]), - {keep_state, State, next_incoming_events({error, Reason})} + ?LOG(error, "~nParse failed for ~p~nStacktrace: ~p~nFrame data:~p", + [Reason, Stk, Data]), + keep_state(State, next_incoming_events(Packets++[{frame_error, Reason}])) end. -compile({inline, [next_incoming_events/1]}). -next_incoming_events({error, Reason}) -> - [next_event(cast, {incoming, {error, Reason}})]; +next_incoming_events([]) -> []; next_incoming_events(Packets) -> [next_event(cast, {incoming, Packet}) || Packet <- Packets]. %%-------------------------------------------------------------------- %% Handle incoming packet -handle_incoming(Packet = ?PACKET(Type), SuccFun, - State = #connection{chan_state = ChanState}) -> +handle_incoming(Packet = ?PACKET(Type), SuccFun, State = #state{chan_state = ChanState}) -> _ = inc_incoming_stats(Type), - ok = emqx_metrics:inc_recv(Packet), + _ = emqx_metrics:inc_recv(Packet), ?LOG(debug, "RECV ~s", [emqx_packet:format(Packet)]), case emqx_channel:handle_in(Packet, ChanState) of {ok, NChanState} -> - SuccFun(State#connection{chan_state= NChanState}); + SuccFun(State#state{chan_state= NChanState}); {ok, OutPackets, NChanState} -> - handle_outgoing(OutPackets, SuccFun, State#connection{chan_state = NChanState}); - {wait_session_expire, Reason, 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, Reason, 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)}; + NState = State#state{chan_state = NChanState}, + handle_outgoing(OutPackets, SuccFun, NState); {stop, Reason, NChanState} -> - stop(Reason, State#connection{chan_state = NChanState}); + stop(Reason, State#state{chan_state = 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)) end. %%------------------------------------------------------------------- %% 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 {ok, NChanState} -> - keep_state(State#connection{chan_state = NChanState}); + keep_state(State#state{chan_state = 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. %%-------------------------------------------------------------------- %% Handle outgoing packets -handle_outgoing(Packets, SuccFun, State = #connection{serialize = Serialize}) - when is_list(Packets) -> - send(lists:map(Serialize, Packets), SuccFun, State); +handle_outgoing(Packet, State) -> + handle_outgoing(Packet, fun (_) -> ok end, State). -handle_outgoing(Packet, SuccFun, State = #connection{serialize = Serialize}) -> - send(Serialize(Packet), SuccFun, State). +handle_outgoing(Packets, SuccFun, State) when is_list(Packets) -> + send(lists:map(serialize_and_inc_stats_fun(State), Packets), SuccFun, State); -%%-------------------------------------------------------------------- -%% Serialize fun +handle_outgoing(Packet, SuccFun, State) -> + 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)) -> - IoData = emqx_frame:serialize(Packet, ProtoVer), - case Type =/= ?PUBLISH orelse MaxPacketSize =:= undefined orelse iolist_size(IoData) =< MaxPacketSize of - true -> - ?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]), - _ = inc_outgoing_stats(Type), - _ = emqx_metrics:inc_sent(Packet), - IoData; - false -> - ?LOG(warning, "DROP ~s due to oversize packet size", [emqx_packet:format(Packet)]), - <<"">> + case Serialize(Packet) of + <<>> -> ?LOG(warning, "~s is discarded due to the frame is too large!", + [emqx_packet:format(Packet)]), + <<>>; + Data -> _ = inc_outgoing_stats(Type), + _ = emqx_metrics:inc_sent(Packet), + ?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]), + Data end end. %%-------------------------------------------------------------------- %% Send data -send(IoData, SuccFun, State = #connection{transport = Transport, - socket = Socket, - chan_state = ChanState}) -> +send(IoData, SuccFun, State = #state{transport = Transport, + socket = Socket, + chan_state = ChanState}) -> Oct = iolist_size(IoData), ok = emqx_metrics:inc('bytes.sent', Oct), case Transport:async_send(Socket, IoData) of ok -> NChanState = emqx_channel:sent(Oct, ChanState), - SuccFun(State#connection{chan_state = NChanState}); + SuccFun(State#state{chan_state = NChanState}); {error, Reason} -> shutdown(Reason, State) end. @@ -529,17 +518,22 @@ send(IoData, SuccFun, State = #connection{transport = Transport, %%-------------------------------------------------------------------- %% 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 {ok, NChanState} -> - keep_state(State#connection{chan_state = NChanState}); + keep_state(State#state{chan_state = NChanState}); {ok, Packets, NChanState} -> - handle_outgoing(Packets, fun keep_state/1, State#connection{chan_state = NChanState}); - {wait_session_expire, Reason, NChanState} -> - ?LOG(debug, "Disconnect and wait for session to expire due to ~p", [Reason]), - {next_state, disconnected, State#connection{chan_state = NChanState}}; + handle_outgoing(Packets, fun keep_state/1, State#state{chan_state = NChanState}); + {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#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. %%-------------------------------------------------------------------- @@ -547,11 +541,11 @@ handle_timeout(TRef, Msg, State = #connection{chan_state = ChanState}) -> -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), Bytes = emqx_pd:reset_counter(incoming_bytes), - Limiters = [{Pl, #connection.pub_limit, Pubs} || ?ENABLED(Pl)] ++ - [{Rl, #connection.rate_limit, Bytes} || ?ENABLED(Rl)], + Limiters = [{Pl, #state.pub_limit, Pubs} || ?ENABLED(Pl)] ++ + [{Rl, #state.rate_limit, Bytes} || ?ENABLED(Rl)], ensure_rate_limit(Limiters, State). ensure_rate_limit([], State) -> @@ -563,30 +557,27 @@ ensure_rate_limit([{Rl, Pos, Cnt}|Limiters], State) -> {Pause, Rl1} -> ?LOG(debug, "Pause ~pms due to rate limit", [Pause]), TRef = erlang:send_after(Pause, self(), activate_socket), - NState = State#connection{active_state = blocked, - limit_timer = TRef}, + NState = State#state{active_state = blocked, + limit_timer = TRef + }, setelement(Pos, NState, Rl1) end. %%-------------------------------------------------------------------- %% Activate Socket -activate_socket(#connection{active_state = blocked}) -> - ok; -activate_socket(#connection{transport = Transport, - socket = Socket, - active_n = N}) -> +-compile({inline, [activate_socket/1]}). +activate_socket(#state{active_state = blocked}) -> ok; +activate_socket(#state{transport = Transport, + socket = Socket, + active_n = N}) -> Transport:setopts(Socket, [{active, N}]). %%-------------------------------------------------------------------- %% Inc incoming/outgoing stats --compile({inline, - [ inc_incoming_stats/1 - , inc_outgoing_stats/1 - ]}). - -inc_incoming_stats(Type) -> +-compile({inline, [inc_incoming_stats/1]}). +inc_incoming_stats(Type) when is_integer(Type) -> emqx_pd:update_counter(recv_pkt, 1), if Type == ?PUBLISH -> @@ -595,6 +586,7 @@ inc_incoming_stats(Type) -> true -> ok end. +-compile({inline, [inc_outgoing_stats/1]}). inc_outgoing_stats(Type) -> emqx_pd:update_counter(send_pkt, 1), (Type == ?PUBLISH) @@ -606,6 +598,7 @@ inc_outgoing_stats(Type) -> -compile({inline, [ reply/3 , keep_state/1 + , keep_state/2 , next_event/2 , shutdown/2 , stop/2 @@ -617,9 +610,17 @@ reply(From, Reply, State) -> keep_state(State) -> {keep_state, State}. +keep_state(State, Events) -> + {keep_state, State, Events}. + 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) -> stop({shutdown, Reason}, State). diff --git a/src/emqx_frame.erl b/src/emqx_frame.erl index e65f5d7c4..b402ad989 100644 --- a/src/emqx_frame.erl +++ b/src/emqx_frame.erl @@ -25,6 +25,8 @@ -export([ parse/1 , parse/2 + , serialize_fun/0 + , serialize_fun/1 , serialize/1 , serialize/2 ]). @@ -32,6 +34,7 @@ -export_type([ options/0 , parse_state/0 , parse_result/0 + , serialize_fun/0 ]). -type(options() :: #{strict_mode => boolean(), @@ -46,7 +49,9 @@ -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, #{strict_mode => false, @@ -84,12 +89,12 @@ parse(<<>>, {none, Options}) -> parse(<>, {none, Options = #{strict_mode := StrictMode}}) -> %% Validate header if strict mode. - StrictMode andalso validate_header(Type, Dup, QoS, Retain), Header = #mqtt_packet_header{type = Type, dup = bool(Dup), qos = QoS, retain = bool(Retain) }, + StrictMode andalso validate_header(Type, Dup, QoS, Retain), Header1 = case fixqos(Type, QoS) of QoS -> Header; 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}) when Length > MaxSize -> - error(mqtt_frame_too_large); + error(frame_too_large); parse_remaining_len(<<>>, Header, Multiplier, Length, Options) -> {more, fun(Bin) -> parse_remaining_len(Bin, Header, Multiplier, Length, Options) end}; %% Match DISCONNECT without payload @@ -124,7 +129,7 @@ parse_remaining_len(<<0:1, Len:7, Rest/binary>>, Header, Multiplier, Value, Options = #{max_size := MaxSize}) -> FrameLen = Value + Len * Multiplier, if - FrameLen > MaxSize -> error(mqtt_frame_too_large); + FrameLen > MaxSize -> error(frame_too_large); true -> parse_frame(Rest, Header, FrameLen, Options) end. @@ -148,6 +153,7 @@ parse_frame(Bin, Header, Length, Options) -> end} end. +-compile({inline, [packet/1, packet/2, packet/3]}). packet(Header) -> #mqtt_packet{header = Header}. packet(Header, Variable) -> @@ -180,7 +186,8 @@ parse_packet(#mqtt_packet_header{type = ?CONNECT}, FrameBin, _Options) -> will_retain = bool(WillRetain), keepalive = KeepAlive, properties = Properties, - clientid = ClientId}, + clientid = ClientId + }, {ConnPacket1, Rest5} = parse_will_message(ConnPacket, Rest4), {Username, Rest6} = parse_utf8_string(Rest5, bool(UsernameFlag)), {Passsword, <<>>} = parse_utf8_string(Rest6, bool(PasswordFlag)), @@ -191,10 +198,10 @@ parse_packet(#mqtt_packet_header{type = ?CONNACK}, {Properties, <<>>} = parse_properties(Rest, Ver), #mqtt_packet_connack{ack_flags = AckFlags, reason_code = ReasonCode, - properties = Properties}; + properties = Properties + }; -parse_packet(#mqtt_packet_header{type = ?PUBLISH, qos = QoS}, Bin, - #{version := Ver}) -> +parse_packet(#mqtt_packet_header{type = ?PUBLISH, qos = QoS}, Bin, #{version := Ver}) -> {TopicName, Rest} = parse_utf8_string(Bin), {PacketId, Rest1} = case QoS of ?QOS_0 -> {undefined, Rest}; @@ -202,14 +209,17 @@ parse_packet(#mqtt_packet_header{type = ?PUBLISH, qos = QoS}, Bin, end, (PacketId =/= undefined) andalso validate_packet_id(PacketId), {Properties, Payload} = parse_properties(Rest1, Ver), - {#mqtt_packet_publish{topic_name = TopicName, - packet_id = PacketId, - properties = Properties}, Payload}; + Publish = #mqtt_packet_publish{topic_name = TopicName, + packet_id = PacketId, + properties = Properties + }, + {Publish, Payload}; parse_packet(#mqtt_packet_header{type = PubAck}, <>, _Options) when ?PUBACK =< PubAck, PubAck =< ?PUBCOMP -> ok = validate_packet_id(PacketId), #mqtt_packet_puback{packet_id = PacketId, reason_code = 0}; + parse_packet(#mqtt_packet_header{type = PubAck}, <>, #{version := Ver = ?MQTT_PROTO_V5}) when ?PUBACK =< PubAck, PubAck =< ?PUBCOMP -> @@ -217,24 +227,29 @@ parse_packet(#mqtt_packet_header{type = PubAck}, <>} = parse_properties(Rest, Ver), #mqtt_packet_puback{packet_id = PacketId, reason_code = ReasonCode, - properties = Properties}; + properties = Properties + }; parse_packet(#mqtt_packet_header{type = ?SUBSCRIBE}, <>, #{version := Ver}) -> ok = validate_packet_id(PacketId), {Properties, Rest1} = parse_properties(Rest, Ver), TopicFilters = parse_topic_filters(subscribe, Rest1), + ok = validate_subqos([QoS || {_, #{qos := QoS}} <- TopicFilters]), #mqtt_packet_subscribe{packet_id = PacketId, properties = Properties, - topic_filters = TopicFilters}; + topic_filters = TopicFilters + }; parse_packet(#mqtt_packet_header{type = ?SUBACK}, <>, #{version := Ver}) -> ok = validate_packet_id(PacketId), {Properties, Rest1} = parse_properties(Rest, Ver), + ReasonCodes = parse_reason_codes(Rest1), #mqtt_packet_suback{packet_id = PacketId, properties = Properties, - reason_codes = parse_reason_codes(Rest1)}; + reason_codes = ReasonCodes + }; parse_packet(#mqtt_packet_header{type = ?UNSUBSCRIBE}, <>, #{version := Ver}) -> @@ -243,11 +258,13 @@ parse_packet(#mqtt_packet_header{type = ?UNSUBSCRIBE}, <>, _Options) -> ok = validate_packet_id(PacketId), #mqtt_packet_unsuback{packet_id = PacketId}; + parse_packet(#mqtt_packet_header{type = ?UNSUBACK}, <>, #{version := Ver}) -> ok = validate_packet_id(PacketId), @@ -255,13 +272,15 @@ parse_packet(#mqtt_packet_header{type = ?UNSUBACK}, <>, #{version := ?MQTT_PROTO_V5}) -> {Properties, <<>>} = parse_properties(Rest, ?MQTT_PROTO_V5), #mqtt_packet_disconnect{reason_code = ReasonCode, - properties = Properties}; + properties = Properties + }; parse_packet(#mqtt_packet_header{type = ?AUTH}, <>, #{version := ?MQTT_PROTO_V5}) -> @@ -275,16 +294,15 @@ parse_will_message(Packet = #mqtt_packet_connect{will_flag = true, {Payload, Rest2} = parse_binary_data(Rest1), {Packet#mqtt_packet_connect{will_props = Props, will_topic = Topic, - will_payload = Payload}, Rest2}; + will_payload = Payload + }, Rest2}; parse_will_message(Packet, Bin) -> {Packet, Bin}. +-compile({inline, [parse_packet_id/1]}). parse_packet_id(<>) -> {PacketId, Rest}. -validate_packet_id(0) -> error(bad_packet_id); -validate_packet_id(_) -> ok. - parse_properties(Bin, Ver) when Ver =/= ?MQTT_PROTO_V5 -> {undefined, Bin}; %% TODO: version mess? @@ -377,7 +395,7 @@ parse_variable_byte_integer(<<0:1, Len:7, Rest/binary>>, Multiplier, Value) -> {Value + Len * Multiplier, Rest}. 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}} || <> <= Bin]; parse_topic_filters(unsubscribe, Bin) -> @@ -405,9 +423,23 @@ parse_binary_data(<>) -> %% 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()). -serialize(Packet) -> - serialize(Packet, ?MQTT_PROTO_V4). +serialize(Packet) -> serialize(Packet, ?MQTT_PROTO_V4). -spec(serialize(emqx_types:packet(), emqx_types:version()) -> iodata()). serialize(#mqtt_packet{header = Header, @@ -418,10 +450,10 @@ serialize(#mqtt_packet{header = Header, serialize(#mqtt_packet_header{type = Type, dup = Dup, qos = QoS, - retain = Retain}, VariableBin, PayloadBin) + retain = Retain + }, VariableBin, PayloadBin) when ?CONNECT =< Type andalso Type =< ?AUTH -> Len = iolist_size(VariableBin) + iolist_size(PayloadBin), - (Len =< ?MAX_PACKET_SIZE) orelse error(mqtt_frame_too_large), [<>, serialize_remaining_len(Len), VariableBin, PayloadBin]. @@ -485,10 +517,11 @@ serialize_variable(#mqtt_packet_puback{packet_id = PacketId}, Ver) <>; serialize_variable(#mqtt_packet_puback{packet_id = PacketId, reason_code = ReasonCode, - properties = Properties}, - ?MQTT_PROTO_V5) -> + properties = Properties + }, + Ver = ?MQTT_PROTO_V5) -> [<>, ReasonCode, - serialize_properties(Properties, ?MQTT_PROTO_V5)]; + serialize_properties(Properties, Ver)]; serialize_variable(#mqtt_packet_subscribe{packet_id = PacketId, properties = Properties, @@ -616,8 +649,7 @@ serialize_property('Shared-Subscription-Available', Val) -> serialize_topic_filters(subscribe, TopicFilters, ?MQTT_PROTO_V5) -> << <<(serialize_utf8_string(Topic))/binary, ?RESERVED:2, Rh:2, (flag(Rap)):1,(flag(Nl)):1, QoS:2 >> - || {Topic, #{rh := Rh, rap := Rap, nl := Nl, qos := QoS}} - <- TopicFilters >>; + || {Topic, #{rh := Rh, rap := Rap, nl := Nl, qos := QoS}} <- TopicFilters >>; serialize_topic_filters(subscribe, TopicFilters, _Ver) -> << <<(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) -> <<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(?CONNECT, 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(_Type, _Dup, _QoS, _Rt) -> error(bad_frame_header). -validate_subqos(QoS) when ?QOS_0 =< QoS, QoS =< ?QOS_2 -> - QoS; -validate_subqos(_) -> error(bad_subqos). +validate_packet_id(0) -> error(bad_packet_id); +validate_packet_id(_) -> ok. + +validate_subqos([3|_]) -> error(bad_subqos); +validate_subqos([_|T]) -> validate_subqos(T); +validate_subqos([]) -> ok. bool(0) -> false; bool(1) -> true. @@ -695,3 +740,4 @@ fixqos(?PUBREL, 0) -> 1; fixqos(?SUBSCRIBE, 0) -> 1; fixqos(?UNSUBSCRIBE, 0) -> 1; fixqos(_Type, QoS) -> QoS. + diff --git a/src/emqx_session.erl b/src/emqx_session.erl index 7b3de773c..a945e23f7 100644 --- a/src/emqx_session.erl +++ b/src/emqx_session.erl @@ -117,7 +117,7 @@ %% Enqueue Count enqueue_cnt :: non_neg_integer(), %% Created at - created_at :: erlang:timestamp() + created_at :: pos_integer() }). -opaque(session() :: #session{}). @@ -125,22 +125,49 @@ -type(publish() :: {publish, emqx_types:packet_id(), emqx_types:message()}). -define(DEFAULT_BATCH_N, 1000). --define(ATTR_KEYS, [max_inflight, max_mqueue, retry_interval, - max_awaiting_rel, await_rel_timeout, created_at]). --define(INFO_KEYS, [subscriptions, max_subscriptions, upgrade_qos, inflight, - max_inflight, retry_interval, mqueue_len, max_mqueue, - mqueue_dropped, next_pkt_id, awaiting_rel, max_awaiting_rel, - await_rel_timeout, created_at]). --define(STATS_KEYS, [subscriptions_cnt, max_subscriptions, inflight, max_inflight, - mqueue_len, max_mqueue, mqueue_dropped, awaiting_rel, - max_awaiting_rel, enqueue_cnt]). + +-define(ATTR_KEYS, [inflight_max, + mqueue_max, + retry_interval, + awaiting_rel_max, + await_rel_timeout, + created_at + ]). + +-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 %%-------------------------------------------------------------------- %% @doc Init a session. --spec(init(emqx_types:client_info(), emqx_types:conninfo()) -> session()). +-spec(init(emqx_types:clientinfo(), emqx_types:conninfo()) -> session()). init(#{zone := Zone}, #{receive_maximum := MaxInflight}) -> #session{max_subscriptions = get_env(Zone, max_subscriptions, 0), subscriptions = #{}, @@ -153,7 +180,7 @@ init(#{zone := Zone}, #{receive_maximum := MaxInflight}) -> max_awaiting_rel = get_env(Zone, max_awaiting_rel, 100), await_rel_timeout = get_env(Zone, await_rel_timeout, 3600*1000), enqueue_cnt = 0, - created_at = os:timestamp() + created_at = erlang:system_time(second) }. init_mqueue(Zone) -> @@ -183,19 +210,19 @@ info(subscriptions, #session{subscriptions = Subs}) -> Subs; info(subscriptions_cnt, #session{subscriptions = Subs}) -> maps:size(Subs); -info(max_subscriptions, #session{max_subscriptions = MaxSubs}) -> +info(subscriptions_max, #session{max_subscriptions = MaxSubs}) -> MaxSubs; info(upgrade_qos, #session{upgrade_qos = UpgradeQoS}) -> UpgradeQoS; info(inflight, #session{inflight = Inflight}) -> emqx_inflight:size(Inflight); -info(max_inflight, #session{inflight = Inflight}) -> +info(inflight_max, #session{inflight = Inflight}) -> emqx_inflight:max_size(Inflight); info(retry_interval, #session{retry_interval = Interval}) -> Interval; info(mqueue_len, #session{mqueue = MQueue}) -> emqx_mqueue:len(MQueue); -info(max_mqueue, #session{mqueue = MQueue}) -> +info(mqueue_max, #session{mqueue = MQueue}) -> emqx_mqueue:max_len(MQueue); info(mqueue_dropped, #session{mqueue = MQueue}) -> emqx_mqueue:dropped(MQueue); @@ -203,7 +230,7 @@ info(next_pkt_id, #session{next_pkt_id = PacketId}) -> PacketId; info(awaiting_rel, #session{awaiting_rel = AwaitingRel}) -> maps:size(AwaitingRel); -info(max_awaiting_rel, #session{max_awaiting_rel = MaxAwaitingRel}) -> +info(awaiting_rel_max, #session{max_awaiting_rel = MaxAwaitingRel}) -> MaxAwaitingRel; info(await_rel_timeout, #session{await_rel_timeout = Timeout}) -> Timeout; @@ -224,14 +251,14 @@ takeover(#session{subscriptions = Subs}) -> ok = emqx_broker:unsubscribe(TopicFilter) 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}) -> %% 1. Subscribe again. lists:foreach(fun({TopicFilter, SubOpts}) -> ok = emqx_broker:subscribe(TopicFilter, ClientId, SubOpts) end, maps:to_list(Subs)). %% 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 %%Session. @@ -252,7 +279,7 @@ redeliver(Session = #session{inflight = Inflight}) -> %% Client -> Broker: SUBSCRIBE %%-------------------------------------------------------------------- --spec(subscribe(emqx_types:client_info(), 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()}). subscribe(ClientInfo, TopicFilter, SubOpts, Session = #session{subscriptions = Subs}) -> case is_subscriptions_full(Session) @@ -269,7 +296,7 @@ is_subscriptions_full(#session{max_subscriptions = MaxLimit, maps:size(Subs) >= MaxLimit. -compile({inline, [do_subscribe/4]}). -do_subscribe(Client = #{client_id := ClientId}, TopicFilter, SubOpts, +do_subscribe(Client = #{clientid := ClientId}, TopicFilter, SubOpts, Session = #session{subscriptions = Subs}) -> case IsNew = (not maps:is_key(TopicFilter, Subs)) of true -> @@ -285,7 +312,7 @@ do_subscribe(Client = #{client_id := ClientId}, TopicFilter, SubOpts, %% Client -> Broker: UNSUBSCRIBE %%-------------------------------------------------------------------- --spec(unsubscribe(emqx_types:client_info(), emqx_types:topic(), session()) +-spec(unsubscribe(emqx_types:clientinfo(), emqx_types:topic(), session()) -> {ok, session()} | {error, emqx_types:reason_code()}). unsubscribe(ClientInfo, TopicFilter, Session = #session{subscriptions = Subs}) -> case maps:find(TopicFilter, Subs) of diff --git a/src/emqx_stats.erl b/src/emqx_stats.erl index d3454c6f2..b6a61d458 100644 --- a/src/emqx_stats.erl +++ b/src/emqx_stats.erl @@ -37,7 +37,9 @@ , setstat/3 , statsfun/1 , statsfun/2 - , update_interval/2 + ]). + +-export([ update_interval/2 , update_interval/3 , cancel_update/1 ]). diff --git a/src/emqx_ws_connection.erl b/src/emqx_ws_connection.erl index 7207669e3..267dbad60 100644 --- a/src/emqx_ws_connection.erl +++ b/src/emqx_ws_connection.erl @@ -25,9 +25,7 @@ -logger_header("[WsConnection]"). -export([ info/1 - , attrs/1 , stats/1 - , state/1 ]). -export([call/2]). @@ -40,17 +38,17 @@ , terminate/3 ]). --record(ws_connection, { +-record(state, { %% Peername of the ws connection. peername :: emqx_types:peername(), %% Sockname of the ws connection sockname :: emqx_types:peername(), - %% FSM state - fsm_state :: idle | connected | disconnected, + %% Conn state + conn_state :: idle | connected | disconnected, %% Parser State parse_state :: emqx_frame:parse_state(), %% Serialize function - serialize :: fun((emqx_types:packet()) -> iodata()), + serialize :: emqx_frame:serialize_fun(), %% Channel State chan_state :: emqx_channel:channel(), %% Out Pending Packets @@ -59,10 +57,9 @@ stop_reason :: term() }). --type(ws_connection() :: #ws_connection{}). +-type(state() :: #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(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]). @@ -70,53 +67,35 @@ %% API %%-------------------------------------------------------------------- --spec(info(pid()|ws_connection()) -> emqx_types:infos()). +-spec(info(pid()|state()) -> emqx_types:infos()). info(WsPid) when is_pid(WsPid) -> call(WsPid, info); -info(WsConn = #ws_connection{chan_state = ChanState}) -> +info(WsConn = #state{chan_state = ChanState}) -> ChanInfo = emqx_channel:info(ChanState), SockInfo = maps:from_list(info(?INFO_KEYS, WsConn)), maps:merge(ChanInfo, #{sockinfo => SockInfo}). info(Keys, WsConn) when is_list(Keys) -> [{Key, info(Key, WsConn)} || Key <- Keys]; -info(socktype, #ws_connection{}) -> - websocket; -info(peername, #ws_connection{peername = Peername}) -> +info(socktype, _State) -> + ws; +info(peername, #state{peername = Peername}) -> Peername; -info(sockname, #ws_connection{sockname = Sockname}) -> +info(sockname, #state{sockname = Sockname}) -> Sockname; -info(active_state, #ws_connection{}) -> +info(active_state, _State) -> running; -info(chan_state, #ws_connection{chan_state = ChanState}) -> +info(chan_state, #state{chan_state = ChanState}) -> emqx_channel:info(ChanState). --spec(attrs(pid()|ws_connection()) -> emqx_types:attrs()). -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()). +-spec(stats(pid()|state()) -> emqx_types:stats()). stats(WsPid) when is_pid(WsPid) -> call(WsPid, stats); -stats(#ws_connection{chan_state = ChanState}) -> - ProcStats = emqx_misc:proc_stats(), - SockStats = wsock_stats(), - ConnStats = conn_stats(), - ChanStats = emqx_channel:stats(ChanState), - lists:append([ProcStats, SockStats, ConnStats, ChanStats]). - -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). +stats(#state{chan_state = ChanState}) -> + [{sock_stats, emqx_pd:get_counters(?SOCK_STATS)}, + {conn_stats, emqx_pd:get_counters(?CONN_STATS)}, + {chan_stats, emqx_channel:stats(ChanState)}, + {proc_stats, emqx_misc:proc_stats()}]. %% kick|discard|takeover -spec(call(pid(), Req :: term()) -> Reply :: term()). @@ -177,44 +156,46 @@ websocket_init([Req, Opts]) -> [Error, Reason]), undefined end, - ChanState = emqx_channel:init(#{peername => Peername, - sockname => Sockname, - peercert => Peercert, - ws_cookie => WsCookie, - protocol => mqtt, - conn_mod => ?MODULE - }, Opts), + ConnInfo = #{socktype => ws, + peername => Peername, + sockname => Sockname, + peercert => Peercert, + ws_cookie => WsCookie, + conn_mod => ?MODULE + }, Zone = proplists:get_value(zone, Opts), - MaxSize = emqx_zone:get_env(Zone, max_packet_size, ?MAX_PACKET_SIZE), - ParseState = emqx_frame:initial_parse_state(#{max_size => MaxSize}), + FrameOpts = emqx_zone:frame_options(Zone), + 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)), - {ok, #ws_connection{peername = Peername, - sockname = Sockname, - fsm_state = idle, - parse_state = ParseState, - chan_state = ChanState, - pendings = [], - serialize = serialize_fun(?MQTT_PROTO_V5, undefined)}}. + {ok, #state{peername = Peername, + sockname = Sockname, + conn_state = idle, + parse_state = ParseState, + serialize = Serialize, + chan_state = ChanState, + pendings = [] + }}. websocket_handle({binary, Data}, State) when is_list(Data) -> 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]), Oct = iolist_size(Data), ok = inc_recv_stats(1, Oct), NChanState = emqx_channel:received(Oct, ChanState), - NState = State#ws_connection{chan_state = NChanState}, + NState = State#state{chan_state = NChanState}, process_incoming(Data, NState); %% Pings should be replied with pongs, cowboy does it automatically %% Pongs can be safely ignored. Clause here simply prevents crash. -websocket_handle(Frame, State) - when Frame =:= ping; Frame =:= pong -> +websocket_handle(Frame, State) when Frame =:= ping; Frame =:= pong -> {ok, State}; -websocket_handle({FrameType, _}, State) - when FrameType =:= ping; FrameType =:= pong -> +websocket_handle({FrameType, _}, State) when FrameType =:= ping; + FrameType =:= pong -> {ok, State}; websocket_handle({FrameType, _}, State) -> @@ -225,10 +206,6 @@ websocket_info({call, From, info}, State) -> gen_server:reply(From, info(State)), {ok, State}; -websocket_info({call, From, attrs}, State) -> - gen_server:reply(From, attrs(State)), - {ok, State}; - websocket_info({call, From, stats}, State) -> gen_server:reply(From, stats(State)), {ok, State}; @@ -237,63 +214,43 @@ websocket_info({call, From, state}, State) -> gen_server:reply(From, 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 {ok, Reply, NChanState} -> _ = gen_server:reply(From, Reply), - {ok, State#ws_connection{chan_state = NChanState}}; + {ok, State#state{chan_state = NChanState}}; {stop, Reason, Reply, NChanState} -> _ = gen_server:reply(From, Reply), - stop(Reason, State#ws_connection{chan_state = NChanState}) + stop(Reason, State#state{chan_state = NChanState}) end; -websocket_info({cast, Msg}, State = #ws_connection{chan_state = ChanState}) -> - case emqx_channel:handle_cast(Msg, ChanState) of +websocket_info({cast, Msg}, State = #state{chan_state = ChanState}) -> + case emqx_channel:handle_info(Msg, ChanState) of + ok -> {ok, State}; {ok, NChanState} -> - {ok, State#ws_connection{chan_state = NChanState}}; + {ok, State#state{chan_state = NChanState}}; {stop, Reason, NChanState} -> - stop(Reason, State#ws_connection{chan_state = NChanState}) + stop(Reason, State#state{chan_state = NChanState}) end; -websocket_info({incoming, {error, Reason}}, State = #ws_connection{fsm_state = idle}) -> - stop({shutdown, Reason}, State); - -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)}, +websocket_info({incoming, Packet = ?CONNECT_PACKET(ConnPkt)}, State) -> + NState = State#state{serialize = emqx_frame:serialize_fun(ConnPkt)}, handle_incoming(Packet, fun connected/1, NState); -websocket_info({incoming, Packet}, State = #ws_connection{fsm_state = idle}) -> - ?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) -> +websocket_info({incoming, Packet}, State) when is_record(Packet, mqtt_packet) -> 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}, - State = #ws_connection{chan_state = ChanState}) -> + State = #state{chan_state = ChanState}) -> Delivers = emqx_misc:drain_deliver([Deliver]), case emqx_channel:handle_out({deliver, Delivers}, ChanState) of {ok, NChanState} -> - reply(State#ws_connection{chan_state = NChanState}); + reply(State#state{chan_state = NChanState}); {ok, Packets, NChanState} -> - reply(enqueue(Packets, State#ws_connection{chan_state = NChanState})) + reply(enqueue(Packets, State#state{chan_state = NChanState})) end; websocket_info({timeout, TRef, keepalive}, State) when is_reference(TRef) -> @@ -312,15 +269,15 @@ websocket_info({shutdown, Reason}, State) -> websocket_info({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 {ok, NChanState} -> - {ok, State#ws_connection{chan_state = NChanState}}; + {ok, State#state{chan_state = NChanState}}; {stop, Reason, NChanState} -> - stop(Reason, State#ws_connection{chan_state = NChanState}) + stop(Reason, State#state{chan_state = NChanState}) end. -terminate(SockError, _Req, #ws_connection{chan_state = ChanState, +terminate(SockError, _Req, #state{chan_state = ChanState, stop_reason = Reason}) -> ?LOG(debug, "Terminated for ~p, sockerror: ~p", [Reason, SockError]), emqx_channel:terminate(Reason, ChanState). @@ -328,31 +285,37 @@ terminate(SockError, _Req, #ws_connection{chan_state = ChanState, %%-------------------------------------------------------------------- %% Connected callback -connected(State = #ws_connection{chan_state = ChanState}) -> - ok = emqx_channel:handle_cast({register, attrs(State), stats(State)}, ChanState), - reply(State#ws_connection{fsm_state = connected}). +connected(State = #state{chan_state = ChanState}) -> + ChanAttrs = emqx_channel:attrs(ChanState), + 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) -> - reply(State#ws_connection{fsm_state = disconnected}). +close(Reason, State) -> + ?LOG(warning, "Closed for ~p", [Reason]), + reply(State#state{conn_state = disconnected}). %%-------------------------------------------------------------------- %% 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 {ok, NChanState} -> - {ok, State#ws_connection{chan_state = NChanState}}; + {ok, State#state{chan_state = NChanState}}; {ok, Packets, NChanState} -> - NState = State#ws_connection{chan_state = NChanState}, + NState = State#state{chan_state = NChanState}, reply(enqueue(Packets, NState)); - {wait_session_expire, Reason, NChanState} -> - ?LOG(debug, "Disconnect and wait for session to expire due to ~p", [Reason]), - disconnected(State#ws_connection{chan_state = NChanState}); + {close, Reason, NChanState} -> + close(Reason, State#state{chan_state = NChanState}); + {close, Reason, OutPackets, NChanState} -> + NState = State#state{chan_state= NChanState}, + close(Reason, enqueue(OutPackets, NState)); {stop, Reason, NChanState} -> - stop(Reason, State#ws_connection{chan_state = NChanState}) + stop(Reason, State#state{chan_state = NChanState}) end. %%-------------------------------------------------------------------- @@ -361,20 +324,18 @@ handle_timeout(TRef, Msg, State = #ws_connection{chan_state = ChanState}) -> process_incoming(<<>>, 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 {more, NParseState} -> - {ok, State#ws_connection{parse_state = NParseState}}; + {ok, State#state{parse_state = NParseState}}; {ok, Packet, Rest, NParseState} -> self() ! {incoming, Packet}, - process_incoming(Rest, State#ws_connection{parse_state = NParseState}); - {error, Reason} -> - self() ! {incoming, {error, Reason}}, - {ok, State} + process_incoming(Rest, State#state{parse_state = NParseState}) catch error:Reason:Stk -> - ?LOG(error, "~nParse failed for ~p~nStacktrace: ~p~nFrame data: ~p", [Reason, Stk, Data]), - self() ! {incoming, {error, Reason}}, + ?LOG(error, "~nParse failed for ~p~nStacktrace: ~p~nFrame data: ~p", + [Reason, Stk, Data]), + self() ! {incoming, {frame_error, Reason}}, {ok, State} end. @@ -382,55 +343,59 @@ process_incoming(Data, State = #ws_connection{parse_state = ParseState}) -> %% Handle incoming packets handle_incoming(Packet = ?PACKET(Type), SuccFun, - State = #ws_connection{chan_state = ChanState}) -> + State = #state{chan_state = ChanState}) -> _ = inc_incoming_stats(Type), - ok = emqx_metrics:inc_recv(Packet), + _ = emqx_metrics:inc_recv(Packet), ?LOG(debug, "RECV ~s", [emqx_packet:format(Packet)]), case emqx_channel:handle_in(Packet, ChanState) of {ok, NChanState} -> - SuccFun(State#ws_connection{chan_state= NChanState}); + SuccFun(State#state{chan_state= NChanState}); {ok, OutPackets, NChanState} -> - NState = State#ws_connection{chan_state= NChanState}, + NState = State#state{chan_state= NChanState}, SuccFun(enqueue(OutPackets, NState)); - {wait_session_expire, Reason, NChanState} -> - ?LOG(debug, "Disconnect and wait for session to expire due to ~p", [Reason]), - disconnected(State#ws_connection{chan_state = NChanState}); - {wait_session_expire, Reason, OutPackets, NChanState} -> - ?LOG(debug, "Disconnect and wait for session to expire due to ~p", [Reason]), - disconnected(enqueue(OutPackets, State#ws_connection{chan_state = NChanState})); + {close, Reason, NChanState} -> + close(Reason, State#state{chan_state = NChanState}); + {close, Reason, OutPackets, NChanState} -> + NState = State#state{chan_state= NChanState}, + close(Reason, enqueue(OutPackets, NState)); {stop, Reason, NChanState} -> - stop(Reason, State#ws_connection{chan_state = NChanState}); + stop(Reason, State#state{chan_state = 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)) end. %%-------------------------------------------------------------------- %% Handle outgoing packets -handle_outgoing(Packets, State = #ws_connection{serialize = Serialize, - chan_state = ChanState}) -> - Data = lists:map(Serialize, Packets), - Oct = iolist_size(Data), +handle_outgoing(Packets, State = #state{chan_state = ChanState}) -> + IoData = lists:map(serialize_and_inc_stats_fun(State), Packets), + Oct = iolist_size(IoData), ok = inc_sent_stats(length(Packets), Oct), NChanState = emqx_channel:sent(Oct, ChanState), - {{binary, Data}, State#ws_connection{chan_state = NChanState}}. + {{binary, IoData}, State#state{chan_state = NChanState}}. -%%-------------------------------------------------------------------- -%% Serialize fun - -serialize_fun(ProtoVer, MaxPacketSize) -> +%% TODO: Duplicated with emqx_channel:serialize_and_inc_stats_fun/1 +serialize_and_inc_stats_fun(#state{serialize = Serialize}) -> fun(Packet = ?PACKET(Type)) -> - IoData = emqx_frame:serialize(Packet, ProtoVer), - case Type =/= ?PUBLISH orelse MaxPacketSize =:= undefined orelse iolist_size(IoData) =< MaxPacketSize of - true -> - ?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]), - _ = inc_outgoing_stats(Type), - _ = emqx_metrics:inc_sent(Packet), - IoData; - false -> - ?LOG(warning, "DROP ~s due to oversize packet size", [emqx_packet:format(Packet)]), - <<"">> + case Serialize(Packet) of + <<>> -> ?LOG(warning, "~s is discarded due to the frame is too large!", + [emqx_packet:format(Packet)]), + <<>>; + Data -> _ = inc_outgoing_stats(Type), + _ = emqx_metrics:inc_sent(Packet), + ?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]), + Data end end. @@ -469,21 +434,21 @@ inc_sent_stats(Cnt, Oct) -> -compile({inline, [reply/1]}). -reply(State = #ws_connection{pendings = []}) -> +reply(State = #state{pendings = []}) -> {ok, State}; -reply(State = #ws_connection{pendings = Pendings}) -> +reply(State = #state{pendings = Pendings}) -> {Reply, NState} = handle_outgoing(Pendings, State), - {reply, Reply, NState#ws_connection{pendings = []}}. + {reply, Reply, NState#state{pendings = []}}. -stop(Reason, State = #ws_connection{pendings = []}) -> - {stop, State#ws_connection{stop_reason = Reason}}; -stop(Reason, State = #ws_connection{pendings = Pendings}) -> +stop(Reason, State = #state{pendings = []}) -> + {stop, State#state{stop_reason = Reason}}; +stop(Reason, State = #state{pendings = Pendings}) -> {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}. enqueue(Packet, State) when is_record(Packet, mqtt_packet) -> enqueue([Packet], State); -enqueue(Packets, State = #ws_connection{pendings = Pendings}) -> - State#ws_connection{pendings = lists:append(Pendings, Packets)}. +enqueue(Packets, State = #state{pendings = Pendings}) -> + State#state{pendings = lists:append(Pendings, Packets)}. diff --git a/src/emqx_zone.erl b/src/emqx_zone.erl index 9d45c64e1..90d56eb0e 100644 --- a/src/emqx_zone.erl +++ b/src/emqx_zone.erl @@ -19,6 +19,7 @@ -behaviour(gen_server). -include("emqx.hrl"). +-include("emqx_mqtt.hrl"). -include("logger.hrl"). -include("types.hrl"). @@ -27,7 +28,10 @@ %% APIs -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_acl/1 , enable_ban/1 @@ -40,6 +44,8 @@ , force_shutdown_policy/1 ]). +-export([check_oom/2]). + -export([ get_env/2 , get_env/3 , set_env/3 @@ -76,6 +82,20 @@ start_link() -> 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()). use_username_as_clientid(Zone) -> get_env(Zone, use_username_as_clientid, false). @@ -120,6 +140,19 @@ force_gc_policy(Zone) -> force_shutdown_policy(Zone) -> 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())). get_env(undefined, Key) -> emqx:get_env(Key); get_env(Zone, Key) -> diff --git a/test/emqx_channel_SUITE.erl b/test/emqx_channel_SUITE.erl index fdd7c1b56..d6bf61b58 100644 --- a/test/emqx_channel_SUITE.erl +++ b/test/emqx_channel_SUITE.erl @@ -28,6 +28,21 @@ -include("emqx_mqtt.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). init_per_suite(Config) -> @@ -50,7 +65,7 @@ t_handle_connect(_) -> clean_start = true, keepalive = 30, properties = #{}, - client_id = <<"clientid">>, + clientid = <<"clientid">>, username = <<"username">>, password = <<"passwd">> }, @@ -58,20 +73,21 @@ t_handle_connect(_) -> fun(Channel) -> {ok, ?CONNACK_PACKET(?RC_SUCCESS), Channel1} = handle_in(?CONNECT_PACKET(ConnPkt), Channel), - #{client_id := ClientId, username := Username} + #{clientid := ClientId, username := Username} = emqx_channel:info(client, Channel1), ?assertEqual(<<"clientid">>, ClientId), ?assertEqual(<<"username">>, Username) end). -t_handle_publish_qos0(_) -> +t_handle_in_publish_qos0(_) -> with_channel( fun(Channel) -> 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). -t_handle_publish_qos1(_) -> +t_handle_in_publish_qos1(_) -> with_channel( fun(Channel) -> Publish = ?PUBLISH_PACKET(?QOS_1, <<"topic">>, 1, <<"payload">>), @@ -91,30 +107,34 @@ t_handle_publish_qos2(_) -> ?assertEqual(2, AwaitingRel) end). -t_handle_puback(_) -> +t_handle_in_puback(_) -> with_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). -t_handle_pubrec(_) -> +t_handle_in_pubrec(_) -> with_channel( fun(Channel) -> - {ok, ?PUBREL_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel} - = handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), Channel) + {ok, ?PUBREL_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel1} + = handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), Channel), + ?assertEqual(#{pubrec_in => 1, pubrel_out => 1}, emqx_channel:info(pub_stats, Channel1)) end). -t_handle_pubrel(_) -> +t_handle_in_pubrel(_) -> with_channel( fun(Channel) -> - {ok, ?PUBCOMP_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel} - = handle_in(?PUBREL_PACKET(1, ?RC_SUCCESS), Channel) + {ok, ?PUBCOMP_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel1} + = handle_in(?PUBREL_PACKET(1, ?RC_SUCCESS), Channel), + ?assertEqual(#{pubrel_in => 1, pubcomp_out => 1}, emqx_channel:info(pub_stats, Channel1)) end). -t_handle_pubcomp(_) -> +t_handle_in_pubcomp(_) -> with_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). t_handle_subscribe(_) -> @@ -144,14 +164,15 @@ t_handle_pingreq(_) -> t_handle_disconnect(_) -> with_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)) end). -t_handle_auth(_) -> +t_handle_in_auth(_) -> with_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). %%-------------------------------------------------------------------- @@ -175,13 +196,13 @@ t_handle_deliver(_) -> %% Test cases for handle_out %%-------------------------------------------------------------------- -t_handle_connack(_) -> +t_handle_out_connack(_) -> ConnPkt = #mqtt_packet_connect{ proto_name = <<"MQTT">>, proto_ver = ?MQTT_PROTO_V4, clean_start = true, properties = #{}, - client_id = <<"clientid">> + clientid = <<"clientid">> }, with_channel( fun(Channel) -> @@ -199,39 +220,44 @@ t_handle_out_publish(_) -> 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_1), Channel} = handle_out(Pub1, Channel), - {ok, Packets, Channel} = handle_out({publish, [Pub0, Pub1]}, Channel), - ?assertEqual(2, length(Packets)) + {ok, Packets, Channel1} = handle_out({publish, [Pub0, Pub1]}, Channel), + ?assertEqual(2, length(Packets)), + ?assertEqual(#{publish_out => 2}, emqx_channel:info(pub_stats, Channel1)) end). t_handle_out_puback(_) -> with_channel( fun(Channel) -> {ok, Channel} = handle_out({puberr, ?RC_NOT_AUTHORIZED}, Channel), - {ok, ?PUBACK_PACKET(1, ?RC_SUCCESS), Channel} - = handle_out({puback, 1, ?RC_SUCCESS}, Channel) + {ok, ?PUBACK_PACKET(1, ?RC_SUCCESS), Channel1} + = handle_out({puback, 1, ?RC_SUCCESS}, Channel), + ?assertEqual(#{puback_out => 1}, emqx_channel:info(pub_stats, Channel1)) end). t_handle_out_pubrec(_) -> with_channel( fun(Channel) -> - {ok, ?PUBREC_PACKET(4, ?RC_SUCCESS), Channel} - = handle_out({pubrec, 4, ?RC_SUCCESS}, Channel) + {ok, ?PUBREC_PACKET(4, ?RC_SUCCESS), Channel1} + = handle_out({pubrec, 4, ?RC_SUCCESS}, Channel), + ?assertEqual(#{pubrec_out => 1}, emqx_channel:info(pub_stats, Channel1)) end). t_handle_out_pubrel(_) -> with_channel( fun(Channel) -> - {ok, ?PUBREL_PACKET(2), Channel} + {ok, ?PUBREL_PACKET(2), Channel1} = handle_out({pubrel, 2, ?RC_SUCCESS}, Channel), - {ok, ?PUBREL_PACKET(3, ?RC_SUCCESS), Channel} - = handle_out({pubrel, 3, ?RC_SUCCESS}, Channel) + {ok, ?PUBREL_PACKET(3, ?RC_SUCCESS), Channel2} + = handle_out({pubrel, 3, ?RC_SUCCESS}, Channel1), + ?assertEqual(#{pubrel_out => 2}, emqx_channel:info(pub_stats, Channel2)) end). t_handle_out_pubcomp(_) -> with_channel( fun(Channel) -> - {ok, ?PUBCOMP_PACKET(5, ?RC_SUCCESS), Channel} - = handle_out({pubcomp, 5, ?RC_SUCCESS}, Channel) + {ok, ?PUBCOMP_PACKET(5, ?RC_SUCCESS), Channel1} + = handle_out({pubcomp, 5, ?RC_SUCCESS}, Channel), + ?assertEqual(#{pubcomp_out => 1}, emqx_channel:info(pub_stats, Channel1)) end). t_handle_out_suback(_) -> @@ -279,32 +305,22 @@ t_terminate(_) -> %%-------------------------------------------------------------------- with_channel(TestFun) -> - ConnInfo = #{peername => {{127,0,0,1}, 3456}, - sockname => {{127,0,0,1}, 1883}, - protocol => mqtt, - conn_mod => emqx_connection, - 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 - }, + with_channel(#{}, TestFun). + +with_channel(ConnInfo, TestFun) -> + ConnInfo1 = maps:merge(?DEFAULT_CONNINFO, ConnInfo), ClientInfo = #{zone => <<"external">>, protocol => mqtt, peerhost => {127,0,0,1}, - client_id => <<"clientid">>, + clientid => <<"clientid">>, username => <<"username">>, peercert => undefined, is_bridge => false, is_superuser => false, mountpoint => undefined }, - Channel = emqx_channel:init(ConnInfo, [{zone, testing}]), - Session = emqx_session:init(ClientInfo, ConnInfo), - Channel1 = emqx_channel:set_field(client, ClientInfo, Channel), + Channel = emqx_channel:init(ConnInfo1, [{zone, testing}]), + Session = emqx_session:init(ClientInfo, ConnInfo1), + Channel1 = emqx_channel:set_field(clientinfo, ClientInfo, Channel), TestFun(emqx_channel:set_field(session, Session, Channel1)). diff --git a/test/emqx_client_SUITE.erl b/test/emqx_client_SUITE.erl index 19557143b..e83aef057 100644 --- a/test/emqx_client_SUITE.erl +++ b/test/emqx_client_SUITE.erl @@ -95,10 +95,10 @@ t_cm(_) -> IdleTimeout = emqx_zone:get_env(external, idle_timeout, 30000), emqx_zone:set_env(external, idle_timeout, 1000), ClientId = <<"myclient">>, - {ok, C} = emqtt:start_link([{client_id, ClientId}]), + {ok, C} = emqtt:start_link([{clientid, ClientId}]), {ok, _} = emqtt:connect(C), ct:sleep(50), - #{client := #{client_id := ClientId}} = emqx_cm:get_chan_attrs(ClientId), + #{client := #{clientid := ClientId}} = emqx_cm:get_chan_attrs(ClientId), emqtt:subscribe(C, <<"mytopic">>, 0), ct:sleep(1200), Stats = emqx_cm:get_chan_stats(ClientId), @@ -135,13 +135,13 @@ t_will_message(_Config) -> t_offline_message_queueing(_) -> {ok, C1} = emqtt:start_link([{clean_start, false}, - {client_id, <<"c1">>}]), + {clientid, <<"c1">>}]), {ok, _} = emqtt:connect(C1), {ok, _, [2]} = emqtt:subscribe(C1, nth(6, ?WILD_TOPICS), 2), ok = emqtt:disconnect(C1), {ok, C2} = emqtt:start_link([{clean_start, true}, - {client_id, <<"c2">>}]), + {clientid, <<"c2">>}]), {ok, _} = emqtt:connect(C2), 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), timer:sleep(10), emqtt:disconnect(C2), - {ok, C3} = emqtt:start_link([{clean_start, false}, - {client_id, <<"c1">>}]), + {ok, C3} = emqtt:start_link([{clean_start, false}, {clientid, <<"c1">>}]), {ok, _} = emqtt:connect(C3), timer:sleep(10), @@ -198,8 +197,7 @@ t_overlapping_subscriptions(_) -> t_redelivery_on_reconnect(_) -> ct:pal("Redelivery on reconnect test starting"), - {ok, C1} = emqtt:start_link([{clean_start, false}, - {client_id, <<"c">>}]), + {ok, C1} = emqtt:start_link([{clean_start, false}, {clientid, <<"c">>}]), {ok, _} = emqtt:connect(C1), {ok, _, [2]} = emqtt:subscribe(C1, nth(7, ?WILD_TOPICS), 2), @@ -212,8 +210,7 @@ t_redelivery_on_reconnect(_) -> timer:sleep(10), ok = emqtt:disconnect(C1), ?assertEqual(0, length(recv_msgs(2))), - {ok, C2} = emqtt:start_link([{clean_start, false}, - {client_id, <<"c">>}]), + {ok, C2} = emqtt:start_link([{clean_start, false}, {clientid, <<"c">>}]), {ok, _} = emqtt:connect(C2), timer:sleep(10), diff --git a/test/emqx_connection_SUITE.erl b/test/emqx_connection_SUITE.erl index d321d2c85..39cc67dee 100644 --- a/test/emqx_connection_SUITE.erl +++ b/test/emqx_connection_SUITE.erl @@ -33,7 +33,7 @@ end_per_suite(_Config) -> t_basic(_) -> 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, _, [1]} = emqtt:subscribe(C, Topic, qos1), {ok, _, [2]} = emqtt:subscribe(C, Topic, qos2), diff --git a/test/emqx_frame_SUITE.erl b/test/emqx_frame_SUITE.erl index 09ef7d901..f70a815fe 100644 --- a/test/emqx_frame_SUITE.erl +++ b/test/emqx_frame_SUITE.erl @@ -129,8 +129,8 @@ t_parse_cont(_) -> t_parse_frame_too_large(_) -> Packet = ?PUBLISH_PACKET(?QOS_1, <<"t">>, 1, payload(1000)), - ?catch_error(mqtt_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 => 256})), + ?catch_error(frame_too_large, parse_serialize(Packet, #{max_size => 512})), ?assertEqual(Packet, parse_serialize(Packet, #{max_size => 2048, version => ?MQTT_PROTO_V4})). t_serialize_parse_connect(_) -> @@ -411,7 +411,7 @@ t_serialize_parse_pubcomp_v5(_) -> t_serialize_parse_subscribe(_) -> %% SUBSCRIBE(Q1, R0, D0, PacketId=2, TopicTable=[{<<"TopicA">>,2}]) Bin = <>, - TopicOpts = #{nl => 0 , rap => 0, rc => 0, rh => 0, qos => 2}, + TopicOpts = #{nl => 0 , rap => 0, rh => 0, qos => 2}, TopicFilters = [{<<"TopicA">>, TopicOpts}], Packet = ?SUBSCRIBE_PACKET(2, TopicFilters), ?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}}]))). t_serialize_parse_subscribe_v5(_) -> - TopicFilters = [{<<"TopicQos0">>, #{rh => 1, qos => ?QOS_2, rap => 0, nl => 0, rc => 0}}, - {<<"TopicQos1">>, #{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}}], Packet = ?SUBSCRIBE_PACKET(3, #{'Subscription-Identifier' => 16#FFFFFFF}, TopicFilters), ?assertEqual(Packet, parse_serialize(Packet, #{version => ?MQTT_PROTO_V5})). diff --git a/test/emqx_misc_SUITE.erl b/test/emqx_misc_SUITE.erl index 677b25c17..6f77ec86c 100644 --- a/test/emqx_misc_SUITE.erl +++ b/test/emqx_misc_SUITE.erl @@ -87,7 +87,7 @@ t_proc_stats(_) -> Pid2 = spawn(fun() -> timer:sleep(100) end), Pid2 ! msg, timer:sleep(10), - ?assertMatch([{mailbox_len, 1}|_], emqx_misc:proc_stats(Pid2)). + ?assertMatch([{message_queue_len, 1}|_], emqx_misc:proc_stats(Pid2)). t_drain_deliver(_) -> self() ! {deliver, t1, m1}, diff --git a/test/emqx_session_SUITE.erl b/test/emqx_session_SUITE.erl index da0c0115a..93a4e1873 100644 --- a/test/emqx_session_SUITE.erl +++ b/test/emqx_session_SUITE.erl @@ -188,12 +188,12 @@ info_args() -> sub_args() -> ?LET({ClientId, TopicFilter, SubOpts}, {clientid(), topic(), sub_opts()}, - {#{client_id => ClientId}, TopicFilter, SubOpts}). + {#{clientid => ClientId}, TopicFilter, SubOpts}). unsub_args() -> ?LET({ClientId, TopicFilter}, {clientid(), topic()}, - {#{client_id => ClientId}, TopicFilter}). + {#{clientid => ClientId}, TopicFilter}). publish_args() -> ?LET({PacketId, Message}, diff --git a/test/emqx_shared_sub_SUITE.erl b/test/emqx_shared_sub_SUITE.erl index 14baf8a76..9fec11171 100644 --- a/test/emqx_shared_sub_SUITE.erl +++ b/test/emqx_shared_sub_SUITE.erl @@ -80,9 +80,9 @@ t_no_connection_nack(_) -> ShareTopic = <<"$share/", Group/binary, $/, Topic/binary>>, 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, SubConnPid2} = emqtt:start_link([{client_id, Subscriber2}] ++ ExpProp), + {ok, SubConnPid2} = emqtt:start_link([{clientid, Subscriber2}] ++ ExpProp), {ok, _Props} = emqtt:connect(SubConnPid2), emqtt:subscribe(SubConnPid1, ShareTopic, QoS), emqtt:subscribe(SubConnPid1, ShareTopic, QoS), @@ -151,9 +151,9 @@ t_not_so_sticky(_) -> ok = ensure_config(sticky), ClientId1 = <<"ClientId1">>, ClientId2 = <<"ClientId2">>, - {ok, C1} = emqtt:start_link([{client_id, ClientId1}]), + {ok, C1} = emqtt:start_link([{clientid, ClientId1}]), {ok, _} = emqtt:connect(C1), - {ok, C2} = emqtt:start_link([{client_id, ClientId2}]), + {ok, C2} = emqtt:start_link([{clientid, ClientId2}]), {ok, _} = emqtt:connect(C2), emqtt:subscribe(C1, {<<"$share/group1/foo/bar">>, 0}), @@ -179,9 +179,9 @@ test_two_messages(Strategy, WithAck) -> Topic = <<"foo/bar">>, ClientId1 = <<"ClientId1">>, ClientId2 = <<"ClientId2">>, - {ok, ConnPid1} = emqtt:start_link([{client_id, ClientId1}]), + {ok, ConnPid1} = emqtt:start_link([{clientid, ClientId1}]), {ok, _} = emqtt:connect(ConnPid1), - {ok, ConnPid2} = emqtt:start_link([{client_id, ClientId2}]), + {ok, ConnPid2} = emqtt:start_link([{clientid, ClientId2}]), {ok, _} = emqtt:connect(ConnPid2), Message1 = emqx_message:make(ClientId1, 0, Topic, <<"hello1">>), From 977b551bbfccff086feb2fefffea760608b7d410 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Sun, 29 Sep 2019 10:53:59 +0800 Subject: [PATCH 11/14] Fix the test cases for session, channel and connection modules --- src/emqx_channel.erl | 6 +++--- src/emqx_connection.erl | 3 +-- src/emqx_misc.erl | 3 ++- src/emqx_ws_connection.erl | 9 +++++---- test/emqx_channel_SUITE.erl | 2 +- test/emqx_client_SUITE.erl | 2 +- test/emqx_misc_SUITE.erl | 2 +- test/emqx_modules_SUITE.erl | 2 +- test/emqx_session_SUITE.erl | 8 ++++---- 9 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/emqx_channel.erl b/src/emqx_channel.erl index cad4111d4..e741dd7cb 100644 --- a/src/emqx_channel.erl +++ b/src/emqx_channel.erl @@ -106,9 +106,9 @@ will_timer => will_message }). --define(ATTR_KEYS, [conninfo, client, session]). +-define(ATTR_KEYS, [conninfo, clientinfo, session]). --define(INFO_KEYS, ?ATTR_KEYS ++ [conninfo, client, session, keepalive, +-define(INFO_KEYS, ?ATTR_KEYS ++ [conninfo, clientinfo, session, keepalive, will_msg, topic_aliases, alias_maximum, gc_state]). %%-------------------------------------------------------------------- @@ -125,7 +125,7 @@ info(Keys, Channel) when is_list(Keys) -> [{Key, info(Key, Channel)} || Key <- Keys]; info(conninfo, #channel{conninfo = ConnInfo}) -> ConnInfo; -info(client, #channel{clientinfo = ClientInfo}) -> +info(clientinfo, #channel{clientinfo = ClientInfo}) -> ClientInfo; info(session, #channel{session = Session}) -> maybe_apply(fun emqx_session:info/1, Session); diff --git a/src/emqx_connection.erl b/src/emqx_connection.erl index a0408dea6..fbe0a8118 100644 --- a/src/emqx_connection.erl +++ b/src/emqx_connection.erl @@ -139,8 +139,7 @@ stats(#state{transport = Transport, ConnStats = emqx_pd:get_counters(?CONN_STATS), ChanStats = emqx_channel:stats(ChanState), ProcStats = emqx_misc:proc_stats(), - [{sock_stats, SockStats}, {conn_stats, ConnStats}, - {chan_stats, ChanStats}, {proc_stats, ProcStats}]. + lists:append([SockStats, ConnStats, ChanStats, ProcStats]). %% kick|discard|takeover -spec(call(pid(), Req :: term()) -> Reply :: term()). diff --git a/src/emqx_misc.erl b/src/emqx_misc.erl index b69b06c55..359bb045f 100644 --- a/src/emqx_misc.erl +++ b/src/emqx_misc.erl @@ -128,7 +128,8 @@ proc_stats(Pid) -> reductions, memory]) of undefined -> []; - ProcStats -> ProcStats + [{message_queue_len, Len}|ProcStats] -> + [{mailbox_len, Len}|ProcStats] end. %% @doc Drain delivers from the channel's mailbox. diff --git a/src/emqx_ws_connection.erl b/src/emqx_ws_connection.erl index 267dbad60..fe6566364 100644 --- a/src/emqx_ws_connection.erl +++ b/src/emqx_ws_connection.erl @@ -92,10 +92,11 @@ info(chan_state, #state{chan_state = ChanState}) -> stats(WsPid) when is_pid(WsPid) -> call(WsPid, stats); stats(#state{chan_state = ChanState}) -> - [{sock_stats, emqx_pd:get_counters(?SOCK_STATS)}, - {conn_stats, emqx_pd:get_counters(?CONN_STATS)}, - {chan_stats, emqx_channel:stats(ChanState)}, - {proc_stats, emqx_misc:proc_stats()}]. + SockStats = emqx_pd:get_counters(?SOCK_STATS), + ConnStats = emqx_pd:get_counters(?CONN_STATS), + ChanStats = emqx_channel:stats(ChanState), + ProcStats = emqx_misc:proc_stats(), + lists:append([SockStats, ConnStats, ChanStats, ProcStats]). %% kick|discard|takeover -spec(call(pid(), Req :: term()) -> Reply :: term()). diff --git a/test/emqx_channel_SUITE.erl b/test/emqx_channel_SUITE.erl index d6bf61b58..6623db872 100644 --- a/test/emqx_channel_SUITE.erl +++ b/test/emqx_channel_SUITE.erl @@ -74,7 +74,7 @@ t_handle_connect(_) -> {ok, ?CONNACK_PACKET(?RC_SUCCESS), Channel1} = handle_in(?CONNECT_PACKET(ConnPkt), Channel), #{clientid := ClientId, username := Username} - = emqx_channel:info(client, Channel1), + = emqx_channel:info(clientinfo, Channel1), ?assertEqual(<<"clientid">>, ClientId), ?assertEqual(<<"username">>, Username) end). diff --git a/test/emqx_client_SUITE.erl b/test/emqx_client_SUITE.erl index e83aef057..0867448f3 100644 --- a/test/emqx_client_SUITE.erl +++ b/test/emqx_client_SUITE.erl @@ -98,7 +98,7 @@ t_cm(_) -> {ok, C} = emqtt:start_link([{clientid, ClientId}]), {ok, _} = emqtt:connect(C), ct:sleep(50), - #{client := #{clientid := ClientId}} = emqx_cm:get_chan_attrs(ClientId), + #{clientinfo := #{clientid := ClientId}} = emqx_cm:get_chan_attrs(ClientId), emqtt:subscribe(C, <<"mytopic">>, 0), ct:sleep(1200), Stats = emqx_cm:get_chan_stats(ClientId), diff --git a/test/emqx_misc_SUITE.erl b/test/emqx_misc_SUITE.erl index 6f77ec86c..677b25c17 100644 --- a/test/emqx_misc_SUITE.erl +++ b/test/emqx_misc_SUITE.erl @@ -87,7 +87,7 @@ t_proc_stats(_) -> Pid2 = spawn(fun() -> timer:sleep(100) end), Pid2 ! msg, timer:sleep(10), - ?assertMatch([{message_queue_len, 1}|_], emqx_misc:proc_stats(Pid2)). + ?assertMatch([{mailbox_len, 1}|_], emqx_misc:proc_stats(Pid2)). t_drain_deliver(_) -> self() ! {deliver, t1, m1}, diff --git a/test/emqx_modules_SUITE.erl b/test/emqx_modules_SUITE.erl index 401cc2895..437a21aec 100644 --- a/test/emqx_modules_SUITE.erl +++ b/test/emqx_modules_SUITE.erl @@ -91,7 +91,7 @@ recv_and_check_presence(ClientId, Presence) -> <<"disconnected">> -> ?assertMatch(#{clientid := <<"clientid">>, username := <<"username">>, - reason := <<"closed">>}, emqx_json:decode(Payload, [{labels, atom}, return_maps])) + reason := <<"normal">>}, emqx_json:decode(Payload, [{labels, atom}, return_maps])) end. %% Test case for emqx_mod_subscription diff --git a/test/emqx_session_SUITE.erl b/test/emqx_session_SUITE.erl index 93a4e1873..e58c4b859 100644 --- a/test/emqx_session_SUITE.erl +++ b/test/emqx_session_SUITE.erl @@ -170,17 +170,17 @@ deliver_args() -> info_args() -> oneof([subscriptions, - max_subscriptions, + subscriptions_max, upgrade_qos, inflight, - max_inflight, + inflight_max, retry_interval, mqueue_len, - max_mqueue, + mqueue_max, mqueue_dropped, next_pkt_id, awaiting_rel, - max_awaiting_rel, + awaiting_rel_max, await_rel_timeout, created_at ]). From fe2a72c66417cb41dd5392c9b23be2bafb4baeca Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Sun, 29 Sep 2019 11:47:31 +0800 Subject: [PATCH 12/14] Add 'state' field to channel info --- src/emqx_channel.erl | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/emqx_channel.erl b/src/emqx_channel.erl index e741dd7cb..01e32410d 100644 --- a/src/emqx_channel.erl +++ b/src/emqx_channel.erl @@ -76,7 +76,7 @@ %% Timers timers :: #{atom() => disabled | maybe(reference())}, %% Fsm State - fsm_state :: fsm_state(), + state :: fsm_state(), %% GC State gc_state :: maybe(emqx_gc:gc_state()), %% Takeover @@ -94,7 +94,7 @@ | connected | disconnected, connected_at := pos_integer(), - disconnected := pos_integer() + disconnected_at := pos_integer() }). -define(TIMER_TABLE, #{ @@ -106,10 +106,10 @@ will_timer => will_message }). --define(ATTR_KEYS, [conninfo, clientinfo, session]). +-define(ATTR_KEYS, [conninfo, clientinfo, state, session]). --define(INFO_KEYS, ?ATTR_KEYS ++ [conninfo, clientinfo, session, keepalive, - will_msg, topic_aliases, alias_maximum, gc_state]). +-define(INFO_KEYS, ?ATTR_KEYS ++ [keepalive, will_msg, topic_aliases, + alias_maximum, gc_state]). %%-------------------------------------------------------------------- %% Info, Attrs and Caps @@ -129,6 +129,8 @@ info(clientinfo, #channel{clientinfo = ClientInfo}) -> ClientInfo; info(session, #channel{session = Session}) -> maybe_apply(fun emqx_session:info/1, Session); +info(state, #channel{state = State}) -> + State; info(keepalive, #channel{keepalive = Keepalive}) -> maybe_apply(fun emqx_keepalive:info/1, Keepalive); info(topic_aliases, #channel{topic_aliases = Aliases}) -> @@ -202,7 +204,7 @@ init(ConnInfo = #{peername := {PeerHost, _Port}}, Options) -> clientinfo = ClientInfo, pub_stats = #{}, timers = #{stats_timer => StatsTimer}, - fsm_state = #{state_name => initialized}, + state = #{state_name => initialized}, gc_state = init_gc_state(Zone), takeover = false, resuming = false, @@ -227,7 +229,7 @@ init_gc_state(Zone) -> | {close, emqx_types:packet(), channel()} | {stop, Error :: term(), channel()} | {stop, Error :: term(), emqx_types:packet(), channel()}). -handle_in(?CONNECT_PACKET(_), Channel = #channel{fsm_state = #{state_name := connected}}) -> +handle_in(?CONNECT_PACKET(_), Channel = #channel{state = #{state_name := connected}}) -> handle_out({disconnect, ?RC_PROTOCOL_ERROR}, Channel); handle_in(?CONNECT_PACKET(ConnPkt), Channel) -> @@ -364,7 +366,7 @@ handle_in(?DISCONNECT_PACKET(ReasonCode, Properties), Channel = #channel{conninf handle_in(?AUTH_PACKET(), Channel) -> handle_out({disconnect, ?RC_IMPLEMENTATION_SPECIFIC_ERROR}, Channel); -handle_in({frame_error, Reason}, Channel = #channel{fsm_state = FsmState}) -> +handle_in({frame_error, Reason}, Channel = #channel{state = FsmState}) -> case FsmState of #{state_name := initialized} -> {stop, {shutdown, Reason}, Channel}; @@ -528,16 +530,16 @@ do_unsubscribe(TopicFilter, _SubOpts, Channel = %%TODO: RunFold or Pipeline handle_out({connack, ?RC_SUCCESS, SP, ConnPkt}, - Channel = #channel{conninfo = ConnInfo, + Channel = #channel{conninfo = ConnInfo, clientinfo = ClientInfo, - fsm_state = FsmState}) -> + state = FsmState}) -> AckProps = run_fold([fun enrich_caps/2, fun enrich_server_keepalive/2, fun enrich_assigned_clientid/2], #{}, Channel), FsmState1 = FsmState#{state_name => connected, connected_at => erlang:system_time(second) }, - Channel1 = Channel#channel{fsm_state = FsmState1, + Channel1 = Channel#channel{state = FsmState1, will_msg = emqx_packet:will_msg(ConnPkt), alias_maximum = init_alias_maximum(ConnPkt, ClientInfo) }, @@ -564,8 +566,8 @@ handle_out({connack, ReasonCode, _ConnPkt}, Channel = #channel{conninfo = ConnIn Reason = emqx_reason_codes:name(ReasonCode1, ProtoVer), {stop, {shutdown, Reason}, ?CONNACK_PACKET(ReasonCode1), Channel}; -handle_out({deliver, Delivers}, Channel = #channel{fsm_state = #{state_name := disconnected}, - session = Session}) -> +handle_out({deliver, Delivers}, Channel = #channel{state = #{state_name := disconnected}, + session = Session}) -> NSession = emqx_session:enqueue(Delivers, Session), {ok, Channel#channel{session = NSession}}; @@ -669,10 +671,10 @@ handle_out({Type, Data}, Channel) -> handle_call(kick, Channel) -> {stop, {shutdown, kicked}, ok, Channel}; -handle_call(discard, Channel = #channel{fsm_state = #{state_name := connected}}) -> +handle_call(discard, Channel = #channel{state = #{state_name := connected}}) -> Packet = ?DISCONNECT_PACKET(?RC_SESSION_TAKEN_OVER), {stop, {shutdown, discarded}, Packet, ok, Channel}; -handle_call(discard, Channel = #channel{fsm_state = #{state_name := disconnected}}) -> +handle_call(discard, Channel = #channel{state = #{state_name := disconnected}}) -> {stop, {shutdown, discarded}, ok, Channel}; %% Session Takeover @@ -718,7 +720,7 @@ handle_info({register, Attrs, Stats}, #channel{clientinfo = #{clientid := Client %%handle_info(disconnected, Channel = #channel{connected = undefined}) -> %% shutdown(closed, Channel); -handle_info(disconnected, Channel = #channel{fsm_state = #{state_name := disconnected}}) -> +handle_info(disconnected, Channel = #channel{state = #{state_name := disconnected}}) -> {ok, Channel}; handle_info(disconnected, Channel = #channel{conninfo = #{expiry_interval := ExpiryInterval}, @@ -864,7 +866,7 @@ disconnect(_Reason, Channel) -> {ok, Channel}. %% Terminate %%-------------------------------------------------------------------- -terminate(_, #channel{fsm_state = #{state_name := initialized}}) -> +terminate(_, #channel{state = #{state_name := initialized}}) -> ok; terminate(normal, #channel{conninfo = ConnInfo, clientinfo = ClientInfo}) -> ok = emqx_hooks:run('client.disconnected', [ClientInfo, normal, ConnInfo]); @@ -1140,10 +1142,10 @@ init_alias_maximum(#mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V5, inbound => emqx_mqtt_caps:get_caps(Zone, max_topic_alias, 0)}; init_alias_maximum(_ConnPkt, _ClientInfo) -> undefined. -ensure_disconnected(Channel = #channel{fsm_state = FsmState}) -> - Channel#channel{fsm_state = FsmState#{state_name := disconnected, - disconnected_at => erlang:system_time(second) - }}. +ensure_disconnected(Channel = #channel{state = FsmState}) -> + Channel#channel{state = FsmState#{state_name := disconnected, + disconnected_at => erlang:system_time(second) + }}. ensure_keepalive(#{'Server-Keep-Alive' := Interval}, Channel) -> ensure_keepalive_timer(Interval, Channel); From ddd68e600d51d8495a70597314a60feede9ac4e0 Mon Sep 17 00:00:00 2001 From: zhouzb Date: Sun, 29 Sep 2019 15:32:53 +0800 Subject: [PATCH 13/14] Fix missing cases --- src/emqx_connection.erl | 5 +++++ src/emqx_ws_connection.erl | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/emqx_connection.erl b/src/emqx_connection.erl index fbe0a8118..e577fd2cc 100644 --- a/src/emqx_connection.erl +++ b/src/emqx_connection.erl @@ -442,6 +442,11 @@ handle_incoming(Packet = ?PACKET(Type), SuccFun, State = #state{chan_state = Cha {ok, OutPackets, NChanState} -> NState = State#state{chan_state = NChanState}, handle_outgoing(OutPackets, SuccFun, NState); + {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} -> diff --git a/src/emqx_ws_connection.erl b/src/emqx_ws_connection.erl index fe6566364..7434fe932 100644 --- a/src/emqx_ws_connection.erl +++ b/src/emqx_ws_connection.erl @@ -279,7 +279,7 @@ websocket_info(Info, State = #state{chan_state = ChanState}) -> end. terminate(SockError, _Req, #state{chan_state = ChanState, - stop_reason = Reason}) -> + stop_reason = Reason}) -> ?LOG(debug, "Terminated for ~p, sockerror: ~p", [Reason, SockError]), emqx_channel:terminate(Reason, ChanState). From d004a5b68e141656fcb49c908bd2f34b15519f97 Mon Sep 17 00:00:00 2001 From: zhouzb Date: Sun, 29 Sep 2019 16:46:27 +0800 Subject: [PATCH 14/14] Rename 'client_id' field to 'clientid' --- include/emqx.hrl | 2 +- priv/emqx.schema | 6 +++--- test/emqx_SUITE.erl | 2 +- test/emqx_request_responser_SUITE.erl | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/emqx.hrl b/include/emqx.hrl index ee048f1fa..0a9bd646e 100644 --- a/include/emqx.hrl +++ b/include/emqx.hrl @@ -154,7 +154,7 @@ %% Banned %%-------------------------------------------------------------------- --type(banned_who() :: {client_id, binary()} +-type(banned_who() :: {clientid, binary()} | {username, binary()} | {ip_address, inet:ip_address()}). diff --git a/priv/emqx.schema b/priv/emqx.schema index 2c43ad577..5e408ef38 100644 --- a/priv/emqx.schema +++ b/priv/emqx.schema @@ -524,10 +524,10 @@ end}. Formatter = {emqx_logger_formatter, #{template => [time," [",level,"] ", - {client_id, + {clientid, [{peername, - [client_id,"@",peername," "], - [client_id, " "]}], + [clientid,"@",peername," "], + [clientid, " "]}], [{peername, [peername," "], []}]}, diff --git a/test/emqx_SUITE.erl b/test/emqx_SUITE.erl index 563683bf1..0a6ef4d0a 100644 --- a/test/emqx_SUITE.erl +++ b/test/emqx_SUITE.erl @@ -43,7 +43,7 @@ t_get_env(_) -> t_emqx_pubsub_api(_) -> emqx:start(), 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), ClientId = <<"myclient">>, Topic = <<"mytopic">>, diff --git a/test/emqx_request_responser_SUITE.erl b/test/emqx_request_responser_SUITE.erl index d603f2b96..7d66c7760 100644 --- a/test/emqx_request_responser_SUITE.erl +++ b/test/emqx_request_responser_SUITE.erl @@ -42,7 +42,7 @@ request_response_per_qos(QoS) -> RspTopic = <<"response_topic">>, {ok, Requester} = emqx_request_sender:start_link(RspTopic, QoS, [{proto_ver, v5}, - {client_id, <<"requester">>}, + {clientid, <<"requester">>}, {properties, #{ 'Request-Response-Information' => 1}}]), %% This is a square service Square = fun(_CorrData, ReqBin) -> @@ -51,7 +51,7 @@ request_response_per_qos(QoS) -> end, {ok, Responser} = emqx_request_handler:start_link(ReqTopic, QoS, Square, [{proto_ver, v5}, - {client_id, <<"responser">>} + {clientid, <<"responser">>} ]), ok = emqx_request_sender:send(Requester, ReqTopic, RspTopic, <<"corr-1">>, <<"2">>, QoS), receive