diff options
Diffstat (limited to 'lib/diameter/src/base/diameter_gen.erl')
-rw-r--r-- | lib/diameter/src/base/diameter_gen.erl | 985 |
1 files changed, 613 insertions, 372 deletions
diff --git a/lib/diameter/src/base/diameter_gen.erl b/lib/diameter/src/base/diameter_gen.erl index e832832876..6add06ea38 100644 --- a/lib/diameter/src/base/diameter_gen.erl +++ b/lib/diameter/src/base/diameter_gen.erl @@ -26,6 +26,14 @@ -module(diameter_gen). +-compile({inline, [incr/8, + incr/4, + field/1, + setopts/4, + avp_arity/5, + set_failed/2, + set_strict/3]}). + -export([encode_avps/3, decode_avps/3, grouped_avp/4, @@ -37,7 +45,7 @@ -define(THROW(T), throw({?MODULE, T})). -type parent_name() :: atom(). %% parent = Message or AVP --type parent_record() :: tuple(). %% +-type parent_record() :: tuple() | avp_values() | map(). -type avp_name() :: atom(). -type avp_record() :: tuple(). -type avp_values() :: [{avp_name(), term()}]. @@ -46,17 +54,21 @@ -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 %% --------------------------------------------------------------------------- --spec encode_avps(parent_name(), parent_record() | avp_values(), map()) +-spec encode_avps(parent_name(), parent_record(), map()) -> iolist() | no_return(). encode_avps(Name, Vals, #{module := Mod} = Opts) -> + Strict = mget(strict_arities, Opts, encode), try - encode(Name, Vals, Opts, Mod) + encode(Name, Vals, Opts, Strict, Mod) catch throw: {?MODULE, Reason} -> diameter_lib:log({encode, error}, @@ -73,128 +85,340 @@ encode_avps(Name, Vals, #{module := Mod} = Opts) -> erlang:error({encode_failure, Reason, Name, Stack}) end. -%% encode/4 +%% encode/5 -encode(Name, Vals, #{ordered_encode := false} = Opts, Mod) +encode(Name, Vals, Opts, Strict, Mod) when is_list(Vals) -> - lists:map(fun({F,V}) -> encode(Name, F, V, Opts, Mod) end, Vals); + case Opts of + #{ordered_encode := false} -> + lists:map(fun({F,V}) -> encode(Name, F, V, Opts, Strict, Mod) end, + Vals); + _ -> + Rec = Mod:'#set-'(Vals, newrec(Mod, Name)), + encode(Name, Rec, Opts, Strict, Mod) + end; -encode(Name, Vals, Opts, Mod) - when is_list(Vals) -> - encode(Name, Mod:'#set-'(Vals, newrec(Mod, Name)), Opts, Mod); +encode(Name, Map, Opts, Strict, Mod) + when is_map(Map) -> + [enc(F, A, V, Opts, Strict, Mod) || {F,A} <- Mod:avp_arity(Name), + V <- [mget(F, Map, undefined)]]; -encode(Name, Rec, Opts, Mod) -> - [encode(Name, F, V, Opts, Mod) || {F,V} <- Mod:'#get-'(Rec)]. +encode(Name, Rec, Opts, Strict, Mod) -> + [encode(Name, F, V, Opts, Strict, Mod) || {F,V} <- Mod:'#get-'(Rec)]. -%% encode/5 +%% encode/6 + +encode(_, AvpName, Values, Opts, Strict, Mod) + when Strict /= encode -> + enc(AvpName, ?ANY, Values, Opts, Strict, Mod); -encode(Name, AvpName, Values, Opts, Mod) -> - enc(Name, AvpName, Mod:avp_arity(Name, AvpName), Values, Opts, Mod). +encode(Name, AvpName, Values, Opts, Strict, Mod) -> + Arity = Mod:avp_arity(Name, AvpName), + enc(AvpName, Arity, Values, Opts, Strict, Mod). %% enc/6 -enc(_, AvpName, 1, undefined, _, _) -> +enc(AvpName, Arity, Values, Opts, Strict, Mod) + when Strict /= encode, Arity /= ?ANY -> + enc(AvpName, ?ANY, Values, Opts, Strict, Mod); + +enc(AvpName, 1, undefined, _, _, _) -> ?THROW([mandatory_avp_missing, AvpName]); -enc(Name, AvpName, 1, Value, Opts, Mod) -> - enc(Name, AvpName, [Value], Opts, Mod); +enc(AvpName, 1, Value, Opts, _, Mod) -> + H = avp_header(AvpName, Mod), + enc(AvpName, H, Value, Opts, Mod); -enc(_, _, {0,_}, [], _, _) -> +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(AvpName, Arity, V, Opts, Strict, Mod) + when not is_list(V) -> + enc(AvpName, Arity, [V], Opts, Strict, Mod); + +enc(AvpName, {Min, Max}, Values, Opts, Strict, Mod) -> + H = avp_header(AvpName, Mod), + enc(AvpName, H, Min, 0, Max, Values, Opts, Strict, Mod). -enc(_, AvpName, {Min, _}, L, _, _) - when length(L) < Min -> - ?THROW([repeated_avp_insufficient_arity, AvpName, Min, L]); +%% enc/9 -enc(_, AvpName, {_, Max}, L, _, _) - when Max < length(L) -> - ?THROW([repeated_avp_excessive_arity, AvpName, Max, L]); +enc(AvpName, H, Min, N, Max, Vs, Opts, Strict, Mod) + when Strict /= encode; + Max == '*', Min =< N -> + [enc(AvpName, H, V, Opts, Mod) || V <- Vs]; + +enc(AvpName, _, Min, N, _, [], _, _, _) + when N < Min -> + ?THROW([repeated_avp_insufficient_arity, AvpName, Min, N]); + +enc(_, _, _, _, _, [], _, _, _) -> + []; -enc(Name, AvpName, _, Values, Opts, Mod) -> - enc(Name, AvpName, Values, Opts, Mod). +enc(AvpName, _, _, N, Max, _, _, _, _) + when Max =< N -> + ?THROW([repeated_avp_excessive_arity, AvpName, Max]); + +enc(AvpName, H, Min, N, Max, [V|Vs], Opts, Strict, Mod) -> + [enc(AvpName, H, V, Opts, Mod) + | enc(AvpName, H, Min, N+1, Max, Vs, Opts, Strict, Mod)]. + +%% avp_header/2 + +avp_header('AVP', _) -> + false; + +avp_header(AvpName, Mod) -> + {_,_,_} = Mod:avp_header(AvpName). %% enc/5 -enc(Name, 'AVP', Values, Opts, Mod) -> - [enc_AVP(Name, A, Opts, Mod) || A <- Values]; +enc('AVP', false, Value, Opts, Mod) -> + enc_AVP(Value, Opts, Mod); -enc(_, AvpName, Values, Opts, Mod) -> - enc(AvpName, Values, Opts, Mod). +enc(AvpName, Hdr, Value, Opts, Mod) -> + enc1(AvpName, Hdr, Value, Opts, Mod). -%% enc/4 +%% enc1/5 -enc(AvpName, Values, Opts, Mod) -> - H = Mod:avp_header(AvpName), - [diameter_codec:pack_data(H, Mod:avp(encode, V, AvpName, Opts)) - || V <- Values]. +enc1(AvpName, {_,_,_} = Hdr, Value, Opts, Mod) -> + diameter_codec:pack_data(Hdr, Mod:avp(encode, Value, AvpName, Opts)). -%% enc_AVP/4 +%% enc1/6 + +enc1(AvpName, {_,_,_} = Hdr, Value, Opts, Mod, Dict) -> + diameter_codec:pack_data(Hdr, avp(encode, Value, AvpName, Opts, Mod, Dict)). + +%% enc_AVP/3 %% No value: assume AVP data is already encoded. The normal case will %% be when this is passed back from #diameter_packet.errors as a %% consequence of a failed decode. Any AVP can be encoded this way %% however, which side-steps any arity checks for known AVP's and %% could potentially encode something unfortunate. -enc_AVP(_, #diameter_avp{value = undefined} = A, Opts, _) -> +enc_AVP(#diameter_avp{value = undefined} = A, Opts, _) -> diameter_codec:pack_avp(A, Opts); -%% Missing name for value encode. -enc_AVP(_, #diameter_avp{name = N, value = V}, _, _) - when N == undefined; - N == 'AVP' -> - ?THROW([value_with_nameless_avp, N, V]); +%% Encode a name/value pair using an alternate dictionary if need be ... +enc_AVP(#diameter_avp{name = AvpName, value = Value}, Opts, Mod) -> + enc_AVP(AvpName, Value, Opts, Mod); +enc_AVP({AvpName, Value}, Opts, Mod) -> + enc_AVP(AvpName, Value, Opts, Mod); + +%% ... or with a specified dictionary. +enc_AVP({Dict, AvpName, Value}, Opts, Mod) -> + enc1(AvpName, Dict:avp_header(AvpName), Value, Opts, Mod, Dict). -%% Or not. Ensure that 'AVP' is the appropriate field. Note that if we -%% don't know this AVP at all then the encode will fail. -enc_AVP(Name, #diameter_avp{name = AvpName, value = Data}, Opts, Mod) -> - 0 == Mod:avp_arity(Name, AvpName) - orelse ?THROW([known_avp_as_AVP, Name, AvpName, Data]), - enc(AvpName, [Data], Opts, Mod); +%% Don't guard against anything being sent as a generic 'AVP', which +%% allows arity restrictions to be abused. -%% The backdoor ... -enc_AVP(_, {AvpName, Value}, Opts, Mod) -> - enc(AvpName, [Value], Opts, Mod); +%% enc_AVP/4 -%% ... and the side door. -enc_AVP(_Name, {_Dict, _AvpName, _Data} = T, Opts, _) -> - diameter_codec:pack_avp(#diameter_avp{data = T}, Opts). +enc_AVP(AvpName, Value, Opts, Mod) -> + try Mod:avp_header(AvpName) of + H -> + enc1(AvpName, H, Value, Opts, Mod) + catch + error: _ -> + Dicts = mget(avp_dictionaries, Opts, []), + enc_AVP(Dicts, AvpName, Value, Opts, Mod) + end. + +%% enc_AVP/5 + +enc_AVP([Dict | Rest], AvpName, Value, Opts, Mod) -> + try Dict:avp_header(AvpName) of + H -> + enc1(AvpName, H, Value, Opts, Mod, Dict) + catch + error: _ -> + enc_AVP(Rest, AvpName, Value, Opts, Mod) + end; + +enc_AVP([], AvpName, _, _, _) -> + ?THROW([no_dictionary, AvpName]). %% --------------------------------------------------------------------------- %% # decode_avps/3 %% --------------------------------------------------------------------------- --spec decode_avps(parent_name(), [#diameter_avp{}], map()) - -> {parent_record(), [avp()], Failed} +-spec decode_avps(parent_name(), binary(), map()) + -> {parent_record() | parent_name(), [avp()], Failed} when Failed :: [{5000..5999, #diameter_avp{}}]. -decode_avps(Name, Recs, #{module := Mod} = Opts) -> - {Avps, {Rec, Failed}} - = mapfoldl(fun(T,A) -> decode(Name, Opts, Mod, T, A) end, - {newrec(Mod, Name), []}, - Recs), - {Rec, Avps, Failed ++ missing(Rec, Name, Failed, Opts, Mod)}. -%% Append 5005 errors so that errors are reported in the order +decode_avps(Name, Bin, #{module := Mod, decode_format := Fmt} = Opts) -> + Strict = mget(strict_arities, Opts, decode), + [AM, Avps, Failed | Rec] + = decode(Bin, Name, Mod, Fmt, Strict, Opts, 0, #{}), + %% AM counts the number of top-level AVPs, which missing/5 then + %% uses when appending 5005 errors. + {reformat(Name, Rec, Strict, Mod, Fmt), + Avps, + Failed ++ missing(Name, Strict, Mod, Opts, AM)}. + +%% Append arity errors so that errors are reported in the order %% encountered. Failed-AVP should typically contain the first -%% encountered error accordg to the RFC. +%% error encountered. + +%% decode/8 + +decode(<<Code:32, V:1, M:1, P:1, _:5, Len:24, I:V/unit:32, Rest/binary>>, + Name, + Mod, + Fmt, + Strict, + Opts, + Idx, + AM) -> + decode(Rest, + Code, + if 1 == V -> I; true -> undefined end, + Len - 8 - 4*V, %% possibly negative, causing case match to fail + (4 - (Len rem 4)) rem 4, + 1 == M, + 1 == P, + Name, + Mod, + Fmt, + Strict, + Opts, + Idx, + AM); + +decode(<<>>, Name, Mod, Fmt, Strict, _, _, AM) -> + [AM, [], [] | newrec(Fmt, Mod, Name, Strict)]; + +decode(Bin, Name, Mod, Fmt, Strict, _, Idx, AM) -> + Avp = #diameter_avp{data = Bin, index = Idx}, + [AM, [Avp], [{5014, Avp}] | newrec(Fmt, Mod, Name, Strict)]. + +%% decode/14 + +decode(Bin, Code, Vid, DataLen, Pad, M, P, Name, Mod, Fmt, Strict, Opts0, + Idx, AM0) -> + case Bin of + <<Data:DataLen/binary, _:Pad/binary, T/binary>> -> + {NameT, Field, Arity, {I, AM}} + = incr(Name, Code, Vid, M, Mod, Strict, Opts0, AM0), + + Opts = setopts(NameT, Name, M, Opts0), + %% Not AvpName or else a failed Failed-AVP + %% decode is packed into 'AVP'. + + Avp = #diameter_avp{code = Code, + vendor_id = Vid, + is_mandatory = M, + need_encryption = P, + data = Data, + name = name(NameT), + type = type(NameT), + index = Idx}, + + Dec = dec(Data, Name, NameT, Mod, Fmt, Opts, Avp), + Acc = decode(T, Name, Mod, Fmt, Strict, Opts, Idx+1, AM),%% recurse + acc(Acc, Dec, I, Field, Arity, Strict, Mod, Opts); + _ -> + {NameT, _Field, _Arity, {_, AM}} + = incr(Name, Code, Vid, M, Mod, Strict, Opts0, AM0), + + Avp = #diameter_avp{code = Code, + vendor_id = Vid, + is_mandatory = M, + need_encryption = P, + data = Bin, + name = name(NameT), + type = type(NameT), + index = Idx}, + + [AM, [Avp], [{5014, Avp}] | newrec(Fmt, Mod, Name, Strict)] + end. + +%% incr/8 + +incr(Name, Code, Vid, M, Mod, Strict, Opts, AM0) -> + NameT = Mod:avp_name(Code, Vid), %% {AvpName, Type} | 'AVP' + Field = field(NameT), %% AvpName | 'AVP' + Arity = avp_arity(Name, Field, Mod, Opts, M), + if 0 == Arity, 'AVP' /= Field -> + A = pack_arity(Name, Field, Opts, Mod, M), + {NameT, 'AVP', A, incr('AVP', A, Strict, AM0)}; + true -> + {NameT, Field, Arity, incr(Field, Arity, Strict, AM0)} + end. -%% mapfoldl/3 +%% Data is a truncated header if command_code = undefined, otherwise +%% payload bytes. The former is padded to the length of a header if +%% the AVP reaches an outgoing encode. %% -%% Like lists:mapfoldl/3, but don't reverse the list. +%% RFC 6733 says that an AVP returned with 5014 can contain a minimal +%% payload for the AVP's type, but don't always know the type. -mapfoldl(F, Acc, List) -> - mapfoldl(F, Acc, List, []). +setopts('AVP', _, _, Opts) -> + Opts; -mapfoldl(F, Acc0, [T|Rest], List) -> - {B, Acc} = F(T, Acc0), - mapfoldl(F, Acc, Rest, [B|List]); -mapfoldl(_, Acc, [], List) -> - {List, Acc}. +setopts({_, Type}, Name, M, Opts) -> + set_failed(Name, set_strict(Type, M, Opts)). -%% 3588: +%% incr/4 + +incr(_, A, SA, AM) + when A == ?ANY; + A == 0; + SA /= decode -> + {undefined, AM}; + +incr(AvpName, _, _, AM) -> + case AM of + #{AvpName := N} -> + {N, AM#{AvpName => N+1}}; + _ -> + {0, AM#{AvpName => 1}} + end. + +%% mget/3 +%% +%% Measurably faster than maps:get/3. + +mget(Key, Map, Def) -> + case Map of + #{Key := V} -> + V; + _ -> + Def + end. + +%% name/1 + +name({Name, _}) -> + Name; +name(_) -> + undefined. + +%% type/1 + +type({_, Type}) -> + Type; +type(_) -> + undefined. + +%% missing/5 + +missing(Name, decode, Mod, Opts, AM) -> + [{5005, empty_avp(N, Opts, Mod)} || {N,A} <- Mod:avp_arity(Name), + N /= 'AVP', + Mn <- [min_arity(A)], + 0 < Mn, + mget(N, AM, 0) < Mn]; + +missing(_, _, _, _, _) -> + []. + +%% 3588/6733: %% %% DIAMETER_MISSING_AVP 5005 %% The request did not contain an AVP that is required by the Command @@ -204,57 +428,20 @@ mapfoldl(_, Acc, [], List) -> %% Vendor-Id if applicable. The value field of the missing AVP %% should be of correct minimum length and contain zeros. -missing(Rec, Name, Failed, Opts, Mod) -> - Avps = lists:foldl(fun({_, #diameter_avp{code = C, vendor_id = V}}, A) -> - maps:put({C,V}, true, A) - end, - maps:new(), - Failed), - missing(Mod:avp_arity(Name), tl(tuple_to_list(Rec)), Avps, Opts, Mod, []). - -missing([{Name, Arity} | As], [Value | Vs], Avps, Opts, Mod, Acc) -> - missing(As, - Vs, - Avps, - Opts, - Mod, - case - [H || missing_arity(Arity, Value), - {C,_,V} = H <- [Mod:avp_header(Name)], - not maps:is_key({C,V}, Avps)] - of - [H] -> - [{5005, empty_avp(Name, H, Opts, Mod)} | Acc]; - [] -> - Acc - end); - -missing([], [], _, _, _, Acc) -> - Acc. - -%% Maximum arities have already been checked in building the record. - -missing_arity(1, V) -> - V == undefined; -missing_arity({0, _}, _) -> - false; -missing_arity({1, _}, L) -> - [] == L; -missing_arity({Min, _}, L) -> - not has_prefix(Min, L). - -%% Compare a non-negative integer and the length of a list without -%% computing the length. -has_prefix(0, _) -> - true; -has_prefix(_, []) -> - false; -has_prefix(N, [_|L]) -> - has_prefix(N-1, L). +%% min_arity/1 -%% empty_avp/4 +min_arity(1) -> + 1; +min_arity({Mn,_}) -> + Mn. -empty_avp(Name, {Code, Flags, VId}, Opts, Mod) -> +%% 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), #diameter_avp{name = Name, code = Code, @@ -273,21 +460,22 @@ empty_avp(Name, {Code, Flags, VId}, Opts, Mod) -> %% specific errors that can be described by this AVP are described in %% the following section. -%% decode/5 +%% field/1 -decode(Name, - Opts, - Mod, - #diameter_avp{code = Code, vendor_id = Vid} - = Avp, - Acc) -> - decode(Name, Opts, Mod, Mod:avp_name(Code, Vid), Avp, Acc). +field({AvpName, _}) -> + AvpName; +field(_) -> + 'AVP'. + +%% dec/7 + +%% AVP not in dictionary: try an alternate. -%% decode/6 +dec(_, _, 'AVP', _Mod, none, _, Avp) -> %% none decode is no-op + Avp; -%% AVP not in dictionary. -decode(Name, Opts, Mod, 'AVP', Avp, Acc) -> - decode_AVP(Name, Avp, Opts, Mod, Acc); +dec(Data, Name, 'AVP', Mod, Fmt, Opts, Avp) -> + dec_AVP(dicts(Mod, Opts), Data, Name, Mod, Fmt, Opts, Avp); %% 6733, 4.4: %% @@ -336,130 +524,130 @@ 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) -> - #diameter_avp{data = Data, is_mandatory = M} - = Avp, +dec(Data, Name, {AvpName, Type}, Mod, Fmt, Opts, Avp) -> + #{app_dictionary := AppMod, failed_avp := Failed} + = Opts, - %% Whether or not to ignore an M-bit on an encapsulated AVP, or on - %% all AVPs with the service_opt() strict_mbit. - Opts1 = set_strict(Type, M, Opts0), + %% Reset the dictionary for best-effort decode of Failed-AVP. + Dict = if Failed -> AppMod; + true -> Mod + end, - %% Whether or not we're decoding within Failed-AVP and should - %% ignore decode errors. - #{dictionary := AppMod, failed_avp := Failed} - = Opts - = set_failed(Name, Opts1), %% Not AvpName or else a failed Failed-AVP - %% decode is packed into 'AVP'. + dec(Data, Name, AvpName, Type, Mod, Dict, Fmt, Failed, Opts, Avp). - %% Reset the dictionary for best-effort decode of Failed-AVP. - DecMod = if Failed -> - AppMod; - true -> - Mod - end, - - %% On decode, a Grouped AVP is represented as a #diameter_avp{} - %% list with AVP as head and component AVPs as tail. On encode, - %% data can be a list of component AVPs. - - try avp_decode(Data, AvpName, Opts, DecMod, Mod) of - {Rec, As} when Type == 'Grouped' -> - A = Avp#diameter_avp{name = AvpName, - value = Rec, - type = Type}, - {[A|As], pack_avp(Name, A, Opts, Mod, Acc)}; +%% dicts/2 - V when Type /= 'Grouped' -> - A = Avp#diameter_avp{name = AvpName, - value = V, - type = Type}, - {A, pack_avp(Name, A, Opts, Mod, Acc)} - catch - throw: {?MODULE, {grouped, Error, ComponentAvps}} -> - decode_error(Name, - Error, - ComponentAvps, - Opts, - Mod, - Avp#diameter_avp{name = AvpName, - data = trim(Avp#diameter_avp.data), - type = Type}, - Acc); +dicts(Mod, #{app_dictionary := Mod, avp_dictionaries := Dicts}) -> + Dicts; + +dicts(_, #{app_dictionary := Dict, avp_dictionaries := Dicts}) -> + [Dict | Dicts]; +dicts(Mod, #{app_dictionary := Mod}) -> + []; + +dicts(_, #{app_dictionary := Dict}) -> + [Dict]. + +%% dec/10 + +dec(Data, Name, AvpName, Type, Mod, Dict, Fmt, Failed, Opts, Avp) -> + try avp(decode, Data, AvpName, Opts, Mod, Dict) of + V -> + set(Type, Fmt, Avp, V) + catch + throw: {?MODULE, T} -> + decode_error(Failed, Fmt, T, Avp); error: Reason -> - decode_error(Name, - Reason, - Opts, - Mod, - Avp#diameter_avp{name = AvpName, - data = trim(Avp#diameter_avp.data), - type = Type}, - Acc) + decode_error(Failed, Reason, Name, Mod, Opts, Avp) end. -%% avp_decode/5 +%% dec_AVP/7 -avp_decode(Data, AvpName, Opts, Mod, Mod) -> - Mod:avp(decode, Data, AvpName, Opts); +dec_AVP([], _, _, _, _, _, Avp) -> + Avp; -avp_decode(Data, AvpName, Opts, Mod, _) -> - Mod:avp(decode, Data, AvpName, Opts, Mod). +dec_AVP(Dicts, Data, Name, Mod, Fmt, Opts, #diameter_avp{code = Code, + vendor_id = Vid} + = Avp) -> + dec_AVP(Dicts, Data, Name, Mod, Fmt, Opts, Code, Vid, Avp). -%% trim/1 +%% dec_AVP/9 %% -%% Remove any extra bit that was added in diameter_codec to induce a -%% 5014 error. +%% Try to decode an AVP in the first alternate dictionary that defines +%% it. + +dec_AVP([Dict | Rest], Data, Name, Mod, Fmt, Opts, Code, Vid, Avp) -> + case Dict:avp_name(Code, Vid) of + {AvpName, Type} -> + A = Avp#diameter_avp{name = AvpName, + type = Type}, + #{failed_avp := Failed} = Opts, + dec(Data, Name, AvpName, Type, Mod, Dict, Fmt, Failed, Opts, A); + _ -> + dec_AVP(Rest, Data, Name, Mod, Fmt, Opts, Code, Vid, Avp) + end; -trim(#diameter_avp{data = Data} = Avp) -> - Avp#diameter_avp{data = trim(Data)}; +dec_AVP([], _, _, _, _, _, _, _, Avp) -> + Avp. -trim({5014, Bin}) -> - Bin; +%% set/4 +%% +%% A Grouped AVP is represented as a #diameter_avp{} list with AVP +%% as head and component AVPs as tail. -trim(Avps) - when is_list(Avps) -> - lists:map(fun trim/1, Avps); +set('Grouped', none, Avp, V) -> + {_Rec, As} = V, + [Avp | As]; -trim(Avp) -> - Avp. +set('Grouped', _, Avp, V) -> + {Rec, As} = V, + [Avp#diameter_avp{value = Rec} | As]; -%% decode_error/7 +set(_, _, Avp, V) -> + Avp#diameter_avp{value = V}. -decode_error(Name, [_ | Rec], _, #{failed_avp := true} = Opts, Mod, Avp, Acc) -> - decode_AVP(Name, Avp#diameter_avp{value = Rec}, Opts, Mod, Acc); +%% decode_error/4 +%% +%% Error when decoding a grouped AVP. -decode_error(Name, _, _, #{failed_avp := true} = Opts, Mod, Avp, Acc) -> - decode_AVP(Name, Avp, Opts, Mod, Acc); +decode_error(true, none, _, Avp) -> + Avp; -decode_error(_, [Error | _], ComponentAvps, _, _, Avp, Acc) -> - decode_error(Error, Avp, Acc, ComponentAvps); +decode_error(true, _, {Rec, _, _}, Avp) -> + Avp#diameter_avp{value = Rec}; -decode_error(_, Error, ComponentAvps, _, _, Avp, Acc) -> - decode_error(Error, Avp, Acc, ComponentAvps). +decode_error(false, _, {_, ComponentAvps, [{RC,A} | _]}, Avp) -> + {RC, [Avp | ComponentAvps], Avp#diameter_avp{data = [A]}}. -%% decode_error/5 +%% decode_error/6 +%% +%% Error when decoding a non-grouped AVP. -decode_error(Name, _Reason, #{failed_avp := true} = Opts, Mod, Avp, Acc) -> - decode_AVP(Name, Avp, Opts, Mod, Acc); +decode_error(true, _, _, _, _, Avp) -> + Avp; -decode_error(Name, Reason, Opts, Mod, Avp, {Rec, Failed}) -> +decode_error(false, Reason, Name, Mod, Opts, Avp) -> Stack = diameter_lib:get_stacktrace(), diameter_lib:log(decode_error, ?MODULE, ?LINE, {Reason, Name, Avp#diameter_avp.name, Mod, Stack}), - {Avp, {Rec, [rc(Reason, Avp, Opts, Mod) | Failed]}}. + rc(Reason, Avp, Opts, Mod). -%% decode_error/4 +%% avp/6 -decode_error({RC, ErrorData}, Avp, {Rec, Failed}, ComponentAvps) -> - E = Avp#diameter_avp{data = [ErrorData]}, - {[Avp | trim(ComponentAvps)], {Rec, [{RC, E} | Failed]}}. +avp(T, Data, AvpName, Opts, Mod, Mod) -> + Mod:avp(T, Data, AvpName, Opts); -%% set_strict/3 +avp(T, Data, AvpName, Opts, _, Mod) -> + Mod:avp(T, Data, AvpName, Opts#{module := Mod}). +%% set_strict/3 +%% %% Set false as soon as we see a Grouped AVP that doesn't set the %% M-bit, to ignore the M-bit on an encapsulated AVP. + set_strict('Grouped', false = M, #{strict_mbit := true} = Opts) -> Opts#{strict_mbit := M}; set_strict(_, _, Opts) -> @@ -476,185 +664,207 @@ set_failed('Failed-AVP', #{failed_avp := false} = Opts) -> set_failed(_, Opts) -> Opts. -%% decode_AVP/5 -%% -%% 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, Opts, Mod, Acc) -> - {trim(Avp), pack_AVP(Name, Avp, Opts, Mod, Acc)}. - -%% rc/2 - -%% diameter_types will raise an error of this form to communicate -%% DIAMETER_INVALID_AVP_LENGTH (5014). A module specified to a -%% @custom_types tag in a dictionary file can also raise an error of -%% this form. -rc({'DIAMETER', 5014 = RC, _}, #diameter_avp{name = AvpName} = Avp, Opts, Mod) -> - {RC, Avp#diameter_avp{data = Mod:empty_value(AvpName, Opts)}}; +%% acc/8 + +acc([AM | Acc], As, I, Field, Arity, Strict, Mod, Opts) -> + [AM | acc1(Acc, As, I, Field, Arity, Strict, Mod, Opts)]. + +%% acc1/8 + +%% Faulty AVP, not grouped. +acc1(Acc, {_RC, Avp} = E, _, _, _, _, _, _) -> + [Avps, Failed | Rec] = Acc, + [[Avp | Avps], [E | Failed] | Rec]; + +%% Faulty component in grouped AVP. +acc1(Acc, {RC, As, Avp}, _, _, _, _, _, _) -> + [Avps, Failed | Rec] = Acc, + [[As | Avps], [{RC, Avp} | Failed] | Rec]; + +%% Grouped AVP ... +acc1([Avps | Acc], [Avp|_] = As, I, Field, Arity, Strict, Mod, Opts) -> + [[As|Avps] | acc2(Acc, Avp, I, Field, Arity, Strict, Mod, Opts)]; + +%% ... or not. +acc1([Avps | Acc], Avp, I, Field, Arity, Strict, Mod, Opts) -> + [[Avp|Avps] | acc2(Acc, Avp, I, Field, Arity, Strict, Mod, Opts)]. + +%% The component list of a Grouped AVP is discarded when packing into +%% the record (or equivalent): the values in an 'AVP' field are +%% diameter_avp records, not a list of records in the Grouped case, +%% and the decode into the value field is best-effort. The reason is +%% history more than logic: it would probably have made more sense to +%% retain the same structure as in diameter_packet.avps, but an 'AVP' +%% list has always been flat. + +%% acc2/8 + +%% No errors, but nowhere to pack. +acc2(Acc, Avp, _, 'AVP', 0, _, _, _) -> + [Failed | Rec] = Acc, + [[{rc(Avp), Avp} | Failed] | Rec]; + +%% Relaxed arities. +acc2(Acc, Avp, _, Field, Arity, Strict, Mod, _) + when Strict /= decode -> + pack(Arity, Field, Avp, Mod, Acc); + +%% No maximum arity. +acc2(Acc, Avp, _, Field, {_,'*'} = Arity, _, Mod, _) -> + pack(Arity, Field, Avp, Mod, Acc); + +%% Or check. +acc2(Acc, Avp, I, Field, Arity, _, Mod, _) -> + Mx = max_arity(Arity), + if Mx =< I -> + [Failed | Rec] = Acc, + [[{5009, Avp} | Failed] | Rec]; + true -> + pack(Arity, Field, Avp, Mod, Acc) + end. -%% 3588: +%% 3588/6733: %% -%% DIAMETER_INVALID_AVP_VALUE 5004 -%% The request contained an AVP with an invalid value in its data -%% portion. A Diameter message indicating this error MUST include -%% the offending AVPs within a Failed-AVP AVP. -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). +%% 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 -%% pack_avp/6 +%% max_arity/1 -pack_avp(Name, 0, Avp, Opts, Mod, Acc) -> - pack_AVP(Name, Avp, Opts, Mod, Acc); +max_arity(1) -> + 1; +max_arity({_,Mx}) -> + Mx. -pack_avp(_, Arity, #diameter_avp{name = AvpName} = Avp, _Opts, Mod, Acc) -> - pack(Arity, AvpName, Avp, Mod, Acc). +%% rc/1 -%% pack_AVP/5 +rc(#diameter_avp{is_mandatory = M}) -> + if M -> 5001; true -> 5008 end. -%% 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 -%% computed. +%% 3588: %% -%% Data is a truncated header if command_code = undefined, otherwise -%% payload bytes. The former is padded to the length of a header if -%% the AVP reaches an outgoing encode in diameter_codec. +%% DIAMETER_AVP_UNSUPPORTED 5001 +%% The peer received a message that contained an AVP that is not +%% recognized or supported and was marked with the Mandatory bit. A +%% Diameter message with this error MUST contain one or more Failed- +%% AVP AVP containing the AVPs that caused the failure. %% -%% RFC 6733 says that an AVP returned with 5014 can contain a minimal -%% 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) -> - {Rec, Failed} = Acc, - {Rec, [{RC, Avp#diameter_avp{data = Data}} | Failed]}; - -pack_AVP(Name, Avp, Opts, Mod, Acc) -> - pack_arity(Name, pack_arity(Name, Opts, Mod, Avp), Avp, Mod, Acc). +%% DIAMETER_AVP_NOT_ALLOWED 5008 +%% A message was received with an AVP that MUST NOT be present. The +%% Failed-AVP AVP MUST be included and contain a copy of the +%% offending AVP. %% pack_arity/5 -pack_arity(_, 0, #diameter_avp{is_mandatory = M} = Avp, _, Acc) -> - {Rec, Failed} = Acc, - {Rec, [{if M -> 5001; true -> 5008 end, Avp} | Failed]}; - -pack_arity(_, Arity, Avp, Mod, Acc) -> - pack(Arity, 'AVP', Avp, Mod, Acc). - %% 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_arity(Name, AvpName, _, Mod, M) + when Name == 'Failed-AVP'; + Name == 'answer-message', AvpName == 'Failed-AVP'; + not M -> + Mod:avp_arity(Name, 'AVP'); +%% Not testing just Name /= 'Failed-AVP' means we're changing the +%% packing of AVPs nested within Failed-AVP, but the point of +%% ignoring errors within Failed-AVP is to decode as much as +%% possible, and failing because a mandatory AVP couldn't be +%% packed into a dedicated field defeats that point. - %% Not testing just Name /= 'Failed-AVP' means we're changing the - %% packing of AVPs nested within Failed-AVP, but the point of - %% ignoring errors within Failed-AVP is to decode as much as - %% possible, and failing because a mandatory AVP couldn't be - %% packed into a dedicated field defeats that point. +pack_arity(Name, _, #{strict_mbit := Strict, failed_avp := Failed}, Mod, _) + when not Strict; + Failed -> + Mod:avp_arity(Name, 'AVP'); - if Failed == true; - Name == 'Failed-AVP'; - Name == 'answer-message', AvpName == 'Failed-AVP'; - not M; - not Strict -> - Mod:avp_arity(Name, 'AVP'); - true -> - 0 - end. +pack_arity(_, _, _, _, _) -> + 0. + +%% avp_arity/5 + +avp_arity(Name, 'AVP' = AvpName, Mod, Opts, M) -> + pack_arity(Name, AvpName, Opts, Mod, M); + +avp_arity(Name, AvpName, Mod, _, _) -> + Mod:avp_arity(Name, AvpName). + +%% rc/4 + +%% Length error communicated from diameter_types or a +%% @custom_types/@codecs module. +rc({'DIAMETER', 5014 = RC, _}, #diameter_avp{name = AvpName} = A, Opts, Mod) -> + {RC, A#diameter_avp{data = Mod:empty_value(AvpName, Opts)}}; %% 3588: %% -%% DIAMETER_AVP_UNSUPPORTED 5001 -%% The peer received a message that contained an AVP that is not -%% recognized or supported and was marked with the Mandatory bit. A -%% Diameter message with this error MUST contain one or more Failed- -%% AVP AVP containing the AVPs that caused the failure. -%% -%% DIAMETER_AVP_NOT_ALLOWED 5008 -%% A message was received with an AVP that MUST NOT be present. The -%% Failed-AVP AVP MUST be included and contain a copy of the -%% offending AVP. +%% DIAMETER_INVALID_AVP_VALUE 5004 +%% The request contained an AVP with an invalid value in its data +%% portion. A Diameter message indicating this error MUST include +%% the offending AVPs within a Failed-AVP AVP. +rc(_, Avp, _, _) -> + {5004, Avp}. %% pack/5 -pack(Arity, FieldName, Avp, Mod, {Rec, _} = Acc) -> - pack(Mod:'#get-'(FieldName, Rec), Arity, FieldName, Avp, Mod, Acc). +pack(Arity, F, Avp, Mod, [Failed | Rec]) -> + [Failed | set(Arity, F, value(F, Avp), Mod, Rec)]. -%% pack/6 +%% set/5 -pack(undefined, 1, 'AVP' = F, Avp, Mod, {Rec, Failed}) -> %% unlikely - {Mod:'#set-'({F, Avp}, Rec), Failed}; +set(_, _, _, _, None) + when is_atom(None) -> + None; -pack(undefined, 1, F, #diameter_avp{value = V}, Mod, {Rec, Failed}) -> - {Mod:'#set-'({F, V}, Rec), Failed}; +set(1, F, Value, _, Map) + when is_map(Map) -> + Map#{F => Value}; -%% 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 -%% +set(_, F, V, _, Map) + when is_map(Map) -> + maps:update_with(F, fun(Vs) -> [V|Vs] end, [V], Map); -pack(_, 1, _, Avp, _, {Rec, Failed}) -> - {Rec, [{5009, Avp} | Failed]}; - -pack(L, {_, Max}, F, Avp, Mod, {Rec, Failed}) -> - case '*' /= Max andalso has_prefix(Max+1, L) of - true -> - {Rec, [{5009, Avp} | Failed]}; - false when F == 'AVP' -> - {Mod:'#set-'({F, [Avp | L]}, Rec), Failed}; - false -> - {Mod:'#set-'({F, [Avp#diameter_avp.value | L]}, Rec), Failed} - end. +set(1, F, Value, Mod, Rec) -> + Mod:'#set-'({F, Value}, Rec); + +set(_, F, V, Mod, Rec) -> + Vs = Mod:'#get-'(F, Rec), + Mod:'#set-'({F, [V|Vs]}, Rec). + +%% value/2 + +value('AVP', Avp) -> + Avp; + +value(_, #diameter_avp{value = V}) -> + V. %% --------------------------------------------------------------------------- %% # grouped_avp/3 %% --------------------------------------------------------------------------- --spec grouped_avp(decode, avp_name(), binary() | {5014, binary()}, term()) +%% Note that Grouped is the only AVP type that doesn't just return a +%% decoded value, also returning the list of component diameter_avp +%% records. + +-spec grouped_avp(decode, avp_name(), binary(), term()) -> {avp_record(), [avp()]}; (encode, avp_name(), avp_record() | avp_values(), term()) -> iolist() | no_return(). -%% Length error induced by diameter_codec:collect_avps/1: the AVP -%% length in the header was too short (insufficient for the extracted -%% header) or too long (past the end of the message). An empty payload -%% is sufficient according to the RFC text for 5014. -grouped_avp(decode, _Name, {5014 = RC, _Bin}, _) -> - ?THROW({grouped, {RC, []}, []}); - -grouped_avp(decode, Name, Data, Opts) -> - grouped_decode(Name, diameter_codec:collect_avps(Data), Opts); +%% An error in decoding a component AVP throws the first faulty +%% component, which a catch wraps in the Grouped AVP in question. A +%% partially decoded record is only used when ignoring errors in +%% Failed-AVP. +grouped_avp(decode, Name, Bin, Opts) -> + {Rec, Avps, Es} = T = decode_avps(Name, Bin, Opts), + [] == Es orelse ?THROW(T), + {Rec, Avps}; grouped_avp(encode, Name, Data, Opts) -> encode_avps(Name, Data, Opts). -%% grouped_decode/2 -%% -%% Note that Grouped is the only AVP type that doesn't just return a -%% decoded value, also returning the list of component diameter_avp -%% records. - -%% Length error in trailing component AVP. -grouped_decode(_Name, {Error, Acc}, _) -> - {5014, Avp} = Error, - ?THROW({grouped, Error, [Avp | Acc]}); - %% 7.5. Failed-AVP AVP %% In the case where the offending AVP is embedded within a Grouped AVP, @@ -665,15 +875,6 @@ grouped_decode(_Name, {Error, Acc}, _) -> %% to the single offending AVP. This enables the recipient to detect %% the location of the offending AVP when embedded in a group. -%% An error in decoding a component AVP throws the first faulty -%% component, which the catch in d/3 wraps in the Grouped AVP in -%% question. A partially decoded record is only used when ignoring -%% errors in Failed-AVP. -grouped_decode(Name, ComponentAvps, Opts) -> - {Rec, Avps, Es} = decode_avps(Name, ComponentAvps, Opts), - [] == Es orelse ?THROW({grouped, [{_,_} = hd(Es) | Rec], Avps}), - {Rec, Avps}. - %% --------------------------------------------------------------------------- %% # empty_group/2 %% --------------------------------------------------------------------------- @@ -705,5 +906,45 @@ empty(Name, #{module := Mod} = Opts) -> %% ------------------------------------------------------------------------------ +%% newrec/4 + +newrec(none, _, Name, _) -> + Name; + +newrec(record, Mod, Name, 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/2 + newrec(Mod, Name) -> Mod:'#new-'(Mod:name2rec(Name)). + +%% reformat/5 + +reformat(Name, Map, _Strict, Mod, list) -> + [{F,V} || {F,_} <- Mod:avp_arity(Name), #{F := V} <- [Map]]; + +reformat(Name, Map, Strict, Mod, record_from_map) -> + RecName = Mod:name2rec(Name), + list_to_tuple([RecName | [mget(F, Map, def(A, Strict)) + || {F,A} <- Mod:avp_arity(Name)]]); + +reformat(_, Rec, _, _, _) -> + Rec. + +%% def/2 + +def(1, decode) -> + undefined; + +def(_, _) -> + []. |