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.erl9
-rw-r--r--lib/diameter/src/base/diameter_codec.erl15
-rw-r--r--lib/diameter/src/base/diameter_config.erl9
-rw-r--r--lib/diameter/src/base/diameter_gen.erl372
-rw-r--r--lib/diameter/src/base/diameter_peer_fsm.erl9
-rw-r--r--lib/diameter/src/base/diameter_service.erl1
-rw-r--r--lib/diameter/src/base/diameter_traffic.erl82
-rw-r--r--lib/diameter/src/base/diameter_watchdog.erl9
8 files changed, 333 insertions, 173 deletions
diff --git a/lib/diameter/src/base/diameter.erl b/lib/diameter/src/base/diameter.erl
index bd92e16fba..85a54c8e61 100644
--- a/lib/diameter/src/base/diameter.erl
+++ b/lib/diameter/src/base/diameter.erl
@@ -47,6 +47,7 @@
stop/0]).
-export_type([evaluable/0,
+ decode_format/0,
restriction/0,
message_length/0,
remotes/0,
@@ -330,6 +331,13 @@ call(SvcName, App, Message) ->
-type message_length()
:: 0..16#FFFFFF.
+-type decode_format()
+ :: record
+ | list
+ | map
+ | false
+ | record_from_map.
+
%% Options passed to start_service/2
-type service_opt()
@@ -338,6 +346,7 @@ call(SvcName, App, Message) ->
| {restrict_connections, restriction()}
| {sequence, sequence() | evaluable()}
| {share_peers, remotes()}
+ | {decode_format, decode_format()}
| {string_decode, boolean()}
| {strict_mbit, boolean()}
| {incoming_maxlen, message_length()}
diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl
index 82fa796e69..9b21bf4141 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]).
@@ -287,7 +287,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).
@@ -375,10 +376,18 @@ decode_avps(MsgName, Mod, AppMod, Opts, Pkt, Avps) -> %% ... or not
Opts#{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(MsgName, Avps, #{decode_format := T})
+ when T == map;
+ T == list ->
+ [MsgName | Avps];
+
+reformat(_, Msg, _) ->
+ Msg.
+
%%% ---------------------------------------------------------------------------
%%% # decode_header/1
%%% ---------------------------------------------------------------------------
diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl
index 34018ae6d3..d591fa903e 100644
--- a/lib/diameter/src/base/diameter_config.erl
+++ b/lib/diameter/src/base/diameter_config.erl
@@ -713,6 +713,7 @@ make_config(SvcName, Opts) ->
{nodes, restrict_connections},
{16#FFFFFF, incoming_maxlen},
{true, strict_mbit},
+ {record, decode_format},
{true, string_decode},
{[], spawn_opt}]),
@@ -756,6 +757,7 @@ opt(K, false = B)
K == monitor;
K == restrict_connections;
K == strict_mbit;
+ K == decode_format;
K == string_decode ->
B;
@@ -766,6 +768,13 @@ opt(K, true = B)
K == string_decode ->
B;
+opt(decode_format, T)
+ when T == record;
+ T == list;
+ T == map;
+ T == record_from_map ->
+ T;
+
opt(restrict_connections, T)
when T == node;
T == nodes ->
diff --git a/lib/diameter/src/base/diameter_gen.erl b/lib/diameter/src/base/diameter_gen.erl
index e832832876..a7dc3aaec3 100644
--- a/lib/diameter/src/base/diameter_gen.erl
+++ b/lib/diameter/src/base/diameter_gen.erl
@@ -50,7 +50,9 @@
%% # encode_avps/3
%% ---------------------------------------------------------------------------
--spec encode_avps(parent_name(), parent_record() | avp_values(), map())
+-spec encode_avps(parent_name(),
+ parent_record() | avp_values() | map(),
+ map())
-> iolist()
| no_return().
@@ -83,6 +85,11 @@ encode(Name, Vals, Opts, Mod)
when is_list(Vals) ->
encode(Name, Mod:'#set-'(Vals, newrec(Mod, Name)), Opts, Mod);
+encode(Name, Map, Opts, Mod)
+ when is_map(Map) ->
+ [enc(Name, F, A, V, Opts, Mod) || {F,A} <- Mod:avp_arity(Name),
+ V <- [maps:get(F, Map, def(A))]];
+
encode(Name, Rec, Opts, Mod) ->
[encode(Name, F, V, Opts, Mod) || {F,V} <- Mod:'#get-'(Rec)].
@@ -97,7 +104,8 @@ enc(_, AvpName, 1, undefined, _, _) ->
?THROW([mandatory_avp_missing, AvpName]);
enc(Name, AvpName, 1, Value, Opts, Mod) ->
- enc(Name, AvpName, [Value], Opts, Mod);
+ H = avp_header(AvpName, Mod),
+ enc1(Name, AvpName, H, Value, Opts, Mod);
enc(_, _, {0,_}, [], _, _) ->
[];
@@ -106,31 +114,51 @@ enc(_, AvpName, _, T, _, _)
when not is_list(T) ->
?THROW([repeated_avp_as_non_list, AvpName, T]);
-enc(_, AvpName, {Min, _}, L, _, _)
- when length(L) < Min ->
- ?THROW([repeated_avp_insufficient_arity, AvpName, Min, L]);
+enc(Name, AvpName, {Min, Max}, Values, Opts, Mod) ->
+ H = avp_header(AvpName, Mod),
+ enc(Name, AvpName, H, Min, 0, Max, Values, Opts, Mod).
-enc(_, AvpName, {_, Max}, L, _, _)
- when Max < length(L) ->
- ?THROW([repeated_avp_excessive_arity, AvpName, Max, L]);
+%% enc/9
-enc(Name, AvpName, _, Values, Opts, Mod) ->
- enc(Name, AvpName, Values, Opts, Mod).
+enc(_, AvpName, _, Min, N, _, [], _, _)
+ when N < Min ->
+ ?THROW([repeated_avp_insufficient_arity, AvpName, Min, N]);
-%% enc/5
+enc(_, _, _, _, _, _, [], _, _) ->
+ [];
-enc(Name, 'AVP', Values, Opts, Mod) ->
- [enc_AVP(Name, A, Opts, Mod) || A <- Values];
+enc(Name, AvpName, H, Min, N, '*', Vs, Opts, Mod)
+ when Min =< N ->
+ [enc1(Name, AvpName, H, V, Opts, Mod) || V <- Vs];
-enc(_, AvpName, Values, Opts, Mod) ->
- enc(AvpName, Values, Opts, Mod).
+enc(_, AvpName, _, _, N, Max, _, _, _)
+ when Max =< N ->
+ ?THROW([repeated_avp_excessive_arity, AvpName, Max]);
-%% enc/4
+enc(Name, AvpName, H, Min, N, Max, [V|Vs], Opts, Mod) ->
+ [enc1(Name, AvpName, H, V, Opts, Mod)
+ | enc(Name, AvpName, H, Min, N+1, Max, Vs, Opts, Mod)].
+
+%% avp_header/2
+
+avp_header('AVP', _) ->
+ false;
-enc(AvpName, Values, Opts, Mod) ->
- H = Mod:avp_header(AvpName),
- [diameter_codec:pack_data(H, Mod:avp(encode, V, AvpName, Opts))
- || V <- Values].
+avp_header(AvpName, Mod) ->
+ {_,_,_} = Mod:avp_header(AvpName).
+
+%% enc1/6
+
+enc1(Name, 'AVP', false, Value, Opts, Mod) ->
+ enc_AVP(Name, Value, Opts, Mod);
+
+enc1(_, AvpName, Hdr, Value, Opts, Mod) ->
+ enc1(AvpName, Hdr, Value, Opts, Mod).
+
+%% enc1/5
+
+enc1(AvpName, {_,_,_} = Hdr, Value, Opts, Mod) ->
+ diameter_codec:pack_data(Hdr, Mod:avp(encode, Value, AvpName, Opts)).
%% enc_AVP/4
@@ -153,16 +181,21 @@ enc_AVP(_, #diameter_avp{name = N, value = V}, _, _)
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(AvpName, Data, Opts, Mod);
%% The backdoor ...
enc_AVP(_, {AvpName, Value}, Opts, Mod) ->
- enc(AvpName, [Value], Opts, Mod);
+ enc(AvpName, Value, Opts, Mod);
%% ... and the side door.
enc_AVP(_Name, {_Dict, _AvpName, _Data} = T, Opts, _) ->
diameter_codec:pack_avp(#diameter_avp{data = T}, Opts).
+%% enc/4
+
+enc(AvpName, Value, Opts, Mod) ->
+ enc1(AvpName, Mod:avp_header(AvpName), Value, Opts, Mod).
+
%% ---------------------------------------------------------------------------
%% # decode_avps/3
%% ---------------------------------------------------------------------------
@@ -171,15 +204,21 @@ enc_AVP(_Name, {_Dict, _AvpName, _Data} = T, Opts, _) ->
-> {parent_record(), [avp()], Failed}
when Failed :: [{5000..5999, #diameter_avp{}}].
-decode_avps(Name, Recs, #{module := Mod} = Opts) ->
- {Avps, {Rec, Failed}}
+decode_avps(Name, Recs, #{module := Mod, decode_format := Fmt} = Opts) ->
+ {Avps, {Rec, AM, Failed}}
= mapfoldl(fun(T,A) -> decode(Name, Opts, Mod, T, A) end,
- {newrec(Mod, Name), []},
+ {newrec(Mod, Name, Fmt), #{}, []},
Recs),
- {Rec, Avps, Failed ++ missing(Rec, Name, Failed, Opts, Mod)}.
+ %% AM counts the number of top-level AVPs, which missing/4 then
+ %% uses when adding 5005 errors.
+ Arities = Mod:avp_arity(Name),
+ {reformat(Name, Rec, Arities, Mod, Fmt),
+ Avps,
+ Failed ++ missing(Arities, Opts, Mod, AM)}.
+
%% Append 5005 errors so that errors are reported in the order
%% encountered. Failed-AVP should typically contain the first
-%% encountered error accordg to the RFC.
+%% error encountered.
%% mapfoldl/3
%%
@@ -194,6 +233,8 @@ mapfoldl(F, Acc0, [T|Rest], List) ->
mapfoldl(_, Acc, [], List) ->
{List, Acc}.
+%% missing/4
+
%% 3588:
%%
%% DIAMETER_MISSING_AVP 5005
@@ -204,57 +245,41 @@ 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(_, []) ->
+missing(Arities, Opts, Mod, AM) ->
+ lists:foldl(fun(T,A) -> missing(T, AM, Opts, Mod, A) end,
+ [],
+ Arities).
+
+%% missing/5
+
+missing({Name, Arity}, AM, Opts, Mod, Acc) ->
+ case missing(Name, Arity, AM) of
+ true -> [{5005, empty_avp(Name, Opts, Mod)} | Acc];
+ false -> Acc
+ end.
+
+%% missing/3
+
+missing(Name, Arity, AM) ->
+ 'AVP' /= Name andalso too_few(Name, Arity, AM).
+
+%% too_few/3
+%%
+%% Maximum arities have already been checked during the decode.
+
+too_few(_, {0, _}, _) ->
false;
-has_prefix(N, [_|L]) ->
- has_prefix(N-1, L).
-%% empty_avp/4
+too_few(FieldName, 1, AM) ->
+ not maps:is_key(FieldName, AM);
+
+too_few(FieldName, {M, _}, AM) ->
+ maps:get(FieldName, AM, 0) < M.
-empty_avp(Name, {Code, Flags, VId}, Opts, Mod) ->
+%% empty_avp/3
+
+empty_avp(Name, Opts, Mod) ->
+ {Code, Flags, VId} = Mod:avp_header(Name),
{Name, Type} = Mod:avp_name(Code, VId),
#diameter_avp{name = Name,
code = Code,
@@ -280,8 +305,27 @@ decode(Name,
Mod,
#diameter_avp{code = Code, vendor_id = Vid}
= Avp,
- Acc) ->
- decode(Name, Opts, Mod, Mod:avp_name(Code, Vid), Avp, Acc).
+ {Rec, AM, Failed}) ->
+ T = Mod:avp_name(Code, Vid),
+ decode(Name, Opts, Mod, T, Avp, {Rec, incr(field(T), AM), Failed}).
+
+%% field/1
+
+field({AvpName, _Type}) ->
+ AvpName;
+
+field('AVP' = A) ->
+ A.
+
+%% incr/2
+
+incr(Key, Map) ->
+ maps:update_with(Key, fun incr/1, 1, Map).
+
+%% incr/1
+
+incr(N) ->
+ N + 1.
%% decode/6
@@ -352,15 +396,13 @@ decode(Name, Opts0, Mod, {AvpName, Type}, Avp, Acc) ->
%% decode is packed into 'AVP'.
%% Reset the dictionary for best-effort decode of Failed-AVP.
- DecMod = if Failed ->
- AppMod;
- true ->
- Mod
+ 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.
+ %% 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' ->
@@ -425,7 +467,7 @@ trim(Avp) ->
%% decode_error/7
-decode_error(Name, [_ | Rec], _, #{failed_avp := true} = Opts, Mod, Avp, Acc) ->
+decode_error(Name, [_|Rec], _, #{failed_avp := true} = Opts, Mod, Avp, Acc) ->
decode_AVP(Name, Avp#diameter_avp{value = Rec}, Opts, Mod, Acc);
decode_error(Name, _, _, #{failed_avp := true} = Opts, Mod, Avp, Acc) ->
@@ -437,24 +479,25 @@ decode_error(_, [Error | _], ComponentAvps, _, _, Avp, Acc) ->
decode_error(_, Error, ComponentAvps, _, _, Avp, Acc) ->
decode_error(Error, Avp, Acc, ComponentAvps).
-%% decode_error/5
+%% decode_error/6
decode_error(Name, _Reason, #{failed_avp := true} = Opts, Mod, Avp, Acc) ->
decode_AVP(Name, Avp, Opts, Mod, Acc);
-decode_error(Name, Reason, Opts, Mod, Avp, {Rec, Failed}) ->
+decode_error(Name, Reason, Opts, Mod, Avp, {Rec, AM, Failed}) ->
Stack = diameter_lib:get_stacktrace(),
+ AvpName = Avp#diameter_avp.name,
diameter_lib:log(decode_error,
?MODULE,
?LINE,
- {Reason, Name, Avp#diameter_avp.name, Mod, Stack}),
- {Avp, {Rec, [rc(Reason, Avp, Opts, Mod) | Failed]}}.
+ {Reason, Name, AvpName, Mod, Stack}),
+ {Avp, {Rec, AM, [rc(Reason, Avp, Opts, Mod) | Failed]}}.
%% decode_error/4
-decode_error({RC, ErrorData}, Avp, {Rec, Failed}, ComponentAvps) ->
+decode_error({RC, ErrorData}, Avp, {Rec, AM, Failed}, ComponentAvps) ->
E = Avp#diameter_avp{data = [ErrorData]},
- {[Avp | trim(ComponentAvps)], {Rec, [{RC, E} | Failed]}}.
+ {[Avp | trim(ComponentAvps)], {Rec, AM, [{RC, E} | Failed]}}.
%% set_strict/3
@@ -490,8 +533,8 @@ decode_AVP(Name, Avp, Opts, Mod, Acc) ->
%% 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)}};
+rc({'DIAMETER', 5014 = RC, _}, #diameter_avp{name = AvpName} = A, Opts, Mod) ->
+ {RC, A#diameter_avp{data = Mod:empty_value(AvpName, Opts)}};
%% 3588:
%%
@@ -531,20 +574,33 @@ pack_avp(_, Arity, #diameter_avp{name = AvpName} = Avp, _Opts, Mod, Acc) ->
%% type.
pack_AVP(_, #diameter_avp{data = {5014 = RC, Data}} = Avp, _, _, Acc) ->
- {Rec, Failed} = Acc,
- {Rec, [{RC, Avp#diameter_avp{data = Data}} | Failed]};
+ {Rec, AM, Failed} = Acc,
+ {Rec, AM, [{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
+ Arity = pack_arity(Name, Opts, Mod, Avp),
+ if 0 == Arity ->
+ M = Avp#diameter_avp.is_mandatory,
+ {Rec, AM, Failed} = Acc,
+ {Rec, AM, [{if M -> 5001; true -> 5008 end, Avp} | Failed]};
+ true ->
+ pack(Arity, 'AVP', Avp, Mod, Acc)
+ end.
-pack_arity(_, 0, #diameter_avp{is_mandatory = M} = Avp, _, Acc) ->
- {Rec, Failed} = Acc,
- {Rec, [{if M -> 5001; true -> 5008 end, Avp} | Failed]};
+%% 3588:
+%%
+%% DIAMETER_AVP_UNSUPPORTED 5001
+%% The peer received a message that contained an AVP that is not
+%% recognized or supported and was marked with the Mandatory bit. A
+%% Diameter message with this error MUST contain one or more Failed-
+%% AVP AVP containing the AVPs that caused the failure.
+%%
+%% DIAMETER_AVP_NOT_ALLOWED 5008
+%% A message was received with an AVP that MUST NOT be present. The
+%% Failed-AVP AVP MUST be included and contain a copy of the
+%% offending AVP.
-pack_arity(_, Arity, Avp, Mod, Acc) ->
- pack(Arity, 'AVP', Avp, Mod, Acc).
+%% pack_arity/4
%% Give Failed-AVP special treatment since (1) it'll contain any
%% unrecognized mandatory AVP's and (2) the RFC 3588 grammar failed to
@@ -573,31 +629,13 @@ pack_arity(Name,
0
end.
-%% 3588:
-%%
-%% DIAMETER_AVP_UNSUPPORTED 5001
-%% The peer received a message that contained an AVP that is not
-%% recognized or supported and was marked with the Mandatory bit. A
-%% Diameter message with this error MUST contain one or more Failed-
-%% AVP AVP containing the AVPs that caused the failure.
-%%
-%% DIAMETER_AVP_NOT_ALLOWED 5008
-%% A message was received with an AVP that MUST NOT be present. The
-%% Failed-AVP AVP MUST be included and contain a copy of the
-%% offending AVP.
-
%% pack/5
-pack(Arity, FieldName, Avp, Mod, {Rec, _} = Acc) ->
- pack(Mod:'#get-'(FieldName, Rec), Arity, FieldName, Avp, Mod, Acc).
-
-%% pack/6
-
-pack(undefined, 1, 'AVP' = F, Avp, Mod, {Rec, Failed}) -> %% unlikely
- {Mod:'#set-'({F, Avp}, Rec), Failed};
-
-pack(undefined, 1, F, #diameter_avp{value = V}, Mod, {Rec, Failed}) ->
- {Mod:'#set-'({F, V}, Rec), Failed};
+pack(Arity, F, Avp, Mod, {Rec, AM, Failed}) ->
+ case too_many(F, Arity, AM) of
+ true -> {Rec, AM, [{5009, Avp} | Failed]};
+ false -> {set(Arity, F, value(F, Avp), Mod, Rec), AM, Failed}
+ end.
%% 3588:
%%
@@ -608,18 +646,45 @@ pack(undefined, 1, F, #diameter_avp{value = V}, Mod, {Rec, Failed}) ->
%% the offending AVP that exceeded the maximum number of occurrences
%%
-pack(_, 1, _, Avp, _, {Rec, Failed}) ->
- {Rec, [{5009, Avp} | Failed]};
+%% too_many/3
-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.
+too_many(_, {_, '*'}, _) ->
+ false;
+
+too_many(FieldName, {_, M}, Map) ->
+ too_many(FieldName, M, Map);
+
+too_many(FieldName, M, Map) ->
+ #{FieldName := N} = Map,
+ M < N.
+
+%% set/5
+
+set(_, _, _, _, false = No) ->
+ No;
+
+set(1, F, Value, _, Map)
+ when is_map(Map) ->
+ maps:put(F, Value, Map);
+
+set(_, F, V, _, Map)
+ when is_map(Map) ->
+ maps:update_with(F, fun(Vs) -> [V|Vs] end, [V], Map);
+
+set(1, F, Value, Mod, Rec) ->
+ Mod:'#set-'({F, Value}, Rec);
+
+set(_, F, V, Mod, Rec) ->
+ Vs = Mod:'#get-'(F, Rec),
+ Mod:'#set-'({F, [V|Vs]}, Rec).
+
+%% value/2
+
+value('AVP', Avp) ->
+ Avp;
+
+value(_, #diameter_avp{value = V}) ->
+ V.
%% ---------------------------------------------------------------------------
%% # grouped_avp/3
@@ -705,5 +770,38 @@ empty(Name, #{module := Mod} = Opts) ->
%% ------------------------------------------------------------------------------
+%% newrec/3
+
+newrec(_, _, false = No) ->
+ No;
+
+newrec(Mod, Name, record) ->
+ newrec(Mod, Name);
+
+newrec(_, _, _) ->
+ #{}.
+
+%% newrec/2
+
newrec(Mod, Name) ->
Mod:'#new-'(Mod:name2rec(Name)).
+
+%% reformat/4
+
+reformat(_, Map, Arities, _Mod, list) ->
+ [{F,V} || {F,_} <- Arities, #{F := V} <- [Map]];
+
+reformat(Name, Map, Arities, Mod, record_from_map) ->
+ RecName = Mod:name2rec(Name),
+ list_to_tuple([RecName | [maps:get(F, Map, def(A)) || {F,A} <- Arities]]);
+
+reformat(_, Rec, _, _, _) ->
+ Rec.
+
+%% def/1
+
+def(1) ->
+ undefined;
+
+def(_) ->
+ [].
diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl
index e43b3f54cf..597e395c62 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 := record,
+ string_decode := boolean(),
strict_mbit := boolean(),
rfc := 3588 | 6733,
ordered_encode := false},
@@ -253,11 +254,13 @@ i({Ack, WPid, {M, Ref} = T, Opts, {SvcOpts, Nodes, Dict0, Svc}}) ->
length_errors = LengthErr,
strict = Strictness,
incoming_maxlen = Maxlen,
- codec = maps:with([string_decode,
+ codec = maps:with([decode_format,
+ string_decode,
strict_mbit,
rfc,
ordered_encode],
- SvcOpts#{ordered_encode => false})}.
+ SvcOpts#{ordered_encode => false,
+ decode_format => record})}.
%% The transport returns its local ip addresses so that different
%% transports on the same service can use different local addresses.
%% The local addresses are put into Host-IP-Address avps here when
diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl
index 788f697627..cbb7549e39 100644
--- a/lib/diameter/src/base/diameter_service.erl
+++ b/lib/diameter/src/base/diameter_service.erl
@@ -113,6 +113,7 @@
restrict_connections := diameter:restriction(),
incoming_maxlen := diameter:message_length(),
strict_mbit := boolean(),
+ decode_format := diameter:decode_format(),
string_decode := boolean(),
spawn_opt := list() | {module(), atom(), list()}}}).
diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl
index 85378babea..e9c422d6ab 100644
--- a/lib/diameter/src/base/diameter_traffic.erl
+++ b/lib/diameter/src/base/diameter_traffic.erl
@@ -76,7 +76,8 @@
service_name :: diameter:service_name(),
apps :: [#diameter_app{}],
sequence :: diameter:sequence(),
- codec :: #{string_decode := boolean(),
+ codec :: #{decode_format := diameter:decode_format(),
+ string_decode := boolean(),
strict_mbit := boolean(),
incoming_maxlen := diameter:message_length()}}).
%% Note that incoming_maxlen is currently handled in diameter_peer_fsm,
@@ -102,7 +103,8 @@ make_recvdata([SvcName, PeerT, Apps, SvcOpts | _]) ->
peerT = PeerT,
apps = Apps,
sequence = Mask,
- codec = maps:with([string_decode,
+ codec = maps:with([decode_format,
+ string_decode,
strict_mbit,
ordered_encode,
incoming_maxlen],
@@ -619,7 +621,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';
@@ -867,7 +869,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 +905,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
%%
@@ -1859,7 +1875,7 @@ str(T) ->
get_avp(?RELAY, Name, Msg) ->
get_avp(?BASE, Name, Msg);
-%% Message as a header/avps list.
+%% Message is a header/avps list.
get_avp(Dict, Name, [#diameter_header{} | Avps]) ->
try
{Code, _, VId} = Dict:avp_header(Name),
@@ -1872,16 +1888,16 @@ get_avp(Dict, Name, [#diameter_header{} | Avps]) ->
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};
_ ->
undefined
end;
-%% Message is typically a record but not necessarily.
+%% ... or record (but not necessarily).
get_avp(Dict, Name, Rec) ->
try
#diameter_avp{name = Name, value = Dict:'#get-'(Name, Rec)}
@@ -1890,6 +1906,16 @@ get_avp(Dict, Name, Rec) ->
undefined
end.
+%% 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) ->
@@ -1911,7 +1937,8 @@ ungroup(Avp) ->
avp_decode(Dict, Name, #diameter_avp{value = undefined,
data = Bin}
- = Avp) ->
+ = Avp)
+ when is_binary(Bin) ->
try Dict:avp(decode, Bin, Name, decode_opts(Dict)) of
V ->
Avp#diameter_avp{value = V}
@@ -1933,7 +1960,8 @@ 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}.
diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl
index a63425d92a..60baf1e8a4 100644
--- a/lib/diameter/src/base/diameter_watchdog.erl
+++ b/lib/diameter/src/base/diameter_watchdog.erl
@@ -72,7 +72,8 @@
restrict := boolean(),
suspect := non_neg_integer(), %% OKAY -> SUSPECT
okay := non_neg_integer()}, %% REOPEN -> OKAY
- codec :: #{string_decode := false,
+ codec :: #{decode_format := false,
+ string_decode := false,
strict_mbit := boolean(),
failed_avp := false,
rfc := 3588 | 6733,
@@ -135,7 +136,8 @@ 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,
+ CodecKeys = [decode_format,
+ string_decode,
strict_mbit,
incoming_maxlen,
spawn_opt,
@@ -155,7 +157,8 @@ i({Ack, T, Pid, {Opts,
suspect => 1,
okay => 3},
Opts)),
- codec = maps:with(CodecKeys, SvcOpts#{string_decode := false,
+ codec = maps:with(CodecKeys, SvcOpts#{decode_format := false,
+ string_decode := false,
ordered_encode => false})}.
wait(Ref, Pid) ->