aboutsummaryrefslogtreecommitdiffstats
path: root/lib/diameter/src/base
diff options
context:
space:
mode:
authorAnders Svensson <[email protected]>2017-07-06 11:02:31 +0200
committerAnders Svensson <[email protected]>2017-08-03 17:14:27 +0200
commit1b3b64af3d9a5441b6da37cf4e97b59cb043f33b (patch)
tree495cd9292b841814f4e509b78d68fe807ca6cd6f /lib/diameter/src/base
parent722fa41564381dff0b7aa2b465193db30bb2f02f (diff)
downloadotp-1b3b64af3d9a5441b6da37cf4e97b59cb043f33b.tar.gz
otp-1b3b64af3d9a5441b6da37cf4e97b59cb043f33b.tar.bz2
otp-1b3b64af3d9a5441b6da37cf4e97b59cb043f33b.zip
Let messages and grouped AVPs be encoded/decoded from/to maps
With {record_decode, map}. The option name is arguably a bit misleading now, but not too objectionable given that the encode/decode in question has historically only been of records. One advantage of the map decode is that the map only contains values for those AVPs existing in the message or grouped AVP in question. The name of the message or grouped AVP is stored in with key ':name', the leading colon ensuring that the key isn't a diameter-name. Decoding to maps makes the hrl files generated from dictionary files largely irrelevant. There are value defines generated into these, but they're typically so long as to be unusable.
Diffstat (limited to 'lib/diameter/src/base')
-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
6 files changed, 99 insertions, 26 deletions
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}