%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2010-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% %% %% This file contains code that encode/decode modules generated by %% diameter_codegen.erl calls to implement the functionality. This %% code does most of the work, the generated code being kept simple. %% -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, empty_group/2, empty/2]). -include_lib("diameter/include/diameter.hrl"). -define(THROW(T), throw({?MODULE, T})). -type parent_name() :: atom(). %% parent = Message or AVP -type parent_record() :: tuple() | avp_values() | map(). -type avp_name() :: atom(). -type avp_record() :: tuple(). -type avp_values() :: [{avp_name(), term()}]. -type non_grouped_avp() :: #diameter_avp{}. -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(), map()) -> iolist() | no_return(). encode_avps(Name, Vals, #{module := Mod} = Opts) -> Strict = mget(strict_arities, Opts, encode), try encode(Name, Vals, Opts, Strict, Mod) catch throw: {?MODULE, Reason} -> diameter_lib:log({encode, error}, ?MODULE, ?LINE, {Reason, Name, Vals, Mod}), erlang:error(list_to_tuple(Reason ++ [Name])); error: Reason -> Stack = erlang:get_stacktrace(), diameter_lib:log({encode, failure}, ?MODULE, ?LINE, {Reason, Name, Vals, Mod, Stack}), erlang:error({encode_failure, Reason, Name, Stack}) end. %% encode/5 encode(Name, Vals, Opts, Strict, Mod) when is_list(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, 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, Strict, Mod) -> [encode(Name, F, V, Opts, Strict, Mod) || {F,V} <- Mod:'#get-'(Rec)]. %% encode/6 encode(_, AvpName, Values, Opts, Strict, Mod) when Strict /= encode -> enc(AvpName, ?ANY, Values, Opts, Strict, 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, 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(AvpName, 1, Value, Opts, _, Mod) -> H = avp_header(AvpName, Mod), enc(AvpName, H, Value, Opts, Mod); enc(_, {0,_}, [], _, _, _) -> []; 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/9 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(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('AVP', false, Value, Opts, Mod) -> enc_AVP(Value, Opts, Mod); enc(AvpName, Hdr, Value, Opts, Mod) -> enc1(AvpName, Hdr, Value, Opts, Mod). %% enc1/5 enc1(AvpName, {_,_,_} = Hdr, Value, Opts, Mod) -> diameter_codec:pack_data(Hdr, Mod:avp(encode, Value, AvpName, Opts)). %% 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, _) -> diameter_codec:pack_avp(A, Opts); %% 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). %% Don't guard against anything being sent as a generic 'AVP', which %% allows arity restrictions to be abused. %% enc_AVP/4 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(), binary(), map()) -> {parent_record() | parent_name(), [avp()], Failed} when Failed :: [{5000..5999, #diameter_avp{}}]. 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 %% error encountered. %% decode/8 decode(<>, 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 <> -> {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, Opts0, Idx+1, AM),%% recurse acc(Acc, Dec, I, Field, Arity, Strict, Mod); _ -> {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. %% 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. %% %% 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. setopts('AVP', _, _, Opts) -> Opts; setopts({_, Type}, Name, M, Opts) -> set_failed(Name, set_strict(Type, M, Opts)). %% 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 %% Code definition. If this value is sent in the Result-Code AVP, a %% Failed-AVP AVP SHOULD be included in the message. The Failed-AVP %% 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. %% min_arity/1 min_arity(1) -> 1; min_arity({Mn,_}) -> Mn. %% 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, vendor_id = VId, is_mandatory = 0 /= (Flags band 2#01000000), need_encryption = 0 /= (Flags band 2#00100000), data = Mod:empty_value(Name, Opts), type = Type}. %% 3588, ch 7: %% %% The Result-Code AVP describes the error that the Diameter node %% encountered in its processing. In case there are multiple errors, %% the Diameter node MUST report only the first error it encountered %% (detected possibly in some implementation dependent order). The %% specific errors that can be described by this AVP are described in %% the following section. %% field/1 field({AvpName, _}) -> AvpName; field(_) -> 'AVP'. %% dec/7 %% AVP not in dictionary: try an alternate. dec(Data, Name, 'AVP', Mod, Fmt, Opts, Avp) -> dec_AVP(dicts(Mod, Opts), Data, Name, Mod, Fmt, Opts, Avp); %% 6733, 4.4: %% %% Receivers of a Grouped AVP that does not have the 'M' (mandatory) %% bit set and one or more of the encapsulated AVPs within the group %% has the 'M' (mandatory) bit set MAY simply be ignored if the %% Grouped AVP itself is unrecognized. The rule applies even if the %% encapsulated AVP with its 'M' (mandatory) bit set is further %% encapsulated within other sub-groups, i.e., other Grouped AVPs %% embedded within the Grouped AVP. %% %% The first sentence is slightly mangled, but take it to mean this: %% %% An unrecognized AVP of type Grouped that does not set the 'M' bit %% MAY be ignored even if one of its encapsulated AVPs sets the 'M' %% bit. %% %% The text above is a change from RFC 3588, which instead says this: %% %% Further, if any of the AVPs encapsulated within a Grouped AVP has %% the 'M' (mandatory) bit set, the Grouped AVP itself MUST also %% include the 'M' bit set. %% %% Both of these texts have problems. If the AVP is unknown then its %% type is unknown since the type isn't sent over the wire, so the %% 6733 text becomes a non-statement: don't know that the AVP not %% setting the M-bit is of type Grouped, therefore can't know that its %% data consists of encapsulated AVPs, therefore can't but ignore that %% one of these might set the M-bit. It should be no worse if we know %% the AVP to have type Grouped. %% %% Similarly, for the 3588 text: if we receive an AVP that doesn't set %% the M-bit and don't know that the AVP has type Grouped then we %% can't realize that its data contains an AVP that sets the M-bit, so %% can't regard the AVP as erroneous on this account. Again, it should %% be no worse if the type is known to be Grouped, but in this case %% the RFC forces us to regard the AVP as erroneous. This is %% inconsistent, and the 3588 text has never been enforced. %% %% So, if an AVP doesn't set the M-bit then we're free to ignore it, %% regardless of the AVP's type. If we know the type to be Grouped %% then we must ignore the M-bit on an encapsulated AVP. That means %% packing such an encapsulated AVP into an 'AVP' field if need be, %% not regarding the lack of a specific field as an error as is %% otherwise the case. (The lack of an AVP-specific field being how we %% defined the RFC's "unrecognized", which is slightly stronger than %% "not defined".) dec(Data, Name, {AvpName, Type}, Mod, Fmt, Opts, Avp) -> #{app_dictionary := AppMod, failed_avp := Failed} = Opts, %% Reset the dictionary for best-effort decode of Failed-AVP. Dict = if Failed -> AppMod; true -> Mod end, dec(Data, Name, AvpName, Type, Mod, Dict, Fmt, Failed, Opts, Avp). %% dicts/2 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(Failed, Reason, Name, Mod, Opts, Avp) end. %% dec_AVP/7 dec_AVP([], _, _, _, _, _, Avp) -> Avp; 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). %% dec_AVP/9 %% %% Try to decode an AVP in the first alternate dictionary that defines %% it. dec_AVP([Dict | Rest], Data, Name, Mod, Fmt, Opts0, Code, Vid, Avp) -> case Dict:avp_name(Code, Vid) of {AvpName, Type} = NameT -> A = Avp#diameter_avp{name = AvpName, type = Type}, #{failed_avp := Failed} = Opts = setopts(NameT, Name, Avp#diameter_avp.is_mandatory, Opts0), dec(Data, Name, AvpName, Type, Mod, Dict, Fmt, Failed, Opts, A); _ -> dec_AVP(Rest, Data, Name, Mod, Fmt, Opts0, Code, Vid, Avp) end; dec_AVP([], _, _, _, _, _, _, _, Avp) -> Avp. %% set/4 %% %% A Grouped AVP is represented as a #diameter_avp{} list with AVP %% as head and component AVPs as tail. set('Grouped', Fmt, Avp, V) -> {Rec, As} = V, [set(Fmt, Avp, Rec) | As]; set(_, _, Avp, V) -> Avp#diameter_avp{value = V}. %% decode_error/4 %% %% Error when decoding a grouped AVP. %% Ignoring errors in Failed-AVP. decode_error(true, Fmt, {Rec, ComponentAvps, _Errors}, Avp) -> [set(Fmt, Avp, Rec) | ComponentAvps]; %% Or not. A faulty component is encoded by itself in Failed-AVP, as %% suggested by 7.5 of RFC 6733 (quoted below), so that errors are %% reported unambigiously. decode_error(false, _, {_, ComponentAvps, [{RC,A} | _]}, Avp) -> {RC, [Avp | ComponentAvps], Avp#diameter_avp{data = [A]}}. %% set/3 set(none, Avp, _Name) -> Avp; set(_, Avp, Rec) -> Avp#diameter_avp{value = Rec}. %% decode_error/6 %% %% Error when decoding a non-grouped AVP. decode_error(true, _, _, _, _, Avp) -> Avp; 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}), case Reason of {'DIAMETER', 5014 = RC, _} -> %% Length error communicated from diameter_types or a %% @custom_types/@codecs module. AvpName = Avp#diameter_avp.name, {RC, Avp#diameter_avp{data = Mod:empty_value(AvpName, Opts)}}; _ -> {5004, Avp} end. %% 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. %% avp/6 avp(T, Data, AvpName, Opts, Mod, Mod) -> Mod:avp(T, Data, AvpName, Opts); 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) -> Opts. %% set_failed/2 %% %% Set true as soon as we see Failed-AVP. Matching on 'Failed-AVP' %% assumes that this is the RFC AVP. Strictly, this doesn't need to be %% the case. set_failed('Failed-AVP', #{failed_avp := false} = Opts) -> Opts#{failed_avp := true}; set_failed(_, Opts) -> Opts. %% acc/7 acc([AM | Acc], As, I, Field, Arity, Strict, Mod) -> [AM | acc1(Acc, As, I, Field, Arity, Strict, Mod)]. %% acc1/7 %% 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) -> [[As|Avps] | acc2(Acc, Avp, I, Field, Arity, Strict, Mod)]; %% ... or not. acc1([Avps | Acc], Avp, I, Field, Arity, Strict, Mod) -> [[Avp|Avps] | acc2(Acc, Avp, I, Field, Arity, Strict, Mod)]. %% 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/7 %% 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/6733: %% %% 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 %% max_arity/1 max_arity(1) -> 1; max_arity({_,Mx}) -> Mx. %% rc/1 rc(#diameter_avp{is_mandatory = M}) -> if M -> 5001; true -> 5008 end. %% 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. %% pack_arity/5 %% 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, 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. pack_arity(Name, _, #{strict_mbit := Strict, failed_avp := Failed}, Mod, _) when not Strict; Failed -> Mod:avp_arity(Name, 'AVP'); 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). %% pack/5 pack(Arity, F, Avp, Mod, [Failed | Rec]) -> [Failed | set(Arity, F, value(F, Avp), Mod, Rec)]. %% set/5 set(_, _, _, _, Name) when is_atom(Name) -> Name; set(1, F, Value, _, Map) when is_map(Map) -> Map#{F => Value}; set(_, F, V, _, Map) when is_map(Map) -> maps:update_with(F, fun(Vs) -> [V|Vs] end, [V], Map); 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 %% --------------------------------------------------------------------------- %% 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(). %% 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). %% 7.5. Failed-AVP AVP %% In the case where the offending AVP is embedded within a Grouped AVP, %% the Failed-AVP MAY contain the grouped AVP, which in turn contains %% the single offending AVP. The same method MAY be employed if the %% grouped AVP itself is embedded in yet another grouped AVP and so on. %% In this case, the Failed-AVP MAY contain the grouped AVP hierarchy up %% to the single offending AVP. This enables the recipient to detect %% the location of the offending AVP when embedded in a group. %% --------------------------------------------------------------------------- %% # empty_group/2 %% --------------------------------------------------------------------------- empty_group(Name, #{module := Mod} = Opts) -> list_to_binary([z(F, A, Opts, Mod) || {F,A} <- Mod:avp_arity(Name)]). z(Name, 1, Opts, Mod) -> z(Name, Opts, Mod); z(_, {0,_}, _, _) -> []; z(Name, {Min, _}, Opts, Mod) -> binary:copy(z(Name, Opts, Mod), Min). z('AVP', _, _) -> <<0:64>>; %% minimal header z(Name, Opts, Mod) -> Bin = diameter_codec:pack_data(Mod:avp_header(Name), Mod:empty_value(Name, Opts)), Sz = iolist_size(Bin), <<0:Sz/unit:8>>. %% --------------------------------------------------------------------------- %% # empty/2 %% --------------------------------------------------------------------------- empty(Name, #{module := Mod} = Opts) -> Mod:avp(encode, zero, Name, 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(_, _) -> [].