aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/diameter/doc/src/diameter.xml9
-rw-r--r--lib/diameter/doc/src/diameter_codec.xml9
-rw-r--r--lib/diameter/src/base/diameter.erl2
-rw-r--r--lib/diameter/src/base/diameter_codec.erl3
-rw-r--r--lib/diameter/src/base/diameter_config.erl3
-rw-r--r--lib/diameter/src/base/diameter_gen.erl28
-rw-r--r--lib/diameter/src/base/diameter_service.erl2
-rw-r--r--lib/diameter/src/base/diameter_traffic.erl87
8 files changed, 111 insertions, 32 deletions
diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml
index 663c9cc785..bfb6da41b5 100644
--- a/lib/diameter/doc/src/diameter.xml
+++ b/lib/diameter/doc/src/diameter.xml
@@ -539,7 +539,7 @@ that matches no peer.</p>
<p>
The <c>host</c> and <c>realm</c> filters cause the Destination-Host
and Destination-Realm AVPs to be extracted from the
-outgoing request, assuming it to be a record- or list-valued
+outgoing request, assuming it to be a record-, list- or map-valued
<c>&codec_message;</c>, and assuming at most one of each AVP.
If this is not the case then the <c>{host|realm, &dict_DiameterIdentity;}</c>
filters must be used to achieve the desired result.
@@ -970,13 +970,14 @@ occur in the message in question.</p>
</item>
<tag>
-<marker id="record_decode"/><c>{record_decode, boolean()}</c></tag>
+<marker id="record_decode"/><c>{record_decode, boolean() | map}</c></tag>
<item>
<p>
Whether or not to decode message and grouped AVPs to records in the
<c>msg</c> field of diameter_packet records and <c>value</c> field of
-diameter_avp records respectively.
-If <c>false</c> then the fields are set to the same value.</p>
+diameter_avp records respectively, or to an alternate format.
+If false then the fields are set to the same value.
+See also &codec_message;.</p>
<p>
Defaults to <c>true</c>.</p>
diff --git a/lib/diameter/doc/src/diameter_codec.xml b/lib/diameter/doc/src/diameter_codec.xml
index 0117c1c88a..6262dbf00d 100644
--- a/lib/diameter/doc/src/diameter_codec.xml
+++ b/lib/diameter/doc/src/diameter_codec.xml
@@ -230,7 +230,7 @@ header.</p>
</item>
<tag>
-<marker id="message"/><c>message() = record() | list()</c></tag>
+<marker id="message"/><c>message() = record() | list() | map()</c></tag>
<item>
<p>
The representation of a Diameter message as passed to
@@ -240,7 +240,12 @@ a message as defined in a dictionary file is encoded as a record with
one field for each component AVP.
Equivalently, a message can also be encoded as a list whose head is
the atom-valued message name (as specified in the relevant dictionary
-file) and whose tail is a list of <c>{AvpName, AvpValue}</c> pairs.</p>
+file) and whose tail is a list of <c>{AvpName, AvpValues}</c> pairs,
+or as a map with values keyed on AVP names and the message name in key
+<c>:name</c>.
+The format at decode is determined by &mod_service_opt;
+<c>record_decode</c>.
+Any of the formats is accepted at encode.</p>
<p>
Another list-valued representation allows a message to be specified
diff --git a/lib/diameter/src/base/diameter.erl b/lib/diameter/src/base/diameter.erl
index f411656e90..4269c84cd2 100644
--- a/lib/diameter/src/base/diameter.erl
+++ b/lib/diameter/src/base/diameter.erl
@@ -338,7 +338,7 @@ call(SvcName, App, Message) ->
| {restrict_connections, restriction()}
| {sequence, sequence() | evaluable()}
| {share_peers, remotes()}
- | {record_decode, boolean()}
+ | {record_decode, boolean() | map}
| {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 5e4c6e6d8f..0c43d52093 100644
--- a/lib/diameter/src/base/diameter_codec.erl
+++ b/lib/diameter/src/base/diameter_codec.erl
@@ -275,6 +275,9 @@ rec2msg(_, [Name|_])
when is_atom(Name) ->
Name;
+rec2msg(_, #{':name' := Name}) ->
+ Name;
+
rec2msg(Mod, Rec) ->
Mod:rec2msg(element(1, Rec)).
diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl
index 46a3e362ac..a790b946c1 100644
--- a/lib/diameter/src/base/diameter_config.erl
+++ b/lib/diameter/src/base/diameter_config.erl
@@ -769,6 +769,9 @@ opt(K, true = B)
K == string_decode ->
B;
+opt(record_decode, map = T) ->
+ 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 4879ad8f6c..be2e221b7e 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)].
@@ -627,6 +634,14 @@ too_many(FieldName, M, Map) ->
set(_, _, _, _, undefined = 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);
@@ -731,6 +746,9 @@ empty(Name, #{module := Mod} = Opts) ->
newrec(_, _, #{record_decode := false}) ->
undefined;
+newrec(_, Name, #{record_decode := map}) ->
+ #{':name' => Name};
+
newrec(Mod, Name, _) ->
newrec(Mod, Name).
@@ -738,3 +756,11 @@ newrec(Mod, Name, _) ->
newrec(Mod, Name) ->
Mod:'#new-'(Mod:name2rec(Name)).
+
+%% def/1
+
+def(1) ->
+ undefined;
+
+def(_) ->
+ [].
diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl
index 6dc4889c82..3e555f6263 100644
--- a/lib/diameter/src/base/diameter_service.erl
+++ b/lib/diameter/src/base/diameter_service.erl
@@ -113,7 +113,7 @@
restrict_connections := diameter:restriction(),
incoming_maxlen := diameter:message_length(),
strict_mbit := boolean(),
- record_decode := boolean(),
+ record_decode := boolean() | map,
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 228d9802ad..f7dec2441f 100644
--- a/lib/diameter/src/base/diameter_traffic.erl
+++ b/lib/diameter/src/base/diameter_traffic.erl
@@ -76,7 +76,7 @@
service_name :: diameter:service_name(),
apps :: [#diameter_app{}],
sequence :: diameter:sequence(),
- codec :: #{record_decode := boolean(),
+ codec :: #{record_decode := boolean() | map,
string_decode := boolean(),
strict_mbit := boolean(),
incoming_maxlen := diameter:message_length()}}).
@@ -625,6 +625,12 @@ is_answer_message([#diameter_header{is_request = R, is_error = E} | _], _) ->
is_answer_message([Name | _], _) ->
Name == 'answer-message';
+%% Message sent as a map.
+is_answer_message(Map, _)
+ when is_map(Map) ->
+ #{':name' := Name} = Map,
+ Name == 'answer-message';
+
%% Message sent as a record.
is_answer_message(Rec, Dict) ->
try
@@ -873,6 +879,11 @@ reset(Msg, [RC | Avps], Dict) ->
set([_|_] = Ans, Avps, _) ->
Ans ++ Avps; %% Values nearer tail take precedence.
+%% ... a map ...
+set(Ans, Avps, _)
+ when is_map(Ans) ->
+ maps:merge(Ans, maps:from_list(Avps));
+
%% ... or record.
set(Rec, Avps, Dict) ->
Dict:'#set-'(Avps, Rec).
@@ -891,6 +902,9 @@ rc([MsgName | _], RC, Dict) ->
_ -> []
end;
+rc(#{':name' := Name}, RC, Dict) ->
+ rc([Name], RC, Dict);
+
rc(Rec, RC, Dict) ->
rc([Dict:rec2msg(element(1, Rec))], RC, Dict).
@@ -902,34 +916,50 @@ 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),
- try
- Dict:'#info-'(RecName, {index, 'Failed-AVP'}),
- {'Failed-AVP', [FailedAvp]}
- catch
- error: _ ->
- Avps = proplists:get_value('AVP', Values, []),
- A = #diameter_avp{name = 'Failed-AVP',
- value = FailedAvp},
- {'AVP', [A|Avps]}
- end;
+%% failed/3
-%% ... or record.
-failed(Rec, FailedAvp, Dict) ->
+failed(Msg, FailedAvp, Dict) ->
+ RecName = msg2rec(Msg, Dict),
try
- RecName = element(1, Rec),
- Dict:'#info-'(RecName, {index, 'Failed-AVP'}),
+ Dict:'#info-'(RecName, {index, 'Failed-AVP'}), %% assert existence
{'Failed-AVP', [FailedAvp]}
catch
error: _ ->
- Avps = Dict:'#get-'('AVP', Rec),
+ 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);
+
+%% ... map ...
+msg2rec(#{':name' := MsgName}, Dict) ->
+ Dict:msg2rec(MsgName);
+
+%% ... or record.
+msg2rec(Rec, _) ->
+ element(1, Rec).
+
+%% values/2
+
+%% Message as name/values list ...
+values([_ | Avps], F, _) ->
+ proplists:get_value(F, Avps, []);
+
+%% ... map ...
+values(Msg, F, _)
+ when is_map(Msg) ->
+ maps:get(F, Msg, []);
+
+%% ... or record.
+values(Rec, F, Dict) ->
+ Dict:'#get-'(F, Rec).
+
%% 3. Diameter Header
%%
%% E(rror) - If set, the message contains a protocol error,
@@ -1861,7 +1891,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),
@@ -1874,7 +1904,7 @@ 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
{_, V} ->
@@ -1883,7 +1913,17 @@ get_avp(_, Name, [_MsgName | Avps]) ->
undefined
end;
-%% Message is typically a record but not necessarily.
+%% ... map ...
+get_avp(_, Name, Map)
+ when is_map(Map) ->
+ case maps:find(Name, Map) of
+ {ok, V} ->
+ #diameter_avp{name = Name, value = V};
+ error ->
+ undefined
+ end;
+
+%% ... or record (but not necessarily).
get_avp(Dict, Name, Rec) ->
try
#diameter_avp{name = Name, value = Dict:'#get-'(Name, Rec)}
@@ -1913,7 +1953,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}