diff options
author | Anders Svensson <[email protected]> | 2017-08-18 10:58:16 +0200 |
---|---|---|
committer | Anders Svensson <[email protected]> | 2017-08-18 10:58:16 +0200 |
commit | c568b8ac624f39bf5a8d4e66f1db32ea629937f4 (patch) | |
tree | 4aa1ec71affb64b433e406caff733277d70c800f /lib/diameter/src | |
parent | 0cf60db18f71f727a28ba726e0338ef41ec17542 (diff) | |
parent | fa233bb7bc4f37632166c468a0381e695433c318 (diff) | |
download | otp-c568b8ac624f39bf5a8d4e66f1db32ea629937f4.tar.gz otp-c568b8ac624f39bf5a8d4e66f1db32ea629937f4.tar.bz2 otp-c568b8ac624f39bf5a8d4e66f1db32ea629937f4.zip |
Merge branch 'anders/diameter/codec/OTP-14511' into maint
* anders/diameter/codec/OTP-14511: (26 commits)
Limit SCTP testing in traffic suite
Increase init_per_group timetrap in traffic suite
Add diameter_util:eprof/1 for test
Don't search forms unnecessarily in diameter_exprecs parse transform
Increase init_per_suite timetrap in traffic suite
Don't count AVPs unnecessarily at encode
Test decode_format record_from_map in traffic suite
Tweak limiting of testcases in traffic suite
Don't take length of AVP lists unnecessarily at encode
Tweak map-valued decode
Rearrange group names in traffic suite
Randomly wrap answers in diameter_packet in transport suite
Don't exercise client/server encoding independently in traffic suite
Add decode_format record_from_map
Rename record_decode -> decode_format
Create fewer client connections in traffic suite
Test record_decode in traffic suite
Map answers to maps in traffic suite
Test map encoding in traffic suite
Let messages and grouped AVPs be decoded to lists
...
Diffstat (limited to 'lib/diameter/src')
-rw-r--r-- | lib/diameter/src/base/diameter.erl | 9 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_codec.erl | 15 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_config.erl | 9 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_gen.erl | 372 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_peer_fsm.erl | 9 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_service.erl | 1 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_traffic.erl | 82 | ||||
-rw-r--r-- | lib/diameter/src/base/diameter_watchdog.erl | 9 | ||||
-rw-r--r-- | lib/diameter/src/compiler/diameter_codegen.erl | 17 | ||||
-rw-r--r-- | lib/diameter/src/compiler/diameter_exprecs.erl | 4 |
10 files changed, 343 insertions, 184 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) -> diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl index f56e4a5249..4e6fe32d69 100644 --- a/lib/diameter/src/compiler/diameter_codegen.erl +++ b/lib/diameter/src/compiler/diameter_codegen.erl @@ -21,15 +21,14 @@ -module(diameter_codegen). %% -%% This module generates erl/hrl files for encode/decode modules -%% from the orddict parsed from a dictionary file (.dia) by -%% diameter_dict_util. The generated code is simple (one-liners), -%% the generated functions being called by code included iin the -%% generated modules from diameter_gen.hrl. The orddict itself is -%% returned by dict/0 in the generated module and diameter_dict_util -%% calls this function when importing dictionaries as a consequence -%% of @inherits sections. That is, @inherits introduces a dependency -%% on the beam file of another dictionary. +%% This module generates erl/hrl files for encode/decode modules from +%% the orddict parsed from a dictionary file by diameter_dict_util. +%% The generated code is simple (one-liners), and is called from +%% diameter_gen. The orddict itself is returned by dict/0 in the +%% generated module and diameter_dict_util calls this function when +%% importing dictionaries as a consequence of @inherits sections. That +%% is, @inherits introduces a dependency on the beam file of another +%% dictionary. %% -export([from_dict/4, diff --git a/lib/diameter/src/compiler/diameter_exprecs.erl b/lib/diameter/src/compiler/diameter_exprecs.erl index 9a0cb6baf2..143dede037 100644 --- a/lib/diameter/src/compiler/diameter_exprecs.erl +++ b/lib/diameter/src/compiler/diameter_exprecs.erl @@ -110,9 +110,9 @@ %% parse_transform/2 parse_transform(Forms, _Options) -> - Rs = [R || {attribute, _, record, R} <- Forms], - Es = lists:append([E || {attribute, _, export_records, E} <- Forms]), {H,T} = lists:splitwith(fun is_head/1, Forms), + Rs = [R || {attribute, _, record, R} <- H], + Es = lists:append([E || {attribute, _, export_records, E} <- H]), H ++ [a_export(Es) | f_accessors(Es, Rs)] ++ T. is_head(T) -> |