%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2010-2017. 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). -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(). %% -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(). %% --------------------------------------------------------------------------- %% # encode_avps/3 %% --------------------------------------------------------------------------- -spec encode_avps(parent_name(), parent_record() | avp_values(), map()) -> iolist() | no_return(). encode_avps(Name, Vals, #{module := Mod} = Opts) -> try encode(Name, Vals, Opts, 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/4 encode(Name, Vals, #{ordered_encode := false} = Opts, Mod) when is_list(Vals) -> lists:map(fun({F,V}) -> encode(Name, F, V, Opts, Mod) end, Vals); encode(Name, Vals, Opts, Mod) when is_list(Vals) -> encode(Name, Mod:'#set-'(Vals, newrec(Mod, Name)), Opts, Mod); encode(Name, Rec, Opts, Mod) -> [encode(Name, F, V, Opts, Mod) || {F,V} <- Mod:'#get-'(Rec)]. %% encode/5 encode(Name, AvpName, Values, Opts, Mod) -> enc(Name, AvpName, Mod:avp_arity(Name, AvpName), Values, Opts, Mod). %% enc/6 enc(_, AvpName, 1, undefined, _, _) -> ?THROW([mandatory_avp_missing, AvpName]); enc(Name, AvpName, 1, Value, Opts, Mod) -> enc(Name, AvpName, [Value], Opts, Mod); enc(_, _, {0,_}, [], _, _) -> []; enc(_, AvpName, _, T, _, _) when not is_list(T) -> ?THROW([repeated_avp_as_non_list, AvpName, T]); enc(_, AvpName, {Min, _}, L, _, _) when length(L) < Min -> ?THROW([repeated_avp_insufficient_arity, AvpName, Min, L]); enc(_, AvpName, {_, Max}, L, _, _) when Max < length(L) -> ?THROW([repeated_avp_excessive_arity, AvpName, Max, L]); enc(Name, AvpName, _, Values, Opts, Mod) -> enc(Name, AvpName, Values, Opts, Mod). %% enc/5 enc(Name, 'AVP', Values, Opts, Mod) -> [enc_AVP(Name, A, Opts, Mod) || A <- Values]; enc(_, AvpName, Values, Opts, Mod) -> enc(AvpName, Values, Opts, Mod). %% enc/4 enc(AvpName, Values, Opts, Mod) -> H = Mod:avp_header(AvpName), [diameter_codec:pack_data(H, Mod:avp(encode, V, AvpName, Opts)) || V <- Values]. %% enc_AVP/4 %% 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); %% 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]); %% 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); %% The backdoor ... enc_AVP(_, {AvpName, Value}, Opts, Mod) -> enc(AvpName, [Value], Opts, Mod); %% ... and the side door. enc_AVP(_Name, {_Dict, _AvpName, _Data} = T, Opts, _) -> diameter_codec:pack_avp(#diameter_avp{data = T}, Opts). %% --------------------------------------------------------------------------- %% # decode_avps/3 %% --------------------------------------------------------------------------- -spec decode_avps(parent_name(), [#diameter_avp{}], map()) -> {parent_record(), [avp()], Failed} when Failed :: [{5000..5999, #diameter_avp{}}]. decode_avps(Name, Recs, #{module := Mod} = Opts) -> {Avps, {Rec, AM, Failed}} = mapfoldl(fun(T,A) -> decode(Name, Opts, Mod, T, A) end, {newrec(Mod, Name), #{}, []}, Recs), %% AM counts the number of top-level AVPs, which missing/4 then %% uses when adding 5005 errors. {Rec, Avps, Failed ++ missing(Name, Opts, Mod, AM)}. %% Append 5005 errors so that errors are reported in the order %% encountered. Failed-AVP should typically contain the first %% error encountered. %% mapfoldl/3 %% %% Like lists:mapfoldl/3, but don't reverse the list. mapfoldl(F, Acc, List) -> mapfoldl(F, Acc, List, []). mapfoldl(F, Acc0, [T|Rest], List) -> {B, Acc} = F(T, Acc0), mapfoldl(F, Acc, Rest, [B|List]); mapfoldl(_, Acc, [], List) -> {List, Acc}. %% missing/4 %% 3588: %% %% 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. missing(Name, Opts, Mod, AM) -> lists:foldl(fun(T,A) -> missing(T, AM, Opts, Mod, A) end, [], Mod:avp_arity(Name)). %% missing/5 missing({Name, Arity}, AM, Opts, Mod, Acc) -> case missing(Name, Arity, AM) of true -> [{5005, empty_avp(Name, Opts, Mod)} | Acc]; false -> Acc end. %% missing/3 missing(Name, Arity, AM) -> 'AVP' /= Name andalso too_few(Name, Arity, AM). %% too_few/3 %% %% Maximum arities have already been checked during the decode. too_few(_, {0, _}, _) -> false; too_few(FieldName, 1, AM) -> not maps:is_key(FieldName, AM); too_few(FieldName, {M, _}, AM) -> maps:get(FieldName, AM, 0) < M. %% empty_avp/3 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. %% 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}). %% field/1 field({AvpName, _Type}) -> AvpName; field('AVP' = A) -> A. %% incr/2 incr(Key, Map) -> maps:update_with(Key, fun incr/1, 1, Map). %% incr/1 incr(N) -> N + 1. %% decode/6 %% AVP not in dictionary. decode(Name, Opts, Mod, 'AVP', Avp, Acc) -> decode_AVP(Name, Avp, Opts, Mod, Acc); %% 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".) decode(Name, Opts0, Mod, {AvpName, Type}, Avp, Acc) -> #diameter_avp{data = Data, is_mandatory = M} = Avp, %% 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), %% 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'. %% Reset the dictionary for best-effort decode of Failed-AVP. DecMod = if Failed -> AppMod; true -> Mod end, %% 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)}; 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); error: Reason -> decode_error(Name, Reason, Opts, Mod, Avp#diameter_avp{name = AvpName, data = trim(Avp#diameter_avp.data), type = Type}, Acc) end. %% avp_decode/5 avp_decode(Data, AvpName, Opts, Mod, Mod) -> Mod:avp(decode, Data, AvpName, Opts); avp_decode(Data, AvpName, Opts, Mod, _) -> Mod:avp(decode, Data, AvpName, Opts, Mod). %% trim/1 %% %% Remove any extra bit that was added in diameter_codec to induce a %% 5014 error. trim(#diameter_avp{data = Data} = Avp) -> Avp#diameter_avp{data = trim(Data)}; trim({5014, Bin}) -> Bin; trim(Avps) when is_list(Avps) -> lists:map(fun trim/1, Avps); trim(Avp) -> Avp. %% decode_error/7 decode_error(Name, [_|Rec], _, #{failed_avp := true} = Opts, Mod, Avp, Acc) -> decode_AVP(Name, Avp#diameter_avp{value = Rec}, Opts, Mod, Acc); decode_error(Name, _, _, #{failed_avp := true} = Opts, Mod, Avp, Acc) -> decode_AVP(Name, Avp, Opts, Mod, Acc); decode_error(_, [Error | _], ComponentAvps, _, _, Avp, Acc) -> decode_error(Error, Avp, Acc, ComponentAvps); decode_error(_, Error, ComponentAvps, _, _, Avp, Acc) -> decode_error(Error, Avp, Acc, ComponentAvps). %% decode_error/6 decode_error(Name, _Reason, #{failed_avp := true} = Opts, Mod, Avp, Acc) -> decode_AVP(Name, Avp, Opts, Mod, Acc); decode_error(Name, Reason, Opts, Mod, Avp, {Rec, AM, Failed}) -> Stack = diameter_lib:get_stacktrace(), AvpName = Avp#diameter_avp.name, diameter_lib:log(decode_error, ?MODULE, ?LINE, {Reason, Name, AvpName, Mod, Stack}), {Avp, {Rec, AM, [rc(Reason, Avp, Opts, Mod) | Failed]}}. %% decode_error/4 decode_error({RC, ErrorData}, Avp, {Rec, AM, Failed}, ComponentAvps) -> E = Avp#diameter_avp{data = [ErrorData]}, {[Avp | trim(ComponentAvps)], {Rec, AM, [{RC, E} | Failed]}}. %% 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. %% 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} = A, Opts, Mod) -> {RC, A#diameter_avp{data = Mod:empty_value(AvpName, Opts)}}; %% 3588: %% %% 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). %% pack_avp/6 pack_avp(Name, 0, Avp, Opts, Mod, Acc) -> pack_AVP(Name, Avp, Opts, Mod, Acc); pack_avp(_, Arity, #diameter_avp{name = AvpName} = Avp, _Opts, Mod, Acc) -> pack(Arity, AvpName, Avp, Mod, Acc). %% pack_AVP/5 %% 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. %% %% 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. %% %% 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, 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 -> M = Avp#diameter_avp.is_mandatory, {Rec, AM, Failed} = Acc, {Rec, AM, [{if M -> 5001; true -> 5008 end, Avp} | Failed]}; true -> pack(Arity, 'AVP', Avp, Mod, Acc) 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/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}) -> %% 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. 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/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/5 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()) -> {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); 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, %% 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. %% 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 %% --------------------------------------------------------------------------- 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(Mod, Name) -> Mod:'#new-'(Mod:name2rec(Name)).