%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2013. 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%
%%
%%
%% Tests of application_opt() request_errors. There's some overlap
%% between this suite and the traffic suite but latter exercises more
%% config.
%%
-module(diameter_3xxx_SUITE).
-export([suite/0,
all/0,
groups/0,
init_per_suite/1,
end_per_suite/1,
init_per_group/2,
end_per_group/2,
init_per_testcase/2,
end_per_testcase/2]).
%% testcases
-export([start/1,
send_unknown_application/1,
send_unknown_command/1,
send_ok/1,
send_invalid_hdr_bits/1,
send_missing_avp/1,
send_ignore_missing_avp/1,
send_5xxx_missing_avp/1,
send_double_error/1,
send_3xxx/1,
send_5xxx/1,
stop/1]).
%% diameter callbacks
-export([peer_up/3,
peer_down/3,
pick_peer/5,
prepare_request/4,
handle_answer/5,
handle_error/5,
handle_request/3]).
-include("diameter.hrl").
-include("diameter_gen_base_rfc6733.hrl").
%% Use the fact that STR/STA is identical in RFC's 3588 and 6733.
%% ===========================================================================
-define(util, diameter_util).
-define(testcase(), proplists:get_value(testcase, get(?MODULE))).
-define(group(Config), begin
put(?MODULE, Config),
?util:name(proplists:get_value(group, Config))
end).
-define(L, atom_to_list).
-define(A, list_to_atom).
-define(CLIENT, "CLIENT").
-define(SERVER, "SERVER").
-define(REALM, "erlang.org").
-define(HOST(Host, Realm), Host ++ [$.|Realm]).
-define(ERRORS, [answer, answer_3xxx, callback]).
-define(RFCS, [rfc3588, rfc6733]).
-define(DICT(RFC), ?A("diameter_gen_base_" ++ ?L(RFC))).
-define(DICT, ?DICT(rfc6733)).
-define(COMMON, ?DIAMETER_APP_ID_COMMON).
%% Config for diameter:start_service/2.
-define(SERVICE(Name, Errors, RFC),
[{'Origin-Host', Name ++ "." ++ ?REALM},
{'Origin-Realm', ?REALM},
{'Host-IP-Address', [{127,0,0,1}]},
{'Vendor-Id', 12345},
{'Product-Name', "OTP/diameter"},
{'Auth-Application-Id', [?COMMON]},
{application, [{dictionary, ?DICT(RFC)},
{module, ?MODULE},
{answer_errors, callback},
{request_errors, Errors}]}]).
-define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT').
%% ===========================================================================
suite() ->
[{timetrap, {seconds, 60}}].
all() ->
[{group, ?util:name([E,D])} || E <- ?ERRORS, D <- ?RFCS].
groups() ->
Tc = tc(),
[{?util:name([E,D]), [], [start] ++ Tc ++ [stop]}
|| E <- ?ERRORS, D <- ?RFCS].
init_per_suite(Config) ->
ok = diameter:start(),
Config.
end_per_suite(_Config) ->
ok = diameter:stop().
init_per_group(Group, Config) ->
[{group, Group} | Config].
end_per_group(_, _) ->
ok.
init_per_testcase(Name, Config) ->
[{testcase, Name} | Config].
end_per_testcase(_, _) ->
ok.
tc() ->
[send_unknown_application,
send_unknown_command,
send_ok,
send_invalid_hdr_bits,
send_missing_avp,
send_ignore_missing_avp,
send_5xxx_missing_avp,
send_double_error,
send_3xxx,
send_5xxx].
%% ===========================================================================
%% start/1
start(Config) ->
Group = proplists:get_value(group, Config),
[Errors, RFC] = ?util:name(Group),
ok = diameter:start_service(?SERVER, ?SERVICE(?L(Group),
Errors,
RFC)),
ok = diameter:start_service(?CLIENT, ?SERVICE(?CLIENT,
callback,
rfc6733)),
LRef = ?util:listen(?SERVER, tcp),
?util:connect(?CLIENT, tcp, LRef).
%% stop/1
stop(_Config) ->
ok = diameter:remove_transport(?CLIENT, true),
ok = diameter:remove_transport(?SERVER, true),
ok = diameter:stop_service(?SERVER),
ok = diameter:stop_service(?CLIENT).
%% send_unknown_application/1
%%
%% Send an unknown application that a callback (which shouldn't take
%% place) fails on.
%% diameter answers.
send_unknown_application([_,_]) ->
#'diameter_base_answer-message'{'Result-Code' = 3007,
%% UNSUPPORTED_APPLICATION
'Failed-AVP' = [],
'AVP' = []}
= call();
send_unknown_application(Config) ->
send_unknown_application(?group(Config)).
%% send_unknown_command/1
%%
%% Send a unknown command that a callback discards.
%% handle_request discards the request.
send_unknown_command([callback, _]) ->
{error, timeout} = call();
%% diameter answers.
send_unknown_command([_,_]) ->
#'diameter_base_answer-message'{'Result-Code' = 3001,
%% UNSUPPORTED_COMMAND
'Failed-AVP' = [],
'AVP' = []}
= call();
send_unknown_command(Config) ->
send_unknown_command(?group(Config)).
%% send_ok/1
%%
%% Send a correct STR that a callback answers with 5002.
%% Callback answers.
send_ok([_,_]) ->
#diameter_base_STA{'Result-Code' = 5002, %% UNKNOWN_SESSION_ID
'Failed-AVP' = [],
'AVP' = []}
= call();
send_ok(Config) ->
send_ok(?group(Config)).
%% send_invalid_hdr_bits/1
%%
%% Send a request with an incorrect E-bit that a callback ignores.
%% Callback answers.
send_invalid_hdr_bits([callback, _]) ->
#diameter_base_STA{'Result-Code' = 2001, %% SUCCESS
'Failed-AVP' = [],
'AVP' = []}
= call();
%% diameter answers.
send_invalid_hdr_bits([_,_]) ->
#'diameter_base_answer-message'{'Result-Code' = 3008, %% INVALID_HDR_BITS
'Failed-AVP' = [],
'AVP' = []}
= call();
send_invalid_hdr_bits(Config) ->
send_invalid_hdr_bits(?group(Config)).
%% send_missing_avp/1
%%
%% Send a request with a missing AVP that a callback answers.
%% diameter answers.
send_missing_avp([answer, rfc6733]) ->
#'diameter_base_answer-message'{'Result-Code' = 5005, %% MISSING_AVP
'Failed-AVP' = [_],
'AVP' = []}
= call();
%% Callback answers.
send_missing_avp([_,_]) ->
#diameter_base_STA{'Result-Code' = 5005, %% MISSING_AVP
'Failed-AVP' = [_],
'AVP' = []}
= call();
send_missing_avp(Config) ->
send_missing_avp(?group(Config)).
%% send_ignore_missing_avp/1
%%
%% Send a request with a missing AVP that a callback ignores.
%% diameter answers.
send_ignore_missing_avp([answer, rfc6733]) ->
#'diameter_base_answer-message'{'Result-Code' = 5005, %% MISSING_AVP
'Failed-AVP' = [_],
'AVP' = []}
= call();
%% Callback answers, ignores the error
send_ignore_missing_avp([_,_]) ->
#diameter_base_STA{'Result-Code' = 2001, %% SUCCESS
'Failed-AVP' = [],
'AVP' = []}
= call();
send_ignore_missing_avp(Config) ->
send_ignore_missing_avp(?group(Config)).
%% send_5xxx_missing_avp/1
%%
%% Send a request with a missing AVP that a callback answers
%% with {answer_message, 5005}.
%% RFC 6733 allows 5xxx in an answer-message.
send_5xxx_missing_avp([_, rfc6733]) ->
#'diameter_base_answer-message'{'Result-Code' = 5005, %% MISSING_AVP
'Failed-AVP' = [_],
'AVP' = []}
= call();
%% RFC 3588 doesn't: sending answer fails.
send_5xxx_missing_avp([_, rfc3588]) ->
{error, timeout} = call();
%% Callback answers, ignores the error
send_5xxx_missing_avp([_,_]) ->
#diameter_base_STA{'Result-Code' = 2001, %% SUCCESS
'Failed-AVP' = [],
'AVP' = []}
= call();
send_5xxx_missing_avp(Config) ->
send_5xxx_missing_avp(?group(Config)).
%% send_double_error/1
%%
%% Send a request with both an invalid E-bit and a missing AVP.
%% Callback answers with STA.
send_double_error([callback, _]) ->
#diameter_base_STA{'Result-Code' = 5005, %% MISSING_AVP
'Failed-AVP' = [_],
'AVP' = []}
= call();
%% diameter answers with answer-message.
send_double_error([_,_]) ->
#'diameter_base_answer-message'{'Result-Code' = 3008, %% INVALID_HDR_BITS
'Failed-AVP' = [],
'AVP' = []}
= call();
send_double_error(Config) ->
send_double_error(?group(Config)).
%% send_3xxx/1
%%
%% Send a request that's answered with a 3xxx result code.
%% Callback answers.
send_3xxx([_,_]) ->
#'diameter_base_answer-message'{'Result-Code' = 3999,
'Failed-AVP' = [],
'AVP' = []}
= call();
send_3xxx(Config) ->
send_3xxx(?group(Config)).
%% send_5xxx/1
%%
%% Send a request that's answered with a 5xxx result code.
%% Callback answers but fails since 5xxx isn't allowed in an RFC 3588
%% answer-message.
send_5xxx([_, rfc3588]) ->
{error, timeout} = call();
%% Callback answers.
send_5xxx([_,_]) ->
#'diameter_base_answer-message'{'Result-Code' = 5999,
'Failed-AVP' = [],
'AVP' = []}
= call();
send_5xxx(Config) ->
send_5xxx(?group(Config)).
%% ===========================================================================
call() ->
Name = ?testcase(),
diameter:call(?CLIENT,
?DICT,
#diameter_base_STR
{'Termination-Cause' = ?LOGOUT,
'Auth-Application-Id' = ?COMMON,
'Class' = [?L(Name)]},
[{extra, [Name]}]).
%% ===========================================================================
%% diameter callbacks
%% peer_up/3
peer_up(_SvcName, _Peer, State) ->
State.
%% peer_down/3
peer_down(_SvcName, _Peer, State) ->
State.
%% pick_peer/5
pick_peer([Peer], _, ?CLIENT, _State, _Name) ->
{ok, Peer}.
%% prepare_request/4
prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, Name) ->
{send, prepare(Pkt, Caps, Name)}.
prepare(Pkt0, Caps, send_unknown_application) ->
Req = sta(Pkt0, Caps),
#diameter_packet{bin = <<H:8/binary, 0:32, T/binary>>}
= Pkt
= diameter_codec:encode(?DICT, Pkt0#diameter_packet{msg = Req}),
Pkt#diameter_packet{bin = <<H/binary, 23:32, T/binary>>};
prepare(Pkt0, Caps, send_unknown_command) ->
Req = sta(Pkt0, Caps),
#diameter_packet{bin = <<H:5/binary, 275:24, T/binary>>}
= Pkt
= diameter_codec:encode(?DICT, Pkt0#diameter_packet{msg = Req}),
Pkt#diameter_packet{bin = <<H/binary, 572:24, T/binary>>};
prepare(Pkt, Caps, T)
when T == send_ok;
T == send_3xxx;
T == send_5xxx ->
sta(Pkt, Caps);
prepare(Pkt0, Caps, send_invalid_hdr_bits) ->
Req = sta(Pkt0, Caps),
%% Set the E-bit to force 3008.
#diameter_packet{bin = <<H:34, 0:1, T/bitstring>>}
= Pkt
= diameter_codec:encode(?DICT, Pkt0#diameter_packet{msg = Req}),
Pkt#diameter_packet{bin = <<H:34, 1:1, T/bitstring>>};
prepare(Pkt0, Caps, send_double_error) ->
dehost(prepare(Pkt0, Caps, send_invalid_hdr_bits));
prepare(Pkt, Caps, T)
when T == send_missing_avp;
T == send_ignore_missing_avp;
T == send_5xxx_missing_avp ->
Req = sta(Pkt, Caps),
dehost(diameter_codec:encode(?DICT, Pkt#diameter_packet{msg = Req})).
sta(Pkt, Caps) ->
#diameter_packet{msg = Req}
= Pkt,
#diameter_caps{origin_host = {OH, _},
origin_realm = {OR, DR}}
= Caps,
Req#diameter_base_STR{'Session-Id' = diameter:session_id(OH),
'Origin-Host' = OH,
'Origin-Realm' = OR,
'Destination-Realm' = DR}.
%% Strip Origin-Host.
dehost(#diameter_packet{bin = Bin} = Pkt) ->
<<V, Len:24, H:16/binary, T0/binary>>
= Bin,
{SessionId, T1} = split_avp(T0),
{OriginHost, T} = split_avp(T1),
Delta = size(OriginHost),
Pkt#diameter_packet{bin = <<V, (Len - Delta):24, H/binary,
SessionId/binary,
T/binary>>}.
%% handle_answer/5
handle_answer(Pkt, _Req, ?CLIENT, _Peer, _Name) ->
Pkt#diameter_packet.msg.
%% handle_error/5
handle_error(Reason, _Req, ?CLIENT, _Peer, _Name) ->
{error, Reason}.
split_avp(<<_:5/binary, Len:24, _/binary>> = Bin) ->
L = pad(Len),
<<Avp:L/binary, T/binary>> = Bin,
{Avp, T}.
pad(N)
when 0 == N rem 4 ->
N;
pad(N) ->
N - (N rem 4) + 4.
%% handle_request/3
handle_request(#diameter_packet{header = #diameter_header{application_id = 0},
msg = Msg},
?SERVER,
{_, Caps}) ->
request(Msg, Caps).
request(undefined, _) -> %% unknown command
discard;
request(#diameter_base_STR{'Class' = [Name]} = Req, Caps) ->
request(?A(Name), Req, Caps).
request(send_ok, Req, Caps) ->
{reply, #diameter_packet{msg = answer(Req, Caps),
errors = [5002]}}; %% UNKNOWN_SESSION_ID
request(send_3xxx, _Req, _Caps) ->
{answer_message, 3999};
request(send_5xxx, _Req, _Caps) ->
{answer_message, 5999};
request(send_invalid_hdr_bits, Req, Caps) ->
%% Default errors field but a non-answer-message and only 3xxx
%% errors detected means diameter sets neither Result-Code nor
%% Failed-AVP.
{reply, #diameter_packet{msg = answer(Req, Caps)}};
request(T, Req, Caps)
when T == send_double_error;
T == send_missing_avp ->
{reply, answer(Req, Caps)};
request(send_ignore_missing_avp, Req, Caps) ->
{reply, #diameter_packet{msg = answer(Req, Caps),
errors = false}}; %% ignore errors
request(send_5xxx_missing_avp, _Req, _Caps) ->
{answer_message, 5005}. %% MISSING_AVP
answer(Req, Caps) ->
#diameter_base_STR{'Session-Id' = SId}
= Req,
#diameter_caps{origin_host = {OH,_},
origin_realm = {OR,_}}
= Caps,
#diameter_base_STA{'Session-Id' = SId,
'Origin-Host' = OH,
'Origin-Realm' = OR,
'Result-Code' = 2001}. %% SUCCESS