|
@@ -20,6 +20,7 @@
|
|
|
|
|
|
|
|
-include("src/mqttsn/include/emqx_sn.hrl").
|
|
-include("src/mqttsn/include/emqx_sn.hrl").
|
|
|
-include_lib("emqx/include/emqx.hrl").
|
|
-include_lib("emqx/include/emqx.hrl").
|
|
|
|
|
+-include_lib("emqx/include/types.hrl").
|
|
|
-include_lib("emqx/include/emqx_mqtt.hrl").
|
|
-include_lib("emqx/include/emqx_mqtt.hrl").
|
|
|
-include_lib("emqx/include/logger.hrl").
|
|
-include_lib("emqx/include/logger.hrl").
|
|
|
|
|
|
|
@@ -66,6 +67,10 @@
|
|
|
clientinfo_override :: map(),
|
|
clientinfo_override :: map(),
|
|
|
%% Connection State
|
|
%% Connection State
|
|
|
conn_state :: conn_state(),
|
|
conn_state :: conn_state(),
|
|
|
|
|
+ %% Inflight register message queue
|
|
|
|
|
+ register_inflight :: maybe(term()),
|
|
|
|
|
+ %% Topics list for awaiting to register to client
|
|
|
|
|
+ register_awaiting_queue :: list(),
|
|
|
%% Timer
|
|
%% Timer
|
|
|
timers :: #{atom() => disable | undefined | reference()},
|
|
timers :: #{atom() => disable | undefined | reference()},
|
|
|
%%% Takeover
|
|
%%% Takeover
|
|
@@ -88,10 +93,12 @@
|
|
|
-type(replies() :: reply() | [reply()]).
|
|
-type(replies() :: reply() | [reply()]).
|
|
|
|
|
|
|
|
-define(TIMER_TABLE, #{
|
|
-define(TIMER_TABLE, #{
|
|
|
- alive_timer => keepalive,
|
|
|
|
|
- retry_timer => retry_delivery,
|
|
|
|
|
- await_timer => expire_awaiting_rel,
|
|
|
|
|
- asleep_timer => expire_asleep
|
|
|
|
|
|
|
+ alive_timer => keepalive,
|
|
|
|
|
+ retry_timer => retry_delivery,
|
|
|
|
|
+ await_timer => expire_awaiting_rel,
|
|
|
|
|
+ expire_timer => expire_session,
|
|
|
|
|
+ asleep_timer => expire_asleep,
|
|
|
|
|
+ register_timer => retry_register
|
|
|
}).
|
|
}).
|
|
|
|
|
|
|
|
-define(DEFAULT_OVERRIDE,
|
|
-define(DEFAULT_OVERRIDE,
|
|
@@ -104,6 +111,9 @@
|
|
|
|
|
|
|
|
-define(NEG_QOS_CLIENT_ID, <<"NegQoS-Client">>).
|
|
-define(NEG_QOS_CLIENT_ID, <<"NegQoS-Client">>).
|
|
|
|
|
|
|
|
|
|
+-define(REGISTER_TIMEOUT, 10000). % 10s
|
|
|
|
|
+-define(DEFAULT_SESSION_EXPIRY, 7200000). %% 2h
|
|
|
|
|
+
|
|
|
%%--------------------------------------------------------------------
|
|
%%--------------------------------------------------------------------
|
|
|
%% Init the channel
|
|
%% Init the channel
|
|
|
%%--------------------------------------------------------------------
|
|
%%--------------------------------------------------------------------
|
|
@@ -148,6 +158,7 @@ init(ConnInfo = #{peername := {PeerHost, _},
|
|
|
, clientinfo = ClientInfo
|
|
, clientinfo = ClientInfo
|
|
|
, clientinfo_override = Override
|
|
, clientinfo_override = Override
|
|
|
, conn_state = idle
|
|
, conn_state = idle
|
|
|
|
|
+ , register_awaiting_queue = []
|
|
|
, timers = #{}
|
|
, timers = #{}
|
|
|
, takeover = false
|
|
, takeover = false
|
|
|
, resuming = false
|
|
, resuming = false
|
|
@@ -195,17 +206,24 @@ stats(#channel{session = Session})->
|
|
|
set_conn_state(ConnState, Channel) ->
|
|
set_conn_state(ConnState, Channel) ->
|
|
|
Channel#channel{conn_state = ConnState}.
|
|
Channel#channel{conn_state = ConnState}.
|
|
|
|
|
|
|
|
-enrich_conninfo(?SN_CONNECT_MSG(_Flags, _ProtoId, Duration, ClientId),
|
|
|
|
|
|
|
+enrich_conninfo(?SN_CONNECT_MSG(Flags, _ProtoId, Duration, ClientId),
|
|
|
Channel = #channel{conninfo = ConnInfo}) ->
|
|
Channel = #channel{conninfo = ConnInfo}) ->
|
|
|
|
|
+ CleanStart = Flags#mqtt_sn_flags.clean_start,
|
|
|
NConnInfo = ConnInfo#{ clientid => ClientId
|
|
NConnInfo = ConnInfo#{ clientid => ClientId
|
|
|
, proto_name => <<"MQTT-SN">>
|
|
, proto_name => <<"MQTT-SN">>
|
|
|
, proto_ver => <<"1.2">>
|
|
, proto_ver => <<"1.2">>
|
|
|
- , clean_start => true
|
|
|
|
|
|
|
+ , clean_start => CleanStart
|
|
|
, keepalive => Duration
|
|
, keepalive => Duration
|
|
|
- , expiry_interval => 0
|
|
|
|
|
|
|
+ , expiry_interval => expiry_interval(Flags)
|
|
|
},
|
|
},
|
|
|
{ok, Channel#channel{conninfo = NConnInfo}}.
|
|
{ok, Channel#channel{conninfo = NConnInfo}}.
|
|
|
|
|
|
|
|
|
|
+expiry_interval(#mqtt_sn_flags{clean_start = false}) ->
|
|
|
|
|
+ %% TODO: make it configurable
|
|
|
|
|
+ ?DEFAULT_SESSION_EXPIRY;
|
|
|
|
|
+expiry_interval(#mqtt_sn_flags{clean_start = true}) ->
|
|
|
|
|
+ 0.
|
|
|
|
|
+
|
|
|
run_conn_hooks(Packet, Channel = #channel{ctx = Ctx,
|
|
run_conn_hooks(Packet, Channel = #channel{ctx = Ctx,
|
|
|
conninfo = ConnInfo}) ->
|
|
conninfo = ConnInfo}) ->
|
|
|
%% XXX: Assign headers of Packet to ConnProps
|
|
%% XXX: Assign headers of Packet to ConnProps
|
|
@@ -308,13 +326,13 @@ ensure_connected(Channel = #channel{
|
|
|
|
|
|
|
|
process_connect(Channel = #channel{
|
|
process_connect(Channel = #channel{
|
|
|
ctx = Ctx,
|
|
ctx = Ctx,
|
|
|
- conninfo = ConnInfo,
|
|
|
|
|
|
|
+ conninfo = ConnInfo = #{clean_start := CleanStart},
|
|
|
clientinfo = ClientInfo
|
|
clientinfo = ClientInfo
|
|
|
}) ->
|
|
}) ->
|
|
|
SessFun = fun(_,_) -> emqx_session:init(#{max_inflight => 1}) end,
|
|
SessFun = fun(_,_) -> emqx_session:init(#{max_inflight => 1}) end,
|
|
|
case emqx_gateway_ctx:open_session(
|
|
case emqx_gateway_ctx:open_session(
|
|
|
Ctx,
|
|
Ctx,
|
|
|
- true,
|
|
|
|
|
|
|
+ CleanStart,
|
|
|
ClientInfo,
|
|
ClientInfo,
|
|
|
ConnInfo,
|
|
ConnInfo,
|
|
|
SessFun
|
|
SessFun
|
|
@@ -327,7 +345,7 @@ process_connect(Channel = #channel{
|
|
|
?SLOG(error, #{ msg => "failed_to_open_session"
|
|
?SLOG(error, #{ msg => "failed_to_open_session"
|
|
|
, reason => Reason
|
|
, reason => Reason
|
|
|
}),
|
|
}),
|
|
|
- handle_out(connack, ?SN_RC_FAILED_SESSION, Channel)
|
|
|
|
|
|
|
+ handle_out(connack, ?SN_RC2_FAILED_SESSION, Channel)
|
|
|
end.
|
|
end.
|
|
|
|
|
|
|
|
%%--------------------------------------------------------------------
|
|
%%--------------------------------------------------------------------
|
|
@@ -501,6 +519,40 @@ handle_in(?SN_REGISTER_MSG(_TopicId, MsgId, TopicName),
|
|
|
{ok, {outgoing, AckPacket}, Channel}
|
|
{ok, {outgoing, AckPacket}, Channel}
|
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
|
|
+handle_in(?SN_REGACK_MSG(TopicId, MsgId, ?SN_RC_ACCEPTED),
|
|
|
|
|
+ Channel = #channel{register_inflight = Inflight}) ->
|
|
|
|
|
+ case Inflight of
|
|
|
|
|
+ {TopicId, _, TopicName} ->
|
|
|
|
|
+ ?SLOG(debug, #{ msg => "register_topic_name_to_client_succesfully"
|
|
|
|
|
+ , topic_id => TopicId
|
|
|
|
|
+ , topic_name => TopicName
|
|
|
|
|
+ }),
|
|
|
|
|
+ NChannel = cancel_timer(
|
|
|
|
|
+ register_timer,
|
|
|
|
|
+ Channel#channel{register_inflight = undefined}),
|
|
|
|
|
+ send_next_register_or_replay_publish(TopicName, NChannel);
|
|
|
|
|
+ _ ->
|
|
|
|
|
+ ?SLOG(error, #{ msg => "unexpected_regack_msg"
|
|
|
|
|
+ , msg_id => MsgId
|
|
|
|
|
+ , topic_id => TopicId
|
|
|
|
|
+ , current_inflight => Inflight
|
|
|
|
|
+ }),
|
|
|
|
|
+ {ok, Channel}
|
|
|
|
|
+ end;
|
|
|
|
|
+
|
|
|
|
|
+handle_in(?SN_REGACK_MSG(_TopicId, _MsgId, Reason), Channel) ->
|
|
|
|
|
+ case Reason of
|
|
|
|
|
+ ?SN_RC_CONGESTION ->
|
|
|
|
|
+ %% TODO: a or b?
|
|
|
|
|
+ %% a. waiting for next register timer
|
|
|
|
|
+ %% b. re-new the re-transmit timer
|
|
|
|
|
+ {ok, Channel};
|
|
|
|
|
+ _ ->
|
|
|
|
|
+ %% disconnect this client, if the reason is
|
|
|
|
|
+ %% ?SN_RC_NOT_SUPPORTED, ?SN_RC_INVALID_TOPIC_ID, etc.
|
|
|
|
|
+ handle_out(disconnect, ?SN_RC_NOT_SUPPORTED, Channel)
|
|
|
|
|
+ end;
|
|
|
|
|
+
|
|
|
handle_in(PubPkt = ?SN_PUBLISH_MSG(_Flags, TopicId0, MsgId, _Data), Channel) ->
|
|
handle_in(PubPkt = ?SN_PUBLISH_MSG(_Flags, TopicId0, MsgId, _Data), Channel) ->
|
|
|
TopicId = case is_integer(TopicId0) of
|
|
TopicId = case is_integer(TopicId0) of
|
|
|
true -> TopicId0;
|
|
true -> TopicId0;
|
|
@@ -560,8 +612,7 @@ handle_in(?SN_PUBACK_MSG(TopicId, MsgId, ReturnCode),
|
|
|
%% involving the predefined topic name in register to
|
|
%% involving the predefined topic name in register to
|
|
|
%% enhance the gateway's robustness even inconsistent
|
|
%% enhance the gateway's robustness even inconsistent
|
|
|
%% with MQTT-SN channels
|
|
%% with MQTT-SN channels
|
|
|
- RegPkt = ?SN_REGISTER_MSG(TopicId, MsgId, TopicName),
|
|
|
|
|
- {ok, {outgoing, RegPkt}, Channel}
|
|
|
|
|
|
|
+ handle_out(register, {TopicId, MsgId, TopicName}, Channel)
|
|
|
end;
|
|
end;
|
|
|
_ ->
|
|
_ ->
|
|
|
?SLOG(error, #{ msg => "cannt_handle_PUBACK"
|
|
?SLOG(error, #{ msg => "cannt_handle_PUBACK"
|
|
@@ -687,12 +738,17 @@ handle_in(?SN_PINGRESP_MSG(), Channel) ->
|
|
|
{ok, Channel};
|
|
{ok, Channel};
|
|
|
|
|
|
|
|
handle_in(?SN_DISCONNECT_MSG(Duration), Channel) ->
|
|
handle_in(?SN_DISCONNECT_MSG(Duration), Channel) ->
|
|
|
- AckPkt = ?SN_DISCONNECT_MSG(undefined),
|
|
|
|
|
case Duration of
|
|
case Duration of
|
|
|
undefined ->
|
|
undefined ->
|
|
|
- shutdown(normal, AckPkt, Channel);
|
|
|
|
|
|
|
+ handle_out(disconnect, normal, Channel);
|
|
|
_ ->
|
|
_ ->
|
|
|
- %% TODO: asleep mechnisa
|
|
|
|
|
|
|
+ %% A DISCONNECT message with a Duration field is sent by a client
|
|
|
|
|
+ %% when it wants to go to the “asleep” state. The receipt of this
|
|
|
|
|
+ %% message is also acknowledged by the gateway by means of a
|
|
|
|
|
+ %% DISCONNECT message (without a duration field) [5.4.21]
|
|
|
|
|
+ %%
|
|
|
|
|
+ %% TODO: asleep mechanism
|
|
|
|
|
+ AckPkt = ?SN_DISCONNECT_MSG(undefined),
|
|
|
{ok, {outgoing, AckPkt}, asleep(Duration, Channel)}
|
|
{ok, {outgoing, AckPkt}, asleep(Duration, Channel)}
|
|
|
end;
|
|
end;
|
|
|
|
|
|
|
@@ -729,6 +785,31 @@ after_message_acked(ClientInfo, Msg, #channel{ctx = Ctx}) ->
|
|
|
outgoing_and_update(Pkt) ->
|
|
outgoing_and_update(Pkt) ->
|
|
|
[{outgoing, Pkt}, {event, update}].
|
|
[{outgoing, Pkt}, {event, update}].
|
|
|
|
|
|
|
|
|
|
+send_next_register_or_replay_publish(TopicName,
|
|
|
|
|
+ Channel = #channel{
|
|
|
|
|
+ session = Session,
|
|
|
|
|
+ register_awaiting_queue = []}) ->
|
|
|
|
|
+ case emqx_inflight:to_list(emqx_session:info(inflight, Session)) of
|
|
|
|
|
+ [] -> {ok, Channel};
|
|
|
|
|
+ [{PktId, {inflight_data, _, Msg, _}}] ->
|
|
|
|
|
+ case TopicName =:= emqx_message:topic(Msg) of
|
|
|
|
|
+ false ->
|
|
|
|
|
+ ?SLOG(warning, #{ msg => "replay_inflight_message_failed"
|
|
|
|
|
+ , acked_topic_name => TopicName
|
|
|
|
|
+ , inflight_message => Msg
|
|
|
|
|
+ }),
|
|
|
|
|
+ {ok, Channel};
|
|
|
|
|
+ true ->
|
|
|
|
|
+ NMsg = emqx_message:set_flag(dup, true, Msg),
|
|
|
|
|
+ handle_out(publish, {PktId, NMsg}, Channel)
|
|
|
|
|
+ end
|
|
|
|
|
+ end;
|
|
|
|
|
+send_next_register_or_replay_publish(_TopicName,
|
|
|
|
|
+ Channel = #channel{
|
|
|
|
|
+ register_awaiting_queue = RAQueue}) ->
|
|
|
|
|
+ [RegisterReq | NRAQueue] = RAQueue,
|
|
|
|
|
+ handle_out(register, RegisterReq, Channel#channel{register_awaiting_queue = NRAQueue}).
|
|
|
|
|
+
|
|
|
%%--------------------------------------------------------------------
|
|
%%--------------------------------------------------------------------
|
|
|
%% Handle Publish
|
|
%% Handle Publish
|
|
|
|
|
|
|
@@ -786,7 +867,7 @@ check_pub_authz({TopicName, _Flags, _Data},
|
|
|
#channel{ctx = Ctx, clientinfo = ClientInfo}) ->
|
|
#channel{ctx = Ctx, clientinfo = ClientInfo}) ->
|
|
|
case emqx_gateway_ctx:authorize(Ctx, ClientInfo, publish, TopicName) of
|
|
case emqx_gateway_ctx:authorize(Ctx, ClientInfo, publish, TopicName) of
|
|
|
allow -> ok;
|
|
allow -> ok;
|
|
|
- deny -> {error, ?SN_RC_NOT_AUTHORIZE}
|
|
|
|
|
|
|
+ deny -> {error, ?SN_RC2_NOT_AUTHORIZE}
|
|
|
end.
|
|
end.
|
|
|
|
|
|
|
|
convert_pub_to_msg({TopicName, Flags, Data},
|
|
convert_pub_to_msg({TopicName, Flags, Data},
|
|
@@ -857,7 +938,7 @@ preproc_subs_type(?SN_SUBSCRIBE_MSG_TYPE(?SN_NORMAL_TOPIC,
|
|
|
%% and returns it within a SUBACK message
|
|
%% and returns it within a SUBACK message
|
|
|
case emqx_sn_registry:register_topic(Registry, ClientId, TopicName) of
|
|
case emqx_sn_registry:register_topic(Registry, ClientId, TopicName) of
|
|
|
{error, too_large} ->
|
|
{error, too_large} ->
|
|
|
- {error, ?SN_EXCEED_LIMITATION};
|
|
|
|
|
|
|
+ {error, ?SN_RC2_EXCEED_LIMITATION};
|
|
|
{error, wildcard_topic} ->
|
|
{error, wildcard_topic} ->
|
|
|
%% If the client subscribes to a topic name which contains a
|
|
%% If the client subscribes to a topic name which contains a
|
|
|
%% wildcard character, the returning SUBACK message will contain
|
|
%% wildcard character, the returning SUBACK message will contain
|
|
@@ -904,7 +985,7 @@ check_subscribe_authz({_TopicId, TopicName, _QoS},
|
|
|
allow ->
|
|
allow ->
|
|
|
{ok, Channel};
|
|
{ok, Channel};
|
|
|
_ ->
|
|
_ ->
|
|
|
- {error, ?SN_RC_NOT_AUTHORIZE}
|
|
|
|
|
|
|
+ {error, ?SN_RC2_NOT_AUTHORIZE}
|
|
|
end.
|
|
end.
|
|
|
|
|
|
|
|
run_client_subs_hook({TopicId, TopicName, QoS},
|
|
run_client_subs_hook({TopicId, TopicName, QoS},
|
|
@@ -920,7 +1001,7 @@ run_client_subs_hook({TopicId, TopicName, QoS},
|
|
|
, topic_name => TopicName
|
|
, topic_name => TopicName
|
|
|
, reason => "'client.subscribe' filtered it"
|
|
, reason => "'client.subscribe' filtered it"
|
|
|
}),
|
|
}),
|
|
|
- {error, ?SN_EXCEED_LIMITATION};
|
|
|
|
|
|
|
+ {error, ?SN_RC2_EXCEED_LIMITATION};
|
|
|
[{NTopicName, NSubOpts}|_] ->
|
|
[{NTopicName, NSubOpts}|_] ->
|
|
|
{ok, {TopicId, NTopicName, NSubOpts}, Channel}
|
|
{ok, {TopicId, NTopicName, NSubOpts}, Channel}
|
|
|
end.
|
|
end.
|
|
@@ -941,7 +1022,7 @@ do_subscribe({TopicId, TopicName, SubOpts},
|
|
|
, topic_name => TopicName
|
|
, topic_name => TopicName
|
|
|
, reason => emqx_reason_codes:text(?RC_QUOTA_EXCEEDED)
|
|
, reason => emqx_reason_codes:text(?RC_QUOTA_EXCEEDED)
|
|
|
}),
|
|
}),
|
|
|
- {error, ?SN_EXCEED_LIMITATION}
|
|
|
|
|
|
|
+ {error, ?SN_RC2_EXCEED_LIMITATION}
|
|
|
end.
|
|
end.
|
|
|
|
|
|
|
|
%%--------------------------------------------------------------------
|
|
%%--------------------------------------------------------------------
|
|
@@ -1080,6 +1161,43 @@ handle_out(pubrel, MsgId, Channel) ->
|
|
|
handle_out(pubcomp, MsgId, Channel) ->
|
|
handle_out(pubcomp, MsgId, Channel) ->
|
|
|
{ok, {outgoing, ?SN_PUBREC_MSG(?SN_PUBCOMP, MsgId)}, Channel};
|
|
{ok, {outgoing, ?SN_PUBREC_MSG(?SN_PUBCOMP, MsgId)}, Channel};
|
|
|
|
|
|
|
|
|
|
+handle_out(register, {TopicId, MsgId, TopicName},
|
|
|
|
|
+ Channel = #channel{register_inflight = undefined}) ->
|
|
|
|
|
+ Outgoing = {outgoing, ?SN_REGISTER_MSG(TopicId, MsgId, TopicName)},
|
|
|
|
|
+ NChannel = Channel#channel{register_inflight = {TopicId, MsgId, TopicName}},
|
|
|
|
|
+ {ok, Outgoing, ensure_timer(register_timer, ?REGISTER_TIMEOUT, NChannel)};
|
|
|
|
|
+
|
|
|
|
|
+handle_out(register, {TopicId, MsgId, TopicName},
|
|
|
|
|
+ Channel = #channel{register_inflight = Inflight,
|
|
|
|
|
+ register_awaiting_queue = RAQueue}) ->
|
|
|
|
|
+ case Inflight of
|
|
|
|
|
+ {_, _, TopicName} ->
|
|
|
|
|
+ ?SLOG(debug, #{ msg => "ingore_handle_out_register"
|
|
|
|
|
+ , requested_register_msg =>
|
|
|
|
|
+ #{ topic_id => TopicId
|
|
|
|
|
+ , msg_id => MsgId
|
|
|
|
|
+ , topic_name => TopicName
|
|
|
|
|
+ }
|
|
|
|
|
+ }),
|
|
|
|
|
+ {ok, Channel};
|
|
|
|
|
+ {InflightTopicId, InflightMsgId, InflightTopicName} ->
|
|
|
|
|
+ NRAQueue = RAQueue ++ [{TopicId, MsgId, TopicName}],
|
|
|
|
|
+ ?SLOG(debug, #{ msg => "put_register_msg_into_awaiting_queue"
|
|
|
|
|
+ , inflight_register_msg =>
|
|
|
|
|
+ #{ topic_id => InflightTopicId
|
|
|
|
|
+ , msg_id => InflightMsgId
|
|
|
|
|
+ , topic_name => InflightTopicName
|
|
|
|
|
+ }
|
|
|
|
|
+ , queued_register_msg =>
|
|
|
|
|
+ #{ topic_id => TopicId
|
|
|
|
|
+ , msg_id => MsgId
|
|
|
|
|
+ , topic_name => TopicName
|
|
|
|
|
+ }
|
|
|
|
|
+ , register_awaiting_queue_size => length(NRAQueue)
|
|
|
|
|
+ }),
|
|
|
|
|
+ {ok, Channel#channel{register_awaiting_queue = NRAQueue}}
|
|
|
|
|
+ end;
|
|
|
|
|
+
|
|
|
handle_out(disconnect, RC, Channel) ->
|
|
handle_out(disconnect, RC, Channel) ->
|
|
|
DisPkt = ?SN_DISCONNECT_MSG(undefined),
|
|
DisPkt = ?SN_DISCONNECT_MSG(undefined),
|
|
|
{ok, [{outgoing, DisPkt}, {close, RC}], Channel}.
|
|
{ok, [{outgoing, DisPkt}, {close, RC}], Channel}.
|
|
@@ -1196,7 +1314,7 @@ handle_call({subscribe, Topic, SubOpts}, _From, Channel) ->
|
|
|
Topic, SubOpts}, Channel) of
|
|
Topic, SubOpts}, Channel) of
|
|
|
{ok, {_, NTopicName, NSubOpts}, NChannel} ->
|
|
{ok, {_, NTopicName, NSubOpts}, NChannel} ->
|
|
|
reply({ok, {NTopicName, NSubOpts}}, NChannel);
|
|
reply({ok, {NTopicName, NSubOpts}}, NChannel);
|
|
|
- {error, ?SN_EXCEED_LIMITATION} ->
|
|
|
|
|
|
|
+ {error, ?SN_RC2_EXCEED_LIMITATION} ->
|
|
|
reply({error, exceed_limitation}, Channel)
|
|
reply({error, exceed_limitation}, Channel)
|
|
|
end;
|
|
end;
|
|
|
_ ->
|
|
_ ->
|
|
@@ -1223,17 +1341,21 @@ handle_call(kick, _From, Channel) ->
|
|
|
handle_call(discard, _From, Channel) ->
|
|
handle_call(discard, _From, Channel) ->
|
|
|
shutdown_and_reply(discarded, ok, Channel);
|
|
shutdown_and_reply(discarded, ok, Channel);
|
|
|
|
|
|
|
|
-%% XXX: No Session Takeover
|
|
|
|
|
-%handle_call({takeover, 'begin'}, _From, Channel = #channel{session = Session}) ->
|
|
|
|
|
-% reply(Session, Channel#channel{takeover = true});
|
|
|
|
|
-%
|
|
|
|
|
-%handle_call({takeover, 'end'}, _From, Channel = #channel{session = Session,
|
|
|
|
|
-% pendings = Pendings}) ->
|
|
|
|
|
-% ok = emqx_session:takeover(Session),
|
|
|
|
|
-% %% TODO: Should not drain deliver here (side effect)
|
|
|
|
|
-% Delivers = emqx_misc:drain_deliver(),
|
|
|
|
|
-% AllPendings = lists:append(Delivers, Pendings),
|
|
|
|
|
-% shutdown_and_reply(takenover, AllPendings, Channel);
|
|
|
|
|
|
|
+handle_call({takeover, 'begin'}, _From, Channel = #channel{session = Session}) ->
|
|
|
|
|
+ %% In MQTT-SN the meaning of a “clean session” is extended to the Will
|
|
|
|
|
+ %% feature, i.e. not only the subscriptions are persistent, but also the
|
|
|
|
|
+ %% Will topic and the Will message. [6.3]
|
|
|
|
|
+ %%
|
|
|
|
|
+ %% FIXME: We need to reply WillMsg and Session
|
|
|
|
|
+ reply(Session, Channel#channel{takeover = true});
|
|
|
|
|
+
|
|
|
|
|
+handle_call({takeover, 'end'}, _From, Channel = #channel{session = Session,
|
|
|
|
|
+ pendings = Pendings}) ->
|
|
|
|
|
+ ok = emqx_session:takeover(Session),
|
|
|
|
|
+ %% TODO: Should not drain deliver here (side effect)
|
|
|
|
|
+ Delivers = emqx_misc:drain_deliver(),
|
|
|
|
|
+ AllPendings = lists:append(Delivers, Pendings),
|
|
|
|
|
+ shutdown_and_reply(takenover, AllPendings, Channel);
|
|
|
|
|
|
|
|
%handle_call(list_authz_cache, _From, Channel) ->
|
|
%handle_call(list_authz_cache, _From, Channel) ->
|
|
|
% {reply, emqx_authz_cache:list_authz_cache(), Channel};
|
|
% {reply, emqx_authz_cache:list_authz_cache(), Channel};
|
|
@@ -1282,8 +1404,10 @@ handle_info({sock_closed, Reason},
|
|
|
%emqx_zone:enable_flapping_detect(Zone)
|
|
%emqx_zone:enable_flapping_detect(Zone)
|
|
|
% andalso emqx_flapping:detect(ClientInfo),
|
|
% andalso emqx_flapping:detect(ClientInfo),
|
|
|
NChannel = ensure_disconnected(Reason, mabye_publish_will_msg(Channel)),
|
|
NChannel = ensure_disconnected(Reason, mabye_publish_will_msg(Channel)),
|
|
|
- %% XXX: Session keepper detect here
|
|
|
|
|
- shutdown(Reason, NChannel);
|
|
|
|
|
|
|
+ case maybe_shutdown(Reason, NChannel) of
|
|
|
|
|
+ {ok, NChannel1} -> {ok, {event, disconnected}, NChannel1};
|
|
|
|
|
+ Shutdown -> Shutdown
|
|
|
|
|
+ end;
|
|
|
|
|
|
|
|
handle_info({sock_closed, Reason},
|
|
handle_info({sock_closed, Reason},
|
|
|
Channel = #channel{conn_state = disconnected}) ->
|
|
Channel = #channel{conn_state = disconnected}) ->
|
|
@@ -1305,6 +1429,14 @@ handle_info(Info, Channel) ->
|
|
|
}),
|
|
}),
|
|
|
{ok, Channel}.
|
|
{ok, Channel}.
|
|
|
|
|
|
|
|
|
|
+maybe_shutdown(Reason, Channel = #channel{conninfo = ConnInfo}) ->
|
|
|
|
|
+ case maps:get(expiry_interval, ConnInfo) of
|
|
|
|
|
+ ?UINT_MAX -> {ok, Channel};
|
|
|
|
|
+ I when I > 0 ->
|
|
|
|
|
+ {ok, ensure_timer(expire_timer, I, Channel)};
|
|
|
|
|
+ _ -> shutdown(Reason, Channel)
|
|
|
|
|
+ end.
|
|
|
|
|
+
|
|
|
%%--------------------------------------------------------------------
|
|
%%--------------------------------------------------------------------
|
|
|
%% Ensure disconnected
|
|
%% Ensure disconnected
|
|
|
|
|
|
|
@@ -1420,7 +1552,7 @@ handle_timeout(_TRef, {keepalive, StatVal},
|
|
|
NChannel = Channel#channel{keepalive = NKeepalive},
|
|
NChannel = Channel#channel{keepalive = NKeepalive},
|
|
|
{ok, reset_timer(alive_timer, NChannel)};
|
|
{ok, reset_timer(alive_timer, NChannel)};
|
|
|
{error, timeout} ->
|
|
{error, timeout} ->
|
|
|
- handle_out(disconnect, ?RC_KEEP_ALIVE_TIMEOUT, Channel)
|
|
|
|
|
|
|
+ handle_out(disconnect, ?SN_RC2_KEEPALIVE_TIMEOUT, Channel)
|
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
handle_timeout(_TRef, retry_delivery,
|
|
handle_timeout(_TRef, retry_delivery,
|
|
@@ -1436,6 +1568,7 @@ handle_timeout(_TRef, retry_delivery,
|
|
|
{ok, clean_timer(retry_timer, Channel#channel{session = NSession})};
|
|
{ok, clean_timer(retry_timer, Channel#channel{session = NSession})};
|
|
|
{ok, Publishes, Timeout, NSession} ->
|
|
{ok, Publishes, Timeout, NSession} ->
|
|
|
NChannel = Channel#channel{session = NSession},
|
|
NChannel = Channel#channel{session = NSession},
|
|
|
|
|
+ %% XXX: These replay messages should awaiting register acked?
|
|
|
handle_out(publish, Publishes, reset_timer(retry_timer, Timeout, NChannel))
|
|
handle_out(publish, Publishes, reset_timer(retry_timer, Timeout, NChannel))
|
|
|
end;
|
|
end;
|
|
|
|
|
|
|
@@ -1454,6 +1587,9 @@ handle_timeout(_TRef, expire_awaiting_rel,
|
|
|
{ok, reset_timer(await_timer, Timeout, Channel#channel{session = NSession})}
|
|
{ok, reset_timer(await_timer, Timeout, Channel#channel{session = NSession})}
|
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
|
|
+handle_timeout(_TRef, expire_session, Channel) ->
|
|
|
|
|
+ shutdown(expired, Channel);
|
|
|
|
|
+
|
|
|
handle_timeout(_TRef, expire_asleep, Channel) ->
|
|
handle_timeout(_TRef, expire_asleep, Channel) ->
|
|
|
shutdown(asleep_timeout, Channel);
|
|
shutdown(asleep_timeout, Channel);
|
|
|
|
|
|
|
@@ -1563,7 +1699,8 @@ returncode_name(?SN_RC_ACCEPTED) -> accepted;
|
|
|
returncode_name(?SN_RC_CONGESTION) -> rejected_congestion;
|
|
returncode_name(?SN_RC_CONGESTION) -> rejected_congestion;
|
|
|
returncode_name(?SN_RC_INVALID_TOPIC_ID) -> rejected_invaild_topic_id;
|
|
returncode_name(?SN_RC_INVALID_TOPIC_ID) -> rejected_invaild_topic_id;
|
|
|
returncode_name(?SN_RC_NOT_SUPPORTED) -> rejected_not_supported;
|
|
returncode_name(?SN_RC_NOT_SUPPORTED) -> rejected_not_supported;
|
|
|
-returncode_name(?SN_RC_NOT_AUTHORIZE) -> rejected_not_authorize;
|
|
|
|
|
-returncode_name(?SN_RC_FAILED_SESSION) -> rejected_failed_open_session;
|
|
|
|
|
-returncode_name(?SN_EXCEED_LIMITATION) -> rejected_exceed_limitation;
|
|
|
|
|
|
|
+returncode_name(?SN_RC2_NOT_AUTHORIZE) -> rejected_not_authorize;
|
|
|
|
|
+returncode_name(?SN_RC2_FAILED_SESSION) -> rejected_failed_open_session;
|
|
|
|
|
+returncode_name(?SN_RC2_KEEPALIVE_TIMEOUT) -> rejected_keepalive_timeout;
|
|
|
|
|
+returncode_name(?SN_RC2_EXCEED_LIMITATION) -> rejected_exceed_limitation;
|
|
|
returncode_name(_) -> accepted.
|
|
returncode_name(_) -> accepted.
|