Fix bug in topic alias maximum (#2074)
* Fix bug in topic alias maximum
This commit is contained in:
parent
3fec9cdf0a
commit
31bf01fd7a
|
@ -64,7 +64,8 @@
|
||||||
send_stats,
|
send_stats,
|
||||||
connected,
|
connected,
|
||||||
connected_at,
|
connected_at,
|
||||||
ignore_loop
|
ignore_loop,
|
||||||
|
topic_alias_maximum
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type(state() :: #pstate{}).
|
-type(state() :: #pstate{}).
|
||||||
|
@ -84,28 +85,29 @@
|
||||||
-spec(init(map(), list()) -> state()).
|
-spec(init(map(), list()) -> state()).
|
||||||
init(#{peername := Peername, peercert := Peercert, sendfun := SendFun}, Options) ->
|
init(#{peername := Peername, peercert := Peercert, sendfun := SendFun}, Options) ->
|
||||||
Zone = proplists:get_value(zone, Options),
|
Zone = proplists:get_value(zone, Options),
|
||||||
#pstate{zone = Zone,
|
#pstate{zone = Zone,
|
||||||
sendfun = SendFun,
|
sendfun = SendFun,
|
||||||
peername = Peername,
|
peername = Peername,
|
||||||
peercert = Peercert,
|
peercert = Peercert,
|
||||||
proto_ver = ?MQTT_PROTO_V4,
|
proto_ver = ?MQTT_PROTO_V4,
|
||||||
proto_name = <<"MQTT">>,
|
proto_name = <<"MQTT">>,
|
||||||
client_id = <<>>,
|
client_id = <<>>,
|
||||||
is_assigned = false,
|
is_assigned = false,
|
||||||
conn_pid = self(),
|
conn_pid = self(),
|
||||||
username = init_username(Peercert, Options),
|
username = init_username(Peercert, Options),
|
||||||
is_super = false,
|
is_super = false,
|
||||||
clean_start = false,
|
clean_start = false,
|
||||||
topic_aliases = #{},
|
topic_aliases = #{},
|
||||||
packet_size = emqx_zone:get_env(Zone, max_packet_size),
|
packet_size = emqx_zone:get_env(Zone, max_packet_size),
|
||||||
mountpoint = emqx_zone:get_env(Zone, mountpoint),
|
mountpoint = emqx_zone:get_env(Zone, mountpoint),
|
||||||
is_bridge = false,
|
is_bridge = false,
|
||||||
enable_ban = emqx_zone:get_env(Zone, enable_ban, false),
|
enable_ban = emqx_zone:get_env(Zone, enable_ban, false),
|
||||||
enable_acl = emqx_zone:get_env(Zone, enable_acl),
|
enable_acl = emqx_zone:get_env(Zone, enable_acl),
|
||||||
recv_stats = #{msg => 0, pkt => 0},
|
recv_stats = #{msg => 0, pkt => 0},
|
||||||
send_stats = #{msg => 0, pkt => 0},
|
send_stats = #{msg => 0, pkt => 0},
|
||||||
connected = false,
|
connected = false,
|
||||||
ignore_loop = emqx_config:get_env(mqtt_ignore_loop_deliver, false)}.
|
ignore_loop = emqx_config:get_env(mqtt_ignore_loop_deliver, false),
|
||||||
|
topic_alias_maximum = #{to_client => 0, from_client => 0}}.
|
||||||
|
|
||||||
init_username(Peercert, Options) ->
|
init_username(Peercert, Options) ->
|
||||||
case proplists:get_value(peer_cert_as_username, Options) of
|
case proplists:get_value(peer_cert_as_username, Options) of
|
||||||
|
@ -212,12 +214,16 @@ received(?PACKET(?CONNECT), PState = #pstate{connected = true}) ->
|
||||||
{error, proto_unexpected_connect, PState};
|
{error, proto_unexpected_connect, PState};
|
||||||
|
|
||||||
received(Packet = ?PACKET(Type), PState) ->
|
received(Packet = ?PACKET(Type), PState) ->
|
||||||
PState1 = set_protover(Packet, PState),
|
PState1 = set_protover(Packet, PState),
|
||||||
trace(recv, Packet),
|
trace(recv, Packet),
|
||||||
try emqx_packet:validate(Packet) of
|
try emqx_packet:validate(Packet) of
|
||||||
true ->
|
true ->
|
||||||
{Packet1, PState2} = preprocess_properties(Packet, PState1),
|
case preprocess_properties(Packet, PState1) of
|
||||||
process_packet(Packet1, inc_stats(recv, Type, PState2))
|
{error, ReasonCode} ->
|
||||||
|
{error, ReasonCode, PState1};
|
||||||
|
{Packet1, PState2} ->
|
||||||
|
process_packet(Packet1, inc_stats(recv, Type, PState2))
|
||||||
|
end
|
||||||
catch
|
catch
|
||||||
error : protocol_error ->
|
error : protocol_error ->
|
||||||
deliver({disconnect, ?RC_PROTOCOL_ERROR}, PState1),
|
deliver({disconnect, ?RC_PROTOCOL_ERROR}, PState1),
|
||||||
|
@ -242,6 +248,13 @@ received(Packet = ?PACKET(Type), PState) ->
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Preprocess MQTT Properties
|
%% Preprocess MQTT Properties
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
preprocess_properties(Packet = #mqtt_packet{
|
||||||
|
variable = #mqtt_packet_connect{
|
||||||
|
properties = #{'Topic-Alias-Maximum' := ToClient}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PState = #pstate{topic_alias_maximum = TopicAliasMaximum}) ->
|
||||||
|
{Packet, PState#pstate{topic_alias_maximum = TopicAliasMaximum#{to_client => ToClient}}};
|
||||||
|
|
||||||
%% Subscription Identifier
|
%% Subscription Identifier
|
||||||
preprocess_properties(Packet = #mqtt_packet{
|
preprocess_properties(Packet = #mqtt_packet{
|
||||||
|
@ -255,22 +268,46 @@ preprocess_properties(Packet = #mqtt_packet{
|
||||||
{Packet#mqtt_packet{variable = Subscribe#mqtt_packet_subscribe{topic_filters = TopicFilters1}}, PState};
|
{Packet#mqtt_packet{variable = Subscribe#mqtt_packet_subscribe{topic_filters = TopicFilters1}}, PState};
|
||||||
|
|
||||||
%% Topic Alias Mapping
|
%% Topic Alias Mapping
|
||||||
|
preprocess_properties(#mqtt_packet{
|
||||||
|
variable = #mqtt_packet_publish{
|
||||||
|
properties = #{'Topic-Alias' := 0}}
|
||||||
|
},
|
||||||
|
PState) ->
|
||||||
|
deliver({disconnect, ?RC_TOPIC_ALIAS_INVALID}, PState),
|
||||||
|
{error, ?RC_TOPIC_ALIAS_INVALID};
|
||||||
|
|
||||||
preprocess_properties(Packet = #mqtt_packet{
|
preprocess_properties(Packet = #mqtt_packet{
|
||||||
variable = Publish = #mqtt_packet_publish{
|
variable = Publish = #mqtt_packet_publish{
|
||||||
topic_name = <<>>,
|
topic_name = <<>>,
|
||||||
properties = #{'Topic-Alias' := AliasId}}
|
properties = #{'Topic-Alias' := AliasId}}
|
||||||
},
|
},
|
||||||
PState = #pstate{proto_ver = ?MQTT_PROTO_V5, topic_aliases = Aliases}) ->
|
PState = #pstate{proto_ver = ?MQTT_PROTO_V5,
|
||||||
{Packet#mqtt_packet{variable = Publish#mqtt_packet_publish{
|
topic_aliases = Aliases,
|
||||||
topic_name = maps:get(AliasId, Aliases, <<>>)}}, PState};
|
topic_alias_maximum = #{from_client := TopicAliasMaximum}}) ->
|
||||||
|
case AliasId =< TopicAliasMaximum of
|
||||||
|
true ->
|
||||||
|
{Packet#mqtt_packet{variable = Publish#mqtt_packet_publish{
|
||||||
|
topic_name = maps:get(AliasId, Aliases, <<>>)}}, PState};
|
||||||
|
false ->
|
||||||
|
deliver({disconnect, ?RC_TOPIC_ALIAS_INVALID}, PState),
|
||||||
|
{error, ?RC_TOPIC_ALIAS_INVALID}
|
||||||
|
end;
|
||||||
|
|
||||||
preprocess_properties(Packet = #mqtt_packet{
|
preprocess_properties(Packet = #mqtt_packet{
|
||||||
variable = #mqtt_packet_publish{
|
variable = #mqtt_packet_publish{
|
||||||
topic_name = Topic,
|
topic_name = Topic,
|
||||||
properties = #{'Topic-Alias' := AliasId}}
|
properties = #{'Topic-Alias' := AliasId}}
|
||||||
},
|
},
|
||||||
PState = #pstate{proto_ver = ?MQTT_PROTO_V5, topic_aliases = Aliases}) ->
|
PState = #pstate{proto_ver = ?MQTT_PROTO_V5,
|
||||||
{Packet, PState#pstate{topic_aliases = maps:put(AliasId, Topic, Aliases)}};
|
topic_aliases = Aliases,
|
||||||
|
topic_alias_maximum = #{from_client := TopicAliasMaximum}}) ->
|
||||||
|
case AliasId =< TopicAliasMaximum of
|
||||||
|
true ->
|
||||||
|
{Packet, PState#pstate{topic_aliases = maps:put(AliasId, Topic, Aliases)}};
|
||||||
|
false ->
|
||||||
|
deliver({disconnect, ?RC_TOPIC_ALIAS_INVALID}, PState),
|
||||||
|
{error, ?RC_TOPIC_ALIAS_INVALID}
|
||||||
|
end;
|
||||||
|
|
||||||
preprocess_properties(Packet, PState) ->
|
preprocess_properties(Packet, PState) ->
|
||||||
{Packet, PState}.
|
{Packet, PState}.
|
||||||
|
@ -278,7 +315,6 @@ preprocess_properties(Packet, PState) ->
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Process MQTT Packet
|
%% Process MQTT Packet
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
process_packet(?CONNECT_PACKET(
|
process_packet(?CONNECT_PACKET(
|
||||||
#mqtt_packet_connect{proto_name = ProtoName,
|
#mqtt_packet_connect{proto_name = ProtoName,
|
||||||
proto_ver = ProtoVer,
|
proto_ver = ProtoVer,
|
||||||
|
@ -308,6 +344,7 @@ process_packet(?CONNECT_PACKET(
|
||||||
will_msg = WillMsg,
|
will_msg = WillMsg,
|
||||||
is_bridge = IsBridge,
|
is_bridge = IsBridge,
|
||||||
connected_at = os:timestamp()}),
|
connected_at = os:timestamp()}),
|
||||||
|
|
||||||
connack(
|
connack(
|
||||||
case check_connect(Connect, PState1) of
|
case check_connect(Connect, PState1) of
|
||||||
{ok, PState2} ->
|
{ok, PState2} ->
|
||||||
|
@ -342,9 +379,6 @@ process_packet(Packet = ?PUBLISH_PACKET(?QOS_0, Topic, _PacketId, _Payload), PSt
|
||||||
case check_publish(Packet, PState) of
|
case check_publish(Packet, PState) of
|
||||||
{ok, PState1} ->
|
{ok, PState1} ->
|
||||||
do_publish(Packet, PState1);
|
do_publish(Packet, PState1);
|
||||||
{error, ?RC_TOPIC_ALIAS_INVALID} ->
|
|
||||||
?LOG(error, "Protocol error - ~p", [?RC_TOPIC_ALIAS_INVALID]),
|
|
||||||
{error, ?RC_TOPIC_ALIAS_INVALID, PState};
|
|
||||||
{error, ReasonCode} ->
|
{error, ReasonCode} ->
|
||||||
?LOG(warning, "Cannot publish qos0 message to ~s for ~s",
|
?LOG(warning, "Cannot publish qos0 message to ~s for ~s",
|
||||||
[Topic, emqx_reason_codes:text(ReasonCode)]),
|
[Topic, emqx_reason_codes:text(ReasonCode)]),
|
||||||
|
@ -523,7 +557,8 @@ deliver({connack, ?RC_SUCCESS, SP}, PState = #pstate{zone = Zone,
|
||||||
proto_ver = ?MQTT_PROTO_V5,
|
proto_ver = ?MQTT_PROTO_V5,
|
||||||
client_id = ClientId,
|
client_id = ClientId,
|
||||||
conn_props = ConnProps,
|
conn_props = ConnProps,
|
||||||
is_assigned = IsAssigned}) ->
|
is_assigned = IsAssigned,
|
||||||
|
topic_alias_maximum = TopicAliasMaximum}) ->
|
||||||
ResponseInformation = case maps:find('Request-Response-Information', ConnProps) of
|
ResponseInformation = case maps:find('Request-Response-Information', ConnProps) of
|
||||||
{ok, 1} ->
|
{ok, 1} ->
|
||||||
iolist_to_binary(emqx_config:get_env(response_topic_prefix));
|
iolist_to_binary(emqx_config:get_env(response_topic_prefix));
|
||||||
|
@ -561,16 +596,19 @@ deliver({connack, ?RC_SUCCESS, SP}, PState = #pstate{zone = Zone,
|
||||||
undefined -> Props2;
|
undefined -> Props2;
|
||||||
Keepalive -> Props2#{'Server-Keep-Alive' => Keepalive}
|
Keepalive -> Props2#{'Server-Keep-Alive' => Keepalive}
|
||||||
end,
|
end,
|
||||||
send(?CONNACK_PACKET(?RC_SUCCESS, SP, Props3), PState);
|
|
||||||
|
PState1 = PState#pstate{topic_alias_maximum = TopicAliasMaximum#{from_client => MaxAlias}},
|
||||||
|
|
||||||
|
send(?CONNACK_PACKET(?RC_SUCCESS, SP, Props3), PState1);
|
||||||
|
|
||||||
deliver({connack, ReasonCode, SP}, PState) ->
|
deliver({connack, ReasonCode, SP}, PState) ->
|
||||||
send(?CONNACK_PACKET(ReasonCode, SP), PState);
|
send(?CONNACK_PACKET(ReasonCode, SP), PState);
|
||||||
|
|
||||||
deliver({publish, PacketId, Msg}, PState = #pstate{mountpoint = MountPoint}) ->
|
deliver({publish, PacketId, Msg = #message{headers = Headers}}, PState = #pstate{mountpoint = MountPoint}) ->
|
||||||
_ = emqx_hooks:run('message.delivered', [credentials(PState)], Msg),
|
_ = emqx_hooks:run('message.delivered', [credentials(PState)], Msg),
|
||||||
Msg1 = emqx_message:update_expiry(Msg),
|
Msg1 = emqx_message:update_expiry(Msg),
|
||||||
Msg2 = emqx_mountpoint:unmount(MountPoint, Msg1),
|
Msg2 = emqx_mountpoint:unmount(MountPoint, Msg1),
|
||||||
send(emqx_packet:from_message(PacketId, Msg2), PState);
|
send(emqx_packet:from_message(PacketId, Msg2#message{headers = maps:remove('Topic-Alias', Headers)}), PState);
|
||||||
|
|
||||||
deliver({puback, PacketId, ReasonCode}, PState) ->
|
deliver({puback, PacketId, ReasonCode}, PState) ->
|
||||||
send(?PUBACK_PACKET(PacketId, ReasonCode), PState);
|
send(?PUBACK_PACKET(PacketId, ReasonCode), PState);
|
||||||
|
@ -758,18 +796,11 @@ check_publish(Packet, PState) ->
|
||||||
run_check_steps([fun check_pub_caps/2,
|
run_check_steps([fun check_pub_caps/2,
|
||||||
fun check_pub_acl/2], Packet, PState).
|
fun check_pub_acl/2], Packet, PState).
|
||||||
|
|
||||||
check_pub_caps(#mqtt_packet{header = #mqtt_packet_header{qos = QoS, retain = Retain},
|
|
||||||
variable = #mqtt_packet_publish{
|
|
||||||
properties = #{'Topic-Alias' := TopicAlias}
|
|
||||||
}},
|
|
||||||
#pstate{zone = Zone}) ->
|
|
||||||
emqx_mqtt_caps:check_pub(Zone, #{qos => QoS, retain => Retain, topic_alias => TopicAlias});
|
|
||||||
check_pub_caps(#mqtt_packet{header = #mqtt_packet_header{qos = QoS, retain = Retain},
|
check_pub_caps(#mqtt_packet{header = #mqtt_packet_header{qos = QoS, retain = Retain},
|
||||||
variable = #mqtt_packet_publish{ properties = _Properties}},
|
variable = #mqtt_packet_publish{ properties = _Properties}},
|
||||||
#pstate{zone = Zone}) ->
|
#pstate{zone = Zone}) ->
|
||||||
emqx_mqtt_caps:check_pub(Zone, #{qos => QoS, retain => Retain}).
|
emqx_mqtt_caps:check_pub(Zone, #{qos => QoS, retain => Retain}).
|
||||||
|
|
||||||
|
|
||||||
check_pub_acl(_Packet, #pstate{is_super = IsSuper, enable_acl = EnableAcl})
|
check_pub_acl(_Packet, #pstate{is_super = IsSuper, enable_acl = EnableAcl})
|
||||||
when IsSuper orelse (not EnableAcl) ->
|
when IsSuper orelse (not EnableAcl) ->
|
||||||
ok;
|
ok;
|
||||||
|
|
|
@ -55,6 +55,7 @@ groups() ->
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
emqx_ct_broker_helpers:run_setup_steps(),
|
emqx_ct_broker_helpers:run_setup_steps(),
|
||||||
|
emqx_zone:set_env(external, max_topic_alias, 20),
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
end_per_suite(_Config) ->
|
end_per_suite(_Config) ->
|
||||||
|
@ -155,6 +156,82 @@ connect_v5(_) ->
|
||||||
raw_recv_parse(Data, ?MQTT_PROTO_V5)
|
raw_recv_parse(Data, ?MQTT_PROTO_V5)
|
||||||
end),
|
end),
|
||||||
|
|
||||||
|
% topic alias = 0
|
||||||
|
with_connection(fun([Sock]) ->
|
||||||
|
emqx_client_sock:send(Sock,
|
||||||
|
raw_send_serialize(
|
||||||
|
?CONNECT_PACKET(
|
||||||
|
#mqtt_packet_connect{
|
||||||
|
proto_ver = ?MQTT_PROTO_V5,
|
||||||
|
properties =
|
||||||
|
#{'Topic-Alias-Maximum' => 10}}),
|
||||||
|
#{version => ?MQTT_PROTO_V5}
|
||||||
|
)),
|
||||||
|
{ok, Data} = gen_tcp:recv(Sock, 0),
|
||||||
|
{ok, ?CONNACK_PACKET(?RC_SUCCESS, 0,
|
||||||
|
#{'Topic-Alias-Maximum' := 20}), _} =
|
||||||
|
raw_recv_parse(Data, ?MQTT_PROTO_V5),
|
||||||
|
|
||||||
|
emqx_client_sock:send(Sock,
|
||||||
|
raw_send_serialize(
|
||||||
|
?PUBLISH_PACKET(?QOS_1, <<"TopicA">>, 1, #{'Topic-Alias' => 0}, <<"hello">>),
|
||||||
|
#{version => ?MQTT_PROTO_V5}
|
||||||
|
)),
|
||||||
|
|
||||||
|
{ok, Data2} = gen_tcp:recv(Sock, 0),
|
||||||
|
{ok, ?DISCONNECT_PACKET(?RC_TOPIC_ALIAS_INVALID), _} = raw_recv_parse(Data2, ?MQTT_PROTO_V5)
|
||||||
|
end),
|
||||||
|
|
||||||
|
% topic alias maximum
|
||||||
|
with_connection(fun([Sock]) ->
|
||||||
|
emqx_client_sock:send(Sock,
|
||||||
|
raw_send_serialize(
|
||||||
|
?CONNECT_PACKET(
|
||||||
|
#mqtt_packet_connect{
|
||||||
|
proto_ver = ?MQTT_PROTO_V5,
|
||||||
|
properties =
|
||||||
|
#{'Topic-Alias-Maximum' => 10}}),
|
||||||
|
#{version => ?MQTT_PROTO_V5}
|
||||||
|
)),
|
||||||
|
{ok, Data} = gen_tcp:recv(Sock, 0),
|
||||||
|
{ok, ?CONNACK_PACKET(?RC_SUCCESS, 0,
|
||||||
|
#{'Topic-Alias-Maximum' := 20}), _} =
|
||||||
|
raw_recv_parse(Data, ?MQTT_PROTO_V5),
|
||||||
|
|
||||||
|
emqx_client_sock:send(Sock, raw_send_serialize(?SUBSCRIBE_PACKET(1, [{<<"TopicA">>, #{rh => 1,
|
||||||
|
qos => ?QOS_2,
|
||||||
|
rap => 0,
|
||||||
|
nl => 0,
|
||||||
|
rc => 0}}]),
|
||||||
|
#{version => ?MQTT_PROTO_V5})),
|
||||||
|
|
||||||
|
{ok, Data2} = gen_tcp:recv(Sock, 0),
|
||||||
|
{ok, ?SUBACK_PACKET(1, #{}, [2]), _} = raw_recv_parse(Data2, ?MQTT_PROTO_V5),
|
||||||
|
|
||||||
|
emqx_client_sock:send(Sock,
|
||||||
|
raw_send_serialize(
|
||||||
|
?PUBLISH_PACKET(?QOS_1, <<"TopicA">>, 1, #{'Topic-Alias' => 15}, <<"hello">>),
|
||||||
|
#{version => ?MQTT_PROTO_V5}
|
||||||
|
)),
|
||||||
|
|
||||||
|
{ok, Data3} = gen_tcp:recv(Sock, 0),
|
||||||
|
|
||||||
|
{ok, ?PUBACK_PACKET(1, 0), _} = raw_recv_parse(Data3, ?MQTT_PROTO_V5),
|
||||||
|
|
||||||
|
{ok, Data4} = gen_tcp:recv(Sock, 0),
|
||||||
|
|
||||||
|
{ok, ?PUBLISH_PACKET(?QOS_1, <<"TopicA">>, _, <<"hello">>), _} = raw_recv_parse(Data4, ?MQTT_PROTO_V5),
|
||||||
|
|
||||||
|
emqx_client_sock:send(Sock,
|
||||||
|
raw_send_serialize(
|
||||||
|
?PUBLISH_PACKET(?QOS_1, <<"TopicA">>, 2, #{'Topic-Alias' => 21}, <<"hello">>),
|
||||||
|
#{version => ?MQTT_PROTO_V5}
|
||||||
|
)),
|
||||||
|
|
||||||
|
{ok, Data5} = gen_tcp:recv(Sock, 0),
|
||||||
|
{ok, ?DISCONNECT_PACKET(?RC_TOPIC_ALIAS_INVALID), _} = raw_recv_parse(Data5, ?MQTT_PROTO_V5)
|
||||||
|
end),
|
||||||
|
|
||||||
% test clean start
|
% test clean start
|
||||||
with_connection(fun([Sock]) ->
|
with_connection(fun([Sock]) ->
|
||||||
emqx_client_sock:send(Sock,
|
emqx_client_sock:send(Sock,
|
||||||
|
|
Loading…
Reference in New Issue