Return Assigned Client Identifier in CONNACK
"If the Client connects using a zero length Client Identifier, the Server MUST respond with a CONNACK containing an Assigned Client Identifier."
This commit is contained in:
parent
f1f8167ec4
commit
e50e994ef4
|
|
@ -135,7 +135,7 @@ process_connect(
|
|||
Result0 =
|
||||
maybe
|
||||
ok ?= check_protocol_version(ProtoVer),
|
||||
{ok, ClientId} ?= ensure_client_id(ClientId0, CleanSess),
|
||||
{ok, ClientId} ?= ensure_client_id(ClientId0, CleanSess, ProtoVer),
|
||||
{ok, {Username1, Password}} ?= check_credentials(Username0, Password0, SslLoginName, PeerIp),
|
||||
|
||||
{VHostPickedUsing, {VHost, Username2}} = get_vhost(Username1, SslLoginName, Port),
|
||||
|
|
@ -186,19 +186,34 @@ process_connect(
|
|||
end,
|
||||
case Result of
|
||||
{ok, SessPresent, State = #state{}} ->
|
||||
send_conn_ack(?RC_SUCCESS, SessPresent, ProtoVer, SendFun, MaxPacketSize),
|
||||
Props0 = #{'Maximum-QoS' => ?QOS_1,
|
||||
'Maximum-Packet-Size' => persistent_term:get(
|
||||
?PERSISTENT_TERM_MAX_PACKET_SIZE_AUTHENTICATED),
|
||||
'Subscription-Identifier-Available' => 0,
|
||||
'Shared-Subscription-Available' => 0
|
||||
},
|
||||
Props = case {ClientId0, ProtoVer} of
|
||||
{<<>>, 5} ->
|
||||
%% "If the Client connects using a zero length Client Identifier, the Server
|
||||
%% MUST respond with a CONNACK containing an Assigned Client Identifier."
|
||||
maps:put('Assigned-Client-Identifier', State#state.cfg#cfg.client_id, Props0);
|
||||
_ ->
|
||||
Props0
|
||||
end,
|
||||
send_conn_ack(?RC_SUCCESS, SessPresent, ProtoVer, SendFun, MaxPacketSize, Props),
|
||||
{ok, State};
|
||||
{error, ConnectReasonCode} = Err
|
||||
when is_integer(ConnectReasonCode) ->
|
||||
%% If a server sends a CONNACK packet containing a non-zero return
|
||||
%% code it MUST set Session Present to 0 [MQTT-3.2.2-4].
|
||||
SessPresent = false,
|
||||
send_conn_ack(ConnectReasonCode, SessPresent, ProtoVer, SendFun, MaxPacketSize),
|
||||
send_conn_ack(ConnectReasonCode, SessPresent, ProtoVer, SendFun, MaxPacketSize, #{}),
|
||||
Err
|
||||
end.
|
||||
|
||||
-spec send_conn_ack(reason_code(), boolean(), protocol_version(), send_fun(), max_packet_size()) -> ok.
|
||||
send_conn_ack(ConnectReasonCode, SessPresent, ProtoVer, SendFun, MaxPacketSize) ->
|
||||
-spec send_conn_ack(reason_code(), boolean(), protocol_version(), send_fun(),
|
||||
max_packet_size(), properties()) -> ok.
|
||||
send_conn_ack(ConnectReasonCode, SessPresent, ProtoVer, SendFun, MaxPacketSize, Props) ->
|
||||
Code = case ProtoVer of
|
||||
5 -> ConnectReasonCode;
|
||||
_ -> connect_reason_code_to_return_code(ConnectReasonCode)
|
||||
|
|
@ -206,7 +221,8 @@ send_conn_ack(ConnectReasonCode, SessPresent, ProtoVer, SendFun, MaxPacketSize)
|
|||
Packet = #mqtt_packet{fixed = #mqtt_packet_fixed{type = ?CONNACK},
|
||||
variable = #mqtt_packet_connack{
|
||||
session_present = SessPresent,
|
||||
code = Code}},
|
||||
code = Code,
|
||||
props = Props}},
|
||||
_ = send(Packet, ProtoVer, SendFun, MaxPacketSize),
|
||||
ok.
|
||||
|
||||
|
|
@ -435,14 +451,17 @@ check_credentials(Username, Password, SslLoginName, PeerIp) ->
|
|||
{ok, {UserBin, PassBin}}
|
||||
end.
|
||||
|
||||
ensure_client_id(<<>>, _CleanSess = false) ->
|
||||
?LOG_ERROR("MQTT client ID must be provided for non-clean session"),
|
||||
-spec ensure_client_id(binary(), boolean(), protocol_version()) ->
|
||||
{ok, binary()} | {error, reason_code()}.
|
||||
ensure_client_id(<<>>, _CleanSess = false, ProtoVer)
|
||||
when ProtoVer < 5 ->
|
||||
?LOG_ERROR("MQTT client ID must be provided for non-clean session in MQTT v~b", [ProtoVer]),
|
||||
{error, ?RC_CLIENT_IDENTIFIER_NOT_VALID};
|
||||
ensure_client_id(<<>>, _CleanSess = true) ->
|
||||
ensure_client_id(<<>>, _, _) ->
|
||||
{ok, rabbit_data_coercion:to_binary(
|
||||
rabbit_misc:base64url(
|
||||
rabbit_guid:gen_secure()))};
|
||||
ensure_client_id(ClientId, _CleanSess)
|
||||
ensure_client_id(ClientId, _, _)
|
||||
when is_binary(ClientId) ->
|
||||
{ok, ClientId}.
|
||||
|
||||
|
|
|
|||
|
|
@ -897,15 +897,28 @@ non_clean_sess_reconnect_qos0_and_qos1(Config) ->
|
|||
C3 = connect(ClientId, Config, [{clean_start, true}]),
|
||||
ok = emqtt:disconnect(C3).
|
||||
|
||||
%% "If the Client supplies a zero-byte ClientId with CleanSession set to 0,
|
||||
%% the Server MUST respond to the CONNECT Packet with a CONNACK return code 0x02
|
||||
%% (Identifier rejected) and then close the Network Connection" [MQTT-3.1.3-8].
|
||||
non_clean_sess_empty_client_id(Config) ->
|
||||
{C, Connect} = util:start_client(<<>>, Config, 0, [{clean_start, false}]),
|
||||
process_flag(trap_exit, true),
|
||||
?assertMatch({error, {client_identifier_not_valid, _}},
|
||||
Connect(C)),
|
||||
ok = await_exit(C).
|
||||
case ?config(mqtt_version, Config) of
|
||||
V when V =:= v3;
|
||||
V =:= v4 ->
|
||||
%% "If the Client supplies a zero-byte ClientId with CleanSession set to 0,
|
||||
%% the Server MUST respond to the CONNECT Packet with a CONNACK return code 0x02
|
||||
%% (Identifier rejected) and then close the Network Connection" [MQTT-3.1.3-8].
|
||||
process_flag(trap_exit, true),
|
||||
?assertMatch({error, {client_identifier_not_valid, _}}, Connect(C)),
|
||||
ok = await_exit(C);
|
||||
v5 ->
|
||||
%% "If the Client connects using a zero length Client Identifier, the Server MUST respond with
|
||||
%% a CONNACK containing an Assigned Client Identifier. The Assigned Client Identifier MUST be
|
||||
%% a new Client Identifier not used by any other Session currently in the Server [MQTT-3.2.2-16]."
|
||||
{ok, #{'Assigned-Client-Identifier' := ClientId}} = Connect(C),
|
||||
{C2, Connect2} = util:start_client(<<>>, Config, 0, [{clean_start, true}]),
|
||||
{ok, #{'Assigned-Client-Identifier' := ClientId2}} = Connect2(C2),
|
||||
?assertNotEqual(ClientId, ClientId2),
|
||||
ok = emqtt:disconnect(C),
|
||||
ok = emqtt:disconnect(C2)
|
||||
end.
|
||||
|
||||
subscribe_same_topic_same_qos(Config) ->
|
||||
C = connect(?FUNCTION_NAME, Config),
|
||||
|
|
@ -1434,7 +1447,8 @@ max_packet_size_authenticated(Config) ->
|
|||
MaxSize = 500,
|
||||
ok = rpc(Config, persistent_term, put, [Key, MaxSize]),
|
||||
|
||||
C = connect(ClientId, Config),
|
||||
{C, Connect} = util:start_client(ClientId, Config, 0, []),
|
||||
{ok, ConnAckProps} = Connect(C),
|
||||
process_flag(trap_exit, true),
|
||||
ok = emqtt:publish(C, Topic, binary:copy(<<"x">>, MaxSize + 1), qos0),
|
||||
await_exit(C),
|
||||
|
|
@ -1442,6 +1456,7 @@ max_packet_size_authenticated(Config) ->
|
|||
V when V =:= v3; V =:= v4 ->
|
||||
ok;
|
||||
v5 ->
|
||||
?assertMatch(#{'Maximum-Packet-Size' := MaxSize}, ConnAckProps),
|
||||
receive {disconnected, _ReasonCodePacketTooLarge = 149, _Props} -> ok
|
||||
after 1000 -> ct:fail("missing DISCONNECT packet from server")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
-import(util,
|
||||
[
|
||||
start_client/4,
|
||||
connect/2, connect/3, connect/4
|
||||
]).
|
||||
|
||||
|
|
@ -124,17 +125,17 @@ client_set_max_packet_size_publish(Config) ->
|
|||
ok = emqtt:disconnect(C).
|
||||
|
||||
client_set_max_packet_size_connack(Config) ->
|
||||
{C, Connect} = util:start_client(?FUNCTION_NAME, Config, 0,
|
||||
[{properties, #{'Maximum-Packet-Size' => 2}},
|
||||
{connect_timeout, 1}]),
|
||||
{C, Connect} = start_client(?FUNCTION_NAME, Config, 0,
|
||||
[{properties, #{'Maximum-Packet-Size' => 2}},
|
||||
{connect_timeout, 1}]),
|
||||
%% We expect the server to drop the CONNACK packet because it's larger than 2 bytes.
|
||||
?assertEqual({error, connack_timeout}, Connect(C)).
|
||||
|
||||
%% "It is a Protocol Error to include the Receive Maximum
|
||||
%% value more than once or for it to have the value 0."
|
||||
client_set_max_packet_size_invalid(Config) ->
|
||||
{C, Connect} = util:start_client(?FUNCTION_NAME, Config, 0,
|
||||
[{properties, #{'Maximum-Packet-Size' => 0}}]),
|
||||
{C, Connect} = start_client(?FUNCTION_NAME, Config, 0,
|
||||
[{properties, #{'Maximum-Packet-Size' => 0}}]),
|
||||
unlink(C),
|
||||
?assertMatch({error, _}, Connect(C)).
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue