%%
%% %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().
%% 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(),
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, 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, 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]);
enc(Name, AvpName, 1, Value, Opts, Mod) ->
H = avp_header(AvpName, Mod),
enc1(Name, AvpName, H, Value, Opts, Mod);
enc(_, _, {0,_}, [], _, _) ->
[];
enc(_, _, _, undefined, _, _) ->
[];
enc(_, AvpName, _, T, _, _)
when not is_list(T) ->
?THROW([repeated_avp_as_non_list, AvpName, T]);
enc(Name, AvpName, {Min, Max}, Values, Opts, Mod) ->
H = avp_header(AvpName, Mod),
enc(Name, AvpName, H, Min, 0, 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]);
enc(_, _, _, _, _, _, [], _, _) ->
[];
enc(_, AvpName, _, _, N, Max, _, _, _)
when Max =< N ->
?THROW([repeated_avp_excessive_arity, AvpName, Max]);
enc(Name, AvpName, H, Min, N, Max, [V|Vs], Opts, Mod) ->
[enc1(Name, AvpName, H, V, Opts, Mod)
| enc(Name, AvpName, H, Min, N+1, Max, Vs, Opts, Mod)].
%% avp_header/2
avp_header('AVP', _) ->
false;
avp_header(AvpName, Mod) ->
{_,_,_} = Mod:avp_header(AvpName).
%% enc1/6
enc1(Name, 'AVP', false, Value, Opts, Mod) ->
enc_AVP(Name, Value, Opts, Mod);
enc1(_, 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)).
%% 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).
%% enc/4
enc(AvpName, Value, Opts, Mod) ->
enc1(AvpName, Mod:avp_header(AvpName), Value, Opts, Mod).
%% ---------------------------------------------------------------------------
%% # 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, decode_format := Fmt} = Opts) ->
{Avps, {Rec, AM, Failed}}
= mapfoldl(fun(T,A) -> decode(Name, Opts, Mod, T, A) end,
{newrec(Fmt, Mod, Name, Opts), #{}, []},
Recs),
%% 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, Opts, Fmt),
Avps,
Failed ++ arities(Arities, Opts, Mod, AM, Avps)}.
%% Append arity 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}.
%% 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.
%%
%% 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
arities(_, #{strict_arities := T}, _, _, _)
when T /= decode ->
[];
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).
%% more/6
more(_Name, ?ANY, _Opts, _Mod, _AM, Acc) ->
Acc;
more(Name, {Mn, Mx}, Opts, Mod, AM, Acc) ->
more(Name, Mn, Mx, Opts, Mod, AM, Acc);
more(Name, 1, Opts, Mod, AM, Acc) ->
more(Name, 1, 1, Opts, Mod, AM, Acc).
%% more/7
more(Name, Mn, Mx, Opts, Mod, AM, Acc) ->
macc(Name, Mn, maps:get(Name, AM, 0), Mx, Opts, Mod, Acc).
%% macc/7
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),
#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, 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, _}) ->
AvpName;
field(_) ->
'AVP'.
%% field/2
field(_, 0) ->
'AVP';
field(F, _) ->
F.
%% incr/2
incr(Key, Map) ->
maps:update_with(Key, fun incr/1, 1, Map).
%% incr/1
incr(N) ->
N + 1.
%% decode/7
%% AVP not in dictionary.
decode(Name, Opts, Mod, 'AVP', Arity, Avp, Acc) ->
decode_AVP(Name, Arity, 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}, Arity, 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, Arity, A, Opts, Mod, Acc)};
V when Type /= 'Grouped' ->
A = Avp#diameter_avp{name = AvpName,
value = V,
type = Type},
{A, pack_avp(Name, Arity, 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, #{strict_arities := T} = Opts, Mod, Acc)
when T /= decode ->
decode_AVP(Name, ?ANY, Avp, Opts, Mod, Acc);
decode_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
%% 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/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
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
%% 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, 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 ->
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_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_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
%% 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.
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}) ->
{set(Arity, F, value(F, Avp), Mod, Rec), AM, Failed}.
%% set/5
set(_, _, _, _, false = No) ->
No;
set(1, F, Value, _, Map)
when is_map(Map) ->
maps:put(F, Value, Map);
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
%% ---------------------------------------------------------------------------
-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/4
newrec(false = No, _, _, _) ->
No;
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/2
newrec(Mod, Name) ->
Mod:'#new-'(Mod:name2rec(Name)).
%% reformat/5
reformat(_, Map, Arities, _Mod, _Opts, list) ->
[{F,V} || {F,_} <- Arities, #{F := V} <- [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, SA))
|| {F,A} <- Arities]]);
reformat(_, Rec, _, _, _, _) ->
Rec.
%% def/2
def(1, decode) ->
undefined;
def(_, _) ->
[].