diff options
Diffstat (limited to 'lib/diameter/test')
21 files changed, 1717 insertions, 329 deletions
diff --git a/lib/diameter/test/.gitignore b/lib/diameter/test/.gitignore index df38dfc5e3..4f19542bbe 100644 --- a/lib/diameter/test/.gitignore +++ b/lib/diameter/test/.gitignore @@ -1,3 +1,4 @@ /log /depend.mk +/coverspec diff --git a/lib/diameter/test/Makefile b/lib/diameter/test/Makefile index 866d135bd9..061f0bcbef 100644 --- a/lib/diameter/test/Makefile +++ b/lib/diameter/test/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2010-2012. All Rights Reserved. +# Copyright Ericsson AB 2010-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 @@ -56,26 +56,28 @@ DATA_DIRS = $(sort $(dir $(DATA))) ERL_COMPILE_FLAGS += +warn_export_vars \ +warn_unused_vars \ -I ../include \ - -I ../src/gen + -I ../src/gen \ + $(STRICT_FLAGS) # ---------------------------------------------------- # Targets # ---------------------------------------------------- +all debug opt: $(TARGET_FILES) + +strict: + $(MAKE) opt STRICT_FLAGS=-Werror + # Require success ... -all: opt +run: $(SUITES) # ... or not. any: opt $(MAKE) -i $(SUITES) -run: $(SUITES) - -debug opt: $(TARGET_FILES) - clean: rm -f $(TARGET_FILES) - rm -f depend.mk + rm -f depend.mk coverspec realclean: clean rm -rf log @@ -101,7 +103,10 @@ help: @echo " Compile all test suites." @echo @echo " run:" - @echo " Compile and run all test suites." + @echo " Compile and run all test suites, stop on failure." + @echo + @echo " any:" + @echo " Compile and run all test suites, ignore any failures." @echo @echo " $(SUITES):" @echo " Compile and run a specific test suite." @@ -113,7 +118,7 @@ help: @echo " Echo some relevant variables." @echo ======================================== -.PHONY: all any run clean debug docs help info opt realclean +.PHONY: all any run clean debug docs help info opt realclean strict # ---------------------------------------------------- # Special Targets @@ -131,10 +136,21 @@ $(SUITES): log opt | awk '{print} / FAILED /{rc=1} END{exit rc}' rc=0 # Shorter in sed but requires a GNU extension (ie. Q). +cover: log opt coverspec + $(ERL) -noinput \ + -pa $(realpath ../ebin) \ + -sname diameter_cover \ + -s diameter_ct cover \ + -s init stop \ + | awk '{print} / FAILED /{rc=1} END{exit rc}' rc=0 + +coverspec: diameter.cover + sed -f [email protected] $< > $@ + log: mkdir $@ -.PHONY: $(SUITES) +.PHONY: $(SUITES) cover # ---------------------------------------------------- # Release Targets diff --git a/lib/diameter/test/coverspec.sed b/lib/diameter/test/coverspec.sed new file mode 100644 index 0000000000..5e81621593 --- /dev/null +++ b/lib/diameter/test/coverspec.sed @@ -0,0 +1,33 @@ +# +# %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% +# + +# +# Morph diameter.cover into a legitimate cover spec. All that's being +# retained is the list of excluded modules. This is used by Makefile +# when running cover locally. +# + +/^{incl_app,/{ + i\ +{level, details}.\ +{incl_dirs, ["../ebin"]}. + d +} + +/^{excl_mods,/s@ .*@@ diff --git a/lib/diameter/test/depend.sed b/lib/diameter/test/depend.sed index 95dca44984..602d1ab497 100644 --- a/lib/diameter/test/depend.sed +++ b/lib/diameter/test/depend.sed @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2010-2011. All Rights Reserved. +# Copyright Ericsson AB 2010-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 @@ -38,4 +38,4 @@ s@^-include("@@ s@".*@@ G -s@^\(.*\)\n\(.*\)@$(EBIN)/\2.$(EMULATOR): \1@ +s@^\(.*\)\n\(.*\)@\2.$(EMULATOR): \1@ diff --git a/lib/diameter/test/diameter.cover b/lib/diameter/test/diameter.cover index 5fde6c7d01..6586733871 100644 --- a/lib/diameter/test/diameter.cover +++ b/lib/diameter/test/diameter.cover @@ -1,6 +1,8 @@ -%% -*- erlang -*- -{exclude, - [ - ] -}. +{incl_app,diameter,details}. +{excl_mods, diameter, + [diameter_dbg, + diameter_info, + diameter_etcp, + diameter_etcp_sup] +}. diff --git a/lib/diameter/test/diameter_3xxx_SUITE.erl b/lib/diameter/test/diameter_3xxx_SUITE.erl new file mode 100644 index 0000000000..89c78d8b57 --- /dev/null +++ b/lib/diameter/test/diameter_3xxx_SUITE.erl @@ -0,0 +1,509 @@ +%% +%% %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_avp_bits/1, + send_missing_avp/1, + send_ignore_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_avp_bits, + send_missing_avp, + send_ignore_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_avp_bits/1 +%% +%% Send a request with an incorrect length on the optional +%% Origin-State-Id that a callback ignores. + +%% Callback answers. +send_invalid_avp_bits([callback, _]) -> + #diameter_base_STA{'Result-Code' = 2001, %% SUCCESS + 'Failed-AVP' = [], + 'AVP' = []} + = call(); + +%% diameter answers. +send_invalid_avp_bits([_,_]) -> + #'diameter_base_answer-message'{'Result-Code' = 3009, %% INVALID_AVP_BITS + 'Failed-AVP' = [], + 'AVP' = []} + = call(); + +send_invalid_avp_bits(Config) -> + send_invalid_avp_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_double_error/1 +%% +%% Send a request with both an incorrect length on the optional +%% Origin-State-Id 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' = 3009, %% INVALID_AVP_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_avp_bits) -> + Req0 = sta(Pkt0, Caps), + %% Append an Origin-State-Id with an incorrect AVP Length in order + %% to force 3009. + Req = Req0#diameter_base_STR{'Origin-State-Id' = [7]}, + #diameter_packet{bin = Bin} + = Pkt + = diameter_codec:encode(?DICT, Pkt0#diameter_packet{msg = Req}), + Offset = size(Bin) - 12 + 5, + <<H:Offset/binary, Len:24, T/binary>> = Bin, + Pkt#diameter_packet{bin = <<H/binary, (Len + 2):24, T/binary>>}; + +prepare(Pkt0, Caps, send_double_error) -> + dehost(prepare(Pkt0, Caps, send_invalid_avp_bits)); + +prepare(Pkt, Caps, T) + when T == send_missing_avp; + T == send_ignore_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_avp_bits, Req, Caps) -> + #diameter_base_STR{'Origin-State-Id' = []} + = Req, + %% 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 + +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 diff --git a/lib/diameter/test/diameter_app_SUITE.erl b/lib/diameter/test/diameter_app_SUITE.erl index 53332af626..209f72adf1 100644 --- a/lib/diameter/test/diameter_app_SUITE.erl +++ b/lib/diameter/test/diameter_app_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2011. All Rights Reserved. +%% Copyright Ericsson AB 2010-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 @@ -48,7 +48,6 @@ diameter_dict_parser, diameter_dict_util, diameter_exprecs, - diameter_nowarn, diameter_make]). -define(HELP_MODULES, [diameter_dbg, diff --git a/lib/diameter/test/diameter_capx_SUITE.erl b/lib/diameter/test/diameter_capx_SUITE.erl index ae128b8203..a4e4195a19 100644 --- a/lib/diameter/test/diameter_capx_SUITE.erl +++ b/lib/diameter/test/diameter_capx_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2012. All Rights Reserved. +%% Copyright Ericsson AB 2010-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 @@ -27,6 +27,8 @@ -export([suite/0, all/0, groups/0, + init_per_group/2, + end_per_group/2, init_per_testcase/2, end_per_testcase/2]). @@ -53,7 +55,6 @@ peer_down/4]). -include("diameter.hrl"). --include("diameter_gen_base_rfc3588.hrl"). %% =========================================================================== @@ -78,8 +79,10 @@ | [{application, [{alias, A}, {dictionary, D}, {module, [?MODULE, A]}]} - || {A,D} <- [{common, ?DIAMETER_DICT_COMMON}, - {accounting, ?DIAMETER_DICT_ACCOUNTING}]]]). + || {A,D} <- [{base3588, diameter_gen_base_rfc3588}, + {acct3588, diameter_gen_base_accounting}, + {base6733, diameter_gen_base_rfc6733}, + {acct6733, diameter_gen_acct_rfc6733}]]]). -define(A, list_to_atom). -define(L, atom_to_list). @@ -88,13 +91,12 @@ -define(caps, #diameter_caps). -define(packet, #diameter_packet). --define(cea, #diameter_base_CEA). --define(answer_message, #'diameter_base_answer-message'). - -define(fail(T), erlang:error({T, process_info(self(), messages)})). -define(TIMEOUT, 10000). +-define(DICTS, [rfc3588, rfc6733]). + %% =========================================================================== suite() -> @@ -102,15 +104,15 @@ suite() -> all() -> [start, start_services, - add_listeners, - {group, all}, - {group, all, [parallel]}, - remove_listeners, + add_listeners] + ++ [{group, D, P} || D <- ?DICTS, P <- [[], [parallel]]] + ++ [remove_listeners, stop_services, stop]. groups() -> - [{all, [], lists:flatmap(fun tc/1, tc())}]. + Tc = lists:flatmap(fun tc/1, tc()), + [{D, [], Tc} || D <- ?DICTS]. %% Generate a unique hostname for each testcase so that watchdogs %% don't prevent a connection from being brought up immediately. @@ -118,6 +120,12 @@ init_per_testcase(Name, Config) -> Uniq = ["." ++ integer_to_list(N) || N <- tuple_to_list(now())], [{host, lists:flatten([?L(Name) | Uniq])} | Config]. +init_per_group(Name, Config) -> + [{rfc, Name} | Config]. + +end_per_group(_, _) -> + ok. + end_per_testcase(N, _) when N == start; N == start_services; @@ -126,6 +134,7 @@ end_per_testcase(N, _) N == stop_services; N == stop -> ok; + end_per_testcase(Name, Config) -> CRef = ?util:read_priv(Config, Name), ok = diameter:remove_transport(?CLIENT, CRef). @@ -155,14 +164,19 @@ start_services(_Config) -> %% to both this and the common application. Share a common service just %% to simplify config, and because we can. add_listeners(Config) -> - Acct = listen(?SERVER, - [{capabilities, [{'Origin-Host', ?HOST("acct-srv")}, - {'Auth-Application-Id', []}]}, - {applications, [accounting]}, - {capabilities_cb, [fun server_capx/3, acct]}]), - Base = listen(?SERVER, - [{capabilities, [{'Origin-Host', ?HOST("base-srv")}]}, - {capabilities_cb, [fun server_capx/3, base]}]), + Acct = [listen(?SERVER, + [{capabilities, [{'Origin-Host', ?HOST(H)}, + {'Auth-Application-Id', []}]}, + {applications, [A]}, + {capabilities_cb, [fun server_capx/3, acct]}]) + || {A,H} <- [{acct3588, "acct3588-srv"}, + {acct6733, "acct6733-srv"}]], + Base = [listen(?SERVER, + [{capabilities, [{'Origin-Host', ?HOST(H)}]}, + {applications, A}, + {capabilities_cb, [fun server_capx/3, base]}]) + || {A,H} <- [{[base3588, acct3588], "base3588-srv"}, + {[base6733, acct6733], "base6733-srv"}]], ?util:write_priv(Config, ?MODULE, {Base, Acct}). %% lref/2 reads remove_listeners(_Config) -> @@ -195,8 +209,9 @@ c_no_common_application(Config) -> client_closed(Config, "acct-srv", fun no_common_application/1, 5010). no_common_application(Config) -> + [Common, _Acct] = apps(Config), connect(Config, acct, [{capabilities, [{'Acct-Application-Id', []}]}, - {applications, [common]}]). + {applications, [Common]}]). %% ==================== %% Ask the base server to speak accounting with an unknown security @@ -209,9 +224,10 @@ c_no_common_security(Config) -> client_closed(Config, "base-srv", fun no_common_security/1, 5017). no_common_security(Config) -> + [Common, _Acct] = apps(Config), connect(Config, base, [{capabilities, [{'Acct-Application-Id', []}, {'Inband-Security-Id', [17, 18]}]}, - {applications, [common]}]). + {applications, [Common]}]). %% ==================== %% Have the base server reject a decent CER with the protocol error @@ -221,18 +237,19 @@ s_unknown_peer(Config) -> server_reject(Config, fun base/1, 3010). c_unknown_peer(Config) -> + Dict0 = dict0(Config), true = diameter:subscribe(?CLIENT), - OH = ?HOST("base-srv"), + OH = host(Config, "base-srv"), {CRef, _} = base(Config), - {'CEA', ?caps{}, - ?packet{msg = ?answer_message{'Origin-Host' = OH, - 'Result-Code' = 3010}}} - = client_recv(CRef). + {'CEA', ?caps{}, ?packet{msg = Msg}} = client_recv(CRef), + + ['diameter_base_answer-message' | _] = Dict0:'#get-'(Msg), + [OH, 3010] = Dict0:'#get-'(['Origin-Host', 'Result-Code'], Msg). base(Config) -> - connect(Config, base, []). + connect(Config, base, [{applications, apps(Config)}]). %% ==================== %% Have the base server reject a decent CER with the non-protocol @@ -266,18 +283,23 @@ s_client_reject(Config) -> end. c_client_reject(Config) -> + Dict0 = dict0(Config), true = diameter:subscribe(?CLIENT), - OH = ?HOST("acct-srv"), + OH = host(Config, "acct-srv"), {CRef, _} = client_reject(Config), {'CEA', {capabilities_cb, _, discard}, ?caps{origin_host = {_, OH}}, - ?packet{msg = ?cea{'Result-Code' = 2001}}} - = client_recv(CRef). + ?packet{msg = CEA}} + = client_recv(CRef), + + [diameter_base_CEA | _] = Dict0:'#get-'(CEA), + [2001] = Dict0:'#get-'(['Result-Code'], CEA). client_reject(Config) -> - connect(Config, acct, [{capabilities_cb, fun client_capx/2}]). + connect(Config, acct, [{capabilities_cb, fun client_capx/2}, + {applications, apps(Config)}]). %% =========================================================================== @@ -327,13 +349,21 @@ server_reject(Config, F, RC) -> client_closed(Config, Host, F, RC) -> true = diameter:subscribe(?CLIENT), - OH = ?HOST(Host), + OH = host(Config, Host), {CRef, _} = F(Config), {'CEA', RC, ?caps{origin_host = {_, OH}}, ?packet{}} = client_recv(CRef). +srv(Config, Host) -> + "rfc" ++ N = atom_to_list(proplists:get_value(rfc, Config)), + [H, "srv" = S] = string:tokens(Host, "-"), + H ++ N ++ "-" ++ S. + +host(Config, Name) -> + ?HOST(srv(Config, Name)). + %% client_recv/1 client_recv(CRef) -> @@ -364,6 +394,18 @@ client_capx(_, ?caps{origin_host = {[_,$_|"client_reject." ++ _], _}}) -> %% =========================================================================== +dict0(Config) -> + case proplists:get_value(rfc, Config) of + rfc3588 -> diameter_gen_base_rfc3588; + rfc6733 -> diameter_gen_base_rfc6733 + end. + +apps(Config) -> + case proplists:get_value(rfc, Config) of + rfc3588 -> [base3588, acct3588]; + rfc6733 -> [base6733, acct6733] + end. + host(Config) -> {_, H} = lists:keyfind(host, 1, Config), ?HOST(H). @@ -394,26 +436,32 @@ opts(PortNr, Opts) -> {port, 0}]} | Opts]. +lref(rfc3588, [LRef, _]) -> + LRef; +lref(rfc6733, [_, LRef]) -> + LRef; + lref(Config, T) -> - case ?util:read_priv(Config, ?MODULE) of - {LRef, _} when T == base -> - LRef; - {_, LRef} when T == acct -> - LRef - end. + lref(proplists:get_value(rfc, Config), + case ?util:read_priv(Config, ?MODULE) of + {R, _} when T == base -> + R; + {_, R} when T == acct -> + R + end). %% =========================================================================== %% diameter callbacks peer_up(?SERVER, - {_, ?caps{origin_host = {"acct-srv." ++ _, + {_, ?caps{origin_host = {"acct" ++ _, [_,$_|"client_reject." ++ _]}}}, State, _) -> State. peer_down(?SERVER, - {_, ?caps{origin_host = {"acct-srv." ++ _, + {_, ?caps{origin_host = {"acct" ++ _, [_,$_|"client_reject." ++ _]}}}, State, _) -> diff --git a/lib/diameter/test/diameter_codec_test.erl b/lib/diameter/test/diameter_codec_test.erl index fbd38067a8..dc8cbffc83 100644 --- a/lib/diameter/test/diameter_codec_test.erl +++ b/lib/diameter/test/diameter_codec_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2011. All Rights Reserved. +%% Copyright Ericsson AB 2010-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 @@ -27,7 +27,8 @@ -include("diameter.hrl"). --define(BASE, diameter_gen_base_rfc3588). +-define(RFC3588, diameter_gen_base_rfc3588). +-define(RFC6733, diameter_gen_base_rfc6733). -define(BOOL, [true, false]). -define(A, list_to_atom). @@ -158,7 +159,8 @@ gen(M, messages, {Name, Code, Flags, _, _}) -> Name = case M:msg_name(Code, lists:member('REQ', Flags)) of N when Name /= 'answer-message' -> N; - '' when Name == 'answer-message', M == ?BASE -> + '' when Name == 'answer-message', (M == ?RFC3588 + orelse M == ?RFC6733) -> Name end, [] = arity(M, Name, Rname); diff --git a/lib/diameter/test/diameter_ct.erl b/lib/diameter/test/diameter_ct.erl index ded50bf6c5..1697287a22 100644 --- a/lib/diameter/test/diameter_ct.erl +++ b/lib/diameter/test/diameter_ct.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2012. All Rights Reserved. +%% Copyright Ericsson AB 2010-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 @@ -23,16 +23,23 @@ %% Module used to run suites from Makefile. %% --export([run/1]). +-export([run/1, + cover/0]). %% The makefile looks for signs of failure so ignore the ct:run_test/1 %% return value. -run([Suite]) -> +run(Suites) -> + ct_run([{suite, Suites}]). + +cover() -> + ct_run([{spec, "./testspec"}]). + +ct_run(Opts) -> Start = info(), - ct:run_test([{suite, Suite}, - {logdir, "./log"}, - {auto_compile, false}]), + ct:run_test([{logdir, "./log"}, + {auto_compile, false} + | Opts]), info(Start , info()). info() -> diff --git a/lib/diameter/test/diameter_event_SUITE.erl b/lib/diameter/test/diameter_event_SUITE.erl new file mode 100644 index 0000000000..18bdcb1f54 --- /dev/null +++ b/lib/diameter/test/diameter_event_SUITE.erl @@ -0,0 +1,180 @@ +%% +%% %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 events sent as a consequence of diameter:subscribe/1. +%% Watchdog events are dealt with more extensively in the watchdog +%% suite. +%% + +-module(diameter_event_SUITE). + +-export([suite/0, + all/0, + init_per_testcase/2, + end_per_testcase/2]). + +%% testcases +-export([start/1, + start_server/1, + up/1, + down/1, + cea_timeout/1, + stop/1]). + +-include("diameter.hrl"). + +%% =========================================================================== + +-define(util, diameter_util). + +-define(ADDR, {127,0,0,1}). +-define(REALM, "REALM"). + +-define(SERVER, "SERVER.SERVER-REALM"). +-define(CLIENT, "CLIENT.CLIENT-REALM"). + +-define(DICT_COMMON, ?DIAMETER_DICT_COMMON). +-define(DICT_ACCT, ?DIAMETER_DICT_ACCOUNTING). + +-define(SERVER_CAPX_TMO, 6000). + +%% Config for diameter:start_service/2. +-define(SERVICE(Host, Dicts), + [{'Origin-Host', Host}, + {'Origin-Realm', realm(Host)}, + {'Host-IP-Address', [?ADDR]}, + {'Vendor-Id', 12345}, + {'Product-Name', "OTP/diameter"}, + {'Acct-Application-Id', [D:id() || D <- Dicts]} + | [{application, [{dictionary, D}, + {module, #diameter_callback{}}]} + || D <- Dicts]]). + +%% Diameter Result-Code's: +-define(NO_COMMON_APP, 5010). + +%% =========================================================================== + +suite() -> + [{timetrap, {seconds, 60}}]. + +all() -> + [start, + start_server, + up, + down, + cea_timeout, + stop]. + +init_per_testcase(Name, Config) -> + [{name, Name} | Config]. + +end_per_testcase(_, _) -> + ok. + +%% =========================================================================== +%% start/stop testcases + +start(_Config) -> + ok = diameter:start(). + +start_server(Config) -> + diameter:subscribe(?SERVER), + ok = diameter:start_service(?SERVER, ?SERVICE(?SERVER, [?DICT_COMMON])), + LRef = ?util:listen(?SERVER, tcp, [{capabilities_cb, fun capx_cb/2}, + {capx_timeout, ?SERVER_CAPX_TMO}]), + [PortNr] = ?util:lport(tcp, LRef, 20), + ?util:write_priv(Config, portnr, PortNr), + start = event(?SERVER). + +%% Connect with matching capabilities and expect the connection to +%% come up. +up(Config) -> + {Svc, Ref} = connect(Config, []), + start = event(Svc), + {up, Ref, {_,_Caps}, _Config, #diameter_packet{}} = event(Svc), + {watchdog, Ref, _, {initial, okay}, _} = event(Svc). + +%% Connect with non-matching capabilities and expect CEA from the peer +%% to indicate as much and then for the transport to be restarted +%% (after reconnect_timer). +down(Config) -> + {Svc, Ref} = connect(Config, [{capabilities, [{'Acct-Application-Id', + [?DICT_ACCT:id()]}]}, + {applications, [?DICT_ACCT]}, + {reconnect_timer, 5000}]), + start = event(Svc), + {closed, Ref, {'CEA', ?NO_COMMON_APP, _, #diameter_packet{}}, _} + = event(Svc), + {reconnect, Ref, _} = event(Svc). + +%% Connect with matching capabilities but have the server delay its +%% CEA and cause the client to timeout. +cea_timeout(Config) -> + {Svc, Ref} = connect(Config, [{capx_timeout, ?SERVER_CAPX_TMO div 2}, + {reconnect_timer, 2*?SERVER_CAPX_TMO}]), + start = event(Svc), + {closed, Ref, {'CEA', timeout}, _} = event(Svc). + +stop(_Config) -> + ok = diameter:stop(). + +%% ---------------------------------------- + +%% Keep the server from sending CEA until the client has timed out. +capx_cb(_, #diameter_caps{origin_host = {_, "cea_timeout-" ++ _}}) -> + receive after ?SERVER_CAPX_TMO -> ok end; + +%% Or not. +capx_cb(_, _Caps) -> + ok. + +%% ---------------------------------------- + +%% Use the testcase name to construct Origin-Host of the client so +%% that the server can match on it in capx_cb/2. +connect(Config, Opts) -> + Pre = atom_to_list(proplists:get_value(name, Config)), + Name = Pre ++ uniq() ++ ?CLIENT, + diameter:subscribe(Name), + ok = start_service(Name, ?SERVICE(Name, [?DICT_COMMON, ?DICT_ACCT])), + {ok, Ref} = diameter:add_transport(Name, opts(Config, Opts)), + {Name, Ref}. + +uniq() -> + {MS,S,US} = now(), + lists:flatten(io_lib:format("-~p-~p-~p-", [MS,S,US])). + +event(Name) -> + receive #diameter_event{service = Name, info = T} -> T end. + +start_service(Name, Opts) -> + diameter:start_service(Name, [{monitor, self()} | Opts]). + +opts(Config, Opts) -> + PortNr = ?util:read_priv(Config, portnr), + + {connect, [{transport_module, diameter_tcp}, + {transport_config, [{ip, ?ADDR}, {port, 0}, + {raddr, ?ADDR}, {rport, PortNr}]} + | Opts]}. + +realm(Host) -> + tl(lists:dropwhile(fun(C) -> C /= $. end, Host)). diff --git a/lib/diameter/test/diameter_failover_SUITE.erl b/lib/diameter/test/diameter_failover_SUITE.erl index ed31670031..dfd3253827 100644 --- a/lib/diameter/test/diameter_failover_SUITE.erl +++ b/lib/diameter/test/diameter_failover_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2012. All Rights Reserved. +%% Copyright Ericsson AB 2010-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 @@ -18,19 +18,19 @@ %% %% -%% Tests of traffic between six Diameter nodes in three realms, +%% Tests of traffic between seven Diameter nodes in four realms, %% connected as follows. %% -%% ----- SERVER1.REALM2 -%% / -%% / ----- SERVER2.REALM2 -%% | / -%% CLIENT.REALM1 ------ SERVER3.REALM2 -%% | \ -%% | \ -%% \ ---- SERVER1.REALM3 -%% \ -%% ----- SERVER2.REALM3 +%% ----- SERVER1.REALM2 ----- +%% / \ +%% / ----- SERVER2.REALM2 ----- \ +%% | / \ | +%% CLIENT.REALM1 ------ SERVER3.REALM2 ------ CLIENT.REALM4 +%% | \ / | +%% | \ / | +%% \ ---- SERVER1.REALM3 ----- / +%% \ / +%% ----- SERVER2.REALM3 ----- %% -module(diameter_failover_SUITE). @@ -44,12 +44,16 @@ connect/1, send_ok/1, send_nok/1, + send_discard_1/1, + send_discard_2/1, stop_services/1, stop/1]). %% diameter callbacks -export([pick_peer/4, prepare_request/3, + prepare_retransmit/3, + handle_error/4, handle_answer/4, handle_request/3]). @@ -62,14 +66,18 @@ -define(ADDR, {127,0,0,1}). --define(CLIENT, "CLIENT.REALM1"). +-define(CLIENT1, "CLIENT.REALM1"). +-define(CLIENT2, "CLIENT.REALM4"). -define(SERVER1, "SERVER1.REALM2"). -define(SERVER2, "SERVER2.REALM2"). -define(SERVER3, "SERVER3.REALM2"). -define(SERVER4, "SERVER1.REALM3"). -define(SERVER5, "SERVER2.REALM3"). --define(SERVICES, [?CLIENT, ?SERVER1, ?SERVER2, ?SERVER3, ?SERVER4, ?SERVER5]). +-define(IS_CLIENT(Svc), Svc == ?CLIENT1; Svc == ?CLIENT2). + +-define(CLIENTS, [?CLIENT1, ?CLIENT2]). +-define(SERVERS, [?SERVER1, ?SERVER2, ?SERVER3, ?SERVER4, ?SERVER5]). -define(DICT_COMMON, ?DIAMETER_DICT_COMMON). @@ -77,26 +85,27 @@ -define(APP_ID, ?DICT_COMMON:id()). %% Config for diameter:start_service/2. --define(SERVICE(Host, Dict), +-define(SERVICE(Host), [{'Origin-Host', Host}, {'Origin-Realm', realm(Host)}, {'Host-IP-Address', [?ADDR]}, {'Vendor-Id', 12345}, {'Product-Name', "OTP/diameter"}, - {'Acct-Application-Id', [Dict:id()]}, + {'Acct-Application-Id', [?APP_ID]}, {application, [{alias, ?APP_ALIAS}, - {dictionary, Dict}, + {dictionary, ?DICT_COMMON}, {module, #diameter_callback {peer_up = false, peer_down = false, - handle_error = false, - prepare_retransmit = false, default = ?MODULE}}, {answer_errors, callback}]}]). -define(SUCCESS, 2001). --define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_LOGOUT'). +%% Value of Termination-Cause determines client/server behaviour. +-define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'). +-define(MOVED, ?'DIAMETER_BASE_TERMINATION-CAUSE_USER_MOVED'). +-define(TIMEOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_SESSION_TIMEOUT'). %% =========================================================================== @@ -109,6 +118,8 @@ all() -> connect, send_ok, send_nok, + send_discard_1, + send_discard_2, stop_services, stop]. @@ -119,19 +130,20 @@ start(_Config) -> ok = diameter:start(). start_services(_Config) -> - S = [server(N, ?DICT_COMMON) || N <- tl(?SERVICES)], - - ok = diameter:start_service(?CLIENT, ?SERVICE(?CLIENT, ?DICT_COMMON)), + Servers = [server(N) || N <- ?SERVERS], + [] = [T || C <- ?CLIENTS, + T <- [diameter:start_service(C, ?SERVICE(C))], + T /= ok], - {save_config, [{?CLIENT, S}]}. + {save_config, Servers}. connect(Config) -> - {_, Conns} = proplists:get_value(saved_config, Config), + {start_services, Servers} = proplists:get_value(saved_config, Config), - lists:foreach(fun({CN,Ss}) -> connect(CN, Ss) end, Conns). + lists:foreach(fun(C) -> connect(C, Servers) end, ?CLIENTS). stop_services(_Config) -> - [] = [{H,T} || H <- ?SERVICES, + [] = [{H,T} || H <- ?CLIENTS ++ ?SERVERS, T <- [diameter:stop_service(H)], T /= ok]. @@ -140,8 +152,8 @@ stop(_Config) -> %% ---------------------------------------- -server(Name, Dict) -> - ok = diameter:start_service(Name, ?SERVICE(Name, Dict)), +server(Name) -> + ok = diameter:start_service(Name, ?SERVICE(Name)), {Name, ?util:listen(Name, tcp)}. connect(Name, Refs) -> @@ -153,30 +165,39 @@ connect(Name, Refs) -> %% Send an STR and expect success after SERVER3 answers after a couple %% of failovers. send_ok(_Config) -> - Req = ['STR', {'Destination-Realm', realm(?SERVER1)}, - {'Termination-Cause', ?LOGOUT}, - {'Auth-Application-Id', ?APP_ID}], + Req = #diameter_base_STR{'Destination-Realm' = realm(?SERVER1), + 'Termination-Cause' = ?LOGOUT, + 'Auth-Application-Id' = ?APP_ID}, #diameter_base_STA{'Result-Code' = ?SUCCESS, 'Origin-Host' = ?SERVER3} - = call(Req, [{filter, realm}]). + = call(?CLIENT1, Req). %% Send an STR and expect failure when both servers fail. send_nok(_Config) -> - Req = ['STR', {'Destination-Realm', realm(?SERVER4)}, - {'Termination-Cause', ?LOGOUT}, - {'Auth-Application-Id', ?APP_ID}], - {error, failover} = call(Req, [{filter, realm}]). + Req = #diameter_base_STR{'Destination-Realm' = realm(?SERVER4), + 'Termination-Cause' = ?LOGOUT, + 'Auth-Application-Id' = ?APP_ID}, + {failover, ?LOGOUT} = call(?CLIENT1, Req). + +%% Send an STR and have prepare_retransmit discard it. +send_discard_1(_Config) -> + Req = #diameter_base_STR{'Destination-Realm' = realm(?SERVER1), + 'Termination-Cause' = ?TIMEOUT, + 'Auth-Application-Id' = ?APP_ID}, + {rejected, ?TIMEOUT} = call(?CLIENT2, Req). +send_discard_2(_Config) -> + Req = #diameter_base_STR{'Destination-Realm' = realm(?SERVER4), + 'Termination-Cause' = ?MOVED, + 'Auth-Application-Id' = ?APP_ID}, + {discarded, ?MOVED} = call(?CLIENT2, Req). %% =========================================================================== realm(Host) -> tl(lists:dropwhile(fun(C) -> C /= $. end, Host)). -call(Req, Opts) -> - diameter:call(?CLIENT, ?APP_ALIAS, Req, Opts). - -set([H|T], Vs) -> - [H | Vs ++ T]. +call(Svc, Req) -> + diameter:call(Svc, ?APP_ALIAS, Req, [{filter, realm}]). %% =========================================================================== %% diameter callbacks @@ -184,7 +205,8 @@ set([H|T], Vs) -> %% pick_peer/4 %% Choose a server other than SERVER3 or SERVER5 if possible. -pick_peer(Peers, _, ?CLIENT, _State) -> +pick_peer(Peers, _, Svc, _State) + when ?IS_CLIENT(Svc) -> case lists:partition(fun({_, #diameter_caps{origin_host = {_, OH}}}) -> OH /= ?SERVER3 andalso OH /= ?SERVER5 end, @@ -198,20 +220,41 @@ pick_peer(Peers, _, ?CLIENT, _State) -> %% prepare_request/3 -prepare_request(Pkt, ?CLIENT, {_Ref, Caps}) -> +prepare_request(Pkt, Svc, {_Ref, Caps}) + when ?IS_CLIENT(Svc) -> {send, prepare(Pkt, Caps)}. prepare(#diameter_packet{msg = Req}, Caps) -> #diameter_caps{origin_host = {OH, _}, origin_realm = {OR, _}} = Caps, - set(Req, [{'Session-Id', diameter:session_id(OH)}, - {'Origin-Host', OH}, - {'Origin-Realm', OR}]). + Req#diameter_base_STR{'Origin-Host' = OH, + 'Origin-Realm' = OR, + 'Session-Id' = diameter:session_id(OH)}. + +%% prepare_retransmit/3 + +prepare_retransmit(#diameter_packet{header = H} = P, Svc, {_,_}) + when ?IS_CLIENT(Svc) -> + #diameter_header{is_retransmitted = true} = H, %% assert + prepare(P). + +prepare(#diameter_packet{msg = M} = P) -> + case M#diameter_base_STR.'Termination-Cause' of + ?LOGOUT -> {send, P}; + ?MOVED -> discard; + ?TIMEOUT -> {discard, rejected} + end. + +%% handle_error/4 + +handle_error(Reason, Req, _, _) -> + {Reason, Req#diameter_base_STR.'Termination-Cause'}. %% handle_answer/4 -handle_answer(Pkt, _Req, ?CLIENT, _Peer) -> +handle_answer(Pkt, _Req, Svc, _Peer) + when ?IS_CLIENT(Svc) -> #diameter_packet{msg = Rec, errors = []} = Pkt, Rec. @@ -219,8 +262,8 @@ handle_answer(Pkt, _Req, ?CLIENT, _Peer) -> %% Only SERVER3 actually answers. handle_request(Pkt, ?SERVER3, {_, Caps}) -> - #diameter_packet{msg = #diameter_base_STR{'Session-Id' = SId, - 'Origin-Host' = ?CLIENT}} + #diameter_packet{header = #diameter_header{is_retransmitted = true}, + msg = #diameter_base_STR{'Session-Id' = SId}} = Pkt, #diameter_caps{origin_host = {OH, _}, origin_realm = {OR, _}} diff --git a/lib/diameter/test/diameter_gen_sctp_SUITE.erl b/lib/diameter/test/diameter_gen_sctp_SUITE.erl index 2fde7b9fdb..51ccb1e6ec 100644 --- a/lib/diameter/test/diameter_gen_sctp_SUITE.erl +++ b/lib/diameter/test/diameter_gen_sctp_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2012. All Rights Reserved. +%% Copyright Ericsson AB 2010-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 @@ -355,7 +355,7 @@ open(Opts) -> gen_sctp:open([{ip, ?ADDR}, {port, 0}, {active, true}, binary, {recbuf, 1 bsl 16}, {sndbuf, 1 bsl 16} | Opts]). - + %% assoc/1 assoc(Sock) -> diff --git a/lib/diameter/test/diameter_length_SUITE.erl b/lib/diameter/test/diameter_length_SUITE.erl new file mode 100644 index 0000000000..ffb19d2288 --- /dev/null +++ b/lib/diameter/test/diameter_length_SUITE.erl @@ -0,0 +1,289 @@ +%% +%% %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 transport_opt() length_errors. +%% + +-module(diameter_length_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/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_rfc3588.hrl"). + +%% =========================================================================== + +-define(util, diameter_util). + +-define(CLIENT, "CLIENT"). +-define(SERVER, "SERVER"). +-define(REALM, "erlang.org"). +-define(HOST(Host, Realm), Host ++ [$.|Realm]). +-define(DICT, diameter_gen_base_rfc3588). + +%% Config for diameter:start_service/2. +-define(SERVICE(Name), + [{'Origin-Host', Name ++ "." ++ ?REALM}, + {'Origin-Realm', ?REALM}, + {'Host-IP-Address', [{127,0,0,1}]}, + {'Vendor-Id', 12345}, + {'Product-Name', "OTP/diameter"}, + {'Auth-Application-Id', [?DIAMETER_APP_ID_COMMON]}, + {application, [{dictionary, ?DICT}, + {module, ?MODULE}, + {answer_errors, callback}]}]). + +-define(SUCCESS, + ?'DIAMETER_BASE_RESULT-CODE_SUCCESS'). +-define(MISSING_AVP, + ?'DIAMETER_BASE_RESULT-CODE_MISSING_AVP'). +-define(INVALID_MESSAGE_LENGTH, + ?'DIAMETER_BASE_RESULT-CODE_INVALID_MESSAGE_LENGTH'). + +-define(LOGOUT, + ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'). + +-define(GROUPS, [exit, handle, discard]). + +-define(L, atom_to_list). + +%% =========================================================================== + +suite() -> + [{timetrap, {seconds, 60}}]. + +all() -> + [{group, G} || G <- ?GROUPS]. + +groups() -> + [{G, [], [start, send, stop]} || G <- ?GROUPS]. + +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) -> + Config. + +end_per_testcase(_, _) -> + ok. + +origin(exit) -> 0; +origin(handle) -> 1; +origin(discard) -> 2; + +origin(0) -> exit; +origin(1) -> handle; +origin(2) -> discard. + +%% =========================================================================== + +%% start/1 + +start(Config) -> + Group = proplists:get_value(group, Config), + ok = diameter:start_service(?SERVER, ?SERVICE(?L(Group))), + ok = diameter:start_service(?CLIENT, ?SERVICE(?CLIENT)), + LRef = ?util:listen(?SERVER, + tcp, + [{length_errors, Group}]), + ?util:connect(?CLIENT, + tcp, + LRef, + [{capabilities, [{'Origin-State-Id', origin(Group)}]}]). + +%% 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/1 + +%% Server transport exits on messages of insuffient length. +send(exit) -> + %% Transport exit is followed by failover but there's only one + %% transport to choose from. + {error, failover} = call(4); + +%% Server transport receives messages of insufficient length. +send(handle) -> + %% Message Length too large: diameter_tcp flushes the request + %% when no additional bytes arrive. + #diameter_base_STA{'Result-Code' = ?INVALID_MESSAGE_LENGTH} + = call(4), + %% Another request answered as it should. + #diameter_base_STA{'Result-Code' = ?SUCCESS} + = call(0), + %% Message Length conveniently small: the trailing optional + %% Origin-State-Id isn't included in the received request. + #diameter_base_STA{'Result-Code' = ?SUCCESS} + = call(-12), + %% Server receives Origin-State-Id AVP as the first 12 bytes of + %% the next request: AVP <<Code:32, Flags:8, Len:24, Data:32>> is + %% interpreted as header <<Version:8, Len:24, Flags:8, Code:24, + %% ApplId: 32>>. In particular, the AVP Length 12 = 00001100 is + %% interpreted as Command Flags, so R=0 and the request is + %% interpreted as an unsolicited answer. Increase Message Length + %% to have the server receive all bytes sent thusfar. + {error, timeout} + = call(12), + %% Another request answered as it should. + #diameter_base_STA{'Result-Code' = ?SUCCESS} + = call(0), + %% Shorten Message Length so much that that the server doesn't + %% receive the required Termination-Cause AVP. + #diameter_base_STA{'Result-Code' = ?MISSING_AVP} + = call(-24); + +%% Server transport discards message of insufficient length. +send(discard) -> + %% First request times out when the server discards it but a + %% second succeeds since the transport remains up. + {error, timeout} + = call(4), + #diameter_base_STA{'Result-Code' = ?SUCCESS} + = call(0); + +send(Config) -> + send(proplists:get_value(group, Config)). + +%% =========================================================================== + +call(Delta) -> + diameter:call(?CLIENT, + ?DICT, + #diameter_base_STR + {'Termination-Cause' = ?LOGOUT, + 'Auth-Application-Id' = ?DIAMETER_APP_ID_COMMON, + 'Origin-State-Id' = [7]}, + [{extra, [Delta]}]). + +%% =========================================================================== +%% 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, _Delta) -> + {ok, Peer}. + +%% prepare_request/4 + +prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, Delta) -> + {send, resize(Delta, prepare(Pkt, Caps))}. + +prepare(#diameter_packet{msg = Req0} = Pkt, Caps) -> + #diameter_caps{origin_host = {OH, _}, + origin_realm = {OR, DR}} + = Caps, + Req = Req0#diameter_base_STR{'Session-Id' = diameter:session_id(OH), + 'Origin-Host' = OH, + 'Origin-Realm' = OR, + 'Destination-Realm' = DR}, + diameter_codec:encode(?DICT, Pkt#diameter_packet{msg = Req}). + +resize(0, Pkt) -> + Pkt; +resize(Delta, #diameter_packet{bin = Bin} = Pkt) -> + Pkt#diameter_packet{bin = resize(Delta, Bin)}; + +resize(Delta, <<V, Len:24, T/binary>>) -> + <<V, (Len + Delta):24, T/binary>>. + +%% handle_answer/5 + +handle_answer(Pkt, _Req, ?CLIENT, _Peer, _Delta) -> + Pkt#diameter_packet.msg. + +%% handle_error/5 + +handle_error(Reason, _Req, ?CLIENT, _Peer, _Delta) -> + {error, Reason}. + +%% handle_request/3 + +handle_request(Pkt, ?SERVER, {_Ref, Caps}) -> + #diameter_caps{origin_host = {OH, _}, + origin_realm = {OR, _}, + origin_state_id = {_,[Id]}} + = Caps, + answer(origin(Id), + Pkt, + #diameter_base_STA{'Result-Code' = ?SUCCESS, + 'Session-Id' = diameter:session_id(OH), + 'Origin-Host' = OH, + 'Origin-Realm' = OR}). + +answer(Group, #diameter_packet{errors = Es}, Ans) -> + answer(Group, Es, Ans); + +%% No errors: just answer. +answer(_, [], Ans) -> + {reply, Ans}; + +%% Otherwise an invalid length should only reach the callback if +%% length_errors = handle. +answer(Group, [RC|_], Ans) + when RC == ?INVALID_MESSAGE_LENGTH, Group == handle; + RC /= ?INVALID_MESSAGE_LENGTH -> + {reply, Ans}. diff --git a/lib/diameter/test/diameter_relay_SUITE.erl b/lib/diameter/test/diameter_relay_SUITE.erl index f10d82bdf8..735a908d97 100644 --- a/lib/diameter/test/diameter_relay_SUITE.erl +++ b/lib/diameter/test/diameter_relay_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2012. All Rights Reserved. +%% Copyright Ericsson AB 2010-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 @@ -107,7 +107,7 @@ -define(LOOP_DETECTED, 3005). -define(UNABLE_TO_DELIVER, 3002). --define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_LOGOUT'). +-define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'). -define(AUTHORIZE_ONLY, ?'DIAMETER_BASE_RE-AUTH-REQUEST-TYPE_AUTHORIZE_ONLY'). %% =========================================================================== diff --git a/lib/diameter/test/diameter_stats_SUITE.erl b/lib/diameter/test/diameter_stats_SUITE.erl index 8b7d8cb1b6..af52afb59c 100644 --- a/lib/diameter/test/diameter_stats_SUITE.erl +++ b/lib/diameter/test/diameter_stats_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2012. All Rights Reserved. +%% Copyright Ericsson AB 2010-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 @@ -69,7 +69,7 @@ reg(_) -> true = ?stat:reg(Ref), false = ?stat:reg(Ref). %% duplicate -incr(_) -> +incr(_) -> Ref = '_', Ctr = x, false = ?stat:incr(Ctr), %% not registered, diff --git a/lib/diameter/test/diameter_tls_SUITE.erl b/lib/diameter/test/diameter_tls_SUITE.erl index 6cc34b20c5..77194a0f56 100644 --- a/lib/diameter/test/diameter_tls_SUITE.erl +++ b/lib/diameter/test/diameter_tls_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2012. All Rights Reserved. +%% Copyright Ericsson AB 2010-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 @@ -122,7 +122,7 @@ {capabilities, Caps}]}). -define(SUCCESS, 2001). --define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_LOGOUT'). +-define(LOGOUT, ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'). %% =========================================================================== diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl index c157b0e304..781ed234cc 100644 --- a/lib/diameter/test/diameter_traffic_SUITE.erl +++ b/lib/diameter/test/diameter_traffic_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2012. All Rights Reserved. +%% Copyright Ericsson AB 2010-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 @@ -38,7 +38,9 @@ result_codes/1, send_ok/1, send_nok/1, + send_eval/1, send_bad_answer/1, + send_protocol_error/1, send_arbitrary/1, send_unknown/1, send_unknown_mandatory/1, @@ -47,6 +49,9 @@ send_unsupported_app/1, send_error_bit/1, send_unsupported_version/1, + send_invalid_avp_bits/1, + send_invalid_avp_length/1, + send_invalid_reject/1, send_long/1, send_nopeer/1, send_noapp/1, @@ -75,7 +80,9 @@ send_multiple_filters_2/1, send_multiple_filters_3/1, send_anything/1, + outstanding/1, remove_transports/1, + empty/1, stop_services/1, stop/1]). @@ -92,11 +99,21 @@ -include("diameter.hrl"). -include("diameter_gen_base_rfc3588.hrl"). -include("diameter_gen_base_accounting.hrl"). +%% The listening transports use RFC 3588 dictionaries, the client +%% transports use either 3588 or 6733. (So can't use the record +%% definitions in the latter case.) %% =========================================================================== -define(util, diameter_util). +-define(A, list_to_atom). +-define(L, atom_to_list). + +%% Don't use is_record/2 since dictionary hrl's aren't included. +%% (Since they define conflicting reqcords with the same names.) +-define(is_record(Rec, Name), (Name == element(1, Rec))). + -define(ADDR, {127,0,0,1}). -define(CLIENT, "CLIENT"). @@ -106,23 +123,42 @@ -define(EXTRA, an_extra_argument). --define(BASE, ?DIAMETER_DICT_COMMON). --define(ACCT, ?DIAMETER_DICT_ACCOUNTING). - %% Sequence mask for End-to-End and Hop-by-Hop identifiers. -define(CLIENT_MASK, {1,26}). %% 1 in top 6 bits -%% Run tests cases in different encoding variants. Send outgoing -%% messages as lists or records. +%% How to construct messages, as record or list. -define(ENCODINGS, [list, record]). -%% Identifers for client connections. --define(CONNECTIONS, [c1,c2,c3]). +%% How to send answers, in a diameter_packet or not. +-define(CONTAINERS, [pkt, msg]). + +%% Which common dictionary to use in the clients. +-define(RFCS, [rfc3588, rfc6733]). + +-record(group, + {client_encoding, + client_dict0, + server_encoding, + server_container}). %% Not really what we should be setting unless the message is sent in %% the common application but diameter doesn't care. -define(APP_ID, ?DIAMETER_APP_ID_COMMON). +%% An Application-ID the server doesn't support. +-define(BAD_APP, 42). + +%% A common match when receiving answers in a client. +-define(answer_message(SessionId, ResultCode), + ['answer-message', + {'Session-Id', SessionId}, + {'Origin-Host', _}, + {'Origin-Realm', _}, + {'Result-Code', ResultCode} + | _]). +-define(answer_message(ResultCode), + ?answer_message(_, ResultCode)). + %% Config for diameter:start_service/2. -define(SERVICE(Name), [{'Origin-Host', Name ++ "." ++ ?REALM}, @@ -136,28 +172,33 @@ | [{application, [{dictionary, D}, {module, ?MODULE}, {answer_errors, callback}]} - || D <- [?BASE, ?ACCT]]]). + || D <- [diameter_gen_base_rfc3588, + diameter_gen_base_accounting, + diameter_gen_base_rfc6733, + diameter_gen_acct_rfc6733]]]). -define(SUCCESS, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_SUCCESS'). + ?'DIAMETER_BASE_RESULT-CODE_SUCCESS'). -define(COMMAND_UNSUPPORTED, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_COMMAND_UNSUPPORTED'). + ?'DIAMETER_BASE_RESULT-CODE_COMMAND_UNSUPPORTED'). -define(TOO_BUSY, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_TOO_BUSY'). + ?'DIAMETER_BASE_RESULT-CODE_TOO_BUSY'). -define(APPLICATION_UNSUPPORTED, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_APPLICATION_UNSUPPORTED'). + ?'DIAMETER_BASE_RESULT-CODE_APPLICATION_UNSUPPORTED'). -define(INVALID_HDR_BITS, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_INVALID_HDR_BITS'). + ?'DIAMETER_BASE_RESULT-CODE_INVALID_HDR_BITS'). -define(INVALID_AVP_BITS, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_INVALID_AVP_BITS'). + ?'DIAMETER_BASE_RESULT-CODE_INVALID_AVP_BITS'). -define(AVP_UNSUPPORTED, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_AVP_UNSUPPORTED'). + ?'DIAMETER_BASE_RESULT-CODE_AVP_UNSUPPORTED'). -define(UNSUPPORTED_VERSION, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_UNSUPPORTED_VERSION'). + ?'DIAMETER_BASE_RESULT-CODE_UNSUPPORTED_VERSION'). -define(REALM_NOT_SERVED, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_REALM_NOT_SERVED'). + ?'DIAMETER_BASE_RESULT-CODE_REALM_NOT_SERVED'). -define(UNABLE_TO_DELIVER, - ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_UNABLE_TO_DELIVER'). + ?'DIAMETER_BASE_RESULT-CODE_UNABLE_TO_DELIVER'). +-define(INVALID_AVP_LENGTH, + ?'DIAMETER_BASE_RESULT-CODE_INVALID_AVP_LENGTH'). -define(EVENT_RECORD, ?'DIAMETER_BASE_ACCOUNTING-RECORD-TYPE_EVENT_RECORD'). @@ -167,14 +208,11 @@ ?'DIAMETER_BASE_RE-AUTH-REQUEST-TYPE_AUTHORIZE_AUTHENTICATE'). -define(LOGOUT, - ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_LOGOUT'). + ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'). -define(BAD_ANSWER, - ?'DIAMETER_BASE_TERMINATION-CAUSE_DIAMETER_BAD_ANSWER'). - --define(A, list_to_atom). --define(L, atom_to_list). - --define(NAME(A,B), ?A(?L(A) ++ "," ++ ?L(B))). + ?'DIAMETER_BASE_TERMINATION-CAUSE_BAD_ANSWER'). +-define(USER_MOVED, + ?'DIAMETER_BASE_TERMINATION-CAUSE_USER_MOVED'). %% =========================================================================== @@ -183,17 +221,27 @@ suite() -> all() -> [start, start_services, add_transports, result_codes] - ++ [{group, ?util:name([E,C]), P} || E <- ?ENCODINGS, - C <- ?CONNECTIONS, - P <- [[], [parallel]]] - ++ [remove_transports, stop_services, stop]. + ++ [{group, ?util:name([R,D,A,C]), P} || R <- ?ENCODINGS, + D <- ?RFCS, + A <- ?ENCODINGS, + C <- ?CONTAINERS, + P <- [[], [parallel]]] + ++ [outstanding, remove_transports, empty, stop_services, stop]. groups() -> Ts = tc(), - [{?util:name([E,C]), [], Ts} || E <- ?ENCODINGS, C <- ?CONNECTIONS]. + [{?util:name([R,D,A,C]), [], Ts} || R <- ?ENCODINGS, + D <- ?RFCS, + A <- ?ENCODINGS, + C <- ?CONTAINERS]. init_per_group(Name, Config) -> - [{group, Name} | Config]. + [R,D,A,C] = ?util:name(Name), + G = #group{client_encoding = R, + client_dict0 = dict0(D), + server_encoding = A, + server_container = C}, + [{group, G} | Config]. end_per_group(_, _) -> ok. @@ -209,7 +257,9 @@ end_per_testcase(_, _) -> tc() -> [send_ok, send_nok, + send_eval, send_bad_answer, + send_protocol_error, send_arbitrary, send_unknown, send_unknown_mandatory, @@ -218,6 +268,9 @@ tc() -> send_unsupported_app, send_error_bit, send_unsupported_version, + send_invalid_avp_bits, + send_invalid_avp_length, + send_invalid_reject, send_long, send_nopeer, send_noapp, @@ -259,10 +312,33 @@ start_services(_Config) -> | ?SERVICE(?CLIENT)]). add_transports(Config) -> - LRef = ?util:listen(?SERVER, tcp, [{capabilities_cb, fun capx/2}]), - Cs = [?util:connect(?CLIENT, tcp, LRef, [{id, C}]) || C <- ?CONNECTIONS], + LRef = ?util:listen(?SERVER, + tcp, + [{capabilities_cb, fun capx/2}, + {applications, apps(rfc3588)}]), + Cs = [?util:connect(?CLIENT, + tcp, + LRef, + [{id, Id}, + {capabilities, [{'Origin-State-Id', origin(Id)}]}, + {applications, apps(D)}]) + || A <- ?ENCODINGS, + C <- ?CONTAINERS, + D <- ?RFCS, + Id <- [{A,C}]], + %% The server uses the client's Origin-State-Id to decide how to + %% answer. ?util:write_priv(Config, "transport", [LRef | Cs]). +apps(D0) -> + D = dict0(D0), + [acct(D), D]. + +%% Ensure there are no outstanding requests in request table. +outstanding(_Config) -> + [] = [T || T <- ets:tab2list(diameter_request), + is_atom(element(1,T))]. + remove_transports(Config) -> [LRef | Cs] = ?util:read_priv(Config, "transport"), [?util:disconnect(?CLIENT, C, ?SERVER, LRef) || C <- Cs]. @@ -271,6 +347,10 @@ stop_services(_Config) -> ok = diameter:stop_service(?CLIENT), ok = diameter:stop_service(?SERVER). +%% Ensure even transports have been removed from request table. +empty(_Config) -> + [] = ets:tab2list(diameter_request). + stop(_Config) -> ok = diameter:stop(). @@ -282,7 +362,7 @@ capx(_, #diameter_caps{origin_host = {OH,DH}}) -> %% Ensure that result codes have the expected values. result_codes(_Config) -> - {2001, 3001, 3002, 3003, 3004, 3007, 3008, 3009, 5001, 5011} + {2001, 3001, 3002, 3003, 3004, 3007, 3008, 3009, 5001, 5011, 5014} = {?SUCCESS, ?COMMAND_UNSUPPORTED, ?UNABLE_TO_DELIVER, @@ -292,22 +372,31 @@ result_codes(_Config) -> ?INVALID_HDR_BITS, ?INVALID_AVP_BITS, ?AVP_UNSUPPORTED, - ?UNSUPPORTED_VERSION}. + ?UNSUPPORTED_VERSION, + ?INVALID_AVP_LENGTH}. %% Send an ACR and expect success. send_ok(Config) -> Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, {'Accounting-Record-Number', 1}], - - #diameter_base_accounting_ACA{'Result-Code' = ?SUCCESS} + + ['ACA', _SessionId, {'Result-Code', ?SUCCESS} | _] = call(Config, Req). %% Send an accounting ACR that the server answers badly to. send_nok(Config) -> Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, {'Accounting-Record-Number', 0}], - - #'diameter_base_answer-message'{'Result-Code' = ?INVALID_AVP_BITS} + + ?answer_message(?INVALID_AVP_BITS) + = call(Config, Req). + +%% Send an ACR and expect success. +send_eval(Config) -> + Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, + {'Accounting-Record-Number', 3}], + + ['ACA', _SessionId, {'Result-Code', ?SUCCESS} | _] = call(Config, Req). %% Send an accounting ACR that the server tries to answer with an @@ -318,43 +407,50 @@ send_bad_answer(Config) -> {'Accounting-Record-Number', 2}], {error, timeout} = call(Config, Req). +%% Send an ACR that the server callback answers explicitly with a +%% protocol error. +send_protocol_error(Config) -> + Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, + {'Accounting-Record-Number', 4}], + + ?answer_message(?TOO_BUSY) + = call(Config, Req). + %% Send an ASR with an arbitrary AVP and expect success and the same %% AVP in the reply. send_arbitrary(Config) -> Req = ['ASR', {'AVP', [#diameter_avp{name = 'Class', value = "XXX"}]}], - #diameter_base_ASA{'Result-Code' = ?SUCCESS, - 'AVP' = Avps} + ['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps] = call(Config, Req), - [#diameter_avp{name = 'Class', - value = "XXX"}] - = Avps. + {'AVP', [#diameter_avp{name = 'Class', + value = "XXX"}]} + = lists:last(Avps). %% Send an unknown AVP (to some client) and check that it comes back. send_unknown(Config) -> Req = ['ASR', {'AVP', [#diameter_avp{code = 999, is_mandatory = false, data = <<17>>}]}], - #diameter_base_ASA{'Result-Code' = ?SUCCESS, - 'AVP' = Avps} + ['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps] = call(Config, Req), - [#diameter_avp{code = 999, - is_mandatory = false, - data = <<17>>}] - = Avps. + {'AVP', [#diameter_avp{code = 999, + is_mandatory = false, + data = <<17>>}]} + = lists:last(Avps). %% Ditto but set the M flag. send_unknown_mandatory(Config) -> Req = ['ASR', {'AVP', [#diameter_avp{code = 999, is_mandatory = true, data = <<17>>}]}], - #diameter_base_ASA{'Result-Code' = ?AVP_UNSUPPORTED, - 'Failed-AVP' = Failed} + ['ASA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | Avps] = call(Config, Req), - [#'diameter_base_Failed-AVP'{'AVP' = Avps}] = Failed, + [#'diameter_base_Failed-AVP'{'AVP' = As}] + = proplists:get_value('Failed-AVP', Avps), [#diameter_avp{code = 999, is_mandatory = true, data = <<17>>}] - = Avps. + = As. %% Send an STR that the server ignores. send_noreply(Config) -> @@ -364,32 +460,55 @@ send_noreply(Config) -> %% Send an unsupported command and expect 3001. send_unsupported(Config) -> Req = ['STR', {'Termination-Cause', ?BAD_ANSWER}], - #'diameter_base_answer-message'{'Result-Code' = ?COMMAND_UNSUPPORTED} + ?answer_message(?COMMAND_UNSUPPORTED) = call(Config, Req). %% Send an unsupported application and expect 3007. send_unsupported_app(Config) -> Req = ['STR', {'Termination-Cause', ?BAD_ANSWER}], - #'diameter_base_answer-message'{'Result-Code' = ?APPLICATION_UNSUPPORTED} + ?answer_message(?APPLICATION_UNSUPPORTED) = call(Config, Req). %% Send a request with the E bit set and expect 3008. send_error_bit(Config) -> Req = ['STR', {'Termination-Cause', ?BAD_ANSWER}], - #'diameter_base_answer-message'{'Result-Code' = ?INVALID_HDR_BITS} + ?answer_message(?INVALID_HDR_BITS) = call(Config, Req). %% Send a bad version and check that we get 5011. send_unsupported_version(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}], - #diameter_base_STA{'Result-Code' = ?UNSUPPORTED_VERSION} + ['STA', _SessionId, {'Result-Code', ?UNSUPPORTED_VERSION} | _] + = call(Config, Req). + +%% Send a request containing an incorrect AVP length. +send_invalid_avp_bits(Config) -> + Req = ['STR', {'Termination-Cause', ?LOGOUT}], + + ?answer_message(?INVALID_AVP_BITS) + = call(Config, Req). + +%% Send a request containing an AVP length that doesn't match the +%% AVP's type. +send_invalid_avp_length(Config) -> + Req = ['STR', {'Termination-Cause', ?LOGOUT}], + + ['STA', _SessionId, {'Result-Code', ?INVALID_AVP_LENGTH} | _] + = call(Config, Req). + +%% Send a request containing 5xxx errors that the server rejects with +%% 3xxx. +send_invalid_reject(Config) -> + Req = ['STR', {'Termination-Cause', ?USER_MOVED}], + + ?answer_message(?TOO_BUSY) = call(Config, Req). %% Send something long that will be fragmented by TCP. send_long(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}, {'User-Name', [lists:duplicate(1 bsl 20, $X)]}], - #diameter_base_STA{'Result-Code' = ?SUCCESS} + ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] = call(Config, Req). %% Send something for which pick_peer finds no suitable peer. @@ -414,14 +533,14 @@ send_any_1(Config) -> send_any_2(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}, {'Destination-Host', [?HOST(?SERVER, "unknown.org")]}], - #'diameter_base_answer-message'{'Result-Code' = ?UNABLE_TO_DELIVER} + ?answer_message(?UNABLE_TO_DELIVER) = call(Config, Req, [{filter, {any, [host, realm]}}]). %% Send with a conjunctive filter. send_all_1(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}], Realm = lists:foldr(fun(C,A) -> [C,A] end, [], ?REALM), - #diameter_base_STA{'Result-Code' = ?SUCCESS} + ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] = call(Config, Req, [{filter, {all, [{host, any}, {realm, Realm}]}}]). send_all_2(Config) -> @@ -439,8 +558,7 @@ send_timeout(Config) -> %% received the Session-Id. send_error(Config) -> Req = ['RAR', {'Re-Auth-Request-Type', ?AUTHORIZE_AUTHENTICATE}], - #'diameter_base_answer-message'{'Result-Code' = ?TOO_BUSY, - 'Session-Id' = SId} + ?answer_message(SId, ?TOO_BUSY) = call(Config, Req), undefined /= SId. @@ -450,10 +568,9 @@ send_detach(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}], Ref = make_ref(), ok = call(Config, Req, [{extra, [{self(), Ref}]}, detach]), - #diameter_packet{msg = Rec, errors = []} - = receive {Ref, T} -> T after 2000 -> false end, - #diameter_base_STA{'Result-Code' = ?SUCCESS} - = Rec. + Ans = receive {Ref, T} -> T after 2000 -> false end, + ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] + = Ans. %% Send a request which can't be encoded and expect {error, encode}. send_encode_error(Config) -> @@ -463,11 +580,11 @@ send_encode_error(Config) -> send_destination_1(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}, {'Destination-Host', [?HOST(?SERVER, ?REALM)]}], - #diameter_base_STA{'Result-Code' = ?SUCCESS} + ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] = call(Config, Req, [{filter, {all, [host, realm]}}]). send_destination_2(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}], - #diameter_base_STA{'Result-Code' = ?SUCCESS} + ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] = call(Config, Req, [{filter, {all, [host, realm]}}]). %% Send with filtering on and expect failure when specifying an @@ -488,12 +605,12 @@ send_destination_4(Config) -> send_destination_5(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}, {'Destination-Realm', "unknown.org"}], - #'diameter_base_answer-message'{'Result-Code' = ?REALM_NOT_SERVED} + ?answer_message(?REALM_NOT_SERVED) = call(Config, Req). send_destination_6(Config) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}, {'Destination-Host', [?HOST(?SERVER, "unknown.org")]}], - #'diameter_base_answer-message'{'Result-Code' = ?UNABLE_TO_DELIVER} + ?answer_message(?UNABLE_TO_DELIVER) = call(Config, Req). %% Specify an invalid option and expect failure. @@ -527,7 +644,7 @@ send_bad_filter(Config, F) -> %% Specify multiple filter options and expect them be conjunctive. send_multiple_filters_1(Config) -> Fun = fun(#diameter_caps{}) -> true end, - #diameter_base_STA{'Result-Code' = ?SUCCESS} + ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] = send_multiple_filters(Config, [host, {eval, Fun}]). send_multiple_filters_2(Config) -> E = {erlang, is_tuple, []}, @@ -538,7 +655,7 @@ send_multiple_filters_3(Config) -> E2 = {erlang, is_tuple, []}, E3 = {erlang, is_record, [diameter_caps]}, E4 = [{erlang, is_record, []}, diameter_caps], - #diameter_base_STA{'Result-Code' = ?SUCCESS} + ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] = send_multiple_filters(Config, [{eval, E} || E <- [E1,E2,E3,E4]]). send_multiple_filters(Config, Fs) -> @@ -549,7 +666,7 @@ send_multiple_filters(Config, Fs) -> %% only the return value from the prepare_request callback being %% significant. send_anything(Config) -> - #diameter_base_STA{'Result-Code' = ?SUCCESS} + ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] = call(Config, anything). %% =========================================================================== @@ -559,33 +676,69 @@ call(Config, Req) -> call(Config, Req, Opts) -> Name = proplists:get_value(testcase, Config), - [Encoding, Client] = ?util:name(proplists:get_value(group, Config)), + #group{client_encoding = ReqEncoding, + client_dict0 = Dict0} + = Group + = proplists:get_value(group, Config), diameter:call(?CLIENT, - dict(Req), - req(Req, Encoding), - [{extra, [Name, Client]} | Opts]). - -req(['ACR' = H | T], record) -> - ?ACCT:'#new-'(?ACCT:msg2rec(H), T); -req([H|T], record) -> - ?BASE:'#new-'(?BASE:msg2rec(H), T); -req(T, _) -> - T. - -dict(['ACR' | _]) -> - ?ACCT; -dict(_) -> - ?BASE. + dict(Req, Dict0), + msg(Req, ReqEncoding, Dict0), + [{extra, [Name, Group]} | Opts]). + +origin({A,C}) -> + 2*codec(A) + container(C); + +origin(N) -> + {codec(N band 2), container(N rem 2)}. + +%% Map booleans, but the readable atoms are part of (constructed) +%% group names, so it's good that they're readable. + +codec(record) -> 0; +codec(list) -> 1; +codec(0) -> record; +codec(_) -> list. + +container(pkt) -> 0; +container(msg) -> 1; +container(0) -> pkt; +container(_) -> msg. + +msg([H|_] = Msg, record = E, diameter_gen_base_rfc3588) + when H == 'ACR'; + H == 'ACA' -> + msg(Msg, E, diameter_gen_base_accounting); +msg([H|_] = Msg, record = E, diameter_gen_base_rfc6733) + when H == 'ACR'; + H == 'ACA' -> + msg(Msg, E, diameter_gen_acct_rfc6733); +msg([H|T], record, Dict) -> + Dict:'#new-'(Dict:msg2rec(H), T); +msg(Msg, _, _) -> + Msg. + +dict0(D) -> + ?A("diameter_gen_base_" ++ ?L(D)). + +dict(Msg, Dict0) + when 'ACR' == hd(Msg); + 'ACA' == hd(Msg); + ?is_record(Msg, diameter_base_accounting_ACR); + ?is_record(Msg, diameter_base_accounting_ACA) -> + acct(Dict0); +dict(_, Dict0) -> + Dict0. + +acct(diameter_gen_base_rfc3588) -> + diameter_gen_base_accounting; +acct(diameter_gen_base_rfc6733) -> + diameter_gen_acct_rfc6733. %% Set only values that aren't already. -set([H|T], Vs) -> +set(_, [H|T], Vs) -> [H | Vs ++ T]; -set(#diameter_base_accounting_ACR{} = Rec, Vs) -> - set(Rec, Vs, ?ACCT); -set(Rec, Vs) -> - set(Rec, Vs, ?BASE). - -set(Rec, Vs, Dict) -> +set(#group{client_dict0 = Dict0} = _Group, Rec, Vs) -> + Dict = dict(Rec, Dict0), lists:foldl(fun({F,_} = FV, A) -> set(Dict, Dict:'#get-'(F, A), FV, A) end, @@ -614,17 +767,18 @@ peer_down(_SvcName, _Peer, State) -> %% pick_peer/6-7 -pick_peer(Peers, _, ?CLIENT, _State, Name, Id) +pick_peer(Peers, _, ?CLIENT, _State, Name, Group) when Name /= send_detach -> - find(Id, Peers). + find(Group, Peers). pick_peer(_Peers, _, ?CLIENT, _State, send_nopeer, _, ?EXTRA) -> false; -pick_peer(Peers, _, ?CLIENT, _State, send_detach, Id, {_,_}) -> - find(Id, Peers). +pick_peer(Peers, _, ?CLIENT, _State, send_detach, Group, {_,_}) -> + find(Group, Peers). -find(Id, Peers) -> +find(#group{server_encoding = A, server_container = C}, Peers) -> + Id = {A,C}, [P] = [P || P <- Peers, id(Id, P)], {ok, P}. @@ -638,115 +792,174 @@ id(Id, {Pid, _Caps}) -> prepare_request(_Pkt, ?CLIENT, {_Ref, _Caps}, send_discard, _) -> {discard, unprepared}; -prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, Name, _) -> - {send, prepare(Pkt, Caps, Name)}. +prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, Name, Group) -> + {send, prepare(Pkt, Caps, Name, Group)}. -prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, send_detach, _, _) -> - {eval_packet, {send, prepare(Pkt, Caps)}, [fun log/2, detach]}. +prepare_request(Pkt, ?CLIENT, {_Ref, Caps}, send_detach, Group, _) -> + {eval_packet, {send, prepare(Pkt, Caps, Group)}, [fun log/2, detach]}. -log(#diameter_packet{} = P, T) -> +log(#diameter_packet{bin = Bin} = P, T) + when is_binary(Bin) -> io:format("~p: ~p~n", [T,P]). -prepare(Pkt, Caps, send_unsupported) -> - Req = prepare(Pkt, Caps), +%% prepare/4 + +prepare(Pkt, Caps, send_invalid_avp_bits, #group{client_dict0 = Dict0} + = Group) -> + Req = prepare(Pkt, Caps, Group), + %% Last AVP in our STR is Termination-Cause of type Unsigned32: + %% set its length improperly. + #diameter_packet{header = #diameter_header{length = L}, + bin = B} + = E + = diameter_codec:encode(Dict0, Pkt#diameter_packet{msg = Req}), + Offset = L - 7, %% to AVP Length + <<H:Offset/binary, 12:24/integer, T:4/binary>> = B, + E#diameter_packet{bin = <<H/binary, 13:24/integer, T/binary>>}; + +prepare(Pkt, Caps, N, #group{client_dict0 = Dict0} = Group) + when N == send_invalid_avp_length; + N == send_invalid_reject -> + Req = prepare(Pkt, Caps, Group), + %% Second last AVP in our STR is Auth-Application-Id of type + %% Unsigned32: Send a value of length 8. + #diameter_packet{header = #diameter_header{length = L}, + bin = B0} + = E + = diameter_codec:encode(Dict0, Pkt#diameter_packet{msg = Req}), + Offset = L - 7 - 12, %% to AVP Length + <<H0:Offset/binary, 12:24/integer, T:16/binary>> = B0, + <<V, L:24/integer, H/binary>> = H0, %% assert + E#diameter_packet{bin = <<V, + (L+4):24/integer, + H/binary, + 16:24/integer, + 0:32/integer, + T/binary>>}; + +prepare(Pkt, Caps, send_unsupported, #group{client_dict0 = Dict0} = Group) -> + Req = prepare(Pkt, Caps, Group), #diameter_packet{bin = <<H:5/binary, _CmdCode:3/binary, T/binary>>} = E - = diameter_codec:encode(?BASE, Pkt#diameter_packet{msg = Req}), + = diameter_codec:encode(Dict0, Pkt#diameter_packet{msg = Req}), E#diameter_packet{bin = <<H/binary, 42:24/integer, T/binary>>}; -prepare(Pkt, Caps, send_unsupported_app) -> - Req = prepare(Pkt, Caps), +prepare(Pkt, Caps, send_unsupported_app, #group{client_dict0 = Dict0} + = Group) -> + Req = prepare(Pkt, Caps, Group), #diameter_packet{bin = <<H:8/binary, _ApplId:4/binary, T/binary>>} = E - = diameter_codec:encode(?BASE, Pkt#diameter_packet{msg = Req}), - E#diameter_packet{bin = <<H/binary, 42:32/integer, T/binary>>}; + = diameter_codec:encode(Dict0, Pkt#diameter_packet{msg = Req}), + E#diameter_packet{bin = <<H/binary, ?BAD_APP:32/integer, T/binary>>}; -prepare(Pkt, Caps, send_error_bit) -> +prepare(Pkt, Caps, send_error_bit, Group) -> #diameter_packet{header = Hdr} = Pkt, Pkt#diameter_packet{header = Hdr#diameter_header{is_error = true}, - msg = prepare(Pkt, Caps)}; + msg = prepare(Pkt, Caps, Group)}; -prepare(Pkt, Caps, send_unsupported_version) -> +prepare(Pkt, Caps, send_unsupported_version, Group) -> #diameter_packet{header = Hdr} = Pkt, Pkt#diameter_packet{header = Hdr#diameter_header{version = 42}, - msg = prepare(Pkt, Caps)}; + msg = prepare(Pkt, Caps, Group)}; -prepare(Pkt, Caps, send_anything) -> +prepare(Pkt, Caps, send_anything, Group) -> Req = ['STR', {'Termination-Cause', ?LOGOUT}], - prepare(Pkt#diameter_packet{msg = Req}, Caps); + prepare(Pkt#diameter_packet{msg = Req}, Caps, Group); + +prepare(Pkt, Caps, _Name, Group) -> + prepare(Pkt, Caps, Group). -prepare(Pkt, Caps, _Name) -> - prepare(Pkt, Caps). +%% prepare/3 -prepare(#diameter_packet{msg = Req}, Caps) - when is_record(Req, diameter_base_accounting_ACR); +prepare(#diameter_packet{msg = Req}, Caps, Group) + when ?is_record(Req, diameter_base_accounting_ACR); 'ACR' == hd(Req) -> #diameter_caps{origin_host = {OH, _}, origin_realm = {OR, DR}} = Caps, - set(Req, [{'Session-Id', diameter:session_id(OH)}, - {'Origin-Host', OH}, - {'Origin-Realm', OR}, - {'Destination-Realm', DR}]); + set(Group, Req, [{'Session-Id', diameter:session_id(OH)}, + {'Origin-Host', OH}, + {'Origin-Realm', OR}, + {'Destination-Realm', DR}]); -prepare(#diameter_packet{msg = Req}, Caps) - when is_record(Req, diameter_base_ASR); +prepare(#diameter_packet{msg = Req}, Caps, Group) + when ?is_record(Req, diameter_base_ASR); 'ASR' == hd(Req) -> #diameter_caps{origin_host = {OH, DH}, origin_realm = {OR, DR}} = Caps, - set(Req, [{'Session-Id', diameter:session_id(OH)}, - {'Origin-Host', OH}, - {'Origin-Realm', OR}, - {'Destination-Host', DH}, - {'Destination-Realm', DR}, - {'Auth-Application-Id', ?APP_ID}]); - -prepare(#diameter_packet{msg = Req}, Caps) - when is_record(Req, diameter_base_STR); + set(Group, Req, [{'Session-Id', diameter:session_id(OH)}, + {'Origin-Host', OH}, + {'Origin-Realm', OR}, + {'Destination-Host', DH}, + {'Destination-Realm', DR}, + {'Auth-Application-Id', ?APP_ID}]); + +prepare(#diameter_packet{msg = Req}, Caps, Group) + when ?is_record(Req, diameter_base_STR); 'STR' == hd(Req) -> #diameter_caps{origin_host = {OH, _}, origin_realm = {OR, DR}} = Caps, - set(Req, [{'Session-Id', diameter:session_id(OH)}, - {'Origin-Host', OH}, - {'Origin-Realm', OR}, - {'Destination-Realm', DR}, - {'Auth-Application-Id', ?APP_ID}]); - -prepare(#diameter_packet{msg = Req}, Caps) - when is_record(Req, diameter_base_RAR); + set(Group, Req, [{'Session-Id', diameter:session_id(OH)}, + {'Origin-Host', OH}, + {'Origin-Realm', OR}, + {'Destination-Realm', DR}, + {'Auth-Application-Id', ?APP_ID}]); + +prepare(#diameter_packet{msg = Req}, Caps, Group) + when ?is_record(Req, diameter_base_RAR); 'RAR' == hd(Req) -> #diameter_caps{origin_host = {OH, DH}, origin_realm = {OR, DR}} = Caps, - set(Req, [{'Session-Id', diameter:session_id(OH)}, - {'Origin-Host', OH}, - {'Origin-Realm', OR}, - {'Destination-Host', DH}, - {'Destination-Realm', DR}, - {'Auth-Application-Id', ?APP_ID}]). + set(Group, Req, [{'Session-Id', diameter:session_id(OH)}, + {'Origin-Host', OH}, + {'Origin-Realm', OR}, + {'Destination-Host', DH}, + {'Destination-Realm', DR}, + {'Auth-Application-Id', ?APP_ID}]). %% prepare_retransmit/5 -prepare_retransmit(_Pkt, false, _Peer, _Name, _Id) -> +prepare_retransmit(_Pkt, false, _Peer, _Name, _Group) -> discard. %% handle_answer/6-7 -handle_answer(Pkt, Req, ?CLIENT, Peer, Name, _Id) -> - answer(Pkt, Req, Peer, Name). - -handle_answer(Pkt, _Req, ?CLIENT, _Peer, send_detach, _Id, {Pid, Ref}) -> - Pid ! {Ref, Pkt}. - -answer(#diameter_packet{msg = Rec, errors = []}, _Req, _Peer, _) -> +handle_answer(Pkt, Req, ?CLIENT, Peer, Name, Group) -> + answer(Pkt, Req, Peer, Name, Group). + +handle_answer(Pkt, Req, ?CLIENT, Peer, send_detach = Name, Group, X) -> + {Pid, Ref} = X, + Pid ! {Ref, answer(Pkt, Req, Peer, Name, Group)}. + +answer(Pkt, Req, _Peer, Name, #group{client_dict0 = Dict0}) -> + #diameter_packet{header = H, msg = Ans, errors = Es} = Pkt, + ApplId = app(Req, Name, Dict0), + #diameter_header{application_id = ApplId} = H, %% assert + Dict = dict(Ans, Dict0), + [R | Vs] = Dict:'#get-'(answer(Ans, Es, Name)), + [Dict:rec2msg(R) | Vs]. + +answer(Rec, [_|_], N) + when N == send_invalid_avp_bits; + N == send_invalid_avp_length; + N == send_invalid_reject -> + Rec; +answer(Rec, [], _) -> Rec. +app(_, send_unsupported_app, _) -> + ?BAD_APP; +app(Req, _, Dict0) -> + Dict = dict(Req, Dict0), + Dict:id(). + %% handle_error/6 -handle_error(Reason, _Req, ?CLIENT, _Peer, _Name, _Id) -> +handle_error(Reason, _Req, ?CLIENT, _Peer, _Name, _Group) -> {error, Reason}. %% handle_request/3 @@ -761,12 +974,25 @@ handle_request(#diameter_packet{header = H, msg = M}, ?SERVER, {_Ref, Caps}) -> {V,B} = ?CLIENT_MASK, V = EI bsr B, %% assert V = HI bsr B, %% - request(M, Caps). + #diameter_caps{origin_state_id = {_,[Id]}} = Caps, + answer(origin(Id), request(M, Caps)). + +answer(T, {Tag, Action, Post}) -> + {Tag, answer(T, Action), Post}; +answer({A,C}, {reply, Ans}) -> + answer(C, {reply, msg(Ans, A, diameter_gen_base_rfc3588)}); +answer(pkt, {reply, Ans}) + when not is_record(Ans, diameter_packet) -> + {reply, #diameter_packet{msg = Ans}}; +answer(_, T) -> + T. +%% send_nok request(#diameter_base_accounting_ACR{'Accounting-Record-Number' = 0}, _) -> {eval_packet, {protocol_error, ?INVALID_AVP_BITS}, [fun log/2, invalid]}; +%% send_bad_answer request(#diameter_base_accounting_ACR{'Session-Id' = SId, 'Accounting-Record-Type' = RT, 'Accounting-Record-Number' = 2 = RN}, @@ -779,12 +1005,27 @@ request(#diameter_base_accounting_ACR{'Session-Id' = SId, {'Accounting-Record-Type', RT}, {'Accounting-Record-Number', RN}], - {reply, #diameter_packet{header = #diameter_header{is_error = true},%% not + {reply, #diameter_packet{header = #diameter_header{is_error = true},%% NOT msg = Ans}}; +%% send_eval request(#diameter_base_accounting_ACR{'Session-Id' = SId, 'Accounting-Record-Type' = RT, - 'Accounting-Record-Number' = RN}, + 'Accounting-Record-Number' = 3 = RN}, + #diameter_caps{origin_host = {OH, _}, + origin_realm = {OR, _}}) -> + Ans = ['ACA', {'Result-Code', ?SUCCESS}, + {'Session-Id', SId}, + {'Origin-Host', OH}, + {'Origin-Realm', OR}, + {'Accounting-Record-Type', RT}, + {'Accounting-Record-Number', RN}], + {eval, {reply, Ans}, {erlang, now, []}}; + +%% send_ok +request(#diameter_base_accounting_ACR{'Session-Id' = SId, + 'Accounting-Record-Type' = RT, + 'Accounting-Record-Number' = 1 = RN}, #diameter_caps{origin_host = {OH, _}, origin_realm = {OR, _}}) -> {reply, ['ACA', {'Result-Code', ?SUCCESS}, @@ -794,27 +1035,44 @@ request(#diameter_base_accounting_ACR{'Session-Id' = SId, {'Accounting-Record-Type', RT}, {'Accounting-Record-Number', RN}]}; +%% send_protocol_error +request(#diameter_base_accounting_ACR{'Accounting-Record-Number' = 4}, + #diameter_caps{origin_host = {OH, _}, + origin_realm = {OR, _}}) -> + Ans = ['answer-message', {'Result-Code', ?TOO_BUSY}, + {'Origin-Host', OH}, + {'Origin-Realm', OR}], + {reply, Ans}; + request(#diameter_base_ASR{'Session-Id' = SId, 'AVP' = Avps}, #diameter_caps{origin_host = {OH, _}, origin_realm = {OR, _}}) -> - {reply, #diameter_base_ASA{'Result-Code' = ?SUCCESS, - 'Session-Id' = SId, - 'Origin-Host' = OH, - 'Origin-Realm' = OR, - 'AVP' = Avps}}; + {reply, ['ASA', {'Result-Code', ?SUCCESS}, + {'Session-Id', SId}, + {'Origin-Host', OH}, + {'Origin-Realm', OR}, + {'AVP', Avps}]}; + +%% send_invalid_reject +request(#diameter_base_STR{'Termination-Cause' = ?USER_MOVED}, + _Caps) -> + {protocol_error, ?TOO_BUSY}; +%% send_noreply request(#diameter_base_STR{'Termination-Cause' = T}, _Caps) when T /= ?LOGOUT -> discard; -request(#diameter_base_STR{'Destination-Realm'= R}, +%% send_destination_5 +request(#diameter_base_STR{'Destination-Realm' = R}, #diameter_caps{origin_realm = {OR, _}}) when R /= undefined, R /= OR -> {protocol_error, ?REALM_NOT_SERVED}; -request(#diameter_base_STR{'Destination-Host'= [H]}, +%% send_destination_6 +request(#diameter_base_STR{'Destination-Host' = [H]}, #diameter_caps{origin_host = {OH, _}}) when H /= OH -> {protocol_error, ?UNABLE_TO_DELIVER}; @@ -822,11 +1080,12 @@ request(#diameter_base_STR{'Destination-Host'= [H]}, request(#diameter_base_STR{'Session-Id' = SId}, #diameter_caps{origin_host = {OH, _}, origin_realm = {OR, _}}) -> - {reply, #diameter_base_STA{'Result-Code' = ?SUCCESS, - 'Session-Id' = SId, - 'Origin-Host' = OH, - 'Origin-Realm' = OR}}; + {reply, ['STA', {'Result-Code', ?SUCCESS}, + {'Session-Id', SId}, + {'Origin-Host', OH}, + {'Origin-Realm', OR}]}; +%% send_error request(#diameter_base_RAR{}, _Caps) -> receive after 2000 -> ok end, {protocol_error, ?TOO_BUSY}. diff --git a/lib/diameter/test/diameter_watchdog_SUITE.erl b/lib/diameter/test/diameter_watchdog_SUITE.erl index 7ce09e93ca..e1e166b834 100644 --- a/lib/diameter/test/diameter_watchdog_SUITE.erl +++ b/lib/diameter/test/diameter_watchdog_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2012. All Rights Reserved. +%% Copyright Ericsson AB 2010-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 @@ -127,13 +127,7 @@ end_per_suite(_Config) -> %% =========================================================================== %% Test the watchdog state machine for the required failover, failback -%% and reopen behaviour. Do this by having the testcase replace -%% diameter_service and start watchdogs, and having this module -%% implement a transport process that plays the role of the peer -%% Diameter node. - -%reopen(_) -> -% reopen(connect, ?WD(10000), 1, 'DWR'); +%% and reopen behaviour by examining watchdog events. reopen(_) -> [] = run([[reopen, T, Wd, N, M] @@ -349,7 +343,7 @@ recv_reopen(listen, Ref, _, _) -> reg(Type, Ref, SvcName, T) -> TPid = tpid(Type, Ref, diameter:service_info(SvcName, transport)), true = diameter_reg:add_new({?MODULE, TPid, T}). - + %% tpid/3 tpid(connect, Ref, [[{ref, Ref}, @@ -375,7 +369,7 @@ tpid(listen, Ref, [[{ref, Ref}, {port, [{owner, TPid} | _]} | _]] = lists:filter(fun([{watchdog, {_,_,S}} | _]) -> - S == okay orelse S == reopen + S == okay orelse S == reopen end, As), TPid. @@ -432,7 +426,7 @@ send({SvcName, {_,_,_} = T}, Sock, Bin) -> putr(origin, [OH, OR]), putr(config, T), send(Sock, Bin); - + %% Discard DWA, failback after another timeout in the peer. send({Wd, 0 = No, Msg}, Sock, Bin) -> Origin = getr(origin), diff --git a/lib/diameter/test/modules.mk b/lib/diameter/test/modules.mk index 5898e125ae..c4a713fb10 100644 --- a/lib/diameter/test/modules.mk +++ b/lib/diameter/test/modules.mk @@ -2,7 +2,7 @@ # %CopyrightBegin% # -# Copyright Ericsson AB 2010-2012. All Rights Reserved. +# Copyright Ericsson AB 2010-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 @@ -22,25 +22,28 @@ COVER_SPEC_FILE = diameter.cover MODULES = \ diameter_ct \ - diameter_util \ diameter_enum \ - diameter_compiler_SUITE \ + diameter_util \ + diameter_3xxx_SUITE \ + diameter_app_SUITE \ + diameter_capx_SUITE \ diameter_codec_SUITE \ diameter_codec_test \ - diameter_app_SUITE \ + diameter_compiler_SUITE \ diameter_dict_SUITE \ - diameter_reg_SUITE \ - diameter_sync_SUITE \ - diameter_stats_SUITE \ - diameter_watchdog_SUITE \ + diameter_dpr_SUITE \ + diameter_event_SUITE \ + diameter_failover_SUITE \ diameter_gen_sctp_SUITE \ - diameter_transport_SUITE \ - diameter_capx_SUITE \ - diameter_traffic_SUITE \ + diameter_length_SUITE \ + diameter_reg_SUITE \ diameter_relay_SUITE \ + diameter_stats_SUITE \ + diameter_sync_SUITE \ diameter_tls_SUITE \ - diameter_failover_SUITE \ - diameter_dpr_SUITE + diameter_traffic_SUITE \ + diameter_transport_SUITE \ + diameter_watchdog_SUITE HRL_FILES = \ diameter_ct.hrl diff --git a/lib/diameter/test/testspec b/lib/diameter/test/testspec new file mode 100644 index 0000000000..2fd8307281 --- /dev/null +++ b/lib/diameter/test/testspec @@ -0,0 +1,3 @@ + +{suites, ".", all}. +{cover, "./coverspec"}. |