%%%-------------------------------------------------------------------
%%% Author  : Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% Created : 18 Oct 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2025   ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------

-module(privacy_tests).

%% API
-compile(export_all).
-import(suite, [disconnect/1, send_recv/2, get_event/1, put_event/2,
		recv_iq/1, recv_presence/1, recv_message/1, recv/1,
		send/2, my_jid/1, server_jid/1, get_features/1,
		set_roster/3, del_roster/1, get_roster/1]).
-include("suite.hrl").
-include("mod_roster.hrl").

%%%===================================================================
%%% API
%%%===================================================================
%%%===================================================================
%%% Single cases
%%%===================================================================
single_cases() ->
    {privacy_single, [sequence],
     [single_test(feature_enabled),
      single_test(set_get_list),
      single_test(get_list_non_existent),
      single_test(get_empty_lists),
      single_test(set_default),
      single_test(del_default),
      single_test(set_default_non_existent),
      single_test(set_active),
      single_test(del_active),
      single_test(set_active_non_existent),
      single_test(remove_list),
      single_test(remove_default_list),
      single_test(remove_active_list),
      single_test(remove_list_non_existent),
      single_test(allow_local_server),
      single_test(malformed_iq_query),
      single_test(malformed_get),
      single_test(malformed_set),
      single_test(malformed_type_value),
      single_test(set_get_block)]}.

feature_enabled(Config) ->
    Features = get_features(Config),
    true = lists:member(?NS_PRIVACY, Features),
    true = lists:member(?NS_BLOCKING, Features),
    disconnect(Config).

set_get_list(Config) ->
    ListName = <<"set-get-list">>,
    Items = [#privacy_item{order = 0, action = deny,
			   type = jid, value = <<"user@jabber.org">>,
			   iq = true},
	     #privacy_item{order = 1, action = allow,
			   type = group, value = <<"group">>,
			   message = true},
	     #privacy_item{order = 2, action = allow,
			   type = subscription, value = <<"both">>,
			   presence_in = true},
	     #privacy_item{order = 3, action = deny,
			   type = subscription, value = <<"from">>,
			   presence_out = true},
	     #privacy_item{order = 4, action = deny,
			   type = subscription, value = <<"to">>,
			   iq = true, message = true},
	     #privacy_item{order = 5, action = deny,
			   type = subscription, value = <<"none">>,
			   _ = true},
	     #privacy_item{order = 6, action = deny}],
    ok = set_items(Config, ListName, Items),
    #privacy_list{name = ListName, items = Items1} = get_list(Config, ListName),
    Items = lists:keysort(#privacy_item.order, Items1),
    del_privacy(disconnect(Config)).

get_list_non_existent(Config) ->
    ListName = <<"get-list-non-existent">>,
    #stanza_error{reason = 'item-not-found'} = get_list(Config, ListName),
    disconnect(Config).

get_empty_lists(Config) ->
    #privacy_query{default = none,
		   active = none,
		   lists = []} = get_lists(Config),
    disconnect(Config).

set_default(Config) ->
    ListName = <<"set-default">>,
    Item = #privacy_item{order = 0, action = deny},
    ok = set_items(Config, ListName, [Item]),
    ok = set_default(Config, ListName),
    #privacy_query{default = ListName} = get_lists(Config),
    del_privacy(disconnect(Config)).

del_default(Config) ->
    ListName = <<"del-default">>,
    Item = #privacy_item{order = 0, action = deny},
    ok = set_items(Config, ListName, [Item]),
    ok = set_default(Config, ListName),
    #privacy_query{default = ListName} = get_lists(Config),
    ok = set_default(Config, none),
    #privacy_query{default = none} = get_lists(Config),
    del_privacy(disconnect(Config)).

set_default_non_existent(Config) ->
    ListName = <<"set-default-non-existent">>,
    #stanza_error{reason = 'item-not-found'} = set_default(Config, ListName),
    disconnect(Config).

set_active(Config) ->
    ListName = <<"set-active">>,
    Item = #privacy_item{order = 0, action = deny},
    ok = set_items(Config, ListName, [Item]),
    ok = set_active(Config, ListName),
    #privacy_query{active = ListName} = get_lists(Config),
    del_privacy(disconnect(Config)).

del_active(Config) ->
    ListName = <<"del-active">>,
    Item = #privacy_item{order = 0, action = deny},
    ok = set_items(Config, ListName, [Item]),
    ok = set_active(Config, ListName),
    #privacy_query{active = ListName} = get_lists(Config),
    ok = set_active(Config, none),
    #privacy_query{active = none} = get_lists(Config),
    del_privacy(disconnect(Config)).

set_active_non_existent(Config) ->
    ListName = <<"set-active-non-existent">>,
    #stanza_error{reason = 'item-not-found'} = set_active(Config, ListName),
    disconnect(Config).

remove_list(Config) ->
    ListName = <<"remove-list">>,
    Item = #privacy_item{order = 0, action = deny},
    ok = set_items(Config, ListName, [Item]),
    ok = del_list(Config, ListName),
    #privacy_query{lists = []} = get_lists(Config),
    del_privacy(disconnect(Config)).

remove_active_list(Config) ->
    ListName = <<"remove-active-list">>,
    Item = #privacy_item{order = 0, action = deny},
    ok = set_items(Config, ListName, [Item]),
    ok = set_active(Config, ListName),
    #stanza_error{reason = 'conflict'} = del_list(Config, ListName),
    del_privacy(disconnect(Config)).

remove_default_list(Config) ->
    ListName = <<"remove-default-list">>,
    Item = #privacy_item{order = 0, action = deny},
    ok = set_items(Config, ListName, [Item]),
    ok = set_default(Config, ListName),
    #stanza_error{reason = 'conflict'} = del_list(Config, ListName),
    del_privacy(disconnect(Config)).

remove_list_non_existent(Config) ->
    ListName = <<"remove-list-non-existent">>,
    #stanza_error{reason = 'item-not-found'} = del_list(Config, ListName),
    disconnect(Config).

allow_local_server(Config) ->
    ListName = <<"allow-local-server">>,
    Item = #privacy_item{order = 0, action = deny},
    ok = set_items(Config, ListName, [Item]),
    ok = set_active(Config, ListName),
    %% Whatever privacy rules are set, we should always communicate
    %% with our home server
    server_send_iqs(Config),
    server_recv_iqs(Config),
    send_stanzas_to_server_resource(Config),
    del_privacy(disconnect(Config)).

