aboutsummaryrefslogtreecommitdiffstats
path: root/lib/diameter
diff options
context:
space:
mode:
Diffstat (limited to 'lib/diameter')
-rw-r--r--lib/diameter/doc/src/diameter.xml3
-rw-r--r--lib/diameter/doc/src/notes.xml41
-rw-r--r--lib/diameter/include/diameter_gen.hrl2
-rw-r--r--lib/diameter/src/base/diameter_codec.erl10
-rw-r--r--lib/diameter/src/base/diameter_traffic.erl176
-rw-r--r--lib/diameter/src/diameter.appup.src78
-rw-r--r--lib/diameter/test/diameter_3xxx_SUITE.erl200
-rw-r--r--lib/diameter/test/diameter_app_SUITE.erl51
-rw-r--r--lib/diameter/test/diameter_traffic_SUITE.erl113
-rw-r--r--lib/diameter/vsn.mk2
10 files changed, 492 insertions, 184 deletions
diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml
index 6e41b01c44..ea175a58b8 100644
--- a/lib/diameter/doc/src/diameter.xml
+++ b/lib/diameter/doc/src/diameter.xml
@@ -1820,7 +1820,8 @@ The information presented here is as in the <c>connect</c> case except
that the client connections are grouped under an <c>accept</c> tuple.</p>
<p>
-Whether or not the &transport_opt; <c>pool_size</c> affects the format
+Whether or not the &transport_opt; <c>pool_size</c> has been
+configured affects the format
of the listing in the case of a connecting transport, since a value
greater than 1 implies multiple transport processes for the same
<c>&transport_ref;</c>, as in the listening case.
diff --git a/lib/diameter/doc/src/notes.xml b/lib/diameter/doc/src/notes.xml
index 479fab21b2..6931788c83 100644
--- a/lib/diameter/doc/src/notes.xml
+++ b/lib/diameter/doc/src/notes.xml
@@ -42,6 +42,47 @@ first.</p>
<!-- ===================================================================== -->
+<section><title>diameter 1.9.1</title>
+
+ <section><title>Known Bugs and Problems</title>
+ <list>
+ <item>
+ <p>
+ Don't leave extra bit in decoded AVP data.</p>
+ <p>
+ OTP-12074 in OTP 17.3 missed one case: a length error on
+ a trailing AVP unknown to the dictionary in question.</p>
+ <p>
+ Own Id: OTP-12642</p>
+ </item>
+ <item>
+ <p>
+ Don't confuse Result-Code and Experimental-Result</p>
+ <p>
+ The errors field of a decoded diameter_packet record was
+ populated with a Result-Code AVP when an
+ Experimental-Result containing a 3xxx Result-Code was
+ received in an answer not setting the E-bit. The correct
+ AVP is now extracted from the incoming message.</p>
+ <p>
+ Own Id: OTP-12654 Aux Id: seq12851 </p>
+ </item>
+ <item>
+ <p>
+ Don't count on unknown Application Id.</p>
+ <p>
+ OTP-11721 in OTP 17.1 missed the case of an Application
+ Id not agreeing with that of the dictionary in question,
+ causing counters to be accumulated on keys containing the
+ unknown id.</p>
+ <p>
+ Own Id: OTP-12701</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>diameter 1.9</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl
index 0eef218a07..e8ffe7f92c 100644
--- a/lib/diameter/include/diameter_gen.hrl
+++ b/lib/diameter/include/diameter_gen.hrl
@@ -445,7 +445,7 @@ reset(_, _) ->
%% undecoded. Note that the type field is 'undefined' in this case.
decode_AVP(Name, Avp, {Avps, Acc}) ->
- {[Avp | Avps], pack_AVP(Name, Avp, Acc)}.
+ {[trim(Avp) | Avps], pack_AVP(Name, Avp, Acc)}.
%% rc/1
diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl
index 15a4c5e86f..bf2fe8e7ca 100644
--- a/lib/diameter/src/base/diameter_codec.erl
+++ b/lib/diameter/src/base/diameter_codec.erl
@@ -640,8 +640,12 @@ split_data(Bin, Len) ->
%% payload if this is a request. Do this (in cases that we
%% know the type) by inducing a decode failure and letting
%% the dictionary's decode (in diameter_gen) deal with it.
- %% Here we don't know type. If the type isn't known, then
- %% the decode just strips the extra bit.
+ %%
+ %% Note that the extra bit can only occur in the trailing
+ %% AVP of a message or Grouped AVP, since a faulty AVP
+ %% Length is otherwise indistinguishable from a correct
+ %% one here, since we don't know the types of the AVPs
+ %% being extracted.
{<<0:1, Bin/binary>>, <<>>}
end.
@@ -690,8 +694,8 @@ pack_avp(#diameter_avp{code = undefined, data = B})
Len = size(<<H:5/binary, _:24, T/binary>> = <<B/binary, 0:Pad>>),
<<H/binary, Len:24, T/binary>>;
-%% ... from a dictionary compiled against old code in diameter_gen ...
%% ... when ignoring errors in Failed-AVP ...
+%% ... during a relay encode ...
pack_avp(#diameter_avp{data = <<0:1, B/binary>>} = A) ->
pack_avp(A#diameter_avp{data = B});
diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl
index 538ebeeeba..ffd2c0afa2 100644
--- a/lib/diameter/src/base/diameter_traffic.erl
+++ b/lib/diameter/src/base/diameter_traffic.erl
@@ -980,8 +980,8 @@ answer_message(OH, OR, RC, Dict0, #diameter_packet{avps = Avps,
session_id(Code, Vid, Dict0, Avps)
when is_list(Avps) ->
try
- {value, #diameter_avp{data = D}} = find_avp(Code, Vid, Avps),
- [{'Session-Id', [Dict0:avp(decode, D, 'Session-Id')]}]
+ #diameter_avp{data = Bin} = find_avp(Code, Vid, Avps),
+ [{'Session-Id', [Dict0:avp(decode, Bin, 'Session-Id')]}]
catch
error: _ ->
[]
@@ -998,26 +998,17 @@ failed_avp(_, [] = No) ->
%% find_avp/3
-find_avp(Code, Vid, Avps)
- when is_integer(Code), (undefined == Vid orelse is_integer(Vid)) ->
- find(fun(A) -> is_avp(Code, Vid, A) end, Avps).
+%% Grouped ...
+find_avp(Code, VId, [[#diameter_avp{code = Code, vendor_id = VId} | _] = As
+ | _]) ->
+ As;
-%% The final argument here could be a list of AVP's, depending on the case,
-%% but we're only searching at the top level.
-is_avp(Code, Vid, #diameter_avp{code = Code, vendor_id = Vid}) ->
- true;
-is_avp(_, _, _) ->
- false.
+%% ... or not.
+find_avp(Code, VId, [#diameter_avp{code = Code, vendor_id = VId} = A | _]) ->
+ A;
-find(_, []) ->
- false;
-find(Pred, [H|T]) ->
- case Pred(H) of
- true ->
- {value, H};
- false ->
- find(Pred, T)
- end.
+find_avp(Code, VId, [_ | Avps]) ->
+ find_avp(Code, VId, Avps).
%% 7. Error Handling
%%
@@ -1086,7 +1077,6 @@ incr_result(_, #diameter_packet{msg = undefined = No}, _, _) ->
incr_result(Dir, Pkt, TPid, {Dict, AppDict, Dict0}) ->
#diameter_packet{header = #diameter_header{is_error = E}
= Hdr,
- msg = Msg,
errors = Es}
= Pkt,
@@ -1096,13 +1086,13 @@ incr_result(Dir, Pkt, TPid, {Dict, AppDict, Dict0}) ->
recv /= Dir orelse [] == Es orelse incr_error(Dir, Id, TPid, AppDict),
%% Exit on a missing result code.
- T = rc_counter(Dict, Msg),
+ T = rc_counter(Dict, Dir, Pkt),
T == false andalso ?LOGX(no_result_code, {Dict, Dir, Hdr}),
- {Ctr, RC} = T,
+ {Ctr, RC, Avp} = T,
%% Or on an inappropriate value.
is_result(RC, E, Dict0)
- orelse ?LOGX(invalid_error_bit, {Dict, Dir, Hdr, RC}),
+ orelse ?LOGX(invalid_error_bit, {Dict, Dir, Hdr, Avp}),
incr(TPid, {Id, Dir, Ctr}),
Ctr.
@@ -1116,19 +1106,15 @@ msg_id(#diameter_packet{header = H}, Dict) ->
%% there are 2^32 (application ids) * 2^24 (command codes) = 2^56
%% pairs for an attacker to choose from.
msg_id(Hdr, Dict) ->
- {_ApplId, Code, R} = Id = diameter_codec:msg_id(Hdr),
- case Dict:msg_name(Code, 0 == R) of
- '' ->
- unknown(Dict:id(), R);
- _ ->
- Id
+ {Aid, Code, R} = Id = diameter_codec:msg_id(Hdr),
+ if Aid == ?APP_ID_RELAY ->
+ {relay, R};
+ true ->
+ choose(Aid /= Dict:id() orelse '' == Dict:msg_name(Code, 0 == R),
+ unknown,
+ Id)
end.
-unknown(?APP_ID_RELAY, R) ->
- {relay, R};
-unknown(_, _) ->
- unknown.
-
%% No E-bit: can't be 3xxx.
is_result(RC, false, _Dict0) ->
RC < 3000 orelse 4000 =< RC;
@@ -1148,7 +1134,7 @@ is_result(RC, true, _) ->
incr(TPid, Counter) ->
diameter_stats:incr(Counter, TPid, 1).
-%% rc_counter/2
+%% rc_counter/3
%% RFC 3588, 7.6:
%%
@@ -1156,39 +1142,45 @@ incr(TPid, Counter) ->
%% applications MUST include either one Result-Code AVP or one
%% Experimental-Result AVP.
-rc_counter(Dict, Msg) ->
- rcc(Dict, Msg, int(get_avp_value(Dict, 'Result-Code', Msg))).
+rc_counter(Dict, recv, #diameter_packet{header = H, avps = As}) ->
+ rc_counter(Dict, [H|As]);
-rcc(Dict, Msg, undefined) ->
- rcc(get_avp_value(Dict, 'Experimental-Result', Msg));
+rc_counter(Dict, _, #diameter_packet{msg = Msg}) ->
+ rc_counter(Dict, Msg).
-rcc(_, _, N)
+rc_counter(Dict, Msg) ->
+ rcc(get_result(Dict, Msg)).
+
+rcc(#diameter_avp{name = 'Result-Code' = Name, value = N} = A)
when is_integer(N) ->
- {{'Result-Code', N}, N}.
+ {{Name, N}, N, A};
-%% Outgoing answers may be in any of the forms messages can be sent
-%% in. Incoming messages will be records. We're assuming here that the
-%% arity of the result code AVP's is 0 or 1.
+rcc(#diameter_avp{name = 'Result-Code' = Name, value = [N|_]} = A)
+ when is_integer(N) ->
+ {{Name, N}, N, A};
-rcc([{_,_,N} = T | _])
+rcc(#diameter_avp{name = 'Experimental-Result', value = {_,_,N} = T} = A)
when is_integer(N) ->
- {T,N};
-rcc({_,_,N} = T)
+ {T, N, A};
+
+rcc(#diameter_avp{name = 'Experimental-Result', value = [{_,_,N} = T|_]} = A)
when is_integer(N) ->
- {T,N};
+ {T, N, A};
+
rcc(_) ->
false.
-%% Extract the first good looking integer. There's no guarantee
-%% that what we're looking for has arity 1.
-int([N|_])
- when is_integer(N) ->
- N;
-int(N)
- when is_integer(N) ->
- N;
-int(_) ->
- undefined.
+%% get_result/2
+
+get_result(Dict, Msg) ->
+ try
+ [throw(A) || N <- ['Result-Code', 'Experimental-Result'],
+ #diameter_avp{} = A <- [get_avp(Dict, N, Msg)]]
+ of
+ [] -> false
+ catch
+ #diameter_avp{} = A -> A
+ end.
x(T) ->
exit(T).
@@ -1528,10 +1520,10 @@ handle_A(Pkt, SvcName, Dict, Dict0, App, #request{transport = TPid} = Req) ->
%% a missing AVP. If both are optional in the dictionary
%% then this isn't a decode error: just continue on.
answer(Pkt, SvcName, App, Req);
- exit: {invalid_error_bit, {_, _, _, RC}} ->
+ exit: {invalid_error_bit, {_, _, _, Avp}} ->
#diameter_packet{errors = Es}
= Pkt,
- E = {5004, #diameter_avp{name = 'Result-Code', value = RC}},
+ E = {5004, Avp},
answer(Pkt#diameter_packet{errors = [E|Es]}, SvcName, App, Req)
end.
@@ -1868,7 +1860,7 @@ str([]) ->
str(T) ->
T.
-%% get_avp_value/3
+%% get_avp/3
%%
%% Find an AVP in a message of one of three forms:
%%
@@ -1885,47 +1877,71 @@ str(T) ->
%% look for are in the common dictionary. This is required since the
%% relay dictionary doesn't inherit the common dictionary (which maybe
%% it should).
-get_avp_value(?RELAY, Name, Msg) ->
- get_avp_value(?BASE, Name, Msg);
+get_avp(?RELAY, Name, Msg) ->
+ get_avp(?BASE, Name, Msg);
-%% Message sent as a header/avps list, probably a relay case but not
-%% necessarily.
-get_avp_value(Dict, Name, [#diameter_header{} | Avps]) ->
+%% Message as a header/avps list.
+get_avp(Dict, Name, [#diameter_header{} | Avps]) ->
try
{Code, _, VId} = Dict:avp_header(Name),
- [A|_] = lists:dropwhile(fun(#diameter_avp{code = C, vendor_id = V}) ->
- C /= Code orelse V /= VId
- end,
- Avps),
- avp_decode(Dict, Name, A)
+ find_avp(Code, VId, Avps)
+ of
+ A ->
+ avp_decode(Dict, Name, ungroup(A))
catch
error: _ ->
undefined
end;
%% Outgoing message as a name/values list.
-get_avp_value(_, Name, [_MsgName | Avps]) ->
+get_avp(_, Name, [_MsgName | Avps]) ->
case lists:keyfind(Name, 1, Avps) of
{_, V} ->
- V;
+ #diameter_avp{name = Name, value = V};
_ ->
undefined
end;
%% Message is typically a record but not necessarily.
-get_avp_value(Dict, Name, Rec) ->
+get_avp(Dict, Name, Rec) ->
try
- Dict:'#get-'(Name, Rec)
+ #diameter_avp{name = Name, value = Dict:'#get-'(Name, Rec)}
catch
error:_ ->
undefined
end.
+%% get_avp_value/3
+
+get_avp_value(Dict, Name, Msg) ->
+ case get_avp(Dict, Name, Msg) of
+ #diameter_avp{value = V} ->
+ V;
+ undefined = No ->
+ No
+ end.
+
+%% ungroup/1
+
+ungroup([Avp|_]) ->
+ Avp;
+ungroup(Avp) ->
+ Avp.
+
+%% avp_decode/3
+
avp_decode(Dict, Name, #diameter_avp{value = undefined,
- data = Bin}) ->
- Dict:avp(decode, Bin, Name);
-avp_decode(_, _, #diameter_avp{value = V}) ->
- V.
+ data = Bin}
+ = Avp) ->
+ try Dict:avp(decode, Bin, Name) of
+ V ->
+ Avp#diameter_avp{value = V}
+ catch
+ error:_ ->
+ Avp
+ end;
+avp_decode(_, _, #diameter_avp{} = Avp) ->
+ Avp.
cb(#diameter_app{module = [_|_] = M}, F, A) ->
eval(M, F, A);
diff --git a/lib/diameter/src/diameter.appup.src b/lib/diameter/src/diameter.appup.src
index a54eb24031..0ef0fd35a9 100644
--- a/lib/diameter/src/diameter.appup.src
+++ b/lib/diameter/src/diameter.appup.src
@@ -35,32 +35,10 @@
{"1.4.3", [{restart_application, diameter}]}, %% R16B02
{"1.4.4", [{restart_application, diameter}]},
{"1.5", [{restart_application, diameter}]}, %% R16B03
- {"1.6", [{load_module, diameter_lib}, %% 17.0
- {load_module, diameter_traffic},
- {load_module, diameter_watchdog},
- {load_module, diameter_peer_fsm},
- {load_module, diameter_service},
- {load_module, diameter_gen_base_rfc6733},
- {load_module, diameter_gen_acct_rfc6733},
- {load_module, diameter_gen_base_rfc3588},
- {load_module, diameter_gen_base_accounting},
- {load_module, diameter_gen_relay},
- {load_module, diameter_codec},
- {load_module, diameter_sctp}]},
- {"1.7", [{load_module, diameter_service}, %% 17.1
- {load_module, diameter_codec},
- {load_module, diameter_gen_base_rfc6733},
- {load_module, diameter_gen_acct_rfc6733},
- {load_module, diameter_gen_base_rfc3588},
- {load_module, diameter_gen_base_accounting},
- {load_module, diameter_gen_relay},
- {load_module, diameter_traffic},
- {load_module, diameter_peer_fsm}]},
- {"1.7.1", [{load_module, diameter_traffic}, %% 17.3
- {load_module, diameter_watchdog},
- {load_module, diameter_peer_fsm},
- {load_module, diameter_service}]},
- {"1.8", [{load_module, diameter_lib}, %% 17.4
+ {"1.6", [{restart_application, diameter}]}, %% 17.0
+ {"1.7", [{restart_application, diameter}]}, %% 17.[12]
+ {<<"^1\\.(7\\.1|8)$">>, %% 17.[34]
+ [{load_module, diameter_lib},
{load_module, diameter_peer},
{load_module, diameter_reg},
{load_module, diameter_session},
@@ -84,7 +62,14 @@
{load_module, diameter_gen_relay},
{update, diameter_transport_sup, supervisor},
{update, diameter_service_sup, supervisor},
- {update, diameter_sup, supervisor}]}
+ {update, diameter_sup, supervisor}]},
+ {"1.9", [{load_module, diameter_codec}, %% 17.5
+ {load_module, diameter_traffic},
+ {load_module, diameter_gen_base_rfc6733},
+ {load_module, diameter_gen_acct_rfc6733},
+ {load_module, diameter_gen_base_rfc3588},
+ {load_module, diameter_gen_base_accounting},
+ {load_module, diameter_gen_relay}]}
],
[
{"0.9", [{restart_application, diameter}]},
@@ -102,32 +87,10 @@
{"1.4.3", [{restart_application, diameter}]},
{"1.4.4", [{restart_application, diameter}]},
{"1.5", [{restart_application, diameter}]},
- {"1.6", [{load_module, diameter_sctp},
- {load_module, diameter_codec},
- {load_module, diameter_gen_relay},
- {load_module, diameter_gen_base_accounting},
- {load_module, diameter_gen_base_rfc3588},
- {load_module, diameter_gen_acct_rfc6733},
- {load_module, diameter_gen_base_rfc6733},
- {load_module, diameter_service},
- {load_module, diameter_peer_fsm},
- {load_module, diameter_watchdog},
- {load_module, diameter_traffic},
- {load_module, diameter_lib}]},
- {"1.7", [{load_module, diameter_peer_fsm},
- {load_module, diameter_traffic},
- {load_module, diameter_gen_relay},
- {load_module, diameter_gen_base_accounting},
- {load_module, diameter_gen_base_rfc3588},
- {load_module, diameter_gen_acct_rfc6733},
- {load_module, diameter_gen_base_rfc6733},
- {load_module, diameter_codec},
- {load_module, diameter_service}]},
- {"1.7.1", [{load_module, diameter_service},
- {load_module, diameter_peer_fsm},
- {load_module, diameter_watchdog},
- {load_module, diameter_traffic}]},
- {"1.8", [{update, diameter_sup, supervisor},
+ {"1.6", [{restart_application, diameter}]},
+ {"1.7", [{restart_application, diameter}]},
+ {<<"^1\\.(7\\.1|8)$">>,
+ [{update, diameter_sup, supervisor},
{update, diameter_service_sup, supervisor},
{update, diameter_transport_sup, supervisor},
{load_module, diameter_gen_relay},
@@ -151,6 +114,13 @@
{load_module, diameter_session},
{load_module, diameter_reg},
{load_module, diameter_peer},
- {load_module, diameter_lib}]}
+ {load_module, diameter_lib}]},
+ {"1.9", [{load_module, diameter_gen_relay},
+ {load_module, diameter_gen_base_accounting},
+ {load_module, diameter_gen_base_rfc3588},
+ {load_module, diameter_gen_acct_rfc6733},
+ {load_module, diameter_gen_base_rfc6733},
+ {load_module, diameter_traffic},
+ {load_module, diameter_codec}]}
]
}.
diff --git a/lib/diameter/test/diameter_3xxx_SUITE.erl b/lib/diameter/test/diameter_3xxx_SUITE.erl
index 071b1a1177..44fc3a60aa 100644
--- a/lib/diameter/test/diameter_3xxx_SUITE.erl
+++ b/lib/diameter/test/diameter_3xxx_SUITE.erl
@@ -47,6 +47,7 @@
send_double_error/1,
send_3xxx/1,
send_5xxx/1,
+ counters/1,
stop/1]).
%% diameter callbacks
@@ -111,7 +112,7 @@ all() ->
groups() ->
Tc = tc(),
- [{?util:name([E,D]), [], [start] ++ Tc ++ [stop]}
+ [{?util:name([E,D]), [], [start] ++ Tc ++ [counters, stop]}
|| E <- ?ERRORS, D <- ?RFCS].
init_per_suite(Config) ->
@@ -169,6 +170,203 @@ stop(_Config) ->
ok = diameter:stop_service(?SERVER),
ok = diameter:stop_service(?CLIENT).
+%% counters/1
+%%
+%% Check that counters are as expected.
+
+counters(Config) ->
+ Group = proplists:get_value(group, Config),
+ [_Errors, _Rfc] = G = ?util:name(Group),
+ [] = ?util:run([[fun counters/3, K, S, G]
+ || K <- [statistics, transport, connections],
+ S <- [?CLIENT, ?SERVER]]).
+
+counters(Key, Svc, Group) ->
+ counters(Key, Svc, Group, [_|_] = diameter:service_info(Svc, Key)).
+
+counters(statistics, Svc, [Errors, Rfc], L) ->
+ [{P, Stats}] = L,
+ true = is_pid(P),
+ stats(Svc, Errors, Rfc, lists:sort(Stats));
+
+counters(_, _, _, _) ->
+ todo.
+
+stats(?CLIENT, E, rfc3588, L)
+ when E == answer;
+ E == answer_3xxx ->
+ [{{unknown,recv},2},
+ {{{0,257,0},recv},1},
+ {{{0,257,1},send},1},
+ {{{0,275,0},recv},6},
+ {{{0,275,1},send},10},
+ {{unknown,recv,{'Result-Code',3001}},1},
+ {{unknown,recv,{'Result-Code',3007}},1},
+ {{{0,257,0},recv,{'Result-Code',2001}},1},
+ {{{0,275,0},recv,{'Result-Code',2001}},1},
+ {{{0,275,0},recv,{'Result-Code',3008}},2},
+ {{{0,275,0},recv,{'Result-Code',3999}},1},
+ {{{0,275,0},recv,{'Result-Code',5002}},1},
+ {{{0,275,0},recv,{'Result-Code',5005}},1}]
+ = L;
+
+stats(?SERVER, E, rfc3588, L)
+ when E == answer;
+ E == answer_3xxx ->
+ [{{unknown,recv},1},
+ {{unknown,send},2},
+ {{{0,257,0},send},1},
+ {{{0,257,1},recv},1},
+ {{{0,275,0},send},6},
+ {{{0,275,1},recv},8},
+ {{unknown,recv,error},1},
+ {{unknown,send,{'Result-Code',3001}},1},
+ {{unknown,send,{'Result-Code',3007}},1},
+ {{{0,257,0},send,{'Result-Code',2001}},1},
+ {{{0,275,0},send,{'Result-Code',2001}},1},
+ {{{0,275,0},send,{'Result-Code',3008}},2},
+ {{{0,275,0},send,{'Result-Code',3999}},1},
+ {{{0,275,0},send,{'Result-Code',5002}},1},
+ {{{0,275,0},send,{'Result-Code',5005}},1},
+ {{{0,275,1},recv,error},5}]
+ = L;
+
+stats(?CLIENT, answer, rfc6733, L) ->
+ [{{unknown,recv},2},
+ {{{0,257,0},recv},1},
+ {{{0,257,1},send},1},
+ {{{0,275,0},recv},8},
+ {{{0,275,1},send},10},
+ {{unknown,recv,{'Result-Code',3001}},1},
+ {{unknown,recv,{'Result-Code',3007}},1},
+ {{{0,257,0},recv,{'Result-Code',2001}},1},
+ {{{0,275,0},recv,{'Result-Code',3008}},2},
+ {{{0,275,0},recv,{'Result-Code',3999}},1},
+ {{{0,275,0},recv,{'Result-Code',5002}},1},
+ {{{0,275,0},recv,{'Result-Code',5005}},3},
+ {{{0,275,0},recv,{'Result-Code',5999}},1}]
+ = L;
+
+stats(?SERVER, answer, rfc6733, L) ->
+ [{{unknown,recv},1},
+ {{unknown,send},2},
+ {{{0,257,0},send},1},
+ {{{0,257,1},recv},1},
+ {{{0,275,0},send},8},
+ {{{0,275,1},recv},8},
+ {{unknown,recv,error},1},
+ {{unknown,send,{'Result-Code',3001}},1},
+ {{unknown,send,{'Result-Code',3007}},1},
+ {{{0,257,0},send,{'Result-Code',2001}},1},
+ {{{0,275,0},send,{'Result-Code',3008}},2},
+ {{{0,275,0},send,{'Result-Code',3999}},1},
+ {{{0,275,0},send,{'Result-Code',5002}},1},
+ {{{0,275,0},send,{'Result-Code',5005}},3},
+ {{{0,275,0},send,{'Result-Code',5999}},1},
+ {{{0,275,1},recv,error},5}]
+ = L;
+
+stats(?CLIENT, answer_3xxx, rfc6733, L) ->
+ [{{unknown,recv},2},
+ {{{0,257,0},recv},1},
+ {{{0,257,1},send},1},
+ {{{0,275,0},recv},8},
+ {{{0,275,1},send},10},
+ {{unknown,recv,{'Result-Code',3001}},1},
+ {{unknown,recv,{'Result-Code',3007}},1},
+ {{{0,257,0},recv,{'Result-Code',2001}},1},
+ {{{0,275,0},recv,{'Result-Code',2001}},1},
+ {{{0,275,0},recv,{'Result-Code',3008}},2},
+ {{{0,275,0},recv,{'Result-Code',3999}},1},
+ {{{0,275,0},recv,{'Result-Code',5002}},1},
+ {{{0,275,0},recv,{'Result-Code',5005}},2},
+ {{{0,275,0},recv,{'Result-Code',5999}},1}]
+ = L;
+
+stats(?SERVER, answer_3xxx, rfc6733, L) ->
+ [{{unknown,recv},1},
+ {{unknown,send},2},
+ {{{0,257,0},send},1},
+ {{{0,257,1},recv},1},
+ {{{0,275,0},send},8},
+ {{{0,275,1},recv},8},
+ {{unknown,recv,error},1},
+ {{unknown,send,{'Result-Code',3001}},1},
+ {{unknown,send,{'Result-Code',3007}},1},
+ {{{0,257,0},send,{'Result-Code',2001}},1},
+ {{{0,275,0},send,{'Result-Code',2001}},1},
+ {{{0,275,0},send,{'Result-Code',3008}},2},
+ {{{0,275,0},send,{'Result-Code',3999}},1},
+ {{{0,275,0},send,{'Result-Code',5002}},1},
+ {{{0,275,0},send,{'Result-Code',5005}},2},
+ {{{0,275,0},send,{'Result-Code',5999}},1},
+ {{{0,275,1},recv,error},5}]
+ = L;
+
+stats(?CLIENT, callback, rfc3588, L) ->
+ [{{unknown,recv},1},
+ {{{0,257,0},recv},1},
+ {{{0,257,1},send},1},
+ {{{0,275,0},recv},6},
+ {{{0,275,1},send},10},
+ {{unknown,recv,{'Result-Code',3007}},1},
+ {{{0,257,0},recv,{'Result-Code',2001}},1},
+ {{{0,275,0},recv,{'Result-Code',2001}},2},
+ {{{0,275,0},recv,{'Result-Code',3999}},1},
+ {{{0,275,0},recv,{'Result-Code',5002}},1},
+ {{{0,275,0},recv,{'Result-Code',5005}},2}]
+ = L;
+
+stats(?SERVER, callback, rfc3588, L) ->
+ [{{unknown,recv},1},
+ {{unknown,send},1},
+ {{{0,257,0},send},1},
+ {{{0,257,1},recv},1},
+ {{{0,275,0},send},6},
+ {{{0,275,1},recv},8},
+ {{unknown,recv,error},1},
+ {{unknown,send,{'Result-Code',3007}},1},
+ {{{0,257,0},send,{'Result-Code',2001}},1},
+ {{{0,275,0},send,{'Result-Code',2001}},2},
+ {{{0,275,0},send,{'Result-Code',3999}},1},
+ {{{0,275,0},send,{'Result-Code',5002}},1},
+ {{{0,275,0},send,{'Result-Code',5005}},2},
+ {{{0,275,1},recv,error},5}]
+ = L;
+
+stats(?CLIENT, callback, rfc6733, L) ->
+ [{{unknown,recv},1},
+ {{{0,257,0},recv},1},
+ {{{0,257,1},send},1},
+ {{{0,275,0},recv},8},
+ {{{0,275,1},send},10},
+ {{unknown,recv,{'Result-Code',3007}},1},
+ {{{0,257,0},recv,{'Result-Code',2001}},1},
+ {{{0,275,0},recv,{'Result-Code',2001}},2},
+ {{{0,275,0},recv,{'Result-Code',3999}},1},
+ {{{0,275,0},recv,{'Result-Code',5002}},1},
+ {{{0,275,0},recv,{'Result-Code',5005}},3},
+ {{{0,275,0},recv,{'Result-Code',5999}},1}]
+ = L;
+
+stats(?SERVER, callback, rfc6733, L) ->
+ [{{unknown,recv},1},
+ {{unknown,send},1},
+ {{{0,257,0},send},1},
+ {{{0,257,1},recv},1},
+ {{{0,275,0},send},8},
+ {{{0,275,1},recv},8},
+ {{unknown,recv,error},1},
+ {{unknown,send,{'Result-Code',3007}},1},
+ {{{0,257,0},send,{'Result-Code',2001}},1},
+ {{{0,275,0},send,{'Result-Code',2001}},2},
+ {{{0,275,0},send,{'Result-Code',3999}},1},
+ {{{0,275,0},send,{'Result-Code',5002}},1},
+ {{{0,275,0},send,{'Result-Code',5005}},3},
+ {{{0,275,0},send,{'Result-Code',5999}},1},
+ {{{0,275,1},recv,error},5}]
+ = L.
+
%% send_unknown_application/1
%%
%% Send an unknown application that a callback (which shouldn't take
diff --git a/lib/diameter/test/diameter_app_SUITE.erl b/lib/diameter/test/diameter_app_SUITE.erl
index 6975e83830..84f8a66a8a 100644
--- a/lib/diameter/test/diameter_app_SUITE.erl
+++ b/lib/diameter/test/diameter_app_SUITE.erl
@@ -249,11 +249,10 @@ release() ->
end.
unversion(App) ->
- T = lists:dropwhile(fun is_vsn_ch/1, lists:reverse(App)),
- lists:reverse(case T of [$-|TT] -> TT; _ -> T end).
-
-is_vsn_ch(C) ->
- $0 =< C andalso C =< $9 orelse $. == C.
+ {Name, [$-|Vsn]} = lists:splitwith(fun(C) -> C /= $- end, App),
+ true = is_app(Name), %% assert
+ Vsn = vsn_str(Vsn), %%
+ Name.
app('$M_EXPR') -> %% could be anything but assume it's ok
"erts";
@@ -322,11 +321,11 @@ acc_rel(Dir, Rel, {Vsn, _}, Acc) ->
%% Write a rel file and return its name.
write_rel(Dir, [Erts | Apps], Vsn) ->
- true = is_vsn(Vsn),
- Name = "diameter_test_" ++ Vsn,
+ VS = vsn_str(Vsn),
+ Name = "diameter_test_" ++ VS,
ok = write_file(filename:join([Dir, Name ++ ".rel"]),
{release,
- {"diameter " ++ Vsn ++ " test release", Vsn},
+ {"diameter " ++ VS ++ " test release", VS},
Erts,
Apps}),
Name.
@@ -341,10 +340,34 @@ fetch(Key, List) ->
write_file(Path, T) ->
file:write_file(Path, io_lib:format("~p.", [T])).
-%% Is a version string of the expected form? Return the argument
-%% itself for 'false' for a useful badmatch.
+%% Is a version string of the expected form?
is_vsn(V) ->
- is_list(V)
- andalso length(V) == string:span(V, "0123456789.")
- andalso V == string:join(string:tokens(V, [$.]), ".") %% no ".."
- orelse {error, V}.
+ V = vsn_str(V),
+ true.
+
+%% Turn a from/to version in appup to a version string after ensuring
+%% that it's valid version number of regexp. In the regexp case, the
+%% regexp itself becomes the version string since there's no
+%% requirement that a version in appup be anything but a string. The
+%% restrictions placed on string-valued version numbers (that they be
+%% '.'-separated integers) are our own.
+
+vsn_str(S)
+ when is_list(S) ->
+ {_, match} = {S, match(S, "^(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))*$")},
+ {_, nomatch} = {S, match(S, "\\.0\\.0$")},
+ S;
+
+vsn_str(B)
+ when is_binary(B) ->
+ {ok, _} = re:compile(B),
+ binary_to_list(B).
+
+match(S, RE) ->
+ re:run(S, RE, [{capture, none}]).
+
+%% Is an application name of the expected form?
+is_app(S)
+ when is_list(S) ->
+ {_, match} = {S, match(S, "^([a-z]([a-z_]*|[a-zA-Z]*))$")},
+ true.
diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl
index 7dd9f39f85..7ff6ba7ab9 100644
--- a/lib/diameter/test/diameter_traffic_SUITE.erl
+++ b/lib/diameter/test/diameter_traffic_SUITE.erl
@@ -41,6 +41,7 @@
send_eval/1,
send_bad_answer/1,
send_protocol_error/1,
+ send_experimental_result/1,
send_arbitrary/1,
send_unknown/1,
send_unknown_short/1,
@@ -301,6 +302,7 @@ tc() ->
send_eval,
send_bad_answer,
send_protocol_error,
+ send_experimental_result,
send_arbitrary,
send_unknown,
send_unknown_short,
@@ -443,7 +445,7 @@ send_ok(Config) ->
Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD},
{'Accounting-Record-Number', 1}],
- ['ACA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['ACA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= call(Config, Req).
%% Send an accounting ACR that the server answers badly to.
@@ -459,7 +461,7 @@ send_eval(Config) ->
Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD},
{'Accounting-Record-Number', 3}],
- ['ACA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['ACA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= call(Config, Req).
%% Send an accounting ACR that the server tries to answer with an
@@ -480,12 +482,20 @@ send_protocol_error(Config) ->
?answer_message(?TOO_BUSY)
= call(Config, Req).
+%% Send a 3xxx Experimental-Result in an answer not setting the E-bit
+%% and missing a Result-Code.
+send_experimental_result(Config) ->
+ Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD},
+ {'Accounting-Record-Number', 5}],
+ ['ACA', {'Session-Id', _} | _]
+ = call(Config, Req).
+
%% Send an ASR with an arbitrary non-mandatory AVP and expect success
%% and the same AVP in the reply.
send_arbitrary(Config) ->
Req = ['ASR', {'AVP', [#diameter_avp{name = 'Product-Name',
value = "XXX"}]}],
- ['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps]
+ ['ASA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | Avps]
= call(Config, Req),
{'AVP', [#diameter_avp{name = 'Product-Name',
value = V}]}
@@ -497,7 +507,7 @@ send_unknown(Config) ->
Req = ['ASR', {'AVP', [#diameter_avp{code = 999,
is_mandatory = false,
data = <<17>>}]}],
- ['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps]
+ ['ASA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | Avps]
= call(Config, Req),
{'AVP', [#diameter_avp{code = 999,
is_mandatory = false,
@@ -513,7 +523,7 @@ send_unknown_short(Config, M, RC) ->
Req = ['ASR', {'AVP', [#diameter_avp{code = 999,
is_mandatory = M,
data = <<17>>}]}],
- ['ASA', _SessionId, {'Result-Code', RC} | Avps]
+ ['ASA', {'Session-Id', _}, {'Result-Code', RC} | Avps]
= call(Config, Req),
[#'diameter_base_Failed-AVP'{'AVP' = As}]
= proplists:get_value('Failed-AVP', Avps),
@@ -527,7 +537,7 @@ send_unknown_mandatory(Config) ->
Req = ['ASR', {'AVP', [#diameter_avp{code = 999,
is_mandatory = true,
data = <<17>>}]}],
- ['ASA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | Avps]
+ ['ASA', {'Session-Id', _}, {'Result-Code', ?AVP_UNSUPPORTED} | Avps]
= call(Config, Req),
[#'diameter_base_Failed-AVP'{'AVP' = As}]
= proplists:get_value('Failed-AVP', Avps),
@@ -547,7 +557,7 @@ send_unexpected_mandatory_decode(Config) ->
Req = ['ASR', {'AVP', [#diameter_avp{code = 27, %% Session-Timeout
is_mandatory = true,
data = <<12:32>>}]}],
- ['ASA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | Avps]
+ ['ASA', {'Session-Id', _}, {'Result-Code', ?AVP_UNSUPPORTED} | Avps]
= call(Config, Req),
[#'diameter_base_Failed-AVP'{'AVP' = As}]
= proplists:get_value('Failed-AVP', Avps),
@@ -583,7 +593,7 @@ send_error_bit(Config) ->
%% Send a bad version and check that we get 5011.
send_unsupported_version(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT}],
- ['STA', _SessionId, {'Result-Code', ?UNSUPPORTED_VERSION} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?UNSUPPORTED_VERSION} | _]
= call(Config, Req).
%% Send a request containing an AVP length > data size.
@@ -603,14 +613,14 @@ send_zero_avp_length(Config) ->
send_invalid_avp_length(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT}],
- ['STA', _SessionId,
+ ['STA', {'Session-Id', _},
{'Result-Code', ?INVALID_AVP_LENGTH},
- _OriginHost,
- _OriginRealm,
- _UserName,
- _Class,
- _ErrorMessage,
- _ErrorReportingHost,
+ {'Origin-Host', _},
+ {'Origin-Realm', _},
+ {'User-Name', _},
+ {'Class', _},
+ {'Error-Message', _},
+ {'Error-Reporting-Host', _},
{'Failed-AVP', [#'diameter_base_Failed-AVP'{'AVP' = [_]}]}
| _]
= call(Config, Req).
@@ -628,14 +638,14 @@ send_invalid_reject(Config) ->
send_unexpected_mandatory(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT}],
- ['STA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?AVP_UNSUPPORTED} | _]
= 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)]}],
- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= call(Config, Req).
%% Send something longer than the configure incoming_maxlen.
@@ -677,7 +687,7 @@ send_any_2(Config) ->
send_all_1(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT}],
Realm = lists:foldr(fun(C,A) -> [C,A] end, [], ?REALM),
- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= call(Config, Req, [{filter, {all, [{host, any},
{realm, Realm}]}}]).
send_all_2(Config) ->
@@ -697,9 +707,8 @@ send_timeout(Config) ->
%% received the Session-Id.
send_error(Config) ->
Req = ['RAR', {'Re-Auth-Request-Type', ?AUTHORIZE_AUTHENTICATE}],
- ?answer_message(SId, ?TOO_BUSY)
- = call(Config, Req),
- true = undefined /= SId.
+ ?answer_message([_], ?TOO_BUSY)
+ = call(Config, Req).
%% Send a request with the detached option and receive it as a message
%% from handle_answer instead.
@@ -708,7 +717,7 @@ send_detach(Config) ->
Ref = make_ref(),
ok = call(Config, Req, [{extra, [{self(), Ref}]}, detach]),
Ans = receive {Ref, T} -> T end,
- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= Ans.
%% Send a request which can't be encoded and expect {error, encode}.
@@ -721,11 +730,11 @@ send_destination_1(Config) ->
= group(Config),
Req = ['STR', {'Termination-Cause', ?LOGOUT},
{'Destination-Host', [?HOST(SN, ?REALM)]}],
- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= call(Config, Req, [{filter, {all, [host, realm]}}]).
send_destination_2(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT}],
- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= call(Config, Req, [{filter, {all, [host, realm]}}]).
%% Send with filtering on and expect failure when specifying an
@@ -789,7 +798,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,
- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= send_multiple_filters(Config, [host, {eval, Fun}]).
send_multiple_filters_2(Config) ->
E = {erlang, is_tuple, []},
@@ -800,7 +809,7 @@ send_multiple_filters_3(Config) ->
E2 = {erlang, is_tuple, []},
E3 = {erlang, is_record, [diameter_caps]},
E4 = [{erlang, is_record, []}, diameter_caps],
- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= send_multiple_filters(Config, [{eval, E} || E <- [E1,E2,E3,E4]]).
send_multiple_filters(Config, Fs) ->
@@ -811,7 +820,7 @@ send_multiple_filters(Config, Fs) ->
%% only the return value from the prepare_request callback being
%% significant.
send_anything(Config) ->
- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
+ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
= call(Config, anything).
%% ===========================================================================
@@ -1144,6 +1153,13 @@ answer(Pkt, Req, _Peer, Name, #group{client_dict0 = Dict0}) ->
[R | Vs] = Dict:'#get-'(answer(Ans, Es, Name)),
[Dict:rec2msg(R) | Vs].
+%% Missing Result-Codec and inapproriate Experimental-Result-Code.
+answer(Rec, Es, send_experimental_result) ->
+ [{5004, #diameter_avp{name = 'Experimental-Result'}},
+ {5005, #diameter_avp{name = 'Result-Code'}}]
+ = Es,
+ Rec;
+
%% An inappropriate E-bit results in a decode error ...
answer(Rec, Es, send_bad_answer) ->
[{5004, #diameter_avp{name = 'Result-Code'}} | _] = Es,
@@ -1175,7 +1191,9 @@ handle_error(Reason, _Req, [$C|_], _Peer, _, _Time) ->
%% Note that diameter will set Result-Code and Failed-AVPs if
%% #diameter_packet.errors is non-null.
-handle_request(#diameter_packet{header = H, msg = M}, _, {_Ref, Caps}) ->
+handle_request(#diameter_packet{header = H, msg = M, avps = As},
+ _,
+ {_Ref, Caps}) ->
#diameter_header{end_to_end_id = EI,
hop_by_hop_id = HI}
= H,
@@ -1183,10 +1201,12 @@ handle_request(#diameter_packet{header = H, msg = M}, _, {_Ref, Caps}) ->
V = EI bsr B, %% assert
V = HI bsr B, %%
#diameter_caps{origin_state_id = {_,[Id]}} = Caps,
- answer(origin(Id), request(M, Caps)).
+ answer(origin(Id), request(M, [H|As], Caps)).
answer(T, {Tag, Action, Post}) ->
{Tag, answer(T, Action), Post};
+answer(_, {reply, [#diameter_header{} | _]} = T) ->
+ T;
answer({A,C}, {reply, Ans}) ->
answer(C, {reply, msg(Ans, A, diameter_gen_base_rfc3588)});
answer(pkt, {reply, Ans})
@@ -1195,6 +1215,41 @@ answer(pkt, {reply, Ans})
answer(_, T) ->
T.
+%% request/3
+
+%% send_experimental_result
+request(#diameter_base_accounting_ACR{'Accounting-Record-Number' = 5},
+ [Hdr | Avps],
+ #diameter_caps{origin_host = {OH, _},
+ origin_realm = {OR, _}}) ->
+ [H,R|T] = [A || N <- ['Origin-Host',
+ 'Origin-Realm',
+ 'Session-Id',
+ 'Accounting-Record-Type',
+ 'Accounting-Record-Number'],
+ #diameter_avp{} = A
+ <- [lists:keyfind(N, #diameter_avp.name, Avps)]],
+ Ans = [Hdr#diameter_header{is_request = false},
+ H#diameter_avp{data = OH},
+ R#diameter_avp{data = OR},
+ #diameter_avp{name = 'Experimental-Result',
+ code = 297,
+ need_encryption = false,
+ data = [#diameter_avp{data = {?DIAMETER_DICT_COMMON,
+ 'Vendor-Id',
+ 123}},
+ #diameter_avp{data
+ = {?DIAMETER_DICT_COMMON,
+ 'Experimental-Result-Code',
+ 3987}}]}
+ | T],
+ {reply, Ans};
+
+request(Msg, _Avps, Caps) ->
+ request(Msg, Caps).
+
+%% request/2
+
%% send_nok
request(#diameter_base_accounting_ACR{'Accounting-Record-Number' = 0},
_) ->
diff --git a/lib/diameter/vsn.mk b/lib/diameter/vsn.mk
index c00bac26bb..db7f72c44e 100644
--- a/lib/diameter/vsn.mk
+++ b/lib/diameter/vsn.mk
@@ -16,5 +16,5 @@
# %CopyrightEnd%
APPLICATION = diameter
-DIAMETER_VSN = 1.9
+DIAMETER_VSN = 1.9.1
APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN)