aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorAnders Svensson <[email protected]>2017-08-18 10:58:25 +0200
committerAnders Svensson <[email protected]>2017-08-18 10:58:25 +0200
commit196bf1ada0be5f5f2927a2028ef38a7547ec3db6 (patch)
tree3384dc9a294a840ad9511c62ff835537e808d472 /lib
parentc568b8ac624f39bf5a8d4e66f1db32ea629937f4 (diff)
parenta14ba6581063c4fca2edc36156e07c6582729e2e (diff)
downloadotp-196bf1ada0be5f5f2927a2028ef38a7547ec3db6.tar.gz
otp-196bf1ada0be5f5f2927a2028ef38a7547ec3db6.tar.bz2
otp-196bf1ada0be5f5f2927a2028ef38a7547ec3db6.zip
Merge branch 'anders/diameter/5009/OTP-14512' into maint
* anders/diameter/5009/OTP-14512: Use relaxed arity checks in traffic suite Be forgiving of non-list values at encode Add service_opt() strict_arities Fix detection of 5009 errors Test Result-Code 5009 in traffic suite
Diffstat (limited to 'lib')
-rw-r--r--lib/diameter/doc/src/diameter.xml31
-rw-r--r--lib/diameter/src/base/diameter.erl7
-rw-r--r--lib/diameter/src/base/diameter_config.erl8
-rw-r--r--lib/diameter/src/base/diameter_gen.erl313
-rw-r--r--lib/diameter/src/base/diameter_peer_fsm.erl1
-rw-r--r--lib/diameter/src/base/diameter_service.erl3
-rw-r--r--lib/diameter/src/base/diameter_traffic.erl2
-rw-r--r--lib/diameter/src/base/diameter_watchdog.erl9
-rw-r--r--lib/diameter/test/diameter_traffic_SUITE.erl68
9 files changed, 294 insertions, 148 deletions
diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml
index 43cb3a538c..017f6bb01d 100644
--- a/lib/diameter/doc/src/diameter.xml
+++ b/lib/diameter/doc/src/diameter.xml
@@ -956,6 +956,37 @@ Defaults to the empty list.</p>
</item>
<tag>
+<marker id="strict_arities"/><c>{strict_arities, boolean()
+ | encode
+ | decode}</c></tag>
+<item>
+<p>
+Whether or not to require that the number of AVPs in a message or
+grouped AVP agree with those specified in the dictionary in question.
+If <c>true</c> then mismatches in an outgoing messages cause message
+encoding to fail, while mismatches in an incoming message are reported
+as 5005/5009 errors in the errors field of the diameter_packet record
+passed to &app_handle_request; or &app_handle_answer; callbacks.
+If <c>false</c> then neither error is enforced/detected.
+If <c>encode</c> or <c>decode</c> then errors are only
+enforced/detected on outgoing or incoming messages respectively.</p>
+
+<p>
+Defaults to <c>true</c>.</p>
+
+<note>
+<p>
+Disabling arity checks affects the form of messages at encode/decode.
+In particular, decoded AVPs are represented as lists of values,
+regardless of the AVP's arity (ie. expected number in the message/AVP
+grammar in question), and values are expected to be supplied as lists
+at encode.
+This differs from the historic decode behaviour of representing AVPs
+of arity 1 as bare values, not wrapped in a list.</p>
+</note>
+</item>
+
+<tag>
<marker id="strict_mbit"/><c>{strict_mbit, boolean()}</c></tag>
<item>
<p>
diff --git a/lib/diameter/src/base/diameter.erl b/lib/diameter/src/base/diameter.erl
index 85a54c8e61..c919ff4c93 100644
--- a/lib/diameter/src/base/diameter.erl
+++ b/lib/diameter/src/base/diameter.erl
@@ -48,6 +48,7 @@
-export_type([evaluable/0,
decode_format/0,
+ strict_arities/0,
restriction/0,
message_length/0,
remotes/0,
@@ -338,6 +339,11 @@ call(SvcName, App, Message) ->
| false
| record_from_map.
+-type strict_arities()
+ :: false
+ | encode
+ | decode.
+
%% Options passed to start_service/2
-type service_opt()
@@ -348,6 +354,7 @@ call(SvcName, App, Message) ->
| {share_peers, remotes()}
| {decode_format, decode_format()}
| {string_decode, boolean()}
+ | {strict_arities, true | strict_arities()}
| {strict_mbit, boolean()}
| {incoming_maxlen, message_length()}
| {use_shared_peers, remotes()}
diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl
index d591fa903e..2b069f40cc 100644
--- a/lib/diameter/src/base/diameter_config.erl
+++ b/lib/diameter/src/base/diameter_config.erl
@@ -712,6 +712,7 @@ make_config(SvcName, Opts) ->
{?NOMASK, sequence},
{nodes, restrict_connections},
{16#FFFFFF, incoming_maxlen},
+ {true, strict_arities},
{true, strict_mbit},
{record, decode_format},
{true, string_decode},
@@ -756,6 +757,7 @@ opt(K, false = B)
K == use_shared_peers;
K == monitor;
K == restrict_connections;
+ K == strict_arities;
K == strict_mbit;
K == decode_format;
K == string_decode ->
@@ -764,6 +766,7 @@ opt(K, false = B)
opt(K, true = B)
when K == share_peers;
K == use_shared_peers;
+ K == strict_arities;
K == strict_mbit;
K == string_decode ->
B;
@@ -775,6 +778,11 @@ opt(decode_format, T)
T == record_from_map ->
T;
+opt(strict_arities, T)
+ when T == encode;
+ T == decode ->
+ T;
+
opt(restrict_connections, T)
when T == node;
T == nodes ->
diff --git a/lib/diameter/src/base/diameter_gen.erl b/lib/diameter/src/base/diameter_gen.erl
index a7dc3aaec3..3d0612c294 100644
--- a/lib/diameter/src/base/diameter_gen.erl
+++ b/lib/diameter/src/base/diameter_gen.erl
@@ -46,6 +46,9 @@
-type grouped_avp() :: nonempty_improper_list(#diameter_avp{}, [avp()]).
-type avp() :: non_grouped_avp() | grouped_avp().
+%% The arbitrary arity returned from dictionary avp_arity functions.
+-define(ANY, {0, '*'}).
+
%% ---------------------------------------------------------------------------
%% # encode_avps/3
%% ---------------------------------------------------------------------------
@@ -88,18 +91,26 @@ encode(Name, Vals, Opts, Mod)
encode(Name, Map, Opts, Mod)
when is_map(Map) ->
[enc(Name, F, A, V, Opts, Mod) || {F,A} <- Mod:avp_arity(Name),
- V <- [maps:get(F, Map, def(A))]];
+ V <- [maps:get(F, Map, undefined)]];
encode(Name, Rec, Opts, Mod) ->
[encode(Name, F, V, Opts, Mod) || {F,V} <- Mod:'#get-'(Rec)].
%% encode/5
+encode(Name, AvpName, Values, #{strict_arities := T} = Opts, Mod)
+ when T /= encode ->
+ enc(Name, AvpName, ?ANY, Values, Opts, Mod);
+
encode(Name, AvpName, Values, Opts, Mod) ->
enc(Name, AvpName, Mod:avp_arity(Name, AvpName), Values, Opts, Mod).
%% enc/6
+enc(Name, AvpName, Arity, Values, #{strict_arities := T} = Opts, Mod)
+ when T /= encode, Arity /= ?ANY ->
+ enc(Name, AvpName, ?ANY, Values, Opts, Mod);
+
enc(_, AvpName, 1, undefined, _, _) ->
?THROW([mandatory_avp_missing, AvpName]);
@@ -110,9 +121,15 @@ enc(Name, AvpName, 1, Value, Opts, Mod) ->
enc(_, _, {0,_}, [], _, _) ->
[];
-enc(_, AvpName, _, T, _, _)
- when not is_list(T) ->
- ?THROW([repeated_avp_as_non_list, AvpName, T]);
+enc(_, _, _, undefined, _, _) ->
+ [];
+
+%% Be forgiving when a list of values is expected. If the value itself
+%% is a list then the user has to wrap it to avoid each member from
+%% being interpreted as an individual AVP value.
+enc(Name, AvpName, Arity, V, Opts, Mod)
+ when not is_list(V) ->
+ enc(Name, AvpName, Arity, [V], Opts, Mod);
enc(Name, AvpName, {Min, Max}, Values, Opts, Mod) ->
H = avp_header(AvpName, Mod),
@@ -120,6 +137,14 @@ enc(Name, AvpName, {Min, Max}, Values, Opts, Mod) ->
%% enc/9
+enc(Name, AvpName, H, Min, N, '*', Vs, Opts, Mod)
+ when Min =< N ->
+ [enc1(Name, AvpName, H, V, Opts, Mod) || V <- Vs];
+
+enc(Name, AvpName, H, _, _, _, Vs, #{strict_arities := T} = Opts, Mod)
+ when T /= encode ->
+ [enc1(Name, AvpName, H, V, Opts, Mod) || V <- Vs];
+
enc(_, AvpName, _, Min, N, _, [], _, _)
when N < Min ->
?THROW([repeated_avp_insufficient_arity, AvpName, Min, N]);
@@ -127,10 +152,6 @@ enc(_, AvpName, _, Min, N, _, [], _, _)
enc(_, _, _, _, _, _, [], _, _) ->
[];
-enc(Name, AvpName, H, Min, N, '*', Vs, Opts, Mod)
- when Min =< N ->
- [enc1(Name, AvpName, H, V, Opts, Mod) || V <- Vs];
-
enc(_, AvpName, _, _, N, Max, _, _, _)
when Max =< N ->
?THROW([repeated_avp_excessive_arity, AvpName, Max]);
@@ -207,16 +228,16 @@ enc(AvpName, Value, Opts, Mod) ->
decode_avps(Name, Recs, #{module := Mod, decode_format := Fmt} = Opts) ->
{Avps, {Rec, AM, Failed}}
= mapfoldl(fun(T,A) -> decode(Name, Opts, Mod, T, A) end,
- {newrec(Mod, Name, Fmt), #{}, []},
+ {newrec(Fmt, Mod, Name, Opts), #{}, []},
Recs),
- %% AM counts the number of top-level AVPs, which missing/4 then
- %% uses when adding 5005 errors.
+ %% AM counts the number of top-level AVPs, which arities/5 then
+ %% uses when adding 500[59] errors.
Arities = Mod:avp_arity(Name),
- {reformat(Name, Rec, Arities, Mod, Fmt),
+ {reformat(Name, Rec, Arities, Mod, Opts, Fmt),
Avps,
- Failed ++ missing(Arities, Opts, Mod, AM)}.
+ Failed ++ arities(Arities, Opts, Mod, AM, Avps)}.
-%% Append 5005 errors so that errors are reported in the order
+%% Append arity errors so that errors are reported in the order
%% encountered. Failed-AVP should typically contain the first
%% error encountered.
@@ -233,9 +254,7 @@ mapfoldl(F, Acc0, [T|Rest], List) ->
mapfoldl(_, Acc, [], List) ->
{List, Acc}.
-%% missing/4
-
-%% 3588:
+%% 3588/6733:
%%
%% DIAMETER_MISSING_AVP 5005
%% The request did not contain an AVP that is required by the Command
@@ -244,40 +263,79 @@ mapfoldl(_, Acc, [], List) ->
%% AVP MUST contain an example of the missing AVP complete with the
%% Vendor-Id if applicable. The value field of the missing AVP
%% should be of correct minimum length and contain zeros.
+%%
+%% DIAMETER_AVP_OCCURS_TOO_MANY_TIMES 5009
+%% A message was received that included an AVP that appeared more
+%% often than permitted in the message definition. The Failed-AVP
+%% AVP MUST be included and contain a copy of the first instance of
+%% the offending AVP that exceeded the maximum number of occurrences
+
+%% arities/5
-missing(Arities, Opts, Mod, AM) ->
- lists:foldl(fun(T,A) -> missing(T, AM, Opts, Mod, A) end,
- [],
- Arities).
+arities(_, #{strict_arities := T}, _, _, _)
+ when T /= decode ->
+ [];
-%% missing/5
+arities(Arities, Opts, Mod, AM, Avps) ->
+ [Count, Map | More]
+ = lists:foldl(fun({N,T}, A) -> more(N, T, Opts, Mod, AM, A) end,
+ [0, #{}],
+ Arities),
+ less(Count, Map, Avps) ++ lists:reverse(More).
-missing({Name, Arity}, AM, Opts, Mod, Acc) ->
- case missing(Name, Arity, AM) of
- true -> [{5005, empty_avp(Name, Opts, Mod)} | Acc];
- false -> Acc
- end.
+%% more/6
-%% missing/3
+more(_Name, ?ANY, _Opts, _Mod, _AM, Acc) ->
+ Acc;
-missing(Name, Arity, AM) ->
- 'AVP' /= Name andalso too_few(Name, Arity, AM).
+more(Name, {Mn, Mx}, Opts, Mod, AM, Acc) ->
+ more(Name, Mn, Mx, Opts, Mod, AM, Acc);
-%% too_few/3
-%%
-%% Maximum arities have already been checked during the decode.
+more(Name, 1, Opts, Mod, AM, Acc) ->
+ more(Name, 1, 1, Opts, Mod, AM, Acc).
-too_few(_, {0, _}, _) ->
- false;
+%% more/7
+
+more(Name, Mn, Mx, Opts, Mod, AM, Acc) ->
+ macc(Name, Mn, maps:get(Name, AM, 0), Mx, Opts, Mod, Acc).
-too_few(FieldName, 1, AM) ->
- not maps:is_key(FieldName, AM);
+%% macc/7
-too_few(FieldName, {M, _}, AM) ->
- maps:get(FieldName, AM, 0) < M.
+macc(Name, Mn, N, _, Opts, Mod, [M, Map | T])
+ when N < Mn ->
+ [M, Map, {5005, empty_avp(Name, Opts, Mod)} | T];
+
+macc(Name, _, N, Mx, _Opts, _Mod, [M, Map | T])
+ when Mx < N ->
+ K = N - Mx,
+ [M + K, maps:put(Name, K, Map) | T];
+
+macc(_Name, _, _, _, _Opts, _Mod, Acc) ->
+ Acc.
+
+%% less/3
+
+less(0, _, _) ->
+ [];
+
+less(N, Map, [#diameter_avp{name = undefined} | Avps]) ->
+ less(N, Map, Avps);
+
+less(N, Map, [#diameter_avp{name = Name} = Avp | Avps]) ->
+ case Map of
+ #{Name := 0} ->
+ [{5009, Avp} | less(N-1, Map, Avps)];
+ #{Name := M} ->
+ less(N, maps:put(Name, M-1, Map), Avps);
+ _ ->
+ less(N, Map, Avps)
+ end.
%% empty_avp/3
+empty_avp('AVP', _, _) ->
+ #diameter_avp{data = <<0:64>>};
+
empty_avp(Name, Opts, Mod) ->
{Code, Flags, VId} = Mod:avp_header(Name),
{Name, Type} = Mod:avp_name(Code, VId),
@@ -300,22 +358,37 @@ empty_avp(Name, Opts, Mod) ->
%% decode/5
-decode(Name,
- Opts,
- Mod,
- #diameter_avp{code = Code, vendor_id = Vid}
- = Avp,
- {Rec, AM, Failed}) ->
- T = Mod:avp_name(Code, Vid),
- decode(Name, Opts, Mod, T, Avp, {Rec, incr(field(T), AM), Failed}).
+decode(Name, Opts, Mod, Avp, Acc) ->
+ #diameter_avp{code = Code, vendor_id = Vid}
+ = Avp,
+ N = Mod:avp_name(Code, Vid),
+ case Opts of
+ #{strict_arities := T} when T /= decode ->
+ decode(Name, Opts, Mod, N, ?ANY, Avp, Acc);
+ _ ->
+ {Rec, AM, Failed} = Acc,
+ F = field(N),
+ A = Mod:avp_arity(Name, F),
+ decode(Name, Opts, Mod, N, A, Avp, {Rec,
+ incr(field(F, A), AM),
+ Failed})
+ end.
%% field/1
-field({AvpName, _Type}) ->
+field({AvpName, _}) ->
AvpName;
-field('AVP' = A) ->
- A.
+field(_) ->
+ 'AVP'.
+
+%% field/2
+
+field(_, 0) ->
+ 'AVP';
+
+field(F, _) ->
+ F.
%% incr/2
@@ -327,11 +400,11 @@ incr(Key, Map) ->
incr(N) ->
N + 1.
-%% decode/6
+%% decode/7
%% AVP not in dictionary.
-decode(Name, Opts, Mod, 'AVP', Avp, Acc) ->
- decode_AVP(Name, Avp, Opts, Mod, Acc);
+decode(Name, Opts, Mod, 'AVP', Arity, Avp, Acc) ->
+ decode_AVP(Name, Arity, Avp, Opts, Mod, Acc);
%% 6733, 4.4:
%%
@@ -380,7 +453,7 @@ decode(Name, Opts, Mod, 'AVP', Avp, Acc) ->
%% defined the RFC's "unrecognized", which is slightly stronger than
%% "not defined".)
-decode(Name, Opts0, Mod, {AvpName, Type}, Avp, Acc) ->
+decode(Name, Opts0, Mod, {AvpName, Type}, Arity, Avp, Acc) ->
#diameter_avp{data = Data, is_mandatory = M}
= Avp,
@@ -409,13 +482,13 @@ decode(Name, Opts0, Mod, {AvpName, Type}, Avp, Acc) ->
A = Avp#diameter_avp{name = AvpName,
value = Rec,
type = Type},
- {[A|As], pack_avp(Name, A, Opts, Mod, Acc)};
+ {[A|As], pack_avp(Name, Arity, A, Opts, Mod, Acc)};
V when Type /= 'Grouped' ->
A = Avp#diameter_avp{name = AvpName,
value = V,
type = Type},
- {A, pack_avp(Name, A, Opts, Mod, Acc)}
+ {A, pack_avp(Name, Arity, A, Opts, Mod, Acc)}
catch
throw: {?MODULE, {grouped, Error, ComponentAvps}} ->
decode_error(Name,
@@ -524,8 +597,17 @@ set_failed(_, Opts) ->
%% Don't know this AVP: see if it can be packed in an 'AVP' field
%% undecoded. Note that the type field is 'undefined' in this case.
+decode_AVP(Name, Avp, #{strict_arities := T} = Opts, Mod, Acc)
+ when T /= decode ->
+ decode_AVP(Name, ?ANY, Avp, Opts, Mod, Acc);
+
decode_AVP(Name, Avp, Opts, Mod, Acc) ->
- {trim(Avp), pack_AVP(Name, Avp, Opts, Mod, Acc)}.
+ decode_AVP(Name, Mod:avp_arity(Name, 'AVP'), Avp, Opts, Mod, Acc).
+
+%% decode_AVP/6
+
+decode_AVP(Name, Arity, Avp, Opts, Mod, Acc) ->
+ {trim(Avp), pack_AVP(Name, Arity, Avp, Opts, Mod, Acc)}.
%% rc/2
@@ -545,11 +627,6 @@ rc({'DIAMETER', 5014 = RC, _}, #diameter_avp{name = AvpName} = A, Opts, Mod) ->
rc(_, Avp, _, _) ->
{5004, Avp}.
-%% pack_avp/5
-
-pack_avp(Name, #diameter_avp{name = AvpName} = Avp, Opts, Mod, Acc) ->
- pack_avp(Name, Mod:avp_arity(Name, AvpName), Avp, Opts, Mod, Acc).
-
%% pack_avp/6
pack_avp(Name, 0, Avp, Opts, Mod, Acc) ->
@@ -560,6 +637,15 @@ pack_avp(_, Arity, #diameter_avp{name = AvpName} = Avp, _Opts, Mod, Acc) ->
%% pack_AVP/5
+pack_AVP(Name, Avp, #{strict_arities := T} = Opts, Mod, Acc)
+ when T /= decode ->
+ pack_AVP(Name, ?ANY, Avp, Opts, Mod, Acc);
+
+pack_AVP(Name, Avp, Opts, Mod, Acc) ->
+ pack_AVP(Name, Mod:avp_arity(Name, 'AVP'), Avp, Opts, Mod, Acc).
+
+%% pack_AVP/6
+
%% Length failure was induced because of a header/payload length
%% mismatch. The AVP Length is reset to match the received data if
%% this AVP is encoded in an answer message, since the length is
@@ -573,17 +659,17 @@ pack_avp(_, Arity, #diameter_avp{name = AvpName} = Avp, _Opts, Mod, Acc) ->
%% payload for the AVP's type, but in this case we don't know the
%% type.
-pack_AVP(_, #diameter_avp{data = {5014 = RC, Data}} = Avp, _, _, Acc) ->
+pack_AVP(_, _, #diameter_avp{data = {5014 = RC, Data}} = Avp, _, _, Acc) ->
{Rec, AM, Failed} = Acc,
{Rec, AM, [{RC, Avp#diameter_avp{data = Data}} | Failed]};
-pack_AVP(Name, Avp, Opts, Mod, Acc) ->
- Arity = pack_arity(Name, Opts, Mod, Avp),
- if 0 == Arity ->
+pack_AVP(Name, Arity, Avp, Opts, Mod, Acc) ->
+ case pack_AVP(Name, Opts, Arity, Avp) of
+ false ->
M = Avp#diameter_avp.is_mandatory,
{Rec, AM, Failed} = Acc,
{Rec, AM, [{if M -> 5001; true -> 5008 end, Avp} | Failed]};
- true ->
+ true ->
pack(Arity, 'AVP', Avp, Mod, Acc)
end.
@@ -600,18 +686,25 @@ pack_AVP(Name, Avp, Opts, Mod, Acc) ->
%% Failed-AVP AVP MUST be included and contain a copy of the
%% offending AVP.
-%% pack_arity/4
+%% pack_AVP/4
%% Give Failed-AVP special treatment since (1) it'll contain any
%% unrecognized mandatory AVP's and (2) the RFC 3588 grammar failed to
%% allow for Failed-AVP in an answer-message.
-pack_arity(Name,
- #{strict_mbit := Strict,
- failed_avp := Failed},
- Mod,
- #diameter_avp{is_mandatory = M,
- name = AvpName}) ->
+pack_AVP(_, #{strict_arities := T}, _, _)
+ when T /= decode ->
+ true;
+
+pack_AVP(_, _, 0, _) ->
+ false;
+
+pack_AVP(Name,
+ #{strict_mbit := Strict,
+ failed_avp := Failed},
+ _,
+ #diameter_avp{is_mandatory = M,
+ name = AvpName}) ->
%% Not testing just Name /= 'Failed-AVP' means we're changing the
%% packing of AVPs nested within Failed-AVP, but the point of
@@ -619,44 +712,16 @@ pack_arity(Name,
%% possible, and failing because a mandatory AVP couldn't be
%% packed into a dedicated field defeats that point.
- if Failed == true;
- Name == 'Failed-AVP';
- Name == 'answer-message', AvpName == 'Failed-AVP';
- not M;
- not Strict ->
- Mod:avp_arity(Name, 'AVP');
- true ->
- 0
- end.
+ Failed == true
+ orelse Name == 'Failed-AVP'
+ orelse (Name == 'answer-message' andalso AvpName == 'Failed-AVP')
+ orelse not M
+ orelse not Strict.
%% pack/5
pack(Arity, F, Avp, Mod, {Rec, AM, Failed}) ->
- case too_many(F, Arity, AM) of
- true -> {Rec, AM, [{5009, Avp} | Failed]};
- false -> {set(Arity, F, value(F, Avp), Mod, Rec), AM, Failed}
- end.
-
-%% 3588:
-%%
-%% DIAMETER_AVP_OCCURS_TOO_MANY_TIMES 5009
-%% A message was received that included an AVP that appeared more
-%% often than permitted in the message definition. The Failed-AVP
-%% AVP MUST be included and contain a copy of the first instance of
-%% the offending AVP that exceeded the maximum number of occurrences
-%%
-
-%% too_many/3
-
-too_many(_, {_, '*'}, _) ->
- false;
-
-too_many(FieldName, {_, M}, Map) ->
- too_many(FieldName, M, Map);
-
-too_many(FieldName, M, Map) ->
- #{FieldName := N} = Map,
- M < N.
+ {set(Arity, F, value(F, Avp), Mod, Rec), AM, Failed}.
%% set/5
@@ -770,15 +835,21 @@ empty(Name, #{module := Mod} = Opts) ->
%% ------------------------------------------------------------------------------
-%% newrec/3
+%% newrec/4
-newrec(_, _, false = No) ->
+newrec(false = No, _, _, _) ->
No;
-newrec(Mod, Name, record) ->
+newrec(record, Mod, Name, #{strict_arities := T})
+ when T /= decode ->
+ RecName = Mod:name2rec(Name),
+ Sz = Mod:'#info-'(RecName, size),
+ erlang:make_tuple(Sz, [], [{1, RecName}]);
+
+newrec(record, Mod, Name, _) ->
newrec(Mod, Name);
-newrec(_, _, _) ->
+newrec(_, _, _, _) ->
#{}.
%% newrec/2
@@ -786,22 +857,24 @@ newrec(_, _, _) ->
newrec(Mod, Name) ->
Mod:'#new-'(Mod:name2rec(Name)).
-%% reformat/4
+%% reformat/5
-reformat(_, Map, Arities, _Mod, list) ->
+reformat(_, Map, Arities, _Mod, _Opts, list) ->
[{F,V} || {F,_} <- Arities, #{F := V} <- [Map]];
-reformat(Name, Map, Arities, Mod, record_from_map) ->
+reformat(Name, Map, Arities, Mod, Opts, record_from_map) ->
+ SA = maps:get(strict_arities, Opts, decode),
RecName = Mod:name2rec(Name),
- list_to_tuple([RecName | [maps:get(F, Map, def(A)) || {F,A} <- Arities]]);
+ list_to_tuple([RecName | [maps:get(F, Map, def(A, SA))
+ || {F,A} <- Arities]]);
-reformat(_, Rec, _, _, _) ->
+reformat(_, Rec, _, _, _, _) ->
Rec.
-%% def/1
+%% def/2
-def(1) ->
+def(1, decode) ->
undefined;
-def(_) ->
+def(_, _) ->
[].
diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl
index 597e395c62..c340c8b21a 100644
--- a/lib/diameter/src/base/diameter_peer_fsm.erl
+++ b/lib/diameter/src/base/diameter_peer_fsm.erl
@@ -130,6 +130,7 @@
%% diameter:call/4.
codec :: #{decode_format := record,
string_decode := boolean(),
+ strict_arities => diameter:strict_arities(),
strict_mbit := boolean(),
rfc := 3588 | 6733,
ordered_encode := false},
diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl
index cbb7549e39..3d3b25c8ad 100644
--- a/lib/diameter/src/base/diameter_service.erl
+++ b/lib/diameter/src/base/diameter_service.erl
@@ -112,6 +112,7 @@
use_shared_peers := diameter:remotes(),%% use from
restrict_connections := diameter:restriction(),
incoming_maxlen := diameter:message_length(),
+ strict_arities => diameter:strict_arities(),
strict_mbit := boolean(),
decode_format := diameter:decode_format(),
string_decode := boolean(),
@@ -708,7 +709,7 @@ init_peers() ->
%% TPid}
service_options(Opts) ->
- maps:from_list(Opts).
+ maps:from_list(lists:delete({strict_arities, true}, Opts)).
mref(false = No) ->
No;
diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl
index e9c422d6ab..3463a93568 100644
--- a/lib/diameter/src/base/diameter_traffic.erl
+++ b/lib/diameter/src/base/diameter_traffic.erl
@@ -78,6 +78,7 @@
sequence :: diameter:sequence(),
codec :: #{decode_format := diameter:decode_format(),
string_decode := boolean(),
+ strict_arities => diameter:strict_arities(),
strict_mbit := boolean(),
incoming_maxlen := diameter:message_length()}}).
%% Note that incoming_maxlen is currently handled in diameter_peer_fsm,
@@ -105,6 +106,7 @@ make_recvdata([SvcName, PeerT, Apps, SvcOpts | _]) ->
sequence = Mask,
codec = maps:with([decode_format,
string_decode,
+ strict_arities,
strict_mbit,
ordered_encode,
incoming_maxlen],
diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl
index 60baf1e8a4..294325751e 100644
--- a/lib/diameter/src/base/diameter_watchdog.erl
+++ b/lib/diameter/src/base/diameter_watchdog.erl
@@ -74,6 +74,7 @@
okay := non_neg_integer()}, %% REOPEN -> OKAY
codec :: #{decode_format := false,
string_decode := false,
+ strict_arities => diameter:strict_arities(),
strict_mbit := boolean(),
failed_avp := false,
rfc := 3588 | 6733,
@@ -138,6 +139,7 @@ i({Ack, T, Pid, {Opts,
Nodes = restrict_nodes(Restrict),
CodecKeys = [decode_format,
string_decode,
+ strict_arities,
strict_mbit,
incoming_maxlen,
spawn_opt,
@@ -157,9 +159,10 @@ i({Ack, T, Pid, {Opts,
suspect => 1,
okay => 3},
Opts)),
- codec = maps:with(CodecKeys, SvcOpts#{decode_format := false,
- string_decode := false,
- ordered_encode => false})}.
+ codec = maps:with(CodecKeys -- [strict_arities],
+ SvcOpts#{decode_format := false,
+ string_decode := false,
+ ordered_encode => false})}.
wait(Ref, Pid) ->
receive
diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl
index f05c850513..f47b223509 100644
--- a/lib/diameter/test/diameter_traffic_SUITE.erl
+++ b/lib/diameter/test/diameter_traffic_SUITE.erl
@@ -66,6 +66,7 @@
send_invalid_reject/1,
send_unexpected_mandatory_decode/1,
send_unexpected_mandatory/1,
+ send_too_many/1,
send_long/1,
send_maxlen/1,
send_nopeer/1,
@@ -236,6 +237,8 @@
?'DIAMETER_BASE_RESULT-CODE_AVP_UNSUPPORTED').
-define(UNSUPPORTED_VERSION,
?'DIAMETER_BASE_RESULT-CODE_UNSUPPORTED_VERSION').
+-define(TOO_MANY,
+ ?'DIAMETER_BASE_RESULT-CODE_AVP_OCCURS_TOO_MANY_TIMES').
-define(REALM_NOT_SERVED,
?'DIAMETER_BASE_RESULT-CODE_REALM_NOT_SERVED').
-define(UNABLE_TO_DELIVER,
@@ -423,6 +426,7 @@ tc() ->
send_invalid_reject,
send_unexpected_mandatory_decode,
send_unexpected_mandatory,
+ send_too_many,
send_long,
send_maxlen,
send_nopeer,
@@ -467,7 +471,8 @@ start_services(Config) ->
= group(Config),
ok = diameter:start_service(SN, [{decode_format, SD}
| ?SERVICE(SN, Grp)]),
- ok = diameter:start_service(CN, [{sequence, ?CLIENT_MASK}
+ ok = diameter:start_service(CN, [{sequence, ?CLIENT_MASK},
+ {strict_arities, decode}
| ?SERVICE(CN, Grp)]).
add_transports(Config) ->
@@ -561,7 +566,9 @@ rfc4005(Config) ->
%% Ensure that result codes have the expected values.
result_codes(_Config) ->
- {2001, 3001, 3002, 3003, 3004, 3007, 3008, 3009, 5001, 5011, 5014}
+ {2001,
+ 3001, 3002, 3003, 3004, 3007, 3008, 3009,
+ 5001, 5009, 5011, 5014}
= {?SUCCESS,
?COMMAND_UNSUPPORTED,
?UNABLE_TO_DELIVER,
@@ -571,6 +578,7 @@ result_codes(_Config) ->
?INVALID_HDR_BITS,
?INVALID_AVP_BITS,
?AVP_UNSUPPORTED,
+ ?TOO_MANY,
?UNSUPPORTED_VERSION,
?INVALID_AVP_LENGTH}.
@@ -703,6 +711,18 @@ send_unexpected_mandatory_decode(Config) ->
data = <<12:32>>}]]
= failed_avps(Avps, Config).
+%% Try to two Auth-Application-Id in ASR expect 5009.
+send_too_many(Config) ->
+ Req = ['ASR', {'Auth-Application-Id', [?APP_ID, 44]}],
+
+ ['ASA' | #{'Session-Id' := _,
+ 'Result-Code' := ?TOO_MANY,
+ 'Failed-AVP' := Avps}]
+ = call(Config, Req),
+ [[#diameter_avp{name = 'Auth-Application-Id',
+ value = 44}]]
+ = failed_avps(Avps, Config).
+
%% Send an containing a faulty Grouped AVP (empty Proxy-Host in
%% Proxy-Info) and expect that only the faulty AVP is sent in
%% Failed-AVP. The encoded values of Proxy-Host and Proxy-State are
@@ -876,7 +896,7 @@ send_detach(Config) ->
%% Send a request which can't be encoded and expect {error, encode}.
send_encode_error(Config) ->
- {error, encode} = call(Config, ['STR']). %% No Termination-Cause
+ {error, encode} = call(Config, ['STR', {'Termination-Cause', huh}]).
%% Send with filtering and expect success.
send_destination_1(Config) ->
@@ -897,14 +917,14 @@ send_destination_2(Config) ->
%% unknown host or realm.
send_destination_3(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT},
- {'Destination-Realm', "unknown.org"}],
+ {'Destination-Realm', <<"unknown.org">>}],
{error, no_connection}
= call(Config, Req, [{filter, {all, [host, realm]}}]).
send_destination_4(Config) ->
#group{server_service = SN}
= group(Config),
Req = ['STR', {'Termination-Cause', ?LOGOUT},
- {'Destination-Host', [?HOST(SN, "unknown.org")]}],
+ {'Destination-Host', [?HOST(SN, ["unknown.org"])]}],
{error, no_connection}
= call(Config, Req, [{filter, {all, [host, realm]}}]).
@@ -912,7 +932,7 @@ send_destination_4(Config) ->
%% an unknown host or realm.
send_destination_5(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT},
- {'Destination-Realm', "unknown.org"}],
+ {'Destination-Realm', [<<"unknown.org">>]}],
?answer_message(?REALM_NOT_SERVED)
= call(Config, Req).
send_destination_6(Config) ->
@@ -1393,10 +1413,10 @@ set(N, #diameter_packet{msg = Req}, Caps, Group)
origin_realm = {OR, DR}}
= Caps,
- set(Group, 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]}]);
set(N, #diameter_packet{msg = Req}, Caps, Group)
when N == {record, diameter_base_ASR};
@@ -1406,11 +1426,11 @@ set(N, #diameter_packet{msg = Req}, Caps, Group)
#diameter_caps{origin_host = {OH, DH},
origin_realm = {OR, DR}}
= Caps,
- set(Group, Req, [{'Session-Id', diameter:session_id(OH)},
- {'Origin-Host', OH},
- {'Origin-Realm', OR},
- {'Destination-Host', DH},
- {'Destination-Realm', DR},
+ 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}]);
set(N, #diameter_packet{msg = Req}, Caps, Group)
@@ -1421,10 +1441,10 @@ set(N, #diameter_packet{msg = Req}, Caps, Group)
#diameter_caps{origin_host = {OH, _},
origin_realm = {OR, DR}}
= Caps,
- set(Group, 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]},
{'Auth-Application-Id', ?APP_ID}]);
set(N, #diameter_packet{msg = Req}, Caps, Group)
@@ -1435,11 +1455,11 @@ set(N, #diameter_packet{msg = Req}, Caps, Group)
#diameter_caps{origin_host = {OH, DH},
origin_realm = {OR, DR}}
= Caps,
- set(Group, Req, [{'Session-Id', diameter:session_id(OH)},
- {'Origin-Host', OH},
- {'Origin-Realm', OR},
- {'Destination-Host', DH},
- {'Destination-Realm', DR},
+ 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}]).
%% name/1