%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2010. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
%%
%%----------------------------------------------------------------------
%% Purpose: Verify the tcp transport component of the Diameter application
%%----------------------------------------------------------------------
%%
-module(diameter_tcp_test).
-export([
init_per_testcase/2, fin_per_testcase/2,
all/0,
groups/0,
init_per_suite/1, end_per_suite/1,
suite_init/1, suite_fin/1,
init_per_group/2, end_per_group/2,
start_and_stop_transport_plain/1,
start_and_listen/1,
simple_connect/1,
simple_send_and_recv/1
]).
-export([t/0, t/1]).
%% diameter_peer (internal) callback API
-export([up/1, up/3, recv/2]).
-include("diameter_test_lib.hrl").
-include_lib("diameter/include/diameter.hrl").
%% -include_lib("diameter/src/tcp/diameter_tcp.hrl").
t() -> diameter_test_server:t(?MODULE).
t(Case) -> diameter_test_server:t({?MODULE, Case}).
%% Test server callbacks
init_per_testcase(Case, Config) ->
diameter_test_server:init_per_testcase(Case, Config).
fin_per_testcase(Case, Config) ->
diameter_test_server:fin_per_testcase(Case, Config).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
all() ->
[
{group, start},
{group, simple}
].
groups() ->
[
{start, [], [start_and_stop_transport_plain, start_and_listen]},
{simple, [], [simple_connect, simple_send_and_recv]}
].
init_per_group(_GroupName, Config) ->
Config.
end_per_group(_GroupName, Config) ->
Config.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
suite_init(X) -> init_per_suite(X).
init_per_suite(suite) -> [];
init_per_suite(doc) -> [];
init_per_suite(Config) when is_list(Config) ->
Config.
suite_fin(X) -> end_per_suite(X).
end_per_suite(suite) -> [];
end_per_suite(doc) -> [];
end_per_suite(Config) when is_list(Config) ->
Config.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Test case(s)
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Plain start and stop of TCP transport
%%
start_and_stop_transport_plain(suite) ->
[];
start_and_stop_transport_plain(doc) ->
[];
start_and_stop_transport_plain(Config) when is_list(Config) ->
?SKIP(not_yet_implemented),
%% This has been changed *a lot* since it was written...
process_flag(trap_exit, true),
Transport = ensure_transport_started(),
TcpTransport = ensure_tcp_transport_started(),
ensure_tcp_transport_stopped(TcpTransport),
ensure_transport_stopped(Transport),
i("done"),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Start TCP transport and then create a listen socket
%%
start_and_listen(suite) ->
[];
start_and_listen(doc) ->
[];
start_and_listen(Config) when is_list(Config) ->
?SKIP(not_yet_implemented),
%% This has been changed *a lot* since it was written...
process_flag(trap_exit, true),
Transport = ensure_transport_started(),
TcpTransport = ensure_tcp_transport_started(),
case listen([{port, 0}]) of
{ok, Acceptor} when is_pid(Acceptor) ->
Ref = erlang:monitor(process, Acceptor),
[{Acceptor, Info}] = diameter_tcp:which_listeners(),
case lists:keysearch(socket, 1, Info) of
{value, {_, Listen}} ->
i("Listen socket: ~p"
"~n Opts: ~p"
"~n Stats: ~p"
"~n Name: ~p",
[Listen,
ok(inet:getopts(Listen, [keepalive, delay_send])),
ok(inet:getstat(Listen)),
ok(inet:sockname(Listen))
]),
ok;
_ ->
?FAIL({bad_listener_info, Acceptor, Info})
end,
Crash = simulate_crash,
exit(Acceptor, Crash),
receive
{'DOWN', Ref, process, Acceptor, Crash} ->
?SLEEP(1000),
case diameter_tcp:which_listeners() of
[{NewAcceptor, _NewInfo}] ->
diameter_tcp_accept:stop(NewAcceptor),
?SLEEP(1000),
case diameter_tcp:which_listeners() of
[] ->
ok;
UnexpectedListeners ->
?FAIL({unexpected_listeners, empty, UnexpectedListeners})
end;
UnexpectedListeners ->
?FAIL({unexpected_listeners, non_empty, UnexpectedListeners})
end
after 5000 ->
?FAIL({failed_killing, Acceptor})
end;
Error ->
?FAIL({failed_creating_acceptor, Error})
end,
ensure_tcp_transport_stopped(TcpTransport),
ensure_transport_stopped(Transport),
i("done"),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% TCP transport connecting
%%
simple_connect(suite) ->
[];
simple_connect(doc) ->
[];
simple_connect(Config) when is_list(Config) ->
?SKIP(not_yet_implemented),
%% This has been changed *a lot* since it was written...
process_flag(trap_exit, true),
Transport = ensure_transport_started(),
TcpTransport = ensure_tcp_transport_started(),
{_Acceptor, Port} = ensure_tcp_listener(),
{ok, Hostname} = inet:gethostname(),
i("try connect"),
Opts = [{host, Hostname}, {port, Port}, {module, ?MODULE}],
Conn = case connect(Opts) of
{ok, C} ->
C;
Error ->
?FAIL({failed_connecting, Error})
end,
i("connected: ~p", [Conn]),
%% Up for connect
receive
{diameter, {up, Host, Port}} ->
i("Received expected connect up (~p:~p)", [Host, Port]),
ok
after 5000 ->
?FAIL(connect_up_confirmation_timeout)
end,
%% Up for accept
receive
{diameter, {up, _ConnPid}} ->
i("Received expected accept up"),
ok
after 5000 ->
?FAIL(acceptor_up_confirmation_timeout)
end,
i("try disconnect"),
diameter_tcp:disconnect(Conn),
ensure_tcp_transport_stopped(TcpTransport),
ensure_transport_stopped(Transport),
i("done"),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Plain start and stop of TCP transport
%%
simple_send_and_recv(suite) ->
[];
simple_send_and_recv(doc) ->
[];
simple_send_and_recv(Config) when is_list(Config) ->
?SKIP(not_yet_implemented),
%% This has been changed *a lot* since it was written...
process_flag(trap_exit, true),
%% --------------------------------------------------
%% Start the TCP transport sub-system
%%
Transport = ensure_transport_started(),
TcpTransport = ensure_tcp_transport_started(),
{_Acceptor, Port} = ensure_tcp_listener(),
{ok, Hostname} = inet:gethostname(),
i("try connect"),
Opts = [{host, Hostname}, {port, Port}, {module, ?MODULE}],
Conn = case connect(Opts) of
{ok, C1} ->
C1;
Error ->
?FAIL({failed_connecting, Error})
end,
i("connected: ~p", [Conn]),
%% Up for connect
receive
{diameter, {up, Host, Port}} ->
i("Received expected connect up (~p:~p)", [Host, Port]),
ok
after 5000 ->
?FAIL(connect_up_confirmation_timeout)
end,
%% Up for accept
APid =
receive
{diameter, {up, C2}} ->
i("Received expected accept up"),
C2
after 5000 ->
?FAIL(acceptor_up_confirmation_timeout)
end,
%% --------------------------------------------------
%% Start some stuff needed for the codec to run
%%
i("start persistent table"),
{ok, _Pers} = diameter_persistent_table:start_link(),
i("start session"),
{ok, _Session} = diameter_session:start_link(),
i("try decode a (DWR) message"),
Base = diameter_gen_base_rfc3588,
DWR = ['DWR',
{'Origin-Host', Hostname},
{'Origin-Realm', "whatever-realm"},
{'Origin-State-Id', [10]}],
#diameter_packet{msg = Msg} = diameter_codec:encode(Base, DWR),
%% --------------------------------------------------
%% Now try to send the message
%%
%% This is not the codec-test suite, so we dont really care what we
%% send, as long as it encoded/decodes correctly in the transport
%%
i("try send from connect side"),
ok = diameter_tcp:send_message(Conn, Msg),
%% Wait for data on Accept side
APkt =
receive
{diameter, {recv, A}} ->
i("[accept] Received expected data message: ~p", [A]),
A
after 5000 ->
?FAIL(acceptor_up_confirmation_timeout)
end,
%% Send the same message back, just to have something to send...
i("try send (\"reply\") from accept side"),
ok = diameter_tcp:send_message(APid, APkt),
%% Wait for data on Connect side
receive
{diameter, {recv, B}} ->
i("[connect] Received expected data message: ~p", [B]),
ok
after 5000 ->
?FAIL(acceptor_up_confirmation_timeout)
end,
i("we are done - now close shop"),
diameter_session:stop(),
diameter_persistent_table:stop(),
ensure_tcp_transport_stopped(TcpTransport),
ensure_transport_stopped(Transport),
i("done"),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
ensure_transport_started() ->
%% i("start diameter transport (top) supervisor"),
case diameter_transport_sup:start_link() of
{ok, TransportSup} ->
TransportSup;
Error ->
?FAIL({failed_starting_transport_sup, Error})
end.
ensure_transport_stopped(Pid) when is_pid(Pid) ->
%% i("stop diameter transport (top) supervisor"),
Stop = fun(P) -> exit(P, kill) end,
ensure_stopped(Pid, Stop, failed_stopping_transport_sup).
ensure_tcp_transport_started() ->
%% i("start diameter TCP transport"),
case diameter_tcp:start_transport() of
{ok, TcpTransport} when is_pid(TcpTransport) ->
TcpTransport;
Error ->
?FAIL({failed_starting_transport, Error})
end.
ensure_tcp_transport_stopped(Pid) when is_pid(Pid) ->
%% i("stop diameter TCP transport supervisor"),
Stop = fun(P) -> diameter_tcp:stop_transport(P) end,
ensure_stopped(Pid, Stop, failed_stopping_tcp_transport).
ensure_tcp_listener() ->
%% i("create diameter TCP transport listen socket"),
case listen([{port, 0}]) of
{ok, Acceptor} ->
[{Acceptor, Info}] = diameter_tcp:which_listeners(),
case lists:keysearch(socket, 1, Info) of
{value, {_, Listen}} ->
{ok, Port} = inet:port(Listen),
{Acceptor, Port};
_ ->
?FAIL({failed_retrieving_listen_socket, Info})
end;
Error ->
?FAIL({failed_creating_listen_socket, Error})
end.
ensure_stopped(Pid, Stop, ReasonTag) when is_pid(Pid) ->
%% i("ensure_stopped -> create monitor to ~p", [Pid]),
Ref = erlang:monitor(process, Pid),
%% i("ensure_stopped -> try stop"),
Stop(Pid),
%% i("ensure_stopped -> await DOWN message"),
receive
{'DOWN', Ref, process, Pid, _} ->
%% i("ensure_stopped -> received DOWN message"),
ok
after 5000 ->
%% i("ensure_stopped -> timeout"),
?FAIL({ReasonTag, Pid})
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
listen(Opts) ->
diameter_tcp:listen([{module, ?MODULE} | Opts]).
connect(Opts) ->
diameter_tcp:connect([{module, ?MODULE} | Opts]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
up(Pid, Host, Port) ->
Pid ! {diameter, {up, Host, Port}},
ok.
up(Pid) ->
Pid ! {diameter, {up, self()}},
ok.
recv(Pid, Pkt) ->
Pid ! {diameter, {recv, Pkt}}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
i(F) ->
i(F, []).
i(F, A) ->
io:format(F ++ "~n", A).
ok({ok, Whatever}) ->
Whatever;
ok(Crap) ->
Crap.