aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnders Svensson <[email protected]>2017-06-11 13:59:02 +0200
committerAnders Svensson <[email protected]>2017-06-13 13:50:07 +0200
commit205521d3927ed6f53c9a6fa3095f8a879bdca929 (patch)
tree7eda323ec84c0cb19bd55dd95735636bc08c6c11
parentcac106defc5060c5e485480e8003b992482d751d (diff)
downloadotp-205521d3927ed6f53c9a6fa3095f8a879bdca929.tar.gz
otp-205521d3927ed6f53c9a6fa3095f8a879bdca929.tar.bz2
otp-205521d3927ed6f53c9a6fa3095f8a879bdca929.zip
Move (most of) diameter_gen.hrl to diameter_gen.erl
To remove the requirement that dictionary modules be recompiled whenever the encode/decode implementation changes. The included diameter_gen.hrl now only contains trivial functions that call info diameter_gen.erl.
-rw-r--r--lib/diameter/include/diameter_gen.hrl659
-rw-r--r--lib/diameter/src/base/diameter_gen.erl709
-rw-r--r--lib/diameter/src/compiler/diameter_codegen.erl25
-rw-r--r--lib/diameter/src/modules.mk3
-rw-r--r--lib/diameter/test/diameter_codec_test.erl5
-rw-r--r--lib/diameter/test/diameter_compiler_SUITE.erl2
6 files changed, 747 insertions, 656 deletions
diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl
index 83f87ea4c4..fb6370fe54 100644
--- a/lib/diameter/include/diameter_gen.hrl
+++ b/lib/diameter/include/diameter_gen.hrl
@@ -20,659 +20,36 @@
%%
%% This file contains code that's included by encode/decode modules
-%% generated by diameter_codegen.erl. This code does most of the work, the
-%% generated code being kept simple.
+%% generated by diameter_codegen.erl. This code used to do most of the
+%% work, but now passes it off to module diameter_gen.
%%
--define(THROW(T), throw({?MODULE, T})).
-
-%% Tag common to generated dictionaries.
--define(TAG, diameter_gen).
-
--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/3
encode_avps(Name, Vals, Opts) ->
- try
- encode(Name, Vals, Opts)
- catch
- throw: {?MODULE, Reason} ->
- diameter_lib:log({encode, error},
- ?MODULE,
- ?LINE,
- {Reason, Name, Vals}),
- erlang:error(list_to_tuple(Reason ++ [Name]));
- error: Reason ->
- Stack = erlang:get_stacktrace(),
- diameter_lib:log({encode, failure},
- ?MODULE,
- ?LINE,
- {Reason, Name, Vals, Stack}),
- erlang:error({encode_failure, Reason, Name, Stack})
- end.
-
-%% encode/3
-
-encode(Name, Vals, #{ordered_encode := false} = Opts)
- when is_list(Vals) ->
- lists:map(fun({F,V}) -> encode(Name, F, V, Opts) end, Vals);
-
-encode(Name, Vals, Opts)
- when is_list(Vals) ->
- encode(Name, '#set-'(Vals, newrec(Name)), Opts);
-
-encode(Name, Rec, Opts) ->
- [encode(Name, F, V, Opts) || {F,V} <- '#get-'(Rec)].
-
-%% encode/4
-
-encode(Name, AvpName, Values, Opts) ->
- enc(Name, AvpName, avp_arity(Name, AvpName), Values, Opts).
-
-%% enc/5
-
-enc(_, AvpName, 1, undefined, _) ->
- ?THROW([mandatory_avp_missing, AvpName]);
-
-enc(Name, AvpName, 1, Value, Opts) ->
- enc(Name, AvpName, [Value], Opts);
-
-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]);
+ diameter_gen:encode_avps(Name, Vals, Opts#{module => ?MODULE}).
-enc(Name, AvpName, _, Values, Opts) ->
- enc(Name, AvpName, Values, Opts).
-
-%% enc/4
-
-enc(Name, 'AVP', Values, Opts) ->
- [enc_AVP(Name, A, Opts) || A <- Values];
-
-enc(_, AvpName, Values, Opts) ->
- enc(AvpName, Values, Opts).
-
-%% env/3
-
-enc(AvpName, Values, Opts) ->
- H = avp_header(AvpName),
- [diameter_codec:pack_data(H, avp(encode, V, AvpName, Opts))
- || V <- Values].
-
-%% 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);
-
-%% 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) ->
- 0 == avp_arity(Name, AvpName)
- orelse ?THROW([known_avp_as_AVP, Name, AvpName, Data]),
- enc(AvpName, [Data], Opts);
-
-%% The backdoor ...
-enc_AVP(_, {AvpName, Value}, Opts) ->
- enc(AvpName, [Value], Opts);
-
-%% ... and the side door.
-enc_AVP(_Name, {_Dict, _AvpName, _Data} = T, Opts) ->
- diameter_codec:pack_avp(#diameter_avp{data = T}, Opts).
-
-%% ---------------------------------------------------------------------------
-%% # decode_avps/2
-%% ---------------------------------------------------------------------------
-
--spec decode_avps(parent_name(), [#diameter_avp{}], map())
- -> {parent_record(), [avp()], Failed}
- when Failed :: [{5000..5999, #diameter_avp{}}].
+%% decode_avps/2
decode_avps(Name, Recs, Opts) ->
- {Avps, {Rec, Failed}}
- = mapfoldl(fun(T,A) -> decode(Name, Opts, T, A) end,
- {newrec(Name), []},
- Recs),
- {Rec, Avps, Failed ++ missing(Rec, Name, Failed)}.
-%% Append 5005 errors so that errors are reported in the order
-%% encountered. Failed-AVP should typically contain the first
-%% encountered error accordg to the RFC.
-
-newrec(Name) ->
- '#new-'(name2rec(Name)).
-
-%% 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:
-%%
-%% 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(Rec, Name, Failed) ->
- Avps = lists:foldl(fun({_, #diameter_avp{code = C, vendor_id = V}}, A) ->
- maps:put({C,V}, true, A)
- end,
- maps:new(),
- Failed),
- missing(avp_arity(Name), tl(tuple_to_list(Rec)), Avps, []).
-
-missing([{Name, Arity} | As], [Value | Vs], Avps, Acc) ->
- missing(As, Vs, Avps, case
- [H || missing_arity(Arity, Value),
- {C,_,V} = H <- [avp_header(Name)],
- not maps:is_key({C,V}, Avps)]
- of
- [H] ->
- [{5005, empty_avp(Name, H)} | 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).
-
-%% empty_avp/2
-
-empty_avp(Name, {Code, Flags, VId}) ->
- {Name, Type} = 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 = empty_value(Name),
- 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/4
-
-decode(Name, Opts, #diameter_avp{code = Code, vendor_id = Vid} = Avp, Acc) ->
- decode(Name, Opts, avp_name(Code, Vid), Avp, Acc).
-
-%% decode/5
-
-%% AVP not in dictionary.
-decode(Name, Opts, 'AVP', Avp, Acc) ->
- decode_AVP(Name, Avp, Opts, 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, {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.
- Mod = if Failed ->
- AppMod;
- true ->
- ?MODULE
- 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 Mod:avp(decode, Data, AvpName, Opts) of
- {Rec, As} when Type == 'Grouped' ->
- A = Avp#diameter_avp{name = AvpName,
- value = Rec,
- type = Type},
- {[A|As], pack_avp(Name, A, Opts, Acc)};
-
- V when Type /= 'Grouped' ->
- A = Avp#diameter_avp{name = AvpName,
- value = V,
- type = Type},
- {A, pack_avp(Name, A, Opts, Acc)}
- catch
- throw: {?TAG, {grouped, Error, ComponentAvps}} ->
- decode_error(Name,
- Error,
- ComponentAvps,
- Opts,
- Avp#diameter_avp{name = AvpName,
- data = trim(Avp#diameter_avp.data),
- type = Type},
- Acc);
-
- error: Reason ->
- decode_error(Name,
- Reason,
- Opts,
- Avp#diameter_avp{name = AvpName,
- data = trim(Avp#diameter_avp.data),
- type = Type},
- Acc)
- end.
-
-%% 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/6
-
-decode_error(Name, [_ | Rec], _, #{failed_avp := true} = Opts, Avp, Acc) ->
- decode_AVP(Name, Avp#diameter_avp{value = Rec}, Opts, Acc);
-
-decode_error(Name, _, _, #{failed_avp := true} = Opts, Avp, Acc) ->
- decode_AVP(Name, Avp, Opts, 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/5
-
-decode_error(Name, _Reason, #{failed_avp := true} = Opts, Avp, Acc) ->
- decode_AVP(Name, Avp, Opts, Acc);
-
-decode_error(Name, Reason, _, Avp, {Rec, Failed}) ->
- Stack = diameter_lib:get_stacktrace(),
- diameter_lib:log(decode_error,
- ?MODULE,
- ?LINE,
- {Name, Avp#diameter_avp.name, Stack}),
- {Avp, {Rec, [rc(Reason, Avp) | Failed]}}.
-
-%% decode_error/4
-
-decode_error({RC, ErrorData}, Avp, {Rec, Failed}, ComponentAvps) ->
- E = Avp#diameter_avp{data = [ErrorData]},
- {[Avp | trim(ComponentAvps)], {Rec, [{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/4
-%%
-%% 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, Acc) ->
- {trim(Avp), pack_AVP(Name, Avp, Opts, Acc)}.
-
-%% rc/1
-
-%% 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) ->
- {RC, Avp#diameter_avp{data = empty_value(AvpName)}};
-
-%% 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/4
-
-pack_avp(Name, #diameter_avp{name = AvpName} = Avp, Opts, Acc) ->
- pack_avp(Name, avp_arity(Name, AvpName), Avp, Opts, Acc).
-
-%% pack_avp/5
-
-pack_avp(Name, 0, Avp, Opts, Acc) ->
- pack_AVP(Name, Avp, Opts, Acc);
-
-pack_avp(_, Arity, #diameter_avp{name = AvpName} = Avp, _, Acc) ->
- pack(Arity, AvpName, Avp, Acc).
-
-%% pack_AVP/4
-
-%% 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, Failed} = Acc,
- {Rec, [{RC, Avp#diameter_avp{data = Data}} | Failed]};
-
-pack_AVP(Name, Avp, Opts, Acc) ->
- pack_AVP(pack_arity(Name, Opts, Avp), Avp, Acc).
-
-%% pack_AVP/3
-
-pack_AVP(0, #diameter_avp{is_mandatory = M} = Avp, Acc) ->
- {Rec, Failed} = Acc,
- {Rec, [{if M -> 5001; true -> 5008 end, Avp} | Failed]};
-
-pack_AVP(Arity, Avp, Acc) ->
- pack(Arity, 'AVP', Avp, 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},
- #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 ->
- avp_arity(Name, 'AVP');
- true ->
- 0
- 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/4
-
-pack(Arity, FieldName, Avp, {Rec, _} = Acc) ->
- pack('#get-'(FieldName, Rec), Arity, FieldName, Avp, Acc).
-
-%% pack/5
-
-pack(undefined, 1, 'AVP' = F, Avp, {Rec, Failed}) -> %% unlikely
- {'#set-'({F, Avp}, Rec), Failed};
-
-pack(undefined, 1, F, #diameter_avp{value = V}, {Rec, Failed}) ->
- {'#set-'({F, V}, Rec), Failed};
-
-%% 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
-%%
-
-pack(_, 1, _, Avp, {Rec, Failed}) ->
- {Rec, [{5009, Avp} | Failed]};
-
-pack(L, {_, Max}, F, Avp, {Rec, Failed}) ->
- case '*' /= Max andalso has_prefix(Max+1, L) of
- true ->
- {Rec, [{5009, Avp} | Failed]};
- false when F == 'AVP' ->
- {'#set-'({F, [Avp | L]}, Rec), Failed};
- false ->
- {'#set-'({F, [Avp#diameter_avp.value | L]}, Rec), Failed}
- end.
-
-%% ---------------------------------------------------------------------------
-%% # 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({?TAG, {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({?TAG, {grouped, Error, [Avp | Acc]}});
-
-%% 7.5. Failed-AVP AVP
+ diameter_gen:decode_avps(Name, Recs, Opts#{module => ?MODULE}).
-%% 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.
+%% avp/5
-%% 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({?TAG, {grouped, [{_,_} = hd(Es) | Rec], Avps}}),
- {Rec, Avps}.
+avp(T, Data, Name, Opts, Mod) ->
+ Mod:avp(T, Data, Name, Opts#{module := Mod}).
-%% ---------------------------------------------------------------------------
-%% # empty_group/1
-%% ---------------------------------------------------------------------------
+%% grouped_avp/4
-empty_group(Name) ->
- list_to_binary([z(F,A) || {F,A} <- avp_arity(Name)]).
+grouped_avp(T, Name, Data, Opts) ->
+ diameter_gen:grouped_avp(T, Name, Data, Opts).
-z(Name, 1) ->
- z(Name);
-z(_, {0,_}) ->
- [];
-z(Name, {Min, _}) ->
- binary:copy(z(Name), Min).
+%% empty_group/2
-z('AVP') ->
- <<0:64>>; %% minimal header
-z(Name) ->
- Bin = diameter_codec:pack_data(avp_header(Name), empty_value(Name)),
- Sz = iolist_size(Bin),
- <<0:Sz/unit:8>>.
+empty_group(Name, Opts) ->
+ diameter_gen:empty_group(Name, Opts).
-%% ---------------------------------------------------------------------------
-%% # empty/1
-%% ---------------------------------------------------------------------------
+%% empty/2
-empty(AvpName) ->
- avp(encode, zero, AvpName, _Opts = #{}).
+empty(Name, Opts) ->
+ diameter_gen:empty(Name, Opts).
diff --git a/lib/diameter/src/base/diameter_gen.erl b/lib/diameter/src/base/diameter_gen.erl
new file mode 100644
index 0000000000..e832832876
--- /dev/null
+++ b/lib/diameter/src/base/diameter_gen.erl
@@ -0,0 +1,709 @@
+%%
+%% %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, 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
+%% encountered. Failed-AVP should typically contain the first
+%% encountered error accordg to the RFC.
+
+%% 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:
+%%
+%% 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(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).
+
+%% empty_avp/4
+
+empty_avp(Name, {Code, Flags, VId}, Opts, Mod) ->
+ {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,
+ Acc) ->
+ decode(Name, Opts, Mod, Mod:avp_name(Code, Vid), Avp, Acc).
+
+%% 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,
+
+ %% 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)};
+
+ 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/5
+
+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, Failed}) ->
+ 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]}}.
+
+%% decode_error/4
+
+decode_error({RC, ErrorData}, Avp, {Rec, Failed}, ComponentAvps) ->
+ E = Avp#diameter_avp{data = [ErrorData]},
+ {[Avp | trim(ComponentAvps)], {Rec, [{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} = Avp, Opts, Mod) ->
+ {RC, Avp#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, 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).
+
+%% 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}) ->
+
+ %% 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.
+
+%% 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/5
+
+pack(Arity, FieldName, Avp, Mod, {Rec, _} = Acc) ->
+ pack(Mod:'#get-'(FieldName, Rec), Arity, FieldName, Avp, Mod, Acc).
+
+%% pack/6
+
+pack(undefined, 1, 'AVP' = F, Avp, Mod, {Rec, Failed}) -> %% unlikely
+ {Mod:'#set-'({F, Avp}, Rec), Failed};
+
+pack(undefined, 1, F, #diameter_avp{value = V}, Mod, {Rec, Failed}) ->
+ {Mod:'#set-'({F, V}, Rec), Failed};
+
+%% 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
+%%
+
+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.
+
+%% ---------------------------------------------------------------------------
+%% # 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)).
diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl
index 5a1e3ba941..2a1c0db381 100644
--- a/lib/diameter/src/compiler/diameter_codegen.erl
+++ b/lib/diameter/src/compiler/diameter_codegen.erl
@@ -152,6 +152,7 @@ erl_forms(Mod, ParseD) ->
{vendor_name, 0},
{decode_avps, 3}, %% in diameter_gen.hrl
{encode_avps, 3}, %%
+ {grouped_avp, 4}, %%
{msg_name, 2},
{msg_header, 1},
{rec2msg, 1},
@@ -162,9 +163,8 @@ erl_forms(Mod, ParseD) ->
{avp_arity, 2},
{avp_header, 1},
{avp, 4},
- {grouped_avp, 4},
{enumerated_avp, 3},
- {empty_value, 1},
+ {empty_value, 2},
{dict, 0}]},
%% diameter.hrl is included for #diameter_avp
{?attribute, include_lib, "diameter/include/diameter.hrl"},
@@ -557,10 +557,11 @@ imported_avp(Mod, {AvpName, _, _, _}, _) ->
c_imported_avp(Mod, AvpName) ->
{?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName), ?VAR('Opts')],
[],
- [?APPLY(Mod, avp, [?VAR('T'),
- ?VAR('Data'),
- ?Atom(AvpName),
- ?VAR('Opts')])]}.
+ [?CALL(avp, [?VAR('T'),
+ ?VAR('Data'),
+ ?Atom(AvpName),
+ ?VAR('Opts'),
+ ?ATOM(Mod)])]}.
cs_custom_avp({Mod, Key, Avps}, Dict) ->
lists:map(fun(N) -> c_custom_avp(Mod, Key, N, orddict:fetch(N, Dict)) end,
@@ -720,7 +721,7 @@ v(false, _, _, _) ->
%%% ------------------------------------------------------------------------
f_empty_value(ParseD) ->
- {?function, empty_value, 1, empty_value(ParseD)}.
+ {?function, empty_value, 2, empty_value(ParseD)}.
empty_value(ParseD) ->
Imported = lists:flatmap(fun avps/1, get_value(import_enums, ParseD)),
@@ -730,15 +731,17 @@ empty_value(ParseD) ->
not lists:keymember(N, 1, Imported)]
++ Imported,
lists:map(fun c_empty_value/1, Groups ++ Enums)
- ++ [{?clause, [?VAR('Name')], [], [?CALL(empty, [?VAR('Name')])]}].
+ ++ [{?clause, [?VAR('Name'), ?VAR('Opts')],
+ [],
+ [?CALL(empty, [?VAR('Name'), ?VAR('Opts')])]}].
c_empty_value({Name, _, _, _}) ->
- {?clause, [?Atom(Name)],
+ {?clause, [?Atom(Name), ?VAR('Opts')],
[],
- [?CALL(empty_group, [?Atom(Name)])]};
+ [?CALL(empty_group, [?Atom(Name), ?VAR('Opts')])]};
c_empty_value({Name, _}) ->
- {?clause, [?Atom(Name)],
+ {?clause, [?Atom(Name), ?VAR('_')],
[],
[?TERM(<<0:32>>)]}.
diff --git a/lib/diameter/src/modules.mk b/lib/diameter/src/modules.mk
index d8867a5ed2..bb3b234d20 100644
--- a/lib/diameter/src/modules.mk
+++ b/lib/diameter/src/modules.mk
@@ -1,7 +1,7 @@
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2010-2016. All Rights Reserved.
+# 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.
@@ -39,6 +39,7 @@ RT_MODULES = \
base/diameter_config \
base/diameter_config_sup \
base/diameter_codec \
+ base/diameter_gen \
base/diameter_lib \
base/diameter_misc_sup \
base/diameter_peer \
diff --git a/lib/diameter/test/diameter_codec_test.erl b/lib/diameter/test/diameter_codec_test.erl
index ccb97615da..b548f85cb8 100644
--- a/lib/diameter/test/diameter_codec_test.erl
+++ b/lib/diameter/test/diameter_codec_test.erl
@@ -171,7 +171,7 @@ gen(M, avp_types, {Name, Code, Type, _Flags}) ->
V = undefined /= VendorId,
V = 0 /= Flags band 2#10000000,
{Name, Type} = M:avp_name(Code, VendorId),
- B = M:empty_value(Name),
+ B = M:empty_value(Name, #{module => M}),
B = z(B),
[] = avp_decode(M, Type, Name);
@@ -215,7 +215,8 @@ avp(Mod, encode = X, V, Name, _) ->
iolist_to_binary(Mod:avp(X, V, Name, opts(Mod))).
opts(Mod) ->
- (opts())#{dictionary => Mod}.
+ (opts())#{module => Mod,
+ dictionary => Mod}.
opts() ->
#{string_decode => true,
diff --git a/lib/diameter/test/diameter_compiler_SUITE.erl b/lib/diameter/test/diameter_compiler_SUITE.erl
index dea14e3870..73fe1ef6e0 100644
--- a/lib/diameter/test/diameter_compiler_SUITE.erl
+++ b/lib/diameter/test/diameter_compiler_SUITE.erl
@@ -552,7 +552,7 @@ flatten2(_Config) ->
T <- [encode, decode],
M <- [M2, M3],
Ref <- [make_ref()],
- RC <- [M:avp(T, Ref, A, #{})],
+ RC <- [M:avp(T, Ref, A, #{module => M})],
RC /= {T, Ref}].
'A1'(T, 'Unsigned32', Ref, _Opts) ->