malformed_iq_query(Config) ->
    lists:foreach(
      fun(Type) ->
	      #iq{type = error} =
		  send_recv(Config,
			    #iq{type = Type,
				sub_els = [#privacy_list{name = <<"foo">>}]})
      end, [get, set]),
    disconnect(Config).

malformed_get(Config) ->
    JID = jid:make(p1_rand:get_string()),
    Item = #block_item{jid = JID},
    lists:foreach(
      fun(SubEl) ->
	      #iq{type = error} =
		  send_recv(Config, #iq{type = get, sub_els = [SubEl]})
      end, [#privacy_query{active = none},
	    #privacy_query{default = none},
	    #privacy_query{lists = [#privacy_list{name = <<"1">>},
				    #privacy_list{name = <<"2">>}]},
	    #block{items = [Item]}, #unblock{items = [Item]},
	    #block{}, #unblock{}]),
    disconnect(Config).

malformed_set(Config) ->
    lists:foreach(
      fun(SubEl) ->
	      #iq{type = error} =
		  send_recv(Config, #iq{type = set, sub_els = [SubEl]})
      end, [#privacy_query{active = none, default = none},
	    #privacy_query{lists = [#privacy_list{name = <<"1">>},
				    #privacy_list{name = <<"2">>}]},
	    #block{},
	    #block_list{},
	    #block_list{
	       items = [#block_item{
			   jid = jid:make(p1_rand:get_string())}]}]),
    disconnect(Config).

malformed_type_value(Config) ->
    Item = #privacy_item{order = 0, action = deny},
    #stanza_error{reason = 'bad-request'} =
	set_items(Config, <<"malformed-jid">>,
		  [Item#privacy_item{type = jid, value = <<"@bad">>}]),
    #stanza_error{reason = 'bad-request'} =
	set_items(Config, <<"malformed-group">>,
		  [Item#privacy_item{type = group, value = <<"">>}]),
    #stanza_error{reason = 'bad-request'} =
	set_items(Config, <<"malformed-subscription">>,
		  [Item#privacy_item{type = subscription, value = <<"bad">>}]),
    disconnect(Config).

set_get_block(Config) ->
    J1 = jid:make(p1_rand:get_string(), p1_rand:get_string()),
    J2 = jid:make(p1_rand:get_string(), p1_rand:get_string()),
    {ok, ListName} = set_block(Config, [J1, J2]),
    JIDs = get_block(Config),
    JIDs = lists:sort([J1, J2]),
    {ok, ListName} = set_unblock(Config, [J2, J1]),
    [] = get_block(Config),
    del_privacy(disconnect(Config)).

%%%===================================================================
%%% Master-slave cases
%%%===================================================================
master_slave_cases() ->
    {privacy_master_slave, [sequence],
     [master_slave_test(deny_bare_jid),
      master_slave_test(deny_full_jid),
      master_slave_test(deny_server_bare_jid),
      master_slave_test(deny_server_full_jid),
      master_slave_test(deny_group),
      master_slave_test(deny_sub_both),
      master_slave_test(deny_sub_from),
      master_slave_test(deny_sub_to),
      master_slave_test(deny_sub_none),
      master_slave_test(deny_all),
      master_slave_test(deny_offline),
      master_slave_test(block),
      master_slave_test(unblock),
      master_slave_test(unblock_all)]}.

deny_bare_jid_master(Config) ->
    PeerJID = ?config(peer, Config),
    PeerBareJID = jid:remove_resource(PeerJID),
    deny_master(Config, {jid, jid:encode(PeerBareJID)}).

deny_bare_jid_slave(Config) ->
    deny_slave(Config).

deny_full_jid_master(Config) ->
    PeerJID = ?config(peer, Config),
    deny_master(Config, {jid, jid:encode(PeerJID)}).

deny_full_jid_slave(Config) ->
    deny_slave(Config).

deny_server_bare_jid_master(Config) ->
    {_, Server, _} = jid:tolower(?config(peer, Config)),
    deny_master(Config, {jid, Server}).

deny_server_bare_jid_slave(Config) ->
    deny_slave(Config).

deny_server_full_jid_master(Config) ->
    {_, Server, Resource} = jid:tolower(?config(peer, Config)),
    deny_master(Config, {jid, jid:encode({<<"">>, Server, Resource})}).

deny_server_full_jid_slave(Config) ->
    deny_slave(Config).

deny_group_master(Config) ->
    Group = p1_rand:get_string(),
    deny_master(Config, {group, Group}).

deny_group_slave(Config) ->
    deny_slave(Config).

deny_sub_both_master(Config) ->
    deny_master(Config, {subscription, <<"both">>}).

deny_sub_both_slave(Config) ->
    deny_slave(Config, 2).

deny_sub_from_master(Config) ->
    deny_master(Config, {subscription, <<"from">>}).

deny_sub_from_slave(Config) ->
    deny_slave(Config, 1).

deny_sub_to_master(Config) ->
    deny_master(Config, {subscription, <<"to">>}).

deny_sub_to_slave(Config) ->
    deny_slave(Config, 2).

deny_sub_none_master(Config) ->
    deny_master(Config, {subscription, <<"none">>}).

deny_sub_none_slave(Config) ->
    deny_slave(Config).

deny_all_master(Config) ->
    deny_master(Config, {undefined, <<"">>}).

deny_all_slave(Config) ->
    deny_slave(Config).

deny_master(Config, {Type, Value}) ->
    Sub = if Type == subscription ->
		  erlang:binary_to_atom(Value, utf8);
	     true ->
		  both
	  end,
    Groups = if Type == group -> [Value];
		true -> []
	     end,
    set_roster(Config, Sub, Groups),
    lists:foreach(
      fun(Opts) ->
	      ct:pal("Set list for ~s, ~s, ~w", [Type, Value, Opts]),
	      ListName = p1_rand:get_string(),
	      Item = #privacy_item{order = 0,
				   action = deny,
				   iq = proplists:get_bool(iq, Opts),
				   message = proplists:get_bool(message, Opts),
				   presence_in = proplists:get_bool(presence_in, Opts),
				   presence_out = proplists:get_bool(presence_out, Opts),
				   type = Type,
				   value = Value},
	      ok = set_items(Config, ListName, [Item]),
	      ok = set_active(Config, ListName),
	      put_event(Config, Opts),
	      case is_presence_in_blocked(Opts) of
		  true -> ok;
		  false -> recv_presences(Config)
	      end,
	      case is_iq_in_blocked(Opts) of
		  true -> ok;
		  false -> recv_iqs(Config)
	      end,
	      case is_message_in_blocked(Opts) of
		  true -> ok;
		  false -> recv_messages(Config)
	      end,
	      ct:comment("Waiting for 'send' command from the slave"),
	      send = get_event(Config),
	      case is_presence_out_blocked(Opts) of
		  true -> check_presence_blocked(Config, 'not-acceptable');
		  false -> ok
	      end,
	      case is_iq_out_blocked(Opts) of
		  true -> check_iq_blocked(Config, 'not-acceptable');
		  false -> send_iqs(Config)
	      end,
	      case is_message_out_blocked(Opts) of
		  true -> check_message_blocked(Config, 'not-acceptable');
		  false -> send_messages(Config)
	      end,
	      case is_other_blocked(Opts) of
		  true ->
		      check_other_blocked(Config, 'not-acceptable', Value);
		  false -> ok
	      end,
	      ct:comment("Waiting for slave to finish processing our stanzas"),
	      done = get_event(Config)
      end,
      [[iq], [message], [presence_in], [presence_out],
       [iq, message, presence_in, presence_out], []]),
    put_event(Config, disconnect),
    clean_up(disconnect(Config)).

deny_slave(Config) ->
    deny_slave(Config, 0).

deny_slave(Config, RosterPushesCount) ->
    set_roster(Config, both, []),
    deny_slave(Config, RosterPushesCount, get_event(Config)).

deny_slave(Config, RosterPushesCount, disconnect) ->
    recv_roster_pushes(Config, RosterPushesCount),
    clean_up(disconnect(Config));
deny_slave(Config, RosterPushesCount, Opts) ->
    send_presences(Config),
    case is_iq_in_blocked(Opts) of
	true -> check_iq_blocked(Config, 'service-unavailable');
	false -> send_iqs(Config)
    end,
    case is_message_in_blocked(Opts) of
	true -> check_message_blocked(Config, 'service-unavailable');
	false -> send_messages(Config)
    end,
    put_event(Config, send),
    case is_iq_out_blocked(Opts) of
	true -> ok;
	false -> recv_iqs(Config)
    end,
    case is_message_out_blocked(Opts) of
	true -> ok;
	false -> recv_messages(Config)
    end,
    put_event(Config, done),
    deny_slave(Config, RosterPushesCount, get_event(Config)).

deny_offline_master(Config) ->
    set_roster(Config, both, []),
    ListName = <<"deny-offline">>,
    Item = #privacy_item{order = 0, action = deny},
    ok = set_items(Config, ListName, [Item]),
    ok = set_default(Config, ListName),
    NewConfig = disconnect(Config),
    put_event(NewConfig, send),
    ct:comment("Waiting for the slave to finish"),
    done = get_event(NewConfig),
    clean_up(NewConfig).

deny_offline_slave(Config) ->
    set_roster(Config, both, []),
    ct:comment("Waiting for 'send' command from the master"),
    send = get_event(Config),
    send_presences(Config),
    check_iq_blocked(Config, 'service-unavailable'),
    check_message_blocked(Config, 'service-unavailable'),
    put_event(Config, done),
    clean_up(disconnect(Config)).

block_master(Config) ->
    PeerJID = ?config(peer, Config),
    set_roster(Config, both, []),
    {ok, _} = set_block(Config, [PeerJID]),
    check_presence_blocked(Config, 'not-acceptable'),
    check_iq_blocked(Config, 'not-acceptable'),
    check_message_blocked(Config, 'not-acceptable'),
    check_other_blocked(Config, 'not-acceptable', other),
    %% We should always be able to communicate with our home server
    server_send_iqs(Config),
    server_recv_iqs(Config),
    send_stanzas_to_server_resource(Config),
    put_event(Config, send),
    done = get_event(Config),
    clean_up(disconnect(Config)).

block_slave(Config) ->
    set_roster(Config, both, []),
    ct:comment("Waiting for 'send' command from master"),
    send = get_event(Config),
    send_presences(Config),
    check_iq_blocked(Config, 'service-unavailable'),
    check_message_blocked(Config, 'service-unavailable'),
    put_event(Config, done),
    clean_up(disconnect(Config)).

unblock_master(Config) ->
    PeerJID = ?config(peer, Config),
    set_roster(Config, both, []),
    {ok, ListName} = set_block(Config, [PeerJID]),
    {ok, ListName} = set_unblock(Config, [PeerJID]),
    put_event(Config, send),
    recv_presences(Config),
    recv_iqs(Config),
    recv_messages(Config),
    clean_up(disconnect(Config)).

unblock_slave(Config) ->
    set_roster(Config, both, []),
    ct:comment("Waiting for 'send' command from master"),
    send = get_event(Config),
    send_presences(Config),
    send_iqs(Config),
    send_messages(Config),
    clean_up(disconnect(Config)).

unblock_all_master(Config) ->
    PeerJID = ?config(peer, Config),
    set_roster(Config, both, []),
    {ok, ListName} = set_block(Config, [PeerJID]),
    {ok, ListName} = set_unblock(Config, []),
    put_event(Config, send),
    recv_presences(Config),
    recv_iqs(Config),
    recv_messages(Config),
    clean_up(disconnect(Config)).

unblock_all_slave(Config) ->
    set_roster(Config, both, []),
    ct:comment("Waiting for 'send' command from master"),
    send = get_event(Config),
    send_presences(Config),
    send_iqs(Config),
    send_messages(Config),
    clean_up(disconnect(Config)).

%%%===================================================================
%%% Internal functions
%%%===================================================================
single_test(T) ->
    list_to_atom("privacy_" ++ atom_to_list(T)).

master_slave_test(T) ->
    {list_to_atom("privacy_" ++ atom_to_list(T)), [parallel],
     [list_to_atom("privacy_" ++ atom_to_list(T) ++ "_master"),
      list_to_atom("privacy_" ++ atom_to_list(T) ++ "_slave")]}.

set_items(Config, Name, Items) ->
    ct:comment("Setting privacy list ~s with items = ~p", [Name, Items]),
    case send_recv(
	   Config,
	   #iq{type = set, sub_els = [#privacy_query{
					 lists = [#privacy_list{
						     name = Name,
						     items = Items}]}]}) of
	#iq{type = result, sub_els = []} ->
	    ct:comment("Receiving privacy list push"),
	    #iq{type = set, id = ID,
		sub_els = [#privacy_query{lists = [#privacy_list{
						      name = Name}]}]} =
		recv_iq(Config),
	    send(Config, #iq{type = result, id = ID}),
	    ok;
	#iq{type = error} = Err ->
	    xmpp:get_error(Err)
    end.

get_list(Config, Name) ->
    ct:comment("Requesting privacy list ~s", [Name]),
    case send_recv(Config,
		   #iq{type = get,
		       sub_els = [#privacy_query{
				     lists = [#privacy_list{name = Name}]}]}) of
	#iq{type = result, sub_els = [#privacy_query{lists = [List]}]} ->
	    List;
	#iq{type = error} = Err ->
	    xmpp:get_error(Err)
    end.

get_lists(Config) ->
    ct:comment("Requesting privacy lists"),
    case send_recv(Config, #iq{type = get, sub_els = [#privacy_query{}]}) of
	#iq{type = result, sub_els = [SubEl]} ->
	    SubEl;
	#iq{type = error} = Err ->
	    xmpp:get_error(Err)
    end.

del_list(Config, Name) ->
    case send_recv(
	   Config,
	   #iq{type = set, sub_els = [#privacy_query{
					 lists = [#privacy_list{
						     name = Name}]}]}) of
	#iq{type = result, sub_els = []} ->
	    ok;
	#iq{type = error} = Err ->
	    xmpp:get_error(Err)
    end.

set_active(Config, Name) ->
    ct:comment("Setting active privacy list ~s", [Name]),
    case send_recv(
	   Config,
	   #iq{type = set, sub_els = [#privacy_query{active = Name}]}) of
	#iq{type = result, sub_els = []} ->
	    ok;
	#iq{type = error} = Err ->
	    xmpp:get_error(Err)
    end.

set_default(Config, Name) ->
    ct:comment("Setting default privacy list ~s", [Name]),
    case send_recv(
	   Config,
	   #iq{type = set, sub_els = [#privacy_query{default = Name}]}) of
	#iq{type = result, sub_els = []} ->
	    ok;
	#iq{type = error} = Err ->
	    xmpp:get_error(Err)
    end.

get_block(Config) ->
    case send_recv(Config, #iq{type = get, sub_els = [#block_list{}]}) of
	#iq{type = result, sub_els = [#block_list{items = Items}]} ->
	    lists:sort([JID || #block_item{jid = JID} <- Items]);
	#iq{type = error} = Err ->
	    xmpp:get_error(Err)
    end.

set_block(Config, JIDs) ->
    Items = [#block_item{jid = JID} || JID <- JIDs],
    case send_recv(Config, #iq{type = set,
			       sub_els = [#block{items = Items}]}) of
	#iq{type = result, sub_els = []} ->
	    {#iq{id = I1, sub_els = [#block{items = Items1}]},
	     #iq{id = I2, sub_els = [#privacy_query{lists = Lists}]}} =
		?recv2(#iq{type = set, sub_els = [#block{}]},
		       #iq{type = set, sub_els = [#privacy_query{}]}),
	    send(Config, #iq{type = result, id = I1}),
	    send(Config, #iq{type = result, id = I2}),
	    ct:comment("Checking if all JIDs present in the push"),
	    true = lists:sort(Items) == lists:sort(Items1),
	    ct:comment("Getting name of the corresponding privacy list"),
	    [#privacy_list{name = Name}] = Lists,
	    {ok, Name};
	#iq{type = error} = Err ->
	    xmpp:get_error(Err)
    end.

set_unblock(Config, JIDs) ->
    ct:comment("Unblocking ~p", [JIDs]),
    Items = [#block_item{jid = JID} || JID <- JIDs],
    case send_recv(Config, #iq{type = set,
			       sub_els = [#unblock{items = Items}]}) of
	#iq{type = result, sub_els = []} ->
	    {#iq{id = I1, sub_els = [#unblock{items = Items1}]},
	     #iq{id = I2, sub_els = [#privacy_query{lists = Lists}]}} =
		?recv2(#iq{type = set, sub_els = [#unblock{}]},
		       #iq{type = set, sub_els = [#privacy_query{}]}),
	    send(Config, #iq{type = result, id = I1}),
	    send(Config, #iq{type = result, id = I2}),
	    ct:comment("Checking if all JIDs present in the push"),
	    true = lists:sort(Items) == lists:sort(Items1),
	    ct:comment("Getting name of the corresponding privacy list"),
	    [#privacy_list{name = Name}] = Lists,
	    {ok, Name};
	#iq{type = error} = Err ->
	    xmpp:get_error(Err)
    end.

del_privacy(Config) ->
    {U, S, _} = jid:tolower(my_jid(Config)),
    ct:comment("Removing all privacy data"),
    mod_privacy:remove_user(U, S),
    Config.

clean_up(Config) ->
    del_privacy(del_roster(Config)).

check_iq_blocked(Config, Reason) ->
    PeerJID = ?config(peer, Config),
    ct:comment("Checking if all IQs are blocked"),
    lists:foreach(
      fun(Type) ->
	      send(Config, #iq{type = Type, to = PeerJID})
      end, [error, result]),
    lists:foreach(
      fun(Type) ->
	      #iq{type = error} = Err =
		  send_recv(Config, #iq{type = Type, to = PeerJID,
					sub_els = [#ping{}]}),
	      #stanza_error{reason = Reason} = xmpp:get_error(Err)
      end, [set, get]).

check_message_blocked(Config, Reason) ->
    PeerJID = ?config(peer, Config),
    ct:comment("Checking if all messages are blocked"),
    %% TODO: do something with headline and groupchat.
    %% The hack from 64d96778b452aad72349b21d2ac94e744617b07a
    %% screws this up.
    lists:foreach(
      fun(Type) ->
	      send(Config, #message{type = Type, to = PeerJID})
      end, [error]),
    lists:foreach(
      fun(Type) ->
	      #message{type = error} = Err =
		  send_recv(Config, #message{type = Type, to = PeerJID}),
	      #stanza_error{reason = Reason} = xmpp:get_error(Err)
      end, [chat, normal]).

check_presence_blocked(Config, Reason) ->
    PeerJID = ?config(peer, Config),
    ct:comment("Checking if all presences are blocked"),
    lists:foreach(
      fun(Type) ->
	      #presence{type = error} = Err =
		  send_recv(Config, #presence{type = Type, to = PeerJID}),
	      #stanza_error{reason = Reason} = xmpp:get_error(Err)
      end, [available, unavailable]).

recv_roster_pushes(_Config, 0) ->
    ok;
recv_roster_pushes(Config, Count) ->
    receive
	#iq{type = set, sub_els = [#roster_query{}]} ->
	    recv_roster_pushes(Config, Count - 1)
    end.

recv_err_and_roster_pushes(Config, Count) ->
    recv_roster_pushes(Config, Count),
    recv_presence(Config).

check_other_blocked(Config, Reason, Subscription) ->
    PeerJID = ?config(peer, Config),
    ct:comment("Checking if subscriptions and presence-errors are blocked"),
    send(Config, #presence{type = error, to = PeerJID}),
    {ErrorFor, PushFor} = case Subscription of
			      <<"both">> ->
				  {[subscribe, subscribed],
				   [unsubscribe, unsubscribed]};
			      <<"from">> ->
				  {[subscribe, subscribed, unsubscribe],
				   [subscribe, unsubscribe, unsubscribed]};
			      <<"to">> ->
				  {[unsubscribe],
				   [subscribed, unsubscribe, unsubscribed]};
			      <<"none">> ->
				  {[subscribe, subscribed, unsubscribe, unsubscribed],
				   [subscribe, unsubscribe]};
			      _ ->
				  {[subscribe, subscribed, unsubscribe, unsubscribed],
				   [unsubscribe, unsubscribed]}
			  end,
    lists:foreach(
	fun(Type) ->
	    send(Config, #presence{type = Type, to = PeerJID}),
	    Count = case lists:member(Type, PushFor) of true -> 1; _ -> 0 end,
	    case lists:member(Type, ErrorFor) of
		true ->
		    Err = recv_err_and_roster_pushes(Config, Count),
		    #stanza_error{reason = Reason} = xmpp:get_error(Err);
		_ ->
		    recv_roster_pushes(Config, Count)
	    end
	end, [subscribe, subscribed, unsubscribe, unsubscribed]).

send_presences(Config) ->
    PeerJID = ?config(peer, Config),
    ct:comment("Sending all types of presences to the peer"),
    lists:foreach(
      fun(Type) ->
	      send(Config, #presence{type = Type, to = PeerJID})
      end, [available, unavailable]).

send_iqs(Config) ->
    PeerJID = ?config(peer, Config),
    ct:comment("Sending all types of IQs to the peer"),
    lists:foreach(
      fun(Type) ->
	      send(Config, #iq{type = Type, to = PeerJID})
      end, [set, get, error, result]).

send_messages(Config) ->
    PeerJID = ?config(peer, Config),
    ct:comment("Sending all types of messages to the peer"),
    lists:foreach(
      fun(Type) ->
	      send(Config, #message{type = Type, to = PeerJID})
      end, [chat, error, groupchat, headline, normal]).

recv_presences(Config) ->
    PeerJID = ?config(peer, Config),
    lists:foreach(
      fun(Type) ->
	      #presence{type = Type, from = PeerJID} =
		  recv_presence(Config)
      end, [available, unavailable]).

recv_iqs(Config) ->
    PeerJID = ?config(peer, Config),
    lists:foreach(
      fun(Type) ->
	      #iq{type = Type, from = PeerJID} = recv_iq(Config)
      end, [set, get, error, result]).

recv_messages(Config) ->
    PeerJID = ?config(peer, Config),
    lists:foreach(
      fun(Type) ->
	      #message{type = Type, from = PeerJID} = recv_message(Config)
      end, [chat, error, groupchat, headline, normal]).

match_all(Opts) ->
    IQ = proplists:get_bool(iq, Opts),
    Message = proplists:get_bool(message, Opts),
    PresenceIn = proplists:get_bool(presence_in, Opts),
    PresenceOut = proplists:get_bool(presence_out, Opts),
    not (IQ or Message or PresenceIn or PresenceOut).

is_message_in_blocked(Opts) ->
    proplists:get_bool(message, Opts) or match_all(Opts).

is_message_out_blocked(Opts) ->
    match_all(Opts).

is_iq_in_blocked(Opts) ->
    proplists:get_bool(iq, Opts) or match_all(Opts).

is_iq_out_blocked(Opts) ->
    match_all(Opts).

is_presence_in_blocked(Opts) ->
    proplists:get_bool(presence_in, Opts) or match_all(Opts).

is_presence_out_blocked(Opts) ->
    proplists:get_bool(presence_out, Opts) or match_all(Opts).

is_other_blocked(Opts) ->
    %% 'other' means subscriptions and presence-errors
    match_all(Opts).

server_send_iqs(Config) ->
    ServerJID = server_jid(Config),
    MyJID = my_jid(Config),
    ct:comment("Sending IQs from ~s to ~s",
	       [jid:encode(ServerJID), jid:encode(MyJID)]),
    lists:foreach(
      fun(Type) ->
	      ejabberd_router:route(
		#iq{from = ServerJID, to = MyJID, type = Type})
      end, [error, result]),
    lists:foreach(
      fun(Type) ->
	      ejabberd_local:route_iq(
		#iq{from = ServerJID, to = MyJID, type = Type},
		fun(#iq{type = result, sub_els = []}) -> ok;
		   (IQ) -> ct:fail({unexpected_iq_result, IQ})
		end)
      end, [set, get]).

server_recv_iqs(Config) ->
    ServerJID = server_jid(Config),
    ct:comment("Receiving IQs from ~s", [jid:encode(ServerJID)]),
    lists:foreach(
      fun(Type) ->
	      #iq{type = Type, from = ServerJID} = recv_iq(Config)
      end, [error, result]),
    lists:foreach(
      fun(Type) ->
	      #iq{type = Type, from = ServerJID, id = I} = recv_iq(Config),
	      send(Config, #iq{to = ServerJID, type = result, id = I})
      end, [set, get]).

send_stanzas_to_server_resource(Config) ->
    ServerJID = server_jid(Config),
    ServerJIDResource = jid:replace_resource(ServerJID, <<"resource">>),
    %% All stanzas sent should be handled by local_send_to_resource_hook
    %% and should be bounced with item-not-found error
    ct:comment("Sending IQs to ~s", [jid:encode(ServerJIDResource)]),
    lists:foreach(
      fun(Type) ->
	      #iq{type = error} = Err =
		  send_recv(Config, #iq{type = Type, to = ServerJIDResource}),
		  #stanza_error{reason = 'item-not-found'} = xmpp:get_error(Err)
      end, [set, get]),
    ct:comment("Sending messages to ~s", [jid:encode(ServerJIDResource)]),
    lists:foreach(
      fun(Type) ->
	      #message{type = error} = Err =
		  send_recv(Config, #message{type = Type, to = ServerJIDResource}),
	      #stanza_error{reason = 'item-not-found'} = xmpp:get_error(Err)
      end, [normal, chat, groupchat, headline]),
    ct:comment("Sending presences to ~s", [jid:encode(ServerJIDResource)]),
    lists:foreach(
      fun(Type) ->
	      #presence{type = error} = Err =
		  send_recv(Config, #presence{type = Type, to = ServerJIDResource}),
	      #stanza_error{reason = 'item-not-found'} = xmpp:get_error(Err)
      end, [available, unavailable]).
