diff options
author | Anders Svensson <[email protected]> | 2017-07-06 11:02:31 +0200 |
---|---|---|
committer | Anders Svensson <[email protected]> | 2017-08-03 17:14:27 +0200 |
commit | 1b3b64af3d9a5441b6da37cf4e97b59cb043f33b (patch) | |
tree | 495cd9292b841814f4e509b78d68fe807ca6cd6f /lib | |
parent | 722fa41564381dff0b7aa2b465193db30bb2f02f (diff) | |
download | otp-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')
-rw-r--r-- | lib/diameter/doc/src/diameter.xml | 9 | ||||
-rw-r--r-- | lib/diameter/doc/src/diameter_codec.xml | 9 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter.erl | 2 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_codec.erl | 3 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_config.erl | 3 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_gen.erl | 28 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_service.erl | 2 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_traffic.erl | 87 |
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} |