aboutsummaryrefslogtreecommitdiffstats
path: root/lib/diameter/src/base
diff options
context:
space:
mode:
Diffstat (limited to 'lib/diameter/src/base')
-rw-r--r--lib/diameter/src/base/diameter.erl71
-rw-r--r--lib/diameter/src/base/diameter_callback.erl6
-rw-r--r--lib/diameter/src/base/diameter_codec.erl203
-rw-r--r--lib/diameter/src/base/diameter_config.erl279
-rw-r--r--lib/diameter/src/base/diameter_gen.erl989
-rw-r--r--lib/diameter/src/base/diameter_lib.erl2
-rw-r--r--lib/diameter/src/base/diameter_peer.erl6
-rw-r--r--lib/diameter/src/base/diameter_peer_fsm.erl237
-rw-r--r--lib/diameter/src/base/diameter_reg.erl235
-rw-r--r--lib/diameter/src/base/diameter_service.erl99
-rw-r--r--lib/diameter/src/base/diameter_traffic.erl479
-rw-r--r--lib/diameter/src/base/diameter_watchdog.erl39
12 files changed, 1481 insertions, 1164 deletions
diff --git a/lib/diameter/src/base/diameter.erl b/lib/diameter/src/base/diameter.erl
index bd92e16fba..b90b794611 100644
--- a/lib/diameter/src/base/diameter.erl
+++ b/lib/diameter/src/base/diameter.erl
@@ -46,7 +46,10 @@
-export([start/0,
stop/0]).
--export_type([evaluable/0,
+-export_type([eval/0,
+ evaluable/0, %% deprecated
+ decode_format/0,
+ strict_arities/0,
restriction/0,
message_length/0,
remotes/0,
@@ -299,7 +302,7 @@ call(SvcName, App, Message) ->
| realm
| {host, any|'DiameterIdentity'()}
| {realm, any|'DiameterIdentity'()}
- | {eval, evaluable()}
+ | {eval, eval()}
| {neg, peer_filter()}
| {all, [peer_filter()]}
| {any, [peer_filter()]}.
@@ -307,10 +310,13 @@ call(SvcName, App, Message) ->
-opaque peer_ref()
:: pid().
--type evaluable()
+-type eval()
:: {module(), atom(), list()}
| fun()
- | maybe_improper_list(evaluable(), list()).
+ | maybe_improper_list(eval(), list()).
+
+-type evaluable()
+ :: eval().
-type sequence()
:: {'Unsigned32'(), 0..32}.
@@ -320,29 +326,61 @@ call(SvcName, App, Message) ->
| node
| nodes
| [node()]
- | evaluable().
+ | eval().
-type remotes()
:: boolean()
| [node()]
- | evaluable().
+ | eval().
-type message_length()
:: 0..16#FFFFFF.
+-type decode_format()
+ :: record
+ | list
+ | map
+ | none
+ | record_from_map.
+
+-type strict_arities()
+ :: false
+ | encode
+ | decode.
+
+%% Options common to both start_service/2 and add_transport/2.
+
+-type common_opt()
+ :: {pool_size, pos_integer()}
+ | {capabilities_cb, eval()}
+ | {capx_timeout, 'Unsigned32'()}
+ | {strict_capx, boolean()}
+ | {strict_mbit, boolean()}
+ | {avp_dictionaries, [module()]}
+ | {disconnect_cb, eval()}
+ | {dpr_timeout, 'Unsigned32'()}
+ | {dpa_timeout, 'Unsigned32'()}
+ | {incoming_maxlen, message_length()}
+ | {length_errors, exit | handle | discard}
+ | {connect_timer, 'Unsigned32'()}
+ | {watchdog_timer, 'Unsigned32'() | {module(), atom(), list()}}
+ | {watchdog_config, [{okay|suspect, non_neg_integer()}]}
+ | {spawn_opt, list()}.
+
%% Options passed to start_service/2
-type service_opt()
:: capability()
| {application, [application_opt()]}
| {restrict_connections, restriction()}
- | {sequence, sequence() | evaluable()}
+ | {sequence, sequence() | eval()}
| {share_peers, remotes()}
+ | {decode_format, decode_format()}
+ | {traffic_counters, boolean()}
| {string_decode, boolean()}
- | {strict_mbit, boolean()}
- | {incoming_maxlen, message_length()}
+ | {strict_arities, true | strict_arities()}
| {use_shared_peers, remotes()}
- | {spawn_opt, list()}.
+ | common_opt().
-type application_opt()
:: {alias, app_alias()}
@@ -372,20 +410,9 @@ call(SvcName, App, Message) ->
:: {transport_module, atom()}
| {transport_config, any()}
| {transport_config, any(), 'Unsigned32'() | infinity}
- | {pool_size, pos_integer()}
| {applications, [app_alias()]}
| {capabilities, [capability()]}
- | {capabilities_cb, evaluable()}
- | {capx_timeout, 'Unsigned32'()}
- | {capx_strictness, boolean()}
- | {disconnect_cb, evaluable()}
- | {dpr_timeout, 'Unsigned32'()}
- | {dpa_timeout, 'Unsigned32'()}
- | {length_errors, exit | handle | discard}
- | {connect_timer, 'Unsigned32'()}
- | {watchdog_timer, 'Unsigned32'() | {module(), atom(), list()}}
- | {watchdog_config, [{okay|suspect, non_neg_integer()}]}
- | {spawn_opt, list()}
+ | common_opt()
| {private, any()}.
%% Predicate passed to remove_transport/2
diff --git a/lib/diameter/src/base/diameter_callback.erl b/lib/diameter/src/base/diameter_callback.erl
index f9cdc66c70..d04a416bef 100644
--- a/lib/diameter/src/base/diameter_callback.erl
+++ b/lib/diameter/src/base/diameter_callback.erl
@@ -26,16 +26,16 @@
%% as the Diameter application callback in question. The record has
%% one field for each callback function as well as 'default' and
%% 'extra' fields. A function-specific field can be set to a
-%% diameter:evaluable() in order to redirect the callback
+%% diameter:eval() in order to redirect the callback
%% corresponding to that field, or to 'false' to request the default
%% callback implemented in this module. If neither of these fields are
%% set then the 'default' field determines the form of the callback: a
%% module name results in the usual callback as if the module had been
-%% configured directly as the callback module, a diameter_evaluable()
+%% configured directly as the callback module, a diameter_eval()
%% in a callback applied to the atom-valued callback name and argument
%% list. For all callbacks not to this module, the 'extra' field is a
%% list of additional arguments, following arguments supplied by
-%% diameter but preceding those of the diameter:evaluable() being
+%% diameter but preceding those of the diameter:eval() being
%% applied.
%%
%% For example, the following config to diameter:start_service/2, in
diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl
index 82fa796e69..2dd2c906a2 100644
--- a/lib/diameter/src/base/diameter_codec.erl
+++ b/lib/diameter/src/base/diameter_codec.erl
@@ -29,7 +29,7 @@
msg_name/2,
msg_id/1]).
-%% Towards generated encoders (from diameter_gen.hrl).
+%% towards diameter_gen
-export([pack_data/2,
pack_avp/2]).
@@ -110,7 +110,7 @@ encode(Mod, Opts, Msg) ->
enc(_, Opts, #diameter_packet{msg = [#diameter_header{} = Hdr | As]}
= Pkt) ->
- try encode_avps(reorder(As), Opts) of
+ try encode_avps(As, Opts) of
Avps ->
Bin = list_to_binary(Avps),
Len = 20 + size(Bin),
@@ -206,51 +206,12 @@ values(Avps) ->
%% Message as a list of #diameter_avp{} ...
encode_avps(_, _, [#diameter_avp{} | _] = Avps, Opts) ->
- encode_avps(reorder(Avps), Opts);
+ encode_avps(Avps, Opts);
%% ... or as a tuple list or record.
encode_avps(Mod, MsgName, Values, Opts) ->
Mod:encode_avps(MsgName, Values, Opts).
-%% reorder/1
-%%
-%% Reorder AVPs for the relay case using the index field of
-%% diameter_avp records. Decode populates this field in collect_avps
-%% and presents AVPs in reverse order. A relay then sends the reversed
-%% list with a Route-Record AVP prepended. The goal here is just to do
-%% lists:reverse/1 in Grouped AVPs and the outer list, but only in the
-%% case there are indexed AVPs at all, so as not to reverse lists that
-%% have been explicilty sent (unindexed, in the desired order) as a
-%% diameter_avp list. The effect is the same as lists:keysort/2, but
-%% only on the cases we expect, not a general sort.
-
-reorder(Avps) ->
- case reorder(Avps, []) of
- false ->
- Avps;
- Sorted ->
- Sorted
- end.
-
-%% reorder/3
-
-%% In case someone has reversed the list already. (Not likely.)
-reorder([#diameter_avp{index = 0} | _] = Avps, Acc) ->
- Avps ++ Acc;
-
-%% Assume indexed AVPs are in reverse order.
-reorder([#diameter_avp{index = N} = A | Avps], Acc)
- when is_integer(N) ->
- lists:reverse(Avps, [A | Acc]);
-
-%% An unindexed AVP.
-reorder([H | T], Acc) ->
- reorder(T, [H | Acc]);
-
-%% No indexed members.
-reorder([], _) ->
- false.
-
%% encode_avps/2
encode_avps(Avps, Opts) ->
@@ -287,7 +248,8 @@ rec2msg(Mod, Rec) ->
%% longer *the* decode.
decode(Mod, Pkt) ->
- Opts = #{string_decode => true,
+ Opts = #{decode_format => record,
+ string_decode => true,
strict_mbit => true,
rfc => 6733},
decode(Mod, Opts, Pkt).
@@ -326,13 +288,7 @@ decode(Mod, AppMod, Opts, Pkt) ->
%% Relay application: just extract the avp's without any decoding of
%% their data since we don't know the application in question.
decode(?APP_ID_RELAY, _, _, _, #diameter_packet{} = Pkt) ->
- case collect_avps(Pkt) of
- {E, As} ->
- Pkt#diameter_packet{avps = As,
- errors = [E]};
- As ->
- Pkt#diameter_packet{avps = As}
- end;
+ collect_avps(Pkt);
%% Otherwise decode using the dictionary.
decode(_, Mod, AppMod, Opts, #diameter_packet{header = Hdr} = Pkt) ->
@@ -341,44 +297,50 @@ decode(_, Mod, AppMod, Opts, #diameter_packet{header = Hdr} = Pkt) ->
is_error = IsError}
= Hdr,
- MsgName = if IsError andalso not IsRequest ->
+ MsgName = if IsError, not IsRequest ->
'answer-message';
true ->
Mod:msg_name(CmdCode, IsRequest)
end,
- decode_avps(MsgName, Mod, AppMod, Opts, Pkt, collect_avps(Pkt));
+ decode_avps(MsgName, Mod, AppMod, Opts, Pkt);
decode(Id, Mod, AppMod, Opts, Bin)
when is_binary(Bin) ->
decode(Id, Mod, AppMod, Opts, #diameter_packet{header = decode_header(Bin),
bin = Bin}).
-%% decode_avps/6
-
-decode_avps(MsgName, Mod, AppMod, Opts, Pkt, {E, Avps}) ->
- ?LOG(invalid_avp_length, Pkt#diameter_packet.header),
- #diameter_packet{errors = Failed}
- = P
- = decode_avps(MsgName, Mod, AppMod, Opts, Pkt, Avps),
- P#diameter_packet{errors = [E | Failed]};
+%% decode_avps/5
-decode_avps('', _, _, _, Pkt, Avps) -> %% unknown message ...
- ?LOG(unknown_message, Pkt#diameter_packet.header),
- Pkt#diameter_packet{avps = lists:reverse(Avps),
- errors = [3001]}; %% DIAMETER_COMMAND_UNSUPPORTED
+decode_avps('', _, _, _, #diameter_packet{header = H, %% unknown message
+ bin = Bin}
+ = Pkt) ->
+ ?LOG(unknown_message, H),
+ Pkt#diameter_packet{avps = collect_avps(Bin),
+ errors = [3001]}; %% DIAMETER_COMMAND_UNSUPPORTED
%% msg = undefined identifies this case.
-decode_avps(MsgName, Mod, AppMod, Opts, Pkt, Avps) -> %% ... or not
+decode_avps(MsgName, Mod, AppMod, Opts, #diameter_packet{bin = Bin} = Pkt) ->
+ {_, Avps} = split_binary(Bin, 20),
{Rec, As, Errors} = Mod:decode_avps(MsgName,
Avps,
- Opts#{dictionary => AppMod,
+ Opts#{app_dictionary => AppMod,
failed_avp => false}),
?LOGC([] /= Errors, decode_errors, Pkt#diameter_packet.header),
- Pkt#diameter_packet{msg = Rec,
+ Pkt#diameter_packet{msg = reformat(MsgName, Rec, Opts),
errors = Errors,
avps = As}.
+%% reformat/3
+
+reformat(MsgName, Avps, #{decode_format := T})
+ when T == map;
+ T == list ->
+ [MsgName | Avps];
+
+reformat(_, Msg, _) ->
+ Msg.
+
%%% ---------------------------------------------------------------------------
%%% # decode_header/1
%%% ---------------------------------------------------------------------------
@@ -515,24 +477,21 @@ msg_id(<<_:32, Rbit:1, _:7, CmdCode:24, ApplId:32, _/binary>>) ->
%%% # collect_avps/1
%%% ---------------------------------------------------------------------------
-%% Note that the returned list of AVP's is reversed relative to their
-%% order in the binary. Note also that grouped avp's aren't unraveled,
-%% only those at the top level.
+%% This is only used for the relay decode. Note that grouped avp's
+%% aren't unraveled, only those at the top level.
--spec collect_avps(#diameter_packet{} | binary())
- -> [Avp]
- | {Error, [Avp]}
- when Avp :: #diameter_avp{},
- Error :: {5014, #diameter_avp{}}.
+-spec collect_avps(#diameter_packet{})
+ -> #diameter_packet{};
+ (binary())
+ -> [#diameter_avp{}].
-collect_avps(#diameter_packet{bin = <<_:20/binary, Avps/binary>>}) ->
- collect_avps(Avps, 0, []);
+collect_avps(#diameter_packet{bin = Bin} = Pkt) ->
+ Pkt#diameter_packet{avps = collect_avps(Bin)};
-collect_avps(Bin)
- when is_binary(Bin) ->
- collect_avps(Bin, 0, []).
+collect_avps(<<_:20/binary, Avps/binary>>) ->
+ collect(Avps).
-%% collect_avps/3
+%% collect/1
%% 0 1 2 3
%% 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
@@ -546,66 +505,48 @@ collect_avps(Bin)
%% | Data ...
%% +-+-+-+-+-+-+-+-+
-collect_avps(<<Code:32, V:1, M:1, P:1, _:5, Len:24, I:V/unit:32, Rest/binary>>,
- N,
- Acc) ->
- collect_avps(Code,
- if 1 == V -> I; 0 == V -> undefined end,
- 1 == M,
- 1 == P,
- Len - 8 - V*4, %% Might be negative, which ensures
- ?PAD(Len), %% failure of the Data match below.
- Rest,
- N,
- Acc);
-
-collect_avps(<<>>, _, Acc) ->
- Acc;
+collect(<<Code:32, V:1, M:1, P:1, _:5, Len:24, I:V/unit:32, Rest/binary>>) ->
+ collect(Rest,
+ Code,
+ if 1 == V -> I; 0 == V -> undefined end,
+ Len - 8 - V*4, %% Might be negative, which ensures
+ ?PAD(Len), %% failure of the match below.
+ 1 == M,
+ 1 == P);
+
+collect(<<>>) ->
+ [];
%% Header is truncated. pack_avp/1 will pad this at encode if sent in
%% a Failed-AVP.
-collect_avps(Bin, _, Acc) ->
- {{5014, #diameter_avp{data = Bin}}, Acc}.
+collect(Bin) ->
+ [#diameter_avp{data = {5014, Bin}}].
-%% collect_avps/9
+%% collect/7
-%% Duplicate the diameter_avp creation in each branch below to avoid
-%% modifying the record, which profiling has shown to be a relatively
-%% costly part of building the list.
-
-collect_avps(Code, VendorId, M, P, Len, Pad, Rest, N, Acc) ->
- case Rest of
- <<Data:Len/binary, _:Pad/binary, T/binary>> ->
+collect(Bin, Code, Vid, DataLen, Pad, M, P) ->
+ case Bin of
+ <<Data:DataLen/binary, _:Pad/binary, Rest/binary>> ->
Avp = #diameter_avp{code = Code,
- vendor_id = VendorId,
+ vendor_id = Vid,
is_mandatory = M,
need_encryption = P,
- data = Data,
- index = N},
- collect_avps(T, N+1, [Avp | Acc]);
+ data = Data},
+ [Avp | collect(Rest)];
_ ->
%% Length in header points past the end of the message, or
- %% doesn't span the header. As stated in the 6733 text
- %% above, it's sufficient to return a zero-filled minimal
- %% payload if this is a request. Do this (in cases that we
- %% know the type) by inducing a decode failure and letting
- %% the dictionary's decode (in diameter_gen) deal with it.
- %%
- %% Note that the extra bit can only occur in the trailing
- %% AVP of a message or Grouped AVP, since a faulty AVP
- %% Length is otherwise indistinguishable from a correct
- %% one here, as we don't know the types of the AVPs being
- %% extracted.
- Avp = #diameter_avp{code = Code,
- vendor_id = VendorId,
- is_mandatory = M,
- need_encryption = P,
- data = {5014, Rest},
- index = N},
- [Avp | Acc]
+ %% doesn't span the header. Note that an length error can
+ %% only occur in the trailing AVP of a message or Grouped
+ %% AVP, since a faulty AVP Length is otherwise
+ %% indistinguishable from a correct one here, as we don't
+ %% know the types of the AVPs being extracted.
+ [#diameter_avp{code = Code,
+ vendor_id = Vid,
+ is_mandatory = M,
+ need_encryption = P,
+ data = {5014, Bin}}]
end.
-
%% 3588:
%%
%% DIAMETER_INVALID_AVP_LENGTH 5014
@@ -673,8 +614,8 @@ pack_avp(#diameter_avp{data = {T, {Type, Value}}}, Opts) ->
pack_avp(#diameter_avp{data = {T, Data}}, _) ->
pack_data(T, Data);
-pack_avp(#diameter_avp{data = {Dict, Name, Data}}, Opts) ->
- pack_data(Dict:avp_header(Name), Dict:avp(encode, Data, Name, Opts));
+pack_avp(#diameter_avp{data = {Dict, Name, Value}}, Opts) ->
+ pack_data(Dict:avp_header(Name), Dict:avp(encode, Value, Name, Opts));
%% ... with a truncated header ...
pack_avp(#diameter_avp{code = undefined, data = B}, _)
diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl
index 34018ae6d3..90a9282349 100644
--- a/lib/diameter/src/base/diameter_config.erl
+++ b/lib/diameter/src/base/diameter_config.erl
@@ -102,9 +102,6 @@
-record(monitor, {mref = make_ref() :: reference(),
service}). %% name
-%% The default sequence mask.
--define(NOMASK, {0,32}).
-
%% Time to lay low before restarting a dead service.
-define(RESTART_SLEEP, 2000).
@@ -560,87 +557,186 @@ add(SvcName, Type, Opts0) ->
end.
transport_opts(Opts) ->
- lists:map(fun topt/1, Opts).
+ [setopt(transport, T) || T <- Opts].
+
+%% setopt/2
-topt(T) ->
- case opt(T) of
+setopt(K, T) ->
+ case opt(K, T) of
{value, X} ->
X;
true ->
T;
false ->
- ?THROW({invalid, T})
+ ?THROW({invalid, T});
+ {error, Reason} ->
+ ?THROW({invalid, T, Reason})
end.
-opt({transport_module, M}) ->
+%% opt/2
+
+opt(_, {incoming_maxlen, N}) ->
+ is_integer(N) andalso 0 =< N andalso N < 1 bsl 24;
+
+opt(service, {K, B})
+ when K == string_decode;
+ K == traffic_counters ->
+ is_boolean(B);
+
+opt(service, {K, false})
+ when K == share_peers;
+ K == use_shared_peers;
+ K == monitor;
+ K == restrict_connections;
+ K == strict_arities ->
+ true;
+
+opt(service, {K, true})
+ when K == share_peers;
+ K == use_shared_peers;
+ K == strict_arities ->
+ true;
+
+opt(service, {decode_format, T})
+ when T == record;
+ T == list;
+ T == map;
+ T == none;
+ T == record_from_map ->
+ true;
+
+opt(service, {strict_arities, T})
+ when T == encode;
+ T == decode ->
+ true;
+
+opt(service, {restrict_connections, T})
+ when T == node;
+ T == nodes ->
+ true;
+
+opt(service, {K, T})
+ when (K == share_peers
+ orelse K == use_shared_peers
+ orelse K == restrict_connections), ([] == T
+ orelse is_atom(hd(T))) ->
+ true;
+
+opt(service, {monitor, P}) ->
+ is_pid(P);
+
+opt(service, {K, F})
+ when K == restrict_connections;
+ K == share_peers;
+ K == use_shared_peers ->
+ try diameter_lib:eval(F) of %% but no guarantee that it won't fail later
+ Nodes ->
+ is_list(Nodes) orelse {error, Nodes}
+ catch
+ E:R ->
+ {error, {E, R, ?STACK}}
+ end;
+
+opt(service, {sequence, {H,N}}) ->
+ 0 =< N andalso N =< 32
+ andalso is_integer(H)
+ andalso 0 =< H
+ andalso 0 == H bsr (32-N);
+
+opt(service = S, {sequence = K, F}) ->
+ try diameter_lib:eval(F) of
+ {_,_} = T ->
+ KT = {K,T},
+ opt(S, KT) andalso {value, KT};
+ V ->
+ {error, V}
+ catch
+ E:R ->
+ {error, {E, R, ?STACK}}
+ end;
+
+opt(transport, {transport_module, M}) ->
is_atom(M);
-opt({transport_config, _, Tmo}) ->
+opt(transport, {transport_config, _, Tmo}) ->
?IS_UINT32(Tmo) orelse Tmo == infinity;
-opt({applications, As}) ->
+opt(transport, {applications, As}) ->
is_list(As);
-opt({capabilities, Os}) ->
- is_list(Os) andalso ok == encode_CER(Os);
+opt(transport, {capabilities, Os}) ->
+ is_list(Os) andalso try ok = encode_CER(Os), true
+ catch ?FAILURE(No) -> {error, No}
+ end;
-opt({K, Tmo})
+opt(_, {K, Tmo})
when K == capx_timeout;
K == dpr_timeout;
K == dpa_timeout ->
?IS_UINT32(Tmo);
-opt({capx_strictness, B}) ->
+opt(_, {capx_strictness, B}) ->
+ is_boolean(B) andalso {value, {strict_capx, B}};
+opt(_, {K, B})
+ when K == strict_capx;
+ K == strict_mbit ->
is_boolean(B);
-opt({length_errors, T}) ->
+opt(_, {avp_dictionaries, Mods}) ->
+ is_list(Mods) andalso lists:all(fun erlang:is_atom/1, Mods);
+
+opt(_, {length_errors, T}) ->
lists:member(T, [exit, handle, discard]);
-opt({K, Tmo})
- when K == reconnect_timer; %% deprecated
- K == connect_timer ->
+opt(transport, {reconnect_timer, Tmo}) -> %% deprecated
+ ?IS_UINT32(Tmo) andalso {value, {connect_timer, Tmo}};
+opt(_, {connect_timer, Tmo}) ->
?IS_UINT32(Tmo);
-opt({watchdog_timer, {M,F,A}})
+opt(_, {watchdog_timer, {M,F,A}})
when is_atom(M), is_atom(F), is_list(A) ->
true;
-opt({watchdog_timer, Tmo}) ->
+opt(_, {watchdog_timer, Tmo}) ->
?IS_UINT32(Tmo);
-opt({watchdog_config, L}) ->
- is_list(L) andalso lists:all(fun wdopt/1, L);
+opt(_, {watchdog_config, L}) ->
+ is_list(L) andalso lists:all(fun wd/1, L);
-opt({spawn_opt, {M,F,A}})
+opt(_, {spawn_opt, {M,F,A}})
when is_atom(M), is_atom(F), is_list(A) ->
true;
-opt({spawn_opt = K, Opts}) ->
+opt(_, {spawn_opt = K, Opts}) ->
if is_list(Opts) ->
{value, {K, spawn_opts(Opts)}};
true ->
false
end;
-opt({pool_size, N}) ->
+opt(_, {pool_size, N}) ->
is_integer(N) andalso 0 < N;
-%% Options that we can't validate.
-opt({K, _})
+%% Options we can't validate.
+opt(_, {K, _})
+ when K == disconnect_cb;
+ K == capabilities_cb ->
+ true;
+opt(transport, {K, _})
when K == transport_config;
- K == capabilities_cb;
- K == disconnect_cb;
K == private ->
true;
-%% Anything else, which is ignored by us. This makes options sensitive
-%% to spelling mistakes but arbitrary options are passed by some users
-%% as a way to identify transports. (That is, can't just do away with
-%% it.)
-opt(_) ->
- true.
+%% Anything else, which is ignored in transport config. This makes
+%% options sensitive to spelling mistakes, but arbitrary options are
+%% passed by some users as a way to identify transports so can't just
+%% do away with it.
+opt(K, _) ->
+ K == transport.
+
+%% wd/1
-wdopt({K,N}) ->
+wd({K,N}) ->
(K == okay orelse K == suspect) andalso is_integer(N) andalso 0 =< N;
-wdopt(_) ->
+wd(_) ->
false.
%% start_transport/2
@@ -705,16 +801,7 @@ make_config(SvcName, Opts) ->
ok = encode_CER(CapOpts),
- SvcOpts = make_opts((Opts -- AppOpts) -- CapOpts,
- [{false, share_peers},
- {false, use_shared_peers},
- {false, monitor},
- {?NOMASK, sequence},
- {nodes, restrict_connections},
- {16#FFFFFF, incoming_maxlen},
- {true, strict_mbit},
- {true, string_decode},
- {[], spawn_opt}]),
+ SvcOpts = service_opts((Opts -- AppOpts) -- CapOpts),
D = proplists:get_value(string_decode, SvcOpts, true),
@@ -728,98 +815,22 @@ binary_caps(Caps, true) ->
binary_caps(Caps, false) ->
diameter_capx:binary_caps(Caps).
-%% make_opts/2
-
-make_opts(Opts, Defs) ->
- Known = [{K, get_opt(K, Opts, D)} || {D,K} <- Defs],
- Unknown = Opts -- Known,
-
- [] == Unknown orelse ?THROW({invalid, hd(Unknown)}),
+%% service_opts/1
- [{K, opt(K,V)} || {K,V} <- Known].
-
-opt(incoming_maxlen, N)
- when 0 =< N, N < 1 bsl 24 ->
- N;
-
-opt(spawn_opt, {M,F,A} = T)
- when is_atom(M), is_atom(F), is_list(A) ->
- T;
-
-opt(spawn_opt, L)
- when is_list(L) ->
- spawn_opts(L);
-
-opt(K, false = B)
- when K == share_peers;
- K == use_shared_peers;
- K == monitor;
- K == restrict_connections;
- K == strict_mbit;
- K == string_decode ->
- B;
-
-opt(K, true = B)
- when K == share_peers;
- K == use_shared_peers;
- K == strict_mbit;
- K == string_decode ->
- B;
-
-opt(restrict_connections, T)
- when T == node;
- T == nodes ->
- T;
-
-opt(K, T)
- when (K == share_peers
- orelse K == use_shared_peers
- orelse K == restrict_connections), ([] == T
- orelse is_atom(hd(T))) ->
- T;
-
-opt(monitor, P)
- when is_pid(P) ->
- P;
-
-opt(K, F)
- when K == restrict_connections;
- K == share_peers;
- K == use_shared_peers ->
- try diameter_lib:eval(F) of %% but no guarantee that it won't fail later
- Nodes when is_list(Nodes) ->
- F;
- V ->
- ?THROW({value, {K,V}})
- catch
- E:R ->
- ?THROW({value, {K, E, R, ?STACK}})
- end;
-
-opt(sequence, {_,_} = T) ->
- sequence(T);
-
-opt(sequence = K, F) ->
- try diameter_lib:eval(F) of
- T -> sequence(T)
- catch
- E:R ->
- ?THROW({value, {K, E, R, ?STACK}})
- end;
-
-opt(K, _) ->
- ?THROW({value, K}).
+service_opts(Opts) ->
+ Res = [setopt(service, T) || T <- Opts],
+ Keys = sets:to_list(sets:from_list([K || {K,_} <- Res])), %% unique
+ Dups = lists:foldl(fun(K,A) -> lists:keydelete(K, 1, A) end, Res, Keys),
+ [] == Dups orelse ?THROW({duplicate, Dups}),
+ Res.
+%% Reject duplicates on a service, but not on a transport. There's no
+%% particular reason for the inconsistency, but the historic behaviour
+%% ignores all but the first of a transport_opt(), and there's no real
+%% reason to change it.
spawn_opts(L) ->
[T || T <- L, T /= link, T /= monitor].
-sequence({H,N} = T)
- when 0 =< N, N =< 32, 0 =< H, 0 == H bsr (32-N) ->
- T;
-
-sequence(_) ->
- ?THROW({value, sequence}).
-
make_caps(Caps, Opts) ->
case diameter_capx:make_caps(Caps, Opts) of
{ok, T} ->
diff --git a/lib/diameter/src/base/diameter_gen.erl b/lib/diameter/src/base/diameter_gen.erl
index e832832876..d3b9f704fe 100644
--- a/lib/diameter/src/base/diameter_gen.erl
+++ b/lib/diameter/src/base/diameter_gen.erl
@@ -26,6 +26,14 @@
-module(diameter_gen).
+-compile({inline, [incr/8,
+ incr/4,
+ field/1,
+ setopts/4,
+ avp_arity/5,
+ set_failed/2,
+ set_strict/3]}).
+
-export([encode_avps/3,
decode_avps/3,
grouped_avp/4,
@@ -37,7 +45,7 @@
-define(THROW(T), throw({?MODULE, T})).
-type parent_name() :: atom(). %% parent = Message or AVP
--type parent_record() :: tuple(). %%
+-type parent_record() :: tuple() | avp_values() | map().
-type avp_name() :: atom().
-type avp_record() :: tuple().
-type avp_values() :: [{avp_name(), term()}].
@@ -46,17 +54,21 @@
-type grouped_avp() :: nonempty_improper_list(#diameter_avp{}, [avp()]).
-type avp() :: non_grouped_avp() | grouped_avp().
+%% The arbitrary arity returned from dictionary avp_arity functions.
+-define(ANY, {0, '*'}).
+
%% ---------------------------------------------------------------------------
%% # encode_avps/3
%% ---------------------------------------------------------------------------
--spec encode_avps(parent_name(), parent_record() | avp_values(), map())
+-spec encode_avps(parent_name(), parent_record(), map())
-> iolist()
| no_return().
encode_avps(Name, Vals, #{module := Mod} = Opts) ->
+ Strict = mget(strict_arities, Opts, encode),
try
- encode(Name, Vals, Opts, Mod)
+ encode(Name, Vals, Opts, Strict, Mod)
catch
throw: {?MODULE, Reason} ->
diameter_lib:log({encode, error},
@@ -73,128 +85,340 @@ encode_avps(Name, Vals, #{module := Mod} = Opts) ->
erlang:error({encode_failure, Reason, Name, Stack})
end.
-%% encode/4
+%% encode/5
-encode(Name, Vals, #{ordered_encode := false} = Opts, Mod)
+encode(Name, Vals, Opts, Strict, Mod)
when is_list(Vals) ->
- lists:map(fun({F,V}) -> encode(Name, F, V, Opts, Mod) end, Vals);
+ case Opts of
+ #{ordered_encode := false} ->
+ lists:map(fun({F,V}) -> encode(Name, F, V, Opts, Strict, Mod) end,
+ Vals);
+ _ ->
+ Rec = Mod:'#set-'(Vals, newrec(Mod, Name)),
+ encode(Name, Rec, Opts, Strict, Mod)
+ end;
-encode(Name, Vals, Opts, Mod)
- when is_list(Vals) ->
- encode(Name, Mod:'#set-'(Vals, newrec(Mod, Name)), Opts, Mod);
+encode(Name, Map, Opts, Strict, Mod)
+ when is_map(Map) ->
+ [enc(F, A, V, Opts, Strict, Mod) || {F,A} <- Mod:avp_arity(Name),
+ V <- [mget(F, Map, undefined)]];
-encode(Name, Rec, Opts, Mod) ->
- [encode(Name, F, V, Opts, Mod) || {F,V} <- Mod:'#get-'(Rec)].
+encode(Name, Rec, Opts, Strict, Mod) ->
+ [encode(Name, F, V, Opts, Strict, Mod) || {F,V} <- Mod:'#get-'(Rec)].
-%% encode/5
+%% encode/6
+
+encode(_, AvpName, Values, Opts, Strict, Mod)
+ when Strict /= encode ->
+ enc(AvpName, ?ANY, Values, Opts, Strict, Mod);
-encode(Name, AvpName, Values, Opts, Mod) ->
- enc(Name, AvpName, Mod:avp_arity(Name, AvpName), Values, Opts, Mod).
+encode(Name, AvpName, Values, Opts, Strict, Mod) ->
+ Arity = Mod:avp_arity(Name, AvpName),
+ enc(AvpName, Arity, Values, Opts, Strict, Mod).
%% enc/6
-enc(_, AvpName, 1, undefined, _, _) ->
+enc(AvpName, Arity, Values, Opts, Strict, Mod)
+ when Strict /= encode, Arity /= ?ANY ->
+ enc(AvpName, ?ANY, Values, Opts, Strict, Mod);
+
+enc(AvpName, 1, undefined, _, _, _) ->
?THROW([mandatory_avp_missing, AvpName]);
-enc(Name, AvpName, 1, Value, Opts, Mod) ->
- enc(Name, AvpName, [Value], Opts, Mod);
+enc(AvpName, 1, Value, Opts, _, Mod) ->
+ H = avp_header(AvpName, Mod),
+ enc(AvpName, H, Value, Opts, Mod);
+
+enc(_, {0,_}, [], _, _, _) ->
+ [];
+
+enc(_, _, undefined, _, _, _) ->
+ [];
+
+%% Be forgiving when a list of values is expected. If the value itself
+%% is a list then the user has to wrap it to avoid each member from
+%% being interpreted as an individual AVP value.
+enc(AvpName, Arity, V, Opts, Strict, Mod)
+ when not is_list(V) ->
+ enc(AvpName, Arity, [V], Opts, Strict, Mod);
-enc(_, _, {0,_}, [], _, _) ->
+enc(AvpName, {Min, Max}, Values, Opts, Strict, Mod) ->
+ H = avp_header(AvpName, Mod),
+ enc(AvpName, H, Min, 0, Max, Values, Opts, Strict, Mod).
+
+%% enc/9
+
+enc(AvpName, H, Min, N, Max, Vs, Opts, Strict, Mod)
+ when Strict /= encode;
+ Max == '*', Min =< N ->
+ [enc(AvpName, H, V, Opts, Mod) || V <- Vs];
+
+enc(AvpName, _, Min, N, _, [], _, _, _)
+ when N < Min ->
+ ?THROW([repeated_avp_insufficient_arity, AvpName, Min, N]);
+
+enc(_, _, _, _, _, [], _, _, _) ->
[];
-enc(_, AvpName, _, T, _, _)
- when not is_list(T) ->
- ?THROW([repeated_avp_as_non_list, AvpName, T]);
+enc(AvpName, _, _, N, Max, _, _, _, _)
+ when Max =< N ->
+ ?THROW([repeated_avp_excessive_arity, AvpName, Max]);
+
+enc(AvpName, H, Min, N, Max, [V|Vs], Opts, Strict, Mod) ->
+ [enc(AvpName, H, V, Opts, Mod)
+ | enc(AvpName, H, Min, N+1, Max, Vs, Opts, Strict, Mod)].
-enc(_, AvpName, {Min, _}, L, _, _)
- when length(L) < Min ->
- ?THROW([repeated_avp_insufficient_arity, AvpName, Min, L]);
+%% avp_header/2
-enc(_, AvpName, {_, Max}, L, _, _)
- when Max < length(L) ->
- ?THROW([repeated_avp_excessive_arity, AvpName, Max, L]);
+avp_header('AVP', _) ->
+ false;
-enc(Name, AvpName, _, Values, Opts, Mod) ->
- enc(Name, AvpName, Values, Opts, Mod).
+avp_header(AvpName, Mod) ->
+ {_,_,_} = Mod:avp_header(AvpName).
%% enc/5
-enc(Name, 'AVP', Values, Opts, Mod) ->
- [enc_AVP(Name, A, Opts, Mod) || A <- Values];
+enc('AVP', false, Value, Opts, Mod) ->
+ enc_AVP(Value, Opts, Mod);
-enc(_, AvpName, Values, Opts, Mod) ->
- enc(AvpName, Values, Opts, Mod).
+enc(AvpName, Hdr, Value, Opts, Mod) ->
+ enc1(AvpName, Hdr, Value, Opts, Mod).
-%% enc/4
+%% enc1/5
-enc(AvpName, Values, Opts, Mod) ->
- H = Mod:avp_header(AvpName),
- [diameter_codec:pack_data(H, Mod:avp(encode, V, AvpName, Opts))
- || V <- Values].
+enc1(AvpName, {_,_,_} = Hdr, Value, Opts, Mod) ->
+ diameter_codec:pack_data(Hdr, Mod:avp(encode, Value, AvpName, Opts)).
-%% enc_AVP/4
+%% enc1/6
+
+enc1(AvpName, {_,_,_} = Hdr, Value, Opts, Mod, Dict) ->
+ diameter_codec:pack_data(Hdr, avp(encode, Value, AvpName, Opts, Mod, Dict)).
+
+%% enc_AVP/3
%% No value: assume AVP data is already encoded. The normal case will
%% be when this is passed back from #diameter_packet.errors as a
%% consequence of a failed decode. Any AVP can be encoded this way
%% however, which side-steps any arity checks for known AVP's and
%% could potentially encode something unfortunate.
-enc_AVP(_, #diameter_avp{value = undefined} = A, Opts, _) ->
+enc_AVP(#diameter_avp{value = undefined} = A, Opts, _) ->
diameter_codec:pack_avp(A, Opts);
-%% Missing name for value encode.
-enc_AVP(_, #diameter_avp{name = N, value = V}, _, _)
- when N == undefined;
- N == 'AVP' ->
- ?THROW([value_with_nameless_avp, N, V]);
+%% Encode a name/value pair using an alternate dictionary if need be ...
+enc_AVP(#diameter_avp{name = AvpName, value = Value}, Opts, Mod) ->
+ enc_AVP(AvpName, Value, Opts, Mod);
+enc_AVP({AvpName, Value}, Opts, Mod) ->
+ enc_AVP(AvpName, Value, Opts, Mod);
+
+%% ... or with a specified dictionary.
+enc_AVP({Dict, AvpName, Value}, Opts, Mod) ->
+ enc1(AvpName, Dict:avp_header(AvpName), Value, Opts, Mod, Dict).
+
+%% Don't guard against anything being sent as a generic 'AVP', which
+%% allows arity restrictions to be abused.
+
+%% enc_AVP/4
+
+enc_AVP(AvpName, Value, Opts, Mod) ->
+ try Mod:avp_header(AvpName) of
+ H ->
+ enc1(AvpName, H, Value, Opts, Mod)
+ catch
+ error: _ ->
+ Dicts = mget(avp_dictionaries, Opts, []),
+ enc_AVP(Dicts, AvpName, Value, Opts, Mod)
+ end.
-%% 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);
+%% enc_AVP/5
-%% The backdoor ...
-enc_AVP(_, {AvpName, Value}, Opts, Mod) ->
- enc(AvpName, [Value], Opts, Mod);
+enc_AVP([Dict | Rest], AvpName, Value, Opts, Mod) ->
+ try Dict:avp_header(AvpName) of
+ H ->
+ enc1(AvpName, H, Value, Opts, Mod, Dict)
+ catch
+ error: _ ->
+ enc_AVP(Rest, AvpName, Value, Opts, Mod)
+ end;
-%% ... and the side door.
-enc_AVP(_Name, {_Dict, _AvpName, _Data} = T, Opts, _) ->
- diameter_codec:pack_avp(#diameter_avp{data = T}, Opts).
+enc_AVP([], AvpName, _, _, _) ->
+ ?THROW([no_dictionary, AvpName]).
%% ---------------------------------------------------------------------------
%% # decode_avps/3
%% ---------------------------------------------------------------------------
--spec decode_avps(parent_name(), [#diameter_avp{}], map())
- -> {parent_record(), [avp()], Failed}
+-spec decode_avps(parent_name(), binary(), map())
+ -> {parent_record() | parent_name(), [avp()], Failed}
when Failed :: [{5000..5999, #diameter_avp{}}].
-decode_avps(Name, Recs, #{module := Mod} = Opts) ->
- {Avps, {Rec, Failed}}
- = mapfoldl(fun(T,A) -> decode(Name, Opts, Mod, T, A) end,
- {newrec(Mod, Name), []},
- Recs),
- {Rec, Avps, Failed ++ missing(Rec, Name, Failed, Opts, Mod)}.
-%% Append 5005 errors so that errors are reported in the order
+decode_avps(Name, Bin, #{module := Mod, decode_format := Fmt} = Opts) ->
+ Strict = mget(strict_arities, Opts, decode),
+ [AM, Avps, Failed | Rec]
+ = decode(Bin, Name, Mod, Fmt, Strict, Opts, 0, #{}),
+ %% AM counts the number of top-level AVPs, which missing/5 then
+ %% uses when appending 5005 errors.
+ {reformat(Name, Rec, Strict, Mod, Fmt),
+ Avps,
+ Failed ++ missing(Name, Strict, Mod, Opts, AM)}.
+
+%% Append arity errors so that errors are reported in the order
%% encountered. Failed-AVP should typically contain the first
-%% encountered error accordg to the RFC.
+%% error encountered.
+
+%% decode/8
+
+decode(<<Code:32, V:1, M:1, P:1, _:5, Len:24, I:V/unit:32, Rest/binary>>,
+ Name,
+ Mod,
+ Fmt,
+ Strict,
+ Opts,
+ Idx,
+ AM) ->
+ decode(Rest,
+ Code,
+ if 1 == V -> I; true -> undefined end,
+ Len - 8 - 4*V, %% possibly negative, causing case match to fail
+ (4 - (Len rem 4)) rem 4,
+ 1 == M,
+ 1 == P,
+ Name,
+ Mod,
+ Fmt,
+ Strict,
+ Opts,
+ Idx,
+ AM);
+
+decode(<<>>, Name, Mod, Fmt, Strict, _, _, AM) ->
+ [AM, [], [] | newrec(Fmt, Mod, Name, Strict)];
+
+decode(Bin, Name, Mod, Fmt, Strict, _, Idx, AM) ->
+ Avp = #diameter_avp{data = Bin, index = Idx},
+ [AM, [Avp], [{5014, Avp}] | newrec(Fmt, Mod, Name, Strict)].
+
+%% decode/14
+
+decode(Bin, Code, Vid, DataLen, Pad, M, P, Name, Mod, Fmt, Strict, Opts0,
+ Idx, AM0) ->
+ case Bin of
+ <<Data:DataLen/binary, _:Pad/binary, T/binary>> ->
+ {NameT, Field, Arity, {I, AM}}
+ = incr(Name, Code, Vid, M, Mod, Strict, Opts0, AM0),
+
+ Opts = setopts(NameT, Name, M, Opts0),
+ %% Not AvpName or else a failed Failed-AVP
+ %% decode is packed into 'AVP'.
+
+ Avp = #diameter_avp{code = Code,
+ vendor_id = Vid,
+ is_mandatory = M,
+ need_encryption = P,
+ data = Data,
+ name = name(NameT),
+ type = type(NameT),
+ index = Idx},
+
+ Dec = dec(Data, Name, NameT, Mod, Fmt, Opts, Avp),
+ Acc = decode(T, Name, Mod, Fmt, Strict, Opts, Idx+1, AM),%% recurse
+ acc(Acc, Dec, I, Field, Arity, Strict, Mod, Opts);
+ _ ->
+ {NameT, _Field, _Arity, {_, AM}}
+ = incr(Name, Code, Vid, M, Mod, Strict, Opts0, AM0),
+
+ Avp = #diameter_avp{code = Code,
+ vendor_id = Vid,
+ is_mandatory = M,
+ need_encryption = P,
+ data = Bin,
+ name = name(NameT),
+ type = type(NameT),
+ index = Idx},
+
+ [AM, [Avp], [{5014, Avp}] | newrec(Fmt, Mod, Name, Strict)]
+ end.
+
+%% incr/8
-%% mapfoldl/3
+incr(Name, Code, Vid, M, Mod, Strict, Opts, AM0) ->
+ NameT = Mod:avp_name(Code, Vid), %% {AvpName, Type} | 'AVP'
+ Field = field(NameT), %% AvpName | 'AVP'
+ Arity = avp_arity(Name, Field, Mod, Opts, M),
+ if 0 == Arity, 'AVP' /= Field ->
+ A = pack_arity(Name, Field, Opts, Mod, M),
+ {NameT, 'AVP', A, incr('AVP', A, Strict, AM0)};
+ true ->
+ {NameT, Field, Arity, incr(Field, Arity, Strict, AM0)}
+ end.
+
+%% Data is a truncated header if command_code = undefined, otherwise
+%% payload bytes. The former is padded to the length of a header if
+%% the AVP reaches an outgoing encode.
%%
-%% Like lists:mapfoldl/3, but don't reverse the list.
+%% RFC 6733 says that an AVP returned with 5014 can contain a minimal
+%% payload for the AVP's type, but don't always know the type.
-mapfoldl(F, Acc, List) ->
- mapfoldl(F, Acc, List, []).
+setopts('AVP', _, _, Opts) ->
+ Opts;
-mapfoldl(F, Acc0, [T|Rest], List) ->
- {B, Acc} = F(T, Acc0),
- mapfoldl(F, Acc, Rest, [B|List]);
-mapfoldl(_, Acc, [], List) ->
- {List, Acc}.
+setopts({_, Type}, Name, M, Opts) ->
+ set_failed(Name, set_strict(Type, M, Opts)).
-%% 3588:
+%% incr/4
+
+incr(_, A, SA, AM)
+ when A == ?ANY;
+ A == 0;
+ SA /= decode ->
+ {undefined, AM};
+
+incr(AvpName, _, _, AM) ->
+ case AM of
+ #{AvpName := N} ->
+ {N, AM#{AvpName => N+1}};
+ _ ->
+ {0, AM#{AvpName => 1}}
+ end.
+
+%% mget/3
+%%
+%% Measurably faster than maps:get/3.
+
+mget(Key, Map, Def) ->
+ case Map of
+ #{Key := V} ->
+ V;
+ _ ->
+ Def
+ end.
+
+%% name/1
+
+name({Name, _}) ->
+ Name;
+name(_) ->
+ undefined.
+
+%% type/1
+
+type({_, Type}) ->
+ Type;
+type(_) ->
+ undefined.
+
+%% missing/5
+
+missing(Name, decode, Mod, Opts, AM) ->
+ [{5005, empty_avp(N, Opts, Mod)} || {N,A} <- Mod:avp_arity(Name),
+ N /= 'AVP',
+ Mn <- [min_arity(A)],
+ 0 < Mn,
+ mget(N, AM, 0) < Mn];
+
+missing(_, _, _, _, _) ->
+ [].
+
+%% 3588/6733:
%%
%% DIAMETER_MISSING_AVP 5005
%% The request did not contain an AVP that is required by the Command
@@ -204,57 +428,20 @@ mapfoldl(_, Acc, [], List) ->
%% Vendor-Id if applicable. The value field of the missing AVP
%% should be of correct minimum length and contain zeros.
-missing(Rec, Name, Failed, Opts, Mod) ->
- Avps = lists:foldl(fun({_, #diameter_avp{code = C, vendor_id = V}}, A) ->
- maps:put({C,V}, true, A)
- end,
- maps:new(),
- Failed),
- missing(Mod:avp_arity(Name), tl(tuple_to_list(Rec)), Avps, Opts, Mod, []).
-
-missing([{Name, Arity} | As], [Value | Vs], Avps, Opts, Mod, Acc) ->
- missing(As,
- Vs,
- Avps,
- Opts,
- Mod,
- case
- [H || missing_arity(Arity, Value),
- {C,_,V} = H <- [Mod:avp_header(Name)],
- not maps:is_key({C,V}, Avps)]
- of
- [H] ->
- [{5005, empty_avp(Name, H, Opts, Mod)} | Acc];
- [] ->
- Acc
- end);
-
-missing([], [], _, _, _, Acc) ->
- Acc.
-
-%% Maximum arities have already been checked in building the record.
-
-missing_arity(1, V) ->
- V == undefined;
-missing_arity({0, _}, _) ->
- false;
-missing_arity({1, _}, L) ->
- [] == L;
-missing_arity({Min, _}, L) ->
- not has_prefix(Min, L).
-
-%% Compare a non-negative integer and the length of a list without
-%% computing the length.
-has_prefix(0, _) ->
- true;
-has_prefix(_, []) ->
- false;
-has_prefix(N, [_|L]) ->
- has_prefix(N-1, L).
+%% min_arity/1
+
+min_arity(1) ->
+ 1;
+min_arity({Mn,_}) ->
+ Mn.
-%% empty_avp/4
+%% empty_avp/3
-empty_avp(Name, {Code, Flags, VId}, Opts, Mod) ->
+empty_avp('AVP', _, _) ->
+ #diameter_avp{data = <<0:64>>};
+
+empty_avp(Name, Opts, Mod) ->
+ {Code, Flags, VId} = Mod:avp_header(Name),
{Name, Type} = Mod:avp_name(Code, VId),
#diameter_avp{name = Name,
code = Code,
@@ -273,21 +460,19 @@ empty_avp(Name, {Code, Flags, VId}, Opts, Mod) ->
%% specific errors that can be described by this AVP are described in
%% the following section.
-%% decode/5
+%% field/1
-decode(Name,
- Opts,
- Mod,
- #diameter_avp{code = Code, vendor_id = Vid}
- = Avp,
- Acc) ->
- decode(Name, Opts, Mod, Mod:avp_name(Code, Vid), Avp, Acc).
+field({AvpName, _}) ->
+ AvpName;
+field(_) ->
+ 'AVP'.
-%% decode/6
+%% dec/7
-%% AVP not in dictionary.
-decode(Name, Opts, Mod, 'AVP', Avp, Acc) ->
- decode_AVP(Name, Avp, Opts, Mod, Acc);
+%% AVP not in dictionary: try an alternate.
+
+dec(Data, Name, 'AVP', Mod, Fmt, Opts, Avp) ->
+ dec_AVP(dicts(Mod, Opts), Data, Name, Mod, Fmt, Opts, Avp);
%% 6733, 4.4:
%%
@@ -336,130 +521,149 @@ decode(Name, Opts, Mod, 'AVP', Avp, Acc) ->
%% defined the RFC's "unrecognized", which is slightly stronger than
%% "not defined".)
-decode(Name, Opts0, Mod, {AvpName, Type}, Avp, Acc) ->
- #diameter_avp{data = Data, is_mandatory = M}
- = Avp,
+dec(Data, Name, {AvpName, Type}, Mod, Fmt, Opts, Avp) ->
+ #{app_dictionary := AppMod, failed_avp := Failed}
+ = Opts,
- %% Whether or not to ignore an M-bit on an encapsulated AVP, or on
- %% all AVPs with the service_opt() strict_mbit.
- Opts1 = set_strict(Type, M, Opts0),
+ %% Reset the dictionary for best-effort decode of Failed-AVP.
+ Dict = if Failed -> AppMod;
+ true -> Mod
+ end,
- %% Whether or not we're decoding within Failed-AVP and should
- %% ignore decode errors.
- #{dictionary := AppMod, failed_avp := Failed}
- = Opts
- = set_failed(Name, Opts1), %% Not AvpName or else a failed Failed-AVP
- %% decode is packed into 'AVP'.
+ dec(Data, Name, AvpName, Type, Mod, Dict, Fmt, Failed, Opts, Avp).
- %% Reset the dictionary for best-effort decode of Failed-AVP.
- DecMod = if Failed ->
- AppMod;
- true ->
- Mod
- end,
-
- %% On decode, a Grouped AVP is represented as a #diameter_avp{}
- %% list with AVP as head and component AVPs as tail. On encode,
- %% data can be a list of component AVPs.
-
- try avp_decode(Data, AvpName, Opts, DecMod, Mod) of
- {Rec, As} when Type == 'Grouped' ->
- A = Avp#diameter_avp{name = AvpName,
- value = Rec,
- type = Type},
- {[A|As], pack_avp(Name, A, Opts, Mod, Acc)};
+%% dicts/2
- V when Type /= 'Grouped' ->
- A = Avp#diameter_avp{name = AvpName,
- value = V,
- type = Type},
- {A, pack_avp(Name, A, Opts, Mod, Acc)}
- catch
- throw: {?MODULE, {grouped, Error, ComponentAvps}} ->
- decode_error(Name,
- Error,
- ComponentAvps,
- Opts,
- Mod,
- Avp#diameter_avp{name = AvpName,
- data = trim(Avp#diameter_avp.data),
- type = Type},
- Acc);
+dicts(Mod, #{app_dictionary := Mod, avp_dictionaries := Dicts}) ->
+ Dicts;
+
+dicts(_, #{app_dictionary := Dict, avp_dictionaries := Dicts}) ->
+ [Dict | Dicts];
+dicts(Mod, #{app_dictionary := Mod}) ->
+ [];
+
+dicts(_, #{app_dictionary := Dict}) ->
+ [Dict].
+
+%% dec/10
+
+dec(Data, Name, AvpName, Type, Mod, Dict, Fmt, Failed, Opts, Avp) ->
+ try avp(decode, Data, AvpName, Opts, Mod, Dict) of
+ V ->
+ set(Type, Fmt, Avp, V)
+ catch
+ throw: {?MODULE, T} ->
+ decode_error(Failed, Fmt, T, Avp);
error: Reason ->
- decode_error(Name,
- Reason,
- Opts,
- Mod,
- Avp#diameter_avp{name = AvpName,
- data = trim(Avp#diameter_avp.data),
- type = Type},
- Acc)
+ decode_error(Failed, Reason, Name, Mod, Opts, Avp)
end.
-%% avp_decode/5
+%% dec_AVP/7
-avp_decode(Data, AvpName, Opts, Mod, Mod) ->
- Mod:avp(decode, Data, AvpName, Opts);
+dec_AVP([], _, _, _, _, _, Avp) ->
+ Avp;
-avp_decode(Data, AvpName, Opts, Mod, _) ->
- Mod:avp(decode, Data, AvpName, Opts, Mod).
+dec_AVP(Dicts, Data, Name, Mod, Fmt, Opts, #diameter_avp{code = Code,
+ vendor_id = Vid}
+ = Avp) ->
+ dec_AVP(Dicts, Data, Name, Mod, Fmt, Opts, Code, Vid, Avp).
-%% trim/1
+%% dec_AVP/9
%%
-%% Remove any extra bit that was added in diameter_codec to induce a
-%% 5014 error.
+%% Try to decode an AVP in the first alternate dictionary that defines
+%% it.
+
+dec_AVP([Dict | Rest], Data, Name, Mod, Fmt, Opts, Code, Vid, Avp) ->
+ case Dict:avp_name(Code, Vid) of
+ {AvpName, Type} ->
+ A = Avp#diameter_avp{name = AvpName,
+ type = Type},
+ #{failed_avp := Failed} = Opts,
+ dec(Data, Name, AvpName, Type, Mod, Dict, Fmt, Failed, Opts, A);
+ _ ->
+ dec_AVP(Rest, Data, Name, Mod, Fmt, Opts, Code, Vid, Avp)
+ end;
-trim(#diameter_avp{data = Data} = Avp) ->
- Avp#diameter_avp{data = trim(Data)};
+dec_AVP([], _, _, _, _, _, _, _, Avp) ->
+ Avp.
-trim({5014, Bin}) ->
- Bin;
+%% set/4
+%%
+%% A Grouped AVP is represented as a #diameter_avp{} list with AVP
+%% as head and component AVPs as tail.
-trim(Avps)
- when is_list(Avps) ->
- lists:map(fun trim/1, Avps);
+set('Grouped', Fmt, Avp, V) ->
+ {Rec, As} = V,
+ [set(Fmt, Avp, Rec) | As];
-trim(Avp) ->
- Avp.
+set(_, _, Avp, V) ->
+ Avp#diameter_avp{value = V}.
-%% decode_error/7
+%% decode_error/4
+%%
+%% Error when decoding a grouped AVP.
-decode_error(Name, [_ | Rec], _, #{failed_avp := true} = Opts, Mod, Avp, Acc) ->
- decode_AVP(Name, Avp#diameter_avp{value = Rec}, Opts, Mod, Acc);
+%% Ignoring errors in Failed-AVP.
+decode_error(true, Fmt, {Rec, ComponentAvps, _Errors}, Avp) ->
+ [set(Fmt, Avp, Rec) | ComponentAvps];
-decode_error(Name, _, _, #{failed_avp := true} = Opts, Mod, Avp, Acc) ->
- decode_AVP(Name, Avp, Opts, Mod, Acc);
+%% Or not. A faulty component is encoded by itself in Failed-AVP, as
+%% suggested by 7.5 of RFC 6733 (quoted below), so that errors are
+%% reported unambigiously.
+decode_error(false, _, {_, ComponentAvps, [{RC,A} | _]}, Avp) ->
+ {RC, [Avp | ComponentAvps], Avp#diameter_avp{data = [A]}}.
-decode_error(_, [Error | _], ComponentAvps, _, _, Avp, Acc) ->
- decode_error(Error, Avp, Acc, ComponentAvps);
+%% set/3
-decode_error(_, Error, ComponentAvps, _, _, Avp, Acc) ->
- decode_error(Error, Avp, Acc, ComponentAvps).
+set(none, Avp, _Name) ->
+ Avp;
+set(_, Avp, Rec) ->
+ Avp#diameter_avp{value = Rec}.
-%% decode_error/5
+%% decode_error/6
+%%
+%% Error when decoding a non-grouped AVP.
-decode_error(Name, _Reason, #{failed_avp := true} = Opts, Mod, Avp, Acc) ->
- decode_AVP(Name, Avp, Opts, Mod, Acc);
+decode_error(true, _, _, _, _, Avp) ->
+ Avp;
-decode_error(Name, Reason, Opts, Mod, Avp, {Rec, Failed}) ->
+decode_error(false, Reason, Name, Mod, Opts, Avp) ->
Stack = diameter_lib:get_stacktrace(),
diameter_lib:log(decode_error,
?MODULE,
?LINE,
{Reason, Name, Avp#diameter_avp.name, Mod, Stack}),
- {Avp, {Rec, [rc(Reason, Avp, Opts, Mod) | Failed]}}.
+ case Reason of
+ {'DIAMETER', 5014 = RC, _} ->
+ %% Length error communicated from diameter_types or a
+ %% @custom_types/@codecs module.
+ AvpName = Avp#diameter_avp.name,
+ {RC, Avp#diameter_avp{data = Mod:empty_value(AvpName, Opts)}};
+ _ ->
+ {5004, Avp}
+ end.
-%% decode_error/4
+%% 3588/6733:
+%%
+%% DIAMETER_INVALID_AVP_VALUE 5004
+%% The request contained an AVP with an invalid value in its data
+%% portion. A Diameter message indicating this error MUST include
+%% the offending AVPs within a Failed-AVP AVP.
-decode_error({RC, ErrorData}, Avp, {Rec, Failed}, ComponentAvps) ->
- E = Avp#diameter_avp{data = [ErrorData]},
- {[Avp | trim(ComponentAvps)], {Rec, [{RC, E} | Failed]}}.
+%% avp/6
-%% set_strict/3
+avp(T, Data, AvpName, Opts, Mod, Mod) ->
+ Mod:avp(T, Data, AvpName, Opts);
+
+avp(T, Data, AvpName, Opts, _, Mod) ->
+ Mod:avp(T, Data, AvpName, Opts#{module := Mod}).
+%% set_strict/3
+%%
%% Set false as soon as we see a Grouped AVP that doesn't set the
%% M-bit, to ignore the M-bit on an encapsulated AVP.
+
set_strict('Grouped', false = M, #{strict_mbit := true} = Opts) ->
Opts#{strict_mbit := M};
set_strict(_, _, Opts) ->
@@ -476,102 +680,84 @@ set_failed('Failed-AVP', #{failed_avp := false} = Opts) ->
set_failed(_, Opts) ->
Opts.
-%% decode_AVP/5
-%%
-%% Don't know this AVP: see if it can be packed in an 'AVP' field
-%% undecoded. Note that the type field is 'undefined' in this case.
-
-decode_AVP(Name, Avp, Opts, Mod, Acc) ->
- {trim(Avp), pack_AVP(Name, Avp, Opts, Mod, Acc)}.
-
-%% rc/2
-
-%% diameter_types will raise an error of this form to communicate
-%% DIAMETER_INVALID_AVP_LENGTH (5014). A module specified to a
-%% @custom_types tag in a dictionary file can also raise an error of
-%% this form.
-rc({'DIAMETER', 5014 = RC, _}, #diameter_avp{name = AvpName} = Avp, Opts, Mod) ->
- {RC, Avp#diameter_avp{data = Mod:empty_value(AvpName, Opts)}};
-
-%% 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
+%% acc/8
+
+acc([AM | Acc], As, I, Field, Arity, Strict, Mod, Opts) ->
+ [AM | acc1(Acc, As, I, Field, Arity, Strict, Mod, Opts)].
+
+%% acc1/8
+
+%% Faulty AVP, not grouped.
+acc1(Acc, {_RC, Avp} = E, _, _, _, _, _, _) ->
+ [Avps, Failed | Rec] = Acc,
+ [[Avp | Avps], [E | Failed] | Rec];
+
+%% Faulty component in grouped AVP.
+acc1(Acc, {RC, As, Avp}, _, _, _, _, _, _) ->
+ [Avps, Failed | Rec] = Acc,
+ [[As | Avps], [{RC, Avp} | Failed] | Rec];
+
+%% Grouped AVP ...
+acc1([Avps | Acc], [Avp|_] = As, I, Field, Arity, Strict, Mod, Opts) ->
+ [[As|Avps] | acc2(Acc, Avp, I, Field, Arity, Strict, Mod, Opts)];
+
+%% ... or not.
+acc1([Avps | Acc], Avp, I, Field, Arity, Strict, Mod, Opts) ->
+ [[Avp|Avps] | acc2(Acc, Avp, I, Field, Arity, Strict, Mod, Opts)].
+
+%% The component list of a Grouped AVP is discarded when packing into
+%% the record (or equivalent): the values in an 'AVP' field are
+%% diameter_avp records, not a list of records in the Grouped case,
+%% and the decode into the value field is best-effort. The reason is
+%% history more than logic: it would probably have made more sense to
+%% retain the same structure as in diameter_packet.avps, but an 'AVP'
+%% list has always been flat.
+
+%% acc2/8
+
+%% No errors, but nowhere to pack.
+acc2(Acc, Avp, _, 'AVP', 0, _, _, _) ->
+ [Failed | Rec] = Acc,
+ [[{rc(Avp), Avp} | Failed] | Rec];
+
+%% Relaxed arities.
+acc2(Acc, Avp, _, Field, Arity, Strict, Mod, _)
+ when Strict /= decode ->
+ pack(Arity, Field, Avp, Mod, Acc);
+
+%% No maximum arity.
+acc2(Acc, Avp, _, Field, {_,'*'} = Arity, _, Mod, _) ->
+ pack(Arity, Field, Avp, Mod, Acc);
+
+%% Or check.
+acc2(Acc, Avp, I, Field, Arity, _, Mod, _) ->
+ Mx = max_arity(Arity),
+ if Mx =< I ->
+ [Failed | Rec] = Acc,
+ [[{5009, Avp} | Failed] | Rec];
+ true ->
+ pack(Arity, Field, Avp, Mod, Acc)
+ end.
-%% 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.
+%% 3588/6733:
%%
-%% 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).
+%% 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
-%% 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.
+%% max_arity/1
-pack_arity(Name,
- #{strict_mbit := Strict,
- failed_avp := Failed},
- Mod,
- #diameter_avp{is_mandatory = M,
- name = AvpName}) ->
+max_arity(1) ->
+ 1;
+max_arity({_,Mx}) ->
+ Mx.
- %% 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.
+%% rc/1
- if Failed == true;
- Name == 'Failed-AVP';
- Name == 'answer-message', AvpName == 'Failed-AVP';
- not M;
- not Strict ->
- Mod:avp_arity(Name, 'AVP');
- true ->
- 0
- end.
+rc(#diameter_avp{is_mandatory = M}) ->
+ if M -> 5001; true -> 5008 end.
%% 3588:
%%
@@ -586,75 +772,99 @@ pack_arity(Name,
%% Failed-AVP AVP MUST be included and contain a copy of the
%% offending AVP.
+%% pack_arity/5
+
+%% Give Failed-AVP special treatment since (1) it'll contain any
+%% unrecognized mandatory AVP's and (2) the RFC 3588 grammar failed to
+%% allow for Failed-AVP in an answer-message.
+
+pack_arity(Name, AvpName, _, Mod, M)
+ when Name == 'Failed-AVP';
+ Name == 'answer-message', AvpName == 'Failed-AVP';
+ not M ->
+ Mod:avp_arity(Name, 'AVP');
+%% Not testing just Name /= 'Failed-AVP' means we're changing the
+%% packing of AVPs nested within Failed-AVP, but the point of
+%% ignoring errors within Failed-AVP is to decode as much as
+%% possible, and failing because a mandatory AVP couldn't be
+%% packed into a dedicated field defeats that point.
+
+pack_arity(Name, _, #{strict_mbit := Strict, failed_avp := Failed}, Mod, _)
+ when not Strict;
+ Failed ->
+ Mod:avp_arity(Name, 'AVP');
+
+pack_arity(_, _, _, _, _) ->
+ 0.
+
+%% avp_arity/5
+
+avp_arity(Name, 'AVP' = AvpName, Mod, Opts, M) ->
+ pack_arity(Name, AvpName, Opts, Mod, M);
+
+avp_arity(Name, AvpName, Mod, _, _) ->
+ Mod:avp_arity(Name, AvpName).
+
%% pack/5
-pack(Arity, FieldName, Avp, Mod, {Rec, _} = Acc) ->
- pack(Mod:'#get-'(FieldName, Rec), Arity, FieldName, Avp, Mod, Acc).
+pack(Arity, F, Avp, Mod, [Failed | Rec]) ->
+ [Failed | set(Arity, F, value(F, Avp), Mod, Rec)].
-%% pack/6
+%% set/5
-pack(undefined, 1, 'AVP' = F, Avp, Mod, {Rec, Failed}) -> %% unlikely
- {Mod:'#set-'({F, Avp}, Rec), Failed};
+set(_, _, _, _, Name)
+ when is_atom(Name) ->
+ Name;
-pack(undefined, 1, F, #diameter_avp{value = V}, Mod, {Rec, Failed}) ->
- {Mod:'#set-'({F, V}, Rec), Failed};
+set(1, F, Value, _, Map)
+ when is_map(Map) ->
+ Map#{F => Value};
-%% 3588:
-%%
-%% DIAMETER_AVP_OCCURS_TOO_MANY_TIMES 5009
-%% A message was received that included an AVP that appeared more
-%% often than permitted in the message definition. The Failed-AVP
-%% AVP MUST be included and contain a copy of the first instance of
-%% the offending AVP that exceeded the maximum number of occurrences
-%%
+set(_, F, V, _, Map)
+ when is_map(Map) ->
+ maps:update_with(F, fun(Vs) -> [V|Vs] end, [V], Map);
-pack(_, 1, _, Avp, _, {Rec, Failed}) ->
- {Rec, [{5009, Avp} | Failed]};
-
-pack(L, {_, Max}, F, Avp, Mod, {Rec, Failed}) ->
- case '*' /= Max andalso has_prefix(Max+1, L) of
- true ->
- {Rec, [{5009, Avp} | Failed]};
- false when F == 'AVP' ->
- {Mod:'#set-'({F, [Avp | L]}, Rec), Failed};
- false ->
- {Mod:'#set-'({F, [Avp#diameter_avp.value | L]}, Rec), Failed}
- end.
+set(1, F, Value, Mod, Rec) ->
+ Mod:'#set-'({F, Value}, Rec);
+
+set(_, F, V, Mod, Rec) ->
+ Vs = Mod:'#get-'(F, Rec),
+ Mod:'#set-'({F, [V|Vs]}, Rec).
+
+%% value/2
+
+value('AVP', Avp) ->
+ Avp;
+
+value(_, #diameter_avp{value = V}) ->
+ V.
%% ---------------------------------------------------------------------------
%% # grouped_avp/3
%% ---------------------------------------------------------------------------
--spec grouped_avp(decode, avp_name(), binary() | {5014, binary()}, term())
+%% Note that Grouped is the only AVP type that doesn't just return a
+%% decoded value, also returning the list of component diameter_avp
+%% records.
+
+-spec grouped_avp(decode, avp_name(), binary(), term())
-> {avp_record(), [avp()]};
(encode, avp_name(), avp_record() | avp_values(), term())
-> iolist()
| no_return().
-%% Length error induced by diameter_codec:collect_avps/1: the AVP
-%% length in the header was too short (insufficient for the extracted
-%% header) or too long (past the end of the message). An empty payload
-%% is sufficient according to the RFC text for 5014.
-grouped_avp(decode, _Name, {5014 = RC, _Bin}, _) ->
- ?THROW({grouped, {RC, []}, []});
-
-grouped_avp(decode, Name, Data, Opts) ->
- grouped_decode(Name, diameter_codec:collect_avps(Data), Opts);
+%% An error in decoding a component AVP throws the first faulty
+%% component, which a catch wraps in the Grouped AVP in question. A
+%% partially decoded record is only used when ignoring errors in
+%% Failed-AVP.
+grouped_avp(decode, Name, Bin, Opts) ->
+ {Rec, Avps, Es} = T = decode_avps(Name, Bin, Opts),
+ [] == Es orelse ?THROW(T),
+ {Rec, Avps};
grouped_avp(encode, Name, Data, Opts) ->
encode_avps(Name, Data, Opts).
-%% grouped_decode/2
-%%
-%% Note that Grouped is the only AVP type that doesn't just return a
-%% decoded value, also returning the list of component diameter_avp
-%% records.
-
-%% Length error in trailing component AVP.
-grouped_decode(_Name, {Error, Acc}, _) ->
- {5014, Avp} = Error,
- ?THROW({grouped, Error, [Avp | Acc]});
-
%% 7.5. Failed-AVP AVP
%% In the case where the offending AVP is embedded within a Grouped AVP,
@@ -665,15 +875,6 @@ grouped_decode(_Name, {Error, Acc}, _) ->
%% to the single offending AVP. This enables the recipient to detect
%% the location of the offending AVP when embedded in a group.
-%% An error in decoding a component AVP throws the first faulty
-%% component, which the catch in d/3 wraps in the Grouped AVP in
-%% question. A partially decoded record is only used when ignoring
-%% errors in Failed-AVP.
-grouped_decode(Name, ComponentAvps, Opts) ->
- {Rec, Avps, Es} = decode_avps(Name, ComponentAvps, Opts),
- [] == Es orelse ?THROW({grouped, [{_,_} = hd(Es) | Rec], Avps}),
- {Rec, Avps}.
-
%% ---------------------------------------------------------------------------
%% # empty_group/2
%% ---------------------------------------------------------------------------
@@ -705,5 +906,45 @@ empty(Name, #{module := Mod} = Opts) ->
%% ------------------------------------------------------------------------------
+%% newrec/4
+
+newrec(none, _, Name, _) ->
+ Name;
+
+newrec(record, Mod, Name, T)
+ when T /= decode ->
+ RecName = Mod:name2rec(Name),
+ Sz = Mod:'#info-'(RecName, size),
+ erlang:make_tuple(Sz, [], [{1, RecName}]);
+
+newrec(record, Mod, Name, _) ->
+ newrec(Mod, Name);
+
+newrec(_, _, _, _) ->
+ #{}.
+
+%% newrec/2
+
newrec(Mod, Name) ->
Mod:'#new-'(Mod:name2rec(Name)).
+
+%% reformat/5
+
+reformat(Name, Map, _Strict, Mod, list) ->
+ [{F,V} || {F,_} <- Mod:avp_arity(Name), #{F := V} <- [Map]];
+
+reformat(Name, Map, Strict, Mod, record_from_map) ->
+ RecName = Mod:name2rec(Name),
+ list_to_tuple([RecName | [mget(F, Map, def(A, Strict))
+ || {F,A} <- Mod:avp_arity(Name)]]);
+
+reformat(_, Rec, _, _, _) ->
+ Rec.
+
+%% def/2
+
+def(1, decode) ->
+ undefined;
+
+def(_, _) ->
+ [].
diff --git a/lib/diameter/src/base/diameter_lib.erl b/lib/diameter/src/base/diameter_lib.erl
index 8792e97621..1c1ea42cb5 100644
--- a/lib/diameter/src/base/diameter_lib.erl
+++ b/lib/diameter/src/base/diameter_lib.erl
@@ -283,7 +283,7 @@ ip(T)
%% Or not: convert from '.'/':'-separated decimal/hex.
ip(Addr) ->
- {ok, A} = inet_parse:address(Addr), %% documented in inet(3)
+ {ok, A} = inet:parse_address(Addr),
A.
%% ---------------------------------------------------------------------------
diff --git a/lib/diameter/src/base/diameter_peer.erl b/lib/diameter/src/base/diameter_peer.erl
index 2759f17e64..4cb5a57a54 100644
--- a/lib/diameter/src/base/diameter_peer.erl
+++ b/lib/diameter/src/base/diameter_peer.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2015. 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.
@@ -202,10 +202,10 @@ match1(Addr, Match) ->
match(Addr, {ok, A}, _) ->
Addr == A;
match(Addr, {error, _}, RE) ->
- match == re:run(inet_parse:ntoa(Addr), RE, [{capture, none}]).
+ match == re:run(inet:ntoa(Addr), RE, [{capture, none}, caseless]).
addr([_|_] = A) ->
- inet_parse:address(A);
+ inet:parse_address(A);
addr(A) ->
{ok, A}.
diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl
index 1b0dc417e5..d99f11a697 100644
--- a/lib/diameter/src/base/diameter_peer_fsm.erl
+++ b/lib/diameter/src/base/diameter_peer_fsm.erl
@@ -128,7 +128,8 @@
%% outgoing DPR; boolean says whether or not
%% the request was sent explicitly with
%% diameter:call/4.
- codec :: #{string_decode := boolean(),
+ codec :: #{decode_format := diameter:decode_format(),
+ string_decode := boolean(),
strict_mbit := boolean(),
rfc := 3588 | 6733,
ordered_encode := false},
@@ -237,7 +238,7 @@ i({Ack, WPid, {M, Ref} = T, Opts, {SvcOpts, Nodes, Dict0, Svc}}) ->
proplists:get_value(dpa_timeout, Opts, ?DPA_TIMEOUT)}),
Tmo = proplists:get_value(capx_timeout, Opts, ?CAPX_TIMEOUT),
- Strictness = proplists:get_value(capx_strictness, Opts, true),
+ Strict = proplists:get_value(strict_capx, Opts, true),
LengthErr = proplists:get_value(length_errors, Opts, exit),
{TPid, Addrs} = start_transport(T, Rest, Svc),
@@ -251,9 +252,10 @@ i({Ack, WPid, {M, Ref} = T, Opts, {SvcOpts, Nodes, Dict0, Svc}}) ->
mode = M,
service = svc(Svc, Addrs),
length_errors = LengthErr,
- strict = Strictness,
+ strict = Strict,
incoming_maxlen = Maxlen,
- codec = maps:with([string_decode,
+ codec = maps:with([decode_format,
+ string_decode,
strict_mbit,
rfc,
ordered_encode],
@@ -542,11 +544,11 @@ put_route(Pid) ->
MRef = monitor(process, Pid),
put(Pid, MRef).
-%% get_route/2
+%% get_route/3
-%% incoming answer
-get_route(_, #diameter_packet{header = #diameter_header{is_request = false}}
- = Pkt) ->
+%% Incoming answer.
+get_route(_, _, #diameter_packet{header = #diameter_header{is_request = false}}
+ = Pkt) ->
Seqs = diameter_codec:sequence_numbers(Pkt),
case erase(Seqs) of
{Pid, Ref, MRef} ->
@@ -557,8 +559,14 @@ get_route(_, #diameter_packet{header = #diameter_header{is_request = false}}
false
end;
-%% incoming request
-get_route(Ack, _) ->
+%% Requests answered here ...
+get_route(_, N, _)
+ when N == 'CER';
+ N == 'DPR' ->
+ false;
+
+%% ... or not.
+get_route(Ack, _, _) ->
Ack.
%% erase_route/1
@@ -649,10 +657,6 @@ encode(Rec, Opts, Dict) ->
%% incoming/2
-incoming({recv = T, Name, Pkt}, #state{parent = Pid, ack = Ack} = S) ->
- Pid ! {T, self(), get_route(Ack, Pkt), Name, Pkt},
- rcv(Name, Pkt, S);
-
incoming(#diameter_header{is_request = R}, #state{transport = TPid,
ack = Ack}) ->
R andalso Ack andalso send(TPid, false),
@@ -670,98 +674,97 @@ incoming(T, _) ->
%% recv/2
-recv(#diameter_packet{header = #diameter_header{} = Hdr}
- = Pkt,
- #state{dictionary = Dict0}
- = S) ->
- recv1(diameter_codec:msg_name(Dict0, Hdr), Pkt, S);
-
-recv(#diameter_packet{header = undefined,
- bin = Bin}
- = Pkt,
- S) ->
- recv(diameter_codec:decode_header(Bin), Pkt, S);
+recv(#diameter_packet{bin = Bin} = Pkt, S) ->
+ recv(Bin, Pkt, S);
recv(Bin, S) ->
- recv(#diameter_packet{bin = Bin}, S).
+ recv(Bin, Bin, S).
+
+%% recv/3
+
+recv(Bin, Msg, S) ->
+ recv(diameter_codec:decode_header(Bin), Bin, Msg, S).
-%% recv1/3
+%% recv/4
-recv1(_,
- #diameter_packet{header = H, bin = Bin},
- #state{incoming_maxlen = M})
+recv(false, Bin, _, #state{length_errors = E}) ->
+ invalid(E, truncated_header, Bin),
+ Bin;
+
+recv(#diameter_header{length = Len} = H, Bin, Msg, #state{length_errors = E,
+ incoming_maxlen = M,
+ dictionary = Dict0}
+ = S)
+ when E == handle;
+ 0 == Len rem 4, bit_size(Bin) == 8*Len, size(Bin) =< M ->
+ recv1(diameter_codec:msg_name(Dict0, H), H, Msg, S);
+
+recv(H, Bin, _, #state{incoming_maxlen = M})
when M < size(Bin) ->
invalid(false, incoming_maxlen_exceeded, {size(Bin), H}),
H;
+recv(H, Bin, _, #state{length_errors = E}) ->
+ T = {size(Bin), bit_size(Bin) rem 8, H},
+ invalid(E, message_length_mismatch, T),
+ H.
+
+%% recv1/4
+
%% Ignore anything but an expected CER/CEA if so configured. This is
%% non-standard behaviour.
-recv1(Name, #diameter_packet{header = H}, #state{state = {'Wait-CEA', _, _},
- strict = false})
+recv1(Name, H, _, #state{state = {'Wait-CEA', _, _},
+ strict = false})
when Name /= 'CEA' ->
H;
-recv1(Name, #diameter_packet{header = H}, #state{state = recv_CER,
- strict = false})
+recv1(Name, H, _, #state{state = recv_CER,
+ strict = false})
when Name /= 'CER' ->
H;
%% Incoming request after outgoing DPR: discard. Don't discard DPR, so
%% both ends don't do so when sending simultaneously.
-recv1(Name,
- #diameter_packet{header = #diameter_header{is_request = true} = H},
- #state{dpr = {_,_,_}})
+recv1(Name, #diameter_header{is_request = true} = H, _, #state{dpr = {_,_,_}})
when Name /= 'DPR' ->
invalid(false, recv_after_outgoing_dpr, H),
H;
%% Incoming request after incoming DPR: discard.
-recv1(_,
- #diameter_packet{header = #diameter_header{is_request = true} = H},
- #state{dpr = true}) ->
+recv1(_, #diameter_header{is_request = true} = H, _, #state{dpr = true}) ->
invalid(false, recv_after_incoming_dpr, H),
H;
%% DPA with identifier mismatch, or in response to a DPR initiated by
%% the service.
-recv1('DPA' = N,
- #diameter_packet{header = #diameter_header{hop_by_hop_id = Hid,
- end_to_end_id = Eid}}
- = Pkt,
- #state{dpr = {X,H,E}}
+recv1('DPA' = Name,
+ #diameter_header{hop_by_hop_id = Hid, end_to_end_id = Eid}
+ = H,
+ Msg,
+ #state{dpr = {X,HI,EI}}
= S)
- when H /= Hid;
- E /= Eid;
+ when HI /= Hid;
+ EI /= Eid;
not X ->
- rcv(N, Pkt, S);
+ Pkt = pkt(H, Msg),
+ handle(Name, Pkt, S);
-%% Any other message with a header and no length errors: send to the
-%% parent.
-recv1(Name, Pkt, #state{}) ->
- {recv, Name, Pkt}.
+%% Any other message with a header and no length errors.
+recv1(Name, H, Msg, #state{parent = Pid, ack = Ack} = S) ->
+ Pkt = pkt(H, Msg),
+ Pid ! {recv, self(), get_route(Ack, Name, Pkt), Name, Pkt},
+ handle(Name, Pkt, S).
-%% recv/3
+%% pkt/2
-recv(#diameter_header{length = Len}
- = H,
- #diameter_packet{bin = Bin}
- = Pkt,
- #state{length_errors = E}
- = S)
- when E == handle;
- 0 == Len rem 4, bit_size(Bin) == 8*Len ->
- recv(Pkt#diameter_packet{header = H}, S);
+pkt(H, Bin)
+ when is_binary(Bin) ->
+ #diameter_packet{header = H,
+ bin = Bin};
-recv(#diameter_header{}
- = H,
- #diameter_packet{bin = Bin},
- #state{length_errors = E}) ->
- T = {size(Bin), bit_size(Bin) rem 8, H},
- invalid(E, message_length_mismatch, T),
- Bin;
+pkt(H, Pkt) ->
+ Pkt#diameter_packet{header = H}.
-recv(false, #diameter_packet{bin = Bin}, #state{length_errors = E}) ->
- invalid(E, truncated_header, Bin),
- Bin.
+%% invalid/3
%% Note that counters here only count discarded messages.
invalid(E, Reason, T) ->
@@ -770,39 +773,39 @@ invalid(E, Reason, T) ->
?LOG(Reason, T),
ok.
-%% rcv/3
+%% handle/3
%% Incoming CEA.
-rcv('CEA' = N,
- #diameter_packet{header = #diameter_header{end_to_end_id = Eid,
- hop_by_hop_id = Hid}}
- = Pkt,
- #state{state = {'Wait-CEA', Hid, Eid}}
- = S) ->
+handle('CEA' = N,
+ #diameter_packet{header = #diameter_header{end_to_end_id = Eid,
+ hop_by_hop_id = Hid}}
+ = Pkt,
+ #state{state = {'Wait-CEA', Hid, Eid}}
+ = S) ->
?LOG(recv, N),
handle_CEA(Pkt, S);
%% Incoming CER
-rcv('CER' = N, Pkt, #state{state = recv_CER} = S) ->
+handle('CER' = N, Pkt, #state{state = recv_CER} = S) ->
handle_request(N, Pkt, S);
%% Anything but CER/CEA in a non-Open state is an error, as is
%% CER/CEA in anything but recv_CER/Wait-CEA.
-rcv(Name, _, #state{state = PS})
+handle(Name, _, #state{state = PS})
when PS /= 'Open';
Name == 'CER';
Name == 'CEA' ->
{stop, {Name, PS}};
-rcv('DPR' = N, Pkt, S) ->
+handle('DPR' = N, Pkt, S) ->
handle_request(N, Pkt, S);
%% DPA in response to DPR, with the expected identifiers.
-rcv('DPA' = N,
- #diameter_packet{header = #diameter_header{end_to_end_id = Eid,
- hop_by_hop_id = Hid}
- = H}
- = Pkt,
+handle('DPA' = N,
+ #diameter_packet{header = #diameter_header{end_to_end_id = Eid,
+ hop_by_hop_id = Hid}
+ = H}
+ = Pkt,
#state{dictionary = Dict0,
transport = TPid,
dpr = {X, Hid, Eid},
@@ -813,7 +816,8 @@ rcv('DPA' = N,
%% service: explicit DPR is counted in the same way
%% as other explicitly sent requests.
incr(recv, H, Dict0),
- incr_rc(recv, diameter_codec:decode(Dict0, Opts, Pkt), Dict0)
+ {_, RecPkt} = decode(Dict0, Opts, Pkt),
+ incr_rc(recv, RecPkt, Dict0)
end,
diameter_peer:close(TPid),
{stop, N};
@@ -821,13 +825,13 @@ rcv('DPA' = N,
%% Ignore an unsolicited DPA in particular. Note that dpa_timeout
%% deals with the case in which the peer sends the wrong identifiers
%% in DPA.
-rcv('DPA' = N, #diameter_packet{header = H}, _) ->
+handle('DPA' = N, #diameter_packet{header = H}, _) ->
?LOG(ignored, N),
%% Note that these aren't counted in the normal recv counter.
diameter_stats:incr({diameter_codec:msg_id(H), recv, ignored}),
ok;
-rcv(_, _, _) ->
+handle(_, _, _) ->
ok.
%% incr/3
@@ -917,21 +921,30 @@ handle_request(Name,
= S) ->
?LOG(recv, Name),
incr(recv, H, Dict0),
- send_answer(Name, diameter_codec:decode(Dict0, Opts, Pkt), S).
+ send_answer(Name, decode(Dict0, Opts, Pkt), S).
+
+%% decode/3
+%%
+%% Decode the message as record for diameter_capx, and in the
+%% configured format for events.
+
+decode(Dict0, Opts, Pkt) ->
+ {diameter_codec:decode(Dict0, Opts, Pkt),
+ diameter_codec:decode(Dict0, Opts#{decode_format := record}, Pkt)}.
%% send_answer/3
-send_answer(Type, ReqPkt, #state{transport = TPid,
- dictionary = Dict,
- codec = Opts}
- = S) ->
- incr_error(recv, ReqPkt, Dict),
+send_answer(Type, {DecPkt, RecPkt}, #state{transport = TPid,
+ dictionary = Dict,
+ codec = Opts}
+ = S) ->
+ incr_error(recv, RecPkt, Dict),
#diameter_packet{header = H,
transport_data = TD}
- = ReqPkt,
+ = RecPkt,
- {Msg, PostF} = build_answer(Type, ReqPkt, S),
+ {Msg, PostF} = build_answer(Type, DecPkt, RecPkt, S),
%% An answer message clears the R and T flags and retains the P
%% flag. The E flag is set at encode.
@@ -959,15 +972,15 @@ eval([F|A], S) ->
eval(T, _) ->
close(T).
-%% build_answer/3
+%% build_answer/4
build_answer('CER',
+ DecPkt,
#diameter_packet{msg = CER,
header = #diameter_header{version
= ?DIAMETER_VERSION,
is_error = false},
- errors = []}
- = Pkt,
+ errors = []},
#state{dictionary = Dict0}
= S) ->
{SupportedApps, RCaps, CEA} = recv_CER(CER, S),
@@ -985,25 +998,25 @@ build_answer('CER',
orelse ?THROW(4003), %% DIAMETER_ELECTION_LOST
caps_cb(Caps)
of
- N -> {cea(CEA, N, Dict0), [fun open/5, Pkt,
+ N -> {cea(CEA, N, Dict0), [fun open/5, DecPkt,
SupportedApps,
Caps,
{accept, inband_security(IS)}]}
catch
?FAILURE(Reason) ->
- rejected(Reason, {'CER', Reason, Caps, Pkt}, S)
+ rejected(Reason, {'CER', Reason, Caps, DecPkt}, S)
end;
%% The error checks below are similar to those in diameter_traffic for
%% other messages. Should factor out the commonality.
build_answer(Type,
+ DecPkt,
#diameter_packet{header = H,
- errors = Es}
- = Pkt,
+ errors = Es},
S) ->
{RC, FailedAVP} = result_code(Type, H, Es),
- {answer(Type, RC, FailedAVP, S), post(Type, RC, Pkt, S)}.
+ {answer(Type, RC, FailedAVP, S), post(Type, RC, DecPkt, S)}.
inband_security([]) ->
?NO_INBAND_SECURITY;
@@ -1175,12 +1188,10 @@ handle_CEA(#diameter_packet{header = H}
= S) ->
incr(recv, H, Dict0),
- #diameter_packet{}
- = DPkt
- = diameter_codec:decode(Dict0, Opts, Pkt),
+ {DecPkt, RecPkt} = decode(Dict0, Opts, Pkt),
- RC = result_code(incr_rc(recv, DPkt, Dict0)),
- {SApps, IS, RCaps} = recv_CEA(DPkt, S),
+ RC = result_code(incr_rc(recv, RecPkt, Dict0)),
+ {SApps, IS, RCaps} = recv_CEA(RecPkt, S),
#diameter_caps{origin_host = {OH, DH}}
= Caps
@@ -1203,9 +1214,9 @@ handle_CEA(#diameter_packet{header = H}
orelse ?THROW(election_lost),
caps_cb(Caps)
of
- _ -> open(DPkt, SApps, Caps, {connect, hd([_] = IS)}, S)
+ _ -> open(DecPkt, SApps, Caps, {connect, hd([_] = IS)}, S)
catch
- ?FAILURE(Reason) -> close({'CEA', Reason, Caps, DPkt})
+ ?FAILURE(Reason) -> close({'CEA', Reason, Caps, DecPkt})
end.
%% Check more than the result code since the peer could send success
%% regardless. If not 2001 then a peer_up callback could do anything
diff --git a/lib/diameter/src/base/diameter_reg.erl b/lib/diameter/src/base/diameter_reg.erl
index 97e74657bd..bd5db54a5c 100644
--- a/lib/diameter/src/base/diameter_reg.erl
+++ b/lib/diameter/src/base/diameter_reg.erl
@@ -19,10 +19,11 @@
%%
%%
-%% The module implements a simple term -> pid registry.
+%% A simple term -> pid registry.
%%
-module(diameter_reg).
+
-behaviour(gen_server).
-export([add/1,
@@ -57,18 +58,18 @@
-type key() :: term().
-type from() :: {pid(), term()}.
+-type rcvr() :: [pid() | term()] %% subscribe
+ | from(). %% wait
-type pattern() :: term().
-record(state, {id = diameter_lib:now(),
- receivers = dict:new()
- :: dict:dict(pattern(), [[pid() | term()]%% subscribe
- | from()]), %% wait
+ notify = #{} :: #{pattern() => [rcvr()]},
monitors = sets:new() :: sets:set(pid())}).
%% The ?TABLE bag contains the Key -> Pid mapping, as {Key, Pid}
%% tuples. Each pid is stored in the monitors set to ensure only one
%% monitor for each pid: more are harmless, but unnecessary. A pattern
-%% is added to receivers a result of calls to wait/1 or subscribe/2:
+%% is added to notify a result of calls to wait/1 or subscribe/2:
%% changes to ?TABLE causes processes to be notified as required.
%% ===========================================================================
@@ -156,7 +157,7 @@ wait(Pat) ->
%% # subscribe(Pat, T)
%%
%% Like match/1, but additionally receive messages of the form
-%% {T, add|remove, {term(), pid()} when associations are added
+%% {T, add|remove, {term(), pid()}} when associations are added
%% or removed.
%% ===========================================================================
@@ -186,15 +187,12 @@ uptime() ->
-> [{pid(), [key()]}].
pids() ->
- to_list(fun swap/1).
-
-to_list(Fun) ->
- ets:foldl(fun(T,D) -> append(Fun(T), D) end, orddict:new(), ?TABLE).
+ append(ets:select(?TABLE, [{{'$1','$2'}, [], [{{'$2', '$1'}}]}])).
-append({K,V}, Dict) ->
- orddict:append(K, V, Dict).
-
-id(T) -> T.
+append(Pairs) ->
+ dict:to_list(lists:foldl(fun({K,V}, D) -> dict:append(K, V, D) end,
+ dict:new(),
+ Pairs)).
%% terms/0
@@ -202,9 +200,7 @@ id(T) -> T.
-> [{key(), [pid()]}].
terms() ->
- to_list(fun id/1).
-
-swap({X,Y}) -> {Y,X}.
+ append(ets:tab2list(?TABLE)).
%% subs/0
@@ -212,31 +208,19 @@ swap({X,Y}) -> {Y,X}.
-> [{pattern(), [{pid(), term()}]}].
subs() ->
- #state{receivers = RD} = state(),
- dict:fold(fun sub/3, orddict:new(), RD).
-
-sub(Pat, Ps, Dict) ->
- lists:foldl(fun([P|T], D) -> orddict:append(Pat, {P,T}, D);
- (_, D) -> D
- end,
- Dict,
- Ps).
+ #state{notify = Dict} = state(),
+ [{K, Ts} || {K,Ps} <- maps:to_list(Dict),
+ Ts <- [[{P,T} || [P|T] <- Ps]]].
%% waits/0
-spec waits()
- -> [{pattern(), [{from(), term()}]}].
+ -> [{pattern(), [from()]}].
waits() ->
- #state{receivers = RD} = state(),
- dict:fold(fun wait/3, orddict:new(), RD).
-
-wait(Pat, Ps, Dict) ->
- lists:foldl(fun({_,_} = F, D) -> orddict:append(Pat, F, D);
- (_, D) -> D
- end,
- Dict,
- Ps).
+ #state{notify = Dict} = state(),
+ [{K, Ts} || {K,Ps} <- maps:to_list(Dict),
+ Ts <- [[T || {_,_} = T <- Ps]]].
%% ----------------------------------------------------------
%% # init/1
@@ -250,33 +234,28 @@ init(_) ->
%% # handle_call/3
%% ----------------------------------------------------------
-handle_call({add, Uniq, Key}, {Pid, _}, S0) ->
+handle_call({add, Uniq, Key}, {Pid, _}, S) ->
Rec = {Key, Pid},
- S1 = flush(Uniq, Rec, S0),
+ NS = flush(Uniq, Rec, S), %% before insert
{Res, New} = insert(Uniq, Rec),
- {Recvs, S} = add(New, Rec, S1),
- notify(Recvs, Rec),
- {reply, Res, S};
+ {reply, Res, notify(add, New andalso Rec, NS)};
handle_call({remove, Key}, {Pid, _}, S) ->
Rec = {Key, Pid},
- Recvs = delete([Rec], S),
ets:delete_object(?TABLE, Rec),
- notify(Recvs, remove),
- {reply, true, S};
+ {reply, true, notify(remove, Rec, S)};
-handle_call({wait, Pat}, {Pid, _} = From, #state{receivers = RD} = S) ->
+handle_call({wait, Pat}, {Pid, _} = From, S) ->
NS = add_monitor(Pid, S),
case match(Pat) of
- [_|_] = L ->
- {reply, L, NS};
+ [_|_] = Recs ->
+ {reply, Recs, NS};
[] ->
- {noreply, NS#state{receivers = dict:append(Pat, From, RD)}}
+ {noreply, queue(Pat, From, NS)}
end;
-handle_call({subscribe, Pat, T}, {Pid, _}, #state{receivers = RD} = S) ->
- NS = add_monitor(Pid, S),
- {reply, match(Pat), NS#state{receivers = dict:append(Pat, [Pid | T], RD)}};
+handle_call({subscribe, Pat, T}, {Pid, _}, S) ->
+ {reply, match(Pat), queue(Pat, [Pid | T], add_monitor(Pid, S))};
handle_call(state, _, S) ->
{reply, S, S};
@@ -332,106 +311,60 @@ insert(true, Rec) ->
B = ets:insert_new(?TABLE, Rec), %% entry inserted?
{B, B}.
-%% add/3
-
+%% add_monitor/2
+%%
%% Only add a single monitor for any given process, since there's no
%% use to more.
-add(true, {_Key, Pid} = Rec, S) ->
- NS = add_monitor(Pid, S),
- {Recvs, RD} = add(Rec, NS),
- {Recvs, S#state{receivers = RD}};
-
-add(false = No, _, S) ->
- {No, S}.
-
-%% add/2
-
-%% Notify processes whose patterns match the inserted key.
-add({_Key, Pid} = Rec, #state{receivers = RD}) ->
- dict:fold(fun(Pt, Ps, A) ->
- add(lists:member(Rec, match(Pt, Pid)), Pt, Ps, Rec, A)
- end,
- {sets:new(), RD},
- RD).
-
-%% add/5
-
-add(true, Pat, Recvs, {_,_} = Rec, {Set, Dict}) ->
- {lists:foldl(fun sets:add_element/2, Set, Recvs),
- remove(fun erlang:is_list/1, Pat, Recvs, Dict)};
-add(false, _, _, _, Acc) ->
- Acc.
+add_monitor(Pid, #state{monitors = Ps} = S) ->
+ case sets:is_element(Pid, Ps) of
+ false ->
+ monitor(process, Pid),
+ S#state{monitors = sets:add_element(Pid, Ps)};
+ true ->
+ S
+ end.
-%% add_monitor/2
-
-add_monitor(Pid, #state{monitors = MS} = S) ->
- add_monitor(sets:is_element(Pid, MS), Pid, S).
-
-%% add_monitor/3
-
-add_monitor(false, Pid, #state{monitors = MS} = S) ->
- monitor(process, Pid),
- S#state{monitors = sets:add_element(Pid, MS)};
-
-add_monitor(true, _, S) ->
- S.
-
-%% delete/2
-
-delete(Recs, #state{receivers = RD}) ->
- lists:foldl(fun(R,S) -> delete(R, RD, S) end, sets:new(), Recs).
+%% notify/3
-%% delete/3
+notify(_, false, S) ->
+ S;
-delete({_Key, Pid} = Rec, RD, Set) ->
- dict:fold(fun(Pt, Ps, S) ->
- delete(lists:member(Rec, match(Pt, Pid)), Rec, Ps, S)
- end,
- Set,
- RD).
+notify(Op, {_,_} = Rec, #state{notify = Dict} = S) ->
+ S#state{notify = maps:fold(fun(P,Rs,D) -> notify(Op, Rec, P, Rs, D) end,
+ Dict,
+ Dict)}.
-%% delete/4
+%% notify/5
-%% Entry matches a pattern ...
-delete(true, Rec, Recvs, Set) ->
- lists:foldl(fun(R,S) -> sets:add_element({R, Rec}, S) end,
- Set,
- Recvs);
-
-%% ... or not.
-delete(false, _, _, Set) ->
- Set.
-
-%% notify/2
-
-notify(false = No, _) ->
- No;
-
-notify(Recvs, remove = Op) ->
- sets:fold(fun({P,R}, N) -> send(P, R, Op), N+1 end, 0, Recvs);
-
-notify(Recvs, {_,_} = Rec) ->
- sets:fold(fun(P,N) -> send(P, Rec, add), N+1 end, 0, Recvs).
+notify(Op, {_, Pid} = Rec, Pat, Rcvrs, Dict) ->
+ case lists:member(Rec, match(Pat, Pid)) of
+ true ->
+ reset(Pat, Dict, [P || P <- Rcvrs, send(P, Op, Rec)]);
+ false ->
+ Dict
+ end.
%% send/3
-%% No processes waiting on remove, by construction: they've either
-%% received notification at add or aren't waiting.
-send([Pid | T], Rec, Op) ->
- Pid ! {T, Op, Rec};
+send([Pid | T], Op, Rec) ->
+ Pid ! {T, Op, Rec},
+ true;
-send({_,_} = From, Rec, add) ->
- gen_server:reply(From, [Rec]).
+%% No processes wait on remove: they receive notification immediately
+%% or at add, by construction.
+send({_,_} = From, add, Rec) ->
+ gen_server:reply(From, [Rec]),
+ false.
%% down/2
-down(Pid, #state{monitors = MS} = S) ->
- NS = flush(Pid, S),
- Recvs = delete(match('_', Pid), NS),
+down(Pid, #state{monitors = Ps} = S) ->
+ Recs = match('_', Pid),
ets:match_delete(?TABLE, {'_', Pid}),
- notify(Recvs, remove),
- NS#state{monitors = sets:del_element(Pid, MS)}.
+ lists:foldl(fun(R,NS) -> notify(remove, R, NS) end,
+ flush(Pid, S#state{monitors = sets:del_element(Pid, Ps)}),
+ Recs).
%% flush/3
@@ -452,16 +385,15 @@ flush(false, _, S) ->
%% flush/2
%% Process has died and should no longer receive messages/replies.
-flush(Pid, #state{receivers = RD} = S)
- when is_pid(Pid) ->
- S#state{receivers = dict:fold(fun(Pt,Ps,D) -> flush(Pid, Pt, Ps, D) end,
- RD,
- RD)}.
+flush(Pid, #state{notify = Dict} = S) ->
+ S#state{notify = maps:fold(fun(P,Rs,D) -> flush(Pid, P, Rs, D) end,
+ Dict,
+ Dict)}.
%% flush/4
-flush(Pid, Pat, Recvs, Dict) ->
- remove(fun(T) -> Pid /= head(T) end, Pat, Recvs, Dict).
+flush(Pid, Pat, Rcvrs, Dict) ->
+ reset(Pat, Dict, [T || T <- Rcvrs, Pid /= head(T)]).
%% head/1
@@ -471,15 +403,18 @@ head([P|_]) ->
head({P,_}) ->
P.
-%% remove/4
+%% reset/3
+
+reset(Key, Map, []) ->
+ maps:remove(Key, Map);
+
+reset(Key, Map, List) ->
+ maps:put(Key, List, Map).
+
+%% queue/3
-remove(Pred, Key, Values, Dict) ->
- case lists:filter(Pred, Values) of
- [] ->
- dict:erase(Key, Dict);
- Rest ->
- dict:store(Key, Rest, Dict)
- end.
+queue(Pat, Rcvr, #state{notify = Dict} = S) ->
+ S#state{notify = maps:put(Pat, [Rcvr | maps:get(Pat, Dict, [])], Dict)}.
%% call/1
diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl
index a976a8b998..31dd92f878 100644
--- a/lib/diameter/src/base/diameter_service.erl
+++ b/lib/diameter/src/base/diameter_service.erl
@@ -112,8 +112,24 @@
use_shared_peers := diameter:remotes(),%% use from
restrict_connections := diameter:restriction(),
incoming_maxlen := diameter:message_length(),
+ strict_arities => diameter:strict_arities(),
strict_mbit := boolean(),
+ decode_format := diameter:decode_format(),
+ avp_dictionaries => nonempty_list(module()),
+ traffic_counters := boolean(),
string_decode := boolean(),
+ capabilities_cb => diameter:evaluable(),
+ pool_size => pos_integer(),
+ capx_timeout => diameter:'Unsigned32'(),
+ strict_capx => boolean(),
+ disconnect_cb => diameter:evaluable(),
+ dpr_timeout => diameter:'Unsigned32'(),
+ dpa_timeout => diameter:'Unsigned32'(),
+ length_errors => exit | handle | discard,
+ connect_timer => diameter:'Unsigned32'(),
+ watchdog_timer => diameter:'Unsigned32'()
+ | {module(), atom(), list()},
+ watchdog_config => [{okay|suspect, non_neg_integer()}],
spawn_opt := list() | {module(), atom(), list()}}}).
%% Record representing an RFC 3539 watchdog process implemented by
@@ -514,6 +530,13 @@ transition({tc_timeout, T}, S) ->
tc_timeout(T, S),
ok;
+transition({nodeup, Node, _}, S) ->
+ nodeup(Node, S),
+ ok;
+
+transition({nodedown, _Node, _}, _) ->
+ ok;
+
transition(Req, S) ->
unexpected(handle_info, [Req], S),
ok.
@@ -679,12 +702,15 @@ i(SvcName) ->
cfg_acc({SvcName, #diameter_service{applications = Apps} = Rec, Opts},
{false, Acc}) ->
lists:foreach(fun init_mod/1, Apps),
+ #{monitor := M}
+ = SvcOpts
+ = service_opts(Opts),
S = #state{service_name = SvcName,
service = Rec#diameter_service{pid = self()},
local = init_peers(),
remote = init_peers(),
- monitor = mref(get_value(monitor, Opts)),
- options = service_options(lists:keydelete(monitor, 1, Opts))},
+ monitor = mref(M),
+ options = maps:remove(monitor, SvcOpts)},
{S, Acc};
cfg_acc({_Ref, Type, _Opts} = T, {S, Acc})
@@ -699,8 +725,29 @@ init_peers() ->
%% Alias,
%% TPid}
-service_options(Opts) ->
- maps:from_list(Opts).
+service_opts(Opts) ->
+ remove([{strict_arities, true},
+ {avp_dictionaries, []}],
+ maps:merge(maps:from_list([{monitor, false} | def_opts()]),
+ maps:from_list(Opts))).
+
+remove(List, Map) ->
+ maps:filter(fun(K,V) -> not lists:member({K,V}, List) end,
+ Map).
+
+def_opts() -> %% defaults on the service map
+ [{share_peers, false},
+ {use_shared_peers, false},
+ {sequence, {0,32}},
+ {restrict_connections, nodes},
+ {incoming_maxlen, 16#FFFFFF},
+ {strict_arities, true},
+ {strict_mbit, true},
+ {decode_format, record},
+ {avp_dictionaries, []},
+ {traffic_counters, true},
+ {string_decode, true},
+ {spawn_opt, []}].
mref(false = No) ->
No;
@@ -709,6 +756,8 @@ mref(P) ->
init_shared(#state{options = #{use_shared_peers := T},
service_name = Svc}) ->
+ T == false orelse net_kernel:monitor_nodes(true, [{node_type, visible},
+ nodedown_reason]),
notify(T, Svc, {service, self()}).
init_mod(#diameter_app{alias = Alias,
@@ -718,16 +767,17 @@ init_mod(#diameter_app{alias = Alias,
start_fsm({Ref, Type, Opts}, S) ->
start(Ref, {Type, Opts}, S).
-get_value(Key, Vs) ->
- {_, V} = lists:keyfind(Key, 1, Vs),
- V.
-
notify(Share, SvcName, T) ->
Nodes = remotes(Share),
[] /= Nodes andalso diameter_peer:notify(Nodes, SvcName, T).
%% Test for the empty list for upgrade reasons: there's no
%% diameter_peer:notify/3 in old code.
+nodeup(Node, #state{options = #{share_peers := SP},
+ service_name = SvcName}) ->
+ lists:member(Node, remotes(SP))
+ andalso diameter_peer:notify([Node], SvcName, {service, self()}).
+
remotes(false) ->
[];
@@ -806,7 +856,7 @@ start(Ref, Type, Opts, State) ->
start(Ref, Type, Opts, N, #state{watchdogT = WatchdogT,
local = {PeerT, _, _},
options = #{string_decode := SD}
- = SvcOpts0,
+ = SvcOpts,
service_name = SvcName,
service = Svc0})
when Type == connect;
@@ -815,12 +865,12 @@ start(Ref, Type, Opts, N, #state{watchdogT = WatchdogT,
= Svc1
= merge_service(Opts, Svc0),
Svc = binary_caps(Svc1, SD),
- SvcOpts = merge_options(Opts, SvcOpts0),
- RecvData = diameter_traffic:make_recvdata([SvcName, PeerT, Apps, SvcOpts]),
- T = {Opts, SvcOpts, RecvData, Svc},
+ {SOpts, TOpts} = merge_opts(SvcOpts, Opts),
+ RecvData = diameter_traffic:make_recvdata([SvcName, PeerT, Apps, SOpts]),
+ T = {TOpts, SOpts, RecvData, Svc},
Rec = #watchdog{type = Type,
ref = Ref,
- options = Opts},
+ options = TOpts},
diameter_lib:fold_n(fun(_,A) ->
[wd(Type, Ref, T, WatchdogT, Rec) | A]
@@ -828,10 +878,14 @@ start(Ref, Type, Opts, N, #state{watchdogT = WatchdogT,
[],
N).
-merge_options(Opts, SvcOpts) ->
- Keys = maps:keys(SvcOpts),
- Map = maps:from_list([KV || {K,_} = KV <- Opts, lists:member(K, Keys)]),
- maps:merge(SvcOpts, Map).
+merge_opts(SvcOpts, Opts) ->
+ Keys = [K || {K,_} <- def_opts()],
+ SO = [T || {K,_} = T <- Opts, lists:member(K, Keys)],
+ TO = Opts -- SO,
+ {maps:merge(maps:with(Keys, SvcOpts), maps:from_list(SO)),
+ TO ++ [T || {K,_} = T <- maps:to_list(SvcOpts),
+ not lists:member(K, Keys),
+ not lists:keymember(K, 1, Opts)]}.
binary_caps(Svc, true) ->
Svc;
@@ -1400,9 +1454,15 @@ is_remote(Pid, T) ->
%% # remote_peer_up/4
%% ---------------------------------------------------------------------------
-remote_peer_up(TPid, Aliases, Caps, #state{options = #{use_shared_peers := T}}
+remote_peer_up(TPid, Aliases, Caps, #state{options = #{use_shared_peers := T},
+ remote = {PeerT, _, _}}
= S) ->
- is_remote(TPid, T) andalso rpu(TPid, Aliases, Caps, S).
+ is_remote(TPid, T)
+ andalso not ets:member(PeerT, TPid)
+ andalso rpu(TPid, Aliases, Caps, S).
+
+%% Notification can be duplicate since remote nodes push and the local
+%% node pulls.
rpu(TPid, Aliases, Caps, #state{service = Svc, remote = RT}) ->
#diameter_service{applications = Apps} = Svc,
@@ -1412,6 +1472,7 @@ rpu(TPid, Aliases, Caps, #state{service = Svc, remote = RT}) ->
rpu(_, [] = No, _, _) ->
No;
+
rpu(TPid, Aliases, Caps, {PeerT, _, _} = RT) ->
monitor(process, TPid),
ets:insert(PeerT, #peer{pid = TPid,
diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl
index 85378babea..d2856ae530 100644
--- a/lib/diameter/src/base/diameter_traffic.erl
+++ b/lib/diameter/src/base/diameter_traffic.erl
@@ -70,13 +70,17 @@
timeout = 5000 :: 0..16#FFFFFFFF, %% for outgoing requests
detach = false :: boolean()}).
-%% Term passed back to receive_message/6 with every incoming message.
+%% Term passed back to receive_message/5 with every incoming message.
-record(recvdata,
{peerT :: ets:tid(),
service_name :: diameter:service_name(),
apps :: [#diameter_app{}],
sequence :: diameter:sequence(),
- codec :: #{string_decode := boolean(),
+ counters :: boolean(),
+ codec :: #{decode_format := diameter:decode_format(),
+ avp_dictionaries => nonempty_list(module()),
+ string_decode := boolean(),
+ strict_arities => diameter:strict_arities(),
strict_mbit := boolean(),
incoming_maxlen := diameter:message_length()}}).
%% Note that incoming_maxlen is currently handled in diameter_peer_fsm,
@@ -89,6 +93,7 @@
caller :: pid() | undefined, %% calling process
handler :: pid(), %% request process
peer :: undefined | {pid(), #diameter_caps{}},
+ caps :: undefined, %% no longer used
packet :: #diameter_packet{} | undefined}). %% of request
%% ---------------------------------------------------------------------------
@@ -96,13 +101,17 @@
%% ---------------------------------------------------------------------------
make_recvdata([SvcName, PeerT, Apps, SvcOpts | _]) ->
- #{sequence := {_,_} = Mask, spawn_opt := Opts}
+ #{sequence := {_,_} = Mask, spawn_opt := Opts, traffic_counters := B}
= SvcOpts,
{Opts, #recvdata{service_name = SvcName,
peerT = PeerT,
apps = Apps,
sequence = Mask,
- codec = maps:with([string_decode,
+ counters = B,
+ codec = maps:with([decode_format,
+ avp_dictionaries,
+ string_decode,
+ strict_arities,
strict_mbit,
ordered_encode,
incoming_maxlen],
@@ -182,7 +191,7 @@ incr_error(Dir, Id, TPid) ->
%% ---------------------------------------------------------------------------
-spec incr_rc(send|recv, Pkt, TPid, DictT)
- -> {Counter, non_neg_integer()}
+ -> Counter
| Reason
when Pkt :: #diameter_packet{},
TPid :: pid(),
@@ -193,18 +202,26 @@ incr_error(Dir, Id, TPid) ->
| {'Experimental-Result', integer(), integer()},
Reason :: atom().
-incr_rc(Dir, Pkt, TPid, {_, AppDict, _} = DictT) ->
- try
- incr_result(Dir, Pkt, TPid, DictT)
+incr_rc(Dir, Pkt, TPid, {MsgDict, AppDict, Dict0}) ->
+ incr_rc(Dir, Pkt, TPid, MsgDict, AppDict, Dict0);
+
+incr_rc(Dir, Pkt, TPid, Dict0) ->
+ incr_rc(Dir, Pkt, TPid, Dict0, Dict0, Dict0).
+
+%% incr_rc/6
+
+incr_rc(Dir, Pkt, TPid, MsgDict, AppDict, Dict0) ->
+ try get_result(Dir, MsgDict, Dict0, Pkt) of
+ false ->
+ unknown;
+ Avp ->
+ incr_result(Dir, Avp, Pkt, TPid, AppDict)
catch
exit: {E,_} when E == no_result_code;
E == invalid_error_bit ->
incr(TPid, {msg_id(Pkt#diameter_packet.header, AppDict), Dir, E}),
E
- end;
-
-incr_rc(Dir, Pkt, TPid, Dict0) ->
- incr_rc(Dir, Pkt, TPid, {Dict0, Dict0, Dict0}).
+ end.
%% ---------------------------------------------------------------------------
%% receive_message/5
@@ -216,13 +233,13 @@ incr_rc(Dir, Pkt, TPid, Dict0) ->
-> pid() %% request handler
| boolean() %% answer, known request or not
| discard %% request discarded by MFA
- when Route :: {Handler, RequestRef, Seqs}
+ when Route :: {Handler, RequestRef, TPid}
| Ack,
RecvData :: {[SpawnOpt], #recvdata{}},
SpawnOpt :: term(),
Handler :: pid(),
RequestRef :: reference(),
- Seqs :: {0..16#FFFFFFFF, 0..16#FFFFFFFF},
+ TPid :: pid(),
Ack :: boolean().
receive_message(TPid, Route, Pkt, Dict0, RecvData) ->
@@ -303,14 +320,15 @@ recv_request(Ack,
= Pkt,
Dict0,
#recvdata{peerT = PeerT,
- apps = Apps}
+ apps = Apps,
+ counters = Count}
= RecvData) ->
Ack andalso (TPid ! {handler, self()}),
case diameter_service:find_incoming_app(PeerT, TPid, Id, Apps) of
{#diameter_app{id = Aid, dictionary = AppDict} = App, Caps} ->
- incr(recv, Pkt, TPid, AppDict),
+ Count andalso incr(recv, Pkt, TPid, AppDict),
DecPkt = decode(Aid, AppDict, RecvData, Pkt),
- incr_error(recv, DecPkt, TPid, AppDict),
+ Count andalso incr_error(recv, DecPkt, TPid, AppDict),
send_A(recv_R(App, TPid, Dict0, Caps, RecvData, DecPkt),
TPid,
App,
@@ -323,9 +341,7 @@ recv_request(Ack,
%% A request was sent for an application that is not
%% supported.
RC = 3007,
- Es = Pkt#diameter_packet.errors,
- DecPkt = Pkt#diameter_packet{avps = collect_avps(Pkt),
- errors = [RC | Es]},
+ DecPkt = diameter_codec:collect_avps(Pkt),
send_answer(answer_message(RC, Dict0, Caps, DecPkt),
TPid,
Dict0,
@@ -338,17 +354,11 @@ recv_request(Ack,
No
end.
+%% decode/4
+
decode(Id, Dict, #recvdata{codec = Opts}, Pkt) ->
errors(Id, diameter_codec:decode(Id, Dict, Opts, Pkt)).
-collect_avps(Pkt) ->
- case diameter_codec:collect_avps(Pkt) of
- {_Error, Avps} ->
- Avps;
- Avps ->
- Avps
- end.
-
%% send_A/7
send_A([T | Fs], TPid, App, Dict0, RecvData, DecPkt, Caps) ->
@@ -541,6 +551,7 @@ send_A({call, Opts}, TPid, App, Dict0, RecvData, Pkt, Caps, Fs) ->
MsgDict,
AppDict,
Dict0,
+ RecvData#recvdata.counters,
Fs);
RC ->
send_answer(answer_message(RC, Dict0, Caps, Pkt),
@@ -584,14 +595,22 @@ send_answer(Ans, TPid, MsgDict, AppDict, Dict0, RecvData, DecPkt, Fs) ->
TPid,
RecvData#recvdata.codec,
make_answer_packet(Ans, DecPkt, MsgDict, Dict0)),
- send_answer(Pkt, TPid, MsgDict, AppDict, Dict0, Fs).
+ send_answer(Pkt,
+ TPid,
+ MsgDict,
+ AppDict,
+ Dict0,
+ RecvData#recvdata.counters,
+ Fs).
-%% send_answer/6
+%% send_answer/7
-send_answer(Pkt, TPid, MsgDict, AppDict, Dict0, [EvalPktFs | EvalFs]) ->
+send_answer(Pkt, TPid, MsgDict, AppDict, Dict0, Count, [EvalPktFs | EvalFs]) ->
eval_packet(Pkt, EvalPktFs),
- incr(send, Pkt, TPid, AppDict),
- incr_rc(send, Pkt, TPid, {MsgDict, AppDict, Dict0}), %% count outgoing
+ Count andalso begin
+ incr(send, Pkt, TPid, AppDict),
+ incr_rc(send, Pkt, TPid, MsgDict, AppDict, Dict0)
+ end,
send(TPid, z(Pkt), _Route = self()),
lists:foreach(fun diameter_lib:eval/1, EvalFs).
@@ -619,7 +638,7 @@ is_answer_message(#diameter_packet{msg = Msg}, Dict0) ->
is_answer_message([#diameter_header{is_request = R, is_error = E} | _], _) ->
E andalso not R;
-%% Message sent as a tagged avp/value list.
+%% Message sent as a map or tagged avp/value list.
is_answer_message([Name | _], _) ->
Name == 'answer-message';
@@ -665,7 +684,7 @@ resend(false,
Route = #diameter_avp{data = {Dict0, 'Route-Record', OH}},
Seq = diameter_session:sequence(Mask),
Hdr = Hdr0#diameter_header{hop_by_hop_id = Seq},
- Msg = [Hdr, Route | Avps], %% reordered at encode
+ Msg = [Hdr | Avps ++ [Route]],
case send_request(SvcName, App, Msg, Opts) of
#diameter_packet{} = Ans ->
Ans;
@@ -867,7 +886,10 @@ reset(Msg, [RC | Avps], Dict) ->
%% set/3
-%% Reply as name and tuple list ...
+%% Reply as name/values list ...
+set([Name|As], Avps, _)
+ when is_map(As) ->
+ [Name | maps:merge(As, maps:from_list(Avps))];
set([_|_] = Ans, Avps, _) ->
Ans ++ Avps; %% Values nearer tail take precedence.
@@ -900,33 +922,44 @@ failed_avp(_, [] = No, _) ->
failed_avp(Msg, [_|_] = Avps, Dict) ->
[failed(Msg, [{'AVP', Avps}], Dict)].
-%% Reply as name and tuple list ...
-failed([MsgName | Values], FailedAvp, Dict) ->
- RecName = Dict:msg2rec(MsgName),
+%% failed/3
+
+failed(Msg, FailedAvp, Dict) ->
+ RecName = msg2rec(Msg, Dict),
try
- Dict:'#info-'(RecName, {index, 'Failed-AVP'}),
+ Dict:'#info-'(RecName, {index, 'Failed-AVP'}), %% assert existence
{'Failed-AVP', [FailedAvp]}
catch
error: _ ->
- Avps = proplists:get_value('AVP', Values, []),
+ Avps = values(Msg, 'AVP', Dict),
A = #diameter_avp{name = 'Failed-AVP',
value = FailedAvp},
{'AVP', [A|Avps]}
+ end.
+
+%% msg2rec/2
+
+%% Message as name/values list ...
+msg2rec([MsgName | _], Dict) ->
+ Dict:msg2rec(MsgName);
+
+%% ... or record.
+msg2rec(Rec, _) ->
+ element(1, Rec).
+
+%% values/2
+
+%% Message as name/values list ...
+values([_ | Avps], F, _) ->
+ if is_map(Avps) ->
+ maps:get(F, Avps, []);
+ is_list(Avps) ->
+ proplists:get_value(F, Avps, [])
end;
%% ... or record.
-failed(Rec, FailedAvp, Dict) ->
- try
- RecName = element(1, Rec),
- Dict:'#info-'(RecName, {index, 'Failed-AVP'}),
- {'Failed-AVP', [FailedAvp]}
- catch
- error: _ ->
- Avps = Dict:'#get-'('AVP', Rec),
- A = #diameter_avp{name = 'Failed-AVP',
- value = FailedAvp},
- {'AVP', [A|Avps]}
- end.
+values(Rec, F, Dict) ->
+ Dict:'#get-'(F, Rec).
%% 3. Diameter Header
%%
@@ -1003,15 +1036,15 @@ answer_message(RC,
origin_realm = {OR,_}},
#diameter_packet{avps = Avps,
errors = Es}) ->
- {Code, _, Vid} = Dict0:avp_header('Session-Id'),
['answer-message', {'Origin-Host', OH},
{'Origin-Realm', OR},
- {'Result-Code', RC}]
- ++ session_id(Code, Vid, Avps)
- ++ failed_avp(RC, Es).
+ {'Result-Code', RC}
+ | session_id(Dict0, Avps)
+ ++ failed_avp(RC, Es)
+ ++ proxy_info(Dict0, Avps)].
-session_id(Code, Vid, Avps)
- when is_list(Avps) ->
+session_id(Dict0, Avps) ->
+ {Code, _, Vid} = Dict0:avp_header('Session-Id'),
try
#diameter_avp{data = Bin} = find_avp(Code, Vid, Avps),
[{'Session-Id', [Bin]}]
@@ -1029,6 +1062,14 @@ failed_avp(RC, [_ | Es]) ->
failed_avp(_, [] = No) ->
No.
+proxy_info(Dict0, Avps) ->
+ {Code, _, Vid} = Dict0:avp_header('Proxy-Info'),
+ [{'AVP', [A#diameter_avp{value = undefined}
+ || [#diameter_avp{code = C, vendor_id = I} = A | _]
+ <- Avps,
+ C == Code,
+ I == Vid]}].
+
%% find_avp/3
%% Grouped ...
@@ -1102,48 +1143,31 @@ find_avp(Code, VId, [_ | Avps]) ->
%% Message sent as a header/avps list.
incr_result(send = Dir,
- #diameter_packet{msg = [#diameter_header{} = H | _]}
- = Pkt,
+ Avp,
+ #diameter_packet{msg = [#diameter_header{} = H | _]},
TPid,
- DictT) ->
- incr_res(Dir, Pkt#diameter_packet{header = H}, TPid, DictT);
-
-%% Outgoing message as binary: don't count. (Sending binaries is only
-%% partially supported.)
-incr_result(send, #diameter_packet{header = undefined = No}, _, _) ->
- No;
+ AppDict) ->
+ incr_result(Dir, Avp, H, [], TPid, AppDict);
%% Incoming or outgoing. Outgoing with encode errors never gets here
%% since encode fails.
-incr_result(Dir, Pkt, TPid, DictT) ->
- incr_res(Dir, Pkt, TPid, DictT).
-
-incr_res(Dir,
- #diameter_packet{header = #diameter_header{is_error = E}
- = Hdr,
- errors = Es}
- = Pkt,
- TPid,
- DictT) ->
- {MsgDict, AppDict, Dict0} = DictT,
+incr_result(Dir, Avp, Pkt, TPid, AppDict) ->
+ #diameter_packet{header = H, errors = Es}
+ = Pkt,
+ incr_result(Dir, Avp, H, Es, TPid, AppDict).
+
+%% incr_result/6
+incr_result(Dir, Avp, Hdr, Es, TPid, AppDict) ->
Id = msg_id(Hdr, AppDict),
%% Could be {relay, 0}, in which case the R-bit is redundant since
%% only answers are being counted. Let it be however, so that the
%% same tuple is in both send/recv and result code counters.
%% Count incoming decode errors.
- recv /= Dir orelse [] == Es orelse incr_error(Dir, Id, TPid, AppDict),
-
- %% Exit on a missing result code.
- T = rc_counter(MsgDict, Dir, Pkt),
- T == false andalso ?LOGX(no_result_code, {MsgDict, Dir, Hdr}),
- {Ctr, RC, Avp} = T,
-
- %% Or on an inappropriate value.
- is_result(RC, E, Dict0)
- orelse ?LOGX(invalid_error_bit, {MsgDict, Dir, Hdr, Avp}),
+ send == Dir orelse [] == Es orelse incr_error(Dir, Id, TPid, AppDict),
+ Ctr = rcc(Avp),
incr(TPid, {Id, Dir, Ctr}),
Ctr.
@@ -1188,7 +1212,50 @@ is_result(RC, true, _) ->
incr(TPid, Counter) ->
diameter_stats:incr(Counter, TPid, 1).
-%% rc_counter/3
+%% rcc/1
+
+rcc(#diameter_avp{name = 'Result-Code' = Name, value = V}) ->
+ {Name, head(V)};
+
+rcc(#diameter_avp{name = 'Experimental-Result', value = V}) ->
+ head(V).
+
+%% head/1
+
+head([V|_]) ->
+ V;
+head(V) ->
+ V.
+
+%% rcv/1
+
+rcv(#diameter_avp{name = N, value = V}) ->
+ rcv(N, head(V)).
+
+%% rcv/2
+
+rcv('Experimental-Result', {_,_,N}) ->
+ N;
+
+rcv('Result-Code', N) ->
+ N.
+
+%% get_result/4
+
+%% Message sent as binary: no checks or counting.
+get_result(_, _, _, #diameter_packet{header = undefined}) ->
+ false;
+
+get_result(Dir, MsgDict, Dict0, Pkt) ->
+ Avp = get_result(MsgDict, msg(Dir, Pkt)),
+ Hdr = Pkt#diameter_packet.header,
+ %% Exit on a missing result code or inappropriate value.
+ Avp == false
+ andalso ?LOGX(no_result_code, {MsgDict, Dir, Hdr}),
+ E = Hdr#diameter_header.is_error,
+ is_result(rcv(Avp), E, Dict0)
+ orelse ?LOGX(invalid_error_bit, {MsgDict, Dir, Hdr, Avp}),
+ Avp.
%% RFC 3588, 7.6:
%%
@@ -1196,46 +1263,29 @@ incr(TPid, Counter) ->
%% applications MUST include either one Result-Code AVP or one
%% Experimental-Result AVP.
-rc_counter(Dict, Dir, #diameter_packet{header = H,
- avps = As,
- msg = Msg})
+%% msg/2
+
+msg(Dir, #diameter_packet{header = H,
+ avps = As,
+ msg = Msg})
when Dir == recv; %% decoded incoming
Msg == undefined -> %% relayed outgoing
- rc_counter(Dict, [H|As]);
-
-rc_counter(Dict, _, #diameter_packet{msg = Msg}) ->
- rc_counter(Dict, Msg).
-
-rc_counter(Dict, Msg) ->
- rcc(get_result(Dict, Msg)).
-
-rcc(#diameter_avp{name = 'Result-Code' = Name, value = N} = A)
- when is_integer(N) ->
- {{Name, N}, N, A};
-
-rcc(#diameter_avp{name = 'Result-Code' = Name, value = [N|_]} = A)
- when is_integer(N) ->
- {{Name, N}, N, A};
+ [H|As];
-rcc(#diameter_avp{name = 'Experimental-Result', value = {_,_,N} = T} = A)
- when is_integer(N) ->
- {T, N, A};
-
-rcc(#diameter_avp{name = 'Experimental-Result', value = [{_,_,N} = T|_]} = A)
- when is_integer(N) ->
- {T, N, A};
-
-rcc(_) ->
- false.
+msg(_, #diameter_packet{msg = Msg}) ->
+ Msg.
%% get_result/2
get_result(Dict, Msg) ->
try
[throw(A) || N <- ['Result-Code', 'Experimental-Result'],
- #diameter_avp{} = A <- [get_avp(Dict, N, Msg)]]
+ #diameter_avp{} = A <- [get_avp(Dict, N, Msg)],
+ is_integer(catch rcv(A))],
+ false
catch
- #diameter_avp{} = A -> A
+ #diameter_avp{} = A ->
+ A
end.
x(T) ->
@@ -1359,7 +1409,7 @@ make_opts([T | _], _, _, _, _, _) ->
send_request({{TPid, _Caps} = TC, App}
= Transport,
- #{sequence := Mask}
+ #{sequence := Mask, traffic_counters := Count}
= SvcOpts,
Msg0,
CallOpts,
@@ -1375,9 +1425,15 @@ send_request({{TPid, _Caps} = TC, App}
SvcOpts,
ReqPkt),
eval_packet(EncPkt, Fs),
- T = send_R(ReqPkt, EncPkt, Transport, CallOpts, Caller, SvcName),
+ T = send_R(ReqPkt,
+ EncPkt,
+ Transport,
+ CallOpts,
+ Caller,
+ Count,
+ SvcName),
Ans = recv_answer(SvcName, App, CallOpts, T),
- handle_answer(SvcName, SvcOpts, App, Ans);
+ handle_answer(SvcName, Count, SvcOpts, App, Ans);
{discard, Reason} ->
{error, Reason};
discard ->
@@ -1520,6 +1576,7 @@ send_R(ReqPkt,
{{TPid, _Caps} = TC, #diameter_app{dictionary = AppDict}},
#options{timeout = Timeout},
{Pid, Ref},
+ Count,
SvcName) ->
Req = #request{ref = Ref,
caller = Pid,
@@ -1527,7 +1584,7 @@ send_R(ReqPkt,
peer = TC,
packet = ReqPkt},
- incr(send, EncPkt, TPid, AppDict),
+ Count andalso incr(send, EncPkt, TPid, AppDict),
{TRef, MRef} = zend_requezt(TPid, EncPkt, Req, SvcName, Timeout),
Pid ! Ref, %% tell caller a send has been attempted
{TRef, MRef, Req}.
@@ -1559,15 +1616,16 @@ failover(SvcName, App, Req, CallOpts) ->
CallOpts,
SvcName).
-%% handle_answer/4
+%% handle_answer/5
-handle_answer(SvcName, _, App, {error, Req, Reason}) ->
+handle_answer(SvcName, _, _, App, {error, Req, Reason}) ->
#request{packet = Pkt,
peer = {_TPid, _Caps} = TC}
= Req,
cb(App, handle_error, [Reason, msg(Pkt), SvcName, TC]);
handle_answer(SvcName,
+ Count,
SvcOpts,
#diameter_app{id = Id,
dictionary = AppDict,
@@ -1581,43 +1639,50 @@ handle_answer(SvcName,
#request{peer = {TPid, _}}
= Req,
- incr(recv, DecPkt, TPid, AppDict),
-
- AnsPkt = try
- incr_result(recv, DecPkt, TPid, {MsgDict, AppDict, Dict0})
- of
- _ -> DecPkt
- catch
- exit: {no_result_code, _} ->
- %% RFC 6733 requires one of Result-Code or
- %% Experimental-Result, but the decode will have
- %% detected a missing AVP. If both are optional in
- %% the dictionary then this isn't a decode error:
- %% just continue on.
- DecPkt;
- exit: {invalid_error_bit, {_, _, _, Avp}} ->
- #diameter_packet{errors = Es}
- = DecPkt,
- E = {5004, Avp},
- DecPkt#diameter_packet{errors = [E|Es]}
- end,
-
- handle_answer(AnsPkt, SvcName, App, AE, Req).
+ answer(answer(DecPkt, TPid, MsgDict, AppDict, Dict0, Count),
+ SvcName,
+ App,
+ AE,
+ Req).
+
+%% answer/6
+
+answer(DecPkt, TPid, MsgDict, AppDict, Dict0, Count) ->
+ Count andalso incr(recv, DecPkt, TPid, AppDict),
+ try get_result(recv, MsgDict, Dict0, DecPkt) of
+ Avp ->
+ Count andalso false /= Avp
+ andalso incr_result(recv, Avp, DecPkt, TPid, AppDict),
+ DecPkt
+ catch
+ exit: {no_result_code, _} ->
+ %% RFC 6733 requires one of Result-Code or
+ %% Experimental-Result, but the decode will have
+ %% detected a missing AVP. If both are optional in
+ %% the dictionary then this isn't a decode error:
+ %% just continue on.
+ DecPkt;
+ exit: {invalid_error_bit, {_, _, _, Avp}} ->
+ #diameter_packet{errors = Es}
+ = DecPkt,
+ E = {5004, Avp},
+ DecPkt#diameter_packet{errors = [E|Es]}
+ end.
-%% handle_answer/5
+%% answer/5
-handle_answer(#diameter_packet{errors = Es}
- = Pkt,
- SvcName,
- App,
- AE,
- #request{peer = {_TPid, _Caps} = TC,
- packet = P})
+answer(#diameter_packet{errors = Es}
+ = Pkt,
+ SvcName,
+ App,
+ AE,
+ #request{peer = {_TPid, _Caps} = TC,
+ packet = P})
when callback == AE;
[] == Es ->
cb(App, handle_answer, [Pkt, msg(P), SvcName, TC]);
-handle_answer(#diameter_packet{header = H}, SvcName, _, AE, _) ->
+answer(#diameter_packet{header = H}, SvcName, _, AE, _) ->
handle_error(H, SvcName, AE).
%% handle_error/3
@@ -1830,10 +1895,8 @@ get_destination(Dict, Msg) ->
[str(get_avp_value(Dict, D, Msg)) || D <- ['Destination-Realm',
'Destination-Host']].
-%% This is not entirely correct. The avp could have an arity 1, in
-%% which case an empty list is a DiameterIdentity of length 0 rather
-%% than the list of no values we treat it as by mapping to undefined.
-%% This behaviour is documented.
+%% A DiameterIdentity has length at least one, so an empty list is not
+%% a Realm/Host.
str([]) ->
undefined;
str(T) ->
@@ -1841,16 +1904,12 @@ str(T) ->
%% get_avp/3
%%
-%% Find an AVP in a message of one of three forms:
-%%
-%% - a message record (as generated from a .dia spec) or
-%% - a list of an atom message name followed by 2-tuple, avp name/value pairs.
-%% - a list of a #diameter_header{} followed by #diameter_avp{} records,
-%%
-%% In the first two forms a dictionary module is used at encode to
-%% identify the type of the AVP and its arity in the message in
-%% question. The third form allows messages to be sent as is, without
-%% a dictionary, which is needed in the case of relay agents, for one.
+%% Find an AVP in a message in one of the decoded formats, or as a
+%% header/avps list. There are only four AVPs that are extracted here:
+%% Result-Code and Experimental-Result in order when constructing
+%% counter keys, and Destination-Host/Realm when selecting a next-hop
+%% peer. Experimental-Result is the only of type Grouped, and is given
+%% special treatment in order to return the value as a record.
%% Messages will be header/avps list as a relay and the only AVP's we
%% look for are in the common dictionary. This is required since the
@@ -1859,37 +1918,58 @@ str(T) ->
get_avp(?RELAY, Name, Msg) ->
get_avp(?BASE, Name, Msg);
-%% Message as a header/avps list.
+%% Message as header/avps list.
get_avp(Dict, Name, [#diameter_header{} | Avps]) ->
try
- {Code, _, VId} = Dict:avp_header(Name),
- find_avp(Code, VId, Avps)
- of
- A ->
- (avp_decode(Dict, Name, ungroup(A)))#diameter_avp{name = Name}
+ {Code, _, Vid} = Dict:avp_header(Name),
+ A = find_avp(Code, Vid, Avps),
+ avp_decode(Dict, Name, ungroup(A))
catch
error: _ ->
undefined
end;
-%% Outgoing message as a name/values list.
+%% Message as name/values list ...
get_avp(_, Name, [_MsgName | Avps]) ->
- case lists:keyfind(Name, 1, Avps) of
+ case find(Name, Avps) of
{_, V} ->
- #diameter_avp{name = Name, value = V};
+ #diameter_avp{name = Name, value = value(Name, V)};
_ ->
undefined
end;
-%% Message is typically a record but not necessarily.
+%% ... or record.
get_avp(Dict, Name, Rec) ->
- try
- #diameter_avp{name = Name, value = Dict:'#get-'(Name, Rec)}
+ try Dict:'#get-'(Name, Rec) of
+ V ->
+ #diameter_avp{name = Name, value = value(Name, V)}
catch
error:_ ->
undefined
end.
+value('Experimental-Result' = N, #{'Vendor-Id' := Vid,
+ 'Experimental-Result-Code' := RC}) ->
+ {N, Vid, RC};
+value('Experimental-Result' = N, [{'Experimental-Result-Code', RC},
+ {'Vendor-Id', Vid}]) ->
+ {N, Vid, RC};
+value('Experimental-Result' = N, [{'Vendor-Id', Vid},
+ {'Experimental-Result-Code', RC}]) ->
+ {N, Vid, RC};
+value(_, V) ->
+ V.
+
+%% find/2
+
+find(Key, Map)
+ when is_map(Map) ->
+ maps:find(Key, Map);
+
+find(Key, List)
+ when is_list(List) ->
+ lists:keyfind(Key, 1, List).
+
%% get_avp_value/3
get_avp_value(Dict, Name, Msg) ->
@@ -1909,18 +1989,25 @@ ungroup(Avp) ->
%% avp_decode/3
+%% Ensure Experimental-Result is decoded as record, since this format
+%% is used for counter keys.
+avp_decode(Dict, 'Experimental-Result' = N, #diameter_avp{data = Bin}
+ = Avp)
+ when is_binary(Bin) ->
+ {V,_} = Dict:avp(decode, Bin, N, decode_opts(Dict)),
+ Avp#diameter_avp{name = N, value = V};
+
avp_decode(Dict, Name, #diameter_avp{value = undefined,
data = Bin}
- = Avp) ->
- try Dict:avp(decode, Bin, Name, decode_opts(Dict)) of
- V ->
- Avp#diameter_avp{value = V}
- catch
- error:_ ->
- Avp
- end;
-avp_decode(_, _, #diameter_avp{} = Avp) ->
- Avp.
+ = Avp)
+ when is_binary(Bin) ->
+ V = Dict:avp(decode, Bin, Name, decode_opts(Dict)),
+ Avp#diameter_avp{name = Name, value = V};
+
+avp_decode(_, Name, #diameter_avp{} = Avp) ->
+ Avp#diameter_avp{name = Name}.
+
+%% cb/3
cb(#diameter_app{module = [_|_] = M}, F, A) ->
eval(M, F, A).
@@ -1933,7 +2020,9 @@ choose(false, _, X) -> X.
%% Decode options sufficient for AVP extraction.
decode_opts(Dict) ->
- #{string_decode => false,
+ #{decode_format => record,
+ string_decode => false,
strict_mbit => false,
failed_avp => false,
- dictionary => Dict}.
+ module => Dict,
+ app_dictionary => Dict}.
diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl
index a63425d92a..43623334a9 100644
--- a/lib/diameter/src/base/diameter_watchdog.erl
+++ b/lib/diameter/src/base/diameter_watchdog.erl
@@ -72,12 +72,11 @@
restrict := boolean(),
suspect := non_neg_integer(), %% OKAY -> SUSPECT
okay := non_neg_integer()}, %% REOPEN -> OKAY
- codec :: #{string_decode := false,
+ codec :: #{decode_format := none,
+ string_decode := false,
strict_mbit := boolean(),
- failed_avp := false,
rfc := 3588 | 6733,
- ordered_encode := false,
- incoming_maxlen := diameter:message_length()},
+ ordered_encode := false},
shutdown = false :: boolean()}).
%% ---------------------------------------------------------------------------
@@ -135,13 +134,6 @@ i({Ack, T, Pid, {Opts,
putr(restart, {T, Opts, Svc, SvcOpts}), %% save seeing it in trace
putr(dwr, dwr(Caps)), %%
Nodes = restrict_nodes(Restrict),
- CodecKeys = [string_decode,
- strict_mbit,
- incoming_maxlen,
- spawn_opt,
- rfc,
- ordered_encode],
-
#watchdog{parent = Pid,
transport = start(T, Opts, SvcOpts, Nodes, Dict0, Svc),
tw = proplists:get_value(watchdog_timer,
@@ -149,14 +141,23 @@ i({Ack, T, Pid, {Opts,
?DEFAULT_TW_INIT),
receive_data = RecvData,
dictionary = Dict0,
- config =
- maps:without(CodecKeys,
- config(SvcOpts#{restrict => restrict(Nodes),
- suspect => 1,
- okay => 3},
- Opts)),
- codec = maps:with(CodecKeys, SvcOpts#{string_decode := false,
- ordered_encode => false})}.
+ config = maps:with([sequence,
+ restrict_connections,
+ restrict,
+ suspect,
+ okay],
+ config(SvcOpts#{restrict => restrict(Nodes),
+ suspect => 1,
+ okay => 3},
+ Opts)),
+ codec = maps:with([decode_format,
+ strict_mbit,
+ string_decode,
+ rfc,
+ ordered_encode],
+ SvcOpts#{decode_format := none,
+ string_decode := false,
+ ordered_encode => false})}.
wait(Ref, Pid) ->
receive