Revert "Format MQTT code with `erlfmt`"
This commit is contained in:
		
							parent
							
								
									6806a9a45e
								
							
						
					
					
						commit
						209f23fa2f
					
				| 
						 | 
					@ -1 +0,0 @@
 | 
				
			||||||
1de9fcf582def91d1cee6bea457dd24e8a53a431
 | 
					 | 
				
			||||||
| 
						 | 
					@ -10,20 +10,18 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour').
 | 
					-behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour').
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-export([
 | 
					-export([scopes/0,
 | 
				
			||||||
    scopes/0,
 | 
					         switches/0,
 | 
				
			||||||
    switches/0,
 | 
					         aliases/0,
 | 
				
			||||||
    aliases/0,
 | 
					         usage/0,
 | 
				
			||||||
    usage/0,
 | 
					         usage_doc_guides/0,
 | 
				
			||||||
    usage_doc_guides/0,
 | 
					         banner/2,
 | 
				
			||||||
    banner/2,
 | 
					         validate/2,
 | 
				
			||||||
    validate/2,
 | 
					         merge_defaults/2,
 | 
				
			||||||
    merge_defaults/2,
 | 
					         run/2,
 | 
				
			||||||
    run/2,
 | 
					         output/2,
 | 
				
			||||||
    output/2,
 | 
					         description/0,
 | 
				
			||||||
    description/0,
 | 
					         help_section/0]).
 | 
				
			||||||
    help_section/0
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
scopes() -> [ctl].
 | 
					scopes() -> [ctl].
 | 
				
			||||||
switches() -> [].
 | 
					switches() -> [].
 | 
				
			||||||
| 
						 | 
					@ -50,29 +48,20 @@ usage() ->
 | 
				
			||||||
usage_doc_guides() ->
 | 
					usage_doc_guides() ->
 | 
				
			||||||
    [?MQTT_GUIDE_URL].
 | 
					    [?MQTT_GUIDE_URL].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
run([Node], #{
 | 
					run([Node], #{node := NodeName,
 | 
				
			||||||
    node := NodeName,
 | 
					              timeout := Timeout}) ->
 | 
				
			||||||
    timeout := Timeout
 | 
					 | 
				
			||||||
}) ->
 | 
					 | 
				
			||||||
    case rabbit_misc:rpc_call(NodeName, rabbit_mqtt_collector, leave, [Node], Timeout) of
 | 
					    case rabbit_misc:rpc_call(NodeName, rabbit_mqtt_collector, leave, [Node], Timeout) of
 | 
				
			||||||
        {badrpc, _} = Error ->
 | 
					        {badrpc, _} = Error ->
 | 
				
			||||||
            Error;
 | 
					            Error;
 | 
				
			||||||
        nodedown ->
 | 
					        nodedown ->
 | 
				
			||||||
            {ok,
 | 
					            {ok, list_to_binary(io_lib:format("Node ~ts is down but has been successfully removed"
 | 
				
			||||||
                list_to_binary(
 | 
					                                         " from the cluster", [Node]))};
 | 
				
			||||||
                    io_lib:format(
 | 
					 | 
				
			||||||
                        "Node ~ts is down but has been successfully removed"
 | 
					 | 
				
			||||||
                        " from the cluster",
 | 
					 | 
				
			||||||
                        [Node]
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                )};
 | 
					 | 
				
			||||||
        Result ->
 | 
					        Result ->
 | 
				
			||||||
            %% 'ok' or 'timeout'
 | 
					            %% 'ok' or 'timeout'
 | 
				
			||||||
            Result
 | 
					            Result
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
banner([Node], _) ->
 | 
					banner([Node], _) -> list_to_binary(io_lib:format("Removing node ~ts from the list of MQTT nodes...", [Node])).
 | 
				
			||||||
    list_to_binary(io_lib:format("Removing node ~ts from the list of MQTT nodes...", [Node])).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
output(Result, _Opts) ->
 | 
					output(Result, _Opts) ->
 | 
				
			||||||
    'Elixir.RabbitMQ.CLI.DefaultOutput':output(Result).
 | 
					    'Elixir.RabbitMQ.CLI.DefaultOutput':output(Result).
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,22 +10,20 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour').
 | 
					-behaviour('Elixir.RabbitMQ.CLI.CommandBehaviour').
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-export([
 | 
					-export([formatter/0,
 | 
				
			||||||
    formatter/0,
 | 
					         scopes/0,
 | 
				
			||||||
    scopes/0,
 | 
					         switches/0,
 | 
				
			||||||
    switches/0,
 | 
					         aliases/0,
 | 
				
			||||||
    aliases/0,
 | 
					         usage/0,
 | 
				
			||||||
    usage/0,
 | 
					         usage_additional/0,
 | 
				
			||||||
    usage_additional/0,
 | 
					         usage_doc_guides/0,
 | 
				
			||||||
    usage_doc_guides/0,
 | 
					         banner/2,
 | 
				
			||||||
    banner/2,
 | 
					         validate/2,
 | 
				
			||||||
    validate/2,
 | 
					         merge_defaults/2,
 | 
				
			||||||
    merge_defaults/2,
 | 
					         run/2,
 | 
				
			||||||
    run/2,
 | 
					         output/2,
 | 
				
			||||||
    output/2,
 | 
					         description/0,
 | 
				
			||||||
    description/0,
 | 
					         help_section/0]).
 | 
				
			||||||
    help_section/0
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
formatter() -> 'Elixir.RabbitMQ.CLI.Formatters.Table'.
 | 
					formatter() -> 'Elixir.RabbitMQ.CLI.Formatters.Table'.
 | 
				
			||||||
scopes() -> [ctl, diagnostics].
 | 
					scopes() -> [ctl, diagnostics].
 | 
				
			||||||
| 
						 | 
					@ -39,14 +37,10 @@ help_section() ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
validate(Args, _) ->
 | 
					validate(Args, _) ->
 | 
				
			||||||
    InfoItems = lists:map(fun atom_to_list/1, ?INFO_ITEMS),
 | 
					    InfoItems = lists:map(fun atom_to_list/1, ?INFO_ITEMS),
 | 
				
			||||||
    case
 | 
					    case 'Elixir.RabbitMQ.CLI.Ctl.InfoKeys':validate_info_keys(Args,
 | 
				
			||||||
        'Elixir.RabbitMQ.CLI.Ctl.InfoKeys':validate_info_keys(
 | 
					                                                               InfoItems) of
 | 
				
			||||||
            Args,
 | 
					 | 
				
			||||||
            InfoItems
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    of
 | 
					 | 
				
			||||||
        {ok, _} -> ok;
 | 
					        {ok, _} -> ok;
 | 
				
			||||||
        Error -> Error
 | 
					        Error   -> Error
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
merge_defaults([], Opts) ->
 | 
					merge_defaults([], Opts) ->
 | 
				
			||||||
| 
						 | 
					@ -61,22 +55,19 @@ usage_additional() ->
 | 
				
			||||||
    Prefix = <<" must be one of ">>,
 | 
					    Prefix = <<" must be one of ">>,
 | 
				
			||||||
    InfoItems = 'Elixir.Enum':join(lists:usort(?INFO_ITEMS), <<", ">>),
 | 
					    InfoItems = 'Elixir.Enum':join(lists:usort(?INFO_ITEMS), <<", ">>),
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {<<"<column>">>, <<Prefix/binary, InfoItems/binary>>}
 | 
					      {<<"<column>">>, <<Prefix/binary, InfoItems/binary>>}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
usage_doc_guides() ->
 | 
					usage_doc_guides() ->
 | 
				
			||||||
    [?MQTT_GUIDE_URL].
 | 
					    [?MQTT_GUIDE_URL].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
run(Args, #{
 | 
					run(Args, #{node := NodeName,
 | 
				
			||||||
    node := NodeName,
 | 
					            timeout := Timeout,
 | 
				
			||||||
    timeout := Timeout,
 | 
					            verbose := Verbose}) ->
 | 
				
			||||||
    verbose := Verbose
 | 
					    InfoKeys = case Verbose of
 | 
				
			||||||
}) ->
 | 
					        true  -> ?INFO_ITEMS;
 | 
				
			||||||
    InfoKeys =
 | 
					        false -> 'Elixir.RabbitMQ.CLI.Ctl.InfoKeys':prepare_info_keys(Args)
 | 
				
			||||||
        case Verbose of
 | 
					    end,
 | 
				
			||||||
            true -> ?INFO_ITEMS;
 | 
					 | 
				
			||||||
            false -> 'Elixir.RabbitMQ.CLI.Ctl.InfoKeys':prepare_info_keys(Args)
 | 
					 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Nodes = 'Elixir.RabbitMQ.CLI.Core.Helpers':nodes_in_cluster(NodeName),
 | 
					    Nodes = 'Elixir.RabbitMQ.CLI.Core.Helpers':nodes_in_cluster(NodeName),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -87,8 +78,7 @@ run(Args, #{
 | 
				
			||||||
        [Nodes, InfoKeys],
 | 
					        [Nodes, InfoKeys],
 | 
				
			||||||
        Timeout,
 | 
					        Timeout,
 | 
				
			||||||
        InfoKeys,
 | 
					        InfoKeys,
 | 
				
			||||||
        length(Nodes)
 | 
					        length(Nodes)).
 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
banner(_, _) -> <<"Listing MQTT connections ...">>.
 | 
					banner(_, _) -> <<"Listing MQTT connections ...">>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,15 +9,13 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-include("mqtt_machine.hrl").
 | 
					-include("mqtt_machine.hrl").
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-export([
 | 
					-export([version/0,
 | 
				
			||||||
    version/0,
 | 
					         which_module/1,
 | 
				
			||||||
    which_module/1,
 | 
					         init/1,
 | 
				
			||||||
    init/1,
 | 
					         apply/3,
 | 
				
			||||||
    apply/3,
 | 
					         state_enter/2,
 | 
				
			||||||
    state_enter/2,
 | 
					         notify_connection/2,
 | 
				
			||||||
    notify_connection/2,
 | 
					         overview/1]).
 | 
				
			||||||
    overview/1
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-type state() :: #machine_state{}.
 | 
					-type state() :: #machine_state{}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,10 +24,9 @@
 | 
				
			||||||
-type reply() :: {ok, term()} | {error, term()}.
 | 
					-type reply() :: {ok, term()} | {error, term()}.
 | 
				
			||||||
-type client_id() :: term().
 | 
					-type client_id() :: term().
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-type command() ::
 | 
					-type command() :: {register, client_id(), pid()} |
 | 
				
			||||||
    {register, client_id(), pid()}
 | 
					                   {unregister, client_id(), pid()} |
 | 
				
			||||||
    | {unregister, client_id(), pid()}
 | 
					                   list.
 | 
				
			||||||
    | list.
 | 
					 | 
				
			||||||
version() -> 1.
 | 
					version() -> 1.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
which_module(1) -> ?MODULE;
 | 
					which_module(1) -> ?MODULE;
 | 
				
			||||||
| 
						 | 
					@ -41,130 +38,93 @@ init(_Conf) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec apply(map(), command(), state()) ->
 | 
					-spec apply(map(), command(), state()) ->
 | 
				
			||||||
    {state(), reply(), ra_machine:effects()}.
 | 
					    {state(), reply(), ra_machine:effects()}.
 | 
				
			||||||
apply(
 | 
					apply(_Meta, {register, ClientId, Pid},
 | 
				
			||||||
    _Meta,
 | 
					      #machine_state{client_ids = Ids,
 | 
				
			||||||
    {register, ClientId, Pid},
 | 
					                     pids = Pids0} = State0) ->
 | 
				
			||||||
    #machine_state{
 | 
					 | 
				
			||||||
        client_ids = Ids,
 | 
					 | 
				
			||||||
        pids = Pids0
 | 
					 | 
				
			||||||
    } = State0
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    {Effects, Ids1, Pids} =
 | 
					    {Effects, Ids1, Pids} =
 | 
				
			||||||
        case maps:find(ClientId, Ids) of
 | 
					        case maps:find(ClientId, Ids) of
 | 
				
			||||||
            {ok, OldPid} when Pid =/= OldPid ->
 | 
					            {ok, OldPid} when Pid =/= OldPid ->
 | 
				
			||||||
                Effects0 = [
 | 
					                Effects0 = [{demonitor, process, OldPid},
 | 
				
			||||||
                    {demonitor, process, OldPid},
 | 
					                            {monitor, process, Pid},
 | 
				
			||||||
                    {monitor, process, Pid},
 | 
					                            {mod_call, ?MODULE, notify_connection,
 | 
				
			||||||
                    {mod_call, ?MODULE, notify_connection, [OldPid, duplicate_id]}
 | 
					                             [OldPid, duplicate_id]}],
 | 
				
			||||||
                ],
 | 
					                Pids2 = case maps:take(OldPid, Pids0) of
 | 
				
			||||||
                Pids2 =
 | 
					                            error ->
 | 
				
			||||||
                    case maps:take(OldPid, Pids0) of
 | 
					                                Pids0;
 | 
				
			||||||
                        error ->
 | 
					                            {[ClientId], Pids1} ->
 | 
				
			||||||
                            Pids0;
 | 
					                                Pids1;
 | 
				
			||||||
                        {[ClientId], Pids1} ->
 | 
					                            {ClientIds, Pids1} ->
 | 
				
			||||||
                            Pids1;
 | 
					                                Pids1#{ClientId => lists:delete(ClientId, ClientIds)}
 | 
				
			||||||
                        {ClientIds, Pids1} ->
 | 
					                        end,
 | 
				
			||||||
                            Pids1#{ClientId => lists:delete(ClientId, ClientIds)}
 | 
					                Pids3 = maps:update_with(Pid, fun(CIds) -> [ClientId | CIds] end,
 | 
				
			||||||
                    end,
 | 
					                                         [ClientId], Pids2),
 | 
				
			||||||
                Pids3 = maps:update_with(
 | 
					 | 
				
			||||||
                    Pid,
 | 
					 | 
				
			||||||
                    fun(CIds) -> [ClientId | CIds] end,
 | 
					 | 
				
			||||||
                    [ClientId],
 | 
					 | 
				
			||||||
                    Pids2
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                {Effects0, maps:remove(ClientId, Ids), Pids3};
 | 
					                {Effects0, maps:remove(ClientId, Ids), Pids3};
 | 
				
			||||||
            {ok, Pid} ->
 | 
					
 | 
				
			||||||
 | 
					            {ok, Pid}  ->
 | 
				
			||||||
                {[], Ids, Pids0};
 | 
					                {[], Ids, Pids0};
 | 
				
			||||||
            error ->
 | 
					            error ->
 | 
				
			||||||
                Pids1 = maps:update_with(
 | 
					                Pids1 = maps:update_with(Pid, fun(CIds) -> [ClientId | CIds] end,
 | 
				
			||||||
                    Pid,
 | 
					                                         [ClientId], Pids0),
 | 
				
			||||||
                    fun(CIds) -> [ClientId | CIds] end,
 | 
					 | 
				
			||||||
                    [ClientId],
 | 
					 | 
				
			||||||
                    Pids0
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                Effects0 = [{monitor, process, Pid}],
 | 
					                Effects0 = [{monitor, process, Pid}],
 | 
				
			||||||
                {Effects0, Ids, Pids1}
 | 
					                {Effects0, Ids, Pids1}
 | 
				
			||||||
        end,
 | 
					        end,
 | 
				
			||||||
    State = State0#machine_state{
 | 
					    State = State0#machine_state{client_ids = maps:put(ClientId, Pid, Ids1),
 | 
				
			||||||
        client_ids = maps:put(ClientId, Pid, Ids1),
 | 
					                                 pids = Pids},
 | 
				
			||||||
        pids = Pids
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {State, ok, Effects};
 | 
					    {State, ok, Effects};
 | 
				
			||||||
apply(
 | 
					 | 
				
			||||||
    Meta,
 | 
					 | 
				
			||||||
    {unregister, ClientId, Pid},
 | 
					 | 
				
			||||||
    #machine_state{
 | 
					 | 
				
			||||||
        client_ids = Ids,
 | 
					 | 
				
			||||||
        pids = Pids0
 | 
					 | 
				
			||||||
    } = State0
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    State =
 | 
					 | 
				
			||||||
        case maps:find(ClientId, Ids) of
 | 
					 | 
				
			||||||
            {ok, Pid} ->
 | 
					 | 
				
			||||||
                Pids =
 | 
					 | 
				
			||||||
                    case maps:get(Pid, Pids0, undefined) of
 | 
					 | 
				
			||||||
                        undefined ->
 | 
					 | 
				
			||||||
                            Pids0;
 | 
					 | 
				
			||||||
                        [ClientId] ->
 | 
					 | 
				
			||||||
                            maps:remove(Pid, Pids0);
 | 
					 | 
				
			||||||
                        Cids ->
 | 
					 | 
				
			||||||
                            Pids0#{Pid => lists:delete(ClientId, Cids)}
 | 
					 | 
				
			||||||
                    end,
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                State0#machine_state{
 | 
					apply(Meta, {unregister, ClientId, Pid}, #machine_state{client_ids = Ids,
 | 
				
			||||||
                    client_ids = maps:remove(ClientId, Ids),
 | 
					                                                        pids = Pids0} = State0) ->
 | 
				
			||||||
                    pids = Pids
 | 
					    State = case maps:find(ClientId, Ids) of
 | 
				
			||||||
                };
 | 
					                {ok, Pid} ->
 | 
				
			||||||
            %% don't delete client id that might belong to a newer connection
 | 
					                    Pids = case maps:get(Pid, Pids0, undefined) of
 | 
				
			||||||
            %% that kicked the one with Pid out
 | 
					                               undefined ->
 | 
				
			||||||
            {ok, _AnotherPid} ->
 | 
					                                   Pids0;
 | 
				
			||||||
                State0;
 | 
					                               [ClientId] ->
 | 
				
			||||||
            error ->
 | 
					                                   maps:remove(Pid, Pids0);
 | 
				
			||||||
                State0
 | 
					                               Cids ->
 | 
				
			||||||
        end,
 | 
					                                   Pids0#{Pid => lists:delete(ClientId, Cids)}
 | 
				
			||||||
 | 
					                           end,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    State0#machine_state{client_ids = maps:remove(ClientId, Ids),
 | 
				
			||||||
 | 
					                                         pids = Pids};
 | 
				
			||||||
 | 
					                %% don't delete client id that might belong to a newer connection
 | 
				
			||||||
 | 
					                %% that kicked the one with Pid out
 | 
				
			||||||
 | 
					                {ok, _AnotherPid} ->
 | 
				
			||||||
 | 
					                    State0;
 | 
				
			||||||
 | 
					                error ->
 | 
				
			||||||
 | 
					                    State0
 | 
				
			||||||
 | 
					            end,
 | 
				
			||||||
    Effects0 = [{demonitor, process, Pid}],
 | 
					    Effects0 = [{demonitor, process, Pid}],
 | 
				
			||||||
    %% snapshot only when the map has changed
 | 
					    %% snapshot only when the map has changed
 | 
				
			||||||
    Effects =
 | 
					    Effects = case State of
 | 
				
			||||||
        case State of
 | 
					      State0 -> Effects0;
 | 
				
			||||||
            State0 -> Effects0;
 | 
					      _      -> Effects0 ++ snapshot_effects(Meta, State)
 | 
				
			||||||
            _ -> Effects0 ++ snapshot_effects(Meta, State)
 | 
					    end,
 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
    {State, ok, Effects};
 | 
					    {State, ok, Effects};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
apply(_Meta, {down, DownPid, noconnection}, State) ->
 | 
					apply(_Meta, {down, DownPid, noconnection}, State) ->
 | 
				
			||||||
    %% Monitor the node the pid is on (see {nodeup, Node} below)
 | 
					    %% Monitor the node the pid is on (see {nodeup, Node} below)
 | 
				
			||||||
    %% so that we can detect when the node is re-connected and discover the
 | 
					    %% so that we can detect when the node is re-connected and discover the
 | 
				
			||||||
    %% actual fate of the connection processes on it
 | 
					    %% actual fate of the connection processes on it
 | 
				
			||||||
    Effect = {monitor, node, node(DownPid)},
 | 
					    Effect = {monitor, node, node(DownPid)},
 | 
				
			||||||
    {State, ok, Effect};
 | 
					    {State, ok, Effect};
 | 
				
			||||||
apply(
 | 
					
 | 
				
			||||||
    Meta,
 | 
					apply(Meta, {down, DownPid, _}, #machine_state{client_ids = Ids,
 | 
				
			||||||
    {down, DownPid, _},
 | 
					                                               pids = Pids0} = State0) ->
 | 
				
			||||||
    #machine_state{
 | 
					 | 
				
			||||||
        client_ids = Ids,
 | 
					 | 
				
			||||||
        pids = Pids0
 | 
					 | 
				
			||||||
    } = State0
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    case maps:get(DownPid, Pids0, undefined) of
 | 
					    case maps:get(DownPid, Pids0, undefined) of
 | 
				
			||||||
        undefined ->
 | 
					        undefined ->
 | 
				
			||||||
            {State0, ok, []};
 | 
					            {State0, ok, []};
 | 
				
			||||||
        ClientIds ->
 | 
					        ClientIds ->
 | 
				
			||||||
            Ids1 = maps:without(ClientIds, Ids),
 | 
					            Ids1 = maps:without(ClientIds, Ids),
 | 
				
			||||||
            State = State0#machine_state{
 | 
					            State = State0#machine_state{client_ids = Ids1,
 | 
				
			||||||
                client_ids = Ids1,
 | 
					                                         pids = maps:remove(DownPid, Pids0)},
 | 
				
			||||||
                pids = maps:remove(DownPid, Pids0)
 | 
					            Effects = lists:map(fun(Id) ->
 | 
				
			||||||
            },
 | 
					                                        [{mod_call, rabbit_log, debug,
 | 
				
			||||||
            Effects = lists:map(
 | 
					                                          ["MQTT connection with client id '~ts' failed", [Id]]}]
 | 
				
			||||||
                fun(Id) ->
 | 
					                                end, ClientIds),
 | 
				
			||||||
                    [
 | 
					 | 
				
			||||||
                        {mod_call, rabbit_log, debug, [
 | 
					 | 
				
			||||||
                            "MQTT connection with client id '~ts' failed", [Id]
 | 
					 | 
				
			||||||
                        ]}
 | 
					 | 
				
			||||||
                    ]
 | 
					 | 
				
			||||||
                end,
 | 
					 | 
				
			||||||
                ClientIds
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            {State, ok, Effects ++ snapshot_effects(Meta, State)}
 | 
					            {State, ok, Effects ++ snapshot_effects(Meta, State)}
 | 
				
			||||||
    end;
 | 
					    end;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
apply(_Meta, {nodeup, Node}, State) ->
 | 
					apply(_Meta, {nodeup, Node}, State) ->
 | 
				
			||||||
    %% Work out if any pids that were disconnected are still
 | 
					    %% Work out if any pids that were disconnected are still
 | 
				
			||||||
    %% alive.
 | 
					    %% alive.
 | 
				
			||||||
| 
						 | 
					@ -173,69 +133,41 @@ apply(_Meta, {nodeup, Node}, State) ->
 | 
				
			||||||
    {State, ok, Effects};
 | 
					    {State, ok, Effects};
 | 
				
			||||||
apply(_Meta, {nodedown, _Node}, State) ->
 | 
					apply(_Meta, {nodedown, _Node}, State) ->
 | 
				
			||||||
    {State, ok};
 | 
					    {State, ok};
 | 
				
			||||||
apply(
 | 
					 | 
				
			||||||
    Meta,
 | 
					 | 
				
			||||||
    {leave, Node},
 | 
					 | 
				
			||||||
    #machine_state{
 | 
					 | 
				
			||||||
        client_ids = Ids,
 | 
					 | 
				
			||||||
        pids = Pids0
 | 
					 | 
				
			||||||
    } = State0
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    {Keep, Remove} = maps:fold(
 | 
					 | 
				
			||||||
        fun(ClientId, Pid, {In, Out}) ->
 | 
					 | 
				
			||||||
            case node(Pid) =/= Node of
 | 
					 | 
				
			||||||
                true ->
 | 
					 | 
				
			||||||
                    {In#{ClientId => Pid}, Out};
 | 
					 | 
				
			||||||
                false ->
 | 
					 | 
				
			||||||
                    {In, Out#{ClientId => Pid}}
 | 
					 | 
				
			||||||
            end
 | 
					 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
        {#{}, #{}},
 | 
					 | 
				
			||||||
        Ids
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    Effects = maps:fold(
 | 
					 | 
				
			||||||
        fun(ClientId, _Pid, Acc) ->
 | 
					 | 
				
			||||||
            Pid = maps:get(ClientId, Ids),
 | 
					 | 
				
			||||||
            [
 | 
					 | 
				
			||||||
                {demonitor, process, Pid},
 | 
					 | 
				
			||||||
                {mod_call, ?MODULE, notify_connection, [Pid, decommission_node]},
 | 
					 | 
				
			||||||
                {mod_call, rabbit_log, debug, [
 | 
					 | 
				
			||||||
                    "MQTT will remove client ID '~ts' from known "
 | 
					 | 
				
			||||||
                    "as its node has been decommissioned",
 | 
					 | 
				
			||||||
                    [ClientId]
 | 
					 | 
				
			||||||
                ]}
 | 
					 | 
				
			||||||
            ] ++ Acc
 | 
					 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
        [],
 | 
					 | 
				
			||||||
        Remove
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    State = State0#machine_state{
 | 
					apply(Meta, {leave, Node}, #machine_state{client_ids = Ids,
 | 
				
			||||||
        client_ids = Keep,
 | 
					                                          pids = Pids0} = State0) ->
 | 
				
			||||||
        pids = maps:without(maps:keys(Remove), Pids0)
 | 
					    {Keep, Remove} = maps:fold(
 | 
				
			||||||
    },
 | 
					                       fun (ClientId, Pid, {In, Out}) ->
 | 
				
			||||||
 | 
					                               case node(Pid) =/= Node of
 | 
				
			||||||
 | 
					                                   true ->
 | 
				
			||||||
 | 
					                                       {In#{ClientId => Pid}, Out};
 | 
				
			||||||
 | 
					                                   false ->
 | 
				
			||||||
 | 
					                                       {In, Out#{ClientId => Pid}}
 | 
				
			||||||
 | 
					                               end
 | 
				
			||||||
 | 
					                       end, {#{}, #{}}, Ids),
 | 
				
			||||||
 | 
					    Effects = maps:fold(fun (ClientId, _Pid, Acc) ->
 | 
				
			||||||
 | 
					                                Pid = maps:get(ClientId, Ids),
 | 
				
			||||||
 | 
					                                [
 | 
				
			||||||
 | 
					                                 {demonitor, process, Pid},
 | 
				
			||||||
 | 
					                                 {mod_call, ?MODULE, notify_connection, [Pid, decommission_node]},
 | 
				
			||||||
 | 
					                                 {mod_call, rabbit_log, debug,
 | 
				
			||||||
 | 
					                                  ["MQTT will remove client ID '~ts' from known "
 | 
				
			||||||
 | 
					                                   "as its node has been decommissioned", [ClientId]]}
 | 
				
			||||||
 | 
					                                ]  ++ Acc
 | 
				
			||||||
 | 
					                        end, [], Remove),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    State = State0#machine_state{client_ids = Keep,
 | 
				
			||||||
 | 
					                                 pids = maps:without(maps:keys(Remove), Pids0)},
 | 
				
			||||||
    {State, ok, Effects ++ snapshot_effects(Meta, State)};
 | 
					    {State, ok, Effects ++ snapshot_effects(Meta, State)};
 | 
				
			||||||
apply(_Meta, {machine_version, 0, 1}, {machine_state, Ids}) ->
 | 
					apply(_Meta, {machine_version, 0, 1}, {machine_state, Ids}) ->
 | 
				
			||||||
    Pids = maps:fold(
 | 
					    Pids = maps:fold(
 | 
				
			||||||
        fun(Id, Pid, Acc) ->
 | 
					             fun(Id, Pid, Acc) ->
 | 
				
			||||||
            maps:update_with(
 | 
					                     maps:update_with(Pid,
 | 
				
			||||||
                Pid,
 | 
					                                      fun(CIds) -> [Id | CIds] end,
 | 
				
			||||||
                fun(CIds) -> [Id | CIds] end,
 | 
					                                      [Id], Acc)
 | 
				
			||||||
                [Id],
 | 
					             end, #{}, Ids),
 | 
				
			||||||
                Acc
 | 
					    {#machine_state{client_ids = Ids,
 | 
				
			||||||
            )
 | 
					                    pids = Pids}, ok, []};
 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
        #{},
 | 
					 | 
				
			||||||
        Ids
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        #machine_state{
 | 
					 | 
				
			||||||
            client_ids = Ids,
 | 
					 | 
				
			||||||
            pids = Pids
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        ok,
 | 
					 | 
				
			||||||
        []
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
apply(_Meta, Unknown, State) ->
 | 
					apply(_Meta, Unknown, State) ->
 | 
				
			||||||
    logger:error("MQTT Raft state machine v1 received unknown command ~tp", [Unknown]),
 | 
					    logger:error("MQTT Raft state machine v1 received unknown command ~tp", [Unknown]),
 | 
				
			||||||
    {State, {error, {unknown_command, Unknown}}, []}.
 | 
					    {State, {error, {unknown_command, Unknown}}, []}.
 | 
				
			||||||
| 
						 | 
					@ -250,21 +182,17 @@ state_enter(_, _) ->
 | 
				
			||||||
    [].
 | 
					    [].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec overview(state()) -> map().
 | 
					-spec overview(state()) -> map().
 | 
				
			||||||
overview(#machine_state{
 | 
					overview(#machine_state{client_ids = ClientIds,
 | 
				
			||||||
    client_ids = ClientIds,
 | 
					                        pids = Pids}) ->
 | 
				
			||||||
    pids = Pids
 | 
					    #{num_client_ids => maps:size(ClientIds),
 | 
				
			||||||
}) ->
 | 
					      num_pids => maps:size(Pids)}.
 | 
				
			||||||
    #{
 | 
					 | 
				
			||||||
        num_client_ids => maps:size(ClientIds),
 | 
					 | 
				
			||||||
        num_pids => maps:size(Pids)
 | 
					 | 
				
			||||||
    }.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
%% ==========================
 | 
					%% ==========================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
%% Avoids blocking the Raft leader.
 | 
					%% Avoids blocking the Raft leader.
 | 
				
			||||||
-spec notify_connection(pid(), duplicate_id | decommission_node) -> pid().
 | 
					-spec notify_connection(pid(), duplicate_id | decommission_node) -> pid().
 | 
				
			||||||
notify_connection(Pid, Reason) ->
 | 
					notify_connection(Pid, Reason) ->
 | 
				
			||||||
    spawn(fun() -> gen_server2:cast(Pid, Reason) end).
 | 
					  spawn(fun() -> gen_server2:cast(Pid, Reason) end).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec snapshot_effects(map(), state()) -> ra_machine:effects().
 | 
					-spec snapshot_effects(map(), state()) -> ra_machine:effects().
 | 
				
			||||||
snapshot_effects(#{index := RaftIdx}, State) ->
 | 
					snapshot_effects(#{index := RaftIdx}, State) ->
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,12 +9,10 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-include("mqtt_machine_v0.hrl").
 | 
					-include("mqtt_machine_v0.hrl").
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-export([
 | 
					-export([init/1,
 | 
				
			||||||
    init/1,
 | 
					         apply/3,
 | 
				
			||||||
    apply/3,
 | 
					         state_enter/2,
 | 
				
			||||||
    state_enter/2,
 | 
					         notify_connection/2]).
 | 
				
			||||||
    notify_connection/2
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-type state() :: #machine_state{}.
 | 
					-type state() :: #machine_state{}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,10 +21,9 @@
 | 
				
			||||||
-type reply() :: {ok, term()} | {error, term()}.
 | 
					-type reply() :: {ok, term()} | {error, term()}.
 | 
				
			||||||
-type client_id() :: term().
 | 
					-type client_id() :: term().
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-type command() ::
 | 
					-type command() :: {register, client_id(), pid()} |
 | 
				
			||||||
    {register, client_id(), pid()}
 | 
					                   {unregister, client_id(), pid()} |
 | 
				
			||||||
    | {unregister, client_id(), pid()}
 | 
					                   list.
 | 
				
			||||||
    | list.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec init(config()) -> state().
 | 
					-spec init(config()) -> state().
 | 
				
			||||||
init(_Conf) ->
 | 
					init(_Conf) ->
 | 
				
			||||||
| 
						 | 
					@ -38,60 +35,53 @@ apply(_Meta, {register, ClientId, Pid}, #machine_state{client_ids = Ids} = State
 | 
				
			||||||
    {Effects, Ids1} =
 | 
					    {Effects, Ids1} =
 | 
				
			||||||
        case maps:find(ClientId, Ids) of
 | 
					        case maps:find(ClientId, Ids) of
 | 
				
			||||||
            {ok, OldPid} when Pid =/= OldPid ->
 | 
					            {ok, OldPid} when Pid =/= OldPid ->
 | 
				
			||||||
                Effects0 = [
 | 
					                Effects0 = [{demonitor, process, OldPid},
 | 
				
			||||||
                    {demonitor, process, OldPid},
 | 
					                            {monitor, process, Pid},
 | 
				
			||||||
                    {monitor, process, Pid},
 | 
					                            {mod_call, ?MODULE, notify_connection, [OldPid, duplicate_id]}],
 | 
				
			||||||
                    {mod_call, ?MODULE, notify_connection, [OldPid, duplicate_id]}
 | 
					 | 
				
			||||||
                ],
 | 
					 | 
				
			||||||
                {Effects0, maps:remove(ClientId, Ids)};
 | 
					                {Effects0, maps:remove(ClientId, Ids)};
 | 
				
			||||||
            _ ->
 | 
					            _ ->
 | 
				
			||||||
                Effects0 = [{monitor, process, Pid}],
 | 
					              Effects0 = [{monitor, process, Pid}],
 | 
				
			||||||
                {Effects0, Ids}
 | 
					              {Effects0, Ids}
 | 
				
			||||||
        end,
 | 
					        end,
 | 
				
			||||||
    State = State0#machine_state{client_ids = maps:put(ClientId, Pid, Ids1)},
 | 
					    State = State0#machine_state{client_ids = maps:put(ClientId, Pid, Ids1)},
 | 
				
			||||||
    {State, ok, Effects};
 | 
					    {State, ok, Effects};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
apply(Meta, {unregister, ClientId, Pid}, #machine_state{client_ids = Ids} = State0) ->
 | 
					apply(Meta, {unregister, ClientId, Pid}, #machine_state{client_ids = Ids} = State0) ->
 | 
				
			||||||
    State =
 | 
					    State = case maps:find(ClientId, Ids) of
 | 
				
			||||||
        case maps:find(ClientId, Ids) of
 | 
					      {ok, Pid}         -> State0#machine_state{client_ids = maps:remove(ClientId, Ids)};
 | 
				
			||||||
            {ok, Pid} -> State0#machine_state{client_ids = maps:remove(ClientId, Ids)};
 | 
					      %% don't delete client id that might belong to a newer connection
 | 
				
			||||||
            %% don't delete client id that might belong to a newer connection
 | 
					      %% that kicked the one with Pid out
 | 
				
			||||||
            %% that kicked the one with Pid out
 | 
					      {ok, _AnotherPid} -> State0;
 | 
				
			||||||
            {ok, _AnotherPid} -> State0;
 | 
					      error             -> State0
 | 
				
			||||||
            error -> State0
 | 
					    end,
 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
    Effects0 = [{demonitor, process, Pid}],
 | 
					    Effects0 = [{demonitor, process, Pid}],
 | 
				
			||||||
    %% snapshot only when the map has changed
 | 
					    %% snapshot only when the map has changed
 | 
				
			||||||
    Effects =
 | 
					    Effects = case State of
 | 
				
			||||||
        case State of
 | 
					      State0 -> Effects0;
 | 
				
			||||||
            State0 -> Effects0;
 | 
					      _      -> Effects0 ++ snapshot_effects(Meta, State)
 | 
				
			||||||
            _ -> Effects0 ++ snapshot_effects(Meta, State)
 | 
					    end,
 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
    {State, ok, Effects};
 | 
					    {State, ok, Effects};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
apply(_Meta, {down, DownPid, noconnection}, State) ->
 | 
					apply(_Meta, {down, DownPid, noconnection}, State) ->
 | 
				
			||||||
    %% Monitor the node the pid is on (see {nodeup, Node} below)
 | 
					    %% Monitor the node the pid is on (see {nodeup, Node} below)
 | 
				
			||||||
    %% so that we can detect when the node is re-connected and discover the
 | 
					    %% so that we can detect when the node is re-connected and discover the
 | 
				
			||||||
    %% actual fate of the connection processes on it
 | 
					    %% actual fate of the connection processes on it
 | 
				
			||||||
    Effect = {monitor, node, node(DownPid)},
 | 
					    Effect = {monitor, node, node(DownPid)},
 | 
				
			||||||
    {State, ok, Effect};
 | 
					    {State, ok, Effect};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
apply(Meta, {down, DownPid, _}, #machine_state{client_ids = Ids} = State0) ->
 | 
					apply(Meta, {down, DownPid, _}, #machine_state{client_ids = Ids} = State0) ->
 | 
				
			||||||
    Ids1 = maps:filter(
 | 
					    Ids1 = maps:filter(fun (_ClientId, Pid) when Pid =:= DownPid ->
 | 
				
			||||||
        fun
 | 
					                               false;
 | 
				
			||||||
            (_ClientId, Pid) when Pid =:= DownPid ->
 | 
					                            (_, _) ->
 | 
				
			||||||
                false;
 | 
					                               true
 | 
				
			||||||
            (_, _) ->
 | 
					                       end, Ids),
 | 
				
			||||||
                true
 | 
					 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
        Ids
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    State = State0#machine_state{client_ids = Ids1},
 | 
					    State = State0#machine_state{client_ids = Ids1},
 | 
				
			||||||
    Delta = maps:keys(Ids) -- maps:keys(Ids1),
 | 
					    Delta = maps:keys(Ids) -- maps:keys(Ids1),
 | 
				
			||||||
    Effects = lists:map(
 | 
					    Effects = lists:map(fun(Id) ->
 | 
				
			||||||
        fun(Id) ->
 | 
					                  [{mod_call, rabbit_log, debug,
 | 
				
			||||||
            [{mod_call, rabbit_log, debug, ["MQTT connection with client id '~ts' failed", [Id]]}]
 | 
					                    ["MQTT connection with client id '~ts' failed", [Id]]}] end, Delta),
 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
        Delta
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    {State, ok, Effects ++ snapshot_effects(Meta, State)};
 | 
					    {State, ok, Effects ++ snapshot_effects(Meta, State)};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
apply(_Meta, {nodeup, Node}, State) ->
 | 
					apply(_Meta, {nodeup, Node}, State) ->
 | 
				
			||||||
    %% Work out if any pids that were disconnected are still
 | 
					    %% Work out if any pids that were disconnected are still
 | 
				
			||||||
    %% alive.
 | 
					    %% alive.
 | 
				
			||||||
| 
						 | 
					@ -100,29 +90,25 @@ apply(_Meta, {nodeup, Node}, State) ->
 | 
				
			||||||
    {State, ok, Effects};
 | 
					    {State, ok, Effects};
 | 
				
			||||||
apply(_Meta, {nodedown, _Node}, State) ->
 | 
					apply(_Meta, {nodedown, _Node}, State) ->
 | 
				
			||||||
    {State, ok};
 | 
					    {State, ok};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
apply(Meta, {leave, Node}, #machine_state{client_ids = Ids} = State0) ->
 | 
					apply(Meta, {leave, Node}, #machine_state{client_ids = Ids} = State0) ->
 | 
				
			||||||
    Ids1 = maps:filter(fun(_ClientId, Pid) -> node(Pid) =/= Node end, Ids),
 | 
					    Ids1 = maps:filter(fun (_ClientId, Pid) -> node(Pid) =/= Node end, Ids),
 | 
				
			||||||
    Delta = maps:keys(Ids) -- maps:keys(Ids1),
 | 
					    Delta = maps:keys(Ids) -- maps:keys(Ids1),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Effects = lists:foldl(
 | 
					    Effects = lists:foldl(fun (ClientId, Acc) ->
 | 
				
			||||||
        fun(ClientId, Acc) ->
 | 
					                          Pid = maps:get(ClientId, Ids),
 | 
				
			||||||
            Pid = maps:get(ClientId, Ids),
 | 
					                          [
 | 
				
			||||||
            [
 | 
					                            {demonitor, process, Pid},
 | 
				
			||||||
                {demonitor, process, Pid},
 | 
					                            {mod_call, ?MODULE, notify_connection, [Pid, decommission_node]},
 | 
				
			||||||
                {mod_call, ?MODULE, notify_connection, [Pid, decommission_node]},
 | 
					                            {mod_call, rabbit_log, debug,
 | 
				
			||||||
                {mod_call, rabbit_log, debug, [
 | 
					                              ["MQTT will remove client ID '~ts' from known "
 | 
				
			||||||
                    "MQTT will remove client ID '~ts' from known "
 | 
					                               "as its node has been decommissioned", [ClientId]]}
 | 
				
			||||||
                    "as its node has been decommissioned",
 | 
					                          ]  ++ Acc
 | 
				
			||||||
                    [ClientId]
 | 
					                          end, [], Delta),
 | 
				
			||||||
                ]}
 | 
					 | 
				
			||||||
            ] ++ Acc
 | 
					 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
        [],
 | 
					 | 
				
			||||||
        Delta
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    State = State0#machine_state{client_ids = Ids1},
 | 
					    State = State0#machine_state{client_ids = Ids1},
 | 
				
			||||||
    {State, ok, Effects ++ snapshot_effects(Meta, State)};
 | 
					    {State, ok, Effects ++ snapshot_effects(Meta, State)};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
apply(_Meta, Unknown, State) ->
 | 
					apply(_Meta, Unknown, State) ->
 | 
				
			||||||
    logger:error("MQTT Raft state machine received an unknown command ~tp", [Unknown]),
 | 
					    logger:error("MQTT Raft state machine received an unknown command ~tp", [Unknown]),
 | 
				
			||||||
    {State, {error, {unknown_command, Unknown}}, []}.
 | 
					    {State, {error, {unknown_command, Unknown}}, []}.
 | 
				
			||||||
| 
						 | 
					@ -141,7 +127,7 @@ state_enter(_, _) ->
 | 
				
			||||||
%% Avoids blocking the Raft leader.
 | 
					%% Avoids blocking the Raft leader.
 | 
				
			||||||
-spec notify_connection(pid(), duplicate_id | decommission_node) -> pid().
 | 
					-spec notify_connection(pid(), duplicate_id | decommission_node) -> pid().
 | 
				
			||||||
notify_connection(Pid, Reason) ->
 | 
					notify_connection(Pid, Reason) ->
 | 
				
			||||||
    spawn(fun() -> gen_server2:cast(Pid, Reason) end).
 | 
					  spawn(fun() -> gen_server2:cast(Pid, Reason) end).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec snapshot_effects(map(), state()) -> ra_machine:effects().
 | 
					-spec snapshot_effects(map(), state()) -> ra_machine:effects().
 | 
				
			||||||
snapshot_effects(#{index := RaftIdx}, State) ->
 | 
					snapshot_effects(#{index := RaftIdx}, State) ->
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,15 +6,8 @@
 | 
				
			||||||
%%
 | 
					%%
 | 
				
			||||||
-module(mqtt_node).
 | 
					-module(mqtt_node).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-export([
 | 
					-export([start/0, node_id/0, server_id/0, all_node_ids/0, leave/1, trigger_election/0,
 | 
				
			||||||
    start/0,
 | 
					         delete/1]).
 | 
				
			||||||
    node_id/0,
 | 
					 | 
				
			||||||
    server_id/0,
 | 
					 | 
				
			||||||
    all_node_ids/0,
 | 
					 | 
				
			||||||
    leave/1,
 | 
					 | 
				
			||||||
    trigger_election/0,
 | 
					 | 
				
			||||||
    delete/1
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-define(ID_NAME, mqtt_node).
 | 
					-define(ID_NAME, mqtt_node).
 | 
				
			||||||
-define(START_TIMEOUT, 100_000).
 | 
					-define(START_TIMEOUT, 100_000).
 | 
				
			||||||
| 
						 | 
					@ -32,11 +25,8 @@ server_id(Node) ->
 | 
				
			||||||
    {?ID_NAME, Node}.
 | 
					    {?ID_NAME, Node}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
all_node_ids() ->
 | 
					all_node_ids() ->
 | 
				
			||||||
    [
 | 
					    [server_id(N) || N <- rabbit_nodes:all(),
 | 
				
			||||||
        server_id(N)
 | 
					                   can_participate_in_clientid_tracking(N)].
 | 
				
			||||||
     || N <- rabbit_nodes:all(),
 | 
					 | 
				
			||||||
        can_participate_in_clientid_tracking(N)
 | 
					 | 
				
			||||||
    ].
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
start() ->
 | 
					start() ->
 | 
				
			||||||
    %% 3s to 6s randomized
 | 
					    %% 3s to 6s randomized
 | 
				
			||||||
| 
						 | 
					@ -50,41 +40,34 @@ start(Delay, AttemptsLeft) ->
 | 
				
			||||||
    NodeId = server_id(),
 | 
					    NodeId = server_id(),
 | 
				
			||||||
    Nodes = compatible_peer_servers(),
 | 
					    Nodes = compatible_peer_servers(),
 | 
				
			||||||
    case ra_directory:uid_of(?RA_SYSTEM, ?ID_NAME) of
 | 
					    case ra_directory:uid_of(?RA_SYSTEM, ?ID_NAME) of
 | 
				
			||||||
        undefined ->
 | 
					          undefined ->
 | 
				
			||||||
            case Nodes of
 | 
					              case Nodes of
 | 
				
			||||||
                [] ->
 | 
					                  [] ->
 | 
				
			||||||
                    %% Since cluster members are not known ahead of time and initial boot can be happening in parallel,
 | 
					                      %% Since cluster members are not known ahead of time and initial boot can be happening in parallel,
 | 
				
			||||||
                    %% we wait and check a few times (up to a few seconds) to see if we can discover any peers to
 | 
					                      %% we wait and check a few times (up to a few seconds) to see if we can discover any peers to
 | 
				
			||||||
                    %% join before forming a cluster. This reduces the probability of N independent clusters being
 | 
					                      %% join before forming a cluster. This reduces the probability of N independent clusters being
 | 
				
			||||||
                    %% formed in the common scenario of N nodes booting in parallel e.g. because they were started
 | 
					                      %% formed in the common scenario of N nodes booting in parallel e.g. because they were started
 | 
				
			||||||
                    %% at the same time by a deployment tool.
 | 
					                      %% at the same time by a deployment tool.
 | 
				
			||||||
                    %%
 | 
					                      %%
 | 
				
			||||||
                    %% This scenario does not guarantee single cluster formation but without knowing the list of members
 | 
					                      %% This scenario does not guarantee single cluster formation but without knowing the list of members
 | 
				
			||||||
                    %% ahead of time, this is a best effort workaround. Multi-node consensus is apparently hard
 | 
					                      %% ahead of time, this is a best effort workaround. Multi-node consensus is apparently hard
 | 
				
			||||||
                    %% to achieve without having consensus around expected cluster members.
 | 
					                      %% to achieve without having consensus around expected cluster members.
 | 
				
			||||||
                    rabbit_log:info(
 | 
					                      rabbit_log:info("MQTT: will wait for ~tp more ms for cluster members to join before triggering a Raft leader election", [Delay]),
 | 
				
			||||||
                        "MQTT: will wait for ~tp more ms for cluster members to join before triggering a Raft leader election",
 | 
					                      timer:sleep(Delay),
 | 
				
			||||||
                        [Delay]
 | 
					                      start(Delay, AttemptsLeft - 1);
 | 
				
			||||||
                    ),
 | 
					                  Peers ->
 | 
				
			||||||
                    timer:sleep(Delay),
 | 
					                      %% Trigger an election.
 | 
				
			||||||
                    start(Delay, AttemptsLeft - 1);
 | 
					                      %% This is required when we start a node for the first time.
 | 
				
			||||||
                Peers ->
 | 
					                      %% Using default timeout because it supposed to reply fast.
 | 
				
			||||||
                    %% Trigger an election.
 | 
					                      rabbit_log:info("MQTT: discovered ~tp cluster peers that support client ID tracking", [length(Peers)]),
 | 
				
			||||||
                    %% This is required when we start a node for the first time.
 | 
					                      ok = start_server(),
 | 
				
			||||||
                    %% Using default timeout because it supposed to reply fast.
 | 
					                      _ = join_peers(NodeId, Peers),
 | 
				
			||||||
                    rabbit_log:info(
 | 
					                      ra:trigger_election(NodeId, ?RA_OPERATION_TIMEOUT)
 | 
				
			||||||
                        "MQTT: discovered ~tp cluster peers that support client ID tracking", [
 | 
					              end;
 | 
				
			||||||
                            length(Peers)
 | 
					          _ ->
 | 
				
			||||||
                        ]
 | 
					              _ = join_peers(NodeId, Nodes),
 | 
				
			||||||
                    ),
 | 
					              ok = ra:restart_server(?RA_SYSTEM, NodeId),
 | 
				
			||||||
                    ok = start_server(),
 | 
					              ra:trigger_election(NodeId, ?RA_OPERATION_TIMEOUT)
 | 
				
			||||||
                    _ = join_peers(NodeId, Peers),
 | 
					 | 
				
			||||||
                    ra:trigger_election(NodeId, ?RA_OPERATION_TIMEOUT)
 | 
					 | 
				
			||||||
            end;
 | 
					 | 
				
			||||||
        _ ->
 | 
					 | 
				
			||||||
            _ = join_peers(NodeId, Nodes),
 | 
					 | 
				
			||||||
            ok = ra:restart_server(?RA_SYSTEM, NodeId),
 | 
					 | 
				
			||||||
            ra:trigger_election(NodeId, ?RA_OPERATION_TIMEOUT)
 | 
					 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
compatible_peer_servers() ->
 | 
					compatible_peer_servers() ->
 | 
				
			||||||
| 
						 | 
					@ -95,15 +78,14 @@ start_server() ->
 | 
				
			||||||
    Nodes = compatible_peer_servers(),
 | 
					    Nodes = compatible_peer_servers(),
 | 
				
			||||||
    UId = ra:new_uid(ra_lib:to_binary(?ID_NAME)),
 | 
					    UId = ra:new_uid(ra_lib:to_binary(?ID_NAME)),
 | 
				
			||||||
    Timeout = application:get_env(kernel, net_ticktime, 60) + 5,
 | 
					    Timeout = application:get_env(kernel, net_ticktime, 60) + 5,
 | 
				
			||||||
    Conf = #{
 | 
					    Conf = #{cluster_name => ?ID_NAME,
 | 
				
			||||||
        cluster_name => ?ID_NAME,
 | 
					             id => NodeId,
 | 
				
			||||||
        id => NodeId,
 | 
					             uid => UId,
 | 
				
			||||||
        uid => UId,
 | 
					             friendly_name => atom_to_list(?ID_NAME),
 | 
				
			||||||
        friendly_name => atom_to_list(?ID_NAME),
 | 
					             initial_members => Nodes,
 | 
				
			||||||
        initial_members => Nodes,
 | 
					             log_init_args => #{uid => UId},
 | 
				
			||||||
        log_init_args => #{uid => UId},
 | 
					             tick_timeout => Timeout,
 | 
				
			||||||
        tick_timeout => Timeout,
 | 
					             machine => {module, mqtt_machine, #{}}
 | 
				
			||||||
        machine => {module, mqtt_machine, #{}}
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    ra:start_server(?RA_SYSTEM, Conf).
 | 
					    ra:start_server(?RA_SYSTEM, Conf).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -121,13 +103,11 @@ join_peers(NodeId, Nodes, RetriesLeft) ->
 | 
				
			||||||
    case ra:members(Nodes, ?START_TIMEOUT) of
 | 
					    case ra:members(Nodes, ?START_TIMEOUT) of
 | 
				
			||||||
        {ok, Members, _} ->
 | 
					        {ok, Members, _} ->
 | 
				
			||||||
            case lists:member(NodeId, Members) of
 | 
					            case lists:member(NodeId, Members) of
 | 
				
			||||||
                true -> ok;
 | 
					                true  -> ok;
 | 
				
			||||||
                false -> ra:add_member(Members, NodeId)
 | 
					                false -> ra:add_member(Members, NodeId)
 | 
				
			||||||
            end;
 | 
					            end;
 | 
				
			||||||
        {timeout, _} ->
 | 
					        {timeout, _} ->
 | 
				
			||||||
            rabbit_log:debug("MQTT: timed out contacting cluster peers, %s retries left", [
 | 
					            rabbit_log:debug("MQTT: timed out contacting cluster peers, %s retries left", [RetriesLeft]),
 | 
				
			||||||
                RetriesLeft
 | 
					 | 
				
			||||||
            ]),
 | 
					 | 
				
			||||||
            timer:sleep(?RETRY_INTERVAL),
 | 
					            timer:sleep(?RETRY_INTERVAL),
 | 
				
			||||||
            join_peers(NodeId, Nodes, RetriesLeft - 1);
 | 
					            join_peers(NodeId, Nodes, RetriesLeft - 1);
 | 
				
			||||||
        Err ->
 | 
					        Err ->
 | 
				
			||||||
| 
						 | 
					@ -148,12 +128,12 @@ leave(Node) ->
 | 
				
			||||||
can_participate_in_clientid_tracking(Node) ->
 | 
					can_participate_in_clientid_tracking(Node) ->
 | 
				
			||||||
    case rpc:call(Node, mqtt_machine, module_info, []) of
 | 
					    case rpc:call(Node, mqtt_machine, module_info, []) of
 | 
				
			||||||
        {badrpc, _} -> false;
 | 
					        {badrpc, _} -> false;
 | 
				
			||||||
        _ -> true
 | 
					        _           -> true
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec delete(Args) -> Ret when
 | 
					-spec delete(Args) -> Ret when
 | 
				
			||||||
    Args :: rabbit_feature_flags:enable_callback_args(),
 | 
					      Args :: rabbit_feature_flags:enable_callback_args(),
 | 
				
			||||||
    Ret :: rabbit_feature_flags:enable_callback_ret().
 | 
					      Ret :: rabbit_feature_flags:enable_callback_ret().
 | 
				
			||||||
delete(_) ->
 | 
					delete(_) ->
 | 
				
			||||||
    RaNodes = all_node_ids(),
 | 
					    RaNodes = all_node_ids(),
 | 
				
			||||||
    Nodes = lists:map(fun({_, N}) -> N end, RaNodes),
 | 
					    Nodes = lists:map(fun({_, N}) -> N end, RaNodes),
 | 
				
			||||||
| 
						 | 
					@ -171,13 +151,12 @@ delete(_) ->
 | 
				
			||||||
                {ok, _Leader} ->
 | 
					                {ok, _Leader} ->
 | 
				
			||||||
                    rabbit_log:info("Successfully deleted Ra cluster ~s", [?ID_NAME]),
 | 
					                    rabbit_log:info("Successfully deleted Ra cluster ~s", [?ID_NAME]),
 | 
				
			||||||
                    ok;
 | 
					                    ok;
 | 
				
			||||||
                {error, _} = Err ->
 | 
					                {error, _}  = Err ->
 | 
				
			||||||
                    rabbit_log:info("Failed to delete Ra cluster ~s: ~p", [?ID_NAME, Err]),
 | 
					                    rabbit_log:info("Failed to delete Ra cluster ~s: ~p", [?ID_NAME, Err]),
 | 
				
			||||||
                    Err
 | 
					                    Err
 | 
				
			||||||
            catch
 | 
					            catch exit:{{shutdown, delete}, _Stacktrace} ->
 | 
				
			||||||
                exit:{{shutdown, delete}, _Stacktrace} ->
 | 
					                      rabbit_log:info("Ra cluster ~s already being deleted", [?ID_NAME]),
 | 
				
			||||||
                    rabbit_log:info("Ra cluster ~s already being deleted", [?ID_NAME]),
 | 
					                      ok
 | 
				
			||||||
                    ok
 | 
					 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
    after
 | 
					    after
 | 
				
			||||||
        true = global:del_lock(LockId, Nodes),
 | 
					        true = global:del_lock(LockId, Nodes),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,13 +13,11 @@
 | 
				
			||||||
-include_lib("stdlib/include/assert.hrl").
 | 
					-include_lib("stdlib/include/assert.hrl").
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-export([start/2, stop/1]).
 | 
					-export([start/2, stop/1]).
 | 
				
			||||||
-export([
 | 
					-export([emit_connection_info_all/4,
 | 
				
			||||||
    emit_connection_info_all/4,
 | 
					         emit_connection_info_local/3,
 | 
				
			||||||
    emit_connection_info_local/3,
 | 
					         close_local_client_connections/1,
 | 
				
			||||||
    close_local_client_connections/1,
 | 
					         %% Exported for tests, but could also be used for debugging.
 | 
				
			||||||
    %% Exported for tests, but could also be used for debugging.
 | 
					         local_connection_pids/0]).
 | 
				
			||||||
    local_connection_pids/0
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
start(normal, []) ->
 | 
					start(normal, []) ->
 | 
				
			||||||
    init_global_counters(),
 | 
					    init_global_counters(),
 | 
				
			||||||
| 
						 | 
					@ -33,11 +31,10 @@ start(normal, []) ->
 | 
				
			||||||
            ok
 | 
					            ok
 | 
				
			||||||
    end,
 | 
					    end,
 | 
				
			||||||
    Result = rabbit_mqtt_sup:start_link({Listeners, SslListeners}, []),
 | 
					    Result = rabbit_mqtt_sup:start_link({Listeners, SslListeners}, []),
 | 
				
			||||||
    EMPid =
 | 
					    EMPid = case rabbit_event:start_link() of
 | 
				
			||||||
        case rabbit_event:start_link() of
 | 
					                {ok, Pid}                       -> Pid;
 | 
				
			||||||
            {ok, Pid} -> Pid;
 | 
					                {error, {already_started, Pid}} -> Pid
 | 
				
			||||||
            {error, {already_started, Pid}} -> Pid
 | 
					            end,
 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
    gen_event:add_handler(EMPid, rabbit_mqtt_internal_event_handler, []),
 | 
					    gen_event:add_handler(EMPid, rabbit_mqtt_internal_event_handler, []),
 | 
				
			||||||
    Result.
 | 
					    Result.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -55,15 +52,9 @@ emit_connection_info_all(Nodes, Items, Ref, AggregatorPid) ->
 | 
				
			||||||
            %% remaining nodes, we send back 'finished' so that the CLI does not time out.
 | 
					            %% remaining nodes, we send back 'finished' so that the CLI does not time out.
 | 
				
			||||||
            [AggregatorPid ! {Ref, finished} || _ <- lists:seq(1, length(Nodes) - 1)];
 | 
					            [AggregatorPid ! {Ref, finished} || _ <- lists:seq(1, length(Nodes) - 1)];
 | 
				
			||||||
        false ->
 | 
					        false ->
 | 
				
			||||||
            Pids = [
 | 
					            Pids = [spawn_link(Node, ?MODULE, emit_connection_info_local,
 | 
				
			||||||
                spawn_link(
 | 
					                               [Items, Ref, AggregatorPid])
 | 
				
			||||||
                    Node,
 | 
					                    || Node <- Nodes],
 | 
				
			||||||
                    ?MODULE,
 | 
					 | 
				
			||||||
                    emit_connection_info_local,
 | 
					 | 
				
			||||||
                    [Items, Ref, AggregatorPid]
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
             || Node <- Nodes
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
            rabbit_control_misc:await_emitters_termination(Pids)
 | 
					            rabbit_control_misc:await_emitters_termination(Pids)
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -74,23 +65,17 @@ emit_connection_info_local(Items, Ref, AggregatorPid) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
emit_connection_info(Items, Ref, AggregatorPid, Pids) ->
 | 
					emit_connection_info(Items, Ref, AggregatorPid, Pids) ->
 | 
				
			||||||
    rabbit_control_misc:emitting_map_with_exit_handler(
 | 
					    rabbit_control_misc:emitting_map_with_exit_handler(
 | 
				
			||||||
        AggregatorPid,
 | 
					      AggregatorPid, Ref,
 | 
				
			||||||
        Ref,
 | 
					      fun(Pid) ->
 | 
				
			||||||
        fun(Pid) ->
 | 
					              rabbit_mqtt_reader:info(Pid, Items)
 | 
				
			||||||
            rabbit_mqtt_reader:info(Pid, Items)
 | 
					      end, Pids).
 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
        Pids
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec close_local_client_connections(string() | binary()) -> {'ok', non_neg_integer()}.
 | 
					-spec close_local_client_connections(string() | binary()) -> {'ok', non_neg_integer()}.
 | 
				
			||||||
close_local_client_connections(Reason) ->
 | 
					close_local_client_connections(Reason) ->
 | 
				
			||||||
    Pids = local_connection_pids(),
 | 
					    Pids = local_connection_pids(),
 | 
				
			||||||
    lists:foreach(
 | 
					    lists:foreach(fun(Pid) ->
 | 
				
			||||||
        fun(Pid) ->
 | 
					                          rabbit_mqtt_reader:close_connection(Pid, Reason)
 | 
				
			||||||
            rabbit_mqtt_reader:close_connection(Pid, Reason)
 | 
					                  end, Pids),
 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
        Pids
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    {ok, length(Pids)}.
 | 
					    {ok, length(Pids)}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec local_connection_pids() -> [pid()].
 | 
					-spec local_connection_pids() -> [pid()].
 | 
				
			||||||
| 
						 | 
					@ -101,12 +86,9 @@ local_connection_pids() ->
 | 
				
			||||||
            lists:filter(fun(Pid) -> node(Pid) =:= node() end, AllPids);
 | 
					            lists:filter(fun(Pid) -> node(Pid) =:= node() end, AllPids);
 | 
				
			||||||
        false ->
 | 
					        false ->
 | 
				
			||||||
            PgScope = persistent_term:get(?PG_SCOPE),
 | 
					            PgScope = persistent_term:get(?PG_SCOPE),
 | 
				
			||||||
            lists:flatmap(
 | 
					            lists:flatmap(fun(Group) ->
 | 
				
			||||||
                fun(Group) ->
 | 
					                                  pg:get_local_members(PgScope, Group)
 | 
				
			||||||
                    pg:get_local_members(PgScope, Group)
 | 
					                          end, pg:which_groups(PgScope))
 | 
				
			||||||
                end,
 | 
					 | 
				
			||||||
                pg:which_groups(PgScope)
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
init_global_counters() ->
 | 
					init_global_counters() ->
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,13 +9,8 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-include("mqtt_machine.hrl").
 | 
					-include("mqtt_machine.hrl").
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-export([
 | 
					-export([register/2, register/3, unregister/2,
 | 
				
			||||||
    register/2, register/3,
 | 
					         list/0, list_pids/0, leave/1]).
 | 
				
			||||||
    unregister/2,
 | 
					 | 
				
			||||||
    list/0,
 | 
					 | 
				
			||||||
    list_pids/0,
 | 
					 | 
				
			||||||
    leave/1
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
%%----------------------------------------------------------------------------
 | 
					%%----------------------------------------------------------------------------
 | 
				
			||||||
-spec register(term(), pid()) -> {ok, reference()} | {error, term()}.
 | 
					-spec register(term(), pid()) -> {ok, reference()} | {error, term()}.
 | 
				
			||||||
| 
						 | 
					@ -26,7 +21,7 @@ register(ClientId, Pid) ->
 | 
				
			||||||
            case ra:members(NodeId) of
 | 
					            case ra:members(NodeId) of
 | 
				
			||||||
                {ok, _, Leader} ->
 | 
					                {ok, _, Leader} ->
 | 
				
			||||||
                    register(Leader, ClientId, Pid);
 | 
					                    register(Leader, ClientId, Pid);
 | 
				
			||||||
                _ = Error ->
 | 
					                 _ = Error ->
 | 
				
			||||||
                    Error
 | 
					                    Error
 | 
				
			||||||
            end;
 | 
					            end;
 | 
				
			||||||
        Leader ->
 | 
					        Leader ->
 | 
				
			||||||
| 
						 | 
					@ -65,31 +60,25 @@ list(QF) ->
 | 
				
			||||||
        undefined ->
 | 
					        undefined ->
 | 
				
			||||||
            NodeIds = mqtt_node:all_node_ids(),
 | 
					            NodeIds = mqtt_node:all_node_ids(),
 | 
				
			||||||
            case ra:leader_query(NodeIds, QF) of
 | 
					            case ra:leader_query(NodeIds, QF) of
 | 
				
			||||||
                {ok, {_, Result}, _} ->
 | 
					                {ok, {_, Result}, _} -> Result;
 | 
				
			||||||
                    Result;
 | 
					                {timeout, _}      ->
 | 
				
			||||||
                {timeout, _} ->
 | 
					                    rabbit_log:debug("~ts:list/1 leader query timed out",
 | 
				
			||||||
                    rabbit_log:debug(
 | 
					                                     [?MODULE]),
 | 
				
			||||||
                        "~ts:list/1 leader query timed out",
 | 
					 | 
				
			||||||
                        [?MODULE]
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    []
 | 
					                    []
 | 
				
			||||||
            end;
 | 
					            end;
 | 
				
			||||||
        Leader ->
 | 
					        Leader ->
 | 
				
			||||||
            case ra:leader_query(Leader, QF) of
 | 
					            case ra:leader_query(Leader, QF) of
 | 
				
			||||||
                {ok, {_, Result}, _} ->
 | 
					                {ok, {_, Result}, _} -> Result;
 | 
				
			||||||
                    Result;
 | 
					 | 
				
			||||||
                {error, _} ->
 | 
					                {error, _} ->
 | 
				
			||||||
                    [];
 | 
					                    [];
 | 
				
			||||||
                {timeout, _} ->
 | 
					                {timeout, _}      ->
 | 
				
			||||||
                    rabbit_log:debug(
 | 
					                    rabbit_log:debug("~ts:list/1 leader query timed out",
 | 
				
			||||||
                        "~ts:list/1 leader query timed out",
 | 
					                                     [?MODULE]),
 | 
				
			||||||
                        [?MODULE]
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    []
 | 
					                    []
 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec leave(binary()) -> ok | timeout | nodedown.
 | 
					-spec leave(binary()) ->  ok | timeout | nodedown.
 | 
				
			||||||
leave(NodeBin) ->
 | 
					leave(NodeBin) ->
 | 
				
			||||||
    Node = binary_to_atom(NodeBin, utf8),
 | 
					    Node = binary_to_atom(NodeBin, utf8),
 | 
				
			||||||
    ServerId = mqtt_node:server_id(),
 | 
					    ServerId = mqtt_node:server_id(),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,15 +10,13 @@
 | 
				
			||||||
-include("rabbit_mqtt_packet.hrl").
 | 
					-include("rabbit_mqtt_packet.hrl").
 | 
				
			||||||
-compile({no_auto_import, [size/1]}).
 | 
					-compile({no_auto_import, [size/1]}).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-export([
 | 
					-export([init/0,
 | 
				
			||||||
    init/0,
 | 
					         insert/3,
 | 
				
			||||||
    insert/3,
 | 
					         confirm/3,
 | 
				
			||||||
    confirm/3,
 | 
					         reject/2,
 | 
				
			||||||
    reject/2,
 | 
					         remove_queue/2,
 | 
				
			||||||
    remove_queue/2,
 | 
					         size/1,
 | 
				
			||||||
    size/1,
 | 
					         contains/2]).
 | 
				
			||||||
    contains/2
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
%% As done in OTP's sets module:
 | 
					%% As done in OTP's sets module:
 | 
				
			||||||
%% Empty list is cheaper to serialize than atom.
 | 
					%% Empty list is cheaper to serialize than atom.
 | 
				
			||||||
| 
						 | 
					@ -41,32 +39,26 @@ contains(PktId, State) ->
 | 
				
			||||||
    maps:is_key(PktId, State).
 | 
					    maps:is_key(PktId, State).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec insert(packet_id(), [queue_name()], state()) -> state().
 | 
					-spec insert(packet_id(), [queue_name()], state()) -> state().
 | 
				
			||||||
insert(PktId, QNames, State) when
 | 
					insert(PktId, QNames, State)
 | 
				
			||||||
    is_integer(PktId) andalso
 | 
					  when is_integer(PktId) andalso
 | 
				
			||||||
        PktId > 0 andalso
 | 
					       PktId > 0 andalso
 | 
				
			||||||
        not is_map_key(PktId, State)
 | 
					       not is_map_key(PktId, State) ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    QMap = maps:from_keys(QNames, ?VALUE),
 | 
					    QMap = maps:from_keys(QNames, ?VALUE),
 | 
				
			||||||
    maps:put(PktId, QMap, State).
 | 
					    maps:put(PktId, QMap, State).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec confirm([packet_id()], queue_name(), state()) ->
 | 
					-spec confirm([packet_id()], queue_name(), state()) ->
 | 
				
			||||||
    {[packet_id()], state()}.
 | 
					    {[packet_id()], state()}.
 | 
				
			||||||
confirm(PktIds, QName, State0) ->
 | 
					confirm(PktIds, QName, State0) ->
 | 
				
			||||||
    {L0, State} = lists:foldl(
 | 
					    {L0, State} = lists:foldl(fun(PktId, Acc) ->
 | 
				
			||||||
        fun(PktId, Acc) ->
 | 
					                                      confirm_one(PktId, QName, Acc)
 | 
				
			||||||
            confirm_one(PktId, QName, Acc)
 | 
					                              end, {[], State0}, PktIds),
 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
        {[], State0},
 | 
					 | 
				
			||||||
        PktIds
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    L = lists:reverse(L0),
 | 
					    L = lists:reverse(L0),
 | 
				
			||||||
    {L, State}.
 | 
					    {L, State}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec reject(packet_id(), state()) ->
 | 
					-spec reject(packet_id(), state()) ->
 | 
				
			||||||
    {ok, state()} | {error, not_found}.
 | 
					    {ok, state()} | {error, not_found}.
 | 
				
			||||||
reject(PktId, State0) when
 | 
					reject(PktId, State0)
 | 
				
			||||||
    is_integer(PktId)
 | 
					  when is_integer(PktId) ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    case maps:take(PktId, State0) of
 | 
					    case maps:take(PktId, State0) of
 | 
				
			||||||
        {_, State} ->
 | 
					        {_, State} ->
 | 
				
			||||||
            {ok, State};
 | 
					            {ok, State};
 | 
				
			||||||
| 
						 | 
					@ -79,31 +71,24 @@ reject(PktId, State0) when
 | 
				
			||||||
    {[packet_id()], state()}.
 | 
					    {[packet_id()], state()}.
 | 
				
			||||||
remove_queue(QName, State) ->
 | 
					remove_queue(QName, State) ->
 | 
				
			||||||
    PktIds = maps:fold(
 | 
					    PktIds = maps:fold(
 | 
				
			||||||
        fun
 | 
					               fun(PktId, QMap, PktIds)
 | 
				
			||||||
            (PktId, QMap, PktIds) when
 | 
					                     when is_map_key(QName, QMap) ->
 | 
				
			||||||
                is_map_key(QName, QMap)
 | 
					                       [PktId | PktIds];
 | 
				
			||||||
            ->
 | 
					                  (_, _, PktIds) ->
 | 
				
			||||||
                [PktId | PktIds];
 | 
					                       PktIds
 | 
				
			||||||
            (_, _, PktIds) ->
 | 
					               end, [], State),
 | 
				
			||||||
                PktIds
 | 
					 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
        [],
 | 
					 | 
				
			||||||
        State
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    confirm(lists:sort(PktIds), QName, State).
 | 
					    confirm(lists:sort(PktIds), QName, State).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
%% INTERNAL
 | 
					%% INTERNAL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
confirm_one(PktId, QName, {PktIds, State0}) when
 | 
					confirm_one(PktId, QName, {PktIds, State0})
 | 
				
			||||||
    is_integer(PktId)
 | 
					  when is_integer(PktId) ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    case maps:take(PktId, State0) of
 | 
					    case maps:take(PktId, State0) of
 | 
				
			||||||
        {QMap0, State1} when
 | 
					        {QMap0, State1}
 | 
				
			||||||
            is_map_key(QName, QMap0) andalso
 | 
					          when is_map_key(QName, QMap0)
 | 
				
			||||||
                map_size(QMap0) =:= 1
 | 
					               andalso map_size(QMap0) =:= 1 ->
 | 
				
			||||||
        ->
 | 
					 | 
				
			||||||
            %% last queue confirm
 | 
					            %% last queue confirm
 | 
				
			||||||
            {[PktId | PktIds], State1};
 | 
					            {[PktId| PktIds], State1};
 | 
				
			||||||
        {QMap0, State1} ->
 | 
					        {QMap0, State1} ->
 | 
				
			||||||
            QMap = maps:remove(QName, QMap0),
 | 
					            QMap = maps:remove(QName, QMap0),
 | 
				
			||||||
            State = maps:put(PktId, QMap, State1),
 | 
					            State = maps:put(PktId, QMap, State1),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,19 +12,17 @@
 | 
				
			||||||
-export([track_client_id_in_ra/0]).
 | 
					-export([track_client_id_in_ra/0]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-rabbit_feature_flag(
 | 
					-rabbit_feature_flag(
 | 
				
			||||||
    {?QUEUE_TYPE_QOS_0, #{
 | 
					   {?QUEUE_TYPE_QOS_0,
 | 
				
			||||||
        desc => "Support pseudo queue type for MQTT QoS 0 subscribers omitting a queue process",
 | 
					    #{desc          => "Support pseudo queue type for MQTT QoS 0 subscribers omitting a queue process",
 | 
				
			||||||
        stability => stable
 | 
					      stability     => stable
 | 
				
			||||||
    }}
 | 
					     }}).
 | 
				
			||||||
).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-rabbit_feature_flag(
 | 
					-rabbit_feature_flag(
 | 
				
			||||||
    {delete_ra_cluster_mqtt_node, #{
 | 
					   {delete_ra_cluster_mqtt_node,
 | 
				
			||||||
        desc => "Delete Ra cluster 'mqtt_node' since MQTT client IDs are tracked locally",
 | 
					    #{desc          => "Delete Ra cluster 'mqtt_node' since MQTT client IDs are tracked locally",
 | 
				
			||||||
        stability => stable,
 | 
					      stability     => stable,
 | 
				
			||||||
        callbacks => #{enable => {mqtt_node, delete}}
 | 
					      callbacks     => #{enable => {mqtt_node, delete}}
 | 
				
			||||||
    }}
 | 
					     }}).
 | 
				
			||||||
).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec track_client_id_in_ra() -> boolean().
 | 
					-spec track_client_id_in_ra() -> boolean().
 | 
				
			||||||
track_client_id_in_ra() ->
 | 
					track_client_id_in_ra() ->
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,9 +28,7 @@ handle_event({event, vhost_deleted, Info, _, _}, ?STATE) ->
 | 
				
			||||||
    {ok, ?STATE};
 | 
					    {ok, ?STATE};
 | 
				
			||||||
handle_event({event, maintenance_connections_closed, _Info, _, _}, ?STATE) ->
 | 
					handle_event({event, maintenance_connections_closed, _Info, _, _}, ?STATE) ->
 | 
				
			||||||
    %% we should close our connections
 | 
					    %% we should close our connections
 | 
				
			||||||
    {ok, NConnections} = rabbit_mqtt:close_local_client_connections(
 | 
					    {ok, NConnections} = rabbit_mqtt:close_local_client_connections("node is being put into maintenance mode"),
 | 
				
			||||||
        "node is being put into maintenance mode"
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    rabbit_log:warning("Closed ~b local MQTT client connections", [NConnections]),
 | 
					    rabbit_log:warning("Closed ~b local MQTT client connections", [NConnections]),
 | 
				
			||||||
    {ok, ?STATE};
 | 
					    {ok, ?STATE};
 | 
				
			||||||
handle_event(_Event, ?STATE) ->
 | 
					handle_event(_Event, ?STATE) ->
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,26 +1,23 @@
 | 
				
			||||||
-module(rabbit_mqtt_keepalive).
 | 
					-module(rabbit_mqtt_keepalive).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-export([
 | 
					-export([init/0,
 | 
				
			||||||
    init/0,
 | 
					         start/2,
 | 
				
			||||||
    start/2,
 | 
					         handle/2,
 | 
				
			||||||
    handle/2,
 | 
					         start_timer/1,
 | 
				
			||||||
    start_timer/1,
 | 
					         cancel_timer/1,
 | 
				
			||||||
    cancel_timer/1,
 | 
					         interval_secs/1]).
 | 
				
			||||||
    interval_secs/1
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-export_type([state/0]).
 | 
					-export_type([state/0]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-record(state, {
 | 
					-record(state, {
 | 
				
			||||||
    %% Keep Alive value as sent in the CONNECT packet.
 | 
					          %% Keep Alive value as sent in the CONNECT packet.
 | 
				
			||||||
    interval_secs :: pos_integer(),
 | 
					          interval_secs :: pos_integer(),
 | 
				
			||||||
    timer :: reference(),
 | 
					          timer :: reference(),
 | 
				
			||||||
    socket :: inet:socket(),
 | 
					          socket :: inet:socket(),
 | 
				
			||||||
    recv_oct :: non_neg_integer(),
 | 
					          recv_oct :: non_neg_integer(),
 | 
				
			||||||
    received :: boolean()
 | 
					          received :: boolean()}).
 | 
				
			||||||
}).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-opaque state() :: disabled | #state{}.
 | 
					-opaque(state() :: disabled | #state{}).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec init() -> state().
 | 
					-spec init() -> state().
 | 
				
			||||||
init() ->
 | 
					init() ->
 | 
				
			||||||
| 
						 | 
					@ -29,9 +26,8 @@ init() ->
 | 
				
			||||||
-spec start(IntervalSeconds :: non_neg_integer(), inet:socket()) -> ok.
 | 
					-spec start(IntervalSeconds :: non_neg_integer(), inet:socket()) -> ok.
 | 
				
			||||||
start(0, _Sock) ->
 | 
					start(0, _Sock) ->
 | 
				
			||||||
    ok;
 | 
					    ok;
 | 
				
			||||||
start(Seconds, Sock) when
 | 
					start(Seconds, Sock)
 | 
				
			||||||
    is_integer(Seconds) andalso Seconds > 0
 | 
					  when is_integer(Seconds) andalso Seconds > 0 ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    self() ! {keepalive, {init, Seconds, Sock}},
 | 
					    self() ! {keepalive, {init, Seconds, Sock}},
 | 
				
			||||||
    ok.
 | 
					    ok.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,28 +36,20 @@ start(Seconds, Sock) when
 | 
				
			||||||
handle({init, IntervalSecs, Sock}, _State) ->
 | 
					handle({init, IntervalSecs, Sock}, _State) ->
 | 
				
			||||||
    case rabbit_net:getstat(Sock, [recv_oct]) of
 | 
					    case rabbit_net:getstat(Sock, [recv_oct]) of
 | 
				
			||||||
        {ok, [{recv_oct, RecvOct}]} ->
 | 
					        {ok, [{recv_oct, RecvOct}]} ->
 | 
				
			||||||
            {ok, #state{
 | 
					            {ok, #state{interval_secs = IntervalSecs,
 | 
				
			||||||
                interval_secs = IntervalSecs,
 | 
					                        timer = start_timer0(IntervalSecs),
 | 
				
			||||||
                timer = start_timer0(IntervalSecs),
 | 
					                        socket = Sock,
 | 
				
			||||||
                socket = Sock,
 | 
					                        recv_oct = RecvOct,
 | 
				
			||||||
                recv_oct = RecvOct,
 | 
					                        received = true}};
 | 
				
			||||||
                received = true
 | 
					 | 
				
			||||||
            }};
 | 
					 | 
				
			||||||
        {error, _} = Err ->
 | 
					        {error, _} = Err ->
 | 
				
			||||||
            Err
 | 
					            Err
 | 
				
			||||||
    end;
 | 
					    end;
 | 
				
			||||||
handle(
 | 
					handle(check, State = #state{socket = Sock,
 | 
				
			||||||
    check,
 | 
					                             recv_oct = SameRecvOct,
 | 
				
			||||||
    State = #state{
 | 
					                             received = ReceivedPreviously}) ->
 | 
				
			||||||
        socket = Sock,
 | 
					 | 
				
			||||||
        recv_oct = SameRecvOct,
 | 
					 | 
				
			||||||
        received = ReceivedPreviously
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    case rabbit_net:getstat(Sock, [recv_oct]) of
 | 
					    case rabbit_net:getstat(Sock, [recv_oct]) of
 | 
				
			||||||
        {ok, [{recv_oct, SameRecvOct}]} when
 | 
					        {ok, [{recv_oct, SameRecvOct}]}
 | 
				
			||||||
            ReceivedPreviously
 | 
					          when ReceivedPreviously ->
 | 
				
			||||||
        ->
 | 
					 | 
				
			||||||
            %% Did not receive from socket for the 1st time.
 | 
					            %% Did not receive from socket for the 1st time.
 | 
				
			||||||
            {ok, start_timer(State#state{received = false})};
 | 
					            {ok, start_timer(State#state{received = false})};
 | 
				
			||||||
        {ok, [{recv_oct, SameRecvOct}]} ->
 | 
					        {ok, [{recv_oct, SameRecvOct}]} ->
 | 
				
			||||||
| 
						 | 
					@ -69,11 +57,8 @@ handle(
 | 
				
			||||||
            {error, timeout};
 | 
					            {error, timeout};
 | 
				
			||||||
        {ok, [{recv_oct, NewRecvOct}]} ->
 | 
					        {ok, [{recv_oct, NewRecvOct}]} ->
 | 
				
			||||||
            %% Received from socket.
 | 
					            %% Received from socket.
 | 
				
			||||||
            {ok,
 | 
					            {ok, start_timer(State#state{recv_oct = NewRecvOct,
 | 
				
			||||||
                start_timer(State#state{
 | 
					                                         received = true})};
 | 
				
			||||||
                    recv_oct = NewRecvOct,
 | 
					 | 
				
			||||||
                    received = true
 | 
					 | 
				
			||||||
                })};
 | 
					 | 
				
			||||||
        {error, _} = Err ->
 | 
					        {error, _} = Err ->
 | 
				
			||||||
            Err
 | 
					            Err
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
| 
						 | 
					@ -89,13 +74,10 @@ start_timer0(KeepAliveSeconds) ->
 | 
				
			||||||
    erlang:send_after(timer_ms(KeepAliveSeconds), self(), {keepalive, check}).
 | 
					    erlang:send_after(timer_ms(KeepAliveSeconds), self(), {keepalive, check}).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec cancel_timer(state()) -> state().
 | 
					-spec cancel_timer(state()) -> state().
 | 
				
			||||||
cancel_timer(#state{timer = Ref} = State) when
 | 
					cancel_timer(#state{timer = Ref} = State)
 | 
				
			||||||
    is_reference(Ref)
 | 
					  when is_reference(Ref) ->
 | 
				
			||||||
->
 | 
					    ok = erlang:cancel_timer(Ref, [{async, true},
 | 
				
			||||||
    ok = erlang:cancel_timer(Ref, [
 | 
					                                   {info, false}]),
 | 
				
			||||||
        {async, true},
 | 
					 | 
				
			||||||
        {info, false}
 | 
					 | 
				
			||||||
    ]),
 | 
					 | 
				
			||||||
    State;
 | 
					    State;
 | 
				
			||||||
cancel_timer(disabled) ->
 | 
					cancel_timer(disabled) ->
 | 
				
			||||||
    disabled.
 | 
					    disabled.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,148 +24,119 @@
 | 
				
			||||||
initial_state() -> none.
 | 
					initial_state() -> none.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec parse(binary(), state()) ->
 | 
					-spec parse(binary(), state()) ->
 | 
				
			||||||
    {more, state()}
 | 
					    {more, state()} |
 | 
				
			||||||
    | {ok, mqtt_packet(), binary()}
 | 
					    {ok, mqtt_packet(), binary()} |
 | 
				
			||||||
    | {error, any()}.
 | 
					    {error, any()}.
 | 
				
			||||||
parse(<<>>, none) ->
 | 
					parse(<<>>, none) ->
 | 
				
			||||||
    {more, fun(Bin) -> parse(Bin, none) end};
 | 
					    {more, fun(Bin) -> parse(Bin, none) end};
 | 
				
			||||||
parse(<<MessageType:4, Dup:1, QoS:2, Retain:1, Rest/binary>>, none) ->
 | 
					parse(<<MessageType:4, Dup:1, QoS:2, Retain:1, Rest/binary>>, none) ->
 | 
				
			||||||
    parse_remaining_len(Rest, #mqtt_packet_fixed{
 | 
					    parse_remaining_len(Rest, #mqtt_packet_fixed{ type   = MessageType,
 | 
				
			||||||
        type = MessageType,
 | 
					                                                  dup    = bool(Dup),
 | 
				
			||||||
        dup = bool(Dup),
 | 
					                                                  qos    = QoS,
 | 
				
			||||||
        qos = QoS,
 | 
					                                                  retain = bool(Retain) });
 | 
				
			||||||
        retain = bool(Retain)
 | 
					parse(Bin, Cont) -> Cont(Bin).
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
parse(Bin, Cont) ->
 | 
					 | 
				
			||||||
    Cont(Bin).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
parse_remaining_len(<<>>, Fixed) ->
 | 
					parse_remaining_len(<<>>, Fixed) ->
 | 
				
			||||||
    {more, fun(Bin) -> parse_remaining_len(Bin, Fixed) end};
 | 
					    {more, fun(Bin) -> parse_remaining_len(Bin, Fixed) end};
 | 
				
			||||||
parse_remaining_len(Rest, Fixed) ->
 | 
					parse_remaining_len(Rest, Fixed) ->
 | 
				
			||||||
    parse_remaining_len(Rest, Fixed, 1, 0).
 | 
					    parse_remaining_len(Rest, Fixed, 1, 0).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
parse_remaining_len(_Bin, _Fixed, _Multiplier, Length) when
 | 
					parse_remaining_len(_Bin, _Fixed, _Multiplier, Length)
 | 
				
			||||||
    Length > ?MAX_LEN
 | 
					  when Length > ?MAX_LEN ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    {error, invalid_mqtt_packet_len};
 | 
					    {error, invalid_mqtt_packet_len};
 | 
				
			||||||
parse_remaining_len(<<>>, Fixed, Multiplier, Length) ->
 | 
					parse_remaining_len(<<>>, Fixed, Multiplier, Length) ->
 | 
				
			||||||
    {more, fun(Bin) -> parse_remaining_len(Bin, Fixed, Multiplier, Length) end};
 | 
					    {more, fun(Bin) -> parse_remaining_len(Bin, Fixed, Multiplier, Length) end};
 | 
				
			||||||
parse_remaining_len(<<1:1, Len:7, Rest/binary>>, Fixed, Multiplier, Value) ->
 | 
					parse_remaining_len(<<1:1, Len:7, Rest/binary>>, Fixed, Multiplier, Value) ->
 | 
				
			||||||
    parse_remaining_len(Rest, Fixed, Multiplier * ?HIGHBIT, Value + Len * Multiplier);
 | 
					    parse_remaining_len(Rest, Fixed, Multiplier * ?HIGHBIT, Value + Len * Multiplier);
 | 
				
			||||||
parse_remaining_len(<<0:1, Len:7, Rest/binary>>, Fixed, Multiplier, Value) ->
 | 
					parse_remaining_len(<<0:1, Len:7, Rest/binary>>, Fixed,  Multiplier, Value) ->
 | 
				
			||||||
    parse_packet(Rest, Fixed, Value + Len * Multiplier).
 | 
					    parse_packet(Rest, Fixed, Value + Len * Multiplier).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
parse_packet(
 | 
					parse_packet(Bin, #mqtt_packet_fixed{ type = Type,
 | 
				
			||||||
    Bin,
 | 
					                                      qos  = Qos } = Fixed, Length)
 | 
				
			||||||
    #mqtt_packet_fixed{
 | 
					  when Length =< ?MAX_LEN ->
 | 
				
			||||||
        type = Type,
 | 
					 | 
				
			||||||
        qos = Qos
 | 
					 | 
				
			||||||
    } = Fixed,
 | 
					 | 
				
			||||||
    Length
 | 
					 | 
				
			||||||
) when
 | 
					 | 
				
			||||||
    Length =< ?MAX_LEN
 | 
					 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    case {Type, Bin} of
 | 
					    case {Type, Bin} of
 | 
				
			||||||
        {?CONNECT, <<PacketBin:Length/binary, Rest/binary>>} ->
 | 
					        {?CONNECT, <<PacketBin:Length/binary, Rest/binary>>} ->
 | 
				
			||||||
            {ProtoName, Rest1} = parse_utf(PacketBin),
 | 
					            {ProtoName, Rest1} = parse_utf(PacketBin),
 | 
				
			||||||
            <<ProtoVersion:8, Rest2/binary>> = Rest1,
 | 
					            <<ProtoVersion : 8, Rest2/binary>> = Rest1,
 | 
				
			||||||
            <<UsernameFlag:1, PasswordFlag:1, WillRetain:1, WillQos:2, WillFlag:1, CleanSession:1,
 | 
					            <<UsernameFlag : 1,
 | 
				
			||||||
                _Reserved:1, KeepAlive:16/big, Rest3/binary>> = Rest2,
 | 
					              PasswordFlag : 1,
 | 
				
			||||||
            {ClientId, Rest4} = parse_utf(Rest3),
 | 
					              WillRetain   : 1,
 | 
				
			||||||
 | 
					              WillQos      : 2,
 | 
				
			||||||
 | 
					              WillFlag     : 1,
 | 
				
			||||||
 | 
					              CleanSession : 1,
 | 
				
			||||||
 | 
					              _Reserved    : 1,
 | 
				
			||||||
 | 
					              KeepAlive    : 16/big,
 | 
				
			||||||
 | 
					              Rest3/binary>>   = Rest2,
 | 
				
			||||||
 | 
					            {ClientId,  Rest4} = parse_utf(Rest3),
 | 
				
			||||||
            {WillTopic, Rest5} = parse_utf(Rest4, WillFlag),
 | 
					            {WillTopic, Rest5} = parse_utf(Rest4, WillFlag),
 | 
				
			||||||
            {WillMsg, Rest6} = parse_msg(Rest5, WillFlag),
 | 
					            {WillMsg,   Rest6} = parse_msg(Rest5, WillFlag),
 | 
				
			||||||
            {UserName, Rest7} = parse_utf(Rest6, UsernameFlag),
 | 
					            {UserName,  Rest7} = parse_utf(Rest6, UsernameFlag),
 | 
				
			||||||
            {PasssWord, <<>>} = parse_utf(Rest7, PasswordFlag),
 | 
					            {PasssWord, <<>>}  = parse_utf(Rest7, PasswordFlag),
 | 
				
			||||||
            case protocol_name_approved(ProtoVersion, ProtoName) of
 | 
					            case protocol_name_approved(ProtoVersion, ProtoName) of
 | 
				
			||||||
                true ->
 | 
					                true ->
 | 
				
			||||||
                    wrap(
 | 
					                    wrap(Fixed,
 | 
				
			||||||
                        Fixed,
 | 
					                         #mqtt_packet_connect{
 | 
				
			||||||
                        #mqtt_packet_connect{
 | 
					                           proto_ver   = ProtoVersion,
 | 
				
			||||||
                            proto_ver = ProtoVersion,
 | 
					                           will_retain = bool(WillRetain),
 | 
				
			||||||
                            will_retain = bool(WillRetain),
 | 
					                           will_qos    = WillQos,
 | 
				
			||||||
                            will_qos = WillQos,
 | 
					                           will_flag   = bool(WillFlag),
 | 
				
			||||||
                            will_flag = bool(WillFlag),
 | 
					                           clean_sess  = bool(CleanSession),
 | 
				
			||||||
                            clean_sess = bool(CleanSession),
 | 
					                           keep_alive  = KeepAlive,
 | 
				
			||||||
                            keep_alive = KeepAlive,
 | 
					                           client_id   = ClientId,
 | 
				
			||||||
                            client_id = ClientId,
 | 
					                           will_topic  = WillTopic,
 | 
				
			||||||
                            will_topic = WillTopic,
 | 
					                           will_msg    = WillMsg,
 | 
				
			||||||
                            will_msg = WillMsg,
 | 
					                           username    = UserName,
 | 
				
			||||||
                            username = UserName,
 | 
					                           password    = PasssWord}, Rest);
 | 
				
			||||||
                            password = PasssWord
 | 
					               false ->
 | 
				
			||||||
                        },
 | 
					 | 
				
			||||||
                        Rest
 | 
					 | 
				
			||||||
                    );
 | 
					 | 
				
			||||||
                false ->
 | 
					 | 
				
			||||||
                    {error, protocol_header_corrupt}
 | 
					                    {error, protocol_header_corrupt}
 | 
				
			||||||
            end;
 | 
					            end;
 | 
				
			||||||
        {?PUBLISH, <<PacketBin:Length/binary, Rest/binary>>} ->
 | 
					        {?PUBLISH, <<PacketBin:Length/binary, Rest/binary>>} ->
 | 
				
			||||||
            {TopicName, Rest1} = parse_utf(PacketBin),
 | 
					            {TopicName, Rest1} = parse_utf(PacketBin),
 | 
				
			||||||
            {PacketId, Payload} =
 | 
					            {PacketId, Payload} = case Qos of
 | 
				
			||||||
                case Qos of
 | 
					                                       0 -> {undefined, Rest1};
 | 
				
			||||||
                    0 ->
 | 
					                                       _ -> <<M:16/big, R/binary>> = Rest1,
 | 
				
			||||||
                        {undefined, Rest1};
 | 
					                                            {M, R}
 | 
				
			||||||
                    _ ->
 | 
					                                   end,
 | 
				
			||||||
                        <<M:16/big, R/binary>> = Rest1,
 | 
					            wrap(Fixed, #mqtt_packet_publish { topic_name = TopicName,
 | 
				
			||||||
                        {M, R}
 | 
					                                               packet_id = PacketId },
 | 
				
			||||||
                end,
 | 
					                 Payload, Rest);
 | 
				
			||||||
            wrap(
 | 
					 | 
				
			||||||
                Fixed,
 | 
					 | 
				
			||||||
                #mqtt_packet_publish{
 | 
					 | 
				
			||||||
                    topic_name = TopicName,
 | 
					 | 
				
			||||||
                    packet_id = PacketId
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                Payload,
 | 
					 | 
				
			||||||
                Rest
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        {?PUBACK, <<PacketBin:Length/binary, Rest/binary>>} ->
 | 
					        {?PUBACK, <<PacketBin:Length/binary, Rest/binary>>} ->
 | 
				
			||||||
            <<PacketId:16/big>> = PacketBin,
 | 
					            <<PacketId:16/big>> = PacketBin,
 | 
				
			||||||
            wrap(Fixed, #mqtt_packet_publish{packet_id = PacketId}, Rest);
 | 
					            wrap(Fixed, #mqtt_packet_publish { packet_id = PacketId }, Rest);
 | 
				
			||||||
        {Subs, <<PacketBin:Length/binary, Rest/binary>>} when
 | 
					        {Subs, <<PacketBin:Length/binary, Rest/binary>>}
 | 
				
			||||||
            Subs =:= ?SUBSCRIBE orelse Subs =:= ?UNSUBSCRIBE
 | 
					          when Subs =:= ?SUBSCRIBE orelse Subs =:= ?UNSUBSCRIBE ->
 | 
				
			||||||
        ->
 | 
					 | 
				
			||||||
            1 = Qos,
 | 
					            1 = Qos,
 | 
				
			||||||
            <<PacketId:16/big, Rest1/binary>> = PacketBin,
 | 
					            <<PacketId:16/big, Rest1/binary>> = PacketBin,
 | 
				
			||||||
            Topics = parse_topics(Subs, Rest1, []),
 | 
					            Topics = parse_topics(Subs, Rest1, []),
 | 
				
			||||||
            wrap(
 | 
					            wrap(Fixed, #mqtt_packet_subscribe { packet_id  = PacketId,
 | 
				
			||||||
                Fixed,
 | 
					                                                 topic_table = Topics }, Rest);
 | 
				
			||||||
                #mqtt_packet_subscribe{
 | 
					        {Minimal, Rest}
 | 
				
			||||||
                    packet_id = PacketId,
 | 
					          when Minimal =:= ?DISCONNECT orelse Minimal =:= ?PINGREQ ->
 | 
				
			||||||
                    topic_table = Topics
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                Rest
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        {Minimal, Rest} when
 | 
					 | 
				
			||||||
            Minimal =:= ?DISCONNECT orelse Minimal =:= ?PINGREQ
 | 
					 | 
				
			||||||
        ->
 | 
					 | 
				
			||||||
            Length = 0,
 | 
					            Length = 0,
 | 
				
			||||||
            wrap(Fixed, Rest);
 | 
					            wrap(Fixed, Rest);
 | 
				
			||||||
        {_, TooShortBin} when
 | 
					        {_, TooShortBin}
 | 
				
			||||||
            byte_size(TooShortBin) < Length
 | 
					          when byte_size(TooShortBin) < Length ->
 | 
				
			||||||
        ->
 | 
					 | 
				
			||||||
            {more, fun(BinMore) ->
 | 
					            {more, fun(BinMore) ->
 | 
				
			||||||
                parse_packet(
 | 
					                           parse_packet(<<TooShortBin/binary, BinMore/binary>>,
 | 
				
			||||||
                    <<TooShortBin/binary, BinMore/binary>>,
 | 
					                                        Fixed, Length)
 | 
				
			||||||
                    Fixed,
 | 
					                   end}
 | 
				
			||||||
                    Length
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            end}
 | 
					 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
parse_topics(_, <<>>, Topics) ->
 | 
					parse_topics(_, <<>>, Topics) ->
 | 
				
			||||||
    Topics;
 | 
					    Topics;
 | 
				
			||||||
parse_topics(?SUBSCRIBE = Sub, Bin, Topics) ->
 | 
					parse_topics(?SUBSCRIBE = Sub, Bin, Topics) ->
 | 
				
			||||||
    {Name, <<_:6, QoS:2, Rest/binary>>} = parse_utf(Bin),
 | 
					    {Name, <<_:6, QoS:2, Rest/binary>>} = parse_utf(Bin),
 | 
				
			||||||
    parse_topics(Sub, Rest, [#mqtt_topic{name = Name, qos = QoS} | Topics]);
 | 
					    parse_topics(Sub, Rest, [#mqtt_topic { name = Name, qos = QoS } | Topics]);
 | 
				
			||||||
parse_topics(?UNSUBSCRIBE = Sub, Bin, Topics) ->
 | 
					parse_topics(?UNSUBSCRIBE = Sub, Bin, Topics) ->
 | 
				
			||||||
    {Name, <<Rest/binary>>} = parse_utf(Bin),
 | 
					    {Name, <<Rest/binary>>} = parse_utf(Bin),
 | 
				
			||||||
    parse_topics(Sub, Rest, [#mqtt_topic{name = Name} | Topics]).
 | 
					    parse_topics(Sub, Rest, [#mqtt_topic { name = Name } | Topics]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
wrap(Fixed, Variable, Payload, Rest) ->
 | 
					wrap(Fixed, Variable, Payload, Rest) ->
 | 
				
			||||||
    {ok, #mqtt_packet{variable = Variable, fixed = Fixed, payload = Payload}, Rest}.
 | 
					    {ok, #mqtt_packet { variable = Variable, fixed = Fixed, payload = Payload }, Rest}.
 | 
				
			||||||
wrap(Fixed, Variable, Rest) ->
 | 
					wrap(Fixed, Variable, Rest) ->
 | 
				
			||||||
    {ok, #mqtt_packet{variable = Variable, fixed = Fixed}, Rest}.
 | 
					    {ok, #mqtt_packet { variable = Variable, fixed = Fixed }, Rest}.
 | 
				
			||||||
wrap(Fixed, Rest) ->
 | 
					wrap(Fixed, Rest) ->
 | 
				
			||||||
    {ok, #mqtt_packet{fixed = Fixed}, Rest}.
 | 
					    {ok, #mqtt_packet { fixed = Fixed }, Rest}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
parse_utf(Bin, 0) ->
 | 
					parse_utf(Bin, 0) ->
 | 
				
			||||||
    {undefined, Bin};
 | 
					    {undefined, Bin};
 | 
				
			||||||
| 
						 | 
					@ -187,109 +158,72 @@ bool(1) -> true.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec serialise(#mqtt_packet{}, ?MQTT_PROTO_V3 | ?MQTT_PROTO_V4) ->
 | 
					-spec serialise(#mqtt_packet{}, ?MQTT_PROTO_V3 | ?MQTT_PROTO_V4) ->
 | 
				
			||||||
    iodata().
 | 
					    iodata().
 | 
				
			||||||
serialise(
 | 
					serialise(#mqtt_packet{fixed    = Fixed,
 | 
				
			||||||
    #mqtt_packet{
 | 
					                       variable = Variable,
 | 
				
			||||||
        fixed = Fixed,
 | 
					                       payload  = Payload}, Vsn) ->
 | 
				
			||||||
        variable = Variable,
 | 
					 | 
				
			||||||
        payload = Payload
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    Vsn
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    serialise_variable(Fixed, Variable, serialise_payload(Payload), Vsn).
 | 
					    serialise_variable(Fixed, Variable, serialise_payload(Payload), Vsn).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
serialise_payload(undefined) ->
 | 
					serialise_payload(undefined) ->
 | 
				
			||||||
    <<>>;
 | 
					    <<>>;
 | 
				
			||||||
serialise_payload(P) when
 | 
					serialise_payload(P)
 | 
				
			||||||
    is_binary(P) orelse is_list(P)
 | 
					  when is_binary(P) orelse is_list(P) ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    P.
 | 
					    P.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
serialise_variable(
 | 
					serialise_variable(#mqtt_packet_fixed   { type        = ?CONNACK } = Fixed,
 | 
				
			||||||
    #mqtt_packet_fixed{type = ?CONNACK} = Fixed,
 | 
					                   #mqtt_packet_connack { session_present = SessionPresent,
 | 
				
			||||||
    #mqtt_packet_connack{
 | 
					                                          return_code = ReturnCode },
 | 
				
			||||||
        session_present = SessionPresent,
 | 
					                   <<>> = PayloadBin, _Vsn) ->
 | 
				
			||||||
        return_code = ReturnCode
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    <<>> = PayloadBin,
 | 
					 | 
				
			||||||
    _Vsn
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    VariableBin = <<?RESERVED:7, (opt(SessionPresent)):1, ReturnCode:8>>,
 | 
					    VariableBin = <<?RESERVED:7, (opt(SessionPresent)):1, ReturnCode:8>>,
 | 
				
			||||||
    serialise_fixed(Fixed, VariableBin, PayloadBin);
 | 
					    serialise_fixed(Fixed, VariableBin, PayloadBin);
 | 
				
			||||||
serialise_variable(
 | 
					
 | 
				
			||||||
    #mqtt_packet_fixed{type = SubAck} = Fixed,
 | 
					serialise_variable(#mqtt_packet_fixed  { type       = SubAck } = Fixed,
 | 
				
			||||||
    #mqtt_packet_suback{
 | 
					                   #mqtt_packet_suback { packet_id = PacketId,
 | 
				
			||||||
        packet_id = PacketId,
 | 
					                                         qos_table  = Qos },
 | 
				
			||||||
        qos_table = Qos
 | 
					                   <<>> = _PayloadBin, Vsn)
 | 
				
			||||||
    },
 | 
					  when SubAck =:= ?SUBACK orelse SubAck =:= ?UNSUBACK ->
 | 
				
			||||||
    <<>> = _PayloadBin,
 | 
					 | 
				
			||||||
    Vsn
 | 
					 | 
				
			||||||
) when
 | 
					 | 
				
			||||||
    SubAck =:= ?SUBACK orelse SubAck =:= ?UNSUBACK
 | 
					 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    VariableBin = <<PacketId:16/big>>,
 | 
					    VariableBin = <<PacketId:16/big>>,
 | 
				
			||||||
    QosBin =
 | 
					    QosBin = case Vsn of
 | 
				
			||||||
        case Vsn of
 | 
					                 ?MQTT_PROTO_V3 ->
 | 
				
			||||||
            ?MQTT_PROTO_V3 ->
 | 
					                     << <<?RESERVED:6, Q:2>> || Q <- Qos >>;
 | 
				
			||||||
                <<<<?RESERVED:6, Q:2>> || Q <- Qos>>;
 | 
					                 ?MQTT_PROTO_V4 ->
 | 
				
			||||||
            ?MQTT_PROTO_V4 ->
 | 
					                     %% Allow error code (0x80) in the MQTT SUBACK message.
 | 
				
			||||||
                %% Allow error code (0x80) in the MQTT SUBACK message.
 | 
					                     << <<Q:8>> || Q <- Qos >>
 | 
				
			||||||
                <<<<Q:8>> || Q <- Qos>>
 | 
					             end,
 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
    serialise_fixed(Fixed, VariableBin, QosBin);
 | 
					    serialise_fixed(Fixed, VariableBin, QosBin);
 | 
				
			||||||
serialise_variable(
 | 
					
 | 
				
			||||||
    #mqtt_packet_fixed{
 | 
					serialise_variable(#mqtt_packet_fixed   { type       = ?PUBLISH,
 | 
				
			||||||
        type = ?PUBLISH,
 | 
					                                          qos        = Qos } = Fixed,
 | 
				
			||||||
        qos = Qos
 | 
					                   #mqtt_packet_publish { topic_name = TopicName,
 | 
				
			||||||
    } = Fixed,
 | 
					                                          packet_id = PacketId },
 | 
				
			||||||
    #mqtt_packet_publish{
 | 
					                   Payload, _Vsn) ->
 | 
				
			||||||
        topic_name = TopicName,
 | 
					 | 
				
			||||||
        packet_id = PacketId
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    Payload,
 | 
					 | 
				
			||||||
    _Vsn
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    TopicBin = serialise_utf(TopicName),
 | 
					    TopicBin = serialise_utf(TopicName),
 | 
				
			||||||
    PacketIdBin =
 | 
					    PacketIdBin = case Qos of
 | 
				
			||||||
        case Qos of
 | 
					                       0 -> <<>>;
 | 
				
			||||||
            0 -> <<>>;
 | 
					                       1 -> <<PacketId:16/big>>
 | 
				
			||||||
            1 -> <<PacketId:16/big>>
 | 
					                   end,
 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
    serialise_fixed(Fixed, <<TopicBin/binary, PacketIdBin/binary>>, Payload);
 | 
					    serialise_fixed(Fixed, <<TopicBin/binary, PacketIdBin/binary>>, Payload);
 | 
				
			||||||
serialise_variable(
 | 
					
 | 
				
			||||||
    #mqtt_packet_fixed{type = ?PUBACK} = Fixed,
 | 
					serialise_variable(#mqtt_packet_fixed   { type       = ?PUBACK } = Fixed,
 | 
				
			||||||
    #mqtt_packet_publish{packet_id = PacketId},
 | 
					                   #mqtt_packet_publish { packet_id = PacketId },
 | 
				
			||||||
    PayloadBin,
 | 
					                   PayloadBin, _Vsn) ->
 | 
				
			||||||
    _Vsn
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    PacketIdBin = <<PacketId:16/big>>,
 | 
					    PacketIdBin = <<PacketId:16/big>>,
 | 
				
			||||||
    serialise_fixed(Fixed, PacketIdBin, PayloadBin);
 | 
					    serialise_fixed(Fixed, PacketIdBin, PayloadBin);
 | 
				
			||||||
serialise_variable(
 | 
					
 | 
				
			||||||
    #mqtt_packet_fixed{} = Fixed,
 | 
					serialise_variable(#mqtt_packet_fixed {} = Fixed,
 | 
				
			||||||
    undefined,
 | 
					                   undefined,
 | 
				
			||||||
    <<>> = _PayloadBin,
 | 
					                   <<>> = _PayloadBin, _Vsn) ->
 | 
				
			||||||
    _Vsn
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    serialise_fixed(Fixed, <<>>, <<>>).
 | 
					    serialise_fixed(Fixed, <<>>, <<>>).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
serialise_fixed(
 | 
					serialise_fixed(#mqtt_packet_fixed{ type   = Type,
 | 
				
			||||||
    #mqtt_packet_fixed{
 | 
					                                    dup    = Dup,
 | 
				
			||||||
        type = Type,
 | 
					                                    qos    = Qos,
 | 
				
			||||||
        dup = Dup,
 | 
					                                    retain = Retain }, VariableBin, Payload)
 | 
				
			||||||
        qos = Qos,
 | 
					  when is_integer(Type) andalso ?CONNECT =< Type andalso Type =< ?DISCONNECT ->
 | 
				
			||||||
        retain = Retain
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    VariableBin,
 | 
					 | 
				
			||||||
    Payload
 | 
					 | 
				
			||||||
) when
 | 
					 | 
				
			||||||
    is_integer(Type) andalso ?CONNECT =< Type andalso Type =< ?DISCONNECT
 | 
					 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    Len = size(VariableBin) + iolist_size(Payload),
 | 
					    Len = size(VariableBin) + iolist_size(Payload),
 | 
				
			||||||
    true = (Len =< ?MAX_LEN),
 | 
					    true = (Len =< ?MAX_LEN),
 | 
				
			||||||
    LenBin = serialise_len(Len),
 | 
					    LenBin = serialise_len(Len),
 | 
				
			||||||
    [
 | 
					    [<<Type:4, (opt(Dup)):1, (opt(Qos)):2, (opt(Retain)):1,
 | 
				
			||||||
        <<Type:4, (opt(Dup)):1, (opt(Qos)):2, (opt(Retain)):1, LenBin/binary, VariableBin/binary>>,
 | 
					       LenBin/binary, VariableBin/binary>>, Payload].
 | 
				
			||||||
        Payload
 | 
					 | 
				
			||||||
    ].
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
serialise_utf(String) ->
 | 
					serialise_utf(String) ->
 | 
				
			||||||
    StringBin = unicode:characters_to_binary(String),
 | 
					    StringBin = unicode:characters_to_binary(String),
 | 
				
			||||||
| 
						 | 
					@ -302,9 +236,9 @@ serialise_len(N) when N =< ?LOWBITS ->
 | 
				
			||||||
serialise_len(N) ->
 | 
					serialise_len(N) ->
 | 
				
			||||||
    <<1:1, (N rem ?HIGHBIT):7, (serialise_len(N div ?HIGHBIT))/binary>>.
 | 
					    <<1:1, (N rem ?HIGHBIT):7, (serialise_len(N div ?HIGHBIT))/binary>>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
opt(undefined) -> ?RESERVED;
 | 
					opt(undefined)            -> ?RESERVED;
 | 
				
			||||||
opt(false) -> 0;
 | 
					opt(false)                -> 0;
 | 
				
			||||||
opt(true) -> 1;
 | 
					opt(true)                 -> 1;
 | 
				
			||||||
opt(X) when is_integer(X) -> X.
 | 
					opt(X) when is_integer(X) -> X.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
protocol_name_approved(Ver, Name) ->
 | 
					protocol_name_approved(Ver, Name) ->
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
					@ -24,50 +24,42 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
%% Stateless rabbit_queue_type callbacks.
 | 
					%% Stateless rabbit_queue_type callbacks.
 | 
				
			||||||
-export([
 | 
					-export([
 | 
				
			||||||
    is_stateful/0,
 | 
					         is_stateful/0,
 | 
				
			||||||
    declare/2,
 | 
					         declare/2,
 | 
				
			||||||
    delete/4,
 | 
					         delete/4,
 | 
				
			||||||
    deliver/2,
 | 
					         deliver/2,
 | 
				
			||||||
    is_enabled/0,
 | 
					         is_enabled/0,
 | 
				
			||||||
    is_compatible/3,
 | 
					         is_compatible/3,
 | 
				
			||||||
    is_recoverable/1,
 | 
					         is_recoverable/1,
 | 
				
			||||||
    recover/2,
 | 
					         recover/2,
 | 
				
			||||||
    purge/1,
 | 
					         purge/1,
 | 
				
			||||||
    policy_changed/1,
 | 
					         policy_changed/1,
 | 
				
			||||||
    info/2,
 | 
					         info/2,
 | 
				
			||||||
    stat/1,
 | 
					         stat/1,
 | 
				
			||||||
    capabilities/0,
 | 
					         capabilities/0,
 | 
				
			||||||
    notify_decorators/1
 | 
					         notify_decorators/1
 | 
				
			||||||
]).
 | 
					        ]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
%% Stateful rabbit_queue_type callbacks are unsupported by this queue type.
 | 
					%% Stateful rabbit_queue_type callbacks are unsupported by this queue type.
 | 
				
			||||||
-define(STATEFUL_CALLBACKS, [
 | 
					-define(STATEFUL_CALLBACKS,
 | 
				
			||||||
    init/1,
 | 
					        [
 | 
				
			||||||
    close/1,
 | 
					         init/1,
 | 
				
			||||||
    update/2,
 | 
					         close/1,
 | 
				
			||||||
    consume/3,
 | 
					         update/2,
 | 
				
			||||||
    cancel/5,
 | 
					         consume/3,
 | 
				
			||||||
    handle_event/3,
 | 
					         cancel/5,
 | 
				
			||||||
    settle/5,
 | 
					         handle_event/3,
 | 
				
			||||||
    credit/5,
 | 
					         settle/5,
 | 
				
			||||||
    dequeue/5,
 | 
					         credit/5,
 | 
				
			||||||
    state_info/1
 | 
					         dequeue/5,
 | 
				
			||||||
]).
 | 
					         state_info/1
 | 
				
			||||||
 | 
					        ]).
 | 
				
			||||||
-export(?STATEFUL_CALLBACKS).
 | 
					-export(?STATEFUL_CALLBACKS).
 | 
				
			||||||
-dialyzer({nowarn_function, ?STATEFUL_CALLBACKS}).
 | 
					-dialyzer({nowarn_function, ?STATEFUL_CALLBACKS}).
 | 
				
			||||||
-define(UNSUPPORTED(Args), erlang:error(unsupported, Args)).
 | 
					-define(UNSUPPORTED(Args), erlang:error(unsupported, Args)).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-define(INFO_KEYS, [
 | 
					-define(INFO_KEYS, [type, name, durable, auto_delete, arguments,
 | 
				
			||||||
    type,
 | 
					                    pid, owner_pid, state, messages]).
 | 
				
			||||||
    name,
 | 
					 | 
				
			||||||
    durable,
 | 
					 | 
				
			||||||
    auto_delete,
 | 
					 | 
				
			||||||
    arguments,
 | 
					 | 
				
			||||||
    pid,
 | 
					 | 
				
			||||||
    owner_pid,
 | 
					 | 
				
			||||||
    state,
 | 
					 | 
				
			||||||
    messages
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec is_stateful() ->
 | 
					-spec is_stateful() ->
 | 
				
			||||||
    boolean().
 | 
					    boolean().
 | 
				
			||||||
| 
						 | 
					@ -75,8 +67,8 @@ is_stateful() ->
 | 
				
			||||||
    false.
 | 
					    false.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec declare(amqqueue:amqqueue(), node()) ->
 | 
					-spec declare(amqqueue:amqqueue(), node()) ->
 | 
				
			||||||
    {'new' | 'existing' | 'owner_died', amqqueue:amqqueue()}
 | 
					    {'new' | 'existing' | 'owner_died', amqqueue:amqqueue()} |
 | 
				
			||||||
    | {'absent', amqqueue:amqqueue(), rabbit_amqqueue:absent_reason()}.
 | 
					    {'absent', amqqueue:amqqueue(), rabbit_amqqueue:absent_reason()}.
 | 
				
			||||||
declare(Q0, _Node) ->
 | 
					declare(Q0, _Node) ->
 | 
				
			||||||
    %% The queue gets persisted such that routing to this
 | 
					    %% The queue gets persisted such that routing to this
 | 
				
			||||||
    %% queue (via the topic exchange) works as usual.
 | 
					    %% queue (via the topic exchange) works as usual.
 | 
				
			||||||
| 
						 | 
					@ -84,29 +76,23 @@ declare(Q0, _Node) ->
 | 
				
			||||||
        {created, Q} ->
 | 
					        {created, Q} ->
 | 
				
			||||||
            Opts = amqqueue:get_options(Q),
 | 
					            Opts = amqqueue:get_options(Q),
 | 
				
			||||||
            ActingUser = maps:get(user, Opts, ?UNKNOWN_USER),
 | 
					            ActingUser = maps:get(user, Opts, ?UNKNOWN_USER),
 | 
				
			||||||
            rabbit_event:notify(
 | 
					            rabbit_event:notify(queue_created,
 | 
				
			||||||
                queue_created,
 | 
					                                [{name, amqqueue:get_name(Q0)},
 | 
				
			||||||
                [
 | 
					                                 {durable, true},
 | 
				
			||||||
                    {name, amqqueue:get_name(Q0)},
 | 
					                                 {auto_delete, false},
 | 
				
			||||||
                    {durable, true},
 | 
					                                 {exclusive, true},
 | 
				
			||||||
                    {auto_delete, false},
 | 
					                                 {type, amqqueue:get_type(Q0)},
 | 
				
			||||||
                    {exclusive, true},
 | 
					                                 {arguments, amqqueue:get_arguments(Q0)},
 | 
				
			||||||
                    {type, amqqueue:get_type(Q0)},
 | 
					                                 {user_who_performed_action, ActingUser}]),
 | 
				
			||||||
                    {arguments, amqqueue:get_arguments(Q0)},
 | 
					 | 
				
			||||||
                    {user_who_performed_action, ActingUser}
 | 
					 | 
				
			||||||
                ]
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            {new, Q};
 | 
					            {new, Q};
 | 
				
			||||||
        Other ->
 | 
					        Other ->
 | 
				
			||||||
            Other
 | 
					            Other
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec delete(
 | 
					-spec delete(amqqueue:amqqueue(),
 | 
				
			||||||
    amqqueue:amqqueue(),
 | 
					             boolean(),
 | 
				
			||||||
    boolean(),
 | 
					             boolean(),
 | 
				
			||||||
    boolean(),
 | 
					             rabbit_types:username()) ->
 | 
				
			||||||
    rabbit_types:username()
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    rabbit_types:ok(non_neg_integer()).
 | 
					    rabbit_types:ok(non_neg_integer()).
 | 
				
			||||||
delete(Q, _IfUnused, _IfEmpty, ActingUser) ->
 | 
					delete(Q, _IfUnused, _IfEmpty, ActingUser) ->
 | 
				
			||||||
    QName = amqqueue:get_name(Q),
 | 
					    QName = amqqueue:get_name(Q),
 | 
				
			||||||
| 
						 | 
					@ -116,41 +102,35 @@ delete(Q, _IfUnused, _IfEmpty, ActingUser) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec deliver([{amqqueue:amqqueue(), stateless}], Delivery :: term()) ->
 | 
					-spec deliver([{amqqueue:amqqueue(), stateless}], Delivery :: term()) ->
 | 
				
			||||||
    {[], rabbit_queue_type:actions()}.
 | 
					    {[], rabbit_queue_type:actions()}.
 | 
				
			||||||
deliver(Qs, #delivery{
 | 
					deliver(Qs, #delivery{message = BasicMessage,
 | 
				
			||||||
    message = BasicMessage,
 | 
					                      confirm = Confirm,
 | 
				
			||||||
    confirm = Confirm,
 | 
					                      msg_seq_no = SeqNo}) ->
 | 
				
			||||||
    msg_seq_no = SeqNo
 | 
					    Msg = {queue_event, ?MODULE,
 | 
				
			||||||
}) ->
 | 
					           {?MODULE, _QPid = none, _QMsgId = none, _Redelivered = false, BasicMessage}},
 | 
				
			||||||
    Msg =
 | 
					 | 
				
			||||||
        {queue_event, ?MODULE,
 | 
					 | 
				
			||||||
            {?MODULE, _QPid = none, _QMsgId = none, _Redelivered = false, BasicMessage}},
 | 
					 | 
				
			||||||
    {Pids, Actions} =
 | 
					    {Pids, Actions} =
 | 
				
			||||||
        case Confirm of
 | 
					    case Confirm of
 | 
				
			||||||
            false ->
 | 
					        false ->
 | 
				
			||||||
                Pids0 = lists:map(fun({Q, stateless}) -> amqqueue:get_pid(Q) end, Qs),
 | 
					            Pids0 = lists:map(fun({Q, stateless}) -> amqqueue:get_pid(Q) end, Qs),
 | 
				
			||||||
                {Pids0, []};
 | 
					            {Pids0, []};
 | 
				
			||||||
            true ->
 | 
					        true ->
 | 
				
			||||||
                %% We confirm the message directly here in the queue client.
 | 
					            %% We confirm the message directly here in the queue client.
 | 
				
			||||||
                %% Alternatively, we could have the target MQTT connection process confirm the message.
 | 
					            %% Alternatively, we could have the target MQTT connection process confirm the message.
 | 
				
			||||||
                %% However, given that this message might be lost anyway between target MQTT connection
 | 
					            %% However, given that this message might be lost anyway between target MQTT connection
 | 
				
			||||||
                %% process and MQTT subscriber, and we know that the MQTT subscriber wants to receive
 | 
					            %% process and MQTT subscriber, and we know that the MQTT subscriber wants to receive
 | 
				
			||||||
                %% this message at most once, we confirm here directly.
 | 
					            %% this message at most once, we confirm here directly.
 | 
				
			||||||
                %% Benefits:
 | 
					            %% Benefits:
 | 
				
			||||||
                %% 1. We do not block sending the confirmation back to the publishing client just because a single
 | 
					            %% 1. We do not block sending the confirmation back to the publishing client just because a single
 | 
				
			||||||
                %% (at-most-once) target queue out of potentially many (e.g. million) queues might be unavailable.
 | 
					            %% (at-most-once) target queue out of potentially many (e.g. million) queues might be unavailable.
 | 
				
			||||||
                %% 2. Memory usage in this (publishing) process is kept lower because the target queue name can be
 | 
					            %% 2. Memory usage in this (publishing) process is kept lower because the target queue name can be
 | 
				
			||||||
                %% directly removed from rabbit_mqtt_confirms and rabbit_confirms.
 | 
					            %% directly removed from rabbit_mqtt_confirms and rabbit_confirms.
 | 
				
			||||||
                %% 3. Reduced network traffic across RabbitMQ nodes.
 | 
					            %% 3. Reduced network traffic across RabbitMQ nodes.
 | 
				
			||||||
                %% 4. Lower latency of sending publisher confirmation back to the publishing client.
 | 
					            %% 4. Lower latency of sending publisher confirmation back to the publishing client.
 | 
				
			||||||
                SeqNos = [SeqNo],
 | 
					            SeqNos = [SeqNo],
 | 
				
			||||||
                lists:mapfoldl(
 | 
					            lists:mapfoldl(fun({Q, stateless}, Actions) ->
 | 
				
			||||||
                    fun({Q, stateless}, Actions) ->
 | 
					                                   {amqqueue:get_pid(Q),
 | 
				
			||||||
                        {amqqueue:get_pid(Q), [{settled, amqqueue:get_name(Q), SeqNos} | Actions]}
 | 
					                                    [{settled, amqqueue:get_name(Q), SeqNos} | Actions]}
 | 
				
			||||||
                    end,
 | 
					                           end, [], Qs)
 | 
				
			||||||
                    [],
 | 
					    end,
 | 
				
			||||||
                    Qs
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
    delegate:invoke_no_result(Pids, {gen_server, cast, [Msg]}),
 | 
					    delegate:invoke_no_result(Pids, {gen_server, cast, [Msg]}),
 | 
				
			||||||
    {[], Actions}.
 | 
					    {[], Actions}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -172,8 +152,8 @@ is_recoverable(Q) ->
 | 
				
			||||||
    Pid = amqqueue:get_pid(Q),
 | 
					    Pid = amqqueue:get_pid(Q),
 | 
				
			||||||
    OwnerPid = amqqueue:get_exclusive_owner(Q),
 | 
					    OwnerPid = amqqueue:get_exclusive_owner(Q),
 | 
				
			||||||
    node() =:= node(Pid) andalso
 | 
					    node() =:= node(Pid) andalso
 | 
				
			||||||
        Pid =:= OwnerPid andalso
 | 
					    Pid =:= OwnerPid andalso
 | 
				
			||||||
        not is_process_alive(Pid).
 | 
					    not is_process_alive(Pid).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
%% We (mis)use the recover callback to clean up our exclusive queues
 | 
					%% We (mis)use the recover callback to clean up our exclusive queues
 | 
				
			||||||
%% which otherwise do not get cleaned up after a node crash.
 | 
					%% which otherwise do not get cleaned up after a node crash.
 | 
				
			||||||
| 
						 | 
					@ -181,24 +161,20 @@ is_recoverable(Q) ->
 | 
				
			||||||
    {Recovered :: [amqqueue:amqqueue()], Failed :: [amqqueue:amqqueue()]}.
 | 
					    {Recovered :: [amqqueue:amqqueue()], Failed :: [amqqueue:amqqueue()]}.
 | 
				
			||||||
recover(_VHost, Queues) ->
 | 
					recover(_VHost, Queues) ->
 | 
				
			||||||
    lists:foreach(
 | 
					    lists:foreach(
 | 
				
			||||||
        fun(Q) ->
 | 
					      fun(Q) ->
 | 
				
			||||||
            %% sanity check
 | 
					              %% sanity check
 | 
				
			||||||
            true = is_recoverable(Q),
 | 
					              true = is_recoverable(Q),
 | 
				
			||||||
            QName = amqqueue:get_name(Q),
 | 
					              QName = amqqueue:get_name(Q),
 | 
				
			||||||
            log_delete(QName, amqqueue:get_exclusive_owner(Q)),
 | 
					              log_delete(QName, amqqueue:get_exclusive_owner(Q)),
 | 
				
			||||||
            rabbit_amqqueue:internal_delete(QName, ?INTERNAL_USER)
 | 
					              rabbit_amqqueue:internal_delete(QName, ?INTERNAL_USER)
 | 
				
			||||||
        end,
 | 
					      end, Queues),
 | 
				
			||||||
        Queues
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    %% We mark the queue recovery as failed because these queues are not really
 | 
					    %% We mark the queue recovery as failed because these queues are not really
 | 
				
			||||||
    %% recovered, but deleted.
 | 
					    %% recovered, but deleted.
 | 
				
			||||||
    {[], Queues}.
 | 
					    {[], Queues}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
log_delete(QName, ConPid) ->
 | 
					log_delete(QName, ConPid) ->
 | 
				
			||||||
    rabbit_log_queue:debug(
 | 
					    rabbit_log_queue:debug("Deleting ~s of type ~s because its declaring connection ~tp was closed",
 | 
				
			||||||
        "Deleting ~s of type ~s because its declaring connection ~tp was closed",
 | 
					                           [rabbit_misc:rs(QName), ?MODULE, ConPid]).
 | 
				
			||||||
        [rabbit_misc:rs(QName), ?MODULE, ConPid]
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec purge(amqqueue:amqqueue()) ->
 | 
					-spec purge(amqqueue:amqqueue()) ->
 | 
				
			||||||
    {ok, non_neg_integer()}.
 | 
					    {ok, non_neg_integer()}.
 | 
				
			||||||
| 
						 | 
					@ -227,13 +203,11 @@ capabilities() ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec info(amqqueue:amqqueue(), all_keys | rabbit_types:info_keys()) ->
 | 
					-spec info(amqqueue:amqqueue(), all_keys | rabbit_types:info_keys()) ->
 | 
				
			||||||
    rabbit_types:infos().
 | 
					    rabbit_types:infos().
 | 
				
			||||||
info(Q, all_keys) when
 | 
					info(Q, all_keys)
 | 
				
			||||||
    ?is_amqqueue(Q)
 | 
					  when ?is_amqqueue(Q) ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    info(Q, ?INFO_KEYS);
 | 
					    info(Q, ?INFO_KEYS);
 | 
				
			||||||
info(Q, Items) when
 | 
					info(Q, Items)
 | 
				
			||||||
    ?is_amqqueue(Q)
 | 
					  when ?is_amqqueue(Q) ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    [{Item, i(Item, Q)} || Item <- Items].
 | 
					    [{Item, i(Item, Q)} || Item <- Items].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
i(type, _) ->
 | 
					i(type, _) ->
 | 
				
			||||||
| 
						 | 
					@ -275,26 +249,26 @@ init(A1) ->
 | 
				
			||||||
close(A1) ->
 | 
					close(A1) ->
 | 
				
			||||||
    ?UNSUPPORTED([A1]).
 | 
					    ?UNSUPPORTED([A1]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
update(A1, A2) ->
 | 
					update(A1,A2) ->
 | 
				
			||||||
    ?UNSUPPORTED([A1, A2]).
 | 
					    ?UNSUPPORTED([A1,A2]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
consume(A1, A2, A3) ->
 | 
					consume(A1,A2,A3) ->
 | 
				
			||||||
    ?UNSUPPORTED([A1, A2, A3]).
 | 
					    ?UNSUPPORTED([A1,A2,A3]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cancel(A1, A2, A3, A4, A5) ->
 | 
					cancel(A1,A2,A3,A4,A5) ->
 | 
				
			||||||
    ?UNSUPPORTED([A1, A2, A3, A4, A5]).
 | 
					    ?UNSUPPORTED([A1,A2,A3,A4,A5]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_event(A1, A2, A3) ->
 | 
					handle_event(A1,A2,A3) ->
 | 
				
			||||||
    ?UNSUPPORTED([A1, A2, A3]).
 | 
					    ?UNSUPPORTED([A1,A2,A3]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
settle(A1, A2, A3, A4, A5) ->
 | 
					settle(A1,A2,A3,A4,A5) ->
 | 
				
			||||||
    ?UNSUPPORTED([A1, A2, A3, A4, A5]).
 | 
					    ?UNSUPPORTED([A1,A2,A3,A4,A5]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
credit(A1, A2, A3, A4, A5) ->
 | 
					credit(A1,A2,A3,A4,A5) ->
 | 
				
			||||||
    ?UNSUPPORTED([A1, A2, A3, A4, A5]).
 | 
					    ?UNSUPPORTED([A1,A2,A3,A4,A5]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dequeue(A1, A2, A3, A4, A5) ->
 | 
					dequeue(A1,A2,A3,A4,A5) ->
 | 
				
			||||||
    ?UNSUPPORTED([A1, A2, A3, A4, A5]).
 | 
					    ?UNSUPPORTED([A1,A2,A3,A4,A5]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
state_info(A1) ->
 | 
					state_info(A1) ->
 | 
				
			||||||
    ?UNSUPPORTED([A1]).
 | 
					    ?UNSUPPORTED([A1]).
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,20 +14,11 @@
 | 
				
			||||||
-include_lib("rabbit_common/include/logging.hrl").
 | 
					-include_lib("rabbit_common/include/logging.hrl").
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-export([start_link/3]).
 | 
					-export([start_link/3]).
 | 
				
			||||||
-export([
 | 
					-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
 | 
				
			||||||
    init/1,
 | 
					         code_change/3, terminate/2, format_status/1]).
 | 
				
			||||||
    handle_call/3,
 | 
					 | 
				
			||||||
    handle_cast/2,
 | 
					 | 
				
			||||||
    handle_info/2,
 | 
					 | 
				
			||||||
    code_change/3,
 | 
					 | 
				
			||||||
    terminate/2,
 | 
					 | 
				
			||||||
    format_status/1
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-export([
 | 
					-export([conserve_resources/3,
 | 
				
			||||||
    conserve_resources/3,
 | 
					         close_connection/2]).
 | 
				
			||||||
    close_connection/2
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-export([info/2]).
 | 
					-export([info/2]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,22 +29,22 @@
 | 
				
			||||||
-define(HIBERNATE_AFTER, 1000).
 | 
					-define(HIBERNATE_AFTER, 1000).
 | 
				
			||||||
-define(PROTO_FAMILY, 'MQTT').
 | 
					-define(PROTO_FAMILY, 'MQTT').
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-record(state, {
 | 
					-record(state,
 | 
				
			||||||
    socket :: rabbit_net:socket(),
 | 
					        {socket :: rabbit_net:socket(),
 | 
				
			||||||
    proxy_socket :: option({rabbit_proxy_socket, any(), any()}),
 | 
					         proxy_socket :: option({rabbit_proxy_socket, any(), any()}),
 | 
				
			||||||
    await_recv :: boolean(),
 | 
					         await_recv :: boolean(),
 | 
				
			||||||
    deferred_recv :: option(binary()),
 | 
					         deferred_recv :: option(binary()),
 | 
				
			||||||
    parse_state :: rabbit_mqtt_packet:state(),
 | 
					         parse_state :: rabbit_mqtt_packet:state(),
 | 
				
			||||||
    proc_state :: rabbit_mqtt_processor:state(),
 | 
					         proc_state :: rabbit_mqtt_processor:state(),
 | 
				
			||||||
    connection_state :: running | blocked,
 | 
					         connection_state :: running | blocked,
 | 
				
			||||||
    conserve :: boolean(),
 | 
					         conserve :: boolean(),
 | 
				
			||||||
    stats_timer :: option(rabbit_event:state()),
 | 
					         stats_timer :: option(rabbit_event:state()),
 | 
				
			||||||
    keepalive = rabbit_mqtt_keepalive:init() :: rabbit_mqtt_keepalive:state(),
 | 
					         keepalive = rabbit_mqtt_keepalive:init() :: rabbit_mqtt_keepalive:state(),
 | 
				
			||||||
    conn_name :: binary(),
 | 
					         conn_name :: binary(),
 | 
				
			||||||
    received_connect_packet :: boolean()
 | 
					         received_connect_packet :: boolean()
 | 
				
			||||||
}).
 | 
					        }).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-type state() :: #state{}.
 | 
					-type(state() :: #state{}).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
%%----------------------------------------------------------------------------
 | 
					%%----------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -61,11 +52,9 @@ start_link(Ref, _Transport, []) ->
 | 
				
			||||||
    Pid = proc_lib:spawn_link(?MODULE, init, [Ref]),
 | 
					    Pid = proc_lib:spawn_link(?MODULE, init, [Ref]),
 | 
				
			||||||
    {ok, Pid}.
 | 
					    {ok, Pid}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec conserve_resources(
 | 
					-spec conserve_resources(pid(),
 | 
				
			||||||
    pid(),
 | 
					                         rabbit_alarm:resource_alarm_source(),
 | 
				
			||||||
    rabbit_alarm:resource_alarm_source(),
 | 
					                         rabbit_alarm:resource_alert()) -> ok.
 | 
				
			||||||
    rabbit_alarm:resource_alert()
 | 
					 | 
				
			||||||
) -> ok.
 | 
					 | 
				
			||||||
conserve_resources(Pid, _, {_, Conserve, _}) ->
 | 
					conserve_resources(Pid, _, {_, Conserve, _}) ->
 | 
				
			||||||
    Pid ! {conserve_resources, Conserve},
 | 
					    Pid ! {conserve_resources, Conserve},
 | 
				
			||||||
    ok.
 | 
					    ok.
 | 
				
			||||||
| 
						 | 
					@ -84,10 +73,8 @@ close_connection(Pid, Reason) ->
 | 
				
			||||||
init(Ref) ->
 | 
					init(Ref) ->
 | 
				
			||||||
    process_flag(trap_exit, true),
 | 
					    process_flag(trap_exit, true),
 | 
				
			||||||
    logger:set_process_metadata(#{domain => ?RMQLOG_DOMAIN_CONN ++ [mqtt]}),
 | 
					    logger:set_process_metadata(#{domain => ?RMQLOG_DOMAIN_CONN ++ [mqtt]}),
 | 
				
			||||||
    {ok, Sock} = rabbit_networking:handshake(
 | 
					    {ok, Sock} = rabbit_networking:handshake(Ref,
 | 
				
			||||||
        Ref,
 | 
					        application:get_env(?APP_NAME, proxy_protocol, false)),
 | 
				
			||||||
        application:get_env(?APP_NAME, proxy_protocol, false)
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    RealSocket = rabbit_net:unwrap_socket(Sock),
 | 
					    RealSocket = rabbit_net:unwrap_socket(Sock),
 | 
				
			||||||
    case rabbit_net:connection_string(Sock, inbound) of
 | 
					    case rabbit_net:connection_string(Sock, inbound) of
 | 
				
			||||||
        {ok, ConnStr} ->
 | 
					        {ok, ConnStr} ->
 | 
				
			||||||
| 
						 | 
					@ -97,17 +84,15 @@ init(Ref) ->
 | 
				
			||||||
            LoginTimeout = application:get_env(?APP_NAME, login_timeout, 10_000),
 | 
					            LoginTimeout = application:get_env(?APP_NAME, login_timeout, 10_000),
 | 
				
			||||||
            erlang:send_after(LoginTimeout, self(), login_timeout),
 | 
					            erlang:send_after(LoginTimeout, self(), login_timeout),
 | 
				
			||||||
            ProcessorState = rabbit_mqtt_processor:initial_state(RealSocket, ConnName),
 | 
					            ProcessorState = rabbit_mqtt_processor:initial_state(RealSocket, ConnName),
 | 
				
			||||||
            State0 = #state{
 | 
					            State0 = #state{socket = RealSocket,
 | 
				
			||||||
                socket = RealSocket,
 | 
					                            proxy_socket = rabbit_net:maybe_get_proxy_socket(Sock),
 | 
				
			||||||
                proxy_socket = rabbit_net:maybe_get_proxy_socket(Sock),
 | 
					                            conn_name = ConnName,
 | 
				
			||||||
                conn_name = ConnName,
 | 
					                            await_recv = false,
 | 
				
			||||||
                await_recv = false,
 | 
					                            connection_state = running,
 | 
				
			||||||
                connection_state = running,
 | 
					                            received_connect_packet = false,
 | 
				
			||||||
                received_connect_packet = false,
 | 
					                            conserve = false,
 | 
				
			||||||
                conserve = false,
 | 
					                            parse_state = rabbit_mqtt_packet:initial_state(),
 | 
				
			||||||
                parse_state = rabbit_mqtt_packet:initial_state(),
 | 
					                            proc_state = ProcessorState},
 | 
				
			||||||
                proc_state = ProcessorState
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            State1 = control_throttle(State0),
 | 
					            State1 = control_throttle(State0),
 | 
				
			||||||
            State = rabbit_event:init_stats_timer(State1, #state.stats_timer),
 | 
					            State = rabbit_event:init_stats_timer(State1, #state.stats_timer),
 | 
				
			||||||
            gen_server:enter_loop(?MODULE, [], State);
 | 
					            gen_server:enter_loop(?MODULE, [], State);
 | 
				
			||||||
| 
						 | 
					@ -123,67 +108,51 @@ init(Ref) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_call({info, InfoItems}, _From, State) ->
 | 
					handle_call({info, InfoItems}, _From, State) ->
 | 
				
			||||||
    {reply, infos(InfoItems, State), State, ?HIBERNATE_AFTER};
 | 
					    {reply, infos(InfoItems, State), State, ?HIBERNATE_AFTER};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_call(Msg, From, State) ->
 | 
					handle_call(Msg, From, State) ->
 | 
				
			||||||
    {stop, {mqtt_unexpected_call, Msg, From}, State}.
 | 
					    {stop, {mqtt_unexpected_call, Msg, From}, State}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_cast(
 | 
					handle_cast(duplicate_id,
 | 
				
			||||||
    duplicate_id,
 | 
					            State = #state{ proc_state = PState,
 | 
				
			||||||
    State = #state{
 | 
					                            conn_name  = ConnName }) ->
 | 
				
			||||||
        proc_state = PState,
 | 
					    ?LOG_WARNING("MQTT disconnecting client ~tp with duplicate id '~ts'",
 | 
				
			||||||
        conn_name = ConnName
 | 
					                 [ConnName, rabbit_mqtt_processor:info(client_id, PState)]),
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    ?LOG_WARNING(
 | 
					 | 
				
			||||||
        "MQTT disconnecting client ~tp with duplicate id '~ts'",
 | 
					 | 
				
			||||||
        [ConnName, rabbit_mqtt_processor:info(client_id, PState)]
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    {stop, {shutdown, duplicate_id}, State};
 | 
					    {stop, {shutdown, duplicate_id}, State};
 | 
				
			||||||
handle_cast(
 | 
					
 | 
				
			||||||
    decommission_node,
 | 
					handle_cast(decommission_node,
 | 
				
			||||||
    State = #state{
 | 
					            State = #state{ proc_state = PState,
 | 
				
			||||||
        proc_state = PState,
 | 
					                            conn_name  = ConnName }) ->
 | 
				
			||||||
        conn_name = ConnName
 | 
					    ?LOG_WARNING("MQTT disconnecting client ~tp with client ID '~ts' as its node is about"
 | 
				
			||||||
    }
 | 
					                 " to be decommissioned",
 | 
				
			||||||
) ->
 | 
					                 [ConnName, rabbit_mqtt_processor:info(client_id, PState)]),
 | 
				
			||||||
    ?LOG_WARNING(
 | 
					 | 
				
			||||||
        "MQTT disconnecting client ~tp with client ID '~ts' as its node is about"
 | 
					 | 
				
			||||||
        " to be decommissioned",
 | 
					 | 
				
			||||||
        [ConnName, rabbit_mqtt_processor:info(client_id, PState)]
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    {stop, {shutdown, decommission_node}, State};
 | 
					    {stop, {shutdown, decommission_node}, State};
 | 
				
			||||||
handle_cast(
 | 
					
 | 
				
			||||||
    {close_connection, Reason},
 | 
					handle_cast({close_connection, Reason},
 | 
				
			||||||
    State = #state{conn_name = ConnName, proc_state = PState}
 | 
					            State = #state{conn_name = ConnName, proc_state = PState}) ->
 | 
				
			||||||
) ->
 | 
					    ?LOG_WARNING("MQTT disconnecting client ~tp with client ID '~ts', reason: ~ts",
 | 
				
			||||||
    ?LOG_WARNING(
 | 
					                 [ConnName, rabbit_mqtt_processor:info(client_id, PState), Reason]),
 | 
				
			||||||
        "MQTT disconnecting client ~tp with client ID '~ts', reason: ~ts",
 | 
					 | 
				
			||||||
        [ConnName, rabbit_mqtt_processor:info(client_id, PState), Reason]
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    {stop, {shutdown, server_initiated_close}, State};
 | 
					    {stop, {shutdown, server_initiated_close}, State};
 | 
				
			||||||
handle_cast(
 | 
					
 | 
				
			||||||
    QueueEvent = {queue_event, _, _},
 | 
					handle_cast(QueueEvent = {queue_event, _, _},
 | 
				
			||||||
    State = #state{proc_state = PState0}
 | 
					            State = #state{proc_state = PState0}) ->
 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    case rabbit_mqtt_processor:handle_queue_event(QueueEvent, PState0) of
 | 
					    case rabbit_mqtt_processor:handle_queue_event(QueueEvent, PState0) of
 | 
				
			||||||
        {ok, PState} ->
 | 
					        {ok, PState} ->
 | 
				
			||||||
            maybe_process_deferred_recv(control_throttle(pstate(State, PState)));
 | 
					            maybe_process_deferred_recv(control_throttle(pstate(State, PState)));
 | 
				
			||||||
        {error, Reason, PState} ->
 | 
					        {error, Reason, PState} ->
 | 
				
			||||||
            {stop, Reason, pstate(State, PState)}
 | 
					            {stop, Reason, pstate(State, PState)}
 | 
				
			||||||
    end;
 | 
					    end;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_cast({force_event_refresh, Ref}, State0) ->
 | 
					handle_cast({force_event_refresh, Ref}, State0) ->
 | 
				
			||||||
    Infos = infos(?CREATION_EVENT_KEYS, State0),
 | 
					    Infos = infos(?CREATION_EVENT_KEYS, State0),
 | 
				
			||||||
    rabbit_event:notify(connection_created, Infos, Ref),
 | 
					    rabbit_event:notify(connection_created, Infos, Ref),
 | 
				
			||||||
    State = rabbit_event:init_stats_timer(State0, #state.stats_timer),
 | 
					    State = rabbit_event:init_stats_timer(State0, #state.stats_timer),
 | 
				
			||||||
    {noreply, State, ?HIBERNATE_AFTER};
 | 
					    {noreply, State, ?HIBERNATE_AFTER};
 | 
				
			||||||
handle_cast(
 | 
					
 | 
				
			||||||
    refresh_config,
 | 
					handle_cast(refresh_config, State = #state{proc_state = PState0,
 | 
				
			||||||
    State = #state{
 | 
					                                           conn_name = ConnName}) ->
 | 
				
			||||||
        proc_state = PState0,
 | 
					 | 
				
			||||||
        conn_name = ConnName
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    PState = rabbit_mqtt_processor:update_trace(ConnName, PState0),
 | 
					    PState = rabbit_mqtt_processor:update_trace(ConnName, PState0),
 | 
				
			||||||
    {noreply, pstate(State, PState), ?HIBERNATE_AFTER};
 | 
					    {noreply, pstate(State, PState), ?HIBERNATE_AFTER};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_cast(Msg, State) ->
 | 
					handle_cast(Msg, State) ->
 | 
				
			||||||
    {stop, {mqtt_unexpected_cast, Msg}, State}.
 | 
					    {stop, {mqtt_unexpected_cast, Msg}, State}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -192,53 +161,49 @@ handle_info(connection_created, State) ->
 | 
				
			||||||
    rabbit_core_metrics:connection_created(self(), Infos),
 | 
					    rabbit_core_metrics:connection_created(self(), Infos),
 | 
				
			||||||
    rabbit_event:notify(connection_created, Infos),
 | 
					    rabbit_event:notify(connection_created, Infos),
 | 
				
			||||||
    {noreply, State, ?HIBERNATE_AFTER};
 | 
					    {noreply, State, ?HIBERNATE_AFTER};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_info(timeout, State) ->
 | 
					handle_info(timeout, State) ->
 | 
				
			||||||
    rabbit_mqtt_processor:handle_pre_hibernate(),
 | 
					    rabbit_mqtt_processor:handle_pre_hibernate(),
 | 
				
			||||||
    {noreply, State, hibernate};
 | 
					    {noreply, State, hibernate};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_info({'EXIT', _Conn, Reason}, State) ->
 | 
					handle_info({'EXIT', _Conn, Reason}, State) ->
 | 
				
			||||||
    {stop, {connection_died, Reason}, State};
 | 
					    {stop, {connection_died, Reason}, State};
 | 
				
			||||||
handle_info(
 | 
					
 | 
				
			||||||
    {Tag, Sock, Data},
 | 
					handle_info({Tag, Sock, Data},
 | 
				
			||||||
    State = #state{socket = Sock, connection_state = blocked}
 | 
					            State = #state{ socket = Sock, connection_state = blocked })
 | 
				
			||||||
) when
 | 
					  when Tag =:= tcp; Tag =:= ssl ->
 | 
				
			||||||
    Tag =:= tcp; Tag =:= ssl
 | 
					    {noreply, State#state{ deferred_recv = Data }, ?HIBERNATE_AFTER};
 | 
				
			||||||
->
 | 
					
 | 
				
			||||||
    {noreply, State#state{deferred_recv = Data}, ?HIBERNATE_AFTER};
 | 
					handle_info({Tag, Sock, Data},
 | 
				
			||||||
handle_info(
 | 
					            State = #state{ socket = Sock, connection_state = running })
 | 
				
			||||||
    {Tag, Sock, Data},
 | 
					            when Tag =:= tcp; Tag =:= ssl ->
 | 
				
			||||||
    State = #state{socket = Sock, connection_state = running}
 | 
					 | 
				
			||||||
) when
 | 
					 | 
				
			||||||
    Tag =:= tcp; Tag =:= ssl
 | 
					 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    process_received_bytes(
 | 
					    process_received_bytes(
 | 
				
			||||||
        Data, control_throttle(State#state{await_recv = false})
 | 
					      Data, control_throttle(State #state{ await_recv = false }));
 | 
				
			||||||
    );
 | 
					
 | 
				
			||||||
handle_info({Tag, Sock}, State = #state{socket = Sock}) when
 | 
					handle_info({Tag, Sock}, State = #state{socket = Sock})
 | 
				
			||||||
    Tag =:= tcp_closed; Tag =:= ssl_closed
 | 
					            when Tag =:= tcp_closed; Tag =:= ssl_closed ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    network_error(closed, State);
 | 
					    network_error(closed, State);
 | 
				
			||||||
handle_info({Tag, Sock, Reason}, State = #state{socket = Sock}) when
 | 
					
 | 
				
			||||||
    Tag =:= tcp_error; Tag =:= ssl_error
 | 
					handle_info({Tag, Sock, Reason}, State = #state{socket = Sock})
 | 
				
			||||||
->
 | 
					            when Tag =:= tcp_error; Tag =:= ssl_error ->
 | 
				
			||||||
    network_error(Reason, State);
 | 
					    network_error(Reason, State);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_info({inet_reply, Sock, ok}, State = #state{socket = Sock}) ->
 | 
					handle_info({inet_reply, Sock, ok}, State = #state{socket = Sock}) ->
 | 
				
			||||||
    {noreply, State, ?HIBERNATE_AFTER};
 | 
					    {noreply, State, ?HIBERNATE_AFTER};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_info({inet_reply, Sock, {error, Reason}}, State = #state{socket = Sock}) ->
 | 
					handle_info({inet_reply, Sock, {error, Reason}}, State = #state{socket = Sock}) ->
 | 
				
			||||||
    network_error(Reason, State);
 | 
					    network_error(Reason, State);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_info({conserve_resources, Conserve}, State) ->
 | 
					handle_info({conserve_resources, Conserve}, State) ->
 | 
				
			||||||
    maybe_process_deferred_recv(
 | 
					    maybe_process_deferred_recv(
 | 
				
			||||||
        control_throttle(State#state{conserve = Conserve})
 | 
					        control_throttle(State #state{ conserve = Conserve }));
 | 
				
			||||||
    );
 | 
					
 | 
				
			||||||
handle_info({bump_credit, Msg}, State) ->
 | 
					handle_info({bump_credit, Msg}, State) ->
 | 
				
			||||||
    credit_flow:handle_bump_msg(Msg),
 | 
					    credit_flow:handle_bump_msg(Msg),
 | 
				
			||||||
    maybe_process_deferred_recv(control_throttle(State));
 | 
					    maybe_process_deferred_recv(control_throttle(State));
 | 
				
			||||||
handle_info(
 | 
					
 | 
				
			||||||
    {keepalive, Req},
 | 
					handle_info({keepalive, Req}, State = #state{keepalive = KState0,
 | 
				
			||||||
    State = #state{
 | 
					                                             conn_name = ConnName}) ->
 | 
				
			||||||
        keepalive = KState0,
 | 
					 | 
				
			||||||
        conn_name = ConnName
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    case rabbit_mqtt_keepalive:handle(Req, KState0) of
 | 
					    case rabbit_mqtt_keepalive:handle(Req, KState0) of
 | 
				
			||||||
        {ok, KState} ->
 | 
					        {ok, KState} ->
 | 
				
			||||||
            {noreply, State#state{keepalive = KState}, ?HIBERNATE_AFTER};
 | 
					            {noreply, State#state{keepalive = KState}, ?HIBERNATE_AFTER};
 | 
				
			||||||
| 
						 | 
					@ -248,6 +213,7 @@ handle_info(
 | 
				
			||||||
        {error, Reason} ->
 | 
					        {error, Reason} ->
 | 
				
			||||||
            {stop, Reason, State}
 | 
					            {stop, Reason, State}
 | 
				
			||||||
    end;
 | 
					    end;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_info(login_timeout, State = #state{received_connect_packet = true}) ->
 | 
					handle_info(login_timeout, State = #state{received_connect_packet = true}) ->
 | 
				
			||||||
    {noreply, State, ?HIBERNATE_AFTER};
 | 
					    {noreply, State, ?HIBERNATE_AFTER};
 | 
				
			||||||
handle_info(login_timeout, State = #state{conn_name = ConnName}) ->
 | 
					handle_info(login_timeout, State = #state{conn_name = ConnName}) ->
 | 
				
			||||||
| 
						 | 
					@ -258,47 +224,43 @@ handle_info(login_timeout, State = #state{conn_name = ConnName}) ->
 | 
				
			||||||
    %% and we don't want to skip closing the connection in that case.
 | 
					    %% and we don't want to skip closing the connection in that case.
 | 
				
			||||||
    ?LOG_ERROR("closing MQTT connection ~tp (login timeout)", [ConnName]),
 | 
					    ?LOG_ERROR("closing MQTT connection ~tp (login timeout)", [ConnName]),
 | 
				
			||||||
    {stop, {shutdown, login_timeout}, State};
 | 
					    {stop, {shutdown, login_timeout}, State};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_info(emit_stats, State) ->
 | 
					handle_info(emit_stats, State) ->
 | 
				
			||||||
    {noreply, emit_stats(State), ?HIBERNATE_AFTER};
 | 
					    {noreply, emit_stats(State), ?HIBERNATE_AFTER};
 | 
				
			||||||
handle_info(
 | 
					
 | 
				
			||||||
    {ra_event, _From, Evt},
 | 
					handle_info({ra_event, _From, Evt},
 | 
				
			||||||
    #state{proc_state = PState0} = State
 | 
					            #state{proc_state = PState0} = State) ->
 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    %% handle applied event to ensure registration command actually got applied
 | 
					    %% handle applied event to ensure registration command actually got applied
 | 
				
			||||||
    %% handle not_leader notification in case we send the command to a non-leader
 | 
					    %% handle not_leader notification in case we send the command to a non-leader
 | 
				
			||||||
    PState = rabbit_mqtt_processor:handle_ra_event(Evt, PState0),
 | 
					    PState = rabbit_mqtt_processor:handle_ra_event(Evt, PState0),
 | 
				
			||||||
    {noreply, pstate(State, PState), ?HIBERNATE_AFTER};
 | 
					    {noreply, pstate(State, PState), ?HIBERNATE_AFTER};
 | 
				
			||||||
handle_info(
 | 
					
 | 
				
			||||||
    {{'DOWN', _QName}, _MRef, process, _Pid, _Reason} = Evt,
 | 
					handle_info({{'DOWN', _QName}, _MRef, process, _Pid, _Reason} = Evt,
 | 
				
			||||||
    #state{proc_state = PState0} = State
 | 
					            #state{proc_state = PState0} = State) ->
 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    case rabbit_mqtt_processor:handle_down(Evt, PState0) of
 | 
					    case rabbit_mqtt_processor:handle_down(Evt, PState0) of
 | 
				
			||||||
        {ok, PState} ->
 | 
					        {ok, PState} ->
 | 
				
			||||||
            maybe_process_deferred_recv(control_throttle(pstate(State, PState)));
 | 
					            maybe_process_deferred_recv(control_throttle(pstate(State, PState)));
 | 
				
			||||||
        {error, Reason} ->
 | 
					        {error, Reason} ->
 | 
				
			||||||
            {stop, {shutdown, Reason, State}}
 | 
					            {stop, {shutdown, Reason, State}}
 | 
				
			||||||
    end;
 | 
					    end;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_info({'DOWN', _MRef, process, QPid, _Reason}, State) ->
 | 
					handle_info({'DOWN', _MRef, process, QPid, _Reason}, State) ->
 | 
				
			||||||
    rabbit_amqqueue_common:notify_sent_queue_down(QPid),
 | 
					    rabbit_amqqueue_common:notify_sent_queue_down(QPid),
 | 
				
			||||||
    {noreply, State, ?HIBERNATE_AFTER};
 | 
					    {noreply, State, ?HIBERNATE_AFTER};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_info({shutdown, Explanation} = Reason, State = #state{conn_name = ConnName}) ->
 | 
					handle_info({shutdown, Explanation} = Reason, State = #state{conn_name = ConnName}) ->
 | 
				
			||||||
    %% rabbitmq_management plugin requests to close connection.
 | 
					    %% rabbitmq_management plugin requests to close connection.
 | 
				
			||||||
    ?LOG_INFO("MQTT closing connection ~tp: ~p", [ConnName, Explanation]),
 | 
					    ?LOG_INFO("MQTT closing connection ~tp: ~p", [ConnName, Explanation]),
 | 
				
			||||||
    {stop, Reason, State};
 | 
					    {stop, Reason, State};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_info(Msg, State) ->
 | 
					handle_info(Msg, State) ->
 | 
				
			||||||
    {stop, {mqtt_unexpected_msg, Msg}, State}.
 | 
					    {stop, {mqtt_unexpected_msg, Msg}, State}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
terminate(Reason, State = #state{}) ->
 | 
					terminate(Reason, State = #state{}) ->
 | 
				
			||||||
    terminate(Reason, {true, State});
 | 
					    terminate(Reason, {true, State});
 | 
				
			||||||
terminate(
 | 
					terminate(Reason, {SendWill, State = #state{conn_name = ConnName,
 | 
				
			||||||
    Reason,
 | 
					                                            keepalive = KState0,
 | 
				
			||||||
    {SendWill,
 | 
					                                            proc_state = PState}}) ->
 | 
				
			||||||
        State = #state{
 | 
					 | 
				
			||||||
            conn_name = ConnName,
 | 
					 | 
				
			||||||
            keepalive = KState0,
 | 
					 | 
				
			||||||
            proc_state = PState
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    KState = rabbit_mqtt_keepalive:cancel_timer(KState0),
 | 
					    KState = rabbit_mqtt_keepalive:cancel_timer(KState0),
 | 
				
			||||||
    maybe_emit_stats(State#state{keepalive = KState}),
 | 
					    maybe_emit_stats(State#state{keepalive = KState}),
 | 
				
			||||||
    _ = rabbit_mqtt_processor:terminate(SendWill, ConnName, ?PROTO_FAMILY, PState),
 | 
					    _ = rabbit_mqtt_processor:terminate(SendWill, ConnName, ?PROTO_FAMILY, PState),
 | 
				
			||||||
| 
						 | 
					@ -306,25 +268,36 @@ terminate(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
log_terminate({network_error, {ssl_upgrade_error, closed}, ConnName}, _State) ->
 | 
					log_terminate({network_error, {ssl_upgrade_error, closed}, ConnName}, _State) ->
 | 
				
			||||||
    ?LOG_ERROR("MQTT detected TLS upgrade error on ~s: connection closed", [ConnName]);
 | 
					    ?LOG_ERROR("MQTT detected TLS upgrade error on ~s: connection closed", [ConnName]);
 | 
				
			||||||
log_terminate(
 | 
					
 | 
				
			||||||
    {network_error, {ssl_upgrade_error, {tls_alert, "handshake failure"}}, ConnName}, _State
 | 
					log_terminate({network_error,
 | 
				
			||||||
) ->
 | 
					               {ssl_upgrade_error,
 | 
				
			||||||
 | 
					                {tls_alert, "handshake failure"}}, ConnName}, _State) ->
 | 
				
			||||||
    log_tls_alert(handshake_failure, ConnName);
 | 
					    log_tls_alert(handshake_failure, ConnName);
 | 
				
			||||||
log_terminate({network_error, {ssl_upgrade_error, {tls_alert, "unknown ca"}}, ConnName}, _State) ->
 | 
					log_terminate({network_error,
 | 
				
			||||||
 | 
					               {ssl_upgrade_error,
 | 
				
			||||||
 | 
					                {tls_alert, "unknown ca"}}, ConnName}, _State) ->
 | 
				
			||||||
    log_tls_alert(unknown_ca, ConnName);
 | 
					    log_tls_alert(unknown_ca, ConnName);
 | 
				
			||||||
log_terminate({network_error, {ssl_upgrade_error, {tls_alert, {Err, _}}}, ConnName}, _State) ->
 | 
					log_terminate({network_error,
 | 
				
			||||||
 | 
					               {ssl_upgrade_error,
 | 
				
			||||||
 | 
					                {tls_alert, {Err, _}}}, ConnName}, _State) ->
 | 
				
			||||||
    log_tls_alert(Err, ConnName);
 | 
					    log_tls_alert(Err, ConnName);
 | 
				
			||||||
log_terminate({network_error, {ssl_upgrade_error, {tls_alert, Alert}}, ConnName}, _State) ->
 | 
					log_terminate({network_error,
 | 
				
			||||||
 | 
					               {ssl_upgrade_error,
 | 
				
			||||||
 | 
					                {tls_alert, Alert}}, ConnName}, _State) ->
 | 
				
			||||||
    log_tls_alert(Alert, ConnName);
 | 
					    log_tls_alert(Alert, ConnName);
 | 
				
			||||||
log_terminate({network_error, {ssl_upgrade_error, Reason}, ConnName}, _State) ->
 | 
					log_terminate({network_error, {ssl_upgrade_error, Reason}, ConnName}, _State) ->
 | 
				
			||||||
    ?LOG_ERROR("MQTT detected TLS upgrade error on ~s: ~p", [ConnName, Reason]);
 | 
					    ?LOG_ERROR("MQTT detected TLS upgrade error on ~s: ~p", [ConnName, Reason]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
log_terminate({network_error, Reason, ConnName}, _State) ->
 | 
					log_terminate({network_error, Reason, ConnName}, _State) ->
 | 
				
			||||||
    ?LOG_ERROR("MQTT detected network error on ~s: ~p", [ConnName, Reason]);
 | 
					    ?LOG_ERROR("MQTT detected network error on ~s: ~p", [ConnName, Reason]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
log_terminate({network_error, Reason}, _State) ->
 | 
					log_terminate({network_error, Reason}, _State) ->
 | 
				
			||||||
    ?LOG_ERROR("MQTT detected network error: ~p", [Reason]);
 | 
					    ?LOG_ERROR("MQTT detected network error: ~p", [Reason]);
 | 
				
			||||||
log_terminate(normal, #state{conn_name = ConnName}) ->
 | 
					
 | 
				
			||||||
 | 
					log_terminate(normal, #state{conn_name  = ConnName}) ->
 | 
				
			||||||
    ?LOG_INFO("closing MQTT connection ~p (~s)", [self(), ConnName]),
 | 
					    ?LOG_INFO("closing MQTT connection ~p (~s)", [self(), ConnName]),
 | 
				
			||||||
    ok;
 | 
					    ok;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
log_terminate(_Reason, _State) ->
 | 
					log_terminate(_Reason, _State) ->
 | 
				
			||||||
    ok.
 | 
					    ok.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -336,80 +309,54 @@ code_change(_OldVsn, State, _Extra) ->
 | 
				
			||||||
log_tls_alert(handshake_failure, ConnName) ->
 | 
					log_tls_alert(handshake_failure, ConnName) ->
 | 
				
			||||||
    ?LOG_ERROR("MQTT detected TLS upgrade error on ~ts: handshake failure", [ConnName]);
 | 
					    ?LOG_ERROR("MQTT detected TLS upgrade error on ~ts: handshake failure", [ConnName]);
 | 
				
			||||||
log_tls_alert(unknown_ca, ConnName) ->
 | 
					log_tls_alert(unknown_ca, ConnName) ->
 | 
				
			||||||
    ?LOG_ERROR(
 | 
					    ?LOG_ERROR("MQTT detected TLS certificate verification error on ~ts: alert 'unknown CA'",
 | 
				
			||||||
        "MQTT detected TLS certificate verification error on ~ts: alert 'unknown CA'",
 | 
					               [ConnName]);
 | 
				
			||||||
        [ConnName]
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
log_tls_alert(Alert, ConnName) ->
 | 
					log_tls_alert(Alert, ConnName) ->
 | 
				
			||||||
    ?LOG_ERROR("MQTT detected TLS upgrade error on ~ts: alert ~ts", [ConnName, Alert]).
 | 
					    ?LOG_ERROR("MQTT detected TLS upgrade error on ~ts: alert ~ts", [ConnName, Alert]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
process_received_bytes(
 | 
					process_received_bytes(<<>>, State = #state{received_connect_packet = false,
 | 
				
			||||||
    <<>>,
 | 
					                                            proc_state = PState,
 | 
				
			||||||
    State = #state{
 | 
					                                            conn_name = ConnName}) ->
 | 
				
			||||||
        received_connect_packet = false,
 | 
					    ?LOG_INFO("Accepted MQTT connection ~p (~s, client ID: ~s)",
 | 
				
			||||||
        proc_state = PState,
 | 
					              [self(), ConnName, rabbit_mqtt_processor:info(client_id, PState)]),
 | 
				
			||||||
        conn_name = ConnName
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    ?LOG_INFO(
 | 
					 | 
				
			||||||
        "Accepted MQTT connection ~p (~s, client ID: ~s)",
 | 
					 | 
				
			||||||
        [self(), ConnName, rabbit_mqtt_processor:info(client_id, PState)]
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    {noreply, ensure_stats_timer(State#state{received_connect_packet = true}), ?HIBERNATE_AFTER};
 | 
					    {noreply, ensure_stats_timer(State#state{received_connect_packet = true}), ?HIBERNATE_AFTER};
 | 
				
			||||||
process_received_bytes(<<>>, State) ->
 | 
					process_received_bytes(<<>>, State) ->
 | 
				
			||||||
    {noreply, ensure_stats_timer(State), ?HIBERNATE_AFTER};
 | 
					    {noreply, ensure_stats_timer(State), ?HIBERNATE_AFTER};
 | 
				
			||||||
process_received_bytes(
 | 
					process_received_bytes(Bytes,
 | 
				
			||||||
    Bytes,
 | 
					                       State = #state{ parse_state = ParseState,
 | 
				
			||||||
    State = #state{
 | 
					                                       proc_state  = ProcState,
 | 
				
			||||||
        parse_state = ParseState,
 | 
					                                       conn_name   = ConnName }) ->
 | 
				
			||||||
        proc_state = ProcState,
 | 
					 | 
				
			||||||
        conn_name = ConnName
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    case parse(Bytes, ParseState) of
 | 
					    case parse(Bytes, ParseState) of
 | 
				
			||||||
        {more, ParseState1} ->
 | 
					        {more, ParseState1} ->
 | 
				
			||||||
            {noreply, ensure_stats_timer(State#state{parse_state = ParseState1}), ?HIBERNATE_AFTER};
 | 
					            {noreply,
 | 
				
			||||||
 | 
					             ensure_stats_timer( State #state{ parse_state = ParseState1 }),
 | 
				
			||||||
 | 
					             ?HIBERNATE_AFTER};
 | 
				
			||||||
        {ok, Packet, Rest} ->
 | 
					        {ok, Packet, Rest} ->
 | 
				
			||||||
            case rabbit_mqtt_processor:process_packet(Packet, ProcState) of
 | 
					            case rabbit_mqtt_processor:process_packet(Packet, ProcState) of
 | 
				
			||||||
                {ok, ProcState1} ->
 | 
					                {ok, ProcState1} ->
 | 
				
			||||||
                    process_received_bytes(
 | 
					                    process_received_bytes(
 | 
				
			||||||
                        Rest,
 | 
					                      Rest,
 | 
				
			||||||
                        State#state{
 | 
					                      State #state{parse_state = rabbit_mqtt_packet:initial_state(),
 | 
				
			||||||
                            parse_state = rabbit_mqtt_packet:initial_state(),
 | 
					                                   proc_state = ProcState1});
 | 
				
			||||||
                            proc_state = ProcState1
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    );
 | 
					 | 
				
			||||||
                %% PUBLISH and more
 | 
					                %% PUBLISH and more
 | 
				
			||||||
                {error, unauthorized = Reason, ProcState1} ->
 | 
					                {error, unauthorized = Reason, ProcState1} ->
 | 
				
			||||||
                    ?LOG_ERROR("MQTT connection ~ts is closing due to an authorization failure", [
 | 
					                    ?LOG_ERROR("MQTT connection ~ts is closing due to an authorization failure", [ConnName]),
 | 
				
			||||||
                        ConnName
 | 
					 | 
				
			||||||
                    ]),
 | 
					 | 
				
			||||||
                    {stop, {shutdown, Reason}, pstate(State, ProcState1)};
 | 
					                    {stop, {shutdown, Reason}, pstate(State, ProcState1)};
 | 
				
			||||||
                %% CONNECT packets only
 | 
					                %% CONNECT packets only
 | 
				
			||||||
                {error, unauthenticated = Reason, ProcState1} ->
 | 
					                {error, unauthenticated = Reason, ProcState1} ->
 | 
				
			||||||
                    ?LOG_ERROR("MQTT connection ~ts is closing due to an authentication failure", [
 | 
					                    ?LOG_ERROR("MQTT connection ~ts is closing due to an authentication failure", [ConnName]),
 | 
				
			||||||
                        ConnName
 | 
					 | 
				
			||||||
                    ]),
 | 
					 | 
				
			||||||
                    {stop, {shutdown, Reason}, pstate(State, ProcState1)};
 | 
					                    {stop, {shutdown, Reason}, pstate(State, ProcState1)};
 | 
				
			||||||
                %% CONNECT packets only
 | 
					                %% CONNECT packets only
 | 
				
			||||||
                {error, invalid_client_id = Reason, ProcState1} ->
 | 
					                {error, invalid_client_id = Reason, ProcState1} ->
 | 
				
			||||||
                    ?LOG_ERROR("MQTT cannot accept connection ~ts: client uses an invalid ID", [
 | 
					                    ?LOG_ERROR("MQTT cannot accept connection ~ts: client uses an invalid ID", [ConnName]),
 | 
				
			||||||
                        ConnName
 | 
					 | 
				
			||||||
                    ]),
 | 
					 | 
				
			||||||
                    {stop, {shutdown, Reason}, pstate(State, ProcState1)};
 | 
					                    {stop, {shutdown, Reason}, pstate(State, ProcState1)};
 | 
				
			||||||
                %% CONNECT packets only
 | 
					                %% CONNECT packets only
 | 
				
			||||||
                {error, unsupported_protocol_version = Reason, ProcState1} ->
 | 
					                {error, unsupported_protocol_version = Reason, ProcState1} ->
 | 
				
			||||||
                    ?LOG_ERROR(
 | 
					                    ?LOG_ERROR("MQTT cannot accept connection ~ts: incompatible protocol version", [ConnName]),
 | 
				
			||||||
                        "MQTT cannot accept connection ~ts: incompatible protocol version", [
 | 
					 | 
				
			||||||
                            ConnName
 | 
					 | 
				
			||||||
                        ]
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    {stop, {shutdown, Reason}, pstate(State, ProcState1)};
 | 
					                    {stop, {shutdown, Reason}, pstate(State, ProcState1)};
 | 
				
			||||||
                {error, unavailable = Reason, ProcState1} ->
 | 
					                {error, unavailable = Reason, ProcState1} ->
 | 
				
			||||||
                    ?LOG_ERROR(
 | 
					                    ?LOG_ERROR("MQTT cannot accept connection ~ts due to an internal error or unavailable component",
 | 
				
			||||||
                        "MQTT cannot accept connection ~ts due to an internal error or unavailable component",
 | 
					                               [ConnName]),
 | 
				
			||||||
                        [ConnName]
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    {stop, {shutdown, Reason}, pstate(State, ProcState1)};
 | 
					                    {stop, {shutdown, Reason}, pstate(State, ProcState1)};
 | 
				
			||||||
                {error, Reason, ProcState1} ->
 | 
					                {error, Reason, ProcState1} ->
 | 
				
			||||||
                    ?LOG_ERROR("MQTT protocol error on connection ~ts: ~tp", [ConnName, Reason]),
 | 
					                    ?LOG_ERROR("MQTT protocol error on connection ~ts: ~tp", [ConnName, Reason]),
 | 
				
			||||||
| 
						 | 
					@ -418,11 +365,9 @@ process_received_bytes(
 | 
				
			||||||
                    {stop, normal, {_SendWill = false, pstate(State, ProcState1)}}
 | 
					                    {stop, normal, {_SendWill = false, pstate(State, ProcState1)}}
 | 
				
			||||||
            end;
 | 
					            end;
 | 
				
			||||||
        {error, {cannot_parse, Reason, Stacktrace}} ->
 | 
					        {error, {cannot_parse, Reason, Stacktrace}} ->
 | 
				
			||||||
            ?LOG_ERROR(
 | 
					            ?LOG_ERROR("MQTT cannot parse a packet on connection '~ts', reason: ~tp, "
 | 
				
			||||||
                "MQTT cannot parse a packet on connection '~ts', reason: ~tp, "
 | 
					                       "stacktrace: ~tp, payload (first 100 bytes): ~tp",
 | 
				
			||||||
                "stacktrace: ~tp, payload (first 100 bytes): ~tp",
 | 
					                       [ConnName, Reason, Stacktrace, rabbit_mqtt_util:truncate_binary(Bytes, 100)]),
 | 
				
			||||||
                [ConnName, Reason, Stacktrace, rabbit_mqtt_util:truncate_binary(Bytes, 100)]
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            {stop, {shutdown, Reason}, State};
 | 
					            {stop, {shutdown, Reason}, State};
 | 
				
			||||||
        {error, Error} ->
 | 
					        {error, Error} ->
 | 
				
			||||||
            ?LOG_ERROR("MQTT detected a framing error on connection ~ts: ~tp", [ConnName, Error]),
 | 
					            ?LOG_ERROR("MQTT detected a framing error on connection ~ts: ~tp", [ConnName, Error]),
 | 
				
			||||||
| 
						 | 
					@ -430,8 +375,8 @@ process_received_bytes(
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec pstate(state(), rabbit_mqtt_processor:state()) -> state().
 | 
					-spec pstate(state(), rabbit_mqtt_processor:state()) -> state().
 | 
				
			||||||
pstate(State = #state{}, PState) ->
 | 
					pstate(State = #state {}, PState) ->
 | 
				
			||||||
    State#state{proc_state = PState}.
 | 
					    State #state{ proc_state = PState }.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
%%----------------------------------------------------------------------------
 | 
					%%----------------------------------------------------------------------------
 | 
				
			||||||
parse(Bytes, ParseState) ->
 | 
					parse(Bytes, ParseState) ->
 | 
				
			||||||
| 
						 | 
					@ -442,13 +387,9 @@ parse(Bytes, ParseState) ->
 | 
				
			||||||
            {error, {cannot_parse, Reason, Stacktrace}}
 | 
					            {error, {cannot_parse, Reason, Stacktrace}}
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
network_error(
 | 
					network_error(closed,
 | 
				
			||||||
    closed,
 | 
					              State = #state{conn_name  = ConnName,
 | 
				
			||||||
    State = #state{
 | 
					                             received_connect_packet = Connected}) ->
 | 
				
			||||||
        conn_name = ConnName,
 | 
					 | 
				
			||||||
        received_connect_packet = Connected
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    Fmt = "MQTT connection ~p will terminate because peer closed TCP connection",
 | 
					    Fmt = "MQTT connection ~p will terminate because peer closed TCP connection",
 | 
				
			||||||
    Args = [ConnName],
 | 
					    Args = [ConnName],
 | 
				
			||||||
    case Connected of
 | 
					    case Connected of
 | 
				
			||||||
| 
						 | 
					@ -456,77 +397,62 @@ network_error(
 | 
				
			||||||
        false -> ?LOG_DEBUG(Fmt, Args)
 | 
					        false -> ?LOG_DEBUG(Fmt, Args)
 | 
				
			||||||
    end,
 | 
					    end,
 | 
				
			||||||
    {stop, {shutdown, conn_closed}, State};
 | 
					    {stop, {shutdown, conn_closed}, State};
 | 
				
			||||||
network_error(
 | 
					
 | 
				
			||||||
    Reason,
 | 
					network_error(Reason,
 | 
				
			||||||
    State = #state{conn_name = ConnName}
 | 
					              State = #state{conn_name  = ConnName}) ->
 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    ?LOG_INFO("MQTT detected network error for ~p: ~p", [ConnName, Reason]),
 | 
					    ?LOG_INFO("MQTT detected network error for ~p: ~p", [ConnName, Reason]),
 | 
				
			||||||
    {stop, {shutdown, conn_closed}, State}.
 | 
					    {stop, {shutdown, conn_closed}, State}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
run_socket(State = #state{connection_state = blocked}) ->
 | 
					run_socket(State = #state{ connection_state = blocked }) ->
 | 
				
			||||||
    State;
 | 
					    State;
 | 
				
			||||||
run_socket(State = #state{deferred_recv = Data}) when Data =/= undefined ->
 | 
					run_socket(State = #state{ deferred_recv = Data }) when Data =/= undefined ->
 | 
				
			||||||
    State;
 | 
					    State;
 | 
				
			||||||
run_socket(State = #state{await_recv = true}) ->
 | 
					run_socket(State = #state{ await_recv = true }) ->
 | 
				
			||||||
    State;
 | 
					    State;
 | 
				
			||||||
run_socket(State = #state{socket = Sock}) ->
 | 
					run_socket(State = #state{ socket = Sock }) ->
 | 
				
			||||||
    ok = rabbit_net:setopts(Sock, [{active, once}]),
 | 
					    ok = rabbit_net:setopts(Sock, [{active, once}]),
 | 
				
			||||||
    State#state{await_recv = true}.
 | 
					    State#state{ await_recv = true }.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
control_throttle(
 | 
					control_throttle(State = #state{connection_state = ConnState,
 | 
				
			||||||
    State = #state{
 | 
					                                conserve = Conserve,
 | 
				
			||||||
        connection_state = ConnState,
 | 
					                                received_connect_packet = Connected,
 | 
				
			||||||
        conserve = Conserve,
 | 
					                                proc_state = PState,
 | 
				
			||||||
        received_connect_packet = Connected,
 | 
					                                keepalive = KState
 | 
				
			||||||
        proc_state = PState,
 | 
					                               }) ->
 | 
				
			||||||
        keepalive = KState
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    Throttle = rabbit_mqtt_processor:throttle(Conserve, Connected, PState),
 | 
					    Throttle = rabbit_mqtt_processor:throttle(Conserve, Connected, PState),
 | 
				
			||||||
    case {ConnState, Throttle} of
 | 
					    case {ConnState, Throttle} of
 | 
				
			||||||
        {running, true} ->
 | 
					        {running, true} ->
 | 
				
			||||||
            State#state{
 | 
					            State#state{connection_state = blocked,
 | 
				
			||||||
                connection_state = blocked,
 | 
					                        keepalive = rabbit_mqtt_keepalive:cancel_timer(KState)};
 | 
				
			||||||
                keepalive = rabbit_mqtt_keepalive:cancel_timer(KState)
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
        {blocked, false} ->
 | 
					        {blocked, false} ->
 | 
				
			||||||
            run_socket(State#state{
 | 
					            run_socket(State#state{connection_state = running,
 | 
				
			||||||
                connection_state = running,
 | 
					                                   keepalive = rabbit_mqtt_keepalive:start_timer(KState)});
 | 
				
			||||||
                keepalive = rabbit_mqtt_keepalive:start_timer(KState)
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        {_, _} ->
 | 
					        {_, _} ->
 | 
				
			||||||
            run_socket(State)
 | 
					            run_socket(State)
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
maybe_process_deferred_recv(State = #state{deferred_recv = undefined}) ->
 | 
					maybe_process_deferred_recv(State = #state{ deferred_recv = undefined }) ->
 | 
				
			||||||
    {noreply, State, ?HIBERNATE_AFTER};
 | 
					    {noreply, State, ?HIBERNATE_AFTER};
 | 
				
			||||||
maybe_process_deferred_recv(State = #state{deferred_recv = Data, socket = Sock}) ->
 | 
					maybe_process_deferred_recv(State = #state{ deferred_recv = Data, socket = Sock }) ->
 | 
				
			||||||
    handle_info(
 | 
					    handle_info({tcp, Sock, Data},
 | 
				
			||||||
        {tcp, Sock, Data},
 | 
					                State#state{ deferred_recv = undefined }).
 | 
				
			||||||
        State#state{deferred_recv = undefined}
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
maybe_emit_stats(#state{stats_timer = undefined}) ->
 | 
					maybe_emit_stats(#state{stats_timer = undefined}) ->
 | 
				
			||||||
    ok;
 | 
					    ok;
 | 
				
			||||||
maybe_emit_stats(State) ->
 | 
					maybe_emit_stats(State) ->
 | 
				
			||||||
    rabbit_event:if_enabled(
 | 
					    rabbit_event:if_enabled(State, #state.stats_timer,
 | 
				
			||||||
        State,
 | 
					                            fun() -> emit_stats(State) end).
 | 
				
			||||||
        #state.stats_timer,
 | 
					 | 
				
			||||||
        fun() -> emit_stats(State) end
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
emit_stats(State = #state{received_connect_packet = false}) ->
 | 
					emit_stats(State=#state{received_connect_packet = false}) ->
 | 
				
			||||||
    %% Avoid emitting stats on terminate when the connection has not yet been
 | 
					    %% Avoid emitting stats on terminate when the connection has not yet been
 | 
				
			||||||
    %% established, as this causes orphan entries on the stats database
 | 
					    %% established, as this causes orphan entries on the stats database
 | 
				
			||||||
    State1 = rabbit_event:reset_stats_timer(State, #state.stats_timer),
 | 
					    State1 = rabbit_event:reset_stats_timer(State, #state.stats_timer),
 | 
				
			||||||
    ensure_stats_timer(State1);
 | 
					    ensure_stats_timer(State1);
 | 
				
			||||||
emit_stats(State) ->
 | 
					emit_stats(State) ->
 | 
				
			||||||
    [
 | 
					    [{_, Pid},
 | 
				
			||||||
        {_, Pid},
 | 
					     {_, RecvOct},
 | 
				
			||||||
        {_, RecvOct},
 | 
					     {_, SendOct},
 | 
				
			||||||
        {_, SendOct},
 | 
					     {_, Reductions}] = infos(?SIMPLE_METRICS, State),
 | 
				
			||||||
        {_, Reductions}
 | 
					 | 
				
			||||||
    ] = infos(?SIMPLE_METRICS, State),
 | 
					 | 
				
			||||||
    Infos = infos(?OTHER_METRICS, State),
 | 
					    Infos = infos(?OTHER_METRICS, State),
 | 
				
			||||||
    rabbit_core_metrics:connection_stats(Pid, Infos),
 | 
					    rabbit_core_metrics:connection_stats(Pid, Infos),
 | 
				
			||||||
    rabbit_core_metrics:connection_stats(Pid, RecvOct, SendOct, Reductions),
 | 
					    rabbit_core_metrics:connection_stats(Pid, RecvOct, SendOct, Reductions),
 | 
				
			||||||
| 
						 | 
					@ -539,13 +465,12 @@ ensure_stats_timer(State = #state{}) ->
 | 
				
			||||||
infos(Items, State) ->
 | 
					infos(Items, State) ->
 | 
				
			||||||
    [{Item, i(Item, State)} || Item <- Items].
 | 
					    [{Item, i(Item, State)} || Item <- Items].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
i(SockStat, #state{socket = Sock}) when
 | 
					i(SockStat, #state{socket = Sock})
 | 
				
			||||||
    SockStat =:= recv_oct;
 | 
					  when SockStat =:= recv_oct;
 | 
				
			||||||
    SockStat =:= recv_cnt;
 | 
					       SockStat =:= recv_cnt;
 | 
				
			||||||
    SockStat =:= send_oct;
 | 
					       SockStat =:= send_oct;
 | 
				
			||||||
    SockStat =:= send_cnt;
 | 
					       SockStat =:= send_cnt;
 | 
				
			||||||
    SockStat =:= send_pend
 | 
					       SockStat =:= send_pend ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    case rabbit_net:getstat(Sock, [SockStat]) of
 | 
					    case rabbit_net:getstat(Sock, [SockStat]) of
 | 
				
			||||||
        {ok, [{_, N}]} when is_number(N) ->
 | 
					        {ok, [{_, N}]} when is_number(N) ->
 | 
				
			||||||
            N;
 | 
					            N;
 | 
				
			||||||
| 
						 | 
					@ -569,19 +494,17 @@ i(connection_state, #state{connection_state = Val}) ->
 | 
				
			||||||
    Val;
 | 
					    Val;
 | 
				
			||||||
i(pid, _) ->
 | 
					i(pid, _) ->
 | 
				
			||||||
    self();
 | 
					    self();
 | 
				
			||||||
i(SSL, #state{socket = Sock, proxy_socket = ProxySock}) when
 | 
					i(SSL, #state{socket = Sock, proxy_socket = ProxySock})
 | 
				
			||||||
    SSL =:= ssl;
 | 
					  when SSL =:= ssl;
 | 
				
			||||||
    SSL =:= ssl_protocol;
 | 
					       SSL =:= ssl_protocol;
 | 
				
			||||||
    SSL =:= ssl_key_exchange;
 | 
					       SSL =:= ssl_key_exchange;
 | 
				
			||||||
    SSL =:= ssl_cipher;
 | 
					       SSL =:= ssl_cipher;
 | 
				
			||||||
    SSL =:= ssl_hash
 | 
					       SSL =:= ssl_hash ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    rabbit_ssl:info(SSL, {Sock, ProxySock});
 | 
					    rabbit_ssl:info(SSL, {Sock, ProxySock});
 | 
				
			||||||
i(Cert, #state{socket = Sock}) when
 | 
					i(Cert, #state{socket = Sock})
 | 
				
			||||||
    Cert =:= peer_cert_issuer;
 | 
					  when Cert =:= peer_cert_issuer;
 | 
				
			||||||
    Cert =:= peer_cert_subject;
 | 
					       Cert =:= peer_cert_subject;
 | 
				
			||||||
    Cert =:= peer_cert_validity
 | 
					       Cert =:= peer_cert_validity ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    rabbit_ssl:cert_info(Cert, Sock);
 | 
					    rabbit_ssl:cert_info(Cert, Sock);
 | 
				
			||||||
i(timeout, #state{keepalive = KState}) ->
 | 
					i(timeout, #state{keepalive = KState}) ->
 | 
				
			||||||
    rabbit_mqtt_keepalive:interval_secs(KState);
 | 
					    rabbit_mqtt_keepalive:interval_secs(KState);
 | 
				
			||||||
| 
						 | 
					@ -591,48 +514,40 @@ i(Key, #state{proc_state = ProcState}) ->
 | 
				
			||||||
    rabbit_mqtt_processor:info(Key, ProcState).
 | 
					    rabbit_mqtt_processor:info(Key, ProcState).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec format_status(Status) -> Status when
 | 
					-spec format_status(Status) -> Status when
 | 
				
			||||||
    Status :: #{
 | 
					      Status :: #{state => term(),
 | 
				
			||||||
        state => term(),
 | 
					                  message => term(),
 | 
				
			||||||
        message => term(),
 | 
					                  reason => term(),
 | 
				
			||||||
        reason => term(),
 | 
					                  log => [sys:system_event()]}.
 | 
				
			||||||
        log => [sys:system_event()]
 | 
					 | 
				
			||||||
    }.
 | 
					 | 
				
			||||||
format_status(Status) ->
 | 
					format_status(Status) ->
 | 
				
			||||||
    maps:map(
 | 
					    maps:map(
 | 
				
			||||||
        fun
 | 
					      fun(state, State) ->
 | 
				
			||||||
            (state, State) ->
 | 
					              format_state(State);
 | 
				
			||||||
                format_state(State);
 | 
					         (_, Value) ->
 | 
				
			||||||
            (_, Value) ->
 | 
					              Value
 | 
				
			||||||
                Value
 | 
					      end, Status).
 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
        Status
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec format_state(state()) -> map().
 | 
					-spec format_state(state()) -> map().
 | 
				
			||||||
format_state(#state{
 | 
					format_state(#state{socket = Socket,
 | 
				
			||||||
    socket = Socket,
 | 
					                    proxy_socket = ProxySock,
 | 
				
			||||||
    proxy_socket = ProxySock,
 | 
					                    await_recv = AwaitRecv,
 | 
				
			||||||
    await_recv = AwaitRecv,
 | 
					                    deferred_recv = DeferredRecv,
 | 
				
			||||||
    deferred_recv = DeferredRecv,
 | 
					                    parse_state = _,
 | 
				
			||||||
    parse_state = _,
 | 
					                    proc_state = PState,
 | 
				
			||||||
    proc_state = PState,
 | 
					                    connection_state = ConnectionState,
 | 
				
			||||||
    connection_state = ConnectionState,
 | 
					                    conserve = Conserve,
 | 
				
			||||||
    conserve = Conserve,
 | 
					                    stats_timer = StatsTimer,
 | 
				
			||||||
    stats_timer = StatsTimer,
 | 
					                    keepalive = Keepalive,
 | 
				
			||||||
    keepalive = Keepalive,
 | 
					                    conn_name = ConnName,
 | 
				
			||||||
    conn_name = ConnName,
 | 
					                    received_connect_packet = ReceivedConnectPacket
 | 
				
			||||||
    received_connect_packet = ReceivedConnectPacket
 | 
					                   }) ->
 | 
				
			||||||
}) ->
 | 
					    #{socket => Socket,
 | 
				
			||||||
    #{
 | 
					      proxy_socket => ProxySock,
 | 
				
			||||||
        socket => Socket,
 | 
					      await_recv => AwaitRecv,
 | 
				
			||||||
        proxy_socket => ProxySock,
 | 
					      deferred_recv => DeferredRecv =/= undefined,
 | 
				
			||||||
        await_recv => AwaitRecv,
 | 
					      proc_state => rabbit_mqtt_processor:format_status(PState),
 | 
				
			||||||
        deferred_recv => DeferredRecv =/= undefined,
 | 
					      connection_state => ConnectionState,
 | 
				
			||||||
        proc_state => rabbit_mqtt_processor:format_status(PState),
 | 
					      conserve => Conserve,
 | 
				
			||||||
        connection_state => ConnectionState,
 | 
					      stats_timer => StatsTimer,
 | 
				
			||||||
        conserve => Conserve,
 | 
					      keepalive => Keepalive,
 | 
				
			||||||
        stats_timer => StatsTimer,
 | 
					      conn_name => ConnName,
 | 
				
			||||||
        keepalive => Keepalive,
 | 
					      received_connect_packet => ReceivedConnectPacket}.
 | 
				
			||||||
        conn_name => ConnName,
 | 
					 | 
				
			||||||
        received_connect_packet => ReceivedConnectPacket
 | 
					 | 
				
			||||||
    }.
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,57 +13,53 @@
 | 
				
			||||||
-export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]).
 | 
					-export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-record(store_state, {
 | 
					-record(store_state, {
 | 
				
			||||||
    %% DETS table name
 | 
					  %% DETS table name
 | 
				
			||||||
    table
 | 
					  table
 | 
				
			||||||
}).
 | 
					}).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-type store_state() :: #store_state{}.
 | 
					-type store_state() :: #store_state{}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec new(file:name_all(), rabbit_types:vhost()) -> store_state().
 | 
					-spec new(file:name_all(), rabbit_types:vhost()) -> store_state().
 | 
				
			||||||
new(Dir, VHost) ->
 | 
					new(Dir, VHost) ->
 | 
				
			||||||
    Tid = open_table(Dir, VHost),
 | 
					  Tid = open_table(Dir, VHost),
 | 
				
			||||||
    #store_state{table = Tid}.
 | 
					  #store_state{table = Tid}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec recover(file:name_all(), rabbit_types:vhost()) ->
 | 
					-spec recover(file:name_all(), rabbit_types:vhost()) ->
 | 
				
			||||||
    {error, uninitialized} | {ok, store_state()}.
 | 
					  {error, uninitialized} | {ok, store_state()}.
 | 
				
			||||||
recover(Dir, VHost) ->
 | 
					recover(Dir, VHost) ->
 | 
				
			||||||
    case open_table(Dir, VHost) of
 | 
					  case open_table(Dir, VHost) of
 | 
				
			||||||
        {error, _} -> {error, uninitialized};
 | 
					    {error, _} -> {error, uninitialized};
 | 
				
			||||||
        {ok, Tid} -> {ok, #store_state{table = Tid}}
 | 
					    {ok, Tid}  -> {ok, #store_state{table = Tid}}
 | 
				
			||||||
    end.
 | 
					  end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec insert(binary(), mqtt_msg(), store_state()) -> ok.
 | 
					-spec insert(binary(), mqtt_msg(), store_state()) -> ok.
 | 
				
			||||||
insert(Topic, Msg, #store_state{table = T}) ->
 | 
					insert(Topic, Msg, #store_state{table = T}) ->
 | 
				
			||||||
    ok = dets:insert(T, #retained_message{topic = Topic, mqtt_msg = Msg}).
 | 
					  ok = dets:insert(T, #retained_message{topic = Topic, mqtt_msg = Msg}).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec lookup(binary(), store_state()) -> retained_message() | not_found.
 | 
					-spec lookup(binary(), store_state()) -> retained_message() | not_found.
 | 
				
			||||||
lookup(Topic, #store_state{table = T}) ->
 | 
					lookup(Topic, #store_state{table = T}) ->
 | 
				
			||||||
    case dets:lookup(T, Topic) of
 | 
					  case dets:lookup(T, Topic) of
 | 
				
			||||||
        [] -> not_found;
 | 
					    []      -> not_found;
 | 
				
			||||||
        [Entry] -> Entry
 | 
					    [Entry] -> Entry
 | 
				
			||||||
    end.
 | 
					  end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec delete(binary(), store_state()) -> ok.
 | 
					-spec delete(binary(), store_state()) -> ok.
 | 
				
			||||||
delete(Topic, #store_state{table = T}) ->
 | 
					delete(Topic, #store_state{table = T}) ->
 | 
				
			||||||
    ok = dets:delete(T, Topic).
 | 
					  ok = dets:delete(T, Topic).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec terminate(store_state()) -> ok.
 | 
					-spec terminate(store_state()) -> ok.
 | 
				
			||||||
terminate(#store_state{table = T}) ->
 | 
					terminate(#store_state{table = T}) ->
 | 
				
			||||||
    ok = dets:close(T).
 | 
					  ok = dets:close(T).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
open_table(Dir, VHost) ->
 | 
					open_table(Dir, VHost) ->
 | 
				
			||||||
    dets:open_file(
 | 
					    dets:open_file(rabbit_mqtt_util:vhost_name_to_table_name(VHost),
 | 
				
			||||||
        rabbit_mqtt_util:vhost_name_to_table_name(VHost),
 | 
					                   table_options(rabbit_mqtt_util:path_for(Dir, VHost, ".dets"))).
 | 
				
			||||||
        table_options(rabbit_mqtt_util:path_for(Dir, VHost, ".dets"))
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
table_options(Path) ->
 | 
					table_options(Path) ->
 | 
				
			||||||
    [
 | 
					    [{type, set},
 | 
				
			||||||
        {type, set},
 | 
					     {keypos, #retained_message.topic},
 | 
				
			||||||
        {keypos, #retained_message.topic},
 | 
					     {file, Path},
 | 
				
			||||||
        {file, Path},
 | 
					     {ram_file, true},
 | 
				
			||||||
        {ram_file, true},
 | 
					     {repair, true},
 | 
				
			||||||
        {repair, true},
 | 
					     {auto_save, rabbit_misc:get_env(rabbit_mqtt, retained_message_store_dets_sync_interval, 2000)}
 | 
				
			||||||
        {auto_save,
 | 
					 | 
				
			||||||
            rabbit_misc:get_env(rabbit_mqtt, retained_message_store_dets_sync_interval, 2000)}
 | 
					 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,51 +13,49 @@
 | 
				
			||||||
-export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]).
 | 
					-export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-record(store_state, {
 | 
					-record(store_state, {
 | 
				
			||||||
    %% ETS table ID
 | 
					  %% ETS table ID
 | 
				
			||||||
    table,
 | 
					  table,
 | 
				
			||||||
    %% where the table is stored on disk
 | 
					  %% where the table is stored on disk
 | 
				
			||||||
    filename
 | 
					  filename
 | 
				
			||||||
}).
 | 
					}).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-type store_state() :: #store_state{}.
 | 
					-type store_state() :: #store_state{}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec new(file:name_all(), rabbit_types:vhost()) -> store_state().
 | 
					-spec new(file:name_all(), rabbit_types:vhost()) -> store_state().
 | 
				
			||||||
new(Dir, VHost) ->
 | 
					new(Dir, VHost) ->
 | 
				
			||||||
    Path = rabbit_mqtt_util:path_for(Dir, VHost),
 | 
					  Path = rabbit_mqtt_util:path_for(Dir, VHost),
 | 
				
			||||||
    TableName = rabbit_mqtt_util:vhost_name_to_table_name(VHost),
 | 
					  TableName = rabbit_mqtt_util:vhost_name_to_table_name(VHost),
 | 
				
			||||||
    _ = file:delete(Path),
 | 
					  _ = file:delete(Path),
 | 
				
			||||||
    Tid = ets:new(TableName, [set, public, {keypos, #retained_message.topic}]),
 | 
					  Tid = ets:new(TableName, [set, public, {keypos, #retained_message.topic}]),
 | 
				
			||||||
    #store_state{table = Tid, filename = Path}.
 | 
					  #store_state{table = Tid, filename = Path}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec recover(file:name_all(), rabbit_types:vhost()) ->
 | 
					-spec recover(file:name_all(), rabbit_types:vhost()) ->
 | 
				
			||||||
    {error, uninitialized} | {ok, store_state()}.
 | 
					  {error, uninitialized} | {ok, store_state()}.
 | 
				
			||||||
recover(Dir, VHost) ->
 | 
					recover(Dir, VHost) ->
 | 
				
			||||||
    Path = rabbit_mqtt_util:path_for(Dir, VHost),
 | 
					  Path = rabbit_mqtt_util:path_for(Dir, VHost),
 | 
				
			||||||
    case ets:file2tab(Path) of
 | 
					  case ets:file2tab(Path) of
 | 
				
			||||||
        {ok, Tid} ->
 | 
					    {ok, Tid}  -> _ = file:delete(Path),
 | 
				
			||||||
            _ = file:delete(Path),
 | 
					                  {ok, #store_state{table = Tid, filename = Path}};
 | 
				
			||||||
            {ok, #store_state{table = Tid, filename = Path}};
 | 
					    {error, _} -> {error, uninitialized}
 | 
				
			||||||
        {error, _} ->
 | 
					  end.
 | 
				
			||||||
            {error, uninitialized}
 | 
					 | 
				
			||||||
    end.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec insert(binary(), mqtt_msg(), store_state()) -> ok.
 | 
					-spec insert(binary(), mqtt_msg(), store_state()) -> ok.
 | 
				
			||||||
insert(Topic, Msg, #store_state{table = T}) ->
 | 
					insert(Topic, Msg, #store_state{table = T}) ->
 | 
				
			||||||
    true = ets:insert(T, #retained_message{topic = Topic, mqtt_msg = Msg}),
 | 
					  true = ets:insert(T, #retained_message{topic = Topic, mqtt_msg = Msg}),
 | 
				
			||||||
    ok.
 | 
					  ok.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec lookup(binary(), store_state()) -> retained_message() | not_found.
 | 
					-spec lookup(binary(), store_state()) -> retained_message() | not_found.
 | 
				
			||||||
lookup(Topic, #store_state{table = T}) ->
 | 
					lookup(Topic, #store_state{table = T}) ->
 | 
				
			||||||
    case ets:lookup(T, Topic) of
 | 
					  case ets:lookup(T, Topic) of
 | 
				
			||||||
        [] -> not_found;
 | 
					    []      -> not_found;
 | 
				
			||||||
        [Entry] -> Entry
 | 
					    [Entry] -> Entry
 | 
				
			||||||
    end.
 | 
					  end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec delete(binary(), store_state()) -> ok.
 | 
					-spec delete(binary(), store_state()) -> ok.
 | 
				
			||||||
delete(Topic, #store_state{table = T}) ->
 | 
					delete(Topic, #store_state{table = T}) ->
 | 
				
			||||||
    true = ets:delete(T, Topic),
 | 
					  true = ets:delete(T, Topic),
 | 
				
			||||||
    ok.
 | 
					  ok.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec terminate(store_state()) -> ok.
 | 
					-spec terminate(store_state()) -> ok.
 | 
				
			||||||
terminate(#store_state{table = T, filename = Path}) ->
 | 
					terminate(#store_state{table = T, filename = Path}) ->
 | 
				
			||||||
    ok = ets:tab2file(T, Path, [{extended_info, [object_count]}]).
 | 
					  ok = ets:tab2file(T, Path, [{extended_info, [object_count]}]).
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,19 +12,19 @@
 | 
				
			||||||
-export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]).
 | 
					-export([new/2, recover/2, insert/3, lookup/2, delete/2, terminate/1]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
new(_Dir, _VHost) ->
 | 
					new(_Dir, _VHost) ->
 | 
				
			||||||
    ok.
 | 
					  ok.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
recover(_Dir, _VHost) ->
 | 
					recover(_Dir, _VHost) ->
 | 
				
			||||||
    {ok, ok}.
 | 
					  {ok, ok}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
insert(_Topic, _Msg, _State) ->
 | 
					insert(_Topic, _Msg, _State) ->
 | 
				
			||||||
    ok.
 | 
					  ok.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
lookup(_Topic, _State) ->
 | 
					lookup(_Topic, _State) ->
 | 
				
			||||||
    not_found.
 | 
					  not_found.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
delete(_Topic, _State) ->
 | 
					delete(_Topic, _State) ->
 | 
				
			||||||
    ok.
 | 
					  ok.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
terminate(_State) ->
 | 
					terminate(_State) ->
 | 
				
			||||||
    ok.
 | 
					  ok.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,23 +12,15 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-behaviour(gen_server).
 | 
					-behaviour(gen_server).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-export([
 | 
					-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
 | 
				
			||||||
    init/1,
 | 
					         terminate/2, start_link/2]).
 | 
				
			||||||
    handle_call/3,
 | 
					 | 
				
			||||||
    handle_cast/2,
 | 
					 | 
				
			||||||
    handle_info/2,
 | 
					 | 
				
			||||||
    terminate/2,
 | 
					 | 
				
			||||||
    start_link/2
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-export([retain/3, fetch/2, clear/2, store_module/0]).
 | 
					-export([retain/3, fetch/2, clear/2, store_module/0]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-define(TIMEOUT, 30_000).
 | 
					-define(TIMEOUT, 30_000).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-record(retainer_state, {
 | 
					-record(retainer_state, {store_mod,
 | 
				
			||||||
    store_mod,
 | 
					                         store}).
 | 
				
			||||||
    store
 | 
					 | 
				
			||||||
}).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
%%----------------------------------------------------------------------------
 | 
					%%----------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,19 +46,12 @@ clear(Pid, Topic) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
init([StoreMod, VHost]) ->
 | 
					init([StoreMod, VHost]) ->
 | 
				
			||||||
    process_flag(trap_exit, true),
 | 
					    process_flag(trap_exit, true),
 | 
				
			||||||
    State =
 | 
					    State = case StoreMod:recover(store_dir(), VHost) of
 | 
				
			||||||
        case StoreMod:recover(store_dir(), VHost) of
 | 
					                {ok, Store} -> #retainer_state{store = Store,
 | 
				
			||||||
            {ok, Store} ->
 | 
					                                               store_mod = StoreMod};
 | 
				
			||||||
                #retainer_state{
 | 
					                {error, _}  -> #retainer_state{store = StoreMod:new(store_dir(), VHost),
 | 
				
			||||||
                    store = Store,
 | 
					                                               store_mod = StoreMod}
 | 
				
			||||||
                    store_mod = StoreMod
 | 
					            end,
 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
            {error, _} ->
 | 
					 | 
				
			||||||
                #retainer_state{
 | 
					 | 
				
			||||||
                    store = StoreMod:new(store_dir(), VHost),
 | 
					 | 
				
			||||||
                    store_mod = StoreMod
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
    {ok, State}.
 | 
					    {ok, State}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec store_module() -> undefined | module().
 | 
					-spec store_module() -> undefined | module().
 | 
				
			||||||
| 
						 | 
					@ -78,33 +63,26 @@ store_module() ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
%%----------------------------------------------------------------------------
 | 
					%%----------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_cast(
 | 
					handle_cast({retain, Topic, Msg},
 | 
				
			||||||
    {retain, Topic, Msg},
 | 
					    State = #retainer_state{store = Store, store_mod = Mod}) ->
 | 
				
			||||||
    State = #retainer_state{store = Store, store_mod = Mod}
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    ok = Mod:insert(Topic, Msg, Store),
 | 
					    ok = Mod:insert(Topic, Msg, Store),
 | 
				
			||||||
    {noreply, State};
 | 
					    {noreply, State};
 | 
				
			||||||
handle_cast(
 | 
					handle_cast({clear, Topic},
 | 
				
			||||||
    {clear, Topic},
 | 
					    State = #retainer_state{store = Store, store_mod = Mod}) ->
 | 
				
			||||||
    State = #retainer_state{store = Store, store_mod = Mod}
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    ok = Mod:delete(Topic, Store),
 | 
					    ok = Mod:delete(Topic, Store),
 | 
				
			||||||
    {noreply, State}.
 | 
					    {noreply, State}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_call(
 | 
					handle_call({fetch, Topic}, _From,
 | 
				
			||||||
    {fetch, Topic},
 | 
					    State = #retainer_state{store = Store, store_mod = Mod}) ->
 | 
				
			||||||
    _From,
 | 
					    Reply = case Mod:lookup(Topic, Store) of
 | 
				
			||||||
    State = #retainer_state{store = Store, store_mod = Mod}
 | 
					                #retained_message{mqtt_msg = Msg} -> Msg;
 | 
				
			||||||
) ->
 | 
					                not_found                         -> undefined
 | 
				
			||||||
    Reply =
 | 
					            end,
 | 
				
			||||||
        case Mod:lookup(Topic, Store) of
 | 
					 | 
				
			||||||
            #retained_message{mqtt_msg = Msg} -> Msg;
 | 
					 | 
				
			||||||
            not_found -> undefined
 | 
					 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
    {reply, Reply, State}.
 | 
					    {reply, Reply, State}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_info(stop, State) ->
 | 
					handle_info(stop, State) ->
 | 
				
			||||||
    {stop, normal, State};
 | 
					    {stop, normal, State};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_info(Info, State) ->
 | 
					handle_info(Info, State) ->
 | 
				
			||||||
    {stop, {unknown_info, Info}, State}.
 | 
					    {stop, {unknown_info, Info}, State}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,13 +8,8 @@
 | 
				
			||||||
-module(rabbit_mqtt_retainer_sup).
 | 
					-module(rabbit_mqtt_retainer_sup).
 | 
				
			||||||
-behaviour(supervisor).
 | 
					-behaviour(supervisor).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-export([
 | 
					-export([start_link/1, init/1, start_child/2,start_child/1, child_for_vhost/1,
 | 
				
			||||||
    start_link/1,
 | 
					         delete_child/1]).
 | 
				
			||||||
    init/1,
 | 
					 | 
				
			||||||
    start_child/2, start_child/1,
 | 
					 | 
				
			||||||
    child_for_vhost/1,
 | 
					 | 
				
			||||||
    delete_child/1
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec start_child(binary()) -> supervisor:startchild_ret().
 | 
					-spec start_child(binary()) -> supervisor:startchild_ret().
 | 
				
			||||||
-spec start_child(term(), binary()) -> supervisor:startchild_ret().
 | 
					-spec start_child(term(), binary()) -> supervisor:startchild_ret().
 | 
				
			||||||
| 
						 | 
					@ -24,13 +19,13 @@ start_link(SupName) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec child_for_vhost(rabbit_types:vhost()) -> pid().
 | 
					-spec child_for_vhost(rabbit_types:vhost()) -> pid().
 | 
				
			||||||
child_for_vhost(VHost) when is_binary(VHost) ->
 | 
					child_for_vhost(VHost) when is_binary(VHost) ->
 | 
				
			||||||
    case rabbit_mqtt_retainer_sup:start_child(VHost) of
 | 
					  case rabbit_mqtt_retainer_sup:start_child(VHost) of
 | 
				
			||||||
        {ok, Pid} -> Pid;
 | 
					    {ok, Pid}                       -> Pid;
 | 
				
			||||||
        {error, {already_started, Pid}} -> Pid
 | 
					    {error, {already_started, Pid}} -> Pid
 | 
				
			||||||
    end.
 | 
					  end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
start_child(VHost) when is_binary(VHost) ->
 | 
					start_child(VHost) when is_binary(VHost) ->
 | 
				
			||||||
    start_child(rabbit_mqtt_retainer:store_module(), VHost).
 | 
					  start_child(rabbit_mqtt_retainer:store_module(), VHost).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
start_child(RetainStoreMod, VHost) ->
 | 
					start_child(RetainStoreMod, VHost) ->
 | 
				
			||||||
    supervisor:start_child(
 | 
					    supervisor:start_child(
 | 
				
			||||||
| 
						 | 
					@ -46,20 +41,18 @@ start_child(RetainStoreMod, VHost) ->
 | 
				
			||||||
    ).
 | 
					    ).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
delete_child(VHost) ->
 | 
					delete_child(VHost) ->
 | 
				
			||||||
    Id = vhost_to_atom(VHost),
 | 
					  Id = vhost_to_atom(VHost),
 | 
				
			||||||
    ok = supervisor:terminate_child(?MODULE, Id),
 | 
					  ok = supervisor:terminate_child(?MODULE, Id),
 | 
				
			||||||
    ok = supervisor:delete_child(?MODULE, Id).
 | 
					  ok = supervisor:delete_child(?MODULE, Id).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
init([]) ->
 | 
					init([]) ->
 | 
				
			||||||
    Mod = rabbit_mqtt_retainer:store_module(),
 | 
					  Mod = rabbit_mqtt_retainer:store_module(),
 | 
				
			||||||
    rabbit_log:info(
 | 
					  rabbit_log:info("MQTT retained message store: ~tp",
 | 
				
			||||||
        "MQTT retained message store: ~tp",
 | 
					    [Mod]),
 | 
				
			||||||
        [Mod]
 | 
					  {ok, {
 | 
				
			||||||
    ),
 | 
					      #{strategy => one_for_one, intensity => 5, period => 5},
 | 
				
			||||||
    {ok, {
 | 
					      child_specs(Mod, rabbit_vhost:list_names())
 | 
				
			||||||
        #{strategy => one_for_one, intensity => 5, period => 5},
 | 
					  }}.
 | 
				
			||||||
        child_specs(Mod, rabbit_vhost:list_names())
 | 
					 | 
				
			||||||
    }}.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
child_specs(Mod, VHosts) ->
 | 
					child_specs(Mod, VHosts) ->
 | 
				
			||||||
    %% see start_child/2
 | 
					    %% see start_child/2
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,61 +23,52 @@ init([{Listeners, SslListeners0}]) ->
 | 
				
			||||||
    NumTcpAcceptors = application:get_env(?APP_NAME, num_tcp_acceptors, 10),
 | 
					    NumTcpAcceptors = application:get_env(?APP_NAME, num_tcp_acceptors, 10),
 | 
				
			||||||
    ConcurrentConnsSups = application:get_env(?APP_NAME, num_conns_sups, 1),
 | 
					    ConcurrentConnsSups = application:get_env(?APP_NAME, num_conns_sups, 1),
 | 
				
			||||||
    {ok, SocketOpts} = application:get_env(?APP_NAME, tcp_listen_options),
 | 
					    {ok, SocketOpts} = application:get_env(?APP_NAME, tcp_listen_options),
 | 
				
			||||||
    {SslOpts, NumSslAcceptors, SslListeners} =
 | 
					    {SslOpts, NumSslAcceptors, SslListeners}
 | 
				
			||||||
        case SslListeners0 of
 | 
					        = case SslListeners0 of
 | 
				
			||||||
            [] ->
 | 
					              [] -> {none, 0, []};
 | 
				
			||||||
                {none, 0, []};
 | 
					              _  -> {rabbit_networking:ensure_ssl(),
 | 
				
			||||||
            _ ->
 | 
					                     application:get_env(?APP_NAME, num_ssl_acceptors, 10),
 | 
				
			||||||
                {
 | 
					                     case rabbit_networking:poodle_check('MQTT') of
 | 
				
			||||||
                    rabbit_networking:ensure_ssl(),
 | 
					                         ok     -> SslListeners0;
 | 
				
			||||||
                    application:get_env(?APP_NAME, num_ssl_acceptors, 10),
 | 
					                         danger -> []
 | 
				
			||||||
                    case rabbit_networking:poodle_check('MQTT') of
 | 
					                     end}
 | 
				
			||||||
                        ok -> SslListeners0;
 | 
					          end,
 | 
				
			||||||
                        danger -> []
 | 
					 | 
				
			||||||
                    end
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
    %% Use separate process group scope per RabbitMQ node. This achieves a local-only
 | 
					    %% Use separate process group scope per RabbitMQ node. This achieves a local-only
 | 
				
			||||||
    %% process group which requires less memory with millions of connections.
 | 
					    %% process group which requires less memory with millions of connections.
 | 
				
			||||||
    PgScope = list_to_atom(io_lib:format("~s_~s", [?PG_SCOPE, node()])),
 | 
					    PgScope = list_to_atom(io_lib:format("~s_~s", [?PG_SCOPE, node()])),
 | 
				
			||||||
    persistent_term:put(?PG_SCOPE, PgScope),
 | 
					    persistent_term:put(?PG_SCOPE, PgScope),
 | 
				
			||||||
    {ok,
 | 
					    {ok,
 | 
				
			||||||
        {
 | 
					     {#{strategy => one_for_all,
 | 
				
			||||||
            #{
 | 
					        intensity => 10,
 | 
				
			||||||
                strategy => one_for_all,
 | 
					        period => 10},
 | 
				
			||||||
                intensity => 10,
 | 
					      [
 | 
				
			||||||
                period => 10
 | 
					       #{id => PgScope,
 | 
				
			||||||
            },
 | 
					         start => {pg, start_link, [PgScope]},
 | 
				
			||||||
            [
 | 
					         restart => transient,
 | 
				
			||||||
                #{
 | 
					         shutdown => ?WORKER_WAIT,
 | 
				
			||||||
                    id => PgScope,
 | 
					         type => worker,
 | 
				
			||||||
                    start => {pg, start_link, [PgScope]},
 | 
					         modules => [pg]
 | 
				
			||||||
                    restart => transient,
 | 
					        },
 | 
				
			||||||
                    shutdown => ?WORKER_WAIT,
 | 
					       #{
 | 
				
			||||||
                    type => worker,
 | 
					         id => rabbit_mqtt_retainer_sup,
 | 
				
			||||||
                    modules => [pg]
 | 
					         start => {rabbit_mqtt_retainer_sup, start_link,
 | 
				
			||||||
                },
 | 
					                   [{local, rabbit_mqtt_retainer_sup}]},
 | 
				
			||||||
                #{
 | 
					         restart => transient,
 | 
				
			||||||
                    id => rabbit_mqtt_retainer_sup,
 | 
					         shutdown => ?SUPERVISOR_WAIT,
 | 
				
			||||||
                    start =>
 | 
					         type => supervisor,
 | 
				
			||||||
                        {rabbit_mqtt_retainer_sup, start_link, [{local, rabbit_mqtt_retainer_sup}]},
 | 
					         modules => [rabbit_mqtt_retainer_sup]
 | 
				
			||||||
                    restart => transient,
 | 
					        }
 | 
				
			||||||
                    shutdown => ?SUPERVISOR_WAIT,
 | 
					       | listener_specs(
 | 
				
			||||||
                    type => supervisor,
 | 
					           fun tcp_listener_spec/1,
 | 
				
			||||||
                    modules => [rabbit_mqtt_retainer_sup]
 | 
					           [SocketOpts, NumTcpAcceptors, ConcurrentConnsSups],
 | 
				
			||||||
                }
 | 
					           Listeners
 | 
				
			||||||
                | listener_specs(
 | 
					          ) ++
 | 
				
			||||||
                    fun tcp_listener_spec/1,
 | 
					       listener_specs(
 | 
				
			||||||
                    [SocketOpts, NumTcpAcceptors, ConcurrentConnsSups],
 | 
					         fun ssl_listener_spec/1,
 | 
				
			||||||
                    Listeners
 | 
					         [SocketOpts, SslOpts, NumSslAcceptors, ConcurrentConnsSups],
 | 
				
			||||||
                ) ++
 | 
					         SslListeners
 | 
				
			||||||
                    listener_specs(
 | 
					        )
 | 
				
			||||||
                        fun ssl_listener_spec/1,
 | 
					      ]}}.
 | 
				
			||||||
                        [SocketOpts, SslOpts, NumSslAcceptors, ConcurrentConnsSups],
 | 
					 | 
				
			||||||
                        SslListeners
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
        }}.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec stop_listeners() -> ok.
 | 
					-spec stop_listeners() -> ok.
 | 
				
			||||||
stop_listeners() ->
 | 
					stop_listeners() ->
 | 
				
			||||||
| 
						 | 
					@ -98,33 +89,33 @@ listener_specs(Fun, Args, Listeners) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
tcp_listener_spec([Address, SocketOpts, NumAcceptors, ConcurrentConnsSups]) ->
 | 
					tcp_listener_spec([Address, SocketOpts, NumAcceptors, ConcurrentConnsSups]) ->
 | 
				
			||||||
    rabbit_networking:tcp_listener_spec(
 | 
					    rabbit_networking:tcp_listener_spec(
 | 
				
			||||||
        rabbit_mqtt_listener_sup,
 | 
					      rabbit_mqtt_listener_sup,
 | 
				
			||||||
        Address,
 | 
					      Address,
 | 
				
			||||||
        SocketOpts,
 | 
					      SocketOpts,
 | 
				
			||||||
        transport(?TCP_PROTOCOL),
 | 
					      transport(?TCP_PROTOCOL),
 | 
				
			||||||
        rabbit_mqtt_reader,
 | 
					      rabbit_mqtt_reader,
 | 
				
			||||||
        [],
 | 
					      [],
 | 
				
			||||||
        mqtt,
 | 
					      mqtt,
 | 
				
			||||||
        NumAcceptors,
 | 
					      NumAcceptors,
 | 
				
			||||||
        ConcurrentConnsSups,
 | 
					      ConcurrentConnsSups,
 | 
				
			||||||
        worker,
 | 
					      worker,
 | 
				
			||||||
        "MQTT TCP listener"
 | 
					      "MQTT TCP listener"
 | 
				
			||||||
    ).
 | 
					     ).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ssl_listener_spec([Address, SocketOpts, SslOpts, NumAcceptors, ConcurrentConnsSups]) ->
 | 
					ssl_listener_spec([Address, SocketOpts, SslOpts, NumAcceptors, ConcurrentConnsSups]) ->
 | 
				
			||||||
    rabbit_networking:tcp_listener_spec(
 | 
					    rabbit_networking:tcp_listener_spec(
 | 
				
			||||||
        rabbit_mqtt_listener_sup,
 | 
					      rabbit_mqtt_listener_sup,
 | 
				
			||||||
        Address,
 | 
					      Address,
 | 
				
			||||||
        SocketOpts ++ SslOpts,
 | 
					      SocketOpts ++ SslOpts,
 | 
				
			||||||
        transport(?TLS_PROTOCOL),
 | 
					      transport(?TLS_PROTOCOL),
 | 
				
			||||||
        rabbit_mqtt_reader,
 | 
					      rabbit_mqtt_reader,
 | 
				
			||||||
        [],
 | 
					      [],
 | 
				
			||||||
        'mqtt/ssl',
 | 
					      'mqtt/ssl',
 | 
				
			||||||
        NumAcceptors,
 | 
					      NumAcceptors,
 | 
				
			||||||
        ConcurrentConnsSups,
 | 
					      ConcurrentConnsSups,
 | 
				
			||||||
        worker,
 | 
					      worker,
 | 
				
			||||||
        "MQTT TLS listener"
 | 
					      "MQTT TLS listener"
 | 
				
			||||||
    ).
 | 
					     ).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
transport(?TCP_PROTOCOL) ->
 | 
					transport(?TCP_PROTOCOL) ->
 | 
				
			||||||
    ranch_tcp;
 | 
					    ranch_tcp;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,21 +11,20 @@
 | 
				
			||||||
-include("rabbit_mqtt.hrl").
 | 
					-include("rabbit_mqtt.hrl").
 | 
				
			||||||
-include("rabbit_mqtt_packet.hrl").
 | 
					-include("rabbit_mqtt_packet.hrl").
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-export([
 | 
					-export([queue_name_bin/2,
 | 
				
			||||||
    queue_name_bin/2,
 | 
					         qos_from_queue_name/2,
 | 
				
			||||||
    qos_from_queue_name/2,
 | 
					         env/1,
 | 
				
			||||||
    env/1,
 | 
					         table_lookup/2,
 | 
				
			||||||
    table_lookup/2,
 | 
					         path_for/2,
 | 
				
			||||||
    path_for/2,
 | 
					         path_for/3,
 | 
				
			||||||
    path_for/3,
 | 
					         vhost_name_to_table_name/1,
 | 
				
			||||||
    vhost_name_to_table_name/1,
 | 
					         register_clientid/2,
 | 
				
			||||||
    register_clientid/2,
 | 
					         remove_duplicate_clientid_connections/2,
 | 
				
			||||||
    remove_duplicate_clientid_connections/2,
 | 
					         init_sparkplug/0,
 | 
				
			||||||
    init_sparkplug/0,
 | 
					         mqtt_to_amqp/1,
 | 
				
			||||||
    mqtt_to_amqp/1,
 | 
					         amqp_to_mqtt/1,
 | 
				
			||||||
    amqp_to_mqtt/1,
 | 
					         truncate_binary/2
 | 
				
			||||||
    truncate_binary/2
 | 
					        ]).
 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-define(MAX_TOPIC_TRANSLATION_CACHE_SIZE, 12).
 | 
					-define(MAX_TOPIC_TRANSLATION_CACHE_SIZE, 12).
 | 
				
			||||||
-define(SPARKPLUG_MP_MQTT_TO_AMQP, sparkplug_mp_mqtt_to_amqp).
 | 
					-define(SPARKPLUG_MP_MQTT_TO_AMQP, sparkplug_mp_mqtt_to_amqp).
 | 
				
			||||||
| 
						 | 
					@ -73,8 +72,7 @@ init_sparkplug() ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec mqtt_to_amqp(binary()) -> binary().
 | 
					-spec mqtt_to_amqp(binary()) -> binary().
 | 
				
			||||||
mqtt_to_amqp(Topic) ->
 | 
					mqtt_to_amqp(Topic) ->
 | 
				
			||||||
    T =
 | 
					    T = case persistent_term:get(?SPARKPLUG_MP_MQTT_TO_AMQP, no_sparkplug) of
 | 
				
			||||||
        case persistent_term:get(?SPARKPLUG_MP_MQTT_TO_AMQP, no_sparkplug) of
 | 
					 | 
				
			||||||
            no_sparkplug ->
 | 
					            no_sparkplug ->
 | 
				
			||||||
                Topic;
 | 
					                Topic;
 | 
				
			||||||
            M2A_SpRe ->
 | 
					            M2A_SpRe ->
 | 
				
			||||||
| 
						 | 
					@ -104,13 +102,12 @@ amqp_to_mqtt(Topic) ->
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cached(CacheName, Fun, Arg) ->
 | 
					cached(CacheName, Fun, Arg) ->
 | 
				
			||||||
    Cache =
 | 
					    Cache = case get(CacheName) of
 | 
				
			||||||
        case get(CacheName) of
 | 
					                undefined ->
 | 
				
			||||||
            undefined ->
 | 
					                    [];
 | 
				
			||||||
                [];
 | 
					                Other ->
 | 
				
			||||||
            Other ->
 | 
					                    Other
 | 
				
			||||||
                Other
 | 
					            end,
 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
    case lists:keyfind(Arg, 1, Cache) of
 | 
					    case lists:keyfind(Arg, 1, Cache) of
 | 
				
			||||||
        {_, V} ->
 | 
					        {_, V} ->
 | 
				
			||||||
            V;
 | 
					            V;
 | 
				
			||||||
| 
						 | 
					@ -145,11 +142,11 @@ env(Key) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
coerce_env_value(default_pass, Val) -> rabbit_data_coercion:to_binary(Val);
 | 
					coerce_env_value(default_pass, Val) -> rabbit_data_coercion:to_binary(Val);
 | 
				
			||||||
coerce_env_value(default_user, Val) -> rabbit_data_coercion:to_binary(Val);
 | 
					coerce_env_value(default_user, Val) -> rabbit_data_coercion:to_binary(Val);
 | 
				
			||||||
coerce_env_value(exchange, Val) -> rabbit_data_coercion:to_binary(Val);
 | 
					coerce_env_value(exchange, Val)     -> rabbit_data_coercion:to_binary(Val);
 | 
				
			||||||
coerce_env_value(vhost, Val) -> rabbit_data_coercion:to_binary(Val);
 | 
					coerce_env_value(vhost, Val)        -> rabbit_data_coercion:to_binary(Val);
 | 
				
			||||||
coerce_env_value(_, Val) -> Val.
 | 
					coerce_env_value(_, Val)            -> Val.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec table_lookup(rabbit_framing:amqp_table() | undefined, binary()) ->
 | 
					-spec table_lookup(rabbit_framing:amqp_table() | undefined,  binary()) ->
 | 
				
			||||||
    tuple() | undefined.
 | 
					    tuple() | undefined.
 | 
				
			||||||
table_lookup(undefined, _Key) ->
 | 
					table_lookup(undefined, _Key) ->
 | 
				
			||||||
    undefined;
 | 
					    undefined;
 | 
				
			||||||
| 
						 | 
					@ -164,11 +161,11 @@ vhost_name_to_dir_name(VHost, Suffix) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec path_for(file:name_all(), rabbit_types:vhost()) -> file:filename_all().
 | 
					-spec path_for(file:name_all(), rabbit_types:vhost()) -> file:filename_all().
 | 
				
			||||||
path_for(Dir, VHost) ->
 | 
					path_for(Dir, VHost) ->
 | 
				
			||||||
    filename:join(Dir, vhost_name_to_dir_name(VHost)).
 | 
					  filename:join(Dir, vhost_name_to_dir_name(VHost)).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec path_for(file:name_all(), rabbit_types:vhost(), string()) -> file:filename_all().
 | 
					-spec path_for(file:name_all(), rabbit_types:vhost(), string()) -> file:filename_all().
 | 
				
			||||||
path_for(Dir, VHost, Suffix) ->
 | 
					path_for(Dir, VHost, Suffix) ->
 | 
				
			||||||
    filename:join(Dir, vhost_name_to_dir_name(VHost, Suffix)).
 | 
					  filename:join(Dir, vhost_name_to_dir_name(VHost, Suffix)).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec vhost_name_to_table_name(rabbit_types:vhost()) ->
 | 
					-spec vhost_name_to_table_name(rabbit_types:vhost()) ->
 | 
				
			||||||
    atom().
 | 
					    atom().
 | 
				
			||||||
| 
						 | 
					@ -177,9 +174,8 @@ vhost_name_to_table_name(VHost) ->
 | 
				
			||||||
    list_to_atom("rabbit_mqtt_retained_" ++ rabbit_misc:format("~36.16.0b", [Num])).
 | 
					    list_to_atom("rabbit_mqtt_retained_" ++ rabbit_misc:format("~36.16.0b", [Num])).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec register_clientid(rabbit_types:vhost(), binary()) -> ok.
 | 
					-spec register_clientid(rabbit_types:vhost(), binary()) -> ok.
 | 
				
			||||||
register_clientid(Vhost, ClientId) when
 | 
					register_clientid(Vhost, ClientId)
 | 
				
			||||||
    is_binary(Vhost), is_binary(ClientId)
 | 
					  when is_binary(Vhost), is_binary(ClientId) ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    PgGroup = {Vhost, ClientId},
 | 
					    PgGroup = {Vhost, ClientId},
 | 
				
			||||||
    ok = pg:join(persistent_term:get(?PG_SCOPE), PgGroup, self()),
 | 
					    ok = pg:join(persistent_term:get(?PG_SCOPE), PgGroup, self()),
 | 
				
			||||||
    case rabbit_mqtt_ff:track_client_id_in_ra() of
 | 
					    case rabbit_mqtt_ff:track_client_id_in_ra() of
 | 
				
			||||||
| 
						 | 
					@ -187,12 +183,10 @@ register_clientid(Vhost, ClientId) when
 | 
				
			||||||
            %% Ra node takes care of removing duplicate client ID connections.
 | 
					            %% Ra node takes care of removing duplicate client ID connections.
 | 
				
			||||||
            ok;
 | 
					            ok;
 | 
				
			||||||
        false ->
 | 
					        false ->
 | 
				
			||||||
            ok = erpc:multicast(
 | 
					            ok = erpc:multicast([node() | nodes()],
 | 
				
			||||||
                [node() | nodes()],
 | 
					                                ?MODULE,
 | 
				
			||||||
                ?MODULE,
 | 
					                                remove_duplicate_clientid_connections,
 | 
				
			||||||
                remove_duplicate_clientid_connections,
 | 
					                                [PgGroup, self()])
 | 
				
			||||||
                [PgGroup, self()]
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec remove_duplicate_clientid_connections({rabbit_types:vhost(), binary()}, pid()) -> ok.
 | 
					-spec remove_duplicate_clientid_connections({rabbit_types:vhost(), binary()}, pid()) -> ok.
 | 
				
			||||||
| 
						 | 
					@ -200,24 +194,18 @@ remove_duplicate_clientid_connections(PgGroup, PidToKeep) ->
 | 
				
			||||||
    try persistent_term:get(?PG_SCOPE) of
 | 
					    try persistent_term:get(?PG_SCOPE) of
 | 
				
			||||||
        PgScope ->
 | 
					        PgScope ->
 | 
				
			||||||
            Pids = pg:get_local_members(PgScope, PgGroup),
 | 
					            Pids = pg:get_local_members(PgScope, PgGroup),
 | 
				
			||||||
            lists:foreach(
 | 
					            lists:foreach(fun(Pid) ->
 | 
				
			||||||
                fun(Pid) ->
 | 
					                                  gen_server:cast(Pid, duplicate_id)
 | 
				
			||||||
                    gen_server:cast(Pid, duplicate_id)
 | 
					                          end, Pids -- [PidToKeep])
 | 
				
			||||||
                end,
 | 
					    catch _:badarg ->
 | 
				
			||||||
                Pids -- [PidToKeep]
 | 
					              %% MQTT supervision tree on this node not fully started
 | 
				
			||||||
            )
 | 
					              ok
 | 
				
			||||||
    catch
 | 
					 | 
				
			||||||
        _:badarg ->
 | 
					 | 
				
			||||||
            %% MQTT supervision tree on this node not fully started
 | 
					 | 
				
			||||||
            ok
 | 
					 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec truncate_binary(binary(), non_neg_integer()) -> binary().
 | 
					-spec truncate_binary(binary(), non_neg_integer()) -> binary().
 | 
				
			||||||
truncate_binary(Bin, Size) when
 | 
					truncate_binary(Bin, Size)
 | 
				
			||||||
    is_binary(Bin) andalso byte_size(Bin) =< Size
 | 
					  when is_binary(Bin) andalso byte_size(Bin) =< Size ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    Bin;
 | 
					    Bin;
 | 
				
			||||||
truncate_binary(Bin, Size) when
 | 
					truncate_binary(Bin, Size)
 | 
				
			||||||
    is_binary(Bin)
 | 
					  when is_binary(Bin) ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    binary:part(Bin, 0, Size).
 | 
					    binary:part(Bin, 0, Size).
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
					@ -9,42 +9,36 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-include_lib("common_test/include/ct.hrl").
 | 
					-include_lib("common_test/include/ct.hrl").
 | 
				
			||||||
-include_lib("eunit/include/eunit.hrl").
 | 
					-include_lib("eunit/include/eunit.hrl").
 | 
				
			||||||
-import(util, [
 | 
					-import(util, [expect_publishes/3,
 | 
				
			||||||
    expect_publishes/3,
 | 
					               connect/3,
 | 
				
			||||||
    connect/3,
 | 
					               connect/4,
 | 
				
			||||||
    connect/4,
 | 
					               await_exit/1]).
 | 
				
			||||||
    await_exit/1
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-import(
 | 
					-import(rabbit_ct_broker_helpers,
 | 
				
			||||||
    rabbit_ct_broker_helpers,
 | 
					        [setup_steps/0,
 | 
				
			||||||
    [
 | 
					         teardown_steps/0,
 | 
				
			||||||
        setup_steps/0,
 | 
					         get_node_config/3,
 | 
				
			||||||
        teardown_steps/0,
 | 
					         rabbitmqctl/3,
 | 
				
			||||||
        get_node_config/3,
 | 
					         rpc/4,
 | 
				
			||||||
        rabbitmqctl/3,
 | 
					         stop_node/2
 | 
				
			||||||
        rpc/4,
 | 
					        ]).
 | 
				
			||||||
        stop_node/2
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-define(OPTS, [
 | 
					-define(OPTS, [{connect_timeout, 1},
 | 
				
			||||||
    {connect_timeout, 1},
 | 
					               {ack_timeout, 1}]).
 | 
				
			||||||
    {ack_timeout, 1}
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
all() ->
 | 
					all() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {group, cluster_size_5}
 | 
					     {group, cluster_size_5}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
groups() ->
 | 
					groups() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {cluster_size_5, [], [
 | 
					     {cluster_size_5, [],
 | 
				
			||||||
            connection_id_tracking,
 | 
					      [
 | 
				
			||||||
            connection_id_tracking_on_nodedown,
 | 
					       connection_id_tracking,
 | 
				
			||||||
            connection_id_tracking_with_decommissioned_node
 | 
					       connection_id_tracking_on_nodedown,
 | 
				
			||||||
        ]}
 | 
					       connection_id_tracking_with_decommissioned_node
 | 
				
			||||||
 | 
					      ]}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
suite() ->
 | 
					suite() ->
 | 
				
			||||||
| 
						 | 
					@ -56,12 +50,11 @@ suite() ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
merge_app_env(Config) ->
 | 
					merge_app_env(Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:merge_app_env(
 | 
					    rabbit_ct_helpers:merge_app_env(
 | 
				
			||||||
        Config,
 | 
					      Config,
 | 
				
			||||||
        {rabbit, [
 | 
					      {rabbit, [
 | 
				
			||||||
            {collect_statistics, basic},
 | 
					                {collect_statistics, basic},
 | 
				
			||||||
            {collect_statistics_interval, 100}
 | 
					                {collect_statistics_interval, 100}
 | 
				
			||||||
        ]}
 | 
					               ]}).
 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
init_per_suite(Config) ->
 | 
					init_per_suite(Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:log_environment(),
 | 
					    rabbit_ct_helpers:log_environment(),
 | 
				
			||||||
| 
						 | 
					@ -72,8 +65,7 @@ end_per_suite(Config) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
init_per_group(cluster_size_5, Config) ->
 | 
					init_per_group(cluster_size_5, Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:set_config(
 | 
					    rabbit_ct_helpers:set_config(
 | 
				
			||||||
        Config, [{rmq_nodes_count, 5}]
 | 
					      Config, [{rmq_nodes_count, 5}]).
 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
end_per_group(_, Config) ->
 | 
					end_per_group(_, Config) ->
 | 
				
			||||||
    Config.
 | 
					    Config.
 | 
				
			||||||
| 
						 | 
					@ -83,25 +75,19 @@ init_per_testcase(Testcase, Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:log_environment(),
 | 
					    rabbit_ct_helpers:log_environment(),
 | 
				
			||||||
    Config1 = rabbit_ct_helpers:set_config(Config, [
 | 
					    Config1 = rabbit_ct_helpers:set_config(Config, [
 | 
				
			||||||
        {rmq_nodename_suffix, Testcase},
 | 
					        {rmq_nodename_suffix, Testcase},
 | 
				
			||||||
        {rmq_extra_tcp_ports, [
 | 
					        {rmq_extra_tcp_ports, [tcp_port_mqtt_extra,
 | 
				
			||||||
            tcp_port_mqtt_extra,
 | 
					                               tcp_port_mqtt_tls_extra]},
 | 
				
			||||||
            tcp_port_mqtt_tls_extra
 | 
					 | 
				
			||||||
        ]},
 | 
					 | 
				
			||||||
        {rmq_nodes_clustered, true}
 | 
					        {rmq_nodes_clustered, true}
 | 
				
			||||||
    ]),
 | 
					      ]),
 | 
				
			||||||
    rabbit_ct_helpers:run_setup_steps(
 | 
					    rabbit_ct_helpers:run_setup_steps(Config1,
 | 
				
			||||||
        Config1,
 | 
					      [ fun merge_app_env/1 ] ++
 | 
				
			||||||
        [fun merge_app_env/1] ++
 | 
					      setup_steps() ++
 | 
				
			||||||
            setup_steps() ++
 | 
					      rabbit_ct_client_helpers:setup_steps()).
 | 
				
			||||||
            rabbit_ct_client_helpers:setup_steps()
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
end_per_testcase(Testcase, Config) ->
 | 
					end_per_testcase(Testcase, Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:run_teardown_steps(
 | 
					    rabbit_ct_helpers:run_teardown_steps(Config,
 | 
				
			||||||
        Config,
 | 
					      rabbit_ct_client_helpers:teardown_steps() ++
 | 
				
			||||||
        rabbit_ct_client_helpers:teardown_steps() ++
 | 
					      teardown_steps()),
 | 
				
			||||||
            teardown_steps()
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    rabbit_ct_helpers:testcase_finished(Config, Testcase).
 | 
					    rabbit_ct_helpers:testcase_finished(Config, Testcase).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
%% -------------------------------------------------------------------
 | 
					%% -------------------------------------------------------------------
 | 
				
			||||||
| 
						 | 
					@ -179,15 +165,14 @@ connection_id_tracking_with_decommissioned_node(Config) ->
 | 
				
			||||||
%% Helpers
 | 
					%% Helpers
 | 
				
			||||||
%%
 | 
					%%
 | 
				
			||||||
 | 
					
 | 
				
			||||||
assert_connection_count(_Config, 0, _, NumElements) ->
 | 
					assert_connection_count(_Config, 0,  _, NumElements) ->
 | 
				
			||||||
    ct:fail("failed to match connection count ~b", [NumElements]);
 | 
					    ct:fail("failed to match connection count ~b", [NumElements]);
 | 
				
			||||||
assert_connection_count(Config, Retries, NodeId, NumElements) ->
 | 
					assert_connection_count(Config, Retries, NodeId, NumElements) ->
 | 
				
			||||||
    case util:all_connection_pids(Config) of
 | 
					    case util:all_connection_pids(Config) of
 | 
				
			||||||
        Pids when
 | 
					        Pids
 | 
				
			||||||
            length(Pids) =:= NumElements
 | 
					          when length(Pids) =:= NumElements ->
 | 
				
			||||||
        ->
 | 
					 | 
				
			||||||
            ok;
 | 
					            ok;
 | 
				
			||||||
        _ ->
 | 
					        _ ->
 | 
				
			||||||
            timer:sleep(500),
 | 
					            timer:sleep(500),
 | 
				
			||||||
            assert_connection_count(Config, Retries - 1, NodeId, NumElements)
 | 
					            assert_connection_count(Config, Retries-1, NodeId, NumElements)
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@
 | 
				
			||||||
%%
 | 
					%%
 | 
				
			||||||
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates.  All rights reserved.
 | 
					%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates.  All rights reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-module(command_SUITE).
 | 
					-module(command_SUITE).
 | 
				
			||||||
-compile([export_all, nowarn_export_all]).
 | 
					-compile([export_all, nowarn_export_all]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,45 +17,39 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
all() ->
 | 
					all() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {group, non_parallel_tests}
 | 
					      {group, non_parallel_tests}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
groups() ->
 | 
					groups() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {non_parallel_tests, [], [
 | 
					      {non_parallel_tests, [], [
 | 
				
			||||||
            merge_defaults,
 | 
					                                merge_defaults,
 | 
				
			||||||
            run
 | 
					                                run
 | 
				
			||||||
        ]}
 | 
					                               ]}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
suite() ->
 | 
					suite() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {timetrap, {minutes, 3}}
 | 
					      {timetrap, {minutes, 3}}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
init_per_suite(Config) ->
 | 
					init_per_suite(Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:log_environment(),
 | 
					    rabbit_ct_helpers:log_environment(),
 | 
				
			||||||
    Config1 = rabbit_ct_helpers:set_config(Config, [
 | 
					    Config1 = rabbit_ct_helpers:set_config(Config, [
 | 
				
			||||||
        {rmq_nodename_suffix, ?MODULE},
 | 
					        {rmq_nodename_suffix, ?MODULE},
 | 
				
			||||||
        {rmq_extra_tcp_ports, [
 | 
					        {rmq_extra_tcp_ports, [tcp_port_mqtt_extra,
 | 
				
			||||||
            tcp_port_mqtt_extra,
 | 
					                               tcp_port_mqtt_tls_extra]},
 | 
				
			||||||
            tcp_port_mqtt_tls_extra
 | 
					 | 
				
			||||||
        ]},
 | 
					 | 
				
			||||||
        {rmq_nodes_clustered, true},
 | 
					        {rmq_nodes_clustered, true},
 | 
				
			||||||
        {rmq_nodes_count, 3}
 | 
					        {rmq_nodes_count, 3}
 | 
				
			||||||
    ]),
 | 
					      ]),
 | 
				
			||||||
    rabbit_ct_helpers:run_setup_steps(
 | 
					    rabbit_ct_helpers:run_setup_steps(Config1,
 | 
				
			||||||
        Config1,
 | 
					      rabbit_ct_broker_helpers:setup_steps() ++
 | 
				
			||||||
        rabbit_ct_broker_helpers:setup_steps() ++
 | 
					      rabbit_ct_client_helpers:setup_steps()).
 | 
				
			||||||
            rabbit_ct_client_helpers:setup_steps()
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
end_per_suite(Config) ->
 | 
					end_per_suite(Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:run_teardown_steps(
 | 
					    rabbit_ct_helpers:run_teardown_steps(Config,
 | 
				
			||||||
        Config,
 | 
					      rabbit_ct_client_helpers:teardown_steps() ++
 | 
				
			||||||
        rabbit_ct_client_helpers:teardown_steps() ++
 | 
					      rabbit_ct_broker_helpers:teardown_steps()).
 | 
				
			||||||
            rabbit_ct_broker_helpers:teardown_steps()
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
init_per_group(_, Config) ->
 | 
					init_per_group(_, Config) ->
 | 
				
			||||||
    Config.
 | 
					    Config.
 | 
				
			||||||
| 
						 | 
					@ -78,6 +73,7 @@ merge_defaults(_Config) ->
 | 
				
			||||||
    {[<<"other_key">>], #{verbose := false}} =
 | 
					    {[<<"other_key">>], #{verbose := false}} =
 | 
				
			||||||
        ?COMMAND:merge_defaults([<<"other_key">>], #{verbose => false}).
 | 
					        ?COMMAND:merge_defaults([<<"other_key">>], #{verbose => false}).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
run(Config) ->
 | 
					run(Config) ->
 | 
				
			||||||
    Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
 | 
					    Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
 | 
				
			||||||
    Opts = #{node => Node, timeout => 10_000, verbose => false},
 | 
					    Opts = #{node => Node, timeout => 10_000, verbose => false},
 | 
				
			||||||
| 
						 | 
					@ -95,27 +91,18 @@ run(Config) ->
 | 
				
			||||||
    C2 = connect(<<"simpleClient1">>, Config, [{ack_timeout, 1}]),
 | 
					    C2 = connect(<<"simpleClient1">>, Config, [{ack_timeout, 1}]),
 | 
				
			||||||
    timer:sleep(200),
 | 
					    timer:sleep(200),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [
 | 
					    [[{client_id, <<"simpleClient">>}, {user, <<"guest">>}],
 | 
				
			||||||
        [{client_id, <<"simpleClient">>}, {user, <<"guest">>}],
 | 
					     [{client_id, <<"simpleClient1">>}, {user, <<"guest">>}]] =
 | 
				
			||||||
        [{client_id, <<"simpleClient1">>}, {user, <<"guest">>}]
 | 
					 | 
				
			||||||
    ] =
 | 
					 | 
				
			||||||
        lists:sort(
 | 
					        lists:sort(
 | 
				
			||||||
            'Elixir.Enum':to_list(
 | 
					            'Elixir.Enum':to_list(?COMMAND:run([<<"client_id">>, <<"user">>],
 | 
				
			||||||
                ?COMMAND:run(
 | 
					                                               Opts))),
 | 
				
			||||||
                    [<<"client_id">>, <<"user">>],
 | 
					 | 
				
			||||||
                    Opts
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp),
 | 
					    Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp),
 | 
				
			||||||
    start_amqp_connection(network, Node, Port),
 | 
					    start_amqp_connection(network, Node, Port),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% There are still just two MQTT connections
 | 
					    %% There are still just two MQTT connections
 | 
				
			||||||
    [
 | 
					    [[{client_id, <<"simpleClient">>}],
 | 
				
			||||||
        [{client_id, <<"simpleClient">>}],
 | 
					     [{client_id, <<"simpleClient1">>}]] =
 | 
				
			||||||
        [{client_id, <<"simpleClient1">>}]
 | 
					 | 
				
			||||||
    ] =
 | 
					 | 
				
			||||||
        lists:sort('Elixir.Enum':to_list(?COMMAND:run([<<"client_id">>], Opts))),
 | 
					        lists:sort('Elixir.Enum':to_list(?COMMAND:run([<<"client_id">>], Opts))),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    start_amqp_connection(direct, Node, Port),
 | 
					    start_amqp_connection(direct, Node, Port),
 | 
				
			||||||
| 
						 | 
					@ -123,19 +110,14 @@ run(Config) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% Still two MQTT connections
 | 
					    %% Still two MQTT connections
 | 
				
			||||||
    ?assertEqual(
 | 
					    ?assertEqual(
 | 
				
			||||||
        [
 | 
					       [[{client_id, <<"simpleClient">>}],
 | 
				
			||||||
            [{client_id, <<"simpleClient">>}],
 | 
					        [{client_id, <<"simpleClient1">>}]],
 | 
				
			||||||
            [{client_id, <<"simpleClient1">>}]
 | 
					       lists:sort('Elixir.Enum':to_list(?COMMAND:run([<<"client_id">>], Opts)))),
 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        lists:sort('Elixir.Enum':to_list(?COMMAND:run([<<"client_id">>], Opts)))
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% Verbose returns all keys
 | 
					    %% Verbose returns all keys
 | 
				
			||||||
    AllKeys = lists:map(fun(I) -> atom_to_binary(I) end, ?INFO_ITEMS),
 | 
					    AllKeys = lists:map(fun(I) -> atom_to_binary(I) end, ?INFO_ITEMS),
 | 
				
			||||||
    [AllInfos1Con1, _AllInfos1Con2] = 'Elixir.Enum':to_list(?COMMAND:run(AllKeys, Opts)),
 | 
					    [AllInfos1Con1, _AllInfos1Con2] = 'Elixir.Enum':to_list(?COMMAND:run(AllKeys, Opts)),
 | 
				
			||||||
    [AllInfos2Con1, _AllInfos2Con2] = 'Elixir.Enum':to_list(
 | 
					    [AllInfos2Con1, _AllInfos2Con2] = 'Elixir.Enum':to_list(?COMMAND:run([], Opts#{verbose => true})),
 | 
				
			||||||
        ?COMMAND:run([], Opts#{verbose => true})
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% Keys are INFO_ITEMS
 | 
					    %% Keys are INFO_ITEMS
 | 
				
			||||||
    InfoItemsSorted = lists:sort(?INFO_ITEMS),
 | 
					    InfoItemsSorted = lists:sort(?INFO_ITEMS),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,10 +5,8 @@
 | 
				
			||||||
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates.  All rights reserved.
 | 
					%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates.  All rights reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-module(config_SUITE).
 | 
					-module(config_SUITE).
 | 
				
			||||||
-compile([
 | 
					-compile([export_all,
 | 
				
			||||||
    export_all,
 | 
					          nowarn_export_all]).
 | 
				
			||||||
    nowarn_export_all
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-include_lib("eunit/include/eunit.hrl").
 | 
					-include_lib("eunit/include/eunit.hrl").
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,16 +14,17 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
all() ->
 | 
					all() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {group, mnesia}
 | 
					     {group, mnesia}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
groups() ->
 | 
					groups() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {mnesia, [shuffle], [
 | 
					     {mnesia, [shuffle],
 | 
				
			||||||
            rabbitmq_default,
 | 
					      [
 | 
				
			||||||
            environment_set,
 | 
					       rabbitmq_default,
 | 
				
			||||||
            flag_set
 | 
					       environment_set,
 | 
				
			||||||
        ]}
 | 
					       flag_set
 | 
				
			||||||
 | 
					      ]}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
suite() ->
 | 
					suite() ->
 | 
				
			||||||
| 
						 | 
					@ -46,12 +45,8 @@ init_per_testcase(rabbitmq_default = Test, Config) ->
 | 
				
			||||||
    init_per_testcase0(Test, Config);
 | 
					    init_per_testcase0(Test, Config);
 | 
				
			||||||
init_per_testcase(environment_set = Test, Config0) ->
 | 
					init_per_testcase(environment_set = Test, Config0) ->
 | 
				
			||||||
    Config = rabbit_ct_helpers:merge_app_env(
 | 
					    Config = rabbit_ct_helpers:merge_app_env(
 | 
				
			||||||
        Config0,
 | 
					               Config0, {mnesia, [{dump_log_write_threshold, 25000},
 | 
				
			||||||
        {mnesia, [
 | 
					                                  {dump_log_time_threshold, 60000}]}),
 | 
				
			||||||
            {dump_log_write_threshold, 25000},
 | 
					 | 
				
			||||||
            {dump_log_time_threshold, 60000}
 | 
					 | 
				
			||||||
        ]}
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    init_per_testcase0(Test, Config);
 | 
					    init_per_testcase0(Test, Config);
 | 
				
			||||||
init_per_testcase(flag_set = Test, Config0) ->
 | 
					init_per_testcase(flag_set = Test, Config0) ->
 | 
				
			||||||
    Config = [{additional_erl_args, "-mnesia dump_log_write_threshold 15000"} | Config0],
 | 
					    Config = [{additional_erl_args, "-mnesia dump_log_write_threshold 15000"} | Config0],
 | 
				
			||||||
| 
						 | 
					@ -60,19 +55,17 @@ init_per_testcase(flag_set = Test, Config0) ->
 | 
				
			||||||
init_per_testcase0(Testcase, Config0) ->
 | 
					init_per_testcase0(Testcase, Config0) ->
 | 
				
			||||||
    Config1 = rabbit_ct_helpers:set_config(Config0, {rmq_nodename_suffix, Testcase}),
 | 
					    Config1 = rabbit_ct_helpers:set_config(Config0, {rmq_nodename_suffix, Testcase}),
 | 
				
			||||||
    Config = rabbit_ct_helpers:run_steps(
 | 
					    Config = rabbit_ct_helpers:run_steps(
 | 
				
			||||||
        Config1,
 | 
					               Config1,
 | 
				
			||||||
        rabbit_ct_broker_helpers:setup_steps() ++
 | 
					               rabbit_ct_broker_helpers:setup_steps() ++
 | 
				
			||||||
            rabbit_ct_client_helpers:setup_steps()
 | 
					               rabbit_ct_client_helpers:setup_steps()),
 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    rabbit_ct_helpers:testcase_started(Config, Testcase).
 | 
					    rabbit_ct_helpers:testcase_started(Config, Testcase).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
end_per_testcase(Testcase, Config0) ->
 | 
					end_per_testcase(Testcase, Config0) ->
 | 
				
			||||||
    Config = rabbit_ct_helpers:testcase_finished(Config0, Testcase),
 | 
					    Config = rabbit_ct_helpers:testcase_finished(Config0, Testcase),
 | 
				
			||||||
    rabbit_ct_helpers:run_teardown_steps(
 | 
					    rabbit_ct_helpers:run_teardown_steps(
 | 
				
			||||||
        Config,
 | 
					      Config,
 | 
				
			||||||
        rabbit_ct_client_helpers:teardown_steps() ++
 | 
					      rabbit_ct_client_helpers:teardown_steps() ++
 | 
				
			||||||
            rabbit_ct_broker_helpers:teardown_steps()
 | 
					      rabbit_ct_broker_helpers:teardown_steps()).
 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
%% -------------------------------------------------------------------
 | 
					%% -------------------------------------------------------------------
 | 
				
			||||||
%% Testsuite cases
 | 
					%% Testsuite cases
 | 
				
			||||||
| 
						 | 
					@ -81,33 +74,21 @@ end_per_testcase(Testcase, Config0) ->
 | 
				
			||||||
%% The MQTT plugin expects Mnesia dump_log_write_threshold to be increased
 | 
					%% The MQTT plugin expects Mnesia dump_log_write_threshold to be increased
 | 
				
			||||||
%% from 1000 (Mnesia default) to 5000 (RabbitMQ default).
 | 
					%% from 1000 (Mnesia default) to 5000 (RabbitMQ default).
 | 
				
			||||||
rabbitmq_default(Config) ->
 | 
					rabbitmq_default(Config) ->
 | 
				
			||||||
    ?assertEqual(
 | 
					    ?assertEqual(5_000,
 | 
				
			||||||
        5_000,
 | 
					                 rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])),
 | 
				
			||||||
        rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])
 | 
					    ?assertEqual(90_000,
 | 
				
			||||||
    ),
 | 
					                 rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])).
 | 
				
			||||||
    ?assertEqual(
 | 
					 | 
				
			||||||
        90_000,
 | 
					 | 
				
			||||||
        rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
%% User configured setting in advanced.config should be respected.
 | 
					%% User configured setting in advanced.config should be respected.
 | 
				
			||||||
environment_set(Config) ->
 | 
					environment_set(Config) ->
 | 
				
			||||||
    ?assertEqual(
 | 
					    ?assertEqual(25_000,
 | 
				
			||||||
        25_000,
 | 
					                 rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])),
 | 
				
			||||||
        rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])
 | 
					    ?assertEqual(60_000,
 | 
				
			||||||
    ),
 | 
					                 rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])).
 | 
				
			||||||
    ?assertEqual(
 | 
					 | 
				
			||||||
        60_000,
 | 
					 | 
				
			||||||
        rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
%% User configured setting in RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS should be respected.
 | 
					%% User configured setting in RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS should be respected.
 | 
				
			||||||
flag_set(Config) ->
 | 
					flag_set(Config) ->
 | 
				
			||||||
    ?assertEqual(
 | 
					    ?assertEqual(15_000,
 | 
				
			||||||
        15_000,
 | 
					                 rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])),
 | 
				
			||||||
        rpc(Config, 0, mnesia, system_info, [dump_log_write_threshold])
 | 
					    ?assertEqual(90_000,
 | 
				
			||||||
    ),
 | 
					                 rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])).
 | 
				
			||||||
    ?assertEqual(
 | 
					 | 
				
			||||||
        90_000,
 | 
					 | 
				
			||||||
        rpc(Config, 0, mnesia, system_info, [dump_log_time_threshold])
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,7 @@ init_per_suite(Config) ->
 | 
				
			||||||
    Config1 = rabbit_ct_helpers:run_setup_steps(Config),
 | 
					    Config1 = rabbit_ct_helpers:run_setup_steps(Config),
 | 
				
			||||||
    rabbit_ct_config_schema:init_schemas(rabbitmq_mqtt, Config1).
 | 
					    rabbit_ct_config_schema:init_schemas(rabbitmq_mqtt, Config1).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
end_per_suite(Config) ->
 | 
					end_per_suite(Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:run_teardown_steps(Config).
 | 
					    rabbit_ct_helpers:run_teardown_steps(Config).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,19 +31,15 @@ init_per_testcase(Testcase, Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:testcase_started(Config, Testcase),
 | 
					    rabbit_ct_helpers:testcase_started(Config, Testcase),
 | 
				
			||||||
    Config1 = rabbit_ct_helpers:set_config(Config, [
 | 
					    Config1 = rabbit_ct_helpers:set_config(Config, [
 | 
				
			||||||
        {rmq_nodename_suffix, Testcase}
 | 
					        {rmq_nodename_suffix, Testcase}
 | 
				
			||||||
    ]),
 | 
					      ]),
 | 
				
			||||||
    rabbit_ct_helpers:run_steps(
 | 
					    rabbit_ct_helpers:run_steps(Config1,
 | 
				
			||||||
        Config1,
 | 
					      rabbit_ct_broker_helpers:setup_steps() ++
 | 
				
			||||||
        rabbit_ct_broker_helpers:setup_steps() ++
 | 
					      rabbit_ct_client_helpers:setup_steps()).
 | 
				
			||||||
            rabbit_ct_client_helpers:setup_steps()
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
end_per_testcase(Testcase, Config) ->
 | 
					end_per_testcase(Testcase, Config) ->
 | 
				
			||||||
    Config1 = rabbit_ct_helpers:run_steps(
 | 
					    Config1 = rabbit_ct_helpers:run_steps(Config,
 | 
				
			||||||
        Config,
 | 
					      rabbit_ct_client_helpers:teardown_steps() ++
 | 
				
			||||||
        rabbit_ct_client_helpers:teardown_steps() ++
 | 
					      rabbit_ct_broker_helpers:teardown_steps()),
 | 
				
			||||||
            rabbit_ct_broker_helpers:teardown_steps()
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    rabbit_ct_helpers:testcase_finished(Config1, Testcase).
 | 
					    rabbit_ct_helpers:testcase_finished(Config1, Testcase).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
%% -------------------------------------------------------------------
 | 
					%% -------------------------------------------------------------------
 | 
				
			||||||
| 
						 | 
					@ -50,13 +47,9 @@ end_per_testcase(Testcase, Config) ->
 | 
				
			||||||
%% -------------------------------------------------------------------
 | 
					%% -------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
run_snippets(Config) ->
 | 
					run_snippets(Config) ->
 | 
				
			||||||
    ok = rabbit_ct_broker_helpers:rpc(
 | 
					    ok = rabbit_ct_broker_helpers:rpc(Config, 0,
 | 
				
			||||||
        Config,
 | 
					      ?MODULE, run_snippets1, [Config]).
 | 
				
			||||||
        0,
 | 
					 | 
				
			||||||
        ?MODULE,
 | 
					 | 
				
			||||||
        run_snippets1,
 | 
					 | 
				
			||||||
        [Config]
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
run_snippets1(Config) ->
 | 
					run_snippets1(Config) ->
 | 
				
			||||||
    rabbit_ct_config_schema:run_snippets(Config).
 | 
					    rabbit_ct_config_schema:run_snippets(Config).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,11 +15,10 @@
 | 
				
			||||||
init(_) ->
 | 
					init(_) ->
 | 
				
			||||||
    {ok, ?INIT_STATE}.
 | 
					    {ok, ?INIT_STATE}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_event(#event{type = T}, State) when
 | 
					handle_event(#event{type = T}, State)
 | 
				
			||||||
    T =:= node_stats orelse
 | 
					  when T =:= node_stats orelse
 | 
				
			||||||
        T =:= node_node_stats orelse
 | 
					       T =:= node_node_stats orelse
 | 
				
			||||||
        T =:= node_node_deleted
 | 
					       T =:= node_node_deleted ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    {ok, State};
 | 
					    {ok, State};
 | 
				
			||||||
handle_event(Event, State) ->
 | 
					handle_event(Event, State) ->
 | 
				
			||||||
    {ok, [Event | State]}.
 | 
					    {ok, [Event | State]}.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,31 +13,27 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-import(rabbit_ct_broker_helpers, [rpc/5]).
 | 
					-import(rabbit_ct_broker_helpers, [rpc/5]).
 | 
				
			||||||
-import(rabbit_ct_helpers, [eventually/1]).
 | 
					-import(rabbit_ct_helpers, [eventually/1]).
 | 
				
			||||||
-import(util, [
 | 
					-import(util, [expect_publishes/3,
 | 
				
			||||||
    expect_publishes/3,
 | 
					               get_global_counters/4,
 | 
				
			||||||
    get_global_counters/4,
 | 
					               connect/2,
 | 
				
			||||||
    connect/2,
 | 
					               connect/4]).
 | 
				
			||||||
    connect/4
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-define(PROTO_VER, v4).
 | 
					-define(PROTO_VER, v4).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
all() ->
 | 
					all() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {group, cluster_size_3}
 | 
					     {group, cluster_size_3}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
groups() ->
 | 
					groups() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {cluster_size_3, [], [
 | 
					     {cluster_size_3, [], [delete_ra_cluster_mqtt_node,
 | 
				
			||||||
            delete_ra_cluster_mqtt_node,
 | 
					                           rabbit_mqtt_qos0_queue]}
 | 
				
			||||||
            rabbit_mqtt_qos0_queue
 | 
					 | 
				
			||||||
        ]}
 | 
					 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
suite() ->
 | 
					suite() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {timetrap, {minutes, 2}}
 | 
					     {timetrap, {minutes, 2}}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
init_per_suite(Config) ->
 | 
					init_per_suite(Config) ->
 | 
				
			||||||
| 
						 | 
					@ -48,36 +44,26 @@ end_per_suite(Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:run_teardown_steps(Config).
 | 
					    rabbit_ct_helpers:run_teardown_steps(Config).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
init_per_group(Group = cluster_size_3, Config0) ->
 | 
					init_per_group(Group = cluster_size_3, Config0) ->
 | 
				
			||||||
    Config1 = rabbit_ct_helpers:set_config(Config0, [
 | 
					    Config1 = rabbit_ct_helpers:set_config(Config0, [{rmq_nodes_count, 3},
 | 
				
			||||||
        {rmq_nodes_count, 3},
 | 
					                                                     {rmq_nodename_suffix, Group}]),
 | 
				
			||||||
        {rmq_nodename_suffix, Group}
 | 
					 | 
				
			||||||
    ]),
 | 
					 | 
				
			||||||
    Config = rabbit_ct_helpers:merge_app_env(
 | 
					    Config = rabbit_ct_helpers:merge_app_env(
 | 
				
			||||||
        Config1, {rabbit, [{forced_feature_flags_on_init, []}]}
 | 
					               Config1, {rabbit, [{forced_feature_flags_on_init, []}]}),
 | 
				
			||||||
    ),
 | 
					    rabbit_ct_helpers:run_steps(Config,
 | 
				
			||||||
    rabbit_ct_helpers:run_steps(
 | 
					                                rabbit_ct_broker_helpers:setup_steps() ++
 | 
				
			||||||
        Config,
 | 
					                                rabbit_ct_client_helpers:setup_steps()).
 | 
				
			||||||
        rabbit_ct_broker_helpers:setup_steps() ++
 | 
					 | 
				
			||||||
            rabbit_ct_client_helpers:setup_steps()
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
end_per_group(_Group, Config) ->
 | 
					end_per_group(_Group, Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:run_steps(
 | 
					    rabbit_ct_helpers:run_steps(Config,
 | 
				
			||||||
        Config,
 | 
					                                rabbit_ct_client_helpers:teardown_steps() ++
 | 
				
			||||||
        rabbit_ct_client_helpers:teardown_steps() ++
 | 
					                                rabbit_ct_broker_helpers:teardown_steps()).
 | 
				
			||||||
            rabbit_ct_broker_helpers:teardown_steps()
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
init_per_testcase(TestCase, Config) ->
 | 
					init_per_testcase(TestCase, Config) ->
 | 
				
			||||||
    case rabbit_ct_broker_helpers:is_feature_flag_supported(Config, TestCase) of
 | 
					    case rabbit_ct_broker_helpers:is_feature_flag_supported(Config, TestCase) of
 | 
				
			||||||
        true ->
 | 
					        true ->
 | 
				
			||||||
            Config;
 | 
					            Config;
 | 
				
			||||||
        false ->
 | 
					        false ->
 | 
				
			||||||
            {skip,
 | 
					            {skip, io_lib:format("feature flag ~s is unsupported",
 | 
				
			||||||
                io_lib:format(
 | 
					                                 [TestCase])}
 | 
				
			||||||
                    "feature flag ~s is unsupported",
 | 
					 | 
				
			||||||
                    [TestCase]
 | 
					 | 
				
			||||||
                )}
 | 
					 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
end_per_testcase(_TestCase, Config) ->
 | 
					end_per_testcase(_TestCase, Config) ->
 | 
				
			||||||
| 
						 | 
					@ -90,27 +76,16 @@ delete_ra_cluster_mqtt_node(Config) ->
 | 
				
			||||||
    %% old client ID tracking works
 | 
					    %% old client ID tracking works
 | 
				
			||||||
    ?assertEqual(1, length(util:all_connection_pids(Config))),
 | 
					    ?assertEqual(1, length(util:all_connection_pids(Config))),
 | 
				
			||||||
    %% Ra processes are alive
 | 
					    %% Ra processes are alive
 | 
				
			||||||
    ?assert(
 | 
					    ?assert(lists:all(fun erlang:is_pid/1,
 | 
				
			||||||
        lists:all(
 | 
					                      rabbit_ct_broker_helpers:rpc_all(Config, erlang, whereis, [mqtt_node]))),
 | 
				
			||||||
            fun erlang:is_pid/1,
 | 
					 | 
				
			||||||
            rabbit_ct_broker_helpers:rpc_all(Config, erlang, whereis, [mqtt_node])
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ?assertEqual(
 | 
					    ?assertEqual(ok,
 | 
				
			||||||
        ok,
 | 
					                 rabbit_ct_broker_helpers:enable_feature_flag(Config, FeatureFlag)),
 | 
				
			||||||
        rabbit_ct_broker_helpers:enable_feature_flag(Config, FeatureFlag)
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% Ra processes should be gone
 | 
					    %% Ra processes should be gone
 | 
				
			||||||
    rabbit_ct_helpers:eventually(
 | 
					    rabbit_ct_helpers:eventually(
 | 
				
			||||||
        ?_assert(
 | 
					      ?_assert(lists:all(fun(Pid) -> Pid =:= undefined end,
 | 
				
			||||||
            lists:all(
 | 
					                         rabbit_ct_broker_helpers:rpc_all(Config, erlang, whereis, [mqtt_node])))),
 | 
				
			||||||
                fun(Pid) -> Pid =:= undefined end,
 | 
					 | 
				
			||||||
                rabbit_ct_broker_helpers:rpc_all(Config, erlang, whereis, [mqtt_node])
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    %% new client ID tracking works
 | 
					    %% new client ID tracking works
 | 
				
			||||||
    ?assertEqual(1, length(util:all_connection_pids(Config))),
 | 
					    ?assertEqual(1, length(util:all_connection_pids(Config))),
 | 
				
			||||||
    ?assert(erlang:is_process_alive(C)),
 | 
					    ?assert(erlang:is_process_alive(C)),
 | 
				
			||||||
| 
						 | 
					@ -124,30 +99,20 @@ rabbit_mqtt_qos0_queue(Config) ->
 | 
				
			||||||
    {ok, _, [0]} = emqtt:subscribe(C1, Topic, qos0),
 | 
					    {ok, _, [0]} = emqtt:subscribe(C1, Topic, qos0),
 | 
				
			||||||
    ok = emqtt:publish(C1, Topic, Msg, qos0),
 | 
					    ok = emqtt:publish(C1, Topic, Msg, qos0),
 | 
				
			||||||
    ok = expect_publishes(C1, Topic, [Msg]),
 | 
					    ok = expect_publishes(C1, Topic, [Msg]),
 | 
				
			||||||
    ?assertEqual(
 | 
					    ?assertEqual(1,
 | 
				
			||||||
        1,
 | 
					                 length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue]))),
 | 
				
			||||||
        length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue]))
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ?assertEqual(
 | 
					    ?assertEqual(ok,
 | 
				
			||||||
        ok,
 | 
					                 rabbit_ct_broker_helpers:enable_feature_flag(Config, FeatureFlag)),
 | 
				
			||||||
        rabbit_ct_broker_helpers:enable_feature_flag(Config, FeatureFlag)
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% Queue type does not chanage for existing connection.
 | 
					    %% Queue type does not chanage for existing connection.
 | 
				
			||||||
    ?assertEqual(
 | 
					    ?assertEqual(1,
 | 
				
			||||||
        1,
 | 
					                 length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue]))),
 | 
				
			||||||
        length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue]))
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    ok = emqtt:publish(C1, Topic, Msg, qos0),
 | 
					    ok = emqtt:publish(C1, Topic, Msg, qos0),
 | 
				
			||||||
    ok = expect_publishes(C1, Topic, [Msg]),
 | 
					    ok = expect_publishes(C1, Topic, [Msg]),
 | 
				
			||||||
    ?assertMatch(
 | 
					    ?assertMatch(#{messages_delivered_total := 2,
 | 
				
			||||||
        #{
 | 
					                   messages_delivered_consume_auto_ack_total := 2},
 | 
				
			||||||
            messages_delivered_total := 2,
 | 
					                 get_global_counters(Config, ?PROTO_VER, 0, [{queue_type, rabbit_classic_queue}])),
 | 
				
			||||||
            messages_delivered_consume_auto_ack_total := 2
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        get_global_counters(Config, ?PROTO_VER, 0, [{queue_type, rabbit_classic_queue}])
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% Reconnecting with the same client ID will terminate the old connection.
 | 
					    %% Reconnecting with the same client ID will terminate the old connection.
 | 
				
			||||||
    true = unlink(C1),
 | 
					    true = unlink(C1),
 | 
				
			||||||
| 
						 | 
					@ -155,22 +120,13 @@ rabbit_mqtt_qos0_queue(Config) ->
 | 
				
			||||||
    {ok, _, [0]} = emqtt:subscribe(C2, Topic, qos0),
 | 
					    {ok, _, [0]} = emqtt:subscribe(C2, Topic, qos0),
 | 
				
			||||||
    %% This time, we get the new queue type.
 | 
					    %% This time, we get the new queue type.
 | 
				
			||||||
    eventually(
 | 
					    eventually(
 | 
				
			||||||
        ?_assertEqual(
 | 
					      ?_assertEqual(0,
 | 
				
			||||||
            0,
 | 
					                    length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue])))),
 | 
				
			||||||
            length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [rabbit_classic_queue]))
 | 
					    ?assertEqual(1,
 | 
				
			||||||
        )
 | 
					                 length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [FeatureFlag]))),
 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    ?assertEqual(
 | 
					 | 
				
			||||||
        1,
 | 
					 | 
				
			||||||
        length(rpc(Config, 0, rabbit_amqqueue, list_by_type, [FeatureFlag]))
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    ok = emqtt:publish(C2, Topic, Msg, qos0),
 | 
					    ok = emqtt:publish(C2, Topic, Msg, qos0),
 | 
				
			||||||
    ok = expect_publishes(C2, Topic, [Msg]),
 | 
					    ok = expect_publishes(C2, Topic, [Msg]),
 | 
				
			||||||
    ?assertMatch(
 | 
					    ?assertMatch(#{messages_delivered_total := 1,
 | 
				
			||||||
        #{
 | 
					                   messages_delivered_consume_auto_ack_total := 1},
 | 
				
			||||||
            messages_delivered_total := 1,
 | 
					                 get_global_counters(Config, ?PROTO_VER, 0, [{queue_type, FeatureFlag}])),
 | 
				
			||||||
            messages_delivered_consume_auto_ack_total := 1
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        get_global_counters(Config, ?PROTO_VER, 0, [{queue_type, FeatureFlag}])
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    ok = emqtt:disconnect(C2).
 | 
					    ok = emqtt:disconnect(C2).
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,25 +12,24 @@
 | 
				
			||||||
-include_lib("eunit/include/eunit.hrl").
 | 
					-include_lib("eunit/include/eunit.hrl").
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-define(BASE_CONF_MQTT,
 | 
					-define(BASE_CONF_MQTT,
 | 
				
			||||||
    {rabbitmq_mqtt, [
 | 
					        {rabbitmq_mqtt, [
 | 
				
			||||||
        {ssl_cert_login, true},
 | 
					           {ssl_cert_login,   true},
 | 
				
			||||||
        {allow_anonymous, false},
 | 
					           {allow_anonymous,  false},
 | 
				
			||||||
        {sparkplug, true},
 | 
					           {sparkplug,        true},
 | 
				
			||||||
        {tcp_listeners, []},
 | 
					           {tcp_listeners,    []},
 | 
				
			||||||
        {ssl_listeners, []}
 | 
					           {ssl_listeners,    []}
 | 
				
			||||||
    ]}
 | 
					           ]}).
 | 
				
			||||||
).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
all() ->
 | 
					all() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {group, non_parallel_tests}
 | 
					      {group, non_parallel_tests}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
groups() ->
 | 
					groups() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {non_parallel_tests, [], [
 | 
					      {non_parallel_tests, [], [
 | 
				
			||||||
            java
 | 
					                                java
 | 
				
			||||||
        ]}
 | 
					                               ]}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
suite() ->
 | 
					suite() ->
 | 
				
			||||||
| 
						 | 
					@ -53,20 +52,16 @@ init_per_suite(Config) ->
 | 
				
			||||||
        {rmq_certspwd, "bunnychow"},
 | 
					        {rmq_certspwd, "bunnychow"},
 | 
				
			||||||
        {rmq_nodes_clustered, true},
 | 
					        {rmq_nodes_clustered, true},
 | 
				
			||||||
        {rmq_nodes_count, 3}
 | 
					        {rmq_nodes_count, 3}
 | 
				
			||||||
    ]),
 | 
					      ]),
 | 
				
			||||||
    rabbit_ct_helpers:run_setup_steps(
 | 
					    rabbit_ct_helpers:run_setup_steps(Config1,
 | 
				
			||||||
        Config1,
 | 
					      [ fun merge_app_env/1 ] ++
 | 
				
			||||||
        [fun merge_app_env/1] ++
 | 
					      rabbit_ct_broker_helpers:setup_steps() ++
 | 
				
			||||||
            rabbit_ct_broker_helpers:setup_steps() ++
 | 
					      rabbit_ct_client_helpers:setup_steps()).
 | 
				
			||||||
            rabbit_ct_client_helpers:setup_steps()
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
end_per_suite(Config) ->
 | 
					end_per_suite(Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:run_teardown_steps(
 | 
					    rabbit_ct_helpers:run_teardown_steps(Config,
 | 
				
			||||||
        Config,
 | 
					      rabbit_ct_client_helpers:teardown_steps() ++
 | 
				
			||||||
        rabbit_ct_client_helpers:teardown_steps() ++
 | 
					      rabbit_ct_broker_helpers:teardown_steps()).
 | 
				
			||||||
            rabbit_ct_broker_helpers:teardown_steps()
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
init_per_group(_, Config) ->
 | 
					init_per_group(_, Config) ->
 | 
				
			||||||
    Config.
 | 
					    Config.
 | 
				
			||||||
| 
						 | 
					@ -79,38 +74,25 @@ init_per_testcase(Testcase, Config) ->
 | 
				
			||||||
    CertFile = filename:join([CertsDir, "client", "cert.pem"]),
 | 
					    CertFile = filename:join([CertsDir, "client", "cert.pem"]),
 | 
				
			||||||
    {ok, CertBin} = file:read_file(CertFile),
 | 
					    {ok, CertBin} = file:read_file(CertFile),
 | 
				
			||||||
    [{'Certificate', Cert, not_encrypted}] = public_key:pem_decode(CertBin),
 | 
					    [{'Certificate', Cert, not_encrypted}] = public_key:pem_decode(CertBin),
 | 
				
			||||||
    UserBin = rabbit_ct_broker_helpers:rpc(
 | 
					    UserBin = rabbit_ct_broker_helpers:rpc(Config, 0,
 | 
				
			||||||
        Config,
 | 
					                                           rabbit_ssl,
 | 
				
			||||||
        0,
 | 
					                                           peer_cert_auth_name,
 | 
				
			||||||
        rabbit_ssl,
 | 
					                                           [Cert]),
 | 
				
			||||||
        peer_cert_auth_name,
 | 
					 | 
				
			||||||
        [Cert]
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    User = binary_to_list(UserBin),
 | 
					    User = binary_to_list(UserBin),
 | 
				
			||||||
    {ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["add_user", User, ""]),
 | 
					    {ok,_} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["add_user", User, ""]),
 | 
				
			||||||
    {ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, [
 | 
					    {ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0, ["set_permissions",  "-p", "/", User, ".*", ".*", ".*"]),
 | 
				
			||||||
        "set_permissions", "-p", "/", User, ".*", ".*", ".*"
 | 
					    {ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(Config, 0,
 | 
				
			||||||
    ]),
 | 
					        ["set_topic_permissions",  "-p", "/", "guest", "amq.topic",
 | 
				
			||||||
    {ok, _} = rabbit_ct_broker_helpers:rabbitmqctl(
 | 
					 | 
				
			||||||
        Config,
 | 
					 | 
				
			||||||
        0,
 | 
					 | 
				
			||||||
        [
 | 
					 | 
				
			||||||
            "set_topic_permissions",
 | 
					 | 
				
			||||||
            "-p",
 | 
					 | 
				
			||||||
            "/",
 | 
					 | 
				
			||||||
            "guest",
 | 
					 | 
				
			||||||
            "amq.topic",
 | 
					 | 
				
			||||||
            % Write permission
 | 
					            % Write permission
 | 
				
			||||||
            "test-topic|test-retained-topic|{username}.{client_id}.a|^sp[AB]v\\d+___\\d+",
 | 
					            "test-topic|test-retained-topic|{username}.{client_id}.a|^sp[AB]v\\d+___\\d+",
 | 
				
			||||||
            % Read permission
 | 
					            % Read permission
 | 
				
			||||||
            "test-topic|test-retained-topic|last-will|{username}.{client_id}.a|^sp[AB]v\\d+___\\d+"
 | 
					            "test-topic|test-retained-topic|last-will|{username}.{client_id}.a|^sp[AB]v\\d+___\\d+"]),
 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    rabbit_ct_helpers:testcase_started(Config, Testcase).
 | 
					    rabbit_ct_helpers:testcase_started(Config, Testcase).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
end_per_testcase(Testcase, Config) ->
 | 
					end_per_testcase(Testcase, Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:testcase_finished(Config, Testcase).
 | 
					    rabbit_ct_helpers:testcase_finished(Config, Testcase).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
%% -------------------------------------------------------------------
 | 
					%% -------------------------------------------------------------------
 | 
				
			||||||
%% Testsuite cases
 | 
					%% Testsuite cases
 | 
				
			||||||
%% -------------------------------------------------------------------
 | 
					%% -------------------------------------------------------------------
 | 
				
			||||||
| 
						 | 
					@ -140,5 +122,5 @@ q(P, [K | Rem]) ->
 | 
				
			||||||
        undefined -> undefined;
 | 
					        undefined -> undefined;
 | 
				
			||||||
        V -> q(V, Rem)
 | 
					        V -> q(V, Rem)
 | 
				
			||||||
    end;
 | 
					    end;
 | 
				
			||||||
q(P, []) ->
 | 
					q(P, []) -> {ok, P}.
 | 
				
			||||||
    {ok, P}.
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,19 +12,20 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
all() ->
 | 
					all() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {group, tests}
 | 
					     {group, tests}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
all_tests() ->
 | 
					all_tests() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        basics,
 | 
					     basics,
 | 
				
			||||||
        machine_upgrade,
 | 
					     machine_upgrade,
 | 
				
			||||||
        many_downs
 | 
					     many_downs
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
groups() ->
 | 
					groups() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {tests, [], all_tests()}
 | 
					     {tests, [], all_tests()}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
init_per_suite(Config) ->
 | 
					init_per_suite(Config) ->
 | 
				
			||||||
| 
						 | 
					@ -52,16 +53,13 @@ end_per_testcase(_TestCase, _Config) ->
 | 
				
			||||||
basics(_Config) ->
 | 
					basics(_Config) ->
 | 
				
			||||||
    S0 = mqtt_machine:init(#{}),
 | 
					    S0 = mqtt_machine:init(#{}),
 | 
				
			||||||
    ClientId = <<"id1">>,
 | 
					    ClientId = <<"id1">>,
 | 
				
			||||||
    OthPid = spawn(fun() -> ok end),
 | 
					    OthPid = spawn(fun () -> ok end),
 | 
				
			||||||
    {S1, ok, _} = mqtt_machine:apply(meta(1), {register, ClientId, self()}, S0),
 | 
					    {S1, ok, _} = mqtt_machine:apply(meta(1), {register, ClientId, self()}, S0),
 | 
				
			||||||
    ?assertMatch(#machine_state{client_ids = Ids} when map_size(Ids) == 1, S1),
 | 
					    ?assertMatch(#machine_state{client_ids = Ids} when map_size(Ids) == 1, S1),
 | 
				
			||||||
    ?assertMatch(#machine_state{pids = Pids} when map_size(Pids) == 1, S1),
 | 
					    ?assertMatch(#machine_state{pids = Pids} when map_size(Pids) == 1, S1),
 | 
				
			||||||
    {S2, ok, _} = mqtt_machine:apply(meta(2), {register, ClientId, OthPid}, S1),
 | 
					    {S2, ok, _} = mqtt_machine:apply(meta(2), {register, ClientId, OthPid}, S1),
 | 
				
			||||||
    ?assertMatch(
 | 
					    ?assertMatch(#machine_state{client_ids = #{ClientId := OthPid} = Ids}
 | 
				
			||||||
        #machine_state{client_ids = #{ClientId := OthPid} = Ids} when
 | 
					                   when map_size(Ids) == 1, S2),
 | 
				
			||||||
            map_size(Ids) == 1,
 | 
					 | 
				
			||||||
        S2
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    {S3, ok, _} = mqtt_machine:apply(meta(3), {down, OthPid, noproc}, S2),
 | 
					    {S3, ok, _} = mqtt_machine:apply(meta(3), {down, OthPid, noproc}, S2),
 | 
				
			||||||
    ?assertMatch(#machine_state{client_ids = Ids} when map_size(Ids) == 0, S3),
 | 
					    ?assertMatch(#machine_state{client_ids = Ids} when map_size(Ids) == 0, S3),
 | 
				
			||||||
    {S4, ok, _} = mqtt_machine:apply(meta(3), {unregister, ClientId, OthPid}, S2),
 | 
					    {S4, ok, _} = mqtt_machine:apply(meta(3), {unregister, ClientId, OthPid}, S2),
 | 
				
			||||||
| 
						 | 
					@ -76,63 +74,41 @@ machine_upgrade(_Config) ->
 | 
				
			||||||
    {S1, ok, _} = mqtt_machine_v0:apply(meta(1), {register, ClientId, self()}, S0),
 | 
					    {S1, ok, _} = mqtt_machine_v0:apply(meta(1), {register, ClientId, self()}, S0),
 | 
				
			||||||
    ?assertMatch({machine_state, Ids} when map_size(Ids) == 1, S1),
 | 
					    ?assertMatch({machine_state, Ids} when map_size(Ids) == 1, S1),
 | 
				
			||||||
    {S2, ok, _} = mqtt_machine:apply(meta(2), {machine_version, 0, 1}, S1),
 | 
					    {S2, ok, _} = mqtt_machine:apply(meta(2), {machine_version, 0, 1}, S1),
 | 
				
			||||||
    ?assertMatch(
 | 
					    ?assertMatch(#machine_state{client_ids = #{ClientId := Self},
 | 
				
			||||||
        #machine_state{
 | 
					                                pids = #{Self := [ClientId]} = Pids}
 | 
				
			||||||
            client_ids = #{ClientId := Self},
 | 
					                                  when map_size(Pids) == 1, S2),
 | 
				
			||||||
            pids = #{Self := [ClientId]} = Pids
 | 
					 | 
				
			||||||
        } when
 | 
					 | 
				
			||||||
            map_size(Pids) == 1,
 | 
					 | 
				
			||||||
        S2
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    {S3, ok, _} = mqtt_machine:apply(meta(3), {down, self(), noproc}, S2),
 | 
					    {S3, ok, _} = mqtt_machine:apply(meta(3), {down, self(), noproc}, S2),
 | 
				
			||||||
    ?assertMatch(
 | 
					    ?assertMatch(#machine_state{client_ids = Ids,
 | 
				
			||||||
        #machine_state{
 | 
					                                pids = Pids}
 | 
				
			||||||
            client_ids = Ids,
 | 
					                   when map_size(Ids) == 0 andalso map_size(Pids) == 0, S3),
 | 
				
			||||||
            pids = Pids
 | 
					 | 
				
			||||||
        } when
 | 
					 | 
				
			||||||
            map_size(Ids) == 0 andalso map_size(Pids) == 0,
 | 
					 | 
				
			||||||
        S3
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ok.
 | 
					    ok.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
many_downs(_Config) ->
 | 
					many_downs(_Config) ->
 | 
				
			||||||
    S0 = mqtt_machine:init(#{}),
 | 
					    S0 = mqtt_machine:init(#{}),
 | 
				
			||||||
    Clients = [
 | 
					    Clients = [{list_to_binary(integer_to_list(I)), spawn(fun() -> ok end)}
 | 
				
			||||||
        {list_to_binary(integer_to_list(I)), spawn(fun() -> ok end)}
 | 
					               || I <- lists:seq(1, 10000)],
 | 
				
			||||||
     || I <- lists:seq(1, 10000)
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    S1 = lists:foldl(
 | 
					    S1 = lists:foldl(
 | 
				
			||||||
        fun({ClientId, Pid}, Acc0) ->
 | 
					           fun ({ClientId, Pid}, Acc0) ->
 | 
				
			||||||
            {Acc, ok, _} = mqtt_machine:apply(meta(1), {register, ClientId, Pid}, Acc0),
 | 
					                   {Acc, ok, _} = mqtt_machine:apply(meta(1), {register, ClientId, Pid}, Acc0),
 | 
				
			||||||
            Acc
 | 
					                   Acc
 | 
				
			||||||
        end,
 | 
					           end, S0, Clients),
 | 
				
			||||||
        S0,
 | 
					 | 
				
			||||||
        Clients
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    _ = lists:foldl(
 | 
					    _ = lists:foldl(
 | 
				
			||||||
        fun({_ClientId, Pid}, Acc0) ->
 | 
					           fun ({_ClientId, Pid}, Acc0) ->
 | 
				
			||||||
            {Acc, ok, _} = mqtt_machine:apply(meta(1), {down, Pid, noproc}, Acc0),
 | 
					                   {Acc, ok, _} = mqtt_machine:apply(meta(1), {down, Pid, noproc}, Acc0),
 | 
				
			||||||
            Acc
 | 
					                   Acc
 | 
				
			||||||
        end,
 | 
					           end, S1, Clients),
 | 
				
			||||||
        S1,
 | 
					 | 
				
			||||||
        Clients
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    _ = lists:foldl(
 | 
					    _ = lists:foldl(
 | 
				
			||||||
        fun({ClientId, Pid}, Acc0) ->
 | 
					           fun ({ClientId, Pid}, Acc0) ->
 | 
				
			||||||
            {Acc, ok, _} = mqtt_machine:apply(meta(1), {unregister, ClientId, Pid}, Acc0),
 | 
					                   {Acc, ok, _} = mqtt_machine:apply(meta(1), {unregister, ClientId,
 | 
				
			||||||
            Acc
 | 
					                                                               Pid}, Acc0),
 | 
				
			||||||
        end,
 | 
					                   Acc
 | 
				
			||||||
        S0,
 | 
					           end, S0, Clients),
 | 
				
			||||||
        Clients
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ok.
 | 
					    ok.
 | 
				
			||||||
%% Utility
 | 
					%% Utility
 | 
				
			||||||
 | 
					
 | 
				
			||||||
meta(Idx) ->
 | 
					meta(Idx) ->
 | 
				
			||||||
    #{
 | 
					    #{index => Idx,
 | 
				
			||||||
        index => Idx,
 | 
					      term => 1,
 | 
				
			||||||
        term => 1,
 | 
					      ts => erlang:system_time(millisecond)}.
 | 
				
			||||||
        ts => erlang:system_time(millisecond)
 | 
					 | 
				
			||||||
    }.
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@
 | 
				
			||||||
%%
 | 
					%%
 | 
				
			||||||
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates.  All rights reserved.
 | 
					%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates.  All rights reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-module(processor_SUITE).
 | 
					-module(processor_SUITE).
 | 
				
			||||||
-compile([export_all, nowarn_export_all]).
 | 
					-compile([export_all, nowarn_export_all]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,17 +14,17 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
all() ->
 | 
					all() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {group, non_parallel_tests}
 | 
					      {group, non_parallel_tests}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
groups() ->
 | 
					groups() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {non_parallel_tests, [], [
 | 
					      {non_parallel_tests, [], [
 | 
				
			||||||
            ignores_colons_in_username_if_option_set,
 | 
					                                ignores_colons_in_username_if_option_set,
 | 
				
			||||||
            interprets_colons_in_username_if_option_not_set,
 | 
					                                interprets_colons_in_username_if_option_not_set,
 | 
				
			||||||
            get_vhosts_from_global_runtime_parameter,
 | 
					                                get_vhosts_from_global_runtime_parameter,
 | 
				
			||||||
            get_vhost
 | 
					                                get_vhost
 | 
				
			||||||
        ]}
 | 
					                               ]}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
suite() ->
 | 
					suite() ->
 | 
				
			||||||
| 
						 | 
					@ -41,50 +42,35 @@ init_per_testcase(get_vhost, Config) ->
 | 
				
			||||||
    mnesia:start(),
 | 
					    mnesia:start(),
 | 
				
			||||||
    mnesia:create_table(rabbit_runtime_parameters, [
 | 
					    mnesia:create_table(rabbit_runtime_parameters, [
 | 
				
			||||||
        {attributes, record_info(fields, runtime_parameters)},
 | 
					        {attributes, record_info(fields, runtime_parameters)},
 | 
				
			||||||
        {record_name, runtime_parameters}
 | 
					        {record_name, runtime_parameters}]),
 | 
				
			||||||
    ]),
 | 
					 | 
				
			||||||
    Config;
 | 
					    Config;
 | 
				
			||||||
init_per_testcase(_, Config) ->
 | 
					init_per_testcase(_, Config) -> Config.
 | 
				
			||||||
    Config.
 | 
					 | 
				
			||||||
end_per_testcase(get_vhost, Config) ->
 | 
					end_per_testcase(get_vhost, Config) ->
 | 
				
			||||||
    mnesia:stop(),
 | 
					    mnesia:stop(),
 | 
				
			||||||
    Config;
 | 
					    Config;
 | 
				
			||||||
end_per_testcase(_, Config) ->
 | 
					end_per_testcase(_, Config) -> Config.
 | 
				
			||||||
    Config.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
ignore_colons(B) -> application:set_env(rabbitmq_mqtt, ignore_colons_in_username, B).
 | 
					ignore_colons(B) -> application:set_env(rabbitmq_mqtt, ignore_colons_in_username, B).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ignores_colons_in_username_if_option_set(_Config) ->
 | 
					ignores_colons_in_username_if_option_set(_Config) ->
 | 
				
			||||||
    ignore_colons(true),
 | 
					    ignore_colons(true),
 | 
				
			||||||
    ?assertEqual(
 | 
					    ?assertEqual({rabbit_mqtt_util:env(vhost), <<"a:b:c">>},
 | 
				
			||||||
        {rabbit_mqtt_util:env(vhost), <<"a:b:c">>},
 | 
					                  rabbit_mqtt_processor:get_vhost_username(<<"a:b:c">>)).
 | 
				
			||||||
        rabbit_mqtt_processor:get_vhost_username(<<"a:b:c">>)
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
interprets_colons_in_username_if_option_not_set(_Config) ->
 | 
					interprets_colons_in_username_if_option_not_set(_Config) ->
 | 
				
			||||||
    ignore_colons(false),
 | 
					   ignore_colons(false),
 | 
				
			||||||
    ?assertEqual(
 | 
					   ?assertEqual({<<"a:b">>, <<"c">>},
 | 
				
			||||||
        {<<"a:b">>, <<"c">>},
 | 
					                 rabbit_mqtt_processor:get_vhost_username(<<"a:b:c">>)).
 | 
				
			||||||
        rabbit_mqtt_processor:get_vhost_username(<<"a:b:c">>)
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
get_vhosts_from_global_runtime_parameter(_Config) ->
 | 
					get_vhosts_from_global_runtime_parameter(_Config) ->
 | 
				
			||||||
    MappingParameter = [
 | 
					    MappingParameter = [
 | 
				
			||||||
        {<<"O=client,CN=dummy1">>, <<"vhost1">>},
 | 
					        {<<"O=client,CN=dummy1">>, <<"vhost1">>},
 | 
				
			||||||
        {<<"O=client,CN=dummy2">>, <<"vhost2">>}
 | 
					        {<<"O=client,CN=dummy2">>, <<"vhost2">>}
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    <<"vhost1">> = rabbit_mqtt_processor:get_vhost_from_user_mapping(
 | 
					    <<"vhost1">> = rabbit_mqtt_processor:get_vhost_from_user_mapping(<<"O=client,CN=dummy1">>, MappingParameter),
 | 
				
			||||||
        <<"O=client,CN=dummy1">>, MappingParameter
 | 
					    <<"vhost2">> = rabbit_mqtt_processor:get_vhost_from_user_mapping(<<"O=client,CN=dummy2">>, MappingParameter),
 | 
				
			||||||
    ),
 | 
					    undefined    = rabbit_mqtt_processor:get_vhost_from_user_mapping(<<"O=client,CN=dummy3">>, MappingParameter),
 | 
				
			||||||
    <<"vhost2">> = rabbit_mqtt_processor:get_vhost_from_user_mapping(
 | 
					    undefined    = rabbit_mqtt_processor:get_vhost_from_user_mapping(<<"O=client,CN=dummy3">>, not_found).
 | 
				
			||||||
        <<"O=client,CN=dummy2">>, MappingParameter
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    undefined = rabbit_mqtt_processor:get_vhost_from_user_mapping(
 | 
					 | 
				
			||||||
        <<"O=client,CN=dummy3">>, MappingParameter
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    undefined = rabbit_mqtt_processor:get_vhost_from_user_mapping(
 | 
					 | 
				
			||||||
        <<"O=client,CN=dummy3">>, not_found
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
get_vhost(_Config) ->
 | 
					get_vhost(_Config) ->
 | 
				
			||||||
    clear_vhost_global_parameters(),
 | 
					    clear_vhost_global_parameters(),
 | 
				
			||||||
| 
						 | 
					@ -97,35 +83,27 @@ get_vhost(_Config) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% not a certificate user, no cert/vhost mapping, vhost in user
 | 
					    %% not a certificate user, no cert/vhost mapping, vhost in user
 | 
				
			||||||
    %% should use vhost in user
 | 
					    %% should use vhost in user
 | 
				
			||||||
    {_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
 | 
					    {_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"somevhost:guest">>, none, 1883),
 | 
				
			||||||
        <<"somevhost:guest">>, none, 1883
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    clear_vhost_global_parameters(),
 | 
					    clear_vhost_global_parameters(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% certificate user, no cert/vhost mapping
 | 
					    %% certificate user, no cert/vhost mapping
 | 
				
			||||||
    %% should use default vhost
 | 
					    %% should use default vhost
 | 
				
			||||||
    {_, {<<"/">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
 | 
					    {_, {<<"/">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
 | 
				
			||||||
        <<"guest">>, <<"O=client,CN=dummy">>, 1883
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    clear_vhost_global_parameters(),
 | 
					    clear_vhost_global_parameters(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% certificate user, cert/vhost mapping with global runtime parameter
 | 
					    %% certificate user, cert/vhost mapping with global runtime parameter
 | 
				
			||||||
    %% should use mapping
 | 
					    %% should use mapping
 | 
				
			||||||
    set_global_parameter(mqtt_default_vhosts, [
 | 
					    set_global_parameter(mqtt_default_vhosts, [
 | 
				
			||||||
        {<<"O=client,CN=dummy">>, <<"somevhost">>},
 | 
					        {<<"O=client,CN=dummy">>,     <<"somevhost">>},
 | 
				
			||||||
        {<<"O=client,CN=otheruser">>, <<"othervhost">>}
 | 
					        {<<"O=client,CN=otheruser">>, <<"othervhost">>}
 | 
				
			||||||
    ]),
 | 
					    ]),
 | 
				
			||||||
    {_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
 | 
					    {_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
 | 
				
			||||||
        <<"guest">>, <<"O=client,CN=dummy">>, 1883
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    clear_vhost_global_parameters(),
 | 
					    clear_vhost_global_parameters(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% certificate user, cert/vhost mapping with global runtime parameter, but no key for the user
 | 
					    %% certificate user, cert/vhost mapping with global runtime parameter, but no key for the user
 | 
				
			||||||
    %% should use default vhost
 | 
					    %% should use default vhost
 | 
				
			||||||
    set_global_parameter(mqtt_default_vhosts, [{<<"O=client,CN=otheruser">>, <<"somevhost">>}]),
 | 
					    set_global_parameter(mqtt_default_vhosts, [{<<"O=client,CN=otheruser">>, <<"somevhost">>}]),
 | 
				
			||||||
    {_, {<<"/">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
 | 
					    {_, {<<"/">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
 | 
				
			||||||
        <<"guest">>, <<"O=client,CN=dummy">>, 1883
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    clear_vhost_global_parameters(),
 | 
					    clear_vhost_global_parameters(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% not a certificate user, port/vhost mapping
 | 
					    %% not a certificate user, port/vhost mapping
 | 
				
			||||||
| 
						 | 
					@ -143,9 +121,7 @@ get_vhost(_Config) ->
 | 
				
			||||||
        {<<"1883">>, <<"somevhost">>},
 | 
					        {<<"1883">>, <<"somevhost">>},
 | 
				
			||||||
        {<<"1884">>, <<"othervhost">>}
 | 
					        {<<"1884">>, <<"othervhost">>}
 | 
				
			||||||
    ]),
 | 
					    ]),
 | 
				
			||||||
    {_, {<<"vhostinusername">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
 | 
					    {_, {<<"vhostinusername">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"vhostinusername:guest">>, none, 1883),
 | 
				
			||||||
        <<"vhostinusername:guest">>, none, 1883
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    clear_vhost_global_parameters(),
 | 
					    clear_vhost_global_parameters(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% not a certificate user, port/vhost mapping, but no mapping for this port
 | 
					    %% not a certificate user, port/vhost mapping, but no mapping for this port
 | 
				
			||||||
| 
						 | 
					@ -162,50 +138,42 @@ get_vhost(_Config) ->
 | 
				
			||||||
        {<<"1883">>, <<"somevhost">>},
 | 
					        {<<"1883">>, <<"somevhost">>},
 | 
				
			||||||
        {<<"1884">>, <<"othervhost">>}
 | 
					        {<<"1884">>, <<"othervhost">>}
 | 
				
			||||||
    ]),
 | 
					    ]),
 | 
				
			||||||
    {_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
 | 
					    {_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
 | 
				
			||||||
        <<"guest">>, <<"O=client,CN=dummy">>, 1883
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    clear_vhost_global_parameters(),
 | 
					    clear_vhost_global_parameters(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% certificate user, port/vhost parameter but no mapping, cert/vhost mapping
 | 
					    %% certificate user, port/vhost parameter but no mapping, cert/vhost mapping
 | 
				
			||||||
    %% should use cert/vhost mapping
 | 
					    %% should use cert/vhost mapping
 | 
				
			||||||
    set_global_parameter(mqtt_default_vhosts, [
 | 
					    set_global_parameter(mqtt_default_vhosts, [
 | 
				
			||||||
        {<<"O=client,CN=dummy">>, <<"somevhost">>},
 | 
					        {<<"O=client,CN=dummy">>,     <<"somevhost">>},
 | 
				
			||||||
        {<<"O=client,CN=otheruser">>, <<"othervhost">>}
 | 
					        {<<"O=client,CN=otheruser">>, <<"othervhost">>}
 | 
				
			||||||
    ]),
 | 
					    ]),
 | 
				
			||||||
    set_global_parameter(mqtt_port_to_vhost_mapping, [
 | 
					    set_global_parameter(mqtt_port_to_vhost_mapping, [
 | 
				
			||||||
        {<<"1884">>, <<"othervhost">>}
 | 
					        {<<"1884">>, <<"othervhost">>}
 | 
				
			||||||
    ]),
 | 
					    ]),
 | 
				
			||||||
    {_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
 | 
					    {_, {<<"somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
 | 
				
			||||||
        <<"guest">>, <<"O=client,CN=dummy">>, 1883
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    clear_vhost_global_parameters(),
 | 
					    clear_vhost_global_parameters(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% certificate user, port/vhost parameter, cert/vhost parameter
 | 
					    %% certificate user, port/vhost parameter, cert/vhost parameter
 | 
				
			||||||
    %% cert/vhost parameter takes precedence
 | 
					    %% cert/vhost parameter takes precedence
 | 
				
			||||||
    set_global_parameter(mqtt_default_vhosts, [
 | 
					    set_global_parameter(mqtt_default_vhosts, [
 | 
				
			||||||
        {<<"O=client,CN=dummy">>, <<"cert-somevhost">>},
 | 
					        {<<"O=client,CN=dummy">>,     <<"cert-somevhost">>},
 | 
				
			||||||
        {<<"O=client,CN=otheruser">>, <<"othervhost">>}
 | 
					        {<<"O=client,CN=otheruser">>, <<"othervhost">>}
 | 
				
			||||||
    ]),
 | 
					    ]),
 | 
				
			||||||
    set_global_parameter(mqtt_port_to_vhost_mapping, [
 | 
					    set_global_parameter(mqtt_port_to_vhost_mapping, [
 | 
				
			||||||
        {<<"1883">>, <<"port-vhost">>},
 | 
					        {<<"1883">>, <<"port-vhost">>},
 | 
				
			||||||
        {<<"1884">>, <<"othervhost">>}
 | 
					        {<<"1884">>, <<"othervhost">>}
 | 
				
			||||||
    ]),
 | 
					    ]),
 | 
				
			||||||
    {_, {<<"cert-somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
 | 
					    {_, {<<"cert-somevhost">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"guest">>, <<"O=client,CN=dummy">>, 1883),
 | 
				
			||||||
        <<"guest">>, <<"O=client,CN=dummy">>, 1883
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    clear_vhost_global_parameters(),
 | 
					    clear_vhost_global_parameters(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% certificate user, no port/vhost or cert/vhost mapping, vhost in username
 | 
					    %% certificate user, no port/vhost or cert/vhost mapping, vhost in username
 | 
				
			||||||
    %% should use vhost in username
 | 
					    %% should use vhost in username
 | 
				
			||||||
    {_, {<<"vhostinusername">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(
 | 
					    {_, {<<"vhostinusername">>, <<"guest">>}} = rabbit_mqtt_processor:get_vhost(<<"vhostinusername:guest">>, <<"O=client,CN=dummy">>, 1883),
 | 
				
			||||||
        <<"vhostinusername:guest">>, <<"O=client,CN=dummy">>, 1883
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% not a certificate user, port/vhost parameter, cert/vhost parameter
 | 
					    %% not a certificate user, port/vhost parameter, cert/vhost parameter
 | 
				
			||||||
    %% port/vhost mapping is used, as cert/vhost should not be used
 | 
					    %% port/vhost mapping is used, as cert/vhost should not be used
 | 
				
			||||||
    set_global_parameter(mqtt_default_vhosts, [
 | 
					    set_global_parameter(mqtt_default_vhosts, [
 | 
				
			||||||
        {<<"O=cert">>, <<"cert-somevhost">>},
 | 
					        {<<"O=cert">>,                <<"cert-somevhost">>},
 | 
				
			||||||
        {<<"O=client,CN=otheruser">>, <<"othervhost">>}
 | 
					        {<<"O=client,CN=otheruser">>, <<"othervhost">>}
 | 
				
			||||||
    ]),
 | 
					    ]),
 | 
				
			||||||
    set_global_parameter(mqtt_port_to_vhost_mapping, [
 | 
					    set_global_parameter(mqtt_port_to_vhost_mapping, [
 | 
				
			||||||
| 
						 | 
					@ -217,15 +185,15 @@ get_vhost(_Config) ->
 | 
				
			||||||
    ok.
 | 
					    ok.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set_global_parameter(Key, Term) ->
 | 
					set_global_parameter(Key, Term) ->
 | 
				
			||||||
    InsertParameterFun = fun() ->
 | 
					    InsertParameterFun = fun () ->
 | 
				
			||||||
        mnesia:write(rabbit_runtime_parameters, #runtime_parameters{key = Key, value = Term}, write)
 | 
					        mnesia:write(rabbit_runtime_parameters, #runtime_parameters{key = Key, value = Term}, write)
 | 
				
			||||||
    end,
 | 
					                         end,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    {atomic, ok} = mnesia:transaction(InsertParameterFun).
 | 
					    {atomic, ok} = mnesia:transaction(InsertParameterFun).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
clear_vhost_global_parameters() ->
 | 
					clear_vhost_global_parameters() ->
 | 
				
			||||||
    DeleteParameterFun = fun() ->
 | 
					    DeleteParameterFun = fun () ->
 | 
				
			||||||
        ok = mnesia:delete(rabbit_runtime_parameters, mqtt_default_vhosts, write),
 | 
					        ok = mnesia:delete(rabbit_runtime_parameters, mqtt_default_vhosts, write),
 | 
				
			||||||
        ok = mnesia:delete(rabbit_runtime_parameters, mqtt_port_to_vhost_mapping, write)
 | 
					        ok = mnesia:delete(rabbit_runtime_parameters, mqtt_port_to_vhost_mapping, write)
 | 
				
			||||||
    end,
 | 
					                         end,
 | 
				
			||||||
    {atomic, ok} = mnesia:transaction(DeleteParameterFun).
 | 
					    {atomic, ok} = mnesia:transaction(DeleteParameterFun).
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,26 +34,21 @@ init_per_suite(Config) ->
 | 
				
			||||||
        {rabbitmq_ct_tls_verify, verify_none}
 | 
					        {rabbitmq_ct_tls_verify, verify_none}
 | 
				
			||||||
    ]),
 | 
					    ]),
 | 
				
			||||||
    MqttConfig = mqtt_config(),
 | 
					    MqttConfig = mqtt_config(),
 | 
				
			||||||
    rabbit_ct_helpers:run_setup_steps(
 | 
					    rabbit_ct_helpers:run_setup_steps(Config1,
 | 
				
			||||||
        Config1,
 | 
					        [ fun(Conf) -> merge_app_env(MqttConfig, Conf) end ] ++
 | 
				
			||||||
        [fun(Conf) -> merge_app_env(MqttConfig, Conf) end] ++
 | 
					 | 
				
			||||||
            rabbit_ct_broker_helpers:setup_steps() ++
 | 
					            rabbit_ct_broker_helpers:setup_steps() ++
 | 
				
			||||||
            rabbit_ct_client_helpers:setup_steps()
 | 
					            rabbit_ct_client_helpers:setup_steps()).
 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
mqtt_config() ->
 | 
					mqtt_config() ->
 | 
				
			||||||
    {rabbitmq_mqtt, [
 | 
					    {rabbitmq_mqtt, [
 | 
				
			||||||
        {proxy_protocol, true},
 | 
					        {proxy_protocol,  true},
 | 
				
			||||||
        {ssl_cert_login, true},
 | 
					        {ssl_cert_login,  true},
 | 
				
			||||||
        {allow_anonymous, true}
 | 
					        {allow_anonymous, true}]}.
 | 
				
			||||||
    ]}.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
end_per_suite(Config) ->
 | 
					end_per_suite(Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:run_teardown_steps(
 | 
					    rabbit_ct_helpers:run_teardown_steps(Config,
 | 
				
			||||||
        Config,
 | 
					 | 
				
			||||||
        rabbit_ct_client_helpers:teardown_steps() ++
 | 
					        rabbit_ct_client_helpers:teardown_steps() ++
 | 
				
			||||||
            rabbit_ct_broker_helpers:teardown_steps()
 | 
					        rabbit_ct_broker_helpers:teardown_steps()).
 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
init_per_group(_, Config) -> Config.
 | 
					init_per_group(_, Config) -> Config.
 | 
				
			||||||
end_per_group(_, Config) -> Config.
 | 
					end_per_group(_, Config) -> Config.
 | 
				
			||||||
| 
						 | 
					@ -66,11 +61,8 @@ end_per_testcase(Testcase, Config) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
proxy_protocol(Config) ->
 | 
					proxy_protocol(Config) ->
 | 
				
			||||||
    Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt),
 | 
					    Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt),
 | 
				
			||||||
    {ok, Socket} = gen_tcp:connect(
 | 
					    {ok, Socket} = gen_tcp:connect({127,0,0,1}, Port,
 | 
				
			||||||
        {127, 0, 0, 1},
 | 
					        [binary, {active, false}, {packet, raw}]),
 | 
				
			||||||
        Port,
 | 
					 | 
				
			||||||
        [binary, {active, false}, {packet, raw}]
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    ok = inet:send(Socket, "PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"),
 | 
					    ok = inet:send(Socket, "PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"),
 | 
				
			||||||
    ok = inet:send(Socket, mqtt_3_1_1_connect_packet()),
 | 
					    ok = inet:send(Socket, mqtt_3_1_1_connect_packet()),
 | 
				
			||||||
    {ok, _Packet} = gen_tcp:recv(Socket, 0, ?TIMEOUT),
 | 
					    {ok, _Packet} = gen_tcp:recv(Socket, 0, ?TIMEOUT),
 | 
				
			||||||
| 
						 | 
					@ -83,11 +75,8 @@ proxy_protocol(Config) ->
 | 
				
			||||||
proxy_protocol_tls(Config) ->
 | 
					proxy_protocol_tls(Config) ->
 | 
				
			||||||
    app_utils:start_applications([asn1, crypto, public_key, ssl]),
 | 
					    app_utils:start_applications([asn1, crypto, public_key, ssl]),
 | 
				
			||||||
    Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt_tls),
 | 
					    Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt_tls),
 | 
				
			||||||
    {ok, Socket} = gen_tcp:connect(
 | 
					    {ok, Socket} = gen_tcp:connect({127,0,0,1}, Port,
 | 
				
			||||||
        {127, 0, 0, 1},
 | 
					        [binary, {active, false}, {packet, raw}]),
 | 
				
			||||||
        Port,
 | 
					 | 
				
			||||||
        [binary, {active, false}, {packet, raw}]
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    ok = inet:send(Socket, "PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"),
 | 
					    ok = inet:send(Socket, "PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"),
 | 
				
			||||||
    {ok, SslSocket} = ssl:connect(Socket, [], ?TIMEOUT),
 | 
					    {ok, SslSocket} = ssl:connect(Socket, [], ?TIMEOUT),
 | 
				
			||||||
    ok = ssl:send(SslSocket, mqtt_3_1_1_connect_packet()),
 | 
					    ok = ssl:send(SslSocket, mqtt_3_1_1_connect_packet()),
 | 
				
			||||||
| 
						 | 
					@ -107,5 +96,29 @@ merge_app_env(MqttConfig, Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:merge_app_env(Config, MqttConfig).
 | 
					    rabbit_ct_helpers:merge_app_env(Config, MqttConfig).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mqtt_3_1_1_connect_packet() ->
 | 
					mqtt_3_1_1_connect_packet() ->
 | 
				
			||||||
    <<16, 24, 0, 4, 77, 81, 84, 84, 4, 2, 0, 60, 0, 12, 84, 101, 115, 116, 67, 111, 110, 115, 117,
 | 
					    <<16,
 | 
				
			||||||
        109, 101, 114>>.
 | 
					    24,
 | 
				
			||||||
 | 
					    0,
 | 
				
			||||||
 | 
					    4,
 | 
				
			||||||
 | 
					    77,
 | 
				
			||||||
 | 
					    81,
 | 
				
			||||||
 | 
					    84,
 | 
				
			||||||
 | 
					    84,
 | 
				
			||||||
 | 
					    4,
 | 
				
			||||||
 | 
					    2,
 | 
				
			||||||
 | 
					    0,
 | 
				
			||||||
 | 
					    60,
 | 
				
			||||||
 | 
					    0,
 | 
				
			||||||
 | 
					    12,
 | 
				
			||||||
 | 
					    84,
 | 
				
			||||||
 | 
					    101,
 | 
				
			||||||
 | 
					    115,
 | 
				
			||||||
 | 
					    116,
 | 
				
			||||||
 | 
					    67,
 | 
				
			||||||
 | 
					    111,
 | 
				
			||||||
 | 
					    110,
 | 
				
			||||||
 | 
					    115,
 | 
				
			||||||
 | 
					    117,
 | 
				
			||||||
 | 
					    109,
 | 
				
			||||||
 | 
					    101,
 | 
				
			||||||
 | 
					    114>>.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,16 +13,11 @@
 | 
				
			||||||
-behaviour(rabbit_authn_backend).
 | 
					-behaviour(rabbit_authn_backend).
 | 
				
			||||||
-behaviour(rabbit_authz_backend).
 | 
					-behaviour(rabbit_authz_backend).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-export([
 | 
					-export([setup/1,
 | 
				
			||||||
    setup/1,
 | 
					         user_login_authentication/2, user_login_authorization/2,
 | 
				
			||||||
    user_login_authentication/2,
 | 
					         check_vhost_access/3, check_resource_access/4, check_topic_access/4,
 | 
				
			||||||
    user_login_authorization/2,
 | 
					         state_can_expire/0,
 | 
				
			||||||
    check_vhost_access/3,
 | 
					         get/1]).
 | 
				
			||||||
    check_resource_access/4,
 | 
					 | 
				
			||||||
    check_topic_access/4,
 | 
					 | 
				
			||||||
    state_can_expire/0,
 | 
					 | 
				
			||||||
    get/1
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
setup(CallerPid) ->
 | 
					setup(CallerPid) ->
 | 
				
			||||||
    ets:new(?MODULE, [set, public, named_table]),
 | 
					    ets:new(?MODULE, [set, public, named_table]),
 | 
				
			||||||
| 
						 | 
					@ -31,13 +26,12 @@ setup(CallerPid) ->
 | 
				
			||||||
        stop -> ok
 | 
					        stop -> ok
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
user_login_authentication(_, AuthProps) ->
 | 
					user_login_authentication(_, AuthProps) ->
 | 
				
			||||||
    ets:insert(?MODULE, {authentication, AuthProps}),
 | 
					    ets:insert(?MODULE, {authentication, AuthProps}),
 | 
				
			||||||
    {ok, #auth_user{
 | 
					    {ok, #auth_user{username = <<"dummy">>,
 | 
				
			||||||
        username = <<"dummy">>,
 | 
					                    tags     = [],
 | 
				
			||||||
        tags = [],
 | 
					                    impl     = none}}.
 | 
				
			||||||
        impl = none
 | 
					 | 
				
			||||||
    }}.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
user_login_authorization(_, _) ->
 | 
					user_login_authorization(_, _) ->
 | 
				
			||||||
    io:format("login authorization"),
 | 
					    io:format("login authorization"),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,45 +5,42 @@
 | 
				
			||||||
%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates.  All rights reserved.
 | 
					%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates.  All rights reserved.
 | 
				
			||||||
%%
 | 
					%%
 | 
				
			||||||
-module(reader_SUITE).
 | 
					-module(reader_SUITE).
 | 
				
			||||||
-compile([
 | 
					-compile([export_all,
 | 
				
			||||||
    export_all,
 | 
					          nowarn_export_all]).
 | 
				
			||||||
    nowarn_export_all
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-include_lib("common_test/include/ct.hrl").
 | 
					-include_lib("common_test/include/ct.hrl").
 | 
				
			||||||
-include_lib("eunit/include/eunit.hrl").
 | 
					-include_lib("eunit/include/eunit.hrl").
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-import(rabbit_ct_broker_helpers, [rpc/4]).
 | 
					-import(rabbit_ct_broker_helpers, [rpc/4]).
 | 
				
			||||||
-import(rabbit_ct_helpers, [eventually/3]).
 | 
					-import(rabbit_ct_helpers, [eventually/3]).
 | 
				
			||||||
-import(util, [
 | 
					-import(util, [all_connection_pids/1,
 | 
				
			||||||
    all_connection_pids/1,
 | 
					               publish_qos1_timeout/4,
 | 
				
			||||||
    publish_qos1_timeout/4,
 | 
					               expect_publishes/3,
 | 
				
			||||||
    expect_publishes/3,
 | 
					               connect/2,
 | 
				
			||||||
    connect/2,
 | 
					               connect/3,
 | 
				
			||||||
    connect/3,
 | 
					               await_exit/1]).
 | 
				
			||||||
    await_exit/1
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
all() ->
 | 
					all() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {group, tests}
 | 
					     {group, tests}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
groups() ->
 | 
					groups() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {tests, [], [
 | 
					     {tests, [],
 | 
				
			||||||
            block_connack_timeout,
 | 
					      [
 | 
				
			||||||
            handle_invalid_packets,
 | 
					       block_connack_timeout,
 | 
				
			||||||
            login_timeout,
 | 
					       handle_invalid_packets,
 | 
				
			||||||
            stats,
 | 
					       login_timeout,
 | 
				
			||||||
            quorum_clean_session_false,
 | 
					       stats,
 | 
				
			||||||
            quorum_clean_session_true,
 | 
					       quorum_clean_session_false,
 | 
				
			||||||
            classic_clean_session_true,
 | 
					       quorum_clean_session_true,
 | 
				
			||||||
            classic_clean_session_false,
 | 
					       classic_clean_session_true,
 | 
				
			||||||
            non_clean_sess_empty_client_id,
 | 
					       classic_clean_session_false,
 | 
				
			||||||
            event_authentication_failure,
 | 
					       non_clean_sess_empty_client_id,
 | 
				
			||||||
            rabbit_mqtt_qos0_queue_overflow
 | 
					       event_authentication_failure,
 | 
				
			||||||
        ]}
 | 
					       rabbit_mqtt_qos0_queue_overflow
 | 
				
			||||||
 | 
					      ]}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
suite() ->
 | 
					suite() ->
 | 
				
			||||||
| 
						 | 
					@ -54,36 +51,28 @@ suite() ->
 | 
				
			||||||
%% -------------------------------------------------------------------
 | 
					%% -------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
merge_app_env(Config) ->
 | 
					merge_app_env(Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:merge_app_env(
 | 
					    rabbit_ct_helpers:merge_app_env(Config,
 | 
				
			||||||
        Config,
 | 
					                                    {rabbit, [
 | 
				
			||||||
        {rabbit, [
 | 
					                                              {collect_statistics, basic},
 | 
				
			||||||
            {collect_statistics, basic},
 | 
					                                              {collect_statistics_interval, 100}
 | 
				
			||||||
            {collect_statistics_interval, 100}
 | 
					                                             ]}).
 | 
				
			||||||
        ]}
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
init_per_suite(Config) ->
 | 
					init_per_suite(Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:log_environment(),
 | 
					    rabbit_ct_helpers:log_environment(),
 | 
				
			||||||
    Config1 = rabbit_ct_helpers:set_config(Config, [
 | 
					    Config1 = rabbit_ct_helpers:set_config(Config, [
 | 
				
			||||||
        {rmq_nodename_suffix, ?MODULE},
 | 
					        {rmq_nodename_suffix, ?MODULE},
 | 
				
			||||||
        {rmq_extra_tcp_ports, [
 | 
					        {rmq_extra_tcp_ports, [tcp_port_mqtt_extra,
 | 
				
			||||||
            tcp_port_mqtt_extra,
 | 
					                               tcp_port_mqtt_tls_extra]}
 | 
				
			||||||
            tcp_port_mqtt_tls_extra
 | 
					      ]),
 | 
				
			||||||
        ]}
 | 
					    rabbit_ct_helpers:run_setup_steps(Config1,
 | 
				
			||||||
    ]),
 | 
					      [ fun merge_app_env/1 ] ++
 | 
				
			||||||
    rabbit_ct_helpers:run_setup_steps(
 | 
					      rabbit_ct_broker_helpers:setup_steps() ++
 | 
				
			||||||
        Config1,
 | 
					      rabbit_ct_client_helpers:setup_steps()).
 | 
				
			||||||
        [fun merge_app_env/1] ++
 | 
					 | 
				
			||||||
            rabbit_ct_broker_helpers:setup_steps() ++
 | 
					 | 
				
			||||||
            rabbit_ct_client_helpers:setup_steps()
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
end_per_suite(Config) ->
 | 
					end_per_suite(Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:run_teardown_steps(
 | 
					    rabbit_ct_helpers:run_teardown_steps(Config,
 | 
				
			||||||
        Config,
 | 
					      rabbit_ct_client_helpers:teardown_steps() ++
 | 
				
			||||||
        rabbit_ct_client_helpers:teardown_steps() ++
 | 
					      rabbit_ct_broker_helpers:teardown_steps()).
 | 
				
			||||||
            rabbit_ct_broker_helpers:teardown_steps()
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
init_per_group(_, Config) ->
 | 
					init_per_group(_, Config) ->
 | 
				
			||||||
    Config.
 | 
					    Config.
 | 
				
			||||||
| 
						 | 
					@ -97,6 +86,7 @@ init_per_testcase(Testcase, Config) ->
 | 
				
			||||||
end_per_testcase(Testcase, Config) ->
 | 
					end_per_testcase(Testcase, Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:testcase_finished(Config, Testcase).
 | 
					    rabbit_ct_helpers:testcase_finished(Config, Testcase).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
%% -------------------------------------------------------------------
 | 
					%% -------------------------------------------------------------------
 | 
				
			||||||
%% Testsuite cases
 | 
					%% Testsuite cases
 | 
				
			||||||
%% -------------------------------------------------------------------
 | 
					%% -------------------------------------------------------------------
 | 
				
			||||||
| 
						 | 
					@ -110,13 +100,11 @@ block_connack_timeout(Config) ->
 | 
				
			||||||
    timer:sleep(100),
 | 
					    timer:sleep(100),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% We can still connect via TCP, but CONNECT packet will not be processed on the server.
 | 
					    %% We can still connect via TCP, but CONNECT packet will not be processed on the server.
 | 
				
			||||||
    {ok, Client} = emqtt:start_link([
 | 
					    {ok, Client} = emqtt:start_link([{host, "localhost"},
 | 
				
			||||||
        {host, "localhost"},
 | 
					                                     {port, P},
 | 
				
			||||||
        {port, P},
 | 
					                                     {clientid, atom_to_binary(?FUNCTION_NAME)},
 | 
				
			||||||
        {clientid, atom_to_binary(?FUNCTION_NAME)},
 | 
					                                     {proto_ver, v4},
 | 
				
			||||||
        {proto_ver, v4},
 | 
					                                     {connect_timeout, 1}]),
 | 
				
			||||||
        {connect_timeout, 1}
 | 
					 | 
				
			||||||
    ]),
 | 
					 | 
				
			||||||
    unlink(Client),
 | 
					    unlink(Client),
 | 
				
			||||||
    ClientMRef = monitor(process, Client),
 | 
					    ClientMRef = monitor(process, Client),
 | 
				
			||||||
    {error, connack_timeout} = emqtt:connect(Client),
 | 
					    {error, connack_timeout} = emqtt:connect(Client),
 | 
				
			||||||
| 
						 | 
					@ -124,7 +112,7 @@ block_connack_timeout(Config) ->
 | 
				
			||||||
        {'DOWN', ClientMRef, process, Client, connack_timeout} ->
 | 
					        {'DOWN', ClientMRef, process, Client, connack_timeout} ->
 | 
				
			||||||
            ok
 | 
					            ok
 | 
				
			||||||
    after 200 ->
 | 
					    after 200 ->
 | 
				
			||||||
        ct:fail("missing connack_timeout in client")
 | 
					              ct:fail("missing connack_timeout in client")
 | 
				
			||||||
    end,
 | 
					    end,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ports = rpc(Config, erlang, ports, []),
 | 
					    Ports = rpc(Config, erlang, ports, []),
 | 
				
			||||||
| 
						 | 
					@ -142,7 +130,7 @@ block_connack_timeout(Config) ->
 | 
				
			||||||
            %% because our client already disconnected.
 | 
					            %% because our client already disconnected.
 | 
				
			||||||
            ok
 | 
					            ok
 | 
				
			||||||
    after 2000 ->
 | 
					    after 2000 ->
 | 
				
			||||||
        ct:fail("missing peername_not_known from server")
 | 
					              ct:fail("missing peername_not_known from server")
 | 
				
			||||||
    end,
 | 
					    end,
 | 
				
			||||||
    %% Ensure that our client is not registered.
 | 
					    %% Ensure that our client is not registered.
 | 
				
			||||||
    ?assertEqual([], all_connection_pids(Config)),
 | 
					    ?assertEqual([], all_connection_pids(Config)),
 | 
				
			||||||
| 
						 | 
					@ -182,12 +170,8 @@ stats(Config) ->
 | 
				
			||||||
    [{Pid, Props}] = rpc(Config, ets, lookup, [connection_metrics, Pid]),
 | 
					    [{Pid, Props}] = rpc(Config, ets, lookup, [connection_metrics, Pid]),
 | 
				
			||||||
    true = proplists:is_defined(garbage_collection, Props),
 | 
					    true = proplists:is_defined(garbage_collection, Props),
 | 
				
			||||||
    %% If the coarse entry is present, stats were successfully emitted
 | 
					    %% If the coarse entry is present, stats were successfully emitted
 | 
				
			||||||
    [{Pid, _, _, _, _}] = rpc(
 | 
					    [{Pid, _, _, _, _}] = rpc(Config, ets, lookup,
 | 
				
			||||||
        Config,
 | 
					                              [connection_coarse_metrics, Pid]),
 | 
				
			||||||
        ets,
 | 
					 | 
				
			||||||
        lookup,
 | 
					 | 
				
			||||||
        [connection_coarse_metrics, Pid]
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    ok = emqtt:disconnect(C).
 | 
					    ok = emqtt:disconnect(C).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
get_durable_queue_type(Server, QNameBin) ->
 | 
					get_durable_queue_type(Server, QNameBin) ->
 | 
				
			||||||
| 
						 | 
					@ -232,41 +216,32 @@ classic_clean_session_true(Config) ->
 | 
				
			||||||
    validate_durable_queue_type(Config, <<"classicCleanSessionTrue">>, true, rabbit_classic_queue).
 | 
					    validate_durable_queue_type(Config, <<"classicCleanSessionTrue">>, true, rabbit_classic_queue).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
classic_clean_session_false(Config) ->
 | 
					classic_clean_session_false(Config) ->
 | 
				
			||||||
    validate_durable_queue_type(
 | 
					    validate_durable_queue_type(Config, <<"classicCleanSessionFalse">>, false, rabbit_classic_queue).
 | 
				
			||||||
        Config, <<"classicCleanSessionFalse">>, false, rabbit_classic_queue
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
%% "If the Client supplies a zero-byte ClientId with CleanSession set to 0,
 | 
					%% "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
 | 
					%% 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].
 | 
					%% (Identifier rejected) and then close the Network Connection" [MQTT-3.1.3-8].
 | 
				
			||||||
non_clean_sess_empty_client_id(Config) ->
 | 
					non_clean_sess_empty_client_id(Config) ->
 | 
				
			||||||
    {ok, C} = emqtt:start_link(
 | 
					    {ok, C} = emqtt:start_link(
 | 
				
			||||||
        [
 | 
					                [{clientid, <<>>},
 | 
				
			||||||
            {clientid, <<>>},
 | 
					                 {clean_start, false},
 | 
				
			||||||
            {clean_start, false},
 | 
					                 {proto_ver, v4},
 | 
				
			||||||
            {proto_ver, v4},
 | 
					                 {host, "localhost"},
 | 
				
			||||||
            {host, "localhost"},
 | 
					                 {port, rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt)}
 | 
				
			||||||
            {port, rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt)}
 | 
					                ]),
 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    process_flag(trap_exit, true),
 | 
					    process_flag(trap_exit, true),
 | 
				
			||||||
    ?assertMatch(
 | 
					    ?assertMatch({error, {client_identifier_not_valid, _}},
 | 
				
			||||||
        {error, {client_identifier_not_valid, _}},
 | 
					                 emqtt:connect(C)),
 | 
				
			||||||
        emqtt:connect(C)
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    ok = await_exit(C).
 | 
					    ok = await_exit(C).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
event_authentication_failure(Config) ->
 | 
					event_authentication_failure(Config) ->
 | 
				
			||||||
    {ok, C} = emqtt:start_link(
 | 
					    {ok, C} = emqtt:start_link(
 | 
				
			||||||
        [
 | 
					                [{username, <<"Trudy">>},
 | 
				
			||||||
            {username, <<"Trudy">>},
 | 
					                 {password, <<"fake-password">>},
 | 
				
			||||||
            {password, <<"fake-password">>},
 | 
					                 {host, "localhost"},
 | 
				
			||||||
            {host, "localhost"},
 | 
					                 {port, rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt)},
 | 
				
			||||||
            {port, rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_mqtt)},
 | 
					                 {clientid, atom_to_binary(?FUNCTION_NAME)},
 | 
				
			||||||
            {clientid, atom_to_binary(?FUNCTION_NAME)},
 | 
					                 {proto_ver, v4}]),
 | 
				
			||||||
            {proto_ver, v4}
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    true = unlink(C),
 | 
					    true = unlink(C),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ok = rabbit_ct_broker_helpers:add_code_path_to_all_nodes(Config, event_recorder),
 | 
					    ok = rabbit_ct_broker_helpers:add_code_path_to_all_nodes(Config, event_recorder),
 | 
				
			||||||
| 
						 | 
					@ -277,13 +252,9 @@ event_authentication_failure(Config) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [E, _ConnectionClosedEvent] = util:get_events(Server),
 | 
					    [E, _ConnectionClosedEvent] = util:get_events(Server),
 | 
				
			||||||
    util:assert_event_type(user_authentication_failure, E),
 | 
					    util:assert_event_type(user_authentication_failure, E),
 | 
				
			||||||
    util:assert_event_prop(
 | 
					    util:assert_event_prop([{name, <<"Trudy">>},
 | 
				
			||||||
        [
 | 
					                            {connection_type, network}],
 | 
				
			||||||
            {name, <<"Trudy">>},
 | 
					                           E),
 | 
				
			||||||
            {connection_type, network}
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        E
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ok = gen_event:delete_handler({rabbit_event, Server}, event_recorder, []).
 | 
					    ok = gen_event:delete_handler({rabbit_event, Server}, event_recorder, []).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -295,12 +266,8 @@ rabbit_mqtt_qos0_queue_overflow(Config) ->
 | 
				
			||||||
    NumMsgs = 10_000,
 | 
					    NumMsgs = 10_000,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% Provoke TCP back-pressure from client to server by using very small buffers.
 | 
					    %% Provoke TCP back-pressure from client to server by using very small buffers.
 | 
				
			||||||
    Opts = [
 | 
					    Opts = [{tcp_opts, [{recbuf, 512},
 | 
				
			||||||
        {tcp_opts, [
 | 
					                        {buffer, 512}]}],
 | 
				
			||||||
            {recbuf, 512},
 | 
					 | 
				
			||||||
            {buffer, 512}
 | 
					 | 
				
			||||||
        ]}
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    Sub = connect(<<"subscriber">>, Config, Opts),
 | 
					    Sub = connect(<<"subscriber">>, Config, Opts),
 | 
				
			||||||
    {ok, _, [0]} = emqtt:subscribe(Sub, Topic, qos0),
 | 
					    {ok, _, [0]} = emqtt:subscribe(Sub, Topic, qos0),
 | 
				
			||||||
    [ServerConnectionPid] = all_connection_pids(Config),
 | 
					    [ServerConnectionPid] = all_connection_pids(Config),
 | 
				
			||||||
| 
						 | 
					@ -312,12 +279,9 @@ rabbit_mqtt_qos0_queue_overflow(Config) ->
 | 
				
			||||||
    %% Let's overflow the receiving server MQTT connection process
 | 
					    %% Let's overflow the receiving server MQTT connection process
 | 
				
			||||||
    %% (i.e. the rabbit_mqtt_qos0_queue) by sending many large messages.
 | 
					    %% (i.e. the rabbit_mqtt_qos0_queue) by sending many large messages.
 | 
				
			||||||
    Pub = connect(<<"publisher">>, Config),
 | 
					    Pub = connect(<<"publisher">>, Config),
 | 
				
			||||||
    lists:foreach(
 | 
					    lists:foreach(fun(_) ->
 | 
				
			||||||
        fun(_) ->
 | 
					                          ok = emqtt:publish(Pub, Topic, Msg, qos0)
 | 
				
			||||||
            ok = emqtt:publish(Pub, Topic, Msg, qos0)
 | 
					                  end, lists:seq(1, NumMsgs)),
 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
        lists:seq(1, NumMsgs)
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    %% Give the server some time to process (either send or drop) the messages.
 | 
					    %% Give the server some time to process (either send or drop) the messages.
 | 
				
			||||||
    timer:sleep(2000),
 | 
					    timer:sleep(2000),
 | 
				
			||||||
| 
						 | 
					@ -354,11 +318,9 @@ rabbit_mqtt_qos0_queue_overflow(Config) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
num_received(Topic, Payload, N) ->
 | 
					num_received(Topic, Payload, N) ->
 | 
				
			||||||
    receive
 | 
					    receive
 | 
				
			||||||
        {publish, #{
 | 
					        {publish, #{topic := Topic,
 | 
				
			||||||
            topic := Topic,
 | 
					                    payload := Payload}} ->
 | 
				
			||||||
            payload := Payload
 | 
					 | 
				
			||||||
        }} ->
 | 
					 | 
				
			||||||
            num_received(Topic, Payload, N + 1)
 | 
					            num_received(Topic, Payload, N + 1)
 | 
				
			||||||
    after 1000 ->
 | 
					    after 1000 ->
 | 
				
			||||||
        N
 | 
					              N
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,31 +8,29 @@
 | 
				
			||||||
-compile([export_all, nowarn_export_all]).
 | 
					-compile([export_all, nowarn_export_all]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-include_lib("common_test/include/ct.hrl").
 | 
					-include_lib("common_test/include/ct.hrl").
 | 
				
			||||||
-import(util, [
 | 
					-import(util, [expect_publishes/3,
 | 
				
			||||||
    expect_publishes/3,
 | 
					               connect/3]).
 | 
				
			||||||
    connect/3
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
all() ->
 | 
					all() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {group, dets},
 | 
					     {group, dets},
 | 
				
			||||||
        {group, ets},
 | 
					     {group, ets},
 | 
				
			||||||
        {group, noop}
 | 
					     {group, noop}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
groups() ->
 | 
					groups() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {dets, [], tests()},
 | 
					     {dets, [], tests()},
 | 
				
			||||||
        {ets, [], tests()},
 | 
					     {ets, [], tests()},
 | 
				
			||||||
        {noop, [], [does_not_retain]}
 | 
					     {noop, [], [does_not_retain]}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
tests() ->
 | 
					tests() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        coerce_configuration_data,
 | 
					     coerce_configuration_data,
 | 
				
			||||||
        should_translate_amqp2mqtt_on_publish,
 | 
					     should_translate_amqp2mqtt_on_publish,
 | 
				
			||||||
        should_translate_amqp2mqtt_on_retention,
 | 
					     should_translate_amqp2mqtt_on_retention,
 | 
				
			||||||
        should_translate_amqp2mqtt_on_retention_search
 | 
					     should_translate_amqp2mqtt_on_retention_search
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
suite() ->
 | 
					suite() ->
 | 
				
			||||||
| 
						 | 
					@ -51,38 +49,31 @@ end_per_suite(Config) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
init_per_group(Group, Config0) ->
 | 
					init_per_group(Group, Config0) ->
 | 
				
			||||||
    Config = rabbit_ct_helpers:set_config(
 | 
					    Config = rabbit_ct_helpers:set_config(
 | 
				
			||||||
        Config0,
 | 
					               Config0,
 | 
				
			||||||
        [
 | 
					               [
 | 
				
			||||||
            {rmq_nodename_suffix, Group},
 | 
					                {rmq_nodename_suffix, Group},
 | 
				
			||||||
            {rmq_extra_tcp_ports, [
 | 
					                {rmq_extra_tcp_ports, [tcp_port_mqtt_extra,
 | 
				
			||||||
                tcp_port_mqtt_extra,
 | 
					                                       tcp_port_mqtt_tls_extra]}
 | 
				
			||||||
                tcp_port_mqtt_tls_extra
 | 
					               ]),
 | 
				
			||||||
            ]}
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    Mod = list_to_atom("rabbit_mqtt_retained_msg_store_" ++ atom_to_list(Group)),
 | 
					    Mod = list_to_atom("rabbit_mqtt_retained_msg_store_" ++ atom_to_list(Group)),
 | 
				
			||||||
    Env = [
 | 
					    Env = [{rabbitmq_mqtt, [{retained_message_store, Mod}]},
 | 
				
			||||||
        {rabbitmq_mqtt, [{retained_message_store, Mod}]},
 | 
					           {rabbit, [
 | 
				
			||||||
        {rabbit, [
 | 
					                     {default_user, "guest"},
 | 
				
			||||||
            {default_user, "guest"},
 | 
					                     {default_pass, "guest"},
 | 
				
			||||||
            {default_pass, "guest"},
 | 
					                     {default_vhost, "/"},
 | 
				
			||||||
            {default_vhost, "/"},
 | 
					                     {default_permissions, [".*", ".*", ".*"]}
 | 
				
			||||||
            {default_permissions, [".*", ".*", ".*"]}
 | 
					                    ]}],
 | 
				
			||||||
        ]}
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    rabbit_ct_helpers:run_setup_steps(
 | 
					    rabbit_ct_helpers:run_setup_steps(
 | 
				
			||||||
        Config,
 | 
					      Config,
 | 
				
			||||||
        [fun(Conf) -> rabbit_ct_helpers:merge_app_env(Conf, Env) end] ++
 | 
					      [fun(Conf) -> rabbit_ct_helpers:merge_app_env(Conf, Env) end] ++
 | 
				
			||||||
            rabbit_ct_broker_helpers:setup_steps() ++
 | 
					      rabbit_ct_broker_helpers:setup_steps() ++
 | 
				
			||||||
            rabbit_ct_client_helpers:setup_steps()
 | 
					      rabbit_ct_client_helpers:setup_steps()).
 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
end_per_group(_, Config) ->
 | 
					end_per_group(_, Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:run_teardown_steps(
 | 
					    rabbit_ct_helpers:run_teardown_steps(
 | 
				
			||||||
        Config,
 | 
					      Config,
 | 
				
			||||||
        rabbit_ct_client_helpers:teardown_steps() ++
 | 
					      rabbit_ct_client_helpers:teardown_steps() ++
 | 
				
			||||||
            rabbit_ct_broker_helpers:teardown_steps()
 | 
					      rabbit_ct_broker_helpers:teardown_steps()).
 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
init_per_testcase(Testcase, Config) ->
 | 
					init_per_testcase(Testcase, Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:testcase_started(Config, Testcase).
 | 
					    rabbit_ct_helpers:testcase_started(Config, Testcase).
 | 
				
			||||||
| 
						 | 
					@ -90,6 +81,7 @@ init_per_testcase(Testcase, Config) ->
 | 
				
			||||||
end_per_testcase(Testcase, Config) ->
 | 
					end_per_testcase(Testcase, Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:testcase_finished(Config, Testcase).
 | 
					    rabbit_ct_helpers:testcase_finished(Config, Testcase).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
%% -------------------------------------------------------------------
 | 
					%% -------------------------------------------------------------------
 | 
				
			||||||
%% Testsuite cases
 | 
					%% Testsuite cases
 | 
				
			||||||
%% -------------------------------------------------------------------
 | 
					%% -------------------------------------------------------------------
 | 
				
			||||||
| 
						 | 
					@ -112,7 +104,7 @@ should_translate_amqp2mqtt_on_publish(Config) ->
 | 
				
			||||||
    C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
 | 
					    C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
 | 
				
			||||||
    %% there's an active consumer
 | 
					    %% there's an active consumer
 | 
				
			||||||
    {ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device.Field">>, qos1),
 | 
					    {ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device.Field">>, qos1),
 | 
				
			||||||
    ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]),
 | 
					    ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{},  <<"Payload">>, [{retain, true}]),
 | 
				
			||||||
    ok = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]),
 | 
					    ok = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]),
 | 
				
			||||||
    ok = emqtt:disconnect(C).
 | 
					    ok = emqtt:disconnect(C).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -124,7 +116,7 @@ should_translate_amqp2mqtt_on_publish(Config) ->
 | 
				
			||||||
should_translate_amqp2mqtt_on_retention(Config) ->
 | 
					should_translate_amqp2mqtt_on_retention(Config) ->
 | 
				
			||||||
    C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
 | 
					    C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
 | 
				
			||||||
    %% publish with retain = true before a consumer comes around
 | 
					    %% publish with retain = true before a consumer comes around
 | 
				
			||||||
    ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]),
 | 
					    ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{},  <<"Payload">>, [{retain, true}]),
 | 
				
			||||||
    {ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device.Field">>, qos1),
 | 
					    {ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device.Field">>, qos1),
 | 
				
			||||||
    ok = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]),
 | 
					    ok = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]),
 | 
				
			||||||
    ok = emqtt:disconnect(C).
 | 
					    ok = emqtt:disconnect(C).
 | 
				
			||||||
| 
						 | 
					@ -136,19 +128,19 @@ should_translate_amqp2mqtt_on_retention(Config) ->
 | 
				
			||||||
%% -------------------------------------------------------------------
 | 
					%% -------------------------------------------------------------------
 | 
				
			||||||
should_translate_amqp2mqtt_on_retention_search(Config) ->
 | 
					should_translate_amqp2mqtt_on_retention_search(Config) ->
 | 
				
			||||||
    C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
 | 
					    C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
 | 
				
			||||||
    ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]),
 | 
					    ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{},  <<"Payload">>, [{retain, true}]),
 | 
				
			||||||
    {ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device/Field">>, qos1),
 | 
					    {ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device/Field">>, qos1),
 | 
				
			||||||
    ok = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]),
 | 
					    ok = expect_publishes(C, <<"TopicA/Device/Field">>, [<<"Payload">>]),
 | 
				
			||||||
    ok = emqtt:disconnect(C).
 | 
					    ok = emqtt:disconnect(C).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
does_not_retain(Config) ->
 | 
					does_not_retain(Config) ->
 | 
				
			||||||
    C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
 | 
					    C = connect(<<"simpleClientRetainer">>, Config, [{ack_timeout, 1}]),
 | 
				
			||||||
    ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{}, <<"Payload">>, [{retain, true}]),
 | 
					    ok = emqtt:publish(C, <<"TopicA/Device.Field">>, #{},  <<"Payload">>, [{retain, true}]),
 | 
				
			||||||
    {ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device.Field">>, qos1),
 | 
					    {ok, _, _} = emqtt:subscribe(C, <<"TopicA/Device.Field">>, qos1),
 | 
				
			||||||
    receive
 | 
					    receive
 | 
				
			||||||
        Unexpected ->
 | 
					        Unexpected ->
 | 
				
			||||||
            ct:fail("Unexpected message: ~p", [Unexpected])
 | 
					            ct:fail("Unexpected message: ~p", [Unexpected])
 | 
				
			||||||
    after 1000 ->
 | 
					    after 1000 ->
 | 
				
			||||||
        ok
 | 
					              ok
 | 
				
			||||||
    end,
 | 
					    end,
 | 
				
			||||||
    ok = emqtt:disconnect(C).
 | 
					    ok = emqtt:disconnect(C).
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
					@ -4,58 +4,46 @@
 | 
				
			||||||
-include_lib("rabbit_common/include/rabbit.hrl").
 | 
					-include_lib("rabbit_common/include/rabbit.hrl").
 | 
				
			||||||
-include_lib("eunit/include/eunit.hrl").
 | 
					-include_lib("eunit/include/eunit.hrl").
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-export([
 | 
					-export([all_connection_pids/1,
 | 
				
			||||||
    all_connection_pids/1,
 | 
					         publish_qos1_timeout/4,
 | 
				
			||||||
    publish_qos1_timeout/4,
 | 
					         sync_publish_result/3,
 | 
				
			||||||
    sync_publish_result/3,
 | 
					         get_global_counters/2,
 | 
				
			||||||
    get_global_counters/2,
 | 
					         get_global_counters/3,
 | 
				
			||||||
    get_global_counters/3,
 | 
					         get_global_counters/4,
 | 
				
			||||||
    get_global_counters/4,
 | 
					         expect_publishes/3,
 | 
				
			||||||
    expect_publishes/3,
 | 
					         connect/2,
 | 
				
			||||||
    connect/2,
 | 
					         connect/3,
 | 
				
			||||||
    connect/3,
 | 
					         connect/4,
 | 
				
			||||||
    connect/4,
 | 
					         get_events/1,
 | 
				
			||||||
    get_events/1,
 | 
					         assert_event_type/2,
 | 
				
			||||||
    assert_event_type/2,
 | 
					         assert_event_prop/2,
 | 
				
			||||||
    assert_event_prop/2,
 | 
					         await_exit/1,
 | 
				
			||||||
    await_exit/1,
 | 
					         await_exit/2
 | 
				
			||||||
    await_exit/2
 | 
					        ]).
 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
all_connection_pids(Config) ->
 | 
					all_connection_pids(Config) ->
 | 
				
			||||||
    Nodes = rabbit_ct_broker_helpers:get_node_configs(Config, nodename),
 | 
					    Nodes = rabbit_ct_broker_helpers:get_node_configs(Config, nodename),
 | 
				
			||||||
    Result = erpc:multicall(Nodes, rabbit_mqtt, local_connection_pids, [], 5000),
 | 
					    Result = erpc:multicall(Nodes, rabbit_mqtt, local_connection_pids, [], 5000),
 | 
				
			||||||
    lists:foldl(
 | 
					    lists:foldl(fun({ok, Pids}, Acc) ->
 | 
				
			||||||
        fun
 | 
					                        Pids ++ Acc;
 | 
				
			||||||
            ({ok, Pids}, Acc) ->
 | 
					                   (_, Acc) ->
 | 
				
			||||||
                Pids ++ Acc;
 | 
					                        Acc
 | 
				
			||||||
            (_, Acc) ->
 | 
					                end, [], Result).
 | 
				
			||||||
                Acc
 | 
					 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
        [],
 | 
					 | 
				
			||||||
        Result
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
publish_qos1_timeout(Client, Topic, Payload, Timeout) ->
 | 
					publish_qos1_timeout(Client, Topic, Payload, Timeout) ->
 | 
				
			||||||
    Mref = erlang:monitor(process, Client),
 | 
					    Mref = erlang:monitor(process, Client),
 | 
				
			||||||
    ok = emqtt:publish_async(
 | 
					    ok = emqtt:publish_async(Client, Topic, #{}, Payload, [{qos, 1}], infinity,
 | 
				
			||||||
        Client,
 | 
					                             {fun ?MODULE:sync_publish_result/3, [self(), Mref]}),
 | 
				
			||||||
        Topic,
 | 
					 | 
				
			||||||
        #{},
 | 
					 | 
				
			||||||
        Payload,
 | 
					 | 
				
			||||||
        [{qos, 1}],
 | 
					 | 
				
			||||||
        infinity,
 | 
					 | 
				
			||||||
        {fun ?MODULE:sync_publish_result/3, [self(), Mref]}
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    receive
 | 
					    receive
 | 
				
			||||||
        {Mref, Reply} ->
 | 
					        {Mref, Reply} ->
 | 
				
			||||||
            erlang:demonitor(Mref, [flush]),
 | 
					            erlang:demonitor(Mref, [flush]),
 | 
				
			||||||
            Reply;
 | 
					            Reply;
 | 
				
			||||||
        {'DOWN', Mref, process, Client, Reason} ->
 | 
					        {'DOWN', Mref, process, Client, Reason} ->
 | 
				
			||||||
            ct:fail("client is down: ~tp", [Reason])
 | 
					            ct:fail("client is down: ~tp", [Reason])
 | 
				
			||||||
    after Timeout ->
 | 
					    after
 | 
				
			||||||
        erlang:demonitor(Mref, [flush]),
 | 
					        Timeout ->
 | 
				
			||||||
        puback_timeout
 | 
					            erlang:demonitor(Mref, [flush]),
 | 
				
			||||||
 | 
					            puback_timeout
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sync_publish_result(Caller, Mref, Result) ->
 | 
					sync_publish_result(Caller, Mref, Result) ->
 | 
				
			||||||
| 
						 | 
					@ -63,27 +51,20 @@ sync_publish_result(Caller, Mref, Result) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
expect_publishes(_, _, []) ->
 | 
					expect_publishes(_, _, []) ->
 | 
				
			||||||
    ok;
 | 
					    ok;
 | 
				
			||||||
expect_publishes(Client, Topic, [Payload | Rest]) when
 | 
					expect_publishes(Client, Topic, [Payload|Rest])
 | 
				
			||||||
    is_pid(Client)
 | 
					  when is_pid(Client) ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    receive
 | 
					    receive
 | 
				
			||||||
        {publish, #{
 | 
					        {publish, #{client_pid := Client,
 | 
				
			||||||
            client_pid := Client,
 | 
					                    topic := Topic,
 | 
				
			||||||
            topic := Topic,
 | 
					                    payload := Payload}} ->
 | 
				
			||||||
            payload := Payload
 | 
					 | 
				
			||||||
        }} ->
 | 
					 | 
				
			||||||
            expect_publishes(Client, Topic, Rest);
 | 
					            expect_publishes(Client, Topic, Rest);
 | 
				
			||||||
        {publish, #{
 | 
					        {publish, #{client_pid := Client,
 | 
				
			||||||
            client_pid := Client,
 | 
					                    topic := Topic,
 | 
				
			||||||
            topic := Topic,
 | 
					                    payload := Other}} ->
 | 
				
			||||||
            payload := Other
 | 
					            ct:fail("Received unexpected PUBLISH payload. Expected: ~p Got: ~p",
 | 
				
			||||||
        }} ->
 | 
					                    [Payload, Other])
 | 
				
			||||||
            ct:fail(
 | 
					 | 
				
			||||||
                "Received unexpected PUBLISH payload. Expected: ~p Got: ~p",
 | 
					 | 
				
			||||||
                [Payload, Other]
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
    after 3000 ->
 | 
					    after 3000 ->
 | 
				
			||||||
        {publish_not_received, Payload}
 | 
					              {publish_not_received, Payload}
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
get_global_counters(Config, ProtoVer) ->
 | 
					get_global_counters(Config, ProtoVer) ->
 | 
				
			||||||
| 
						 | 
					@ -97,14 +78,11 @@ get_global_counters(Config, v3, Node, QType) ->
 | 
				
			||||||
get_global_counters(Config, v4, Node, QType) ->
 | 
					get_global_counters(Config, v4, Node, QType) ->
 | 
				
			||||||
    get_global_counters(Config, ?MQTT_PROTO_V4, Node, QType);
 | 
					    get_global_counters(Config, ?MQTT_PROTO_V4, Node, QType);
 | 
				
			||||||
get_global_counters(Config, Proto, Node, QType) ->
 | 
					get_global_counters(Config, Proto, Node, QType) ->
 | 
				
			||||||
    maps:get(
 | 
					    maps:get([{protocol, Proto}] ++ QType,
 | 
				
			||||||
        [{protocol, Proto}] ++ QType,
 | 
					             rabbit_ct_broker_helpers:rpc(Config, Node, rabbit_global_counters, overview, [])).
 | 
				
			||||||
        rabbit_ct_broker_helpers:rpc(Config, Node, rabbit_global_counters, overview, [])
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
get_events(Node) ->
 | 
					get_events(Node) ->
 | 
				
			||||||
    %% events are sent and processed asynchronously
 | 
					    timer:sleep(300), %% events are sent and processed asynchronously
 | 
				
			||||||
    timer:sleep(300),
 | 
					 | 
				
			||||||
    Result = gen_event:call({rabbit_event, Node}, event_recorder, take_state),
 | 
					    Result = gen_event:call({rabbit_event, Node}, event_recorder, take_state),
 | 
				
			||||||
    ?assert(is_list(Result)),
 | 
					    ?assert(is_list(Result)),
 | 
				
			||||||
    Result.
 | 
					    Result.
 | 
				
			||||||
| 
						 | 
					@ -114,26 +92,24 @@ assert_event_type(ExpectedType, #event{type = ActualType}) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
assert_event_prop(ExpectedProp = {Key, _Value}, #event{props = Props}) ->
 | 
					assert_event_prop(ExpectedProp = {Key, _Value}, #event{props = Props}) ->
 | 
				
			||||||
    ?assertEqual(ExpectedProp, lists:keyfind(Key, 1, Props));
 | 
					    ?assertEqual(ExpectedProp, lists:keyfind(Key, 1, Props));
 | 
				
			||||||
assert_event_prop(ExpectedProps, Event) when
 | 
					assert_event_prop(ExpectedProps, Event)
 | 
				
			||||||
    is_list(ExpectedProps)
 | 
					  when is_list(ExpectedProps) ->
 | 
				
			||||||
->
 | 
					    lists:foreach(fun(P) ->
 | 
				
			||||||
    lists:foreach(
 | 
					                          assert_event_prop(P, Event)
 | 
				
			||||||
        fun(P) ->
 | 
					                  end, ExpectedProps).
 | 
				
			||||||
            assert_event_prop(P, Event)
 | 
					 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
        ExpectedProps
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
await_exit(Pid) ->
 | 
					await_exit(Pid) ->
 | 
				
			||||||
    receive
 | 
					    receive
 | 
				
			||||||
        {'EXIT', Pid, _} -> ok
 | 
					        {'EXIT', Pid, _} -> ok
 | 
				
			||||||
    after 20_000 -> ct:fail({missing_exit, Pid})
 | 
					    after
 | 
				
			||||||
 | 
					        20_000 -> ct:fail({missing_exit, Pid})
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
await_exit(Pid, Reason) ->
 | 
					await_exit(Pid, Reason) ->
 | 
				
			||||||
    receive
 | 
					    receive
 | 
				
			||||||
        {'EXIT', Pid, Reason} -> ok
 | 
					        {'EXIT', Pid, Reason} -> ok
 | 
				
			||||||
    after 20_000 -> ct:fail({missing_exit, Pid})
 | 
					    after
 | 
				
			||||||
 | 
					        20_000 -> ct:fail({missing_exit, Pid})
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
connect(ClientId, Config) ->
 | 
					connect(ClientId, Config) ->
 | 
				
			||||||
| 
						 | 
					@ -144,27 +120,21 @@ connect(ClientId, Config, AdditionalOpts) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
connect(ClientId, Config, Node, AdditionalOpts) ->
 | 
					connect(ClientId, Config, Node, AdditionalOpts) ->
 | 
				
			||||||
    {Port, WsOpts, Connect} =
 | 
					    {Port, WsOpts, Connect} =
 | 
				
			||||||
        case rabbit_ct_helpers:get_config(Config, websocket, false) of
 | 
					    case rabbit_ct_helpers:get_config(Config, websocket, false) of
 | 
				
			||||||
            false ->
 | 
					        false ->
 | 
				
			||||||
                {
 | 
					            {rabbit_ct_broker_helpers:get_node_config(Config, Node, tcp_port_mqtt),
 | 
				
			||||||
                    rabbit_ct_broker_helpers:get_node_config(Config, Node, tcp_port_mqtt),
 | 
					             [],
 | 
				
			||||||
                    [],
 | 
					             fun emqtt:connect/1};
 | 
				
			||||||
                    fun emqtt:connect/1
 | 
					        true ->
 | 
				
			||||||
                };
 | 
					            {rabbit_ct_broker_helpers:get_node_config(Config, Node, tcp_port_web_mqtt),
 | 
				
			||||||
            true ->
 | 
					             [{ws_path, "/ws"}],
 | 
				
			||||||
                {
 | 
					             fun emqtt:ws_connect/1}
 | 
				
			||||||
                    rabbit_ct_broker_helpers:get_node_config(Config, Node, tcp_port_web_mqtt),
 | 
					    end,
 | 
				
			||||||
                    [{ws_path, "/ws"}],
 | 
					    Options = [{host, "localhost"},
 | 
				
			||||||
                    fun emqtt:ws_connect/1
 | 
					               {port, Port},
 | 
				
			||||||
                }
 | 
					               {proto_ver, v4},
 | 
				
			||||||
        end,
 | 
					               {clientid, rabbit_data_coercion:to_binary(ClientId)}
 | 
				
			||||||
    Options =
 | 
					              ] ++ WsOpts ++ AdditionalOpts,
 | 
				
			||||||
        [
 | 
					 | 
				
			||||||
            {host, "localhost"},
 | 
					 | 
				
			||||||
            {port, Port},
 | 
					 | 
				
			||||||
            {proto_ver, v4},
 | 
					 | 
				
			||||||
            {clientid, rabbit_data_coercion:to_binary(ClientId)}
 | 
					 | 
				
			||||||
        ] ++ WsOpts ++ AdditionalOpts,
 | 
					 | 
				
			||||||
    {ok, C} = emqtt:start_link(Options),
 | 
					    {ok, C} = emqtt:start_link(Options),
 | 
				
			||||||
    {ok, _Properties} = Connect(C),
 | 
					    {ok, _Properties} = Connect(C),
 | 
				
			||||||
    C.
 | 
					    C.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,18 +12,19 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
all() ->
 | 
					all() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {group, tests}
 | 
					      {group, tests}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
groups() ->
 | 
					groups() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {tests, [parallel], [
 | 
					     {tests, [parallel], [
 | 
				
			||||||
            coerce_exchange,
 | 
					                          coerce_exchange,
 | 
				
			||||||
            coerce_vhost,
 | 
					                          coerce_vhost,
 | 
				
			||||||
            coerce_default_user,
 | 
					                          coerce_default_user,
 | 
				
			||||||
            coerce_default_pass,
 | 
					                          coerce_default_pass,
 | 
				
			||||||
            mqtt_amqp_topic_translation
 | 
					                          mqtt_amqp_topic_translation
 | 
				
			||||||
        ]}
 | 
					                         ]
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
suite() ->
 | 
					suite() ->
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -49,7 +49,7 @@ init([]) -> {ok, {{one_for_one, 1, 5}, []}}.
 | 
				
			||||||
-spec list_connections() -> [pid()].
 | 
					-spec list_connections() -> [pid()].
 | 
				
			||||||
list_connections() ->
 | 
					list_connections() ->
 | 
				
			||||||
    PlainPids = connection_pids_of_protocol(?TCP_PROTOCOL),
 | 
					    PlainPids = connection_pids_of_protocol(?TCP_PROTOCOL),
 | 
				
			||||||
    TLSPids = connection_pids_of_protocol(?TLS_PROTOCOL),
 | 
					    TLSPids   = connection_pids_of_protocol(?TLS_PROTOCOL),
 | 
				
			||||||
    PlainPids ++ TLSPids.
 | 
					    PlainPids ++ TLSPids.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
%%
 | 
					%%
 | 
				
			||||||
| 
						 | 
					@ -58,8 +58,7 @@ list_connections() ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
connection_pids_of_protocol(Protocol) ->
 | 
					connection_pids_of_protocol(Protocol) ->
 | 
				
			||||||
    case rabbit_networking:ranch_ref_of_protocol(Protocol) of
 | 
					    case rabbit_networking:ranch_ref_of_protocol(Protocol) of
 | 
				
			||||||
        undefined ->
 | 
					        undefined   -> [];
 | 
				
			||||||
            [];
 | 
					 | 
				
			||||||
        AcceptorRef ->
 | 
					        AcceptorRef ->
 | 
				
			||||||
            lists:map(fun cowboy_ws_connection_pid/1, ranch:procs(AcceptorRef, connections))
 | 
					            lists:map(fun cowboy_ws_connection_pid/1, ranch:procs(AcceptorRef, connections))
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
| 
						 | 
					@ -71,38 +70,36 @@ cowboy_ws_connection_pid(RanchConnPid) ->
 | 
				
			||||||
    Pid.
 | 
					    Pid.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mqtt_init() ->
 | 
					mqtt_init() ->
 | 
				
			||||||
    CowboyOpts0 = maps:from_list(get_env(cowboy_opts, [])),
 | 
					  CowboyOpts0  = maps:from_list(get_env(cowboy_opts, [])),
 | 
				
			||||||
    CowboyWsOpts = maps:from_list(get_env(cowboy_ws_opts, [])),
 | 
					  CowboyWsOpts = maps:from_list(get_env(cowboy_ws_opts, [])),
 | 
				
			||||||
    Routes = cowboy_router:compile([
 | 
					  Routes = cowboy_router:compile([{'_', [
 | 
				
			||||||
        {'_', [
 | 
					      {get_env(ws_path, "/ws"), rabbit_web_mqtt_handler, [{ws_opts, CowboyWsOpts}]}
 | 
				
			||||||
            {get_env(ws_path, "/ws"), rabbit_web_mqtt_handler, [{ws_opts, CowboyWsOpts}]}
 | 
					  ]}]),
 | 
				
			||||||
        ]}
 | 
					  CowboyOpts = CowboyOpts0#{
 | 
				
			||||||
    ]),
 | 
					                 env => #{dispatch => Routes},
 | 
				
			||||||
    CowboyOpts = CowboyOpts0#{
 | 
					                 proxy_header => get_env(proxy_protocol, false),
 | 
				
			||||||
        env => #{dispatch => Routes},
 | 
					                 stream_handlers => [rabbit_web_mqtt_stream_handler, cowboy_stream_h]
 | 
				
			||||||
        proxy_header => get_env(proxy_protocol, false),
 | 
					                },
 | 
				
			||||||
        stream_handlers => [rabbit_web_mqtt_stream_handler, cowboy_stream_h]
 | 
					  case get_env(tcp_config, []) of
 | 
				
			||||||
    },
 | 
					      []       -> ok;
 | 
				
			||||||
    case get_env(tcp_config, []) of
 | 
					      TCPConf0 -> start_tcp_listener(TCPConf0, CowboyOpts)
 | 
				
			||||||
        [] -> ok;
 | 
					  end,
 | 
				
			||||||
        TCPConf0 -> start_tcp_listener(TCPConf0, CowboyOpts)
 | 
					  case get_env(ssl_config, []) of
 | 
				
			||||||
    end,
 | 
					      []       -> ok;
 | 
				
			||||||
    case get_env(ssl_config, []) of
 | 
					      TLSConf0 -> start_tls_listener(TLSConf0, CowboyOpts)
 | 
				
			||||||
        [] -> ok;
 | 
					  end,
 | 
				
			||||||
        TLSConf0 -> start_tls_listener(TLSConf0, CowboyOpts)
 | 
					  ok.
 | 
				
			||||||
    end,
 | 
					 | 
				
			||||||
    ok.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
start_tcp_listener(TCPConf0, CowboyOpts) ->
 | 
					start_tcp_listener(TCPConf0, CowboyOpts) ->
 | 
				
			||||||
    {TCPConf, IpStr, Port} = get_tcp_conf(TCPConf0),
 | 
					    {TCPConf, IpStr, Port} = get_tcp_conf(TCPConf0),
 | 
				
			||||||
    RanchRef = rabbit_networking:ranch_ref(TCPConf),
 | 
					    RanchRef = rabbit_networking:ranch_ref(TCPConf),
 | 
				
			||||||
    RanchTransportOpts =
 | 
					    RanchTransportOpts =
 | 
				
			||||||
        #{
 | 
					    #{
 | 
				
			||||||
            socket_opts => TCPConf,
 | 
					      socket_opts => TCPConf,
 | 
				
			||||||
            max_connections => get_max_connections(),
 | 
					      max_connections => get_max_connections(),
 | 
				
			||||||
            num_acceptors => get_env(num_tcp_acceptors, 10),
 | 
					      num_acceptors => get_env(num_tcp_acceptors, 10),
 | 
				
			||||||
            num_conns_sups => get_env(num_conns_sup, 1)
 | 
					      num_conns_sups => get_env(num_conns_sup, 1)
 | 
				
			||||||
        },
 | 
					     },
 | 
				
			||||||
    case cowboy:start_clear(RanchRef, RanchTransportOpts, CowboyOpts) of
 | 
					    case cowboy:start_clear(RanchRef, RanchTransportOpts, CowboyOpts) of
 | 
				
			||||||
        {ok, _} ->
 | 
					        {ok, _} ->
 | 
				
			||||||
            ok;
 | 
					            ok;
 | 
				
			||||||
| 
						 | 
					@ -110,28 +107,25 @@ start_tcp_listener(TCPConf0, CowboyOpts) ->
 | 
				
			||||||
            ok;
 | 
					            ok;
 | 
				
			||||||
        {error, ErrTCP} ->
 | 
					        {error, ErrTCP} ->
 | 
				
			||||||
            rabbit_log:error(
 | 
					            rabbit_log:error(
 | 
				
			||||||
                "Failed to start a WebSocket (HTTP) listener. Error: ~p, listener settings: ~p",
 | 
					              "Failed to start a WebSocket (HTTP) listener. Error: ~p, listener settings: ~p",
 | 
				
			||||||
                [ErrTCP, TCPConf]
 | 
					              [ErrTCP, TCPConf]),
 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            throw(ErrTCP)
 | 
					            throw(ErrTCP)
 | 
				
			||||||
    end,
 | 
					    end,
 | 
				
			||||||
    listener_started(?TCP_PROTOCOL, TCPConf),
 | 
					    listener_started(?TCP_PROTOCOL, TCPConf),
 | 
				
			||||||
    rabbit_log:info(
 | 
					    rabbit_log:info("rabbit_web_mqtt: listening for HTTP connections on ~s:~w",
 | 
				
			||||||
        "rabbit_web_mqtt: listening for HTTP connections on ~s:~w",
 | 
					                    [IpStr, Port]).
 | 
				
			||||||
        [IpStr, Port]
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
start_tls_listener(TLSConf0, CowboyOpts) ->
 | 
					start_tls_listener(TLSConf0, CowboyOpts) ->
 | 
				
			||||||
    _ = rabbit_networking:ensure_ssl(),
 | 
					    _ = rabbit_networking:ensure_ssl(),
 | 
				
			||||||
    {TLSConf, TLSIpStr, TLSPort} = get_tls_conf(TLSConf0),
 | 
					    {TLSConf, TLSIpStr, TLSPort} = get_tls_conf(TLSConf0),
 | 
				
			||||||
    RanchRef = rabbit_networking:ranch_ref(TLSConf),
 | 
					    RanchRef = rabbit_networking:ranch_ref(TLSConf),
 | 
				
			||||||
    RanchTransportOpts =
 | 
					    RanchTransportOpts =
 | 
				
			||||||
        #{
 | 
					    #{
 | 
				
			||||||
            socket_opts => TLSConf,
 | 
					      socket_opts => TLSConf,
 | 
				
			||||||
            max_connections => get_max_connections(),
 | 
					      max_connections => get_max_connections(),
 | 
				
			||||||
            num_acceptors => get_env(num_ssl_acceptors, 10),
 | 
					      num_acceptors => get_env(num_ssl_acceptors, 10),
 | 
				
			||||||
            num_conns_sups => get_env(num_conns_sup, 1)
 | 
					      num_conns_sups => get_env(num_conns_sup, 1)
 | 
				
			||||||
        },
 | 
					     },
 | 
				
			||||||
    case cowboy:start_tls(RanchRef, RanchTransportOpts, CowboyOpts) of
 | 
					    case cowboy:start_tls(RanchRef, RanchTransportOpts, CowboyOpts) of
 | 
				
			||||||
        {ok, _} ->
 | 
					        {ok, _} ->
 | 
				
			||||||
            ok;
 | 
					            ok;
 | 
				
			||||||
| 
						 | 
					@ -139,45 +133,34 @@ start_tls_listener(TLSConf0, CowboyOpts) ->
 | 
				
			||||||
            ok;
 | 
					            ok;
 | 
				
			||||||
        {error, ErrTLS} ->
 | 
					        {error, ErrTLS} ->
 | 
				
			||||||
            rabbit_log:error(
 | 
					            rabbit_log:error(
 | 
				
			||||||
                "Failed to start a TLS WebSocket (HTTPS) listener. Error: ~p, listener settings: ~p",
 | 
					              "Failed to start a TLS WebSocket (HTTPS) listener. Error: ~p, listener settings: ~p",
 | 
				
			||||||
                [ErrTLS, TLSConf]
 | 
					              [ErrTLS, TLSConf]),
 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            throw(ErrTLS)
 | 
					            throw(ErrTLS)
 | 
				
			||||||
    end,
 | 
					    end,
 | 
				
			||||||
    listener_started(?TLS_PROTOCOL, TLSConf),
 | 
					    listener_started(?TLS_PROTOCOL, TLSConf),
 | 
				
			||||||
    rabbit_log:info(
 | 
					    rabbit_log:info("rabbit_web_mqtt: listening for HTTPS connections on ~s:~w",
 | 
				
			||||||
        "rabbit_web_mqtt: listening for HTTPS connections on ~s:~w",
 | 
					                    [TLSIpStr, TLSPort]).
 | 
				
			||||||
        [TLSIpStr, TLSPort]
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
listener_started(Protocol, Listener) ->
 | 
					listener_started(Protocol, Listener) ->
 | 
				
			||||||
    Port = rabbit_misc:pget(port, Listener),
 | 
					    Port = rabbit_misc:pget(port, Listener),
 | 
				
			||||||
    [
 | 
					    [rabbit_networking:tcp_listener_started(Protocol, Listener,
 | 
				
			||||||
        rabbit_networking:tcp_listener_started(
 | 
					                                            IPAddress, Port)
 | 
				
			||||||
            Protocol,
 | 
					     || {IPAddress, _Port, _Family}
 | 
				
			||||||
            Listener,
 | 
					        <- rabbit_networking:tcp_listener_addresses(Port)],
 | 
				
			||||||
            IPAddress,
 | 
					 | 
				
			||||||
            Port
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
     || {IPAddress, _Port, _Family} <-
 | 
					 | 
				
			||||||
            rabbit_networking:tcp_listener_addresses(Port)
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    ok.
 | 
					    ok.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
get_tcp_conf(TCPConf0) ->
 | 
					get_tcp_conf(TCPConf0) ->
 | 
				
			||||||
    TCPConf1 =
 | 
					    TCPConf1 = case proplists:get_value(port, TCPConf0) of
 | 
				
			||||||
        case proplists:get_value(port, TCPConf0) of
 | 
					                   undefined -> [{port, 15675}|TCPConf0];
 | 
				
			||||||
            undefined -> [{port, 15675} | TCPConf0];
 | 
					                   _ -> TCPConf0
 | 
				
			||||||
            _ -> TCPConf0
 | 
					               end,
 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
    get_ip_port(TCPConf1).
 | 
					    get_ip_port(TCPConf1).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
get_tls_conf(TLSConf0) ->
 | 
					get_tls_conf(TLSConf0) ->
 | 
				
			||||||
    TLSConf1 =
 | 
					    TLSConf1 = case proplists:get_value(port, TLSConf0) of
 | 
				
			||||||
        case proplists:get_value(port, TLSConf0) of
 | 
					                   undefined -> [{port, 15675}|proplists:delete(port, TLSConf0)];
 | 
				
			||||||
            undefined -> [{port, 15675} | proplists:delete(port, TLSConf0)];
 | 
					                   _ -> TLSConf0
 | 
				
			||||||
            _ -> TLSConf0
 | 
					               end,
 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
    get_ip_port(TLSConf1).
 | 
					    get_ip_port(TLSConf1).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
get_ip_port(Conf0) ->
 | 
					get_ip_port(Conf0) ->
 | 
				
			||||||
| 
						 | 
					@ -194,7 +177,7 @@ normalize_ip(Ip) ->
 | 
				
			||||||
    Ip.
 | 
					    Ip.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
get_max_connections() ->
 | 
					get_max_connections() ->
 | 
				
			||||||
    get_env(max_connections, infinity).
 | 
					  get_env(max_connections, infinity).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
get_env(Key, Default) ->
 | 
					get_env(Key, Default) ->
 | 
				
			||||||
    rabbit_misc:get_env(rabbitmq_web_mqtt, Key, Default).
 | 
					    rabbit_misc:get_env(rabbitmq_web_mqtt, Key, Default).
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,25 +24,23 @@
 | 
				
			||||||
-export([conserve_resources/3]).
 | 
					-export([conserve_resources/3]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
%% cowboy_sub_protocol
 | 
					%% cowboy_sub_protocol
 | 
				
			||||||
-export([
 | 
					-export([upgrade/4,
 | 
				
			||||||
    upgrade/4,
 | 
					         upgrade/5,
 | 
				
			||||||
    upgrade/5,
 | 
					         takeover/7]).
 | 
				
			||||||
    takeover/7
 | 
					 | 
				
			||||||
]).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
-type option(T) :: undefined | T.
 | 
					-type option(T) :: undefined | T.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-record(state, {
 | 
					-record(state, {
 | 
				
			||||||
    socket :: {rabbit_proxy_socket, any(), any()} | rabbit_net:socket(),
 | 
					          socket :: {rabbit_proxy_socket, any(), any()} | rabbit_net:socket(),
 | 
				
			||||||
    parse_state = rabbit_mqtt_packet:initial_state() :: rabbit_mqtt_packet:state(),
 | 
					          parse_state = rabbit_mqtt_packet:initial_state() :: rabbit_mqtt_packet:state(),
 | 
				
			||||||
    proc_state :: undefined | rabbit_mqtt_processor:state(),
 | 
					          proc_state :: undefined | rabbit_mqtt_processor:state(),
 | 
				
			||||||
    connection_state = running :: running | blocked,
 | 
					          connection_state = running :: running | blocked,
 | 
				
			||||||
    conserve = false :: boolean(),
 | 
					          conserve = false :: boolean(),
 | 
				
			||||||
    stats_timer :: option(rabbit_event:state()),
 | 
					          stats_timer :: option(rabbit_event:state()),
 | 
				
			||||||
    keepalive = rabbit_mqtt_keepalive:init() :: rabbit_mqtt_keepalive:state(),
 | 
					          keepalive = rabbit_mqtt_keepalive:init() :: rabbit_mqtt_keepalive:state(),
 | 
				
			||||||
    conn_name :: option(binary()),
 | 
					          conn_name :: option(binary()),
 | 
				
			||||||
    received_connect_packet = false :: boolean()
 | 
					          received_connect_packet = false :: boolean()
 | 
				
			||||||
}).
 | 
					        }).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-type state() :: #state{}.
 | 
					-type state() :: #state{}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -60,22 +58,14 @@ upgrade(Req, Env, Handler, HandlerState, Opts) ->
 | 
				
			||||||
    cowboy_websocket:upgrade(Req, Env, Handler, HandlerState, Opts).
 | 
					    cowboy_websocket:upgrade(Req, Env, Handler, HandlerState, Opts).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
takeover(Parent, Ref, Socket, Transport, Opts, Buffer, {Handler, {HandlerState, PeerAddr}}) ->
 | 
					takeover(Parent, Ref, Socket, Transport, Opts, Buffer, {Handler, {HandlerState, PeerAddr}}) ->
 | 
				
			||||||
    Sock =
 | 
					    Sock = case HandlerState#state.socket of
 | 
				
			||||||
        case HandlerState#state.socket of
 | 
					               undefined ->
 | 
				
			||||||
            undefined ->
 | 
					                   Socket;
 | 
				
			||||||
                Socket;
 | 
					               ProxyInfo ->
 | 
				
			||||||
            ProxyInfo ->
 | 
					                   {rabbit_proxy_socket, Socket, ProxyInfo}
 | 
				
			||||||
                {rabbit_proxy_socket, Socket, ProxyInfo}
 | 
					           end,
 | 
				
			||||||
        end,
 | 
					    cowboy_websocket:takeover(Parent, Ref, Socket, Transport, Opts, Buffer,
 | 
				
			||||||
    cowboy_websocket:takeover(
 | 
					                              {Handler, {HandlerState#state{socket = Sock}, PeerAddr}}).
 | 
				
			||||||
        Parent,
 | 
					 | 
				
			||||||
        Ref,
 | 
					 | 
				
			||||||
        Socket,
 | 
					 | 
				
			||||||
        Transport,
 | 
					 | 
				
			||||||
        Opts,
 | 
					 | 
				
			||||||
        Buffer,
 | 
					 | 
				
			||||||
        {Handler, {HandlerState#state{socket = Sock}, PeerAddr}}
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
%% cowboy_websocket
 | 
					%% cowboy_websocket
 | 
				
			||||||
init(Req, Opts) ->
 | 
					init(Req, Opts) ->
 | 
				
			||||||
| 
						 | 
					@ -85,20 +75,22 @@ init(Req, Opts) ->
 | 
				
			||||||
        Protocol ->
 | 
					        Protocol ->
 | 
				
			||||||
            {PeerAddr, _PeerPort} = maps:get(peer, Req),
 | 
					            {PeerAddr, _PeerPort} = maps:get(peer, Req),
 | 
				
			||||||
            WsOpts0 = proplists:get_value(ws_opts, Opts, #{}),
 | 
					            WsOpts0 = proplists:get_value(ws_opts, Opts, #{}),
 | 
				
			||||||
            WsOpts = maps:merge(#{compress => true}, WsOpts0),
 | 
					            WsOpts  = maps:merge(#{compress => true}, WsOpts0),
 | 
				
			||||||
            case lists:member(<<"mqtt">>, Protocol) of
 | 
					            case lists:member(<<"mqtt">>, Protocol) of
 | 
				
			||||||
                false ->
 | 
					                false ->
 | 
				
			||||||
                    no_supported_sub_protocol(Protocol, Req);
 | 
					                    no_supported_sub_protocol(Protocol, Req);
 | 
				
			||||||
                true ->
 | 
					                true ->
 | 
				
			||||||
                    {?MODULE,
 | 
					                    {?MODULE,
 | 
				
			||||||
                        cowboy_req:set_resp_header(<<"sec-websocket-protocol">>, <<"mqtt">>, Req),
 | 
					                     cowboy_req:set_resp_header(<<"sec-websocket-protocol">>, <<"mqtt">>, Req),
 | 
				
			||||||
                        {#state{socket = maps:get(proxy_header, Req, undefined)}, PeerAddr}, WsOpts}
 | 
					                     {#state{socket = maps:get(proxy_header, Req, undefined)},
 | 
				
			||||||
 | 
					                      PeerAddr},
 | 
				
			||||||
 | 
					                     WsOpts}
 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec websocket_init({state(), PeerAddr :: binary()}) ->
 | 
					-spec websocket_init({state(), PeerAddr :: binary()}) ->
 | 
				
			||||||
    {cowboy_websocket:commands(), state()}
 | 
					    {cowboy_websocket:commands(), state()} |
 | 
				
			||||||
    | {cowboy_websocket:commands(), state(), hibernate}.
 | 
					    {cowboy_websocket:commands(), state(), hibernate}.
 | 
				
			||||||
websocket_init({State0 = #state{socket = Sock}, PeerAddr}) ->
 | 
					websocket_init({State0 = #state{socket = Sock}, PeerAddr}) ->
 | 
				
			||||||
    logger:set_process_metadata(#{domain => ?RMQLOG_DOMAIN_CONN ++ [web_mqtt]}),
 | 
					    logger:set_process_metadata(#{domain => ?RMQLOG_DOMAIN_CONN ++ [web_mqtt]}),
 | 
				
			||||||
    ok = file_handle_cache:obtain(),
 | 
					    ok = file_handle_cache:obtain(),
 | 
				
			||||||
| 
						 | 
					@ -108,15 +100,12 @@ websocket_init({State0 = #state{socket = Sock}, PeerAddr}) ->
 | 
				
			||||||
            ?LOG_INFO("Accepting Web MQTT connection ~s", [ConnName]),
 | 
					            ?LOG_INFO("Accepting Web MQTT connection ~s", [ConnName]),
 | 
				
			||||||
            _ = rabbit_alarm:register(self(), {?MODULE, conserve_resources, []}),
 | 
					            _ = rabbit_alarm:register(self(), {?MODULE, conserve_resources, []}),
 | 
				
			||||||
            PState = rabbit_mqtt_processor:initial_state(
 | 
					            PState = rabbit_mqtt_processor:initial_state(
 | 
				
			||||||
                rabbit_net:unwrap_socket(Sock),
 | 
					                       rabbit_net:unwrap_socket(Sock),
 | 
				
			||||||
                ConnName,
 | 
					                       ConnName,
 | 
				
			||||||
                fun send_reply/2,
 | 
					                       fun send_reply/2,
 | 
				
			||||||
                PeerAddr
 | 
					                       PeerAddr),
 | 
				
			||||||
            ),
 | 
					            State1 = State0#state{conn_name = ConnName,
 | 
				
			||||||
            State1 = State0#state{
 | 
					                                  proc_state = PState},
 | 
				
			||||||
                conn_name = ConnName,
 | 
					 | 
				
			||||||
                proc_state = PState
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            State = rabbit_event:init_stats_timer(State1, #state.stats_timer),
 | 
					            State = rabbit_event:init_stats_timer(State1, #state.stats_timer),
 | 
				
			||||||
            process_flag(trap_exit, true),
 | 
					            process_flag(trap_exit, true),
 | 
				
			||||||
            {[], State, hibernate};
 | 
					            {[], State, hibernate};
 | 
				
			||||||
| 
						 | 
					@ -124,28 +113,24 @@ websocket_init({State0 = #state{socket = Sock}, PeerAddr}) ->
 | 
				
			||||||
            {[{shutdown_reason, Reason}], State0}
 | 
					            {[{shutdown_reason, Reason}], State0}
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec conserve_resources(
 | 
					-spec conserve_resources(pid(),
 | 
				
			||||||
    pid(),
 | 
					                         rabbit_alarm:resource_alarm_source(),
 | 
				
			||||||
    rabbit_alarm:resource_alarm_source(),
 | 
					                         rabbit_alarm:resource_alert()) -> ok.
 | 
				
			||||||
    rabbit_alarm:resource_alert()
 | 
					 | 
				
			||||||
) -> ok.
 | 
					 | 
				
			||||||
conserve_resources(Pid, _, {_, Conserve, _}) ->
 | 
					conserve_resources(Pid, _, {_, Conserve, _}) ->
 | 
				
			||||||
    Pid ! {conserve_resources, Conserve},
 | 
					    Pid ! {conserve_resources, Conserve},
 | 
				
			||||||
    ok.
 | 
					    ok.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec websocket_handle(ping | pong | {text | binary | ping | pong, binary()}, State) ->
 | 
					-spec websocket_handle(ping | pong | {text | binary | ping | pong, binary()}, State) ->
 | 
				
			||||||
    {cowboy_websocket:commands(), State}
 | 
					    {cowboy_websocket:commands(), State} |
 | 
				
			||||||
    | {cowboy_websocket:commands(), State, hibernate}.
 | 
					    {cowboy_websocket:commands(), State, hibernate}.
 | 
				
			||||||
websocket_handle({binary, Data}, State) ->
 | 
					websocket_handle({binary, Data}, State) ->
 | 
				
			||||||
    handle_data(Data, State);
 | 
					    handle_data(Data, State);
 | 
				
			||||||
%% Silently ignore ping and pong frames as Cowboy will automatically reply to ping frames.
 | 
					%% Silently ignore ping and pong frames as Cowboy will automatically reply to ping frames.
 | 
				
			||||||
websocket_handle({Ping, _}, State) when
 | 
					websocket_handle({Ping, _}, State)
 | 
				
			||||||
    Ping =:= ping orelse Ping =:= pong
 | 
					  when Ping =:= ping orelse Ping =:= pong ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    {[], State, hibernate};
 | 
					    {[], State, hibernate};
 | 
				
			||||||
websocket_handle(Ping, State) when
 | 
					websocket_handle(Ping, State)
 | 
				
			||||||
    Ping =:= ping orelse Ping =:= pong
 | 
					  when Ping =:= ping orelse Ping =:= pong ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    {[], State, hibernate};
 | 
					    {[], State, hibernate};
 | 
				
			||||||
%% Log and close connection when receiving any other unexpected frames.
 | 
					%% Log and close connection when receiving any other unexpected frames.
 | 
				
			||||||
websocket_handle(Frame, State) ->
 | 
					websocket_handle(Frame, State) ->
 | 
				
			||||||
| 
						 | 
					@ -153,8 +138,8 @@ websocket_handle(Frame, State) ->
 | 
				
			||||||
    stop(State, ?CLOSE_UNACCEPTABLE_DATA_TYPE, <<"unexpected WebSocket frame">>).
 | 
					    stop(State, ?CLOSE_UNACCEPTABLE_DATA_TYPE, <<"unexpected WebSocket frame">>).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-spec websocket_info(any(), State) ->
 | 
					-spec websocket_info(any(), State) ->
 | 
				
			||||||
    {cowboy_websocket:commands(), State}
 | 
					    {cowboy_websocket:commands(), State} |
 | 
				
			||||||
    | {cowboy_websocket:commands(), State, hibernate}.
 | 
					    {cowboy_websocket:commands(), State, hibernate}.
 | 
				
			||||||
websocket_info({conserve_resources, Conserve}, State) ->
 | 
					websocket_info({conserve_resources, Conserve}, State) ->
 | 
				
			||||||
    handle_credits(State#state{conserve = Conserve});
 | 
					    handle_credits(State#state{conserve = Conserve});
 | 
				
			||||||
websocket_info({bump_credit, Msg}, State) ->
 | 
					websocket_info({bump_credit, Msg}, State) ->
 | 
				
			||||||
| 
						 | 
					@ -164,66 +149,39 @@ websocket_info({reply, Data}, State) ->
 | 
				
			||||||
    {[{binary, Data}], State, hibernate};
 | 
					    {[{binary, Data}], State, hibernate};
 | 
				
			||||||
websocket_info({'EXIT', _, _}, State) ->
 | 
					websocket_info({'EXIT', _, _}, State) ->
 | 
				
			||||||
    stop(State);
 | 
					    stop(State);
 | 
				
			||||||
websocket_info(
 | 
					websocket_info({'$gen_cast', QueueEvent = {queue_event, _, _}},
 | 
				
			||||||
    {'$gen_cast', QueueEvent = {queue_event, _, _}},
 | 
					               State = #state{proc_state = PState0}) ->
 | 
				
			||||||
    State = #state{proc_state = PState0}
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    case rabbit_mqtt_processor:handle_queue_event(QueueEvent, PState0) of
 | 
					    case rabbit_mqtt_processor:handle_queue_event(QueueEvent, PState0) of
 | 
				
			||||||
        {ok, PState} ->
 | 
					        {ok, PState} ->
 | 
				
			||||||
            handle_credits(State#state{proc_state = PState});
 | 
					            handle_credits(State#state{proc_state = PState});
 | 
				
			||||||
        {error, Reason, PState} ->
 | 
					        {error, Reason, PState} ->
 | 
				
			||||||
            ?LOG_ERROR(
 | 
					            ?LOG_ERROR("Web MQTT connection ~p failed to handle queue event: ~p",
 | 
				
			||||||
                "Web MQTT connection ~p failed to handle queue event: ~p",
 | 
					                       [State#state.conn_name, Reason]),
 | 
				
			||||||
                [State#state.conn_name, Reason]
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            stop(State#state{proc_state = PState})
 | 
					            stop(State#state{proc_state = PState})
 | 
				
			||||||
    end;
 | 
					    end;
 | 
				
			||||||
websocket_info(
 | 
					websocket_info({'$gen_cast', duplicate_id}, State = #state{ proc_state = ProcState,
 | 
				
			||||||
    {'$gen_cast', duplicate_id},
 | 
					                                                            conn_name = ConnName }) ->
 | 
				
			||||||
    State = #state{
 | 
					    ?LOG_WARNING("Web MQTT disconnecting a client with duplicate ID '~s' (~p)",
 | 
				
			||||||
        proc_state = ProcState,
 | 
					                 [rabbit_mqtt_processor:info(client_id, ProcState), ConnName]),
 | 
				
			||||||
        conn_name = ConnName
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    ?LOG_WARNING(
 | 
					 | 
				
			||||||
        "Web MQTT disconnecting a client with duplicate ID '~s' (~p)",
 | 
					 | 
				
			||||||
        [rabbit_mqtt_processor:info(client_id, ProcState), ConnName]
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    stop(State);
 | 
					    stop(State);
 | 
				
			||||||
websocket_info(
 | 
					websocket_info({'$gen_cast', {close_connection, Reason}}, State = #state{ proc_state = ProcState,
 | 
				
			||||||
    {'$gen_cast', {close_connection, Reason}},
 | 
					                                                                          conn_name = ConnName }) ->
 | 
				
			||||||
    State = #state{
 | 
					    ?LOG_WARNING("Web MQTT disconnecting client with ID '~s' (~p), reason: ~s",
 | 
				
			||||||
        proc_state = ProcState,
 | 
					                 [rabbit_mqtt_processor:info(client_id, ProcState), ConnName, Reason]),
 | 
				
			||||||
        conn_name = ConnName
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    ?LOG_WARNING(
 | 
					 | 
				
			||||||
        "Web MQTT disconnecting client with ID '~s' (~p), reason: ~s",
 | 
					 | 
				
			||||||
        [rabbit_mqtt_processor:info(client_id, ProcState), ConnName, Reason]
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    stop(State);
 | 
					    stop(State);
 | 
				
			||||||
websocket_info({'$gen_cast', {force_event_refresh, Ref}}, State0) ->
 | 
					websocket_info({'$gen_cast', {force_event_refresh, Ref}}, State0) ->
 | 
				
			||||||
    Infos = infos(?CREATION_EVENT_KEYS, State0),
 | 
					    Infos = infos(?CREATION_EVENT_KEYS, State0),
 | 
				
			||||||
    rabbit_event:notify(connection_created, Infos, Ref),
 | 
					    rabbit_event:notify(connection_created, Infos, Ref),
 | 
				
			||||||
    State = rabbit_event:init_stats_timer(State0, #state.stats_timer),
 | 
					    State = rabbit_event:init_stats_timer(State0, #state.stats_timer),
 | 
				
			||||||
    {[], State, hibernate};
 | 
					    {[], State, hibernate};
 | 
				
			||||||
websocket_info(
 | 
					websocket_info({'$gen_cast', refresh_config},
 | 
				
			||||||
    {'$gen_cast', refresh_config},
 | 
					               State0 = #state{proc_state = PState0,
 | 
				
			||||||
    State0 = #state{
 | 
					                               conn_name = ConnName}) ->
 | 
				
			||||||
        proc_state = PState0,
 | 
					 | 
				
			||||||
        conn_name = ConnName
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    PState = rabbit_mqtt_processor:update_trace(ConnName, PState0),
 | 
					    PState = rabbit_mqtt_processor:update_trace(ConnName, PState0),
 | 
				
			||||||
    State = State0#state{proc_state = PState},
 | 
					    State = State0#state{proc_state = PState},
 | 
				
			||||||
    {[], State, hibernate};
 | 
					    {[], State, hibernate};
 | 
				
			||||||
websocket_info(
 | 
					websocket_info({keepalive, Req}, State = #state{keepalive = KState0,
 | 
				
			||||||
    {keepalive, Req},
 | 
					                                                conn_name = ConnName}) ->
 | 
				
			||||||
    State = #state{
 | 
					 | 
				
			||||||
        keepalive = KState0,
 | 
					 | 
				
			||||||
        conn_name = ConnName
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    case rabbit_mqtt_keepalive:handle(Req, KState0) of
 | 
					    case rabbit_mqtt_keepalive:handle(Req, KState0) of
 | 
				
			||||||
        {ok, KState} ->
 | 
					        {ok, KState} ->
 | 
				
			||||||
            {[], State#state{keepalive = KState}, hibernate};
 | 
					            {[], State#state{keepalive = KState}, hibernate};
 | 
				
			||||||
| 
						 | 
					@ -231,24 +189,18 @@ websocket_info(
 | 
				
			||||||
            ?LOG_ERROR("keepalive timeout in Web MQTT connection ~p", [ConnName]),
 | 
					            ?LOG_ERROR("keepalive timeout in Web MQTT connection ~p", [ConnName]),
 | 
				
			||||||
            stop(State, ?CLOSE_NORMAL, <<"MQTT keepalive timeout">>);
 | 
					            stop(State, ?CLOSE_NORMAL, <<"MQTT keepalive timeout">>);
 | 
				
			||||||
        {error, Reason} ->
 | 
					        {error, Reason} ->
 | 
				
			||||||
            ?LOG_ERROR(
 | 
					            ?LOG_ERROR("keepalive error in Web MQTT connection ~p: ~p",
 | 
				
			||||||
                "keepalive error in Web MQTT connection ~p: ~p",
 | 
					                       [ConnName, Reason]),
 | 
				
			||||||
                [ConnName, Reason]
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            stop(State)
 | 
					            stop(State)
 | 
				
			||||||
    end;
 | 
					    end;
 | 
				
			||||||
websocket_info(emit_stats, State) ->
 | 
					websocket_info(emit_stats, State) ->
 | 
				
			||||||
    {[], emit_stats(State), hibernate};
 | 
					    {[], emit_stats(State), hibernate};
 | 
				
			||||||
websocket_info(
 | 
					websocket_info({ra_event, _From, Evt},
 | 
				
			||||||
    {ra_event, _From, Evt},
 | 
					               #state{proc_state = PState0} = State) ->
 | 
				
			||||||
    #state{proc_state = PState0} = State
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    PState = rabbit_mqtt_processor:handle_ra_event(Evt, PState0),
 | 
					    PState = rabbit_mqtt_processor:handle_ra_event(Evt, PState0),
 | 
				
			||||||
    {[], State#state{proc_state = PState}, hibernate};
 | 
					    {[], State#state{proc_state = PState}, hibernate};
 | 
				
			||||||
websocket_info(
 | 
					websocket_info({{'DOWN', _QName}, _MRef, process, _Pid, _Reason} = Evt,
 | 
				
			||||||
    {{'DOWN', _QName}, _MRef, process, _Pid, _Reason} = Evt,
 | 
					               State = #state{proc_state = PState0}) ->
 | 
				
			||||||
    State = #state{proc_state = PState0}
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    case rabbit_mqtt_processor:handle_down(Evt, PState0) of
 | 
					    case rabbit_mqtt_processor:handle_down(Evt, PState0) of
 | 
				
			||||||
        {ok, PState} ->
 | 
					        {ok, PState} ->
 | 
				
			||||||
            handle_credits(State#state{proc_state = PState});
 | 
					            handle_credits(State#state{proc_state = PState});
 | 
				
			||||||
| 
						 | 
					@ -275,16 +227,10 @@ terminate(_Reason, _Req, #state{proc_state = undefined}) ->
 | 
				
			||||||
    ok;
 | 
					    ok;
 | 
				
			||||||
terminate(Reason, Request, #state{} = State) ->
 | 
					terminate(Reason, Request, #state{} = State) ->
 | 
				
			||||||
    terminate(Reason, Request, {true, State});
 | 
					    terminate(Reason, Request, {true, State});
 | 
				
			||||||
terminate(
 | 
					terminate(_Reason, _Request,
 | 
				
			||||||
    _Reason,
 | 
					          {SendWill, #state{conn_name = ConnName,
 | 
				
			||||||
    _Request,
 | 
					                            proc_state = PState,
 | 
				
			||||||
    {SendWill,
 | 
					                            keepalive = KState} = State}) ->
 | 
				
			||||||
        #state{
 | 
					 | 
				
			||||||
            conn_name = ConnName,
 | 
					 | 
				
			||||||
            proc_state = PState,
 | 
					 | 
				
			||||||
            keepalive = KState
 | 
					 | 
				
			||||||
        } = State}
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    ?LOG_INFO("Web MQTT closing connection ~ts", [ConnName]),
 | 
					    ?LOG_INFO("Web MQTT closing connection ~ts", [ConnName]),
 | 
				
			||||||
    maybe_emit_stats(State),
 | 
					    maybe_emit_stats(State),
 | 
				
			||||||
    _ = rabbit_mqtt_keepalive:cancel_timer(KState),
 | 
					    _ = rabbit_mqtt_keepalive:cancel_timer(KState),
 | 
				
			||||||
| 
						 | 
					@ -306,49 +252,29 @@ handle_data(Data, State0 = #state{}) ->
 | 
				
			||||||
            Other
 | 
					            Other
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_data1(
 | 
					handle_data1(<<>>, State0 = #state{received_connect_packet = false,
 | 
				
			||||||
    <<>>,
 | 
					                                   proc_state = PState,
 | 
				
			||||||
    State0 = #state{
 | 
					                                   conn_name = ConnName}) ->
 | 
				
			||||||
        received_connect_packet = false,
 | 
					    ?LOG_INFO("Accepted web MQTT connection ~p (~s, client ID: ~s)",
 | 
				
			||||||
        proc_state = PState,
 | 
					              [self(), ConnName, rabbit_mqtt_processor:info(client_id, PState)]),
 | 
				
			||||||
        conn_name = ConnName
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    ?LOG_INFO(
 | 
					 | 
				
			||||||
        "Accepted web MQTT connection ~p (~s, client ID: ~s)",
 | 
					 | 
				
			||||||
        [self(), ConnName, rabbit_mqtt_processor:info(client_id, PState)]
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    State = State0#state{received_connect_packet = true},
 | 
					    State = State0#state{received_connect_packet = true},
 | 
				
			||||||
    {ok, ensure_stats_timer(control_throttle(State)), hibernate};
 | 
					    {ok, ensure_stats_timer(control_throttle(State)), hibernate};
 | 
				
			||||||
handle_data1(<<>>, State) ->
 | 
					handle_data1(<<>>, State) ->
 | 
				
			||||||
    {ok, ensure_stats_timer(control_throttle(State)), hibernate};
 | 
					    {ok, ensure_stats_timer(control_throttle(State)), hibernate};
 | 
				
			||||||
handle_data1(
 | 
					handle_data1(Data, State = #state{ parse_state = ParseState,
 | 
				
			||||||
    Data,
 | 
					                                   proc_state  = ProcState,
 | 
				
			||||||
    State = #state{
 | 
					                                   conn_name   = ConnName }) ->
 | 
				
			||||||
        parse_state = ParseState,
 | 
					 | 
				
			||||||
        proc_state = ProcState,
 | 
					 | 
				
			||||||
        conn_name = ConnName
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    case parse(Data, ParseState) of
 | 
					    case parse(Data, ParseState) of
 | 
				
			||||||
        {more, ParseState1} ->
 | 
					        {more, ParseState1} ->
 | 
				
			||||||
            {ok,
 | 
					            {ok, ensure_stats_timer(control_throttle(
 | 
				
			||||||
                ensure_stats_timer(
 | 
					                State #state{ parse_state = ParseState1 })), hibernate};
 | 
				
			||||||
                    control_throttle(
 | 
					 | 
				
			||||||
                        State#state{parse_state = ParseState1}
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                hibernate};
 | 
					 | 
				
			||||||
        {ok, Packet, Rest} ->
 | 
					        {ok, Packet, Rest} ->
 | 
				
			||||||
            case rabbit_mqtt_processor:process_packet(Packet, ProcState) of
 | 
					            case rabbit_mqtt_processor:process_packet(Packet, ProcState) of
 | 
				
			||||||
                {ok, ProcState1} ->
 | 
					                {ok, ProcState1} ->
 | 
				
			||||||
                    handle_data1(
 | 
					                    handle_data1(
 | 
				
			||||||
                        Rest,
 | 
					                      Rest,
 | 
				
			||||||
                        State#state{
 | 
					                      State#state{parse_state = rabbit_mqtt_packet:initial_state(),
 | 
				
			||||||
                            parse_state = rabbit_mqtt_packet:initial_state(),
 | 
					                                  proc_state = ProcState1});
 | 
				
			||||||
                            proc_state = ProcState1
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    );
 | 
					 | 
				
			||||||
                {error, Reason, _} ->
 | 
					                {error, Reason, _} ->
 | 
				
			||||||
                    stop_mqtt_protocol_error(State, Reason, ConnName);
 | 
					                    stop_mqtt_protocol_error(State, Reason, ConnName);
 | 
				
			||||||
                {stop, disconnect, ProcState1} ->
 | 
					                {stop, disconnect, ProcState1} ->
 | 
				
			||||||
| 
						 | 
					@ -363,11 +289,9 @@ parse(Data, ParseState) ->
 | 
				
			||||||
        rabbit_mqtt_packet:parse(Data, ParseState)
 | 
					        rabbit_mqtt_packet:parse(Data, ParseState)
 | 
				
			||||||
    catch
 | 
					    catch
 | 
				
			||||||
        _:Reason:Stacktrace ->
 | 
					        _:Reason:Stacktrace ->
 | 
				
			||||||
            ?LOG_ERROR(
 | 
					            ?LOG_ERROR("Web MQTT cannot parse a packet, reason: ~tp, stacktrace: ~tp, "
 | 
				
			||||||
                "Web MQTT cannot parse a packet, reason: ~tp, stacktrace: ~tp, "
 | 
					                       "payload (first 100 bytes): ~tp",
 | 
				
			||||||
                "payload (first 100 bytes): ~tp",
 | 
					                       [Reason, Stacktrace, rabbit_mqtt_util:truncate_binary(Data, 100)]),
 | 
				
			||||||
                [Reason, Stacktrace, rabbit_mqtt_util:truncate_binary(Data, 100)]
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            {error, cannot_parse}
 | 
					            {error, cannot_parse}
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -384,34 +308,26 @@ stop(State, CloseCode, Error0) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handle_credits(State0) ->
 | 
					handle_credits(State0) ->
 | 
				
			||||||
    State = #state{connection_state = CS} = control_throttle(State0),
 | 
					    State = #state{connection_state = CS} = control_throttle(State0),
 | 
				
			||||||
    Active =
 | 
					    Active = case CS of
 | 
				
			||||||
        case CS of
 | 
					                 running -> true;
 | 
				
			||||||
            running -> true;
 | 
					                 blocked -> false
 | 
				
			||||||
            blocked -> false
 | 
					             end,
 | 
				
			||||||
        end,
 | 
					 | 
				
			||||||
    {[{active, Active}], State, hibernate}.
 | 
					    {[{active, Active}], State, hibernate}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
control_throttle(
 | 
					control_throttle(State = #state{connection_state = ConnState,
 | 
				
			||||||
    State = #state{
 | 
					                                conserve = Conserve,
 | 
				
			||||||
        connection_state = ConnState,
 | 
					                                received_connect_packet = Connected,
 | 
				
			||||||
        conserve = Conserve,
 | 
					                                proc_state = PState,
 | 
				
			||||||
        received_connect_packet = Connected,
 | 
					                                keepalive = KState
 | 
				
			||||||
        proc_state = PState,
 | 
					                               }) ->
 | 
				
			||||||
        keepalive = KState
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
) ->
 | 
					 | 
				
			||||||
    Throttle = rabbit_mqtt_processor:throttle(Conserve, Connected, PState),
 | 
					    Throttle = rabbit_mqtt_processor:throttle(Conserve, Connected, PState),
 | 
				
			||||||
    case {ConnState, Throttle} of
 | 
					    case {ConnState, Throttle} of
 | 
				
			||||||
        {running, true} ->
 | 
					        {running, true} ->
 | 
				
			||||||
            State#state{
 | 
					            State#state{connection_state = blocked,
 | 
				
			||||||
                connection_state = blocked,
 | 
					                        keepalive = rabbit_mqtt_keepalive:cancel_timer(KState)};
 | 
				
			||||||
                keepalive = rabbit_mqtt_keepalive:cancel_timer(KState)
 | 
					        {blocked,false} ->
 | 
				
			||||||
            };
 | 
					            State#state{connection_state = running,
 | 
				
			||||||
        {blocked, false} ->
 | 
					                        keepalive = rabbit_mqtt_keepalive:start_timer(KState)};
 | 
				
			||||||
            State#state{
 | 
					 | 
				
			||||||
                connection_state = running,
 | 
					 | 
				
			||||||
                keepalive = rabbit_mqtt_keepalive:start_timer(KState)
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
        {_, _} ->
 | 
					        {_, _} ->
 | 
				
			||||||
            State
 | 
					            State
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
| 
						 | 
					@ -425,23 +341,18 @@ ensure_stats_timer(State) ->
 | 
				
			||||||
maybe_emit_stats(#state{stats_timer = undefined}) ->
 | 
					maybe_emit_stats(#state{stats_timer = undefined}) ->
 | 
				
			||||||
    ok;
 | 
					    ok;
 | 
				
			||||||
maybe_emit_stats(State) ->
 | 
					maybe_emit_stats(State) ->
 | 
				
			||||||
    rabbit_event:if_enabled(
 | 
					    rabbit_event:if_enabled(State, #state.stats_timer,
 | 
				
			||||||
        State,
 | 
					                                fun() -> emit_stats(State) end).
 | 
				
			||||||
        #state.stats_timer,
 | 
					 | 
				
			||||||
        fun() -> emit_stats(State) end
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
emit_stats(State = #state{received_connect_packet = false}) ->
 | 
					emit_stats(State=#state{received_connect_packet = false}) ->
 | 
				
			||||||
    %% Avoid emitting stats on terminate when the connection has not yet been
 | 
					    %% Avoid emitting stats on terminate when the connection has not yet been
 | 
				
			||||||
    %% established, as this causes orphan entries on the stats database
 | 
					    %% established, as this causes orphan entries on the stats database
 | 
				
			||||||
    rabbit_event:reset_stats_timer(State, #state.stats_timer);
 | 
					    rabbit_event:reset_stats_timer(State, #state.stats_timer);
 | 
				
			||||||
emit_stats(State) ->
 | 
					emit_stats(State) ->
 | 
				
			||||||
    [
 | 
					    [{_, Pid},
 | 
				
			||||||
        {_, Pid},
 | 
					     {_, RecvOct},
 | 
				
			||||||
        {_, RecvOct},
 | 
					     {_, SendOct},
 | 
				
			||||||
        {_, SendOct},
 | 
					     {_, Reductions}] = infos(?SIMPLE_METRICS, State),
 | 
				
			||||||
        {_, Reductions}
 | 
					 | 
				
			||||||
    ] = infos(?SIMPLE_METRICS, State),
 | 
					 | 
				
			||||||
    Infos = infos(?OTHER_METRICS, State),
 | 
					    Infos = infos(?OTHER_METRICS, State),
 | 
				
			||||||
    rabbit_core_metrics:connection_stats(Pid, Infos),
 | 
					    rabbit_core_metrics:connection_stats(Pid, Infos),
 | 
				
			||||||
    rabbit_core_metrics:connection_stats(Pid, RecvOct, SendOct, Reductions),
 | 
					    rabbit_core_metrics:connection_stats(Pid, RecvOct, SendOct, Reductions),
 | 
				
			||||||
| 
						 | 
					@ -453,13 +364,12 @@ infos(Items, State) ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
i(pid, _) ->
 | 
					i(pid, _) ->
 | 
				
			||||||
    self();
 | 
					    self();
 | 
				
			||||||
i(SockStat, #state{socket = Sock}) when
 | 
					i(SockStat, #state{socket = Sock})
 | 
				
			||||||
    SockStat =:= recv_oct;
 | 
					  when SockStat =:= recv_oct;
 | 
				
			||||||
    SockStat =:= recv_cnt;
 | 
					       SockStat =:= recv_cnt;
 | 
				
			||||||
    SockStat =:= send_oct;
 | 
					       SockStat =:= send_oct;
 | 
				
			||||||
    SockStat =:= send_cnt;
 | 
					       SockStat =:= send_cnt;
 | 
				
			||||||
    SockStat =:= send_pend
 | 
					       SockStat =:= send_pend ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    case rabbit_net:getstat(Sock, [SockStat]) of
 | 
					    case rabbit_net:getstat(Sock, [SockStat]) of
 | 
				
			||||||
        {ok, [{_, N}]} when is_number(N) ->
 | 
					        {ok, [{_, N}]} when is_number(N) ->
 | 
				
			||||||
            N;
 | 
					            N;
 | 
				
			||||||
| 
						 | 
					@ -473,23 +383,22 @@ i(garbage_collection, _) ->
 | 
				
			||||||
    rabbit_misc:get_gc_info(self());
 | 
					    rabbit_misc:get_gc_info(self());
 | 
				
			||||||
i(protocol, #state{proc_state = PState}) ->
 | 
					i(protocol, #state{proc_state = PState}) ->
 | 
				
			||||||
    {?PROTO_FAMILY, rabbit_mqtt_processor:proto_version_tuple(PState)};
 | 
					    {?PROTO_FAMILY, rabbit_mqtt_processor:proto_version_tuple(PState)};
 | 
				
			||||||
i(SSL, #state{socket = Sock}) when
 | 
					i(SSL, #state{socket = Sock})
 | 
				
			||||||
    SSL =:= ssl;
 | 
					  when SSL =:= ssl;
 | 
				
			||||||
    SSL =:= ssl_protocol;
 | 
					       SSL =:= ssl_protocol;
 | 
				
			||||||
    SSL =:= ssl_key_exchange;
 | 
					       SSL =:= ssl_key_exchange;
 | 
				
			||||||
    SSL =:= ssl_cipher;
 | 
					       SSL =:= ssl_cipher;
 | 
				
			||||||
    SSL =:= ssl_hash
 | 
					       SSL =:= ssl_hash ->
 | 
				
			||||||
->
 | 
					    rabbit_ssl:info(SSL, {rabbit_net:unwrap_socket(Sock),
 | 
				
			||||||
    rabbit_ssl:info(SSL, {rabbit_net:unwrap_socket(Sock), rabbit_net:maybe_get_proxy_socket(Sock)});
 | 
					                          rabbit_net:maybe_get_proxy_socket(Sock)});
 | 
				
			||||||
i(name, S) ->
 | 
					i(name, S) ->
 | 
				
			||||||
    i(conn_name, S);
 | 
					    i(conn_name, S);
 | 
				
			||||||
i(conn_name, #state{conn_name = Val}) ->
 | 
					i(conn_name, #state{conn_name = Val}) ->
 | 
				
			||||||
    Val;
 | 
					    Val;
 | 
				
			||||||
i(Cert, #state{socket = Sock}) when
 | 
					i(Cert, #state{socket = Sock})
 | 
				
			||||||
    Cert =:= peer_cert_issuer;
 | 
					  when Cert =:= peer_cert_issuer;
 | 
				
			||||||
    Cert =:= peer_cert_subject;
 | 
					       Cert =:= peer_cert_subject;
 | 
				
			||||||
    Cert =:= peer_cert_validity
 | 
					       Cert =:= peer_cert_validity ->
 | 
				
			||||||
->
 | 
					 | 
				
			||||||
    rabbit_ssl:cert_info(Cert, rabbit_net:unwrap_socket(Sock));
 | 
					    rabbit_ssl:cert_info(Cert, rabbit_net:unwrap_socket(Sock));
 | 
				
			||||||
i(state, S) ->
 | 
					i(state, S) ->
 | 
				
			||||||
    i(connection_state, S);
 | 
					    i(connection_state, S);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,7 @@
 | 
				
			||||||
-export([terminate/3]).
 | 
					-export([terminate/3]).
 | 
				
			||||||
-export([early_error/5]).
 | 
					-export([early_error/5]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-record(state, {next}).
 | 
					-record(state, {next}).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
init(StreamID, Req, Opts) ->
 | 
					init(StreamID, Req, Opts) ->
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,7 @@ init_per_suite(Config) ->
 | 
				
			||||||
    Config1 = rabbit_ct_helpers:run_setup_steps(Config),
 | 
					    Config1 = rabbit_ct_helpers:run_setup_steps(Config),
 | 
				
			||||||
    rabbit_ct_config_schema:init_schemas(rabbitmq_web_mqtt, Config1).
 | 
					    rabbit_ct_config_schema:init_schemas(rabbitmq_web_mqtt, Config1).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
end_per_suite(Config) ->
 | 
					end_per_suite(Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:run_teardown_steps(Config).
 | 
					    rabbit_ct_helpers:run_teardown_steps(Config).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,19 +31,15 @@ init_per_testcase(Testcase, Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:testcase_started(Config, Testcase),
 | 
					    rabbit_ct_helpers:testcase_started(Config, Testcase),
 | 
				
			||||||
    Config1 = rabbit_ct_helpers:set_config(Config, [
 | 
					    Config1 = rabbit_ct_helpers:set_config(Config, [
 | 
				
			||||||
        {rmq_nodename_suffix, Testcase}
 | 
					        {rmq_nodename_suffix, Testcase}
 | 
				
			||||||
    ]),
 | 
					      ]),
 | 
				
			||||||
    rabbit_ct_helpers:run_steps(
 | 
					    rabbit_ct_helpers:run_steps(Config1,
 | 
				
			||||||
        Config1,
 | 
					      rabbit_ct_broker_helpers:setup_steps() ++
 | 
				
			||||||
        rabbit_ct_broker_helpers:setup_steps() ++
 | 
					      rabbit_ct_client_helpers:setup_steps()).
 | 
				
			||||||
            rabbit_ct_client_helpers:setup_steps()
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
end_per_testcase(Testcase, Config) ->
 | 
					end_per_testcase(Testcase, Config) ->
 | 
				
			||||||
    Config1 = rabbit_ct_helpers:run_steps(
 | 
					    Config1 = rabbit_ct_helpers:run_steps(Config,
 | 
				
			||||||
        Config,
 | 
					      rabbit_ct_client_helpers:teardown_steps() ++
 | 
				
			||||||
        rabbit_ct_client_helpers:teardown_steps() ++
 | 
					      rabbit_ct_broker_helpers:teardown_steps()),
 | 
				
			||||||
            rabbit_ct_broker_helpers:teardown_steps()
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    rabbit_ct_helpers:testcase_finished(Config1, Testcase).
 | 
					    rabbit_ct_helpers:testcase_finished(Config1, Testcase).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
%% -------------------------------------------------------------------
 | 
					%% -------------------------------------------------------------------
 | 
				
			||||||
| 
						 | 
					@ -50,13 +47,9 @@ end_per_testcase(Testcase, Config) ->
 | 
				
			||||||
%% -------------------------------------------------------------------
 | 
					%% -------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
run_snippets(Config) ->
 | 
					run_snippets(Config) ->
 | 
				
			||||||
    ok = rabbit_ct_broker_helpers:rpc(
 | 
					    ok = rabbit_ct_broker_helpers:rpc(Config, 0,
 | 
				
			||||||
        Config,
 | 
					      ?MODULE, run_snippets1, [Config]).
 | 
				
			||||||
        0,
 | 
					 | 
				
			||||||
        ?MODULE,
 | 
					 | 
				
			||||||
        run_snippets1,
 | 
					 | 
				
			||||||
        [Config]
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
run_snippets1(Config) ->
 | 
					run_snippets1(Config) ->
 | 
				
			||||||
    rabbit_ct_config_schema:run_snippets(Config).
 | 
					    rabbit_ct_config_schema:run_snippets(Config).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-module(proxy_protocol_SUITE).
 | 
					-module(proxy_protocol_SUITE).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-compile([export_all, nowarn_export_all]).
 | 
					-compile([export_all, nowarn_export_all]).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-include_lib("common_test/include/ct.hrl").
 | 
					-include_lib("common_test/include/ct.hrl").
 | 
				
			||||||
| 
						 | 
					@ -14,24 +15,20 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
suite() ->
 | 
					suite() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        %% If a test hangs, no need to wait for 30 minutes.
 | 
					      %% If a test hangs, no need to wait for 30 minutes.
 | 
				
			||||||
        {timetrap, {minutes, 2}}
 | 
					      {timetrap, {minutes, 2}}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
all() ->
 | 
					all() ->
 | 
				
			||||||
    [
 | 
					    [{group, http_tests},
 | 
				
			||||||
        {group, http_tests},
 | 
					     {group, https_tests}].
 | 
				
			||||||
        {group, https_tests}
 | 
					 | 
				
			||||||
    ].
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
groups() ->
 | 
					groups() ->
 | 
				
			||||||
    Tests = [
 | 
					    Tests = [
 | 
				
			||||||
        proxy_protocol
 | 
					        proxy_protocol
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    [
 | 
					    [{https_tests, [], Tests},
 | 
				
			||||||
        {https_tests, [], Tests},
 | 
					     {http_tests, [], Tests}].
 | 
				
			||||||
        {http_tests, [], Tests}
 | 
					 | 
				
			||||||
    ].
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
init_per_suite(Config) ->
 | 
					init_per_suite(Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:log_environment(),
 | 
					    rabbit_ct_helpers:log_environment(),
 | 
				
			||||||
| 
						 | 
					@ -41,29 +38,22 @@ end_per_suite(Config) ->
 | 
				
			||||||
    Config.
 | 
					    Config.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
init_per_group(Group, Config) ->
 | 
					init_per_group(Group, Config) ->
 | 
				
			||||||
    Protocol =
 | 
					    Protocol = case Group of
 | 
				
			||||||
        case Group of
 | 
					        http_tests -> "ws";
 | 
				
			||||||
            http_tests -> "ws";
 | 
					        https_tests -> "wss"
 | 
				
			||||||
            https_tests -> "wss"
 | 
					    end,
 | 
				
			||||||
        end,
 | 
					    Config1 = rabbit_ct_helpers:set_config(Config,
 | 
				
			||||||
    Config1 = rabbit_ct_helpers:set_config(
 | 
					                                           [{rmq_nodename_suffix, ?MODULE},
 | 
				
			||||||
        Config,
 | 
					                                            {protocol, Protocol},
 | 
				
			||||||
        [
 | 
					                                            {rabbitmq_ct_tls_verify, verify_none},
 | 
				
			||||||
            {rmq_nodename_suffix, ?MODULE},
 | 
					                                            {rabbitmq_ct_tls_fail_if_no_peer_cert, false}]),
 | 
				
			||||||
            {protocol, Protocol},
 | 
					 | 
				
			||||||
            {rabbitmq_ct_tls_verify, verify_none},
 | 
					 | 
				
			||||||
            {rabbitmq_ct_tls_fail_if_no_peer_cert, false}
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    rabbit_ct_helpers:run_setup_steps(
 | 
					    rabbit_ct_helpers:run_setup_steps(
 | 
				
			||||||
        Config1,
 | 
					        Config1,
 | 
				
			||||||
        rabbit_ct_broker_helpers:setup_steps() ++
 | 
					        rabbit_ct_broker_helpers:setup_steps() ++ [
 | 
				
			||||||
            [
 | 
					            fun configure_proxy_protocol/1,
 | 
				
			||||||
                fun configure_proxy_protocol/1,
 | 
					            fun configure_ssl/1
 | 
				
			||||||
                fun configure_ssl/1
 | 
					        ]).
 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
configure_proxy_protocol(Config) ->
 | 
					configure_proxy_protocol(Config) ->
 | 
				
			||||||
    rabbit_ws_test_util:update_app_env(Config, proxy_protocol, true),
 | 
					    rabbit_ws_test_util:update_app_env(Config, proxy_protocol, true),
 | 
				
			||||||
| 
						 | 
					@ -74,16 +64,12 @@ configure_ssl(Config) ->
 | 
				
			||||||
    RabbitAppConfig = proplists:get_value(rabbit, ErlangConfig, []),
 | 
					    RabbitAppConfig = proplists:get_value(rabbit, ErlangConfig, []),
 | 
				
			||||||
    RabbitSslConfig = proplists:get_value(ssl_options, RabbitAppConfig, []),
 | 
					    RabbitSslConfig = proplists:get_value(ssl_options, RabbitAppConfig, []),
 | 
				
			||||||
    Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_web_mqtt_tls),
 | 
					    Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_web_mqtt_tls),
 | 
				
			||||||
    rabbit_ws_test_util:update_app_env(Config, ssl_config, [
 | 
					    rabbit_ws_test_util:update_app_env(Config, ssl_config, [{port, Port} | lists:keydelete(port, 1, RabbitSslConfig)]),
 | 
				
			||||||
        {port, Port} | lists:keydelete(port, 1, RabbitSslConfig)
 | 
					 | 
				
			||||||
    ]),
 | 
					 | 
				
			||||||
    Config.
 | 
					    Config.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
end_per_group(_Group, Config) ->
 | 
					end_per_group(_Group, Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:run_teardown_steps(
 | 
					    rabbit_ct_helpers:run_teardown_steps(Config,
 | 
				
			||||||
        Config,
 | 
					      rabbit_ct_broker_helpers:teardown_steps()).
 | 
				
			||||||
        rabbit_ct_broker_helpers:teardown_steps()
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
init_per_testcase(Testcase, Config) ->
 | 
					init_per_testcase(Testcase, Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:testcase_started(Config, Testcase).
 | 
					    rabbit_ct_helpers:testcase_started(Config, Testcase).
 | 
				
			||||||
| 
						 | 
					@ -95,23 +81,13 @@ proxy_protocol(Config) ->
 | 
				
			||||||
    PortStr = rabbit_ws_test_util:get_web_mqtt_port_str(Config),
 | 
					    PortStr = rabbit_ws_test_util:get_web_mqtt_port_str(Config),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Protocol = ?config(protocol, Config),
 | 
					    Protocol = ?config(protocol, Config),
 | 
				
			||||||
    WS = rfc6455_client:new(
 | 
					    WS = rfc6455_client:new(Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws", self(),
 | 
				
			||||||
        Protocol ++ "://127.0.0.1:" ++ PortStr ++ "/ws",
 | 
					        undefined, ["mqtt"], "PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"),
 | 
				
			||||||
        self(),
 | 
					 | 
				
			||||||
        undefined,
 | 
					 | 
				
			||||||
        ["mqtt"],
 | 
					 | 
				
			||||||
        "PROXY TCP4 192.168.1.1 192.168.1.2 80 81\r\n"
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    {ok, _} = rfc6455_client:open(WS),
 | 
					    {ok, _} = rfc6455_client:open(WS),
 | 
				
			||||||
    rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()),
 | 
					    rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()),
 | 
				
			||||||
    {binary, _P} = rfc6455_client:recv(WS),
 | 
					    {binary, _P} = rfc6455_client:recv(WS),
 | 
				
			||||||
    ConnectionName = rabbit_ct_broker_helpers:rpc(
 | 
					    ConnectionName = rabbit_ct_broker_helpers:rpc(Config, 0,
 | 
				
			||||||
        Config,
 | 
					        ?MODULE, connection_name, []),
 | 
				
			||||||
        0,
 | 
					 | 
				
			||||||
        ?MODULE,
 | 
					 | 
				
			||||||
        connection_name,
 | 
					 | 
				
			||||||
        []
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    match = re:run(ConnectionName, <<"^192.168.1.1:80 -> 192.168.1.2:81$">>, [{capture, none}]),
 | 
					    match = re:run(ConnectionName, <<"^192.168.1.1:80 -> 192.168.1.2:81$">>, [{capture, none}]),
 | 
				
			||||||
    {close, _} = rfc6455_client:close(WS),
 | 
					    {close, _} = rfc6455_client:close(WS),
 | 
				
			||||||
    ok.
 | 
					    ok.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,13 +18,13 @@ all() ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
groups() ->
 | 
					groups() ->
 | 
				
			||||||
    [
 | 
					    [
 | 
				
			||||||
        {tests, [], [
 | 
					     {tests, [],
 | 
				
			||||||
            no_websocket_subprotocol,
 | 
					      [no_websocket_subprotocol
 | 
				
			||||||
            unsupported_websocket_subprotocol,
 | 
					       ,unsupported_websocket_subprotocol
 | 
				
			||||||
            unacceptable_data_type,
 | 
					       ,unacceptable_data_type
 | 
				
			||||||
            handle_invalid_packets,
 | 
					       ,handle_invalid_packets
 | 
				
			||||||
            duplicate_connect
 | 
					       ,duplicate_connect
 | 
				
			||||||
        ]}
 | 
					      ]}
 | 
				
			||||||
    ].
 | 
					    ].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
suite() ->
 | 
					suite() ->
 | 
				
			||||||
| 
						 | 
					@ -35,19 +35,15 @@ init_per_suite(Config) ->
 | 
				
			||||||
    Config1 = rabbit_ct_helpers:set_config(Config, [
 | 
					    Config1 = rabbit_ct_helpers:set_config(Config, [
 | 
				
			||||||
        {rmq_nodename_suffix, ?MODULE},
 | 
					        {rmq_nodename_suffix, ?MODULE},
 | 
				
			||||||
        {protocol, "ws"}
 | 
					        {protocol, "ws"}
 | 
				
			||||||
    ]),
 | 
					      ]),
 | 
				
			||||||
    rabbit_ct_helpers:run_setup_steps(
 | 
					    rabbit_ct_helpers:run_setup_steps(Config1,
 | 
				
			||||||
        Config1,
 | 
					      rabbit_ct_broker_helpers:setup_steps() ++
 | 
				
			||||||
        rabbit_ct_broker_helpers:setup_steps() ++
 | 
					      rabbit_ct_client_helpers:setup_steps()).
 | 
				
			||||||
            rabbit_ct_client_helpers:setup_steps()
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
end_per_suite(Config) ->
 | 
					end_per_suite(Config) ->
 | 
				
			||||||
    rabbit_ct_helpers:run_teardown_steps(
 | 
					    rabbit_ct_helpers:run_teardown_steps(Config,
 | 
				
			||||||
        Config,
 | 
					      rabbit_ct_client_helpers:teardown_steps() ++
 | 
				
			||||||
        rabbit_ct_client_helpers:teardown_steps() ++
 | 
					      rabbit_ct_broker_helpers:teardown_steps()).
 | 
				
			||||||
            rabbit_ct_broker_helpers:teardown_steps()
 | 
					 | 
				
			||||||
    ).
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
init_per_group(_, Config) ->
 | 
					init_per_group(_, Config) ->
 | 
				
			||||||
    Config.
 | 
					    Config.
 | 
				
			||||||
| 
						 | 
					@ -76,9 +72,7 @@ websocket_subprotocol(Config, SubProtocol) ->
 | 
				
			||||||
    PortStr = rabbit_ws_test_util:get_web_mqtt_port_str(Config),
 | 
					    PortStr = rabbit_ws_test_util:get_web_mqtt_port_str(Config),
 | 
				
			||||||
    WS = rfc6455_client:new("ws://localhost:" ++ PortStr ++ "/ws", self(), undefined, SubProtocol),
 | 
					    WS = rfc6455_client:new("ws://localhost:" ++ PortStr ++ "/ws", self(), undefined, SubProtocol),
 | 
				
			||||||
    {_, [{http_response, Res}]} = rfc6455_client:open(WS),
 | 
					    {_, [{http_response, Res}]} = rfc6455_client:open(WS),
 | 
				
			||||||
    {'HTTP/1.1', 400, <<"Bad Request">>, _} = cow_http:parse_status_line(
 | 
					    {'HTTP/1.1', 400, <<"Bad Request">>, _} = cow_http:parse_status_line(rabbit_data_coercion:to_binary(Res)),
 | 
				
			||||||
        rabbit_data_coercion:to_binary(Res)
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()),
 | 
					    rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()),
 | 
				
			||||||
    {close, _} = rfc6455_client:recv(WS, timer:seconds(1)).
 | 
					    {close, _} = rfc6455_client:recv(WS, timer:seconds(1)).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -116,8 +110,7 @@ duplicate_connect(Config) ->
 | 
				
			||||||
    process_flag(trap_exit, true),
 | 
					    process_flag(trap_exit, true),
 | 
				
			||||||
    rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()),
 | 
					    rfc6455_client:send_binary(WS, rabbit_ws_test_util:mqtt_3_1_1_connect_packet()),
 | 
				
			||||||
    eventually(?_assertEqual(0, num_mqtt_connections(Config, 0))),
 | 
					    eventually(?_assertEqual(0, num_mqtt_connections(Config, 0))),
 | 
				
			||||||
    receive
 | 
					    receive {'EXIT', WS, _} -> ok
 | 
				
			||||||
        {'EXIT', WS, _} -> ok
 | 
					 | 
				
			||||||
    after 500 -> ct:fail("expected web socket to exit")
 | 
					    after 500 -> ct:fail("expected web socket to exit")
 | 
				
			||||||
    end.
 | 
					    end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,10 +14,3 @@
 | 
				
			||||||
                inline_items => {when_under, 4}
 | 
					                inline_items => {when_under, 4}
 | 
				
			||||||
               }}
 | 
					               }}
 | 
				
			||||||
]}.
 | 
					]}.
 | 
				
			||||||
 | 
					 | 
				
			||||||
{project_plugins, [erlfmt]}.
 | 
					 | 
				
			||||||
{erlfmt, [
 | 
					 | 
				
			||||||
    write,
 | 
					 | 
				
			||||||
    {print_width, 100},
 | 
					 | 
				
			||||||
    {files, "deps/{rabbitmq_mqtt,rabbitmq_web_mqtt}/{test,src}/*.erl"}
 | 
					 | 
				
			||||||
]}.
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue