From 4f8f3e551f23cbdadcad5488c828f09bf832da33 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 15 Apr 2017 21:03:09 +0200 Subject: Fix comment typos --- lib/diameter/include/diameter_gen.hrl | 2 +- lib/diameter/src/base/diameter_peer_fsm.erl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index 611ad796a9..9a5ca8e09f 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -695,7 +695,7 @@ grouped_decode(_Name, {Error, Acc}) -> %% to the single offending AVP. This enables the recipient to detect %% the location of the offending AVP when embedded in a group. -%% An error in decoding a component AVP throws the first fauly +%% An error in decoding a component AVP throws the first faulty %% component, which the catch in d/3 wraps in the Grouped AVP in %% question. A partially decoded record is only used when ignoring %% errors in Failed-AVP. diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl index d2af8fe425..2383d830e6 100644 --- a/lib/diameter/src/base/diameter_peer_fsm.erl +++ b/lib/diameter/src/base/diameter_peer_fsm.erl @@ -865,7 +865,7 @@ outgoing(#diameter_packet{header = #diameter_header{application_id = 0, invalid(false, dpr_after_dpr, H) %% DPR sent: discard end; -%% Explict CER or DWR: discard. These are sent by us. +%% Explicit CER or DWR: discard. These are sent by us. outgoing(#diameter_packet{header = #diameter_header{application_id = 0, cmd_code = C, is_request = true} -- cgit v1.2.3 From dfeb8b9193ba716518cb632de8589aea378c178e Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Mon, 17 Apr 2017 11:30:05 +0200 Subject: Add {init,end}_per_suite/1 to codec suite Do nothing, but convenient for adding trace. --- lib/diameter/test/diameter_codec_SUITE.erl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/diameter/test/diameter_codec_SUITE.erl b/lib/diameter/test/diameter_codec_SUITE.erl index 558ba3b848..0666bb9d89 100644 --- a/lib/diameter/test/diameter_codec_SUITE.erl +++ b/lib/diameter/test/diameter_codec_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2015. All Rights Reserved. +%% Copyright Ericsson AB 2010-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -31,6 +31,8 @@ -export([suite/0, all/0, groups/0, + init_per_suite/1, + end_per_suite/1, init_per_group/2, end_per_group/2, init_per_testcase/2, @@ -63,6 +65,12 @@ groups() -> grouped_error, failed_error]}]. +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + init_per_group(recode, Config) -> ok = diameter:start(), Config. -- cgit v1.2.3 From 3da3055f5c807c3c11a349cba6c19a5abc6bc1c7 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Fri, 14 Apr 2017 15:50:15 +0200 Subject: Remove bloat from zero encode Don't call a function when we know the result, and consistently return a binary. --- lib/diameter/include/diameter_gen.hrl | 4 +-- lib/diameter/src/base/diameter_types.erl | 48 ++++++++++++++++---------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index 9a5ca8e09f..dd8c720e68 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2015. All Rights Reserved. +%% Copyright Ericsson AB 2010-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -719,7 +719,7 @@ z(Name, 1) -> z(_, {0,_}) -> []; z(Name, {Min, _}) -> - lists:duplicate(Min, z(Name)). + binary:copy(z(Name), Min). z('AVP') -> <<0:64/integer>>; %% minimal header diff --git a/lib/diameter/src/base/diameter_types.erl b/lib/diameter/src/base/diameter_types.erl index 6ecf385239..95361e8422 100644 --- a/lib/diameter/src/base/diameter_types.erl +++ b/lib/diameter/src/base/diameter_types.erl @@ -101,8 +101,8 @@ 'OctetString'(decode, B) -> ?INVALID_LENGTH(B); -'OctetString'(encode = M, zero) -> - 'OctetString'(M, []); +'OctetString'(encode, zero) -> + <<>>; 'OctetString'(encode, Str) -> iolist_to_binary(Str). @@ -115,8 +115,8 @@ 'Integer32'(decode, B) -> ?INVALID_LENGTH(B); -'Integer32'(encode = M, zero) -> - 'Integer32'(M, 0); +'Integer32'(encode, zero) -> + <<0:32/signed>>; 'Integer32'(encode, I) when ?SINT(32,I) -> @@ -130,8 +130,8 @@ 'Integer64'(decode, B) -> ?INVALID_LENGTH(B); -'Integer64'(encode = M, zero) -> - 'Integer64'(M, 0); +'Integer64'(encode, zero) -> + <<0:64/signed>>; 'Integer64'(encode, I) when ?SINT(64,I) -> @@ -145,8 +145,8 @@ 'Unsigned32'(decode, B) -> ?INVALID_LENGTH(B); -'Unsigned32'(encode = M, zero) -> - 'Unsigned32'(M, 0); +'Unsigned32'(encode, zero) -> + <<0:32>>; 'Unsigned32'(encode, I) when ?UINT(32,I) -> @@ -160,8 +160,8 @@ 'Unsigned64'(decode, B) -> ?INVALID_LENGTH(B); -'Unsigned64'(encode = M, zero) -> - 'Unsigned64'(M, 0); +'Unsigned64'(encode, zero) -> + <<0:64>>; 'Unsigned64'(encode, I) when ?UINT(64,I) -> @@ -193,8 +193,8 @@ 'Float32'(decode, B) -> ?INVALID_LENGTH(B); -'Float32'(encode = M, zero) -> - 'Float32'(M, 0.0); +'Float32'(encode, zero) -> + <<0.0:32/float>>; 'Float32'(encode, infinity) -> <<0:1, 255:8, 0:23>>; @@ -237,8 +237,8 @@ 'Float64'(encode, '-infinity') -> <<1:1, 2047:11, 0:52>>; -'Float64'(encode = M, zero) -> - 'Float64'(M, 0.0); +'Float64'(encode, zero) -> + <<0.0:64/float>>; 'Float64'(encode, X) when is_float(X) -> @@ -278,8 +278,8 @@ %% A DiameterIdentity is a FQDN as definined in RFC 1035, which is at %% least one character. -'DiameterIdentity'(encode = M, zero) -> - 'OctetString'(M, [0]); +'DiameterIdentity'(encode, zero) -> + <<0>>; 'DiameterIdentity'(encode = M, X) -> <<_,_/binary>> = 'OctetString'(M, X); @@ -300,8 +300,8 @@ ?INVALID_LENGTH(B); %% The minimal DiameterURI is "aaa://x", 7 characters. -'DiameterURI'(encode = M, zero) -> - 'OctetString'(M, lists:duplicate(0,7)); +'DiameterURI'(encode, zero) -> + <<0:7/unit:8>>; 'DiameterURI'(encode, #diameter_uri{type = Type, fqdn = DN, @@ -332,8 +332,8 @@ %% -------------------- %% This minimal rule is "deny in 0 from 0.0.0.0 to 0.0.0.0", 33 characters. -'IPFilterRule'(encode = M, zero) -> - 'OctetString'(M, lists:duplicate(0,33)); +'IPFilterRule'(encode, zero) -> + <<0:33/unit:8>>; 'IPFilterRule'(M, X) -> 'OctetString'(M, X). @@ -341,8 +341,8 @@ %% -------------------- %% This minimal rule is the same as for an IPFilterRule. -'QoSFilterRule'(encode = M, zero = X) -> - 'IPFilterRule'(M, X); +'QoSFilterRule'(encode, zero) -> + <<0:33/unit:8>>; 'QoSFilterRule'(M, X) -> 'OctetString'(M, X). @@ -362,8 +362,8 @@ 'UTF8String'(decode, B) -> ?INVALID_LENGTH(B); -'UTF8String'(encode = M, zero) -> - 'UTF8String'(M, []); +'UTF8String'(encode, zero) -> + <<>>; 'UTF8String'(encode, S) -> <<_/binary>> = unicode:characters_to_binary(S). %% assert binary return -- cgit v1.2.3 From bcef012fe4a4534a9098b15ce42830b9a7a38db9 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 15 Apr 2017 14:46:47 +0200 Subject: Make AVP splitting more efficient Profiling with fprof showed this prior to this commit: {[{{diameter_codec,decode,3}, 1000, 231.122, 4.092}, {{diameter_codec,collect_avps,1}, 1000, 0.000, 3.929}], { {diameter_codec,collect_avps,1}, 2000, 231.122, 8.021}, % [{{diameter_codec,collect_avps,3}, 1000, 222.932, 11.644}, {garbage_collect, 19, 0.169, 0.169}, {{diameter_codec,collect_avps,1}, 1000, 0.000, 3.929}]}. {[{{diameter_codec,collect_avps,1}, 1000, 222.932, 11.644}, {{diameter_codec,collect_avps,3}, 7000, 0.000, 68.186}], { {diameter_codec,collect_avps,3}, 8000, 222.932, 79.830}, % [{{diameter_codec,split_avp,1}, 7000, 120.886, 72.382}, {{erlang,setelement,3}, 7000, 21.830, 21.830}, {garbage_collect, 48, 0.386, 0.386}, {{diameter_codec,collect_avps,3}, 7000, 0.000, 68.186}]}. Note the time consumed in split_avp/1 and erlang:setelement/3. This commit does more matching in one go, without intermediate results, giving this: {[{{diameter_codec,decode,3}, 1000, 42.512, 3.701}, {{diameter_codec,collect_avps,1}, 1000, 0.000, 3.594}], { {diameter_codec,collect_avps,1}, 2000, 42.512, 7.295}, % [{{diameter_codec,collect_avps,3}, 1000, 35.217, 4.577}, {{diameter_codec,collect_avps,1}, 1000, 0.000, 3.594}]}. {[{{diameter_codec,collect_avps,1}, 1000, 35.217, 4.577}, {{diameter_codec,collect_avps,3}, 7000, 0.000, 27.754}], { {diameter_codec,collect_avps,3}, 8000, 35.217, 32.331}, % [{garbage_collect, 262, 2.647, 2.647}, {suspend, 9, 0.239, 0.000}, {{diameter_codec,collect_avps,3}, 7000, 0.000, 27.754}]}. --- lib/diameter/src/base/diameter_codec.erl | 113 ++++++++++++++----------------- 1 file changed, 50 insertions(+), 63 deletions(-) diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index 1ea5357924..3a2f1caf2b 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2015. All Rights Reserved. +%% Copyright Ericsson AB 2010-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -538,23 +538,14 @@ msg_id(<<_:32, Rbit:1, _:7, CmdCode:24, ApplId:32, _/binary>>) -> Error :: {5014, #diameter_avp{}}. collect_avps(#diameter_packet{bin = Bin}) -> - <<_:20/binary, Avps/binary>> = Bin, + <<_:20/binary, Avps/binary>> = Bin, %% assert collect_avps(Avps); collect_avps(Bin) when is_binary(Bin) -> collect_avps(Bin, 0, []). -collect_avps(<<>>, _, Acc) -> - Acc; -collect_avps(Bin, N, Acc) -> - try split_avp(Bin) of - {Rest, AVP} -> - collect_avps(Rest, N+1, [AVP#diameter_avp{index = N} | Acc]) - catch - ?FAILURE(Error) -> - {Error, Acc} - end. +%% collect_avps/3 %% 0 1 2 3 %% 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 @@ -568,32 +559,55 @@ collect_avps(Bin, N, Acc) -> %% | Data ... %% +-+-+-+-+-+-+-+-+ -%% split_avp/1 - -split_avp(Bin) -> - {Code, V, M, P, Len, HdrLen} = split_head(Bin), - - <<_:HdrLen/binary, Rest/binary>> = Bin, - {Data, B} = split_data(Rest, Len - HdrLen), - - {B, #diameter_avp{code = Code, - vendor_id = V, - is_mandatory = 1 == M, - need_encryption = 1 == P, - data = Data}}. - -%% split_head/1 - -split_head(<>) -> - {Code, V, M, P, Len, 12}; +collect_avps(<>, + N, + Acc) -> + DataLen = Len - 8 - V*4, %% Might be negative, which ensures + Pad = (4 - (Len rem 4)) rem 4, %% failure of the Data match below. + VendorId = if 1 == V -> I; 0 == V -> undefined end, + + %% Duplicate the diameter_avp creation in each branch below to + %% avoid modifying the record, which profiling has shown to be a + %% relatively costly part of building the list. + + case Rest of + <> -> + Avp = #diameter_avp{code = Code, + vendor_id = VendorId, + is_mandatory = 1 == M, + need_encryption = 1 == P, + data = Data, + index = N}, + collect_avps(T, N+1, [Avp | Acc]); + _ -> + %% Len points past the end of the message, or doesn't span + %% the header. As stated in the 6733 text above, it's + %% sufficient to return a zero-filled minimal payload if + %% this is a request. Do this (in cases that we know the + %% type) by inducing a decode failure and letting the + %% dictionary's decode (in diameter_gen) deal with it. + %% + %% Note that the extra bit can only occur in the trailing + %% AVP of a message or Grouped AVP, since a faulty AVP + %% Length is otherwise indistinguishable from a correct + %% one here, as we don't know the types of the AVPs being + %% extracted. + Avp = #diameter_avp{code = Code, + vendor_id = VendorId, + is_mandatory = 1 == M, + need_encryption = 1 == P, + data = <<0:1, Rest/binary>>, + index = N}, + [Avp | Acc] + end; -split_head(<>) -> - {Code, undefined, M, P, Len, 8}; +collect_avps(<<>>, _, Acc) -> + Acc; -%% Header is truncated. -split_head(Bin) -> - ?THROW({5014, #diameter_avp{data = Bin}}). -%% Note that pack_avp/1 will pad this at encode if sent in a Failed-AVP. +%% Header is truncated. pack_avp/1 will pad this at encode if sent in +%% a Failed-AVP. +collect_avps(Bin, _, Acc) -> + {{5014, #diameter_avp{data = Bin}}, Acc}. %% 3588: %% @@ -626,33 +640,6 @@ split_head(Bin) -> %% the minimum value mean we might not know the identity of the AVP and %% (2) the last sentence covers this case. -%% split_data/3 - -split_data(Bin, Len) -> - Pad = (4 - (Len rem 4)) rem 4, - - %% Len might be negative here, but that ensures the failure of the - %% binary match. - - case Bin of - <> -> - {Data, Rest}; - _ -> - %% Header length points past the end of the message, or - %% doesn't span the header. As stated in the 6733 text - %% above, it's sufficient to return a zero-filled minimal - %% payload if this is a request. Do this (in cases that we - %% know the type) by inducing a decode failure and letting - %% the dictionary's decode (in diameter_gen) deal with it. - %% - %% Note that the extra bit can only occur in the trailing - %% AVP of a message or Grouped AVP, since a faulty AVP - %% Length is otherwise indistinguishable from a correct - %% one here, since we don't know the types of the AVPs - %% being extracted. - {<<0:1, Bin/binary>>, <<>>} - end. - %%% --------------------------------------------------------------------------- %%% # pack_avp/1 %%% --------------------------------------------------------------------------- -- cgit v1.2.3 From 836b02d325082063fc5887091bfc04db6afab181 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 15 Apr 2017 22:26:57 +0200 Subject: Use maps when detecting missing AVPs Instead of the slower sets. Bump application dependencies to 17.5, even though earlier versions may do fine. --- lib/diameter/include/diameter_gen.hrl | 15 +++++++-------- lib/diameter/src/diameter.app.src | 10 +++++----- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index dd8c720e68..f7d432912d 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -209,15 +209,15 @@ newrec(Name) -> missing(Rec, Name, Failed) -> Avps = lists:foldl(fun({_, #diameter_avp{code = C, vendor_id = V}}, A) -> - sets:add_element({C,V}, A) + maps:put({C,V}, true, A) end, - sets:new(), + maps:new(), Failed), [{5005, A} || F <- '#info-'(element(1, Rec), fields), not has_arity(avp_arity(Name, F), '#get-'(F, Rec)), - #diameter_avp{code = C, vendor_id = V} - = A <- [empty_avp(F)], - not sets:is_element({C,V}, Avps)]. + {C,_,V} = H <- [avp_header(F)], + not maps:is_key({C,V}, Avps), + A <- [empty_avp(F,H)]]. %% Maximum arities have already been checked in building the record. @@ -235,10 +235,9 @@ has_prefix(_, []) -> has_prefix(N, L) -> has_prefix(N-1, tl(L)). -%% empty_avp/1 +%% empty_avp/2 -empty_avp(Name) -> - {Code, Flags, VId} = avp_header(Name), +empty_avp(Name, {Code, Flags, VId}) -> {Name, Type} = avp_name(Code, VId), #diameter_avp{name = Name, code = Code, diff --git a/lib/diameter/src/diameter.app.src b/lib/diameter/src/diameter.app.src index d380ebbd92..9a6e47006b 100644 --- a/lib/diameter/src/diameter.app.src +++ b/lib/diameter/src/diameter.app.src @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2016. All Rights Reserved. +%% Copyright Ericsson AB 2010-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -28,10 +28,10 @@ ]}, {registered, [%REGISTERED%]}, {applications, [ - {stdlib, "2.0"}, {kernel, "3.0"}%, {erts, "6.0"} - %% {syntax-tools, "1.6.14"} - %% {runtime-tools, "1.8.14"} - %, {ssl, "5.3.4"} + {stdlib, "2.4"}, {kernel, "3.2"}%, {erts, "6.4"} + %% {syntax-tools, "1.6,18"} + %% {runtime-tools, "1.8.16"} + %, {ssl, "6.0"} ]}, {env, []}, {mod, {diameter_app, []}}, -- cgit v1.2.3 From 501cd3fae71dcbcf56da55748abd82b512127220 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sun, 16 Apr 2017 01:14:14 +0200 Subject: Simplify missing AVP detection By using the existing '#get-'/1 in generated dictionary modules to retrieve fields and values at the same time. Before: {[{{diameter_gen_base_rfc6733,missing,3}, 1000, 211.722, 8.741}, {{diameter_gen_base_rfc6733,'-missing/3-lc$^0/1-0-',4},12000, 0.000, 95.764}], { {diameter_gen_base_rfc6733,'-missing/3-lc$^0/1-0-',4},13000, 211.722, 104.505}, % [{{diameter_gen_base_rfc6733,'#get-',2}, 12000, 49.917, 28.221}, {{diameter_gen_base_rfc6733,has_arity,2}, 12000, 31.811, 23.442}, {{diameter_gen_base_rfc6733,avp_arity,2}, 12000, 21.076, 20.975}, {garbage_collect, 457, 3.918, 3.918}, {suspend, 31, 0.495, 0.000}, {{diameter_gen_base_rfc6733,'-missing/3-lc$^0/1-0-',4},12000, 0.000, 95.764}]}. After: {[{{diameter_gen_base_rfc6733,missing,3}, 1000, 134.098, 2.402}, {{diameter_gen_base_rfc6733,'-missing/3-lc$^0/1-0-',3},13000, 0.000, 77.327}], { {diameter_gen_base_rfc6733,'-missing/3-lc$^0/1-0-',3},14000, 134.098, 79.729}, % [{{diameter_gen_base_rfc6733,has_arity,2}, 12000, 31.084, 22.913}, {{diameter_gen_base_rfc6733,avp_arity,2}, 12000, 20.526, 20.440}, {garbage_collect, 253, 2.504, 2.504}, {suspend, 17, 0.255, 0.000}, {{diameter_gen_base_rfc6733,'-missing/3-lc$^0/1-0-',3},13000, 0.000, 77.327}]}. --- lib/diameter/include/diameter_gen.hrl | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index f7d432912d..09af829259 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -213,11 +213,10 @@ missing(Rec, Name, Failed) -> end, maps:new(), Failed), - [{5005, A} || F <- '#info-'(element(1, Rec), fields), - not has_arity(avp_arity(Name, F), '#get-'(F, Rec)), - {C,_,V} = H <- [avp_header(F)], - not maps:is_key({C,V}, Avps), - A <- [empty_avp(F,H)]]. + [{5005, empty_avp(F,H)} || {F,T} <- '#get-'(Rec), + not has_arity(avp_arity(Name, F), T), + {C,_,V} = H <- [avp_header(F)], + not maps:is_key({C,V}, Avps)]. %% Maximum arities have already been checked in building the record. @@ -232,8 +231,8 @@ has_prefix(0, _) -> true; has_prefix(_, []) -> false; -has_prefix(N, L) -> - has_prefix(N-1, tl(L)). +has_prefix(N, [_|L]) -> + has_prefix(N-1, L). %% empty_avp/2 -- cgit v1.2.3 From 0911c97768caeb908c3f18fd1ba514c1c7f273d5 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sun, 16 Apr 2017 02:31:35 +0200 Subject: Decode message header in a single match --- lib/diameter/src/base/diameter_codec.erl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index 3a2f1caf2b..73da9398f3 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -399,14 +399,12 @@ decode_avps(MsgName, Mod, Pkt, Avps) -> %% ... or not decode_header(<>) -> - <> - = CmdFlags, %% 3588 (ch 3) says that reserved bits MUST be set to 0 and ignored %% by the receiver. -- cgit v1.2.3 From f2e75976772b6a1d52a2c71ec1eecb4b338ed632 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sun, 16 Apr 2017 09:51:34 +0200 Subject: Replace slow binary comprehensions Which appears to be about an order of magnitude slower than just creating a binary of the desired size. --- lib/diameter/include/diameter_gen.hrl | 3 ++- lib/diameter/test/diameter_codec_test.erl | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index 09af829259..f8d6cbde89 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -723,7 +723,8 @@ z('AVP') -> <<0:64/integer>>; %% minimal header z(Name) -> Bin = diameter_codec:pack_avp(avp_header(Name), empty_value(Name)), - << <<0>> || <<_>> <= Bin >>. + Sz = size(Bin), + <<0:Sz/unit:8>>. %% --------------------------------------------------------------------------- %% # empty/1 diff --git a/lib/diameter/test/diameter_codec_test.erl b/lib/diameter/test/diameter_codec_test.erl index 869797f11f..d8b7377c0c 100644 --- a/lib/diameter/test/diameter_codec_test.erl +++ b/lib/diameter/test/diameter_codec_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2016. All Rights Reserved. +%% Copyright Ericsson AB 2010-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -280,7 +280,8 @@ d(F, Eq, V) -> end. z(B) -> - << <<0>> || <<_>> <= B >>. + Sz = size(B), + <<0:Sz/unit:8>>. %% values/1 %% -- cgit v1.2.3 From 398a52b28ab64d1737b15b91b6be0a6e1426303b Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sun, 16 Apr 2017 11:16:47 +0200 Subject: Make encoding of diameter_avp records more efficient Prepend the header in a single step. Before: {[{{diameter_codec,pack_avp,1}, 7000, 126.074, 51.058}], { {diameter_codec,pack_avp,2}, 7000, 126.074, 51.058}, % [{{diameter_codec,pack_avp,5}, 7000, 51.144, 25.758}, {{diameter_codec,pad,2}, 7000, 23.844, 23.570}, {suspend, 1, 0.028, 0.000}]}. After: {[{{diameter_codec,pack_avp,1}, 7000, 78.563, 26.986}], { {diameter_codec,pack_avp,2}, 7000, 78.563, 26.986}, % [{{diameter_codec,pack_avp,6}, 7000, 51.459, 26.381}, {suspend, 4, 0.118, 0.000}]}. --- lib/diameter/src/base/diameter_codec.erl | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index 73da9398f3..656c86df5f 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -40,6 +40,7 @@ -include("diameter_internal.hrl"). -define(MASK(N,I), ((I) band (1 bsl (N)))). +-define(PAD(Len), ((4 - (Len rem 4)) rem 4)). -type u32() :: 0..16#FFFFFFFF. -type u24() :: 0..16#FFFFFF. @@ -561,7 +562,7 @@ collect_avps(<>, N, Acc) -> DataLen = Len - 8 - V*4, %% Might be negative, which ensures - Pad = (4 - (Len rem 4)) rem 4, %% failure of the Data match below. + Pad = ?PAD(Len), %% failure of the Data match below. VendorId = if 1 == V -> I; 0 == V -> undefined end, %% Duplicate the diameter_avp creation in each branch below to @@ -723,30 +724,22 @@ flag_avp({false, _}, F) -> pack_avp({Code, Flags, VendorId}, Bin) when is_binary(Bin) -> Sz = size(Bin), - pack_avp(Code, Flags, VendorId, Sz, pad(Sz rem 4, Bin)). + pack_avp(Code, Flags, Sz, VendorId, Bin, ?PAD(Sz)). +%% Padding is not included in the length field, as mandated by the RFC. -pad(0, Bin) -> - Bin; -pad(N, Bin) -> - P = 8*(4-N), - <>. -%% Note that padding is not included in the length field as mandated by -%% the RFC. - -%% pack_avp/5 +%% pack_avp/6 %% %% Prepend the vendor id as required. -pack_avp(Code, Flags, Vid, Sz, Bin) +pack_avp(Code, Flags, Sz, Vid, Bin, Pad) when 0 == Flags band 2#10000000 -> undefined = Vid, %% sanity check - pack_avp(Code, Flags, Sz, Bin); + pack_avp(Code, Flags, Sz, 0, 0, Bin, Pad); -pack_avp(Code, Flags, Vid, Sz, Bin) -> - pack_avp(Code, Flags, Sz+4, <>). +pack_avp(Code, Flags, Sz, Vid, Bin, Pad) -> + pack_avp(Code, Flags, Sz+4, Vid, 1, Bin, Pad). -%% pack_avp/4 +%% pack_avp/7 -pack_avp(Code, Flags, Sz, Bin) -> - Length = Sz + 8, - <>. +pack_avp(Code, Flags, Sz, VId, V, Bin, Pad) -> + <>. -- cgit v1.2.3 From dff7f1ce02cf1de8233e4e60cc962e346eaee8a8 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sun, 16 Apr 2017 15:02:21 +0200 Subject: Make encoding of Diameter header more efficient On the same theme as the parent commit, building binaries in fewer steps. --- lib/diameter/src/base/diameter_codec.erl | 79 ++++++++++++++++---------------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index 656c86df5f..8f313e8e39 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -39,8 +39,10 @@ -include_lib("diameter/include/diameter.hrl"). -include("diameter_internal.hrl"). --define(MASK(N,I), ((I) band (1 bsl (N)))). -define(PAD(Len), ((4 - (Len rem 4)) rem 4)). +-define(BIT(B), (if B -> 1; true -> 0 end)). +-define(FLAGS(R,P,E,T), ?BIT(R):1, ?BIT(P):1, ?BIT(E):1, ?BIT(T):1, 0:4). +-define(FLAG(B,D), (if is_boolean(B) -> B; true -> 0 /= (D) end)). -type u32() :: 0..16#FFFFFFFF. -type u24() :: 0..16#FFFFFF. @@ -142,20 +144,22 @@ encode(Mod, Msg) -> e(_, #diameter_packet{msg = [#diameter_header{} = Hdr | As]} = Pkt) -> try encode_avps(reorder(As)) of Avps -> - Length = size(Avps) + 20, + Len = 20 + size(Avps), #diameter_header{version = Vsn, + is_request = R, + is_proxiable = P, + is_error = E, + is_retransmitted = T, cmd_code = Code, application_id = Aid, hop_by_hop_id = Hid, end_to_end_id = Eid} = Hdr, - Flags = make_flags(0, Hdr), - Pkt#diameter_packet{header = Hdr, - bin = < end; e(Mod, #diameter_packet{header = Hdr0, msg = Msg} = Pkt) -> + MsgName = rec2msg(Mod, Msg), + {Code, Flags, Aid} = msg_header(Mod, MsgName, Hdr0), + #diameter_header{version = Vsn, + is_request = R, + is_proxiable = P, + is_error = E, + is_retransmitted = T, hop_by_hop_id = Hid, end_to_end_id = Eid} = Hdr0, - MsgName = rec2msg(Mod, Msg), - {Code, Flags0, Aid} = msg_header(Mod, MsgName, Hdr0), - Flags = make_flags(Flags0, Hdr0), - Hdr = Hdr0#diameter_header{cmd_code = Code, - application_id = Aid, - is_request = 0 /= ?MASK(7, Flags), - is_proxiable = 0 /= ?MASK(6, Flags), - is_error = 0 /= ?MASK(5, Flags), - is_retransmitted = 0 /= ?MASK(4, Flags)}, + RB = ?FLAG(R, Flags band 2#10000000), + PB = ?FLAG(P, Flags band 2#01000000), + EB = ?FLAG(E, Flags band 2#00100000), + TB = ?FLAG(T, Flags band 2#00010000), + Values = values(Msg), try encode_avps(Mod, MsgName, Values) of Avps -> - Length = size(Avps) + 20, - Pkt#diameter_packet{header = Hdr#diameter_header{length = Length}, - bin = <>} catch error: Reason -> + Hdr = Hdr0#diameter_header{cmd_code = Code, + application_id = Aid, + is_request = RB, + is_proxiable = PB, + is_error = EB, + is_retransmitted = TB}, exit({Reason, diameter_lib:get_stacktrace(), Hdr}) end. -%% make_flags/2 - -make_flags(Flags0, #diameter_header{is_request = R, - is_proxiable = P, - is_error = E, - is_retransmitted = T}) -> - {Flags, 3} = lists:foldl(fun(B,{F,N}) -> {mf(B,F,N), N-1} end, - {Flags0, 7}, - [R,P,E,T]), - Flags. - -mf(undefined, F, _) -> - F; -mf(B, F, N) -> %% reset the affected bit - (F bxor (F band (1 bsl N))) bor bit(B, N). - -bit(true, N) -> 1 bsl N; -bit(false, _) -> 0. - %% values/1 values([H|T]) @@ -517,7 +518,7 @@ msg_id(#diameter_packet{header = #diameter_header{} = Hdr}) -> msg_id(#diameter_header{application_id = A, cmd_code = C, is_request = R}) -> - {A, C, if R -> 1; true -> 0 end}; + {A, C, ?BIT(R)}; msg_id(<<_:32, Rbit:1, _:7, CmdCode:24, ApplId:32, _/binary>>) -> {ApplId, CmdCode, Rbit}. -- cgit v1.2.3 From b51872b742b18bcb0c53aaeb09713a46ec91f788 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sun, 16 Apr 2017 18:47:05 +0200 Subject: Remove minor encode bloat As when detecting missing AVPs, extract a list of field/value pairs in one step, which looks to be slightly more efficient. Flattening the list was unnecessary since the result is passed to list_to_binary. --- lib/diameter/include/diameter_gen.hrl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index f8d6cbde89..e745e3d2d3 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -104,8 +104,7 @@ encode_avps(Name, Rec) -> %% encode/2 encode(Name, Rec) -> - lists:flatmap(fun(A) -> encode(Name, A, '#get-'(A, Rec)) end, - '#info-'(element(1, Rec), fields)). + [encode(Name, F, V) || {F,V} <- '#get-'(Rec)]. %% encode/3 -- cgit v1.2.3 From 8c975c3b325c6fa4926c1d12fba0b48e281a08cd Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Mon, 17 Apr 2017 09:01:25 +0200 Subject: Fix encode of Grouped AVP as {Dict, Name, Data} This is a special case to allow encode of something other than an iolist. Eg. #diameter_avp{data = {diameter_gen_base_rfc6733, 'Proxy-Info', [{'Proxy-Host', "HOST"}, {'Proxy-State', "STATE"}]}} Only worked as expected for AVPs of type other than Grouped. --- lib/diameter/src/base/diameter_codec.erl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index 8f313e8e39..f7ba9bd33f 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -675,10 +675,8 @@ pack_avp(#diameter_avp{data = {{_,_,_} = T, Bin}}) when is_binary(Bin) -> pack_avp(T, Bin); -pack_avp(#diameter_avp{data = {Dict, Name, Value}} = A) -> - {Code, _Flags, Vid} = Hdr = Dict:avp_header(Name), - {Name, Type} = Dict:avp_name(Code, Vid), - pack_avp(A#diameter_avp{data = {Hdr, {Type, Value}}}); +pack_avp(#diameter_avp{data = {Dict, Name, Data}}) -> + pack_avp(Dict:avp_header(Name), Dict:avp(encode, Data, Name)); %% ... with a truncated header ... pack_avp(#diameter_avp{code = undefined, data = B}) -- cgit v1.2.3 From f1a78f768414e97d30e72b1530475f2893fc75c5 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Mon, 17 Apr 2017 10:51:56 +0200 Subject: Don't create intermediate binaries unnecessarily during encode Dict:avp(encode, Value, Name) no longer needs to return a binary, only an iolist(). Message encode runs list_to_binary/1 to convert accumulated lists into a message binary. --- lib/diameter/include/diameter_gen.hrl | 10 ++++---- lib/diameter/src/base/diameter_codec.erl | 42 ++++++++++++++++--------------- lib/diameter/test/diameter_codec_test.erl | 6 +++-- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index e745e3d2d3..e35d448754 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -75,7 +75,7 @@ eraser(K) -> %% --------------------------------------------------------------------------- -spec encode_avps(parent_name(), parent_record() | avp_values()) - -> binary() + -> iolist() | no_return(). encode_avps(Name, Vals) @@ -84,7 +84,7 @@ encode_avps(Name, Vals) encode_avps(Name, Rec) -> try - list_to_binary(encode(Name, Rec)) + encode(Name, Rec) catch throw: {?MODULE, Reason} -> diameter_lib:log({encode, error}, @@ -655,7 +655,7 @@ value(_, Avp) -> -spec grouped_avp(decode, avp_name(), bitstring()) -> {avp_record(), [avp()]}; (encode, avp_name(), avp_record() | avp_values()) - -> binary() + -> iolist() | no_return(). %% Length error induced by diameter_codec:collect_avps/1: the AVP @@ -719,10 +719,10 @@ z(Name, {Min, _}) -> binary:copy(z(Name), Min). z('AVP') -> - <<0:64/integer>>; %% minimal header + <<0:64>>; %% minimal header z(Name) -> Bin = diameter_codec:pack_avp(avp_header(Name), empty_value(Name)), - Sz = size(Bin), + Sz = iolist_size(Bin), <<0:Sz/unit:8>>. %% --------------------------------------------------------------------------- diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index f7ba9bd33f..0d107bf8da 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -144,7 +144,8 @@ encode(Mod, Msg) -> e(_, #diameter_packet{msg = [#diameter_header{} = Hdr | As]} = Pkt) -> try encode_avps(reorder(As)) of Avps -> - Len = 20 + size(Avps), + Bin = list_to_binary(Avps), + Len = 20 + size(Bin), #diameter_header{version = Vsn, is_request = R, @@ -163,7 +164,7 @@ e(_, #diameter_packet{msg = [#diameter_header{} = Hdr | As]} = Pkt) -> Aid:32, Hid:32, Eid:32, - Avps/binary>>} + Bin/binary>>} catch error: Reason -> exit({Reason, diameter_lib:get_stacktrace(), Hdr}) @@ -191,7 +192,9 @@ e(Mod, #diameter_packet{header = Hdr0, msg = Msg} = Pkt) -> try encode_avps(Mod, MsgName, Values) of Avps -> - Len = size(Avps) + 20, + Bin = list_to_binary(Avps), + Len = 20 + size(Bin), + Hdr = Hdr0#diameter_header{length = Len, cmd_code = Code, application_id = Aid, @@ -199,13 +202,14 @@ e(Mod, #diameter_packet{header = Hdr0, msg = Msg} = Pkt) -> is_proxiable = PB, is_error = EB, is_retransmitted = TB}, + Pkt#diameter_packet{header = Hdr, bin = <>} + Bin/binary>>} catch error: Reason -> Hdr = Hdr0#diameter_header{cmd_code = Code, @@ -282,7 +286,7 @@ reorder([], _) -> %% encode_avps/1 encode_avps(Avps) -> - list_to_binary(lists:map(fun pack_avp/1, Avps)). + lists:map(fun pack_avp/1, Avps). %% msg_header/3 @@ -669,11 +673,10 @@ pack_avp(#diameter_avp{data = {Type, Value}} = A) %% ... with a header in various forms ... pack_avp(#diameter_avp{data = {{_,_,_} = T, {Type, Value}}}) -> - pack_avp(T, iolist_to_binary(diameter_types:Type(encode, Value))); + pack_avp(T, diameter_types:Type(encode, Value)); -pack_avp(#diameter_avp{data = {{_,_,_} = T, Bin}}) - when is_binary(Bin) -> - pack_avp(T, Bin); +pack_avp(#diameter_avp{data = {{_,_,_} = T, Data}}) -> + pack_avp(T, Data); pack_avp(#diameter_avp{data = {Dict, Name, Data}}) -> pack_avp(Dict:avp_header(Name), Dict:avp(encode, Data, Name)); @@ -704,7 +707,7 @@ pack_avp(#diameter_avp{code = Code, Flags = lists:foldl(fun flag_avp/2, 0, [{V /= undefined, 2#10000000}, {M, 2#01000000}, {P, 2#00100000}]), - pack_avp({Code, Flags, V}, iolist_to_binary(Data)). + pack_avp({Code, Flags, V}, Data). header_length(<<_:32, 1:1, _/bitstring>>) -> 12; @@ -720,25 +723,24 @@ flag_avp({false, _}, F) -> %%% # pack_avp/2 %%% --------------------------------------------------------------------------- -pack_avp({Code, Flags, VendorId}, Bin) - when is_binary(Bin) -> - Sz = size(Bin), - pack_avp(Code, Flags, Sz, VendorId, Bin, ?PAD(Sz)). +pack_avp({Code, Flags, VendorId}, Data) -> + Sz = iolist_size(Data), + pack_avp(Code, Flags, Sz, VendorId, Data, ?PAD(Sz)). %% Padding is not included in the length field, as mandated by the RFC. %% pack_avp/6 %% %% Prepend the vendor id as required. -pack_avp(Code, Flags, Sz, Vid, Bin, Pad) +pack_avp(Code, Flags, Sz, Vid, Data, Pad) when 0 == Flags band 2#10000000 -> undefined = Vid, %% sanity check - pack_avp(Code, Flags, Sz, 0, 0, Bin, Pad); + pack_avp(Code, Flags, Sz, 0, 0, Data, Pad); -pack_avp(Code, Flags, Sz, Vid, Bin, Pad) -> - pack_avp(Code, Flags, Sz+4, Vid, 1, Bin, Pad). +pack_avp(Code, Flags, Sz, Vid, Data, Pad) -> + pack_avp(Code, Flags, Sz+4, Vid, 1, Data, Pad). %% pack_avp/7 -pack_avp(Code, Flags, Sz, VId, V, Bin, Pad) -> - <>. +pack_avp(Code, Flags, Sz, VId, V, Data, Pad) -> + [<>, Data, <<0:Pad/unit:8>>]. diff --git a/lib/diameter/test/diameter_codec_test.erl b/lib/diameter/test/diameter_codec_test.erl index d8b7377c0c..e041500b37 100644 --- a/lib/diameter/test/diameter_codec_test.erl +++ b/lib/diameter/test/diameter_codec_test.erl @@ -209,8 +209,10 @@ avp_decode(Mod, Name, Type, Eq, Value) -> avp(Mod, decode = X, V, Name, 'Grouped') -> {Rec, _} = Mod:avp(X, V, Name), Rec; -avp(Mod, X, V, Name, _) -> - Mod:avp(X, V, Name). +avp(Mod, decode = X, V, Name, _) -> + Mod:avp(X, V, Name); +avp(Mod, encode = X, V, Name, _) -> + iolist_to_binary(Mod:avp(X, V, Name)). %% v/1 -- cgit v1.2.3 From a75a449b831b857dc3d2fc6f4ba872a3b6563ee5 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Mon, 17 Apr 2017 13:21:32 +0200 Subject: Don't create intermediate terms unnecessarily during encode --- lib/diameter/src/base/diameter_codec.erl | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index 0d107bf8da..9a7a7ad945 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -40,7 +40,8 @@ -include("diameter_internal.hrl"). -define(PAD(Len), ((4 - (Len rem 4)) rem 4)). --define(BIT(B), (if B -> 1; true -> 0 end)). +-define(BIT(B,I), (if B -> I; true -> 0 end)). +-define(BIT(B), ?BIT(B,1)). -define(FLAGS(R,P,E,T), ?BIT(R):1, ?BIT(P):1, ?BIT(E):1, ?BIT(T):1, 0:4). -define(FLAG(B,D), (if is_boolean(B) -> B; true -> 0 /= (D) end)). @@ -704,26 +705,27 @@ pack_avp(#diameter_avp{code = Code, is_mandatory = M, need_encryption = P, data = Data}) -> - Flags = lists:foldl(fun flag_avp/2, 0, [{V /= undefined, 2#10000000}, - {M, 2#01000000}, - {P, 2#00100000}]), - pack_avp({Code, Flags, V}, Data). + Flags = ?BIT(V /= undefined, 2#10000000) + bor ?BIT(M, 2#01000000) + bor ?BIT(P, 2#00100000), + + pack_avp(Code, Flags, V, Data). header_length(<<_:32, 1:1, _/bitstring>>) -> 12; header_length(_) -> 8. -flag_avp({true, B}, F) -> - F bor B; -flag_avp({false, _}, F) -> - F. - %%% --------------------------------------------------------------------------- %%% # pack_avp/2 %%% --------------------------------------------------------------------------- pack_avp({Code, Flags, VendorId}, Data) -> + pack_avp(Code, Flags, VendorId, Data). + +%% pack_avp/4 + +pack_avp(Code, Flags, VendorId, Data) -> Sz = iolist_size(Data), pack_avp(Code, Flags, Sz, VendorId, Data, ?PAD(Sz)). %% Padding is not included in the length field, as mandated by the RFC. -- cgit v1.2.3 From 9f2e4404374d7352a6296e287b6066ff86a604f9 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Mon, 17 Apr 2017 16:54:54 +0200 Subject: Avoid modifying records during encode --- lib/diameter/src/base/diameter_codec.erl | 33 +++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index 9a7a7ad945..54f5fd2e25 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -665,18 +665,18 @@ pack_avp([#diameter_avp{} = Grouped | _Components]) -> %% and the list is flat, nesting being accomplished in the data %% fields. pack_avp(#diameter_avp{data = [#diameter_avp{} | _] = Components} = Grouped) -> - pack_avp(Grouped#diameter_avp{data = encode_avps(Components)}); + pack_data(encode_avps(Components), Grouped); %% Data as a type/value tuple ... pack_avp(#diameter_avp{data = {Type, Value}} = A) when is_atom(Type) -> - pack_avp(A#diameter_avp{data = diameter_types:Type(encode, Value)}); + pack_data(diameter_types:Type(encode, Value), A); %% ... with a header in various forms ... -pack_avp(#diameter_avp{data = {{_,_,_} = T, {Type, Value}}}) -> +pack_avp(#diameter_avp{data = {T, {Type, Value}}}) -> pack_avp(T, diameter_types:Type(encode, Value)); -pack_avp(#diameter_avp{data = {{_,_,_} = T, Data}}) -> +pack_avp(#diameter_avp{data = {T, Data}}) -> pack_avp(T, Data); pack_avp(#diameter_avp{data = {Dict, Name, Data}}) -> @@ -697,25 +697,28 @@ pack_avp(#diameter_avp{code = undefined, data = B}) %% ... when ignoring errors in Failed-AVP ... %% ... during a relay encode ... pack_avp(#diameter_avp{data = <<0:1, B/binary>>} = A) -> - pack_avp(A#diameter_avp{data = B}); + pack_data(B, A); %% ... or as an iolist. -pack_avp(#diameter_avp{code = Code, - vendor_id = V, - is_mandatory = M, - need_encryption = P, - data = Data}) -> - Flags = ?BIT(V /= undefined, 2#10000000) - bor ?BIT(M, 2#01000000) - bor ?BIT(P, 2#00100000), - - pack_avp(Code, Flags, V, Data). +pack_avp(#diameter_avp{data = Data} = A) -> + pack_data(Data, A). header_length(<<_:32, 1:1, _/bitstring>>) -> 12; header_length(_) -> 8. +%% pack_data/2 + +pack_data(Data, #diameter_avp{code = Code, + vendor_id = V, + is_mandatory = M, + need_encryption = P}) -> + Flags = ?BIT(V /= undefined, 2#10000000) + bor ?BIT(M, 2#01000000) + bor ?BIT(P, 2#00100000), + pack_avp(Code, Flags, V, Data). + %%% --------------------------------------------------------------------------- %%% # pack_avp/2 %%% --------------------------------------------------------------------------- -- cgit v1.2.3 From 7dc93e6ed372c37d45be2db156e8a8a6b88fd4ec Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Mon, 17 Apr 2017 18:18:21 +0200 Subject: Don't match for no useful reason --- lib/diameter/src/base/diameter_codec.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index 54f5fd2e25..8efc0801af 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -737,9 +737,8 @@ pack_avp(Code, Flags, VendorId, Data) -> %% %% Prepend the vendor id as required. -pack_avp(Code, Flags, Sz, Vid, Data, Pad) +pack_avp(Code, Flags, Sz, _Vid, Data, Pad) when 0 == Flags band 2#10000000 -> - undefined = Vid, %% sanity check pack_avp(Code, Flags, Sz, 0, 0, Data, Pad); pack_avp(Code, Flags, Sz, Vid, Data, Pad) -> -- cgit v1.2.3 From c5cdd4e3b4f0bad45e0c1f223835625afa873179 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Mon, 17 Apr 2017 19:04:47 +0200 Subject: Optimize sub binary creation base/diameter_codec.erl:600: Warning: OPTIMIZED: creation of sub binary delayed --- lib/diameter/src/base/diameter_codec.erl | 60 +++++++++++++++++++------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index 8efc0801af..9acf847aba 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -567,30 +567,47 @@ collect_avps(Bin) collect_avps(<>, N, Acc) -> - DataLen = Len - 8 - V*4, %% Might be negative, which ensures - Pad = ?PAD(Len), %% failure of the Data match below. - VendorId = if 1 == V -> I; 0 == V -> undefined end, + collect_avps(Code, + if 1 == V -> I; 0 == V -> undefined end, + 1 == M, + 1 == P, + Len - 8 - V*4, %% Might be negative, which ensures + ?PAD(Len), %% failure of the Data match below. + Rest, + N, + Acc); - %% Duplicate the diameter_avp creation in each branch below to - %% avoid modifying the record, which profiling has shown to be a - %% relatively costly part of building the list. +collect_avps(<<>>, _, Acc) -> + Acc; + +%% Header is truncated. pack_avp/1 will pad this at encode if sent in +%% a Failed-AVP. +collect_avps(Bin, _, Acc) -> + {{5014, #diameter_avp{data = Bin}}, Acc}. + +%% collect_avps/9 + +%% Duplicate the diameter_avp creation in each branch below to avoid +%% modifying the record, which profiling has shown to be a relatively +%% costly part of building the list. +collect_avps(Code, VendorId, M, P, Len, Pad, Rest, N, Acc) -> case Rest of - <> -> + <> -> Avp = #diameter_avp{code = Code, vendor_id = VendorId, - is_mandatory = 1 == M, - need_encryption = 1 == P, + is_mandatory = M, + need_encryption = P, data = Data, index = N}, collect_avps(T, N+1, [Avp | Acc]); _ -> - %% Len points past the end of the message, or doesn't span - %% the header. As stated in the 6733 text above, it's - %% sufficient to return a zero-filled minimal payload if - %% this is a request. Do this (in cases that we know the - %% type) by inducing a decode failure and letting the - %% dictionary's decode (in diameter_gen) deal with it. + %% Length in header points past the end of the message, or + %% doesn't span the header. As stated in the 6733 text + %% above, it's sufficient to return a zero-filled minimal + %% payload if this is a request. Do this (in cases that we + %% know the type) by inducing a decode failure and letting + %% the dictionary's decode (in diameter_gen) deal with it. %% %% Note that the extra bit can only occur in the trailing %% AVP of a message or Grouped AVP, since a faulty AVP @@ -599,20 +616,13 @@ collect_avps(<>, %% extracted. Avp = #diameter_avp{code = Code, vendor_id = VendorId, - is_mandatory = 1 == M, - need_encryption = 1 == P, + is_mandatory = M, + need_encryption = P, data = <<0:1, Rest/binary>>, index = N}, [Avp | Acc] - end; - -collect_avps(<<>>, _, Acc) -> - Acc; + end. -%% Header is truncated. pack_avp/1 will pad this at encode if sent in -%% a Failed-AVP. -collect_avps(Bin, _, Acc) -> - {{5014, #diameter_avp{data = Bin}}, Acc}. %% 3588: %% -- cgit v1.2.3 From c20f643e9539082f969a12f0a5513e543c5dad3e Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Mon, 17 Apr 2017 22:27:25 +0200 Subject: Optimize sub binary creation base/diameter_codec.erl:545: Warning: OPTIMIZED: creation of sub binary delayed --- lib/diameter/src/base/diameter_codec.erl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index 9acf847aba..ac74a1b9c1 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -542,9 +542,8 @@ msg_id(<<_:32, Rbit:1, _:7, CmdCode:24, ApplId:32, _/binary>>) -> when Avp :: #diameter_avp{}, Error :: {5014, #diameter_avp{}}. -collect_avps(#diameter_packet{bin = Bin}) -> - <<_:20/binary, Avps/binary>> = Bin, %% assert - collect_avps(Avps); +collect_avps(#diameter_packet{bin = <<_:20/binary, Avps/binary>>}) -> + collect_avps(Avps, 0, []); collect_avps(Bin) when is_binary(Bin) -> -- cgit v1.2.3 From 0dd4fe49665a39a407764823ea3fabbc7bd8935b Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Tue, 18 Apr 2017 10:46:39 +0200 Subject: Optimize sub binary creation base/diameter_codec.erl:716: Warning: OPTIMIZED: creation of sub binary delayed --- lib/diameter/src/base/diameter_codec.erl | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index ac74a1b9c1..90683e7329 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -703,20 +703,23 @@ pack_avp(#diameter_avp{code = undefined, data = B}) Len = size(<> = <>), <>; -%% ... when ignoring errors in Failed-AVP ... -%% ... during a relay encode ... -pack_avp(#diameter_avp{data = <<0:1, B/binary>>} = A) -> - pack_data(B, A); - -%% ... or as an iolist. pack_avp(#diameter_avp{data = Data} = A) -> - pack_data(Data, A). + pack_bits(Data, A). header_length(<<_:32, 1:1, _/bitstring>>) -> 12; header_length(_) -> 8. +%% pack_bits/2 + +%% Ignoring errors in Failed-AVP or during a relay encode. +pack_bits(<<0:1, B/binary>>, Avp) -> + pack_bits(B, Avp); + +pack_bits(Data, Avp) -> + pack_data(Data, Avp). + %% pack_data/2 pack_data(Data, #diameter_avp{code = Code, -- cgit v1.2.3 From d5d3d5fa029cd04261921427bb0f64ca0efc7b8c Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Mon, 17 Apr 2017 16:43:19 +0200 Subject: Optimize sub binary creation --- lib/diameter/src/base/diameter_codec.erl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index 90683e7329..43cc49903d 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -698,15 +698,17 @@ pack_avp(#diameter_avp{code = undefined, data = B}) %% error. The RFC doesn't explicitly say to do this but the %% receiver can't correctly extract this and following AVP's %% without a correct length. On the downside, the header doesn't - %% reveal if the received header has been padded. - Pad = 8*header_length(B) - bit_size(B), - Len = size(<> = <>), - <>; + %% reveal if the received header has been padded. Discard bytes + %% from the length header for this reason, to avoid creating a sub + %% binary for no useful reason. + Len = header_length(B), + Sz = min(5, size(B)), + <>; pack_avp(#diameter_avp{data = Data} = A) -> pack_bits(Data, A). -header_length(<<_:32, 1:1, _/bitstring>>) -> +header_length(<<_:32, 1:1, _/bits>>) -> 12; header_length(_) -> 8. -- cgit v1.2.3 From 3574624cd77afe2acbae81f712e5f4623e3c3c4d Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Tue, 18 Apr 2017 20:18:07 +0200 Subject: Use dictionary '#new-'/1 when creating CEA Which is the equivalent of what was done with '#new-'/1 and '#set-'/2. --- lib/diameter/src/base/diameter_capx.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/diameter/src/base/diameter_capx.erl b/lib/diameter/src/base/diameter_capx.erl index 07a678c617..30ac847a54 100644 --- a/lib/diameter/src/base/diameter_capx.erl +++ b/lib/diameter/src/base/diameter_capx.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2015. All Rights Reserved. +%% Copyright Ericsson AB 2010-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -349,7 +349,7 @@ cs(LS, RS) -> cea_from_cer(CER, Dict) -> RecName = Dict:msg2rec('CEA'), [_ | Values] = Dict:'#get-'(CER), - Dict:'#set-'(Values, Dict:'#new-'(RecName)). + Dict:'#new-'([RecName | Values]). %% rCEA/3 -- cgit v1.2.3 From 6bf25fd661a43f9c72cf6292965f76fd7570d24f Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Wed, 19 Apr 2017 14:23:30 +0200 Subject: Remove unnecessary value mapping Since value is ignored. --- lib/diameter/src/base/diameter_traffic.erl | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index ccfab22e9c..be934a6255 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -1190,8 +1190,6 @@ get_result(Dict, Msg) -> try [throw(A) || N <- ['Result-Code', 'Experimental-Result'], #diameter_avp{} = A <- [get_avp(Dict, N, Msg)]] - of - [] -> false catch #diameter_avp{} = A -> A end. -- cgit v1.2.3 From 1b6f8a04ac3375217d9e0203898ffaedfb1f234f Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Wed, 19 Apr 2017 23:15:14 +0200 Subject: Be more flexible in what can be packed as 'AVP' In particular, allow {Name, Value} and {Dict, Name, Value} without requiring a diameter_avp wrapper. --- lib/diameter/include/diameter_gen.hrl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index e35d448754..99f5da5aee 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -173,7 +173,15 @@ pack_AVP(Name, #diameter_avp{name = AvpName, value = Data}) -> 0 == avp_arity(Name, AvpName) orelse ?THROW([known_avp_as_AVP, Name, AvpName, Data]), - e(AvpName, [Data]). + e(AvpName, [Data]); + +%% The backdoor ... +pack_AVP(_, {AvpName, Value}) -> + e(AvpName, [Value]); + +%% ... and the side door. +pack_AVP(_Name, {_Dict, _AvpName, _Data}= T) -> + diameter_codec:pack_avp(#diameter_avp{data = T}). %% --------------------------------------------------------------------------- %% # decode_avps/2 -- cgit v1.2.3 From fb75d1cf29ba5f63de021f3cb4e7165ed265be3c Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Fri, 21 Apr 2017 17:16:48 +0200 Subject: Simplify AVP decode as mapfoldl --- lib/diameter/include/diameter_gen.hrl | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index 99f5da5aee..1b79aa6d32 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -193,8 +193,8 @@ pack_AVP(_Name, {_Dict, _AvpName, _Data}= T) -> decode_avps(Name, Recs) -> {Avps, {Rec, Failed}} - = lists:foldl(fun(T,A) -> decode(Name, T, A) end, - {[], {newrec(Name), []}}, + = mapfoldl(fun(T,A) -> decode(Name, T, A) end, + {newrec(Name), []}, Recs), {Rec, Avps, Failed ++ missing(Rec, Name, Failed)}. %% Append 5005 errors so that errors are reported in the order @@ -204,6 +204,19 @@ decode_avps(Name, Recs) -> newrec(Name) -> '#new-'(name2rec(Name)). +%% mapfoldl/3 +%% +%% Like lists:mapfoldl/3, but don't reverse the list. + +mapfoldl(F, Acc, List) -> + mapfoldl(F, Acc, List, []). + +mapfoldl(F, Acc0, [T|Rest], List) -> + {B, Acc} = F(T, Acc0), + mapfoldl(F, Acc, Rest, [B|List]); +mapfoldl(_, Acc, [], List) -> + {List, Acc}. + %% 3588: %% %% DIAMETER_MISSING_AVP 5005 @@ -355,9 +368,8 @@ d(Name, Avp, Acc) -> try Mod:avp(decode, Data, AvpName) of V -> - {Avps, T} = Acc, {H, A} = ungroup(V, Avp), - {[H | Avps], pack_avp(Name, A, T)} + {H, pack_avp(Name, A, Acc)} catch throw: {?TAG, {grouped, Error, ComponentAvps}} -> g(is_failed(), Error, Name, trim(Avp), Acc, ComponentAvps); @@ -417,9 +429,9 @@ g(false, Error, _Name, Avp, Acc, ComponentAvps) -> %% g/4 g({RC, ErrorData}, Avp, Acc, ComponentAvps) -> - {Avps, {Rec, Errors}} = Acc, + {Rec, Errors} = Acc, E = Avp#diameter_avp{data = [ErrorData]}, - {[[Avp | trim(ComponentAvps)] | Avps], {Rec, [{RC, E} | Errors]}}. + {[Avp | trim(ComponentAvps)], {Rec, [{RC, E} | Errors]}}. %% d/5 @@ -431,14 +443,14 @@ d(true, _, Name, Avp, Acc) -> %% occurrence if the peer sends a faulty AVP that we need to respond %% sensibly to. Log the occurence for traceability, but the peer will %% also receive info in the resulting answer message. -d(false, Reason, Name, Avp, {Avps, Acc}) -> +d(false, Reason, Name, Avp, Acc) -> Stack = diameter_lib:get_stacktrace(), diameter_lib:log(decode_error, ?MODULE, ?LINE, {Name, Avp#diameter_avp.name, Stack}), {Rec, Failed} = Acc, - {[Avp|Avps], {Rec, [rc(Reason, Avp) | Failed]}}. + {Avp, {Rec, [rc(Reason, Avp) | Failed]}}. %% relax/2 @@ -498,8 +510,8 @@ reset(_, _) -> %% Don't know this AVP: see if it can be packed in an 'AVP' field %% undecoded. Note that the type field is 'undefined' in this case. -decode_AVP(Name, Avp, {Avps, Acc}) -> - {[trim(Avp) | Avps], pack_AVP(Name, Avp, Acc)}. +decode_AVP(Name, Avp, Acc) -> + {trim(Avp), pack_AVP(Name, Avp, Acc)}. %% rc/1 -- cgit v1.2.3 From d6b15a127ec9263bd9ec8571ec577f4e7e304182 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Fri, 21 Apr 2017 15:41:00 +0200 Subject: Simplify value setting at decode --- lib/diameter/include/diameter_gen.hrl | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index 1b79aa6d32..4624d42c6f 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -635,8 +635,11 @@ pack(Arity, FieldName, Avp, {Rec, _} = Acc) -> %% pack/5 -pack(undefined, 1, FieldName, Avp, Acc) -> - p(FieldName, fun(V) -> V end, Avp, Acc); +pack(undefined, 1, 'AVP' = F, Avp, {Rec, Failed}) -> %% unlikely + {'#set-'({F, Avp}, Rec), Failed}; + +pack(undefined, 1, F, #diameter_avp{value = V}, {Rec, Failed}) -> + {'#set-'({F, V}, Rec), Failed}; %% 3588: %% @@ -649,25 +652,17 @@ pack(undefined, 1, FieldName, Avp, Acc) -> pack(_, 1, _, Avp, {Rec, Failed}) -> {Rec, [{5009, Avp} | Failed]}; -pack(L, {_, Max}, FieldName, Avp, Acc) -> + +pack(L, {_, Max}, F, Avp, {Rec, Failed}) -> case '*' /= Max andalso has_prefix(Max, L) of true -> - {Rec, Failed} = Acc, {Rec, [{5009, Avp} | Failed]}; + false when F == 'AVP' -> + {'#set-'({F, [Avp | L]}, Rec), Failed}; false -> - p(FieldName, fun(V) -> [V|L] end, Avp, Acc) + {'#set-'({F, [Avp#diameter_avp.value | L]}, Rec), Failed} end. -%% p/4 - -p(F, Fun, Avp, {Rec, Failed}) -> - {'#set-'({F, Fun(value(F, Avp))}, Rec), Failed}. - -value('AVP', Avp) -> - Avp; -value(_, Avp) -> - Avp#diameter_avp.value. - %% --------------------------------------------------------------------------- %% # grouped_avp/3 %% --------------------------------------------------------------------------- -- cgit v1.2.3 From c83d5ac4d4df41924b52cb577c255cd0c23f36ed Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 22 Apr 2017 00:29:08 +0200 Subject: Remove upgrade-related code This and subsequent commits are destined for OTP 20.0. --- lib/diameter/include/diameter_gen.hrl | 9 +- lib/diameter/src/base/diameter_dict.erl | 154 ----------------------------- lib/diameter/src/base/diameter_service.erl | 77 +-------------- lib/diameter/src/modules.mk | 1 - lib/diameter/test/diameter_dict_SUITE.erl | 145 --------------------------- lib/diameter/test/modules.mk | 3 +- 6 files changed, 3 insertions(+), 386 deletions(-) delete mode 100644 lib/diameter/src/base/diameter_dict.erl delete mode 100644 lib/diameter/test/diameter_dict_SUITE.erl diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index 4624d42c6f..fadc6f2b60 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -58,14 +58,7 @@ putr(K,V) -> put({?TAG, K}, V). getr(K) -> - case get({?TAG, K}) of - undefined -> - V = erase({?MODULE, K}), %% written in old code - V == undefined orelse putr(K,V), - V; - V -> - V - end. + get({?TAG, K}). eraser(K) -> erase({?TAG, K}). diff --git a/lib/diameter/src/base/diameter_dict.erl b/lib/diameter/src/base/diameter_dict.erl deleted file mode 100644 index 7db294a1b1..0000000000 --- a/lib/diameter/src/base/diameter_dict.erl +++ /dev/null @@ -1,154 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2010-2016. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% - -%% -%% This module provide OTP's dict interface built on top of ets. -%% -%% Note that while the interface is the same as dict the semantics -%% aren't quite. A Dict here is just a table identifier (although -%% this fact can't be used if you want dict/ets-based implementations -%% to be interchangeable) so changes made to the Dict modify the -%% underlying table. For merge/3, the first argument table is modified. -%% -%% The underlying ets table implementing a dict is deleted when the -%% process from which new() was invoked exits and the dict is only -%% writable from this process. -%% -%% The reason for this is to be able to swap dict/ets-based -%% implementations: the former is easier to debug, the latter is -%% faster for larger tables. It's also just a nice interface even -%% when there's no need for swapability. -%% - --module(diameter_dict). - --export([append/3, - append_list/3, - erase/2, - fetch/2, - fetch_keys/1, - filter/2, - find/2, - fold/3, - from_list/1, - is_key/2, - map/2, - merge/3, - new/0, - store/3, - to_list/1, - update/3, - update/4, - update_counter/3]). - -%%% ---------------------------------------------------------- -%%% EXPORTED INTERNAL FUNCTIONS -%%% ---------------------------------------------------------- - -append(Key, Value, Dict) -> - append_list(Key, [Value], Dict). - -append_list(Key, ValueList, Dict) - when is_list(ValueList) -> - update(Key, fun(V) -> V ++ ValueList end, ValueList, Dict). - -erase(Key, Dict) -> - ets:delete(Dict, Key), - Dict. - -fetch(Key, Dict) -> - {ok, V} = find(Key, Dict), - V. - -fetch_keys(Dict) -> - ets:foldl(fun({K,_}, Acc) -> [K | Acc] end, [], Dict). - -filter(Pred, Dict) -> - lists:foreach(fun({K,V}) -> filter(Pred(K,V), K, Dict) end, to_list(Dict)), - Dict. - -find(Key, Dict) -> - case ets:lookup(Dict, Key) of - [{Key, V}] -> - {ok, V}; - [] -> - error - end. - -fold(Fun, Acc0, Dict) -> - ets:foldl(fun({K,V}, Acc) -> Fun(K, V, Acc) end, Acc0, Dict). - -from_list(List) -> - lists:foldl(fun store/2, new(), List). - -is_key(Key, Dict) -> - ets:member(Dict, Key). - -map(Fun, Dict) -> - lists:foreach(fun({K,V}) -> store(K, Fun(K,V), Dict) end, to_list(Dict)), - Dict. - -merge(Fun, Dict1, Dict2) -> - fold(fun(K2,V2,_) -> - update(K2, fun(V1) -> Fun(K2, V1, V2) end, V2, Dict1) - end, - Dict1, - Dict2). - -new() -> - ets:new(?MODULE, [set]). - -store(Key, Value, Dict) -> - store({Key, Value}, Dict). - -to_list(Dict) -> - ets:tab2list(Dict). - -update(Key, Fun, Dict) -> - store(Key, Fun(fetch(Key, Dict)), Dict). - -update(Key, Fun, Initial, Dict) -> - store(Key, map(Key, Fun, Dict, Initial), Dict). - -update_counter(Key, Increment, Dict) - when is_integer(Increment) -> - update(Key, fun(V) -> V + Increment end, Increment, Dict). - -%%% --------------------------------------------------------- -%%% INTERNAL FUNCTIONS -%%% --------------------------------------------------------- - -store({_,_} = T, Dict) -> - ets:insert(Dict, T), - Dict. - -filter(true, _, _) -> - ok; -filter(false, K, Dict) -> - erase(K, Dict). - -map(Key, Fun, Dict, Error) -> - case find(Key, Dict) of - {ok, V} -> - Fun(V); - error -> - Error - end. - diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl index e4f77e3a24..d6fe8d7e05 100644 --- a/lib/diameter/src/base/diameter_service.erl +++ b/lib/diameter/src/base/diameter_service.erl @@ -110,9 +110,6 @@ service :: #diameter_service{}, watchdogT = ets_new(watchdogs) %% #watchdog{} at start :: ets:tid(), - peerT, %% undefined in new code, but remain for upgrade - shared_peers, %% reasons. Replaced by local/remote. - local_peers, %% local :: {ets:tid(), ets:tid(), ets:tid()}, remote :: {ets:tid(), ets:tid(), ets:tid()}, monitor = false :: false | pid(), %% process to die with @@ -556,81 +553,9 @@ terminate(Reason, #state{service_name = Name, local = {PeerT, _, _}} = S) -> %% # code_change/3 %% --------------------------------------------------------------------------- -code_change(_FromVsn, #state{} = S, _Extra) -> - {ok, S}; - -%% Don't support downgrade since we won't in appup. -code_change({down = T, _}, _, _Extra) -> - {error, T}; - -%% Upgrade local/shared peers dicts populated in old code. Don't -code_change(_FromVsn, S0, _Extra) -> - {state, Id, SvcName, Svc, WT, PeerT, SDict, LDict, Monitor, Opts} - = S0, - - init_peers(LT = setelement(1, {PT, _, _} = init_peers(), PeerT), - fun({_,A}) -> A end), - init_peers(init_peers(RT = init_peers(), SDict), - fun(A) -> A end), - - S = #state{id = Id, - service_name = SvcName, - service = Svc, - watchdogT = WT, - peerT = PT, %% empty - shared_peers = SDict, - local_peers = LDict, - local = LT, - remote = RT, - monitor = Monitor, - options = Opts}, - - %% Replacing the table entry and deleting the old shared tables - %% can make outgoing requests return {error, no_connection} until - %% everyone is running new code. Don't delete the tables to avoid - %% crashing request processes. - ets:delete_all_objects(SDict), - ets:delete_all_objects(LDict), - ets:insert(?STATE_TABLE, S), +code_change(_FromVsn, S, _Extra) -> {ok, S}. -%% init_peers/2 - -%% Populate app and identity bags from a new-style #peer{} sets. -init_peers({PeerT, _, _} = T, F) - when is_function(F) -> - ets:foldl(fun(#peer{pid = P, apps = As, caps = C}, N) -> - insert_peer(P, lists:map(F, As), C, T), - N+1 - end, - 0, - PeerT); - -%% Populate #peer{} table given a shared peers dict. -init_peers({PeerT, _, _}, SDict) -> - dict:fold(fun(P, As, N) -> - ets:update_element(PeerT, P, {#peer.apps, As}), - N+1 - end, - 0, - diameter_dict:fold(fun(A, Ps, D) -> - init_peers(A, Ps, PeerT, D) - end, - dict:new(), - SDict)). - -%% init_peers/4 - -init_peers(App, TCs, PeerT, Dict) -> - lists:foldl(fun({P,C}, D) -> - ets:insert(PeerT, #peer{pid = P, - apps = [], - caps = C}), - dict:append(P, App, D) - end, - Dict, - TCs). - %% =========================================================================== %% =========================================================================== diff --git a/lib/diameter/src/modules.mk b/lib/diameter/src/modules.mk index 4e4ce60ddf..d8867a5ed2 100644 --- a/lib/diameter/src/modules.mk +++ b/lib/diameter/src/modules.mk @@ -39,7 +39,6 @@ RT_MODULES = \ base/diameter_config \ base/diameter_config_sup \ base/diameter_codec \ - base/diameter_dict \ base/diameter_lib \ base/diameter_misc_sup \ base/diameter_peer \ diff --git a/lib/diameter/test/diameter_dict_SUITE.erl b/lib/diameter/test/diameter_dict_SUITE.erl deleted file mode 100644 index 4c1349f4eb..0000000000 --- a/lib/diameter/test/diameter_dict_SUITE.erl +++ /dev/null @@ -1,145 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2010-2016. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% - -%% -%% Tests of the dict-like diameter_dict. -%% - --module(diameter_dict_SUITE). - --export([suite/0, - all/0, - groups/0]). - -%% testcases --export([append/1, - fetch/1, - fetch_keys/1, - filter/1, - find/1, - fold/1, - is_key/1, - map/1, - merge/1, - update/1, - update_counter/1]). - --include("diameter_ct.hrl"). - --define(dict, diameter_dict). --define(util, diameter_util). - -%% =========================================================================== - -suite() -> - [{timetrap, {seconds, 60}}]. - -all() -> - [{group, all}, - {group, all, [parallel]}]. - -groups() -> - [{all, [], tc()}]. - -tc() -> - [append, - fetch, - fetch_keys, - filter, - find, - fold, - is_key, - map, - merge, - update, - update_counter]. - -%% =========================================================================== - --define(KV100, [{N,[N]} || N <- lists:seq(1,100)]). - -append(_) -> - D = ?dict:append(k, v, ?dict:new()), - [{k,[v,v]}] = ?dict:to_list(?dict:append(k, v, D)). - -fetch(_) -> - D = ?dict:from_list(?KV100), - [50] = ?dict:fetch(50, D), - Ref = make_ref(), - Ref = try ?dict:fetch(Ref, D) catch _:_ -> Ref end. - -fetch_keys(_) -> - L = ?KV100, - D = ?dict:from_list(L), - L = [{N,[N]} || N <- lists:sort(?dict:fetch_keys(D))]. - -filter(_) -> - L = ?KV100, - F = fun(K,[_]) -> 0 == K rem 2 end, - D = ?dict:filter(F, ?dict:from_list(L)), - true = [T || {K,V} = T <- L, F(K,V)] == lists:sort(?dict:to_list(D)). - -find(_) -> - D = ?dict:from_list(?KV100), - {ok, [50]} = ?dict:find(50, D), - error = ?dict:find(make_ref(), D). - -fold(_) -> - L = ?KV100, - S = lists:sum([N || {N,_} <- L]), - S = ?dict:fold(fun(K,[_],A) -> K + A end, 0, ?dict:from_list(L)). - -is_key(_) -> - L = ?KV100, - D = ?dict:from_list(L), - true = lists:all(fun({N,_}) -> ?dict:is_key(N,D) end, L), - false = ?dict:is_key(make_ref(), D). - -map(_) -> - L = ?KV100, - F = fun(_,V) -> [N] = V, N*2 end, - D = ?dict:map(F, ?dict:from_list(L)), - M = [{K, F(K,V)} || {K,V} <- L], - M = lists:sort(?dict:to_list(D)). - -merge(_) -> - L = ?KV100, - F = fun(_,V1,V2) -> V1 ++ V2 end, - D = ?dict:merge(F, ?dict:from_list(L), ?dict:from_list(L)), - M = [{K, F(K,V,V)} || {K,V} <- L], - M = lists:sort(?dict:to_list(D)). - -update(_) -> - L = ?KV100, - F = fun([V]) -> 2*V end, - D = ?dict:update(50, F, ?dict:from_list(L)), - 100 = ?dict:fetch(50, D), - Ref = make_ref(), - Ref = try ?dict:update(Ref, F, D) catch _:_ -> Ref end, - [Ref] = ?dict:fetch(Ref, ?dict:update(Ref, - fun(_,_) -> ?ERROR(i_think_not) end, - [Ref], - D)). - -update_counter(_) -> - L = [{N,2*N} || {N,_} <- ?KV100], - D = ?dict:update_counter(50, 20, ?dict:from_list(L)), - 120 = ?dict:fetch(50,D), - 2 = ?dict:fetch(1,D). diff --git a/lib/diameter/test/modules.mk b/lib/diameter/test/modules.mk index 80d0f8d59c..0c73adca12 100644 --- a/lib/diameter/test/modules.mk +++ b/lib/diameter/test/modules.mk @@ -1,7 +1,7 @@ # %CopyrightBegin% # -# Copyright Ericsson AB 2010-2015. All Rights Reserved. +# Copyright Ericsson AB 2010-2017. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -31,7 +31,6 @@ MODULES = \ diameter_codec_test \ diameter_config_SUITE \ diameter_compiler_SUITE \ - diameter_dict_SUITE \ diameter_distribution_SUITE \ diameter_dpr_SUITE \ diameter_event_SUITE \ -- cgit v1.2.3 From 2ae059913dc332e5655f44d1b2292342cb470fc1 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 22 Apr 2017 08:46:48 +0200 Subject: Don't prepend bit for sub binary optimization --- lib/diameter/include/diameter_gen.hrl | 17 ++++++++++------- lib/diameter/src/base/diameter_codec.erl | 17 ++++++----------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index fadc6f2b60..968cf66ea7 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -378,8 +378,11 @@ d(Name, Avp, Acc) -> %% Remove any extra bit that was added in diameter_codec to induce a %% 5014 error. -trim(#diameter_avp{data = <<0:1, Bin/binary>>} = Avp) -> - Avp#diameter_avp{data = Bin}; +trim(#diameter_avp{data = Data} = Avp) -> + Avp#diameter_avp{data = trim(Data)}; + +trim({5014, Bin}) -> + Bin; trim(Avps) when is_list(Avps) -> @@ -569,9 +572,9 @@ pack_avp(_, Arity, Avp, Acc) -> %% payload for the AVP's type, but in this case we don't know the %% type. -pack_AVP(_, #diameter_avp{data = <<0:1, Data/binary>>} = Avp, Acc) -> +pack_AVP(_, #diameter_avp{data = {5014 = RC, Data}} = Avp, Acc) -> {Rec, Failed} = Acc, - {Rec, [{5014, Avp#diameter_avp{data = Data}} | Failed]}; + {Rec, [{RC, Avp#diameter_avp{data = Data}} | Failed]}; pack_AVP(Name, #diameter_avp{is_mandatory = M, name = AvpName} = Avp, Acc) -> case pack_arity(Name, AvpName, M) of @@ -660,7 +663,7 @@ pack(L, {_, Max}, F, Avp, {Rec, Failed}) -> %% # grouped_avp/3 %% --------------------------------------------------------------------------- --spec grouped_avp(decode, avp_name(), bitstring()) +-spec grouped_avp(decode, avp_name(), binary() | {5014, binary()}) -> {avp_record(), [avp()]}; (encode, avp_name(), avp_record() | avp_values()) -> iolist() @@ -670,8 +673,8 @@ pack(L, {_, Max}, F, Avp, {Rec, Failed}) -> %% length in the header was too short (insufficient for the extracted %% header) or too long (past the end of the message). An empty payload %% is sufficient according to the RFC text for 5014. -grouped_avp(decode, _Name, <<0:1, _/binary>>) -> - throw({?TAG, {grouped, {5014, []}, []}}); +grouped_avp(decode, _Name, {5014 = RC, _Bin}) -> + throw({?TAG, {grouped, {RC, []}, []}}); grouped_avp(decode, Name, Data) -> grouped_decode(Name, diameter_codec:collect_avps(Data)); diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index 43cc49903d..789395abe9 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -617,7 +617,7 @@ collect_avps(Code, VendorId, M, P, Len, Pad, Rest, N, Acc) -> vendor_id = VendorId, is_mandatory = M, need_encryption = P, - data = <<0:1, Rest/binary>>, + data = {5014, Rest}, index = N}, [Avp | Acc] end. @@ -705,23 +705,18 @@ pack_avp(#diameter_avp{code = undefined, data = B}) Sz = min(5, size(B)), <>; +%% Ignoring errors in Failed-AVP or during a relay encode. +pack_avp(#diameter_avp{data = {5014, Data}} = A) -> + pack_data(Data, A); + pack_avp(#diameter_avp{data = Data} = A) -> - pack_bits(Data, A). + pack_data(Data, A). header_length(<<_:32, 1:1, _/bits>>) -> 12; header_length(_) -> 8. -%% pack_bits/2 - -%% Ignoring errors in Failed-AVP or during a relay encode. -pack_bits(<<0:1, B/binary>>, Avp) -> - pack_bits(B, Avp); - -pack_bits(Data, Avp) -> - pack_data(Data, Avp). - %% pack_data/2 pack_data(Data, #diameter_avp{code = Code, -- cgit v1.2.3 From 05a9572161fde1054946ca17386890b0c7138500 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 22 Apr 2017 11:48:12 +0200 Subject: Add dictionary function avp_arity/1 --- lib/diameter/src/compiler/diameter_codegen.erl | 30 +++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl index 864d5f0691..9dd3930866 100644 --- a/lib/diameter/src/compiler/diameter_codegen.erl +++ b/lib/diameter/src/compiler/diameter_codegen.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2016. All Rights Reserved. +%% Copyright Ericsson AB 2010-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -158,6 +158,7 @@ erl_forms(Mod, ParseD) -> {msg2rec, 1}, {name2rec, 1}, {avp_name, 2}, + {avp_arity, 1}, {avp_arity, 2}, {avp_header, 1}, {avp, 3}, @@ -178,7 +179,8 @@ erl_forms(Mod, ParseD) -> f_msg2rec(ParseD), f_name2rec(ParseD), f_avp_name(ParseD), - f_avp_arity(ParseD), + f_avp_arity_1(ParseD), + f_avp_arity_2(ParseD), f_avp_header(ParseD), f_avp(ParseD), f_enumerated_avp(ParseD), @@ -417,11 +419,33 @@ vendor_id_map(ParseD) -> end, get_value(grouped, ParseD)). +%%% ------------------------------------------------------------------------ +%%% # avp_arity/1 +%%% ------------------------------------------------------------------------ + +f_avp_arity_1(ParseD) -> + {?function, avp_arity, 1, avp_arities(ParseD) ++ [?BADARG(1)]}. + +avp_arities(ParseD) -> + Msgs = get_value(messages, ParseD), + Groups = get_value(grouped, ParseD) + ++ lists:flatmap(fun avps/1, get_value(import_groups, ParseD)), + lists:map(fun c_avp_arities/1, Msgs ++ Groups). + +c_avp_arities({N,_,_,_,As}) -> + c_avp_arities(N,As); +c_avp_arities({N,_,_,As}) -> + c_avp_arities(N,As). + +c_avp_arities(Name, Avps) -> + Arities = [{?A(N), A} || T <- Avps, {N,A} <- [avp_info(T)]], + {?clause, [?Atom(Name)], [], [?TERM(Arities)]}. + %%% ------------------------------------------------------------------------ %%% # avp_arity/2 %%% ------------------------------------------------------------------------ -f_avp_arity(ParseD) -> +f_avp_arity_2(ParseD) -> {?function, avp_arity, 2, avp_arity(ParseD)}. avp_arity(ParseD) -> -- cgit v1.2.3 From 6e753c9861effb4ae820d7b1ad20fdb66dca34f6 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 22 Apr 2017 12:43:33 +0200 Subject: Use avp_arity/1 when detecting missing AVPs Recursing over the entire list of arities and values is faster than retrieving them one at a time. --- lib/diameter/include/diameter_gen.hrl | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index 968cf66ea7..3c59370e73 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -226,17 +226,33 @@ missing(Rec, Name, Failed) -> end, maps:new(), Failed), - [{5005, empty_avp(F,H)} || {F,T} <- '#get-'(Rec), - not has_arity(avp_arity(Name, F), T), - {C,_,V} = H <- [avp_header(F)], - not maps:is_key({C,V}, Avps)]. + missing(avp_arity(Name), tl(tuple_to_list(Rec)), Avps, []). + +missing([{Name, Arity} | As], [Value | Vs], Avps, Acc) -> + missing(As, Vs, Avps, case + [H || missing_arity(Arity, Value), + {C,_,V} = H <- [avp_header(Name)], + not maps:is_key({C,V}, Avps)] + of + [H] -> + [{5005, empty_avp(Name, H)} | Acc]; + [] -> + Acc + end); + +missing([], [], _, Acc) -> + Acc. %% Maximum arities have already been checked in building the record. -has_arity({Min, _}, L) -> - has_prefix(Min, L); -has_arity(N, V) -> - N /= 1 orelse V /= undefined. +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. -- cgit v1.2.3 From 7aec6ad7e56a377bf84fe833dfde43f52263224a Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 22 Apr 2017 23:57:48 +0200 Subject: Use avp_arity/1 when creating a zero group Converting with list_to_binary/1 appears to be faster than the equivalent binary comprehension: << (z(F,A)) || {F,A} <- avp_arity(Name) >> --- lib/diameter/include/diameter_gen.hrl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index 3c59370e73..f788264647 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -733,10 +733,7 @@ grouped_decode(Name, ComponentAvps) -> %% --------------------------------------------------------------------------- empty_group(Name) -> - list_to_binary(empty_body(Name)). - -empty_body(Name) -> - [z(F, avp_arity(Name, F)) || F <- '#info-'(name2rec(Name), fields)]. + list_to_binary([z(F,A) || {F,A} <- avp_arity(Name)]). z(Name, 1) -> z(Name); -- cgit v1.2.3 From 6838d0a8988eb7264641eb68bb7d7a79ff7bfbbb Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sun, 23 Apr 2017 09:39:32 +0200 Subject: Fix maximum AVP arity check --- lib/diameter/include/diameter_gen.hrl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index f788264647..67b9f890a7 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -666,7 +666,7 @@ pack(_, 1, _, Avp, {Rec, Failed}) -> {Rec, [{5009, Avp} | Failed]}; pack(L, {_, Max}, F, Avp, {Rec, Failed}) -> - case '*' /= Max andalso has_prefix(Max, L) of + case '*' /= Max andalso has_prefix(Max+1, L) of true -> {Rec, [{5009, Avp} | Failed]}; false when F == 'AVP' -> -- cgit v1.2.3 From 0bbe8462cc8d28355ae5006b737d59184fc6c3eb Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Mon, 24 Apr 2017 11:50:40 +0200 Subject: Remove minor diameter_config bloat Folded when I should have mapped. --- lib/diameter/src/base/diameter_config.erl | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl index 386ae967f2..38781d5c23 100644 --- a/lib/diameter/src/base/diameter_config.erl +++ b/lib/diameter/src/base/diameter_config.erl @@ -676,7 +676,7 @@ stop_transport(SvcName, Refs) -> make_config(SvcName, Opts) -> AppOpts = [T || {application, _} = T <- Opts], - Apps = init_apps(AppOpts), + Apps = [init_app(T) || T <- AppOpts], [] == Apps andalso ?THROW(no_apps), @@ -822,10 +822,7 @@ encode_CER(Opts) -> ?THROW(Reason) end. -init_apps(Opts) -> - lists:foldl(fun app_acc/2, [], lists:reverse(Opts)). - -app_acc({application, Opts} = T, Acc) -> +init_app({application, Opts} = T) -> is_list(Opts) orelse ?THROW(T), [Dict, Mod] = get_opt([dictionary, module], Opts), @@ -834,15 +831,14 @@ app_acc({application, Opts} = T, Acc) -> M = get_opt(call_mutates_state, Opts, false, [true]), A = get_opt(answer_errors, Opts, discard, [callback, report]), P = get_opt(request_errors, Opts, answer_3xxx, [answer, callback]), - [#diameter_app{alias = Alias, - dictionary = Dict, - id = cb(Dict, id), - module = init_mod(Mod), - init_state = ModS, - mutable = M, - options = [{answer_errors, A}, - {request_errors, P}]} - | Acc]. + #diameter_app{alias = Alias, + dictionary = Dict, + id = cb(Dict, id), + module = init_mod(Mod), + init_state = ModS, + mutable = M, + options = [{answer_errors, A}, + {request_errors, P}]}. init_mod(#diameter_callback{} = R) -> init_mod([diameter_callback, R]); -- cgit v1.2.3 From d56888b318baf62cdaf9d6e9f875f9656e420e4f Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Tue, 25 Apr 2017 00:09:06 +0200 Subject: Remove use of process dictionary in decode By passing additional arguments through it. --- lib/diameter/include/diameter_gen.hrl | 453 ++++++++++--------------- lib/diameter/src/base/diameter_codec.erl | 232 ++++++------- lib/diameter/src/base/diameter_config.erl | 29 +- lib/diameter/src/base/diameter_peer_fsm.erl | 42 +-- lib/diameter/src/base/diameter_service.erl | 100 ++---- lib/diameter/src/base/diameter_traffic.erl | 361 ++++++++++---------- lib/diameter/src/base/diameter_types.erl | 250 ++++++-------- lib/diameter/src/base/diameter_watchdog.erl | 120 ++++--- lib/diameter/src/compiler/diameter_codegen.erl | 53 +-- 9 files changed, 730 insertions(+), 910 deletions(-) diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index 67b9f890a7..1683d93557 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -29,18 +29,6 @@ %% Tag common to generated dictionaries. -define(TAG, diameter_gen). -%% Key to a value in the process dictionary that determines whether or -%% not an unrecognized AVP setting the M-bit should be regarded as an -%% error or not. See is_strict/0. This is only used to relax M-bit -%% interpretation inside Grouped AVPs not setting the M-bit. The -%% service_opt() strict_mbit can be used to disable the check -%% globally. --define(STRICT_KEY, strict). - -%% Key that says whether or not we should do a best-effort decode -%% within Failed-AVP. --define(FAILED_KEY, failed). - -type parent_name() :: atom(). %% parent = Message or AVP -type parent_record() :: tuple(). %% -type avp_name() :: atom(). @@ -51,33 +39,21 @@ -type grouped_avp() :: nonempty_improper_list(#diameter_avp{}, [avp()]). -type avp() :: non_grouped_avp() | grouped_avp(). -%% Use a (hopefully) unique key when manipulating the process -%% dictionary. - -putr(K,V) -> - put({?TAG, K}, V). - -getr(K) -> - get({?TAG, K}). - -eraser(K) -> - erase({?TAG, K}). - %% --------------------------------------------------------------------------- -%% # encode_avps/2 +%% # encode_avps/3 %% --------------------------------------------------------------------------- --spec encode_avps(parent_name(), parent_record() | avp_values()) +-spec encode_avps(parent_name(), parent_record() | avp_values(), term()) -> iolist() | no_return(). -encode_avps(Name, Vals) +encode_avps(Name, Vals, Opts) when is_list(Vals) -> - encode_avps(Name, '#set-'(Vals, newrec(Name))); + encode_avps(Name, '#set-'(Vals, newrec(Name)), Opts); -encode_avps(Name, Rec) -> +encode_avps(Name, Rec, Opts) -> try - encode(Name, Rec) + encode(Name, Rec, Opts) catch throw: {?MODULE, Reason} -> diameter_lib:log({encode, error}, @@ -94,99 +70,99 @@ encode_avps(Name, Rec) -> erlang:error({encode_failure, Reason, Name, Stack}) end. -%% encode/2 +%% encode/3 -encode(Name, Rec) -> - [encode(Name, F, V) || {F,V} <- '#get-'(Rec)]. +encode(Name, Rec, Opts) -> + [encode(Name, F, V, Opts) || {F,V} <- '#get-'(Rec)]. -%% encode/3 +%% encode/4 -encode(Name, AvpName, Values) -> - e(Name, AvpName, avp_arity(Name, AvpName), Values). +encode(Name, AvpName, Values, Opts) -> + enc(Name, AvpName, avp_arity(Name, AvpName), Values, Opts). -%% e/4 +%% enc/5 -e(_, AvpName, 1, undefined) -> +enc(_, AvpName, 1, undefined, _) -> ?THROW([mandatory_avp_missing, AvpName]); -e(Name, AvpName, 1, Value) -> - e(Name, AvpName, [Value]); +enc(Name, AvpName, 1, Value, Opts) -> + enc(Name, AvpName, [Value], Opts); -e(_, _, {0,_}, []) -> +enc(_, _, {0,_}, [], _) -> []; -e(_, AvpName, _, T) +enc(_, AvpName, _, T, _) when not is_list(T) -> ?THROW([repeated_avp_as_non_list, AvpName, T]); -e(_, AvpName, {Min, _}, L) +enc(_, AvpName, {Min, _}, L, _) when length(L) < Min -> ?THROW([repeated_avp_insufficient_arity, AvpName, Min, L]); -e(_, AvpName, {_, Max}, L) +enc(_, AvpName, {_, Max}, L, _) when Max < length(L) -> ?THROW([repeated_avp_excessive_arity, AvpName, Max, L]); -e(Name, AvpName, _, Values) -> - e(Name, AvpName, Values). +enc(Name, AvpName, _, Values, Opts) -> + enc(Name, AvpName, Values, Opts). -%% e/3 +%% enc/4 -e(Name, 'AVP', Values) -> - [pack_AVP(Name, A) || A <- Values]; +enc(Name, 'AVP', Values, Opts) -> + [enc_AVP(Name, A, Opts) || A <- Values]; -e(_, AvpName, Values) -> - e(AvpName, Values). +enc(_, AvpName, Values, Opts) -> + enc(AvpName, Values, Opts). -%% e/2 +%% env/3 -e(AvpName, Values) -> +enc(AvpName, Values, Opts) -> H = avp_header(AvpName), - [diameter_codec:pack_avp(H, avp(encode, V, AvpName)) || V <- Values]. + [diameter_codec:pack_data(H, avp(encode, V, AvpName, Opts)) + || V <- Values]. -%% pack_AVP/2 +%% enc_AVP/3 %% No value: assume AVP data is already encoded. The normal case will %% be when this is passed back from #diameter_packet.errors as a %% consequence of a failed decode. Any AVP can be encoded this way %% however, which side-steps any arity checks for known AVP's and %% could potentially encode something unfortunate. -pack_AVP(_, #diameter_avp{value = undefined} = A) -> - diameter_codec:pack_avp(A); +enc_AVP(_, #diameter_avp{value = undefined} = A, Opts) -> + diameter_codec:pack_avp(A, Opts); %% Missing name for value encode. -pack_AVP(_, #diameter_avp{name = N, value = V}) +enc_AVP(_, #diameter_avp{name = N, value = V}, _) when N == undefined; N == 'AVP' -> ?THROW([value_with_nameless_avp, N, V]); %% Or not. Ensure that 'AVP' is the appropriate field. Note that if we %% don't know this AVP at all then the encode will fail. -pack_AVP(Name, #diameter_avp{name = AvpName, - value = Data}) -> +enc_AVP(Name, #diameter_avp{name = AvpName, value = Data}, Opts) -> 0 == avp_arity(Name, AvpName) orelse ?THROW([known_avp_as_AVP, Name, AvpName, Data]), - e(AvpName, [Data]); + enc(AvpName, [Data], Opts); %% The backdoor ... -pack_AVP(_, {AvpName, Value}) -> - e(AvpName, [Value]); +enc_AVP(_, {AvpName, Value}, Opts) -> + enc(AvpName, [Value], Opts); %% ... and the side door. -pack_AVP(_Name, {_Dict, _AvpName, _Data}= T) -> - diameter_codec:pack_avp(#diameter_avp{data = T}). +enc_AVP(_Name, {_Dict, _AvpName, _Data} = T, Opts) -> + diameter_codec:pack_avp(#diameter_avp{data = T}, Opts). %% --------------------------------------------------------------------------- %% # decode_avps/2 %% --------------------------------------------------------------------------- --spec decode_avps(parent_name(), [#diameter_avp{}]) +-spec decode_avps(parent_name(), [#diameter_avp{}], map()) -> {parent_record(), [avp()], Failed} when Failed :: [{5000..5999, #diameter_avp{}}]. -decode_avps(Name, Recs) -> +decode_avps(Name, Recs, Opts) -> {Avps, {Rec, Failed}} - = mapfoldl(fun(T,A) -> decode(Name, T, A) end, + = mapfoldl(fun(T,A) -> decode(Name, Opts, T, A) end, {newrec(Name), []}, Recs), {Rec, Avps, Failed ++ missing(Rec, Name, Failed)}. @@ -284,20 +260,16 @@ empty_avp(Name, {Code, Flags, VId}) -> %% specific errors that can be described by this AVP are described in %% the following section. -%% decode/3 - -decode(Name, #diameter_avp{code = Code, vendor_id = Vid} = Avp, Acc) -> - decode(Name, avp_name(Code, Vid), Avp, Acc). - %% decode/4 -%% AVP is defined in the dictionary ... -decode(Name, {AvpName, Type}, Avp, Acc) -> - d(Name, Avp#diameter_avp{name = AvpName, type = Type}, Acc); +decode(Name, Opts, #diameter_avp{code = Code, vendor_id = Vid} = Avp, Acc) -> + decode(Name, Opts, avp_name(Code, Vid), Avp, Acc). -%% ... or not. -decode(Name, 'AVP', Avp, Acc) -> - decode_AVP(Name, Avp, Acc). +%% decode/5 + +%% AVP not in dictionary. +decode(Name, Opts, 'AVP', Avp, Acc) -> + decode_AVP(Name, Avp, Opts, Acc); %% 6733, 4.4: %% @@ -346,47 +318,63 @@ decode(Name, 'AVP', Avp, Acc) -> %% defined the RFC's "unrecognized", which is slightly stronger than %% "not defined".) -%% d/3 - -d(Name, Avp, Acc) -> - #diameter_avp{name = AvpName, - data = Data, - type = Type, - is_mandatory = M} +decode(Name, Opts0, {AvpName, Type}, Avp, Acc) -> + #diameter_avp{data = Data, is_mandatory = M} = Avp, - %% Use the process dictionary is to keep track of whether or not - %% to ignore an M-bit on an encapsulated AVP. Not ideal, but the - %% alternative requires widespread changes to be able to pass the - %% value around through the entire decode. The solution here is - %% simple in comparison, both to implement and to understand. + %% Whether or not to ignore an M-bit on an encapsulated AVP, or on + %% all AVPs with the service_opt() strict_mbit. + Opts1 = set_strict(Type, M, Opts0), - Strict = relax(Type, M), + %% Whether or not we're decoding within Failed-AVP and should + %% ignore decode errors. + #{dictionary := AppMod, failed_avp := Failed} + = Opts + = set_failed(Name, Opts1), %% Not AvpName or else a failed Failed-AVP + %% decode is packed into 'AVP'. - %% Use the process dictionary again to keep track of whether we're - %% decoding within Failed-AVP and should ignore decode errors - %% altogether. - - Failed = relax(Name), %% Not AvpName or else a failed Failed-AVP - %% decode is packed into 'AVP'. - Mod = dict(Failed), %% Dictionary to decode in. + %% Reset the dictionary for best-effort decode of Failed-AVP. + Mod = if Failed -> + AppMod; + true -> + ?MODULE + 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. - try Mod:avp(decode, Data, AvpName) of - V -> - {H, A} = ungroup(V, Avp), - {H, pack_avp(Name, A, Acc)} + try Mod:avp(decode, Data, AvpName, Opts) of + {Rec, As} when Type == 'Grouped' -> + A = Avp#diameter_avp{name = AvpName, + value = Rec, + type = Type}, + {[A|As], pack_avp(Name, A, Opts, Acc)}; + + V when Type /= 'Grouped' -> + A = Avp#diameter_avp{name = AvpName, + value = V, + type = Type}, + {A, pack_avp(Name, A, Opts, Acc)} catch throw: {?TAG, {grouped, Error, ComponentAvps}} -> - g(is_failed(), Error, Name, trim(Avp), Acc, ComponentAvps); + decode_error(Name, + Error, + ComponentAvps, + Opts, + Avp#diameter_avp{name = AvpName, + data = trim(Avp#diameter_avp.data), + type = Type}, + Acc); + error: Reason -> - d(is_failed(), Reason, Name, trim(Avp), Acc) - after - reset(?STRICT_KEY, Strict), - reset(?FAILED_KEY, Failed) + decode_error(Name, + Reason, + Opts, + Avp#diameter_avp{name = AvpName, + data = trim(Avp#diameter_avp.data), + type = Type}, + Acc) end. %% trim/1 @@ -407,123 +395,66 @@ trim(Avps) trim(Avp) -> Avp. -%% dict/1 -%% -%% Retrieve the dictionary for the best-effort decode of Failed-AVP, -%% as put by diameter_codec:decode/2. See that function for the -%% explanation. - -dict(true) -> - case get({diameter_codec, dictionary}) of - undefined -> - ?MODULE; - Mod -> - Mod - end; - -dict(_) -> - ?MODULE. - -%% g/5 - -%% Ignore decode errors within Failed-AVP (best-effort) ... -g(true, [_Error | Rec], Name, Avp, Acc, _ComponentAvps) -> - decode_AVP(Name, Avp#diameter_avp{value = Rec}, Acc); -g(true, _Error, Name, Avp, Acc, _ComponentAvps) -> - decode_AVP(Name, Avp, Acc); - -%% ... or not. -g(false, [Error | _Rec], _Name, Avp, Acc, ComponentAvps) -> - g(Error, Avp, Acc, ComponentAvps); -g(false, Error, _Name, Avp, Acc, ComponentAvps) -> - g(Error, Avp, Acc, ComponentAvps). - -%% g/4 - -g({RC, ErrorData}, Avp, Acc, ComponentAvps) -> - {Rec, Errors} = Acc, - E = Avp#diameter_avp{data = [ErrorData]}, - {[Avp | trim(ComponentAvps)], {Rec, [{RC, E} | Errors]}}. +%% decode_error/6 + +decode_error(Name, [_ | Rec], _, #{failed_avp := true} = Opts, Avp, Acc) -> + decode_AVP(Name, Avp#diameter_avp{value = Rec}, Opts, Acc); + +decode_error(Name, _, _, #{failed_avp := true} = Opts, Avp, Acc) -> + decode_AVP(Name, Avp, Opts, Acc); -%% d/5 +decode_error(_, [Error | _], ComponentAvps, _, Avp, Acc) -> + decode_error(Error, Avp, Acc, ComponentAvps); -%% Ignore a decode error within Failed-AVP ... -d(true, _, Name, Avp, Acc) -> - decode_AVP(Name, Avp, Acc); +decode_error(_, Error, ComponentAvps, _, Avp, Acc) -> + decode_error(Error, Avp, Acc, ComponentAvps). -%% ... or not. Failures here won't be visible since they're a "normal" -%% occurrence if the peer sends a faulty AVP that we need to respond -%% sensibly to. Log the occurence for traceability, but the peer will -%% also receive info in the resulting answer message. -d(false, Reason, Name, Avp, Acc) -> +%% decode_error/5 + +decode_error(Name, _Reason, #{failed_avp := true} = Opts, Avp, Acc) -> + decode_AVP(Name, Avp, Opts, Acc); + +decode_error(Name, Reason, _, Avp, {Rec, Failed}) -> Stack = diameter_lib:get_stacktrace(), diameter_lib:log(decode_error, ?MODULE, ?LINE, {Name, Avp#diameter_avp.name, Stack}), - {Rec, Failed} = Acc, {Avp, {Rec, [rc(Reason, Avp) | Failed]}}. -%% relax/2 - -%% Set false in the process dictionary as soon as we see a Grouped AVP -%% that doesn't set the M-bit, so that is_strict() can say whether or -%% not to ignore the M-bit on an encapsulated AVP. -relax('Grouped', M) -> - case getr(?STRICT_KEY) of - undefined when not M -> - putr(?STRICT_KEY, M); - _ -> - false - end; -relax(_, _) -> - false. - -is_strict() -> - diameter_codec:getopt(strict_mbit) - andalso false /= getr(?STRICT_KEY). - -%% relax/1 -%% -%% Set true in the process dictionary as soon as we see Failed-AVP. -%% Matching on 'Failed-AVP' assumes that this is the RFC AVP. -%% Strictly, this doesn't need to be the case. - -relax('Failed-AVP') -> - putr(?FAILED_KEY, true); +%% decode_error/4 -relax(_) -> - is_failed(). - -%% is_failed/0 -%% -%% Is the AVP currently being decoded nested within Failed-AVP? Note -%% that this is only true when Failed-AVP is the parent. In -%% particular, it's not true when Failed-AVP itself is being decoded -%% (unless nested). - -is_failed() -> - true == getr(?FAILED_KEY). +decode_error({RC, ErrorData}, Avp, {Rec, Failed}, ComponentAvps) -> + E = Avp#diameter_avp{data = [ErrorData]}, + {[Avp | trim(ComponentAvps)], {Rec, [{RC, E} | Failed]}}. -%% is_failed/1 +%% set_strict/3 -is_failed(Name) -> - 'Failed-AVP' == Name orelse is_failed(). +%% Set false as soon as we see a Grouped AVP that doesn't set the +%% M-bit, to ignore the M-bit on an encapsulated AVP. +set_strict('Grouped', false = M, #{strict_mbit := true} = Opts) -> + Opts#{strict_mbit := M}; +set_strict(_, _, Opts) -> + Opts. -%% reset/2 +%% set_failed/2 +%% +%% Set true as soon as we see Failed-AVP. Matching on 'Failed-AVP' +%% assumes that this is the RFC AVP. Strictly, this doesn't need to be +%% the case. -reset(Key, undefined) -> - eraser(Key); -reset(_, _) -> - ok. +set_failed('Failed-AVP', #{failed_avp := false} = Opts) -> + Opts#{failed_avp := true}; +set_failed(_, Opts) -> + Opts. -%% decode_AVP/3 +%% decode_AVP/4 %% %% Don't know this AVP: see if it can be packed in an 'AVP' field %% undecoded. Note that the type field is 'undefined' in this case. -decode_AVP(Name, Avp, Acc) -> - {trim(Avp), pack_AVP(Name, Avp, Acc)}. +decode_AVP(Name, Avp, Opts, Acc) -> + {trim(Avp), pack_AVP(Name, Avp, Opts, Acc)}. %% rc/1 @@ -543,37 +474,20 @@ rc({'DIAMETER', 5014 = RC, _}, #diameter_avp{name = AvpName} = Avp) -> rc(_, Avp) -> {5004, Avp}. -%% ungroup/2 - --spec ungroup(term(), #diameter_avp{}) - -> {avp(), #diameter_avp{}}. - -%% The decoded value in the Grouped case is as returned by grouped_avp/3: -%% a record and a list of component AVP's. -ungroup(V, #diameter_avp{type = 'Grouped'} = Avp) -> - {Rec, As} = V, - A = Avp#diameter_avp{value = Rec}, - {[A|As], A}; - -%% Otherwise it's just a plain value. -ungroup(V, #diameter_avp{} = Avp) -> - A = Avp#diameter_avp{value = V}, - {A, A}. - -%% pack_avp/3 +%% pack_avp/4 -pack_avp(Name, #diameter_avp{name = AvpName} = Avp, Acc) -> - pack_avp(Name, avp_arity(Name, AvpName), Avp, Acc). +pack_avp(Name, #diameter_avp{name = AvpName} = Avp, Opts, Acc) -> + pack_avp(Name, avp_arity(Name, AvpName), Avp, Opts, Acc). -%% pack_avp/4 +%% pack_avp/5 -pack_avp(Name, 0, Avp, Acc) -> - pack_AVP(Name, Avp, Acc); +pack_avp(Name, 0, Avp, Opts, Acc) -> + pack_AVP(Name, Avp, Opts, Acc); -pack_avp(_, Arity, Avp, Acc) -> - pack(Arity, Avp#diameter_avp.name, Avp, Acc). +pack_avp(_, Arity, #diameter_avp{name = AvpName} = Avp, _, Acc) -> + pack(Arity, AvpName, Avp, Acc). -%% pack_AVP/3 +%% pack_AVP/4 %% Length failure was induced because of a header/payload length %% mismatch. The AVP Length is reset to match the received data if @@ -588,44 +502,47 @@ pack_avp(_, Arity, Avp, Acc) -> %% payload for the AVP's type, but in this case we don't know the %% type. -pack_AVP(_, #diameter_avp{data = {5014 = RC, Data}} = Avp, Acc) -> +pack_AVP(_, #diameter_avp{data = {5014 = RC, Data}} = Avp, _, Acc) -> {Rec, Failed} = Acc, {Rec, [{RC, Avp#diameter_avp{data = Data}} | Failed]}; -pack_AVP(Name, #diameter_avp{is_mandatory = M, name = AvpName} = Avp, Acc) -> - case pack_arity(Name, AvpName, M) of - 0 -> - {Rec, Failed} = Acc, - {Rec, [{if M -> 5001; true -> 5008 end, Avp} | Failed]}; - Arity -> - pack(Arity, 'AVP', Avp, Acc) - end. +pack_AVP(Name, Avp, Opts, Acc) -> + pack_AVP(pack_arity(Name, Opts, Avp), Avp, Acc). + +%% pack_AVP/3 + +pack_AVP(0, #diameter_avp{is_mandatory = M} = Avp, Acc) -> + {Rec, Failed} = Acc, + {Rec, [{if M -> 5001; true -> 5008 end, Avp} | Failed]}; + +pack_AVP(Arity, Avp, Acc) -> + pack(Arity, 'AVP', Avp, Acc). %% Give Failed-AVP special treatment since (1) it'll contain any %% unrecognized mandatory AVP's and (2) the RFC 3588 grammar failed to %% allow for Failed-AVP in an answer-message. -pack_arity(Name, AvpName, M) -> +pack_arity(Name, + #{strict_mbit := Strict, + failed_avp := Failed}, + #diameter_avp{is_mandatory = M, + name = AvpName}) -> %% Not testing just Name /= 'Failed-AVP' means we're changing the %% packing of AVPs nested within Failed-AVP, but the point of %% ignoring errors within Failed-AVP is to decode as much as %% possible, and failing because a mandatory AVP couldn't be - %% packed into a dedicated field defeats that point. Note - %% is_failed/1 since is_failed/0 will return false when packing - %% 'AVP' within Failed-AVP. - - pack_arity(is_failed(Name) - orelse {Name, AvpName} == {'answer-message', 'Failed-AVP'} - orelse not M - orelse not is_strict(), - Name). - -pack_arity(true, Name) -> - avp_arity(Name, 'AVP'); - -pack_arity(false, _) -> - 0. + %% packed into a dedicated field defeats that point. + + if Failed == true; + Name == 'Failed-AVP'; + Name == 'answer-message', AvpName == 'Failed-AVP'; + not M; + not Strict -> + avp_arity(Name, 'AVP'); + true -> + 0 + end. %% 3588: %% @@ -679,9 +596,9 @@ pack(L, {_, Max}, F, Avp, {Rec, Failed}) -> %% # grouped_avp/3 %% --------------------------------------------------------------------------- --spec grouped_avp(decode, avp_name(), binary() | {5014, binary()}) +-spec grouped_avp(decode, avp_name(), binary() | {5014, binary()}, term()) -> {avp_record(), [avp()]}; - (encode, avp_name(), avp_record() | avp_values()) + (encode, avp_name(), avp_record() | avp_values(), term()) -> iolist() | no_return(). @@ -689,14 +606,14 @@ pack(L, {_, Max}, F, Avp, {Rec, Failed}) -> %% length in the header was too short (insufficient for the extracted %% header) or too long (past the end of the message). An empty payload %% is sufficient according to the RFC text for 5014. -grouped_avp(decode, _Name, {5014 = RC, _Bin}) -> +grouped_avp(decode, _Name, {5014 = RC, _Bin}, _) -> throw({?TAG, {grouped, {RC, []}, []}}); -grouped_avp(decode, Name, Data) -> - grouped_decode(Name, diameter_codec:collect_avps(Data)); +grouped_avp(decode, Name, Data, Opts) -> + grouped_decode(Name, diameter_codec:collect_avps(Data), Opts); -grouped_avp(encode, Name, Data) -> - encode_avps(Name, Data). +grouped_avp(encode, Name, Data, Opts) -> + encode_avps(Name, Data, Opts). %% grouped_decode/2 %% @@ -705,7 +622,7 @@ grouped_avp(encode, Name, Data) -> %% records. %% Length error in trailing component AVP. -grouped_decode(_Name, {Error, Acc}) -> +grouped_decode(_Name, {Error, Acc}, _) -> {5014, Avp} = Error, throw({?TAG, {grouped, Error, [Avp | Acc]}}); @@ -723,8 +640,8 @@ grouped_decode(_Name, {Error, Acc}) -> %% component, which the catch in d/3 wraps in the Grouped AVP in %% question. A partially decoded record is only used when ignoring %% errors in Failed-AVP. -grouped_decode(Name, ComponentAvps) -> - {Rec, Avps, Es} = decode_avps(Name, ComponentAvps), +grouped_decode(Name, ComponentAvps, Opts) -> + {Rec, Avps, Es} = decode_avps(Name, ComponentAvps, Opts), [] == Es orelse throw({?TAG, {grouped, [{_,_} = hd(Es) | Rec], Avps}}), {Rec, Avps}. @@ -745,7 +662,7 @@ z(Name, {Min, _}) -> z('AVP') -> <<0:64>>; %% minimal header z(Name) -> - Bin = diameter_codec:pack_avp(avp_header(Name), empty_value(Name)), + Bin = diameter_codec:pack_data(avp_header(Name), empty_value(Name)), Sz = iolist_size(Bin), <<0:Sz/unit:8>>. @@ -754,4 +671,4 @@ z(Name) -> %% --------------------------------------------------------------------------- empty(AvpName) -> - avp(encode, zero, AvpName). + avp(encode, zero, AvpName, _Opts = []). diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index 789395abe9..52eb10b8c2 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -21,10 +21,8 @@ -module(diameter_codec). -export([encode/2, - decode/2, decode/3, - setopts/1, - getopt/1, + decode/4, collect_avps/1, decode_header/1, sequence_numbers/1, @@ -33,7 +31,7 @@ msg_id/1]). %% Towards generated encoders (from diameter_gen.hrl). --export([pack_avp/1, +-export([pack_data/2, pack_avp/2]). -include_lib("diameter/include/diameter.hrl"). @@ -65,52 +63,6 @@ %% | AVPs ... %% +-+-+-+-+-+-+-+-+-+-+-+-+- -%%% --------------------------------------------------------------------------- -%%% # setopts/1 -%%% # getopt/1 -%%% --------------------------------------------------------------------------- - -%% These functions are a compromise in the same vein as the use of the -%% process dictionary in diameter_gen.hrl in generated codec modules. -%% Instead of rewriting the entire dictionary generation to pass -%% encode/decode options around, the calling process sets them by -%% calling setopts/1. At current, the only option is whether or not to -%% decode binaries as strings, which is used by diameter_types. - -setopts(Opts) - when is_list(Opts) -> - lists:foreach(fun setopt/1, Opts). - -%% The default string_decode true is for backwards compatibility. -setopt({K, false = B}) - when K == string_decode; - K == strict_mbit -> - setopt(K, B); - -%% Regard anything but the generated RFC 3588 dictionary as modern. -%% This affects the interpretation of defaults during the decode -%% of values of type DiameterURI, this having changed from RFC 3588. -%% (So much for backwards compatibility.) -setopt({common_dictionary, diameter_gen_base_rfc3588}) -> - setopt(rfc, 3588); - -setopt(_) -> - ok. - -setopt(Key, Value) -> - put({diameter, Key}, Value). - -getopt(Key) -> - case get({diameter, Key}) of - undefined when Key == string_decode; - Key == strict_mbit -> - true; - undefined when Key == rfc -> - 6733; - V -> - V - end. - %%% --------------------------------------------------------------------------- %%% # encode/2 %%% --------------------------------------------------------------------------- @@ -121,7 +73,7 @@ getopt(Key) -> encode(Mod, #diameter_packet{} = Pkt) -> try - e(Mod, Pkt) + encode(Mod, _Opts = [], Pkt) catch exit: {Reason, Stack, #diameter_header{} = H} = T -> %% Exit with a header in the reason to let the caller @@ -139,11 +91,14 @@ encode(Mod, Msg) -> Hdr = #diameter_header{version = ?DIAMETER_VERSION, end_to_end_id = Seq, hop_by_hop_id = Seq}, - encode(Mod, #diameter_packet{header = Hdr, - msg = Msg}). + encode(Mod, #diameter_packet{header = Hdr, + msg = Msg}). -e(_, #diameter_packet{msg = [#diameter_header{} = Hdr | As]} = Pkt) -> - try encode_avps(reorder(As)) of +%% encode/3 + +encode(_, Opts, #diameter_packet{msg = [#diameter_header{} = Hdr | As]} + = Pkt) -> + try encode_avps(reorder(As), Opts) of Avps -> Bin = list_to_binary(Avps), Len = 20 + size(Bin), @@ -171,7 +126,7 @@ e(_, #diameter_packet{msg = [#diameter_header{} = Hdr | As]} = Pkt) -> exit({Reason, diameter_lib:get_stacktrace(), Hdr}) end; -e(Mod, #diameter_packet{header = Hdr0, msg = Msg} = Pkt) -> +encode(Mod, Opts, #diameter_packet{header = Hdr0, msg = Msg} = Pkt) -> MsgName = rec2msg(Mod, Msg), {Code, Flags, Aid} = msg_header(Mod, MsgName, Hdr0), @@ -191,7 +146,7 @@ e(Mod, #diameter_packet{header = Hdr0, msg = Msg} = Pkt) -> Values = values(Msg), - try encode_avps(Mod, MsgName, Values) of + try encode_avps(Mod, MsgName, Values, Opts) of Avps -> Bin = list_to_binary(Avps), Len = 20 + size(Bin), @@ -230,7 +185,7 @@ values([H|T]) values(Avps) -> Avps. -%% encode_avps/3 +%% encode_avps/4 %% Specifying values as a #diameter_avp list bypasses arity and other %% checks: the values are expected to be already encoded and the AVP's @@ -238,12 +193,12 @@ values(Avps) -> %% these have to be able to resend whatever comes. %% Message as a list of #diameter_avp{} ... -encode_avps(_, _, [#diameter_avp{} | _] = Avps) -> - encode_avps(reorder(Avps)); +encode_avps(_, _, [#diameter_avp{} | _] = Avps, Opts) -> + encode_avps(reorder(Avps), Opts); %% ... or as a tuple list or record. -encode_avps(Mod, MsgName, Values) -> - Mod:encode_avps(MsgName, Values). +encode_avps(Mod, MsgName, Values, Opts) -> + Mod:encode_avps(MsgName, Values, Opts). %% reorder/1 %% @@ -284,10 +239,10 @@ reorder([H | T], Acc) -> reorder([], _) -> false. -%% encode_avps/1 +%% encode_avps/2 -encode_avps(Avps) -> - lists:map(fun pack_avp/1, Avps). +encode_avps(Avps, Opts) -> + [pack_avp(A, Opts) || A <- Avps]. %% msg_header/3 @@ -312,41 +267,39 @@ rec2msg(Mod, Rec) -> Mod:rec2msg(element(1, Rec)). %%% --------------------------------------------------------------------------- -%%% # decode/2 +%%% # decode/3 %%% --------------------------------------------------------------------------- %% Unsuccessfully decoded AVPs will be placed in #diameter_packet.errors. --spec decode(module() | {module(), module()}, #diameter_packet{} | binary()) +-spec decode(module() | {module(), module()}, + map(), + #diameter_packet{} | binary()) -> #diameter_packet{}. %% An Answer setting the E-bit. The application dictionary is needed -%% for the best-effort decode of Failed-AVP, and the best way to make -%% this available to the AVP decode in diameter_gen.hrl, without -%% having to rewrite the entire codec generation, is to place it in -%% the process dictionary. It's the code in diameter_gen.hrl (that's -%% included by every generated codec module) that looks for the entry. -%% Not ideal, but it solves the problem relatively simply. -decode({Mod, Mod}, Pkt) -> - decode(Mod, Pkt); -decode({Mod, AppMod}, Pkt) -> - Key = {?MODULE, dictionary}, - put(Key, AppMod), - try - decode(Mod, Pkt) - after - erase(Key) - end; +%% for the best-effort decode of Failed-AVP. +decode({Mod, AppMod}, Opts, Pkt) -> + decode(Mod, AppMod, Opts, Pkt); %% Or not: a request, or an answer not setting the E-bit. -decode(Mod, Pkt) -> - decode(Mod:id(), Mod, Pkt). +decode(Mod, Opts, Pkt) -> + decode(Mod, Mod, Opts, Pkt). + +%% decode/4 -%% decode/3 +decode(Id, Mod, Opts, Pkt) + when is_integer(Id) -> + decode(Id, Mod, Mod, Opts, Pkt); + +decode(Mod, AppMod, Opts, Pkt) -> + decode(Mod:id(), Mod, AppMod, Opts, Pkt). + +%% decode/5 %% Relay application: just extract the avp's without any decoding of %% their data since we don't know the application in question. -decode(?APP_ID_RELAY, _, #diameter_packet{} = Pkt) -> +decode(?APP_ID_RELAY, _, _, _, #diameter_packet{} = Pkt) -> case collect_avps(Pkt) of {E, As} -> Pkt#diameter_packet{avps = As, @@ -356,7 +309,7 @@ decode(?APP_ID_RELAY, _, #diameter_packet{} = Pkt) -> end; %% Otherwise decode using the dictionary. -decode(_, Mod, #diameter_packet{header = Hdr} = Pkt) -> +decode(_, Mod, AppMod, Opts, #diameter_packet{header = Hdr} = Pkt) -> #diameter_header{cmd_code = CmdCode, is_request = IsRequest, is_error = IsError} @@ -368,29 +321,33 @@ decode(_, Mod, #diameter_packet{header = Hdr} = Pkt) -> Mod:msg_name(CmdCode, IsRequest) end, - decode_avps(MsgName, Mod, Pkt, collect_avps(Pkt)); + decode_avps(MsgName, Mod, AppMod, Opts, Pkt, collect_avps(Pkt)); -decode(Id, Mod, Bin) +decode(Id, Mod, AppMod, Opts, Bin) when is_binary(Bin) -> - decode(Id, Mod, #diameter_packet{header = decode_header(Bin), bin = Bin}). + decode(Id, Mod, AppMod, Opts, #diameter_packet{header = decode_header(Bin), + bin = Bin}). -%% decode_avps/4 +%% decode_avps/6 -decode_avps(MsgName, Mod, Pkt, {E, Avps}) -> +decode_avps(MsgName, Mod, AppMod, Opts, Pkt, {E, Avps}) -> ?LOG(invalid_avp_length, Pkt#diameter_packet.header), #diameter_packet{errors = Failed} = P - = decode_avps(MsgName, Mod, Pkt, Avps), + = decode_avps(MsgName, Mod, AppMod, Opts, Pkt, Avps), P#diameter_packet{errors = [E | Failed]}; -decode_avps('', _, Pkt, Avps) -> %% unknown message ... +decode_avps('', _, _, _, Pkt, Avps) -> %% unknown message ... ?LOG(unknown_message, Pkt#diameter_packet.header), Pkt#diameter_packet{avps = lists:reverse(Avps), errors = [3001]}; %% DIAMETER_COMMAND_UNSUPPORTED %% msg = undefined identifies this case. -decode_avps(MsgName, Mod, Pkt, Avps) -> %% ... or not - {Rec, As, Errors} = Mod:decode_avps(MsgName, Avps), +decode_avps(MsgName, Mod, AppMod, Opts, Pkt, Avps) -> %% ... or not + {Rec, As, Errors} = Mod:decode_avps(MsgName, + Avps, + Opts#{dictionary => AppMod, + failed_avp => false}), ?LOGC([] /= Errors, decode_errors, Pkt#diameter_packet.header), Pkt#diameter_packet{msg = Rec, errors = Errors, @@ -655,7 +612,7 @@ collect_avps(Code, VendorId, M, P, Len, Pad, Rest, N, Acc) -> %% (2) the last sentence covers this case. %%% --------------------------------------------------------------------------- -%%% # pack_avp/1 +%%% # pack_avp/2 %%% --------------------------------------------------------------------------- %% The normal case here is data as an #diameter_avp{} list or an @@ -665,34 +622,36 @@ collect_avps(Code, VendorId, M, P, Len, Pad, Rest, N, Acc) -> %% Decoded Grouped AVP with decoded components: ignore components %% since they're already encoded in the Grouped AVP. -pack_avp([#diameter_avp{} = Grouped | _Components]) -> - pack_avp(Grouped); +pack_avp([#diameter_avp{} = Grouped | _Components], Opts) -> + pack_avp(Grouped, Opts); %% Grouped AVP whose components need packing. It's intentional that %% this isn't equivalent to [Grouped | Components]: here the %% components need to be encoded before wrapping with the Grouped AVP, %% and the list is flat, nesting being accomplished in the data %% fields. -pack_avp(#diameter_avp{data = [#diameter_avp{} | _] = Components} = Grouped) -> - pack_data(encode_avps(Components), Grouped); +pack_avp(#diameter_avp{data = [#diameter_avp{} | _] = Components} + = Grouped, + Opts) -> + pack_data(Grouped, encode_avps(Components, Opts)); %% Data as a type/value tuple ... -pack_avp(#diameter_avp{data = {Type, Value}} = A) +pack_avp(#diameter_avp{data = {Type, Value}} = A, Opts) when is_atom(Type) -> - pack_data(diameter_types:Type(encode, Value), A); + pack_data(A, diameter_types:Type(encode, Value, Opts)); %% ... with a header in various forms ... -pack_avp(#diameter_avp{data = {T, {Type, Value}}}) -> - pack_avp(T, diameter_types:Type(encode, Value)); +pack_avp(#diameter_avp{data = {T, {Type, Value}}}, Opts) -> + pack_data(T, diameter_types:Type(encode, Value, Opts)); -pack_avp(#diameter_avp{data = {T, Data}}) -> - pack_avp(T, Data); +pack_avp(#diameter_avp{data = {T, Data}}, _) -> + pack_data(T, Data); -pack_avp(#diameter_avp{data = {Dict, Name, Data}}) -> - pack_avp(Dict:avp_header(Name), Dict:avp(encode, Data, Name)); +pack_avp(#diameter_avp{data = {Dict, Name, Data}}, Opts) -> + pack_data(Dict:avp_header(Name), Dict:avp(encode, Data, Name, Opts)); %% ... with a truncated header ... -pack_avp(#diameter_avp{code = undefined, data = B}) +pack_avp(#diameter_avp{code = undefined, data = B}, _) when is_binary(B) -> %% Reset the AVP Length of an AVP Header resulting from a 5014 %% error. The RFC doesn't explicitly say to do this but the @@ -706,54 +665,53 @@ pack_avp(#diameter_avp{code = undefined, data = B}) <>; %% Ignoring errors in Failed-AVP or during a relay encode. -pack_avp(#diameter_avp{data = {5014, Data}} = A) -> - pack_data(Data, A); +pack_avp(#diameter_avp{data = {5014, Data}} = A, _) -> + pack_data(A, Data); -pack_avp(#diameter_avp{data = Data} = A) -> - pack_data(Data, A). +pack_avp(#diameter_avp{data = Data} = A, _) -> + pack_data(A, Data). header_length(<<_:32, 1:1, _/bits>>) -> 12; header_length(_) -> 8. -%% pack_data/2 +%%% --------------------------------------------------------------------------- +%%% # pack_data/2 +%%% --------------------------------------------------------------------------- -pack_data(Data, #diameter_avp{code = Code, - vendor_id = V, - is_mandatory = M, - need_encryption = P}) -> +pack_data(#diameter_avp{code = Code, + vendor_id = V, + is_mandatory = M, + need_encryption = P}, + Data) -> Flags = ?BIT(V /= undefined, 2#10000000) bor ?BIT(M, 2#01000000) bor ?BIT(P, 2#00100000), - pack_avp(Code, Flags, V, Data). - -%%% --------------------------------------------------------------------------- -%%% # pack_avp/2 -%%% --------------------------------------------------------------------------- + pack(Code, Flags, V, Data); -pack_avp({Code, Flags, VendorId}, Data) -> - pack_avp(Code, Flags, VendorId, Data). +pack_data({Code, Flags, VendorId}, Data) -> + pack(Code, Flags, VendorId, Data). -%% pack_avp/4 +%% pack/4 -pack_avp(Code, Flags, VendorId, Data) -> +pack(Code, Flags, VendorId, Data) -> Sz = iolist_size(Data), - pack_avp(Code, Flags, Sz, VendorId, Data, ?PAD(Sz)). + pack(Code, Flags, Sz, VendorId, Data, ?PAD(Sz)). %% Padding is not included in the length field, as mandated by the RFC. -%% pack_avp/6 +%% pack/6 %% %% Prepend the vendor id as required. -pack_avp(Code, Flags, Sz, _Vid, Data, Pad) +pack(Code, Flags, Sz, _Vid, Data, Pad) when 0 == Flags band 2#10000000 -> - pack_avp(Code, Flags, Sz, 0, 0, Data, Pad); + pack(Code, Flags, Sz, 0, 0, Data, Pad); -pack_avp(Code, Flags, Sz, Vid, Data, Pad) -> - pack_avp(Code, Flags, Sz+4, Vid, 1, Data, Pad). +pack(Code, Flags, Sz, Vid, Data, Pad) -> + pack(Code, Flags, Sz+4, Vid, 1, Data, Pad). -%% pack_avp/7 +%% pack/7 -pack_avp(Code, Flags, Sz, VId, V, Data, Pad) -> +pack(Code, Flags, Sz, VId, V, Data, Pad) -> [<>, Data, <<0:Pad/unit:8>>]. diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl index 38781d5c23..1db9b52dfa 100644 --- a/lib/diameter/src/base/diameter_config.erl +++ b/lib/diameter/src/base/diameter_config.erl @@ -535,12 +535,12 @@ stop(SvcName) -> %% restrict applications so that that there's one while the service %% has many. -add(SvcName, Type, Opts) -> +add(SvcName, Type, Opts0) -> %% Ensure acceptable transport options. This won't catch all %% possible errors (faulty callbacks for example) but it catches %% many. diameter_service:merge_service/2 depends on usable %% capabilities for example. - ok = transport_opts(Opts), + Opts = transport_opts(Opts0), Ref = make_ref(), true = diameter_reg:add_new(?TRANSPORT_KEY(Ref)), @@ -560,7 +560,17 @@ add(SvcName, Type, Opts) -> end. transport_opts(Opts) -> - lists:foreach(fun(T) -> opt(T) orelse ?THROW({invalid, T}) end, Opts). + lists:map(fun topt/1, Opts). + +topt(T) -> + case opt(T) of + {value, X} -> + X; + true -> + T; + false -> + ?THROW({invalid, T}) + end. opt({transport_module, M}) -> is_atom(M); @@ -600,8 +610,12 @@ opt({watchdog_timer, Tmo}) -> opt({watchdog_config, L}) -> is_list(L) andalso lists:all(fun wdopt/1, L); -opt({spawn_opt, Opts}) -> - is_list(Opts); +opt({spawn_opt = K, Opts}) -> + if is_list(Opts) -> + {value, {K, spawn_opts(Opts)}}; + true -> + false + end; opt({pool_size, N}) -> is_integer(N) andalso 0 < N; @@ -727,7 +741,7 @@ opt(incoming_maxlen, N) opt(spawn_opt, L) when is_list(L) -> - L; + spawn_opts(L); opt(K, false = B) when K == share_peers; @@ -789,6 +803,9 @@ opt(sequence = K, F) -> opt(K, _) -> ?THROW({value, K}). +spawn_opts(L) -> + [T || T <- L, T /= link, T /= monitor]. + sequence({H,N} = T) when 0 =< N, N =< 32, 0 =< H, 0 == H bsr (32-N) -> T; diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl index 2383d830e6..e0bcc565e7 100644 --- a/lib/diameter/src/base/diameter_peer_fsm.erl +++ b/lib/diameter/src/base/diameter_peer_fsm.erl @@ -128,6 +128,9 @@ %% outgoing DPR; boolean says whether or not %% the request was sent explicitly with %% diameter:call/4. + codec :: #{string_decode := boolean(), + strict_mbit := boolean(), + rfc := 3588 | 6733}, strict :: boolean(), ack = false :: boolean(), length_errors :: exit | handle | discard, @@ -160,10 +163,7 @@ %% # start/3 %% --------------------------------------------------------------------------- --spec start(T, [Opt], {[diameter:service_opt()], - [node()], - module(), - #diameter_service{}}) +-spec start(T, [Opt], {map(), [node()], module(), #diameter_service{}}) -> {reference(), pid()} when T :: {connect|accept, diameter:transport_ref()}, Opt :: diameter:transport_opt(). @@ -222,9 +222,10 @@ i({Ack, WPid, {M, Ref} = T, Opts, {SvcOpts, Nodes, Dict0, Svc}}) -> erlang:monitor(process, WPid), wait(Ack, WPid), diameter_stats:reg(Ref), - diameter_codec:setopts([{common_dictionary, Dict0} | SvcOpts]), - {_,_} = Mask = proplists:get_value(sequence, SvcOpts), - Maxlen = proplists:get_value(incoming_maxlen, SvcOpts, 16#FFFFFF), + + #{sequence := Mask, incoming_maxlen := Maxlen} + = SvcOpts, + {[Cs,Ds], Rest} = proplists:split(Opts, [capabilities_cb, disconnect_cb]), putr(?CB_KEY, {Ref, [F || {_,F} <- Cs]}), putr(?DPR_KEY, [F || {_, F} <- Ds]), @@ -250,7 +251,8 @@ i({Ack, WPid, {M, Ref} = T, Opts, {SvcOpts, Nodes, Dict0, Svc}}) -> service = svc(Svc, Addrs), length_errors = LengthErr, strict = Strictness, - incoming_maxlen = Maxlen}. + incoming_maxlen = Maxlen, + codec = maps:with([string_decode, strict_mbit, rfc], SvcOpts)}. %% 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 @@ -797,14 +799,15 @@ rcv('DPA' = N, = Pkt, #state{dictionary = Dict0, transport = TPid, - dpr = {X, Hid, Eid}}) -> + dpr = {X, Hid, Eid}, + codec = Opts}) -> ?LOG(recv, N), X orelse begin %% Only count DPA in response to a DPR sent by the %% service: explicit DPR is counted in the same way %% as other explicitly sent requests. incr(recv, H, Dict0), - incr_rc(recv, diameter_codec:decode(Dict0, Pkt), Dict0) + incr_rc(recv, diameter_codec:decode(Dict0, Opts, Pkt), Dict0) end, diameter_peer:close(TPid), {stop, N}; @@ -901,11 +904,14 @@ header(Bin) -> %% DWR %% Incoming CER or DPR. handle_request(Name, - #diameter_packet{header = H} = Pkt, - #state{dictionary = Dict0} = S) -> + #diameter_packet{header = H} + = Pkt, + #state{dictionary = Dict0, + codec = Opts} + = S) -> ?LOG(recv, Name), incr(recv, H, Dict0), - send_answer(Name, diameter_codec:decode(Dict0, Pkt), S). + send_answer(Name, diameter_codec:decode(Dict0, Opts, Pkt), S). %% send_answer/3 @@ -955,8 +961,6 @@ build_answer('CER', = Pkt, #state{dictionary = Dict0} = S) -> - diameter_codec:setopts([{string_decode, false}]), - {SupportedApps, RCaps, CEA} = recv_CER(CER, S), [RC, IS] = Dict0:'#get-'(['Result-Code', 'Inband-Security-Id'], CEA), @@ -1157,18 +1161,16 @@ recv_CER(CER, #state{service = Svc, dictionary = Dict}) -> handle_CEA(#diameter_packet{header = H} = Pkt, #state{dictionary = Dict0, - service = #diameter_service{capabilities = LCaps}} + service = #diameter_service{capabilities = LCaps}, + codec = Opts} = S) -> incr(recv, H, Dict0), #diameter_packet{} = DPkt - = diameter_codec:decode(Dict0, Pkt), - - diameter_codec:setopts([{string_decode, false}]), + = diameter_codec:decode(Dict0, Opts, Pkt), RC = result_code(incr_rc(recv, DPkt, Dict0)), - {SApps, IS, RCaps} = recv_CEA(DPkt, S), #diameter_caps{origin_host = {OH, DH}} diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl index d6fe8d7e05..78bc5afdf1 100644 --- a/lib/diameter/src/base/diameter_service.erl +++ b/lib/diameter/src/base/diameter_service.erl @@ -88,12 +88,6 @@ %% outside of the service process. -define(STATE_TABLE, ?MODULE). -%% The default sequence mask. --define(NOMASK, {0,32}). - -%% The default restrict_connections. --define(RESTRICT, nodes). - %% Workaround for dialyzer's lack of understanding of match specs. -type match(T) :: T | '_' | '$1' | '$2'. @@ -113,15 +107,14 @@ local :: {ets:tid(), ets:tid(), ets:tid()}, remote :: {ets:tid(), ets:tid(), ets:tid()}, monitor = false :: false | pid(), %% process to die with - options - :: [{sequence, diameter:sequence()} %% sequence mask - | {share_peers, diameter:remotes()} %% broadcast to - | {use_shared_peers, diameter:remotes()} %% use from - | {restrict_connections, diameter:restriction()} - | {strict_mbit, boolean()} - | {string_decode, boolean()} - | {incoming_maxlen, diameter:message_length()}]}). -%% shared_peers reflects the peers broadcast from remote nodes. + options :: #{sequence := diameter:sequence(), %% sequence mask + share_peers := diameter:remotes(),%% broadcast to + use_shared_peers := diameter:remotes(),%% use from + restrict_connections := diameter:restriction(), + incoming_maxlen := diameter:message_length(), + strict_mbit := boolean(), + string_decode := boolean(), + spawn_opt := list()}}). %% Record representing an RFC 3539 watchdog process implemented by %% diameter_watchdog. @@ -281,7 +274,7 @@ whois(SvcName) -> %% --------------------------------------------------------------------------- -spec pick_peer(SvcName, AppOrAlias, Opts) - -> {{TPid, Caps, App}, Mask, SvcOpts} + -> {{TPid, Caps, App}, SvcOpts} | false %% no selection | {error, no_service} when SvcName :: diameter:service_name(), @@ -293,10 +286,7 @@ whois(SvcName) -> TPid :: pid(), Caps :: #diameter_caps{}, App :: #diameter_app{}, - Mask :: diameter:sequence(), - SvcOpts :: [diameter:service_opt()]. -%% Extract Mask in the returned tuple so that diameter_traffic doesn't -%% need to know about the ordering of SvcOpts used here. + SvcOpts :: map(). pick_peer(SvcName, App, Opts) -> pick(lookup_state(SvcName), App, Opts). @@ -316,7 +306,7 @@ pick(#state{service = #diameter_service{applications = Apps}} pick(_, false = No, _) -> No; -pick(#state{options = [{_, Mask} | SvcOpts]} +pick(#state{options = SvcOpts} = S, #diameter_app{module = ModX, dictionary = Dict} = App0, @@ -325,7 +315,7 @@ pick(#state{options = [{_, Mask} | SvcOpts]} [_,_] = RealmAndHost = diameter_lib:eval([DestF, Dict]), case pick_peer(App, RealmAndHost, Filter, S) of {TPid, Caps} -> - {{TPid, Caps, App}, Mask, SvcOpts}; + {{TPid, Caps, App}, SvcOpts}; false = No -> No end. @@ -693,7 +683,7 @@ cfg_acc({SvcName, #diameter_service{applications = Apps} = Rec, Opts}, local = init_peers(), remote = init_peers(), monitor = mref(get_value(monitor, Opts)), - options = service_options(Opts)}, + options = service_options(lists:keydelete(monitor, 1, Opts))}, {S, Acc}; cfg_acc({_Ref, Type, _Opts} = T, {S, Acc}) @@ -709,24 +699,14 @@ init_peers() -> %% TPid} service_options(Opts) -> - [{sequence, proplists:get_value(sequence, Opts, ?NOMASK)}, - {share_peers, get_value(share_peers, Opts)}, - {use_shared_peers, get_value(use_shared_peers, Opts)}, - {restrict_connections, proplists:get_value(restrict_connections, - Opts, - ?RESTRICT)}, - {spawn_opt, proplists:get_value(spawn_opt, Opts, [])}, - {string_decode, proplists:get_value(string_decode, Opts, true)}, - {incoming_maxlen, proplists:get_value(incoming_maxlen, Opts, 16#FFFFFF)}, - {strict_mbit, proplists:get_value(strict_mbit, Opts, true)}]. -%% The order of options is significant since we match against the list. + maps:from_list(Opts). mref(false = No) -> No; mref(P) -> monitor(process, P). -init_shared(#state{options = [_, _, {_,T} | _], +init_shared(#state{options = #{use_shared_peers := T}, service_name = Svc}) -> notify(T, Svc, {service, self()}). @@ -824,7 +804,8 @@ start(Ref, Type, Opts, State) -> start(Ref, Type, Opts, N, #state{watchdogT = WatchdogT, local = {PeerT, _, _}, - options = SvcOpts, + options = #{string_decode := SD} + = SvcOpts0, service_name = SvcName, service = Svc0}) when Type == connect; @@ -832,21 +813,25 @@ start(Ref, Type, Opts, N, #state{watchdogT = WatchdogT, #diameter_service{applications = Apps} = Svc1 = merge_service(Opts, Svc0), - Svc = binary_caps(Svc1, proplists:get_value(string_decode, SvcOpts, true)), - RecvData = diameter_traffic:make_recvdata([SvcName, - PeerT, - Apps, - SvcOpts]), - T = {{spawn_opts([Opts, SvcOpts]), RecvData}, Opts, SvcOpts, Svc}, + Svc = binary_caps(Svc1, SD), + SvcOpts = merge_options(Opts, SvcOpts0), + RecvData = diameter_traffic:make_recvdata([SvcName, PeerT, Apps, SvcOpts]), + T = {Opts, SvcOpts, RecvData, Svc}, Rec = #watchdog{type = Type, ref = Ref, options = Opts}, + diameter_lib:fold_n(fun(_,A) -> [wd(Type, Ref, T, WatchdogT, Rec) | A] end, [], N). +merge_options(Opts, SvcOpts) -> + Keys = maps:keys(SvcOpts), + Map = maps:from_list([KV || {K,_} = KV <- Opts, lists:member(K, Keys)]), + maps:merge(SvcOpts, Map). + binary_caps(Svc, true) -> Svc; binary_caps(#diameter_service{capabilities = Caps} = Svc, false) -> @@ -861,12 +846,6 @@ wd(Type, Ref, T, WatchdogT, Rec) -> %% record so that each watchdog may get a different record. This %% record is what is passed back into application callbacks. -spawn_opts(Optss) -> - SpawnOpts = get_value(spawn_opt, Optss, []), - [T || T <- SpawnOpts, - T /= link, - T /= monitor]. - start_watchdog(Type, Ref, T) -> {_MRef, Pid} = diameter_watchdog:start({Type, Ref}, T), Pid. @@ -1079,18 +1058,6 @@ keyfind([Key | Rest], Pos, L) -> T end. -%% get_value/3 - -get_value(_, [], Def) -> - Def; -get_value(Key, [L | Rest], Def) -> - case lists:keyfind(Key, 1, L) of - {_,V} -> - V; - _ -> - get_value(Key, Rest, Def) - end. - %% find_outgoing_app/2 find_outgoing_app(Alias, Apps) -> @@ -1388,19 +1355,19 @@ send_event(#diameter_event{service = SvcName} = E) -> %% # share_peer/5 %% --------------------------------------------------------------------------- -share_peer(up, Caps, Apps, TPid, #state{options = [_, {_,T} | _], +share_peer(up, Caps, Apps, TPid, #state{options = #{share_peers := SP}, service_name = Svc}) -> - notify(T, Svc, {peer, TPid, [A || {_,A} <- Apps], Caps}); + notify(SP, Svc, {peer, TPid, [A || {_,A} <- Apps], Caps}); -share_peer(down, _Caps, _Apps, TPid, #state{options = [_, {_,T} | _], +share_peer(down, _Caps, _Apps, TPid, #state{options = #{share_peers := SP}, service_name = Svc}) -> - notify(T, Svc, {peer, TPid}). + notify(SP, Svc, {peer, TPid}). %% --------------------------------------------------------------------------- %% # share_peers/2 %% --------------------------------------------------------------------------- -share_peers(Pid, #state{options = [_, {_,SP} | _], +share_peers(Pid, #state{options = #{share_peers := SP}, local = {PeerT, AppT, _}}) -> is_remote(Pid, SP) andalso ets:foldl(fun(T, N) -> N + sp(Pid, AppT, T) end, @@ -1432,7 +1399,8 @@ is_remote(Pid, T) -> %% # remote_peer_up/4 %% --------------------------------------------------------------------------- -remote_peer_up(TPid, Aliases, Caps, #state{options = [_, _, {_,T} | _]} = S) -> +remote_peer_up(TPid, Aliases, Caps, #state{options = #{use_shared_peers := T}} + = S) -> is_remote(TPid, T) andalso rpu(TPid, Aliases, Caps, S). rpu(TPid, Aliases, Caps, #state{service = Svc, remote = RT}) -> diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index be934a6255..3489602a39 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -73,9 +73,9 @@ service_name :: diameter:service_name(), apps :: [#diameter_app{}], sequence :: diameter:sequence(), - codec :: [{string_decode, boolean()} - | {strict_mbit, boolean()} - | {incoming_maxlen, diameter:message_length()}]}). + codec :: #{string_decode := boolean(), + strict_mbit := boolean(), + incoming_maxlen := diameter:message_length()}}). %% Note that incoming_maxlen is currently handled in diameter_peer_fsm, %% so that any message exceeding the maximum is discarded. Retain the %% option in case we want to extend the values and semantics. @@ -94,15 +94,16 @@ %% --------------------------------------------------------------------------- make_recvdata([SvcName, PeerT, Apps, SvcOpts | _]) -> - {_,_} = Mask = proplists:get_value(sequence, SvcOpts), - #recvdata{service_name = SvcName, - peerT = PeerT, - apps = Apps, - sequence = Mask, - codec = [T || {K,_} = T <- SvcOpts, - lists:member(K, [string_decode, - incoming_maxlen, - strict_mbit])]}. + #{sequence := {_,_} = Mask, spawn_opt := Opts} + = SvcOpts, + {Opts, #recvdata{service_name = SvcName, + peerT = PeerT, + apps = Apps, + sequence = Mask, + codec = maps:with([string_decode, + strict_mbit, + incoming_maxlen], + SvcOpts)}}. %% --------------------------------------------------------------------------- %% peer_up/1 @@ -227,9 +228,9 @@ receive_message(TPid, Route, Pkt, Dict0, RecvData) -> %% recv/6 %% Incoming request ... -recv(true, Ack, TPid, Pkt, Dict0, T) +recv(true, Ack, TPid, Pkt, Dict0, RecvData) when is_boolean(Ack) -> - spawn_request(Ack, TPid, Pkt, Dict0, T); + spawn_request(Ack, TPid, Pkt, Dict0, RecvData); %% ... answer to known request ... recv(false, {Pid, Ref, TPid}, _, Pkt, Dict0, _) -> @@ -269,11 +270,9 @@ recv_request(Ack, = Pkt, Dict0, #recvdata{peerT = PeerT, - apps = Apps, - codec = Opts} + apps = Apps} = RecvData) -> Ack andalso (TPid ! {handler, self()}), - diameter_codec:setopts([{common_dictionary, Dict0} | Opts]), send_A(recv_R(diameter_service:find_incoming_app(PeerT, TPid, Id, Apps), TPid, Pkt, @@ -289,9 +288,10 @@ recv_R({#diameter_app{id = Id, dictionary = AppDict} = App, Caps}, TPid, Pkt0, Dict0, - RecvData) -> + #recvdata{codec = Opts} + = RecvData) -> incr(recv, Pkt0, TPid, AppDict), - Pkt = errors(Id, diameter_codec:decode(Id, AppDict, Pkt0)), + Pkt = errors(Id, diameter_codec:decode(Id, AppDict, Opts, Pkt0)), incr_error(recv, Pkt, TPid, AppDict), {Caps, Pkt, App, recv_R(App, TPid, Dict0, Caps, RecvData, Pkt)}; %% Note that the decode is different depending on whether or not Id is @@ -667,7 +667,7 @@ is_loop(Code, Vid, OH, Dict0, [_ | Avps]) is_loop(Code, Vid, OH, Dict0, Avps); is_loop(Code, Vid, OH, Dict0, Avps) -> - is_loop(Code, Vid, Dict0:avp(encode, OH, 'Route-Record'), Dict0, Avps). + is_loop(Code, Vid, list_to_binary(OH), Dict0, Avps). %% reply/5 @@ -963,14 +963,14 @@ answer_message(OH, OR, RC, Dict0, #diameter_packet{avps = Avps, ['answer-message', {'Origin-Host', OH}, {'Origin-Realm', OR}, {'Result-Code', RC}] - ++ session_id(Code, Vid, Dict0, Avps) + ++ session_id(Code, Vid, Avps) ++ failed_avp(RC, Es). -session_id(Code, Vid, Dict0, Avps) +session_id(Code, Vid, Avps) when is_list(Avps) -> try #diameter_avp{data = Bin} = find_avp(Code, Vid, Avps), - [{'Session-Id', [Dict0:avp(decode, Bin, 'Session-Id')]}] + [{'Session-Id', [Bin]}] catch error: _ -> [] @@ -1251,11 +1251,10 @@ answer_rc(_, _, Sent) -> %% %% In the process spawned for the outgoing request. -send_R(SvcName, AppOrAlias, Msg, Opts, Caller) -> - case pick_peer(SvcName, AppOrAlias, Msg, Opts) of - {Transport, Mask, SvcOpts} -> - diameter_codec:setopts(SvcOpts), - send_request(Transport, Mask, Msg, Opts, Caller, SvcName); +send_R(SvcName, AppOrAlias, Msg, CallOpts, Caller) -> + case pick_peer(SvcName, AppOrAlias, Msg, CallOpts) of + {{_,_,_} = Transport, SvcOpts} -> + send_request(Transport, SvcOpts, Msg, CallOpts, Caller, SvcName); {error, _} = No -> No end. @@ -1302,42 +1301,45 @@ mo(T, _) -> send_request({TPid, Caps, App} = Transport, - Mask, - Msg, - Opts, + #{sequence := Mask} + = SvcOpts, + Msg0, + CallOpts, Caller, SvcName) -> - Pkt = make_prepare_packet(Mask, Msg), - - send_R(cb(App, prepare_request, [Pkt, SvcName, {TPid, Caps}]), - Pkt, - Transport, - Opts, - Caller, - SvcName, - []). + Pkt = make_prepare_packet(Mask, Msg0), + + case prepare(cb(App, prepare_request, [Pkt, SvcName, {TPid, Caps}]), []) of + [Msg | Fs] -> + ReqPkt = make_request_packet(Msg, Pkt), + EncPkt = encode(App#diameter_app.dictionary, TPid, ReqPkt, Fs), + T = send_R(ReqPkt, EncPkt, Transport, CallOpts, Caller, SvcName), + Ans = recv_answer(SvcName, App, CallOpts, T), + handle_answer(SvcName, SvcOpts, App, Ans); + {discard, Reason} -> + {error, Reason}; + discard -> + {error, discarded}; + {error, Reason} -> + ?ERROR({invalid_return, Reason, prepare_request, App}) + end. -%% send_R/7 +%% prepare/2 -send_R({send, Msg}, Pkt, Transport, Opts, Caller, SvcName, Fs) -> - send_R(make_request_packet(Msg, Pkt), - Transport, - Opts, - Caller, - SvcName, - Fs); +prepare({send, Msg}, Fs) -> + [Msg | Fs]; -send_R({discard, Reason} , _, _, _, _, _, _) -> - {error, Reason}; +prepare({eval_packet, RC, F}, Fs) -> + prepare(RC, [F|Fs]); -send_R(discard, _, _, _, _, _, _) -> - {error, discarded}; +prepare({discard, _Reason} = RC, _) -> + RC; -send_R({eval_packet, RC, F}, Pkt, T, Opts, Caller, SvcName, Fs) -> - send_R(RC, Pkt, T, Opts, Caller, SvcName, [F|Fs]); +prepare(discard = RC, _) -> + RC; -send_R(E, _, {_, _, App}, _, _, _, _) -> - ?ERROR({invalid_return, E, prepare_request, App}). +prepare(Reason, _) -> + {error, Reason}. %% make_prepare_packet/2 %% @@ -1445,34 +1447,27 @@ fold_record(Rec, R) -> %% send_R/6 -send_R(Pkt0, - {TPid, Caps, #diameter_app{dictionary = AppDict} = App}, - Opts, +send_R(ReqPkt, + EncPkt, + {TPid, Caps, #diameter_app{dictionary = AppDict}}, + #options{timeout = Timeout}, {Pid, Ref}, - SvcName, - Fs) -> - Pkt = encode(AppDict, TPid, Pkt0, Fs), - - #options{timeout = Timeout} - = Opts, - + SvcName) -> Req = #request{ref = Ref, caller = Pid, handler = self(), transport = TPid, caps = Caps, - packet = Pkt0}, + packet = ReqPkt}, - incr(send, Pkt, TPid, AppDict), - {TRef, MRef} = zend_requezt(TPid, Pkt, Req, SvcName, Timeout), + incr(send, EncPkt, TPid, AppDict), + {TRef, MRef} = zend_requezt(TPid, EncPkt, Req, SvcName, Timeout), Pid ! Ref, %% tell caller a send has been attempted - handle_answer(SvcName, - App, - recv_A(Timeout, SvcName, App, Opts, {TRef, MRef, Req})). + {TRef, MRef, Req}. -%% recv_A/5 +%% recv_answer/4 -recv_A(Timeout, SvcName, App, Opts, {TRef, MRef, #request{ref = Ref} = Req}) -> +recv_answer(SvcName, App, CallOpts, {TRef, MRef, #request{ref = Ref} = Req}) -> %% Matching on TRef below ensures we ignore messages that pertain %% to a previous transport prior to failover. The answer message %% includes the pid of the transport on which it was received, @@ -1483,97 +1478,92 @@ recv_A(Timeout, SvcName, App, Opts, {TRef, MRef, #request{ref = Ref} = Req}) -> {timeout = Reason, TRef, _} -> %% No timely reply {error, Req, Reason}; {'DOWN', MRef, process, _, _} when false /= MRef -> %% local peer_down - failover(SvcName, App, Req, Opts, Timeout); + failover(SvcName, App, Req, CallOpts); {failover, TRef} -> %% local or remote peer_down - failover(SvcName, App, Req, Opts, Timeout) + failover(SvcName, App, Req, CallOpts) end. -%% failover/5 +%% failover/4 -failover(SvcName, App, Req, Opts, Timeout) -> - retransmit(pick_peer(SvcName, App, Req, Opts), - Req, - Opts, - SvcName, - Timeout). +failover(SvcName, App, Req, CallOpts) -> + resend_request(pick_peer(SvcName, App, Req, CallOpts), + Req, + CallOpts, + SvcName). -%% handle_answer/3 +%% handle_answer/4 -handle_answer(SvcName, App, {error, Req, Reason}) -> - handle_error(App, Req, Reason, SvcName); +handle_answer(SvcName, _, App, {error, Req, Reason}) -> + #request{packet = Pkt, + transport = TPid, + caps = Caps} + = Req, + cb(App, handle_error, [Reason, msg(Pkt), SvcName, {TPid, Caps}]); handle_answer(SvcName, - #diameter_app{dictionary = AppDict, - id = Id} + SvcOpts, + #diameter_app{id = Id, + dictionary = AppDict, + options = [{answer_errors, AE} | _]} = App, {answer, Req, Dict0, Pkt}) -> MsgDict = msg_dict(AppDict, Dict0, Pkt), - handle_A(errors(Id, diameter_codec:decode({MsgDict, AppDict}, Pkt)), - SvcName, - MsgDict, - Dict0, - App, - Req). - -%% We don't really need to do a full decode if we're a relay and will -%% just resend with a new hop by hop identifier, but might a proxy -%% want to examine the answer? - -handle_A(Pkt, SvcName, Dict, Dict0, App, #request{transport = TPid} = Req) -> - AppDict = App#diameter_app.dictionary, - - incr(recv, Pkt, TPid, AppDict), - - try - incr_result(recv, Pkt, TPid, {Dict, AppDict, Dict0}) %% count incoming - of - _ -> answer(Pkt, SvcName, App, Req) - catch - exit: {no_result_code, _} -> - %% RFC 6733 requires one of Result-Code or - %% Experimental-Result, but the decode will have detected - %% a missing AVP. If both are optional in the dictionary - %% then this isn't a decode error: just continue on. - answer(Pkt, SvcName, App, Req); - exit: {invalid_error_bit, {_, _, _, Avp}} -> - #diameter_packet{errors = Es} - = Pkt, - E = {5004, Avp}, - answer(Pkt#diameter_packet{errors = [E|Es]}, SvcName, App, Req) - end. - -%% answer/4 - -answer(Pkt, - SvcName, - #diameter_app{module = ModX, - options = [{answer_errors, AE} | _]}, - Req) -> - a(Pkt, SvcName, ModX, AE, Req). - --spec a(_, _, _) -> no_return(). %% silence dialyzer - -a(#diameter_packet{errors = Es} - = Pkt, - SvcName, - ModX, - AE, - #request{transport = TPid, - caps = Caps, - packet = P}) - when [] == Es; - callback == AE -> - cb(ModX, handle_answer, [Pkt, msg(P), SvcName, {TPid, Caps}]); - -a(Pkt, SvcName, _, AE, _) -> - a(Pkt#diameter_packet.header, SvcName, AE). - -a(Hdr, SvcName, report) -> + DecPkt = errors(Id, diameter_codec:decode({MsgDict, AppDict}, + SvcOpts, + Pkt)), + #request{transport = TPid} + = Req, + + incr(recv, DecPkt, TPid, AppDict), + + AnsPkt = try + incr_result(recv, DecPkt, TPid, {MsgDict, AppDict, Dict0}) + of + _ -> DecPkt + catch + exit: {no_result_code, _} -> + %% RFC 6733 requires one of Result-Code or + %% Experimental-Result, but the decode will have + %% detected a missing AVP. If both are optional in + %% the dictionary then this isn't a decode error: + %% just continue on. + DecPkt; + exit: {invalid_error_bit, {_, _, _, Avp}} -> + #diameter_packet{errors = Es} + = DecPkt, + E = {5004, Avp}, + DecPkt#diameter_packet{errors = [E|Es]} + end, + + handle_answer(AnsPkt, SvcName, App, AE, Req). + +%% handle_answer/5 + +handle_answer(#diameter_packet{errors = Es} + = Pkt, + SvcName, + App, + AE, + #request{transport = TPid, + caps = Caps, + packet = P}) + when callback == AE; + [] == Es -> + cb(App, handle_answer, [Pkt, msg(P), SvcName, {TPid, Caps}]); + +handle_answer(#diameter_packet{header = H}, SvcName, _, AE, _) -> + handle_error(H, SvcName, AE). + +%% handle_error/3 + +-spec handle_error(_, _, _) -> no_return(). %% silence dialyzer + +handle_error(Hdr, SvcName, report) -> MFA = {?MODULE, handle_answer, [SvcName, Hdr]}, diameter_lib:warning_report(errors, MFA), - a(Hdr, SvcName, discard); + handle_error(Hdr, SvcName, discard); -a(Hdr, SvcName, discard) -> +handle_error(Hdr, SvcName, discard) -> x({answer_errors, {SvcName, Hdr}}). %% Note that we don't check that the application id in the answer's @@ -1584,16 +1574,16 @@ a(Hdr, SvcName, discard) -> %% timer value is ignored. This means that an answer could be accepted %% from a peer after timeout in the case of failover. -%% retransmit/5 +%% resend_request/4 -retransmit({{_,_,App} = Transport, _, _}, Req, Opts, SvcName, Timeout) -> - try retransmit(Transport, Req, SvcName, Timeout) of - T -> recv_A(Timeout, SvcName, App, Opts, T) +resend_request({{_,_,App} = Transport, _}, Req, CallOpts, SvcName) -> + try retransmit(Transport, Req, SvcName, CallOpts#options.timeout) of + T -> recv_answer(SvcName, App, CallOpts, T) catch ?FAILURE(Reason) -> {error, Req, Reason} end; -retransmit(_, Req, _, _, _) -> %% no alternate peer +resend_request(_, Req, _, _) -> %% no alternate peer {error, Req, failover}. %% pick_peer/4 @@ -1603,8 +1593,8 @@ retransmit(_, Req, _, _, _) -> %% no alternate peer pick_peer(SvcName, App, #request{packet = #diameter_packet{msg = Msg}}, - Opts) -> - pick_peer(SvcName, App, Msg, Opts#options{extra = []}); + CallOpts) -> + pick_peer(SvcName, App, Msg, CallOpts#options{extra = []}); pick_peer(_, _, undefined, _) -> {error, no_connection}; @@ -1613,27 +1603,13 @@ pick_peer(SvcName, AppOrAlias, Msg, #options{filter = Filter, extra = Xtra}) -> - pick(diameter_service:pick_peer(SvcName, - AppOrAlias, - {fun(D) -> get_destination(D, Msg) end, - Filter, - Xtra})). - -pick(false) -> - {error, no_connection}; - -pick(T) -> - T. - -%% handle_error/4 - -handle_error(App, - #request{packet = Pkt, - transport = TPid, - caps = Caps}, - Reason, - SvcName) -> - cb(App, handle_error, [Reason, msg(Pkt), SvcName, {TPid, Caps}]). + X = {fun(D) -> get_destination(D, Msg) end, Filter, Xtra}, + case diameter_service:pick_peer(SvcName, AppOrAlias, X) of + false -> + {error, no_connection}; + T -> + T + end. msg(#diameter_packet{msg = undefined, bin = Bin}) -> Bin; @@ -1745,14 +1721,14 @@ send(Pid, Pkt, Route) -> retransmit({TPid, Caps, App} = Transport, - #request{packet = Pkt0} + #request{packet = ReqPkt} = Req, SvcName, Timeout) -> undefined == get(TPid) %% Don't failover to a peer we've orelse ?THROW(timeout), %% already sent to. - Pkt = make_retransmit_packet(Pkt0), + Pkt = make_retransmit_packet(ReqPkt), retransmit(cb(App, prepare_retransmit, [Pkt, SvcName, {TPid, Caps}]), Transport, @@ -1789,21 +1765,21 @@ retransmit({eval_packet, RC, F}, Transport, Req, SvcName, Timeout, Fs) -> retransmit(T, {_, _, App}, _, _, _, _) -> ?ERROR({invalid_return, T, prepare_retransmit, App}). -resend_request(Pkt0, +resend_request(ReqPkt, {TPid, Caps, #diameter_app{dictionary = AppDict}}, Req0, SvcName, Tmo, Fs) -> - Pkt = encode(AppDict, TPid, Pkt0, Fs), + EncPkt = encode(AppDict, TPid, ReqPkt, Fs), Req = Req0#request{transport = TPid, - packet = Pkt0, + packet = ReqPkt, caps = Caps}, - ?LOG(retransmission, Pkt#diameter_packet.header), - incr(TPid, {msg_id(Pkt, AppDict), send, retransmission}), - {TRef, MRef} = zend_requezt(TPid, Pkt, Req, SvcName, Tmo), + ?LOG(retransmission, EncPkt#diameter_packet.header), + incr(TPid, {msg_id(EncPkt, AppDict), send, retransmission}), + {TRef, MRef} = zend_requezt(TPid, EncPkt, Req, SvcName, Tmo), {TRef, MRef, Req}. %% peer_monitor/2 @@ -1905,7 +1881,7 @@ ungroup(Avp) -> avp_decode(Dict, Name, #diameter_avp{value = undefined, data = Bin} = Avp) -> - try Dict:avp(decode, Bin, Name) of + try Dict:avp(decode, Bin, Name, decode_opts(Dict)) of V -> Avp#diameter_avp{value = V} catch @@ -1916,8 +1892,6 @@ avp_decode(_, _, #diameter_avp{} = Avp) -> Avp. cb(#diameter_app{module = [_|_] = M}, F, A) -> - eval(M, F, A); -cb([_|_] = M, F, A) -> eval(M, F, A). eval([M|X], F, A) -> @@ -1925,3 +1899,10 @@ eval([M|X], F, A) -> choose(true, X, _) -> X; choose(false, _, X) -> X. + +%% Decode options sufficient for AVP extraction. +decode_opts(Dict) -> + #{string_decode => false, + strict_mbit => false, + failed_avp => false, + dictionary => Dict}. diff --git a/lib/diameter/src/base/diameter_types.erl b/lib/diameter/src/base/diameter_types.erl index 95361e8422..17164ec89a 100644 --- a/lib/diameter/src/base/diameter_types.erl +++ b/lib/diameter/src/base/diameter_types.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2015. All Rights Reserved. +%% Copyright Ericsson AB 2010-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -26,32 +26,16 @@ %% %% Basic types. --export(['OctetString'/2, - 'Integer32'/2, - 'Integer64'/2, - 'Unsigned32'/2, - 'Unsigned64'/2, - 'Float32'/2, - 'Float64'/2]). - -%% Derived types. --export(['Address'/2, - 'Time'/2, - 'UTF8String'/2, - 'DiameterIdentity'/2, - 'DiameterURI'/2, - 'IPFilterRule'/2, - 'QoSFilterRule'/2]). - -%% Functions taking the AVP name in question as second parameter. -export(['OctetString'/3, 'Integer32'/3, 'Integer64'/3, 'Unsigned32'/3, 'Unsigned64'/3, 'Float32'/3, - 'Float64'/3, - 'Address'/3, + 'Float64'/3]). + +%% Derived types. +-export(['Address'/3, 'Time'/3, 'UTF8String'/3, 'DiameterIdentity'/3, @@ -89,81 +73,80 @@ %% AVP Data Format is needed, a new version of this RFC must be created. %% -------------------- -'OctetString'(decode, Bin) +'OctetString'(decode, Bin, #{string_decode := true}) when is_binary(Bin) -> - case diameter_codec:getopt(string_decode) of - true -> - binary_to_list(Bin); - false -> - Bin - end; - -'OctetString'(decode, B) -> + binary_to_list(Bin); + +'OctetString'(decode, Bin, _) + when is_binary(Bin) -> + Bin; + +'OctetString'(decode, B, _) -> ?INVALID_LENGTH(B); -'OctetString'(encode, zero) -> +'OctetString'(encode, zero, _) -> <<>>; -'OctetString'(encode, Str) -> +'OctetString'(encode, Str, _) -> iolist_to_binary(Str). %% -------------------- -'Integer32'(decode, <>) -> +'Integer32'(decode, <>, _) -> X; -'Integer32'(decode, B) -> +'Integer32'(decode, B, _) -> ?INVALID_LENGTH(B); -'Integer32'(encode, zero) -> +'Integer32'(encode, zero, _) -> <<0:32/signed>>; -'Integer32'(encode, I) +'Integer32'(encode, I, _) when ?SINT(32,I) -> <>. %% -------------------- -'Integer64'(decode, <>) -> +'Integer64'(decode, <>, _) -> X; -'Integer64'(decode, B) -> +'Integer64'(decode, B, _) -> ?INVALID_LENGTH(B); -'Integer64'(encode, zero) -> +'Integer64'(encode, zero, _) -> <<0:64/signed>>; -'Integer64'(encode, I) +'Integer64'(encode, I, _) when ?SINT(64,I) -> <>. %% -------------------- -'Unsigned32'(decode, <>) -> +'Unsigned32'(decode, <>, _) -> X; -'Unsigned32'(decode, B) -> +'Unsigned32'(decode, B, _) -> ?INVALID_LENGTH(B); -'Unsigned32'(encode, zero) -> +'Unsigned32'(encode, zero, _) -> <<0:32>>; -'Unsigned32'(encode, I) +'Unsigned32'(encode, I, _) when ?UINT(32,I) -> <>. %% -------------------- -'Unsigned64'(decode, <>) -> +'Unsigned64'(decode, <>, _) -> X; -'Unsigned64'(decode, B) -> +'Unsigned64'(decode, B, _) -> ?INVALID_LENGTH(B); -'Unsigned64'(encode, zero) -> +'Unsigned64'(encode, zero, _) -> <<0:64>>; -'Unsigned64'(encode, I) +'Unsigned64'(encode, I, _) when ?UINT(64,I) -> <>. @@ -184,25 +167,25 @@ %% arithmetic is performed on the decoded value. Better to be explicit %% that precision has been lost. -'Float32'(decode, <>) -> +'Float32'(decode, <>, _) -> choose(S, infinity, '-infinity'); -'Float32'(decode, <>) -> +'Float32'(decode, <>, _) -> X; -'Float32'(decode, B) -> +'Float32'(decode, B, _) -> ?INVALID_LENGTH(B); -'Float32'(encode, zero) -> +'Float32'(encode, zero, _) -> <<0.0:32/float>>; -'Float32'(encode, infinity) -> +'Float32'(encode, infinity, _) -> <<0:1, 255:8, 0:23>>; -'Float32'(encode, '-infinity') -> +'Float32'(encode, '-infinity', _) -> <<1:1, 255:8, 0:23>>; -'Float32'(encode, X) +'Float32'(encode, X, _) when is_float(X) -> <>. %% Note that this could also encode infinity/-infinity for large @@ -222,25 +205,25 @@ %% The 64 bit format is entirely analogous to the 32 bit format. -'Float64'(decode, <>) -> +'Float64'(decode, <>, _) -> choose(S, infinity, '-infinity'); -'Float64'(decode, <>) -> +'Float64'(decode, <>, _) -> X; -'Float64'(decode, B) -> +'Float64'(decode, B, _) -> ?INVALID_LENGTH(B); -'Float64'(encode, infinity) -> +'Float64'(encode, infinity, _) -> <<0:1, 2047:11, 0:52>>; -'Float64'(encode, '-infinity') -> +'Float64'(encode, '-infinity', _) -> <<1:1, 2047:11, 0:52>>; -'Float64'(encode, zero) -> +'Float64'(encode, zero, _) -> <<0.0:64/float>>; -'Float64'(encode, X) +'Float64'(encode, X, _) when is_float(X) -> <>. @@ -256,18 +239,18 @@ %% format. %% -------------------- -'Address'(encode, zero) -> +'Address'(encode, zero, _) -> <<0:48>>; -'Address'(decode, <>) +'Address'(decode, <>, _) when 1 == A, 4 == size(B); 2 == A, 16 == size(B) -> list_to_tuple([N || <> <= B]); -'Address'(decode, B) -> +'Address'(decode, B, _) -> ?INVALID_LENGTH(B); -'Address'(encode, T) -> +'Address'(encode, T, _) -> Ns = tuple_to_list(diameter_lib:ipaddr(T)), %% length 4 or 8 A = length(Ns) div 4, %% 1 or 2 B = << <> || N <- Ns >>, @@ -278,36 +261,38 @@ %% A DiameterIdentity is a FQDN as definined in RFC 1035, which is at %% least one character. -'DiameterIdentity'(encode, zero) -> +'DiameterIdentity'(encode, zero, _) -> <<0>>; -'DiameterIdentity'(encode = M, X) -> - <<_,_/binary>> = 'OctetString'(M, X); +'DiameterIdentity'(encode = M, X, Opts) -> + <<_,_/binary>> = 'OctetString'(M, X, Opts); -'DiameterIdentity'(decode = M, <<_,_/binary>> = X) -> - 'OctetString'(M, X); +'DiameterIdentity'(decode = M, <<_,_/binary>> = X, Opts) -> + 'OctetString'(M, X, Opts); -'DiameterIdentity'(decode, X) -> +'DiameterIdentity'(decode, X, _) -> ?INVALID_LENGTH(X). %% -------------------- -'DiameterURI'(decode, Bin) +'DiameterURI'(decode, Bin, Opts) when is_binary(Bin) -> - scan_uri(Bin); + scan_uri(Bin, Opts); -'DiameterURI'(decode, B) -> +'DiameterURI'(decode, B, _) -> ?INVALID_LENGTH(B); %% The minimal DiameterURI is "aaa://x", 7 characters. -'DiameterURI'(encode, zero) -> +'DiameterURI'(encode, zero, _) -> <<0:7/unit:8>>; -'DiameterURI'(encode, #diameter_uri{type = Type, - fqdn = DN, - port = PN, - transport = T, - protocol = P}) +'DiameterURI'(encode, + #diameter_uri{type = Type, + fqdn = DN, + port = PN, + transport = T, + protocol = P}, + _) when (Type == 'aaa' orelse Type == 'aaas'), is_integer(PN), 0 =< PN, @@ -324,48 +309,47 @@ %% defaults, so it's best to be explicit. Interpret defaults on decode %% since there's no choice. -'DiameterURI'(encode, Str) -> +'DiameterURI'(encode, Str, Opts) -> Bin = iolist_to_binary(Str), - #diameter_uri{} = scan_uri(Bin), %% assert + #diameter_uri{} = scan_uri(Bin, Opts), %% assert Bin. %% -------------------- %% This minimal rule is "deny in 0 from 0.0.0.0 to 0.0.0.0", 33 characters. -'IPFilterRule'(encode, zero) -> +'IPFilterRule'(encode, zero, _) -> <<0:33/unit:8>>; -'IPFilterRule'(M, X) -> - 'OctetString'(M, X). +'IPFilterRule'(M, X, Opts) -> + 'OctetString'(M, X, Opts). %% -------------------- %% This minimal rule is the same as for an IPFilterRule. -'QoSFilterRule'(encode, zero) -> +'QoSFilterRule'(encode, zero, _) -> <<0:33/unit:8>>; -'QoSFilterRule'(M, X) -> - 'OctetString'(M, X). +'QoSFilterRule'(M, X, Opts) -> + 'OctetString'(M, X, Opts). %% -------------------- -'UTF8String'(decode, Bin) +'UTF8String'(decode, Bin, #{string_decode := true}) when is_binary(Bin) -> - case diameter_codec:getopt(string_decode) of - true -> - %% assert list return - tl([0|_] = unicode:characters_to_list([0, Bin])); - false -> - <<_/binary>> = unicode:characters_to_binary(Bin) - end; - -'UTF8String'(decode, B) -> + %% assert list return + tl([0|_] = unicode:characters_to_list([0, Bin])); + +'UTF8String'(decode, Bin, _) + when is_binary(Bin) -> + <<_/binary>> = unicode:characters_to_binary(Bin); + +'UTF8String'(decode, B, _) -> ?INVALID_LENGTH(B); -'UTF8String'(encode, zero) -> +'UTF8String'(encode, zero, _) -> <<>>; -'UTF8String'(encode, S) -> +'UTF8String'(encode, S, _) -> <<_/binary>> = unicode:characters_to_binary(S). %% assert binary return %% -------------------- @@ -414,67 +398,23 @@ -define(TIME_MIN, {{1968,1,20},{3,14,8}}). %% TIME_1900 + 1 bsl 31 -define(TIME_MAX, {{2104,2,26},{9,42,24}}). %% TIME_2036 + 1 bsl 31 -'Time'(decode, <>) -> +'Time'(decode, <>, _) -> Offset = msb(1 == Time bsr 31), calendar:gregorian_seconds_to_datetime(Time + Offset); -'Time'(decode, B) -> +'Time'(decode, B, _) -> ?INVALID_LENGTH(B); -'Time'(encode, {{_Y,_M,_D},{_HH,_MM,_SS}} = Datetime) +'Time'(encode, {{_Y,_M,_D},{_HH,_MM,_SS}} = Datetime, _) when ?TIME_MIN =< Datetime, Datetime < ?TIME_MAX -> S = calendar:datetime_to_gregorian_seconds(Datetime), T = S - msb(S < ?TIME_2036), 0 = T bsr 32, %% sanity check <>; -'Time'(encode, zero) -> +'Time'(encode, zero, _) -> <<0:32>>. -%% ------------------------------------------------------------------------- - -'OctetString'(M, _, Data) -> - 'OctetString'(M, Data). - -'Integer32'(M, _, Data) -> - 'Integer32'(M, Data). - -'Integer64'(M, _, Data) -> - 'Integer64'(M, Data). - -'Unsigned32'(M, _, Data) -> - 'Unsigned32'(M, Data). - -'Unsigned64'(M, _, Data) -> - 'Unsigned64'(M, Data). - -'Float32'(M, _, Data) -> - 'Float32'(M, Data). - -'Float64'(M, _, Data) -> - 'Float64'(M, Data). - -'Address'(M, _, Data) -> - 'Address'(M, Data). - -'Time'(M, _, Data) -> - 'Time'(M, Data). - -'UTF8String'(M, _, Data) -> - 'UTF8String'(M, Data). - -'DiameterIdentity'(M, _, Data) -> - 'DiameterIdentity'(M, Data). - -'DiameterURI'(M, _, Data) -> - 'DiameterURI'(M, Data). - -'IPFilterRule'(M, _, Data) -> - 'IPFilterRule'(M, Data). - -'QoSFilterRule'(M, _, Data) -> - 'QoSFilterRule'(M, Data). - %% =========================================================================== %% =========================================================================== @@ -564,7 +504,7 @@ msb(false) -> ?TIME_2036. %% %% aaa-protocol = ( "diameter" / "radius" / "tacacs+" ) -scan_uri(Bin) -> +scan_uri(Bin, Opts) -> RE = "^(aaas?)://" "([-a-zA-Z0-9.]{1,255})" "(:0{0,5}([0-9]{1,5}))?" @@ -583,21 +523,21 @@ scan_uri(Bin) -> RE, [{capture, [1,2,4,6,8], binary}]), Type = to_atom(A), - {PN0, T0} = defaults(diameter_codec:getopt(rfc), Type), + {PN0, T0} = defaults(Opts, Type), PortNr = to_int(PN, PN0), 0 = PortNr bsr 16, %% assert #diameter_uri{type = Type, - fqdn = 'OctetString'(decode, DN), + fqdn = 'OctetString'(decode, DN, Opts), port = PortNr, transport = to_atom(T, T0), protocol = to_atom(P, diameter)}. %% Choose defaults based on the RFC, since 6733 has changed them. -defaults(3588, _) -> +defaults(#{rfc := 3588}, _) -> {3868, sctp}; -defaults(6733, aaa) -> +defaults(#{rfc := 6733}, aaa) -> {3868, tcp}; -defaults(6733, aaas) -> +defaults(#{rfc := 6733}, aaas) -> {5658, tcp}. to_int(<<>>, N) -> diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl index a2eb661870..b827925400 100644 --- a/lib/diameter/src/base/diameter_watchdog.erl +++ b/lib/diameter/src/base/diameter_watchdog.erl @@ -50,10 +50,6 @@ -define(IS_NATURAL(N), (is_integer(N) andalso 0 =< N)). --record(config, - {suspect = 1 :: non_neg_integer(), %% OKAY -> SUSPECT - okay = 3 :: non_neg_integer()}). %% REOPEN -> OKAY - -record(watchdog, {%% PCB - Peer Control Block; see RFC 3539, Appendix A status = initial :: initial | okay | suspect | down | reopen, @@ -70,12 +66,18 @@ | integer() %% monotonic time | undefined, dictionary :: module(), %% common dictionary - receive_data :: term(), - %% term passed into diameter_service with incoming message - sequence :: diameter:sequence(), %% mask - restrict :: {diameter:restriction(), boolean()}, - shutdown = false :: boolean(), - config :: #config{}}). + receive_data :: term(), %% term passed with incoming message + config :: #{sequence := diameter:sequence(), %% mask + restrict_connections := diameter:restriction(), + restrict := boolean(), + suspect := non_neg_integer(), %% OKAY -> SUSPECT + okay := non_neg_integer()}, %% REOPEN -> OKAY + codec :: #{string_decode := false, + strict_mbit := boolean(), + failed_avp := false, + rfc := 3588 | 6733, + incoming_maxlen := diameter:message_length()}, + shutdown = false :: boolean()}). %% --------------------------------------------------------------------------- %% start/2 @@ -85,12 +87,12 @@ %% reason. %% --------------------------------------------------------------------------- --spec start(Type, {RecvData, [Opt], SvcOpts, #diameter_service{}}) +-spec start(Type, {[Opt], SvcOpts, RecvData, #diameter_service{}}) -> {reference(), pid()} when Type :: {connect|accept, diameter:transport_ref()}, - RecvData :: term(), Opt :: diameter:transport_opt(), - SvcOpts :: [diameter:service_opt()]. + SvcOpts :: map(), + RecvData :: term(). start({_,_} = Type, T) -> Ack = make_ref(), @@ -117,22 +119,23 @@ init(T) -> proc_lib:init_ack({ok, self()}), gen_server:enter_loop(?MODULE, [], i(T)). -i({Ack, T, Pid, {RecvData, - Opts, - SvcOpts, +i({Ack, T, Pid, {Opts, + #{restrict_connections := Restrict} + = SvcOpts0, + RecvData, #diameter_service{applications = Apps, capabilities = Caps} = Svc}}) -> monitor(process, Pid), wait(Ack, Pid), + + Dict0 = common_dictionary(Apps), + SvcOpts = SvcOpts0#{rfc => rfc(Dict0)}, putr(restart, {T, Opts, Svc, SvcOpts}), %% save seeing it in trace putr(dwr, dwr(Caps)), %% - {_,_} = Mask = proplists:get_value(sequence, SvcOpts), - Restrict = proplists:get_value(restrict_connections, SvcOpts), Nodes = restrict_nodes(Restrict), - Dict0 = common_dictionary(Apps), - diameter_codec:setopts([{common_dictionary, Dict0}, - {string_decode, false}]), + CodecKeys = [string_decode, strict_mbit, incoming_maxlen, spawn_opt, rfc], + #watchdog{parent = Pid, transport = start(T, Opts, SvcOpts, Nodes, Dict0, Svc), tw = proplists:get_value(watchdog_timer, @@ -140,9 +143,13 @@ i({Ack, T, Pid, {RecvData, ?DEFAULT_TW_INIT), receive_data = RecvData, dictionary = Dict0, - sequence = Mask, - restrict = {Restrict, lists:member(node(), Nodes)}, - config = config(Opts)}. + config = + maps:without(CodecKeys, + config(SvcOpts#{restrict => restrict(Nodes), + suspect => 1, + okay => 3}, + Opts)), + codec = maps:with(CodecKeys, SvcOpts#{string_decode := false})}. wait(Ref, Pid) -> receive @@ -152,22 +159,31 @@ wait(Ref, Pid) -> exit({shutdown, D}) end. -%% config/1 +%% Regard anything but the generated RFC 3588 dictionary as modern. +%% This affects the interpretation of defaults during the decode +%% of values of type DiameterURI, this having changed from RFC 3588. +%% (So much for backwards compatibility.) +rfc(?BASE) -> + 3588; +rfc(_) -> + 6733. + +%% config/2 %% %% Could also configure counts for SUSPECT to DOWN and REOPEN to DOWN, %% but don't. -config(Opts) -> +config(Map, Opts) -> Config = proplists:get_value(watchdog_config, Opts, []), - lists:foldl(fun config/2, #config{}, Config). + lists:foldl(fun cfg/2, Map, Config). -config({suspect, N}, Rec) +cfg({suspect, N}, Map) when ?IS_NATURAL(N) -> - Rec#config{suspect = N}; + Map#{suspect := N}; -config({okay, N}, Rec) +cfg({okay, N}, Map) when ?IS_NATURAL(N) -> - Rec#config{okay = N}. + Map#{okay := N}. %% start/6 @@ -377,8 +393,8 @@ transition({accepted = T, TPid}, #watchdog{transport = TPid, transition({open, TPid, Hosts, _} = Open, #watchdog{transport = TPid, status = initial, - restrict = {_,R}, - config = #config{suspect = OS}} + config = #{restrict := R, + suspect := OS}} = S) -> case okay(role(), Hosts, R) of okay -> @@ -396,8 +412,8 @@ transition({open, TPid, Hosts, _} = Open, transition({open = Key, TPid, _Hosts, T}, #watchdog{transport = TPid, status = down, - config = #config{suspect = OS, - okay = RO}} + config = #{suspect := OS, + okay := RO}} = S) -> case RO of 0 -> %% non-standard: skip REOPEN @@ -430,7 +446,7 @@ transition({'DOWN', _, process, TPid, _Reason}, transition({'DOWN', _, process, TPid, _Reason} = D, #watchdog{transport = TPid, status = T, - restrict = {_,R}} + config = #{restrict := R}} = S0) -> S = S0#watchdog{pending = false, transport = undefined}, @@ -577,7 +593,7 @@ tw({M,F,A}) -> send_watchdog(#watchdog{pending = false, transport = TPid, dictionary = Dict0, - sequence = Mask} + config = #{sequence := Mask}} = S) -> #diameter_packet{bin = Bin} = EPkt = encode(dwr, Dict0, Mask), diameter_traffic:incr(send, EPkt, TPid, Dict0), @@ -601,10 +617,11 @@ incoming(Route, Name, Pkt, S) -> %% rcv/3 rcv('DWR', Pkt, #watchdog{transport = TPid, - dictionary = Dict0} + dictionary = Dict0, + codec = Opts} = S) -> ?LOG(recv, 'DWR'), - DPkt = diameter_codec:decode(Dict0, Pkt), + DPkt = diameter_codec:decode(Dict0, Opts, Pkt), diameter_traffic:incr(recv, DPkt, TPid, Dict0), diameter_traffic:incr_error(recv, DPkt, TPid, Dict0), #diameter_packet{header = H, @@ -623,12 +640,13 @@ rcv('DWR', Pkt, #watchdog{transport = TPid, throw(S); rcv('DWA', Pkt, #watchdog{transport = TPid, - dictionary = Dict0} + dictionary = Dict0, + codec = Opts} = S) -> ?LOG(recv, 'DWA'), diameter_traffic:incr(recv, Pkt, TPid, Dict0), diameter_traffic:incr_rc(recv, - diameter_codec:decode(Dict0, Pkt), + diameter_codec:decode(Dict0, Opts, Pkt), TPid, Dict0), throw(S); @@ -690,12 +708,12 @@ rcv(_, #watchdog{status = okay} = S) -> %% SUSPECT Receive non-DWA Failback() %% SetWatchdog() OKAY -rcv('DWA', #watchdog{status = suspect, config = #config{suspect = OS}} = S) -> +rcv('DWA', #watchdog{status = suspect, config = #{suspect := OS}} = S) -> set_watchdog(S#watchdog{status = okay, num_dwa = OS, pending = false}); -rcv(_, #watchdog{status = suspect, config = #config{suspect = OS}} = S) -> +rcv(_, #watchdog{status = suspect, config = #{suspect := OS}} = S) -> set_watchdog(S#watchdog{status = okay, num_dwa = OS}); @@ -705,8 +723,8 @@ rcv(_, #watchdog{status = suspect, config = #config{suspect = OS}} = S) -> rcv('DWA', #watchdog{status = reopen, num_dwa = N, - config = #config{suspect = OS, - okay = RO}} + config = #{suspect := OS, + okay := RO}} = S) when N+1 == RO -> S#watchdog{status = okay, @@ -837,18 +855,19 @@ restart(S) -> %% reconnect has won race with timeout restart({{connect, _} = T, Opts, Svc, SvcOpts}, #watchdog{parent = Pid, - restrict = {R,_}, + config = #{restrict_connections := R} + = M, dictionary = Dict0} = S) -> send(Pid, {reconnect, self()}), Nodes = restrict_nodes(R), S#watchdog{transport = start(T, Opts, SvcOpts, Nodes, Dict0, Svc), - restrict = {R, lists:member(node(), Nodes)}}; + config = M#{restrict => restrict(Nodes)}}; %% No restriction on the number of connections to the same peer: just %% die. Note that a state machine never enters state REOPEN in this %% case. -restart({{accept, _}, _, _, _}, #watchdog{restrict = {_, false}}) -> +restart({{accept, _}, _, _, _}, #watchdog{config = #{restrict := false}}) -> stop; %% Otherwise hang around until told to die, either by the service or @@ -892,3 +911,8 @@ restrict_nodes(Nodes) restrict_nodes(F) -> diameter_lib:eval(F). + +%% restrict/1 + +restrict(Nodes) -> + lists:member(node(), Nodes). diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl index 9dd3930866..b6fc78b6cc 100644 --- a/lib/diameter/src/compiler/diameter_codegen.erl +++ b/lib/diameter/src/compiler/diameter_codegen.erl @@ -150,8 +150,8 @@ erl_forms(Mod, ParseD) -> {id, 0}, {vendor_id, 0}, {vendor_name, 0}, - {decode_avps, 2}, %% in diameter_gen.hrl - {encode_avps, 2}, %% + {decode_avps, 3}, %% in diameter_gen.hrl + {encode_avps, 3}, %% {msg_name, 2}, {msg_header, 1}, {rec2msg, 1}, @@ -161,8 +161,8 @@ erl_forms(Mod, ParseD) -> {avp_arity, 1}, {avp_arity, 2}, {avp_header, 1}, - {avp, 3}, - {grouped_avp, 3}, + {avp, 4}, + {grouped_avp, 4}, {enumerated_avp, 3}, {empty_value, 1}, {dict, 0}]}, @@ -476,7 +476,7 @@ c_arity(Name, Avp) -> %%% ------------------------------------------------------------------------ f_avp(ParseD) -> - {?function, avp, 3, avp(ParseD) ++ [?BADARG(3)]}. + {?function, avp, 4, avp(ParseD) ++ [?BADARG(4)]}. avp(ParseD) -> Native = get_value(avp_types, ParseD), @@ -515,19 +515,25 @@ avp(Native, Imported, Custom, Enums) -> not_in(List, X) -> not lists:member(X, List). -c_base_avp({AvpName, T}) -> - {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName)], +c_base_avp({AvpName, "Enumerated"}) -> + {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName), ?VAR('_')], [], - [b_base_avp(AvpName, T)]}. + [?CALL(enumerated_avp, [?VAR('T'), ?Atom(AvpName), ?VAR('Data')])]}; -b_base_avp(AvpName, "Enumerated") -> - ?CALL(enumerated_avp, [?VAR('T'), ?Atom(AvpName), ?VAR('Data')]); - -b_base_avp(AvpName, "Grouped") -> - ?CALL(grouped_avp, [?VAR('T'), ?Atom(AvpName), ?VAR('Data')]); +c_base_avp({AvpName, "Grouped"}) -> + {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName), ?VAR('Opts')], + [], + [?CALL(grouped_avp, [?VAR('T'), + ?Atom(AvpName), + ?VAR('Data'), + ?VAR('Opts')])]}; -b_base_avp(_, Type) -> - ?APPLY(diameter_types, ?A(Type), [?VAR('T'), ?VAR('Data')]). +c_base_avp({AvpName, Type}) -> + {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName), ?VAR('Opts')], + [], + [?APPLY(diameter_types, ?A(Type), [?VAR('T'), + ?VAR('Data'), + ?VAR('Opts')])]}. cs_imported_avp({Mod, Avps}, Enums, CustomNames) -> lists:map(fun(A) -> imported_avp(Mod, A, Enums) end, @@ -549,11 +555,12 @@ imported_avp(Mod, {AvpName, _, _, _}, _) -> c_imported_avp(Mod, AvpName). c_imported_avp(Mod, AvpName) -> - {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName)], + {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName), ?VAR('Opts')], [], [?APPLY(Mod, avp, [?VAR('T'), ?VAR('Data'), - ?Atom(AvpName)])]}. + ?Atom(AvpName), + ?VAR('Opts')])]}. cs_custom_avp({Mod, Key, Avps}, Dict) -> lists:map(fun(N) -> c_custom_avp(Mod, Key, N, orddict:fetch(N, Dict)) end, @@ -561,9 +568,11 @@ cs_custom_avp({Mod, Key, Avps}, Dict) -> c_custom_avp(Mod, Key, AvpName, Type) -> {F,A} = custom(Key, AvpName, Type), - {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName)], + {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName), ?VAR('_')], [], - [?APPLY(?A(Mod), ?A(F), [?VAR('T'), ?Atom(A), ?VAR('Data')])]}. + [?APPLY(?A(Mod), ?A(F), [?VAR('T'), + ?Atom(A), + ?VAR('Data')])]}. custom(custom_types, AvpName, Type) -> {AvpName, Type}; @@ -592,7 +601,11 @@ enumerated_avp(Mod, Es, Enums) -> Es). cs_enumerated_avp(true, Mod, Name) -> - [c_imported_avp(Mod, Name)]; + [{?clause, [?VAR('T'), ?Atom(Name), ?VAR('Data')], + [], + [?APPLY(Mod, enumerated_avp, [?VAR('T'), + ?Atom(Name), + ?VAR('Data')])]}]; cs_enumerated_avp(false, _, _) -> []. -- cgit v1.2.3 From f489c0d5f2b5fbb2e40bfacd9981d5515d375e98 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Thu, 27 Apr 2017 23:58:27 +0200 Subject: Don't deconstruct {TPid, Caps} unnecessarily The tuple is returned from and passed to callbacks, so retain the tuple instead of its elements. --- lib/diameter/src/base/diameter_service.erl | 6 ++--- lib/diameter/src/base/diameter_traffic.erl | 41 +++++++++++++----------------- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl index 78bc5afdf1..be50e87179 100644 --- a/lib/diameter/src/base/diameter_service.erl +++ b/lib/diameter/src/base/diameter_service.erl @@ -274,7 +274,7 @@ whois(SvcName) -> %% --------------------------------------------------------------------------- -spec pick_peer(SvcName, AppOrAlias, Opts) - -> {{TPid, Caps, App}, SvcOpts} + -> {{{TPid, Caps}, App}, SvcOpts} | false %% no selection | {error, no_service} when SvcName :: diameter:service_name(), @@ -314,8 +314,8 @@ pick(#state{options = SvcOpts} App = App0#diameter_app{module = ModX ++ Xtra}, [_,_] = RealmAndHost = diameter_lib:eval([DestF, Dict]), case pick_peer(App, RealmAndHost, Filter, S) of - {TPid, Caps} -> - {{TPid, Caps, App}, SvcOpts}; + {_TPid, _Caps} = TC -> + {{TC, App}, SvcOpts}; false = No -> No end. diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index 3489602a39..4a5df722e7 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -85,8 +85,7 @@ {ref :: reference(), %% used to receive answer caller :: pid() | undefined, %% calling process handler :: pid(), %% request process - transport :: pid() | undefined, %% peer process - caps :: #diameter_caps{} | undefined, %% of connection + peer :: undefined | {pid(), #diameter_caps{}}, packet :: #diameter_packet{} | undefined}). %% of request %% --------------------------------------------------------------------------- @@ -1253,7 +1252,7 @@ answer_rc(_, _, Sent) -> send_R(SvcName, AppOrAlias, Msg, CallOpts, Caller) -> case pick_peer(SvcName, AppOrAlias, Msg, CallOpts) of - {{_,_,_} = Transport, SvcOpts} -> + {{_,_} = Transport, SvcOpts} -> send_request(Transport, SvcOpts, Msg, CallOpts, Caller, SvcName); {error, _} = No -> No @@ -1299,7 +1298,7 @@ mo(T, _) -> %% The module field of the #diameter_app{} here includes any extra %% arguments passed to diameter:call/4. -send_request({TPid, Caps, App} +send_request({{TPid, _Caps} = TC, App} = Transport, #{sequence := Mask} = SvcOpts, @@ -1309,7 +1308,7 @@ send_request({TPid, Caps, App} SvcName) -> Pkt = make_prepare_packet(Mask, Msg0), - case prepare(cb(App, prepare_request, [Pkt, SvcName, {TPid, Caps}]), []) of + case prepare(cb(App, prepare_request, [Pkt, SvcName, TC]), []) of [Msg | Fs] -> ReqPkt = make_request_packet(Msg, Pkt), EncPkt = encode(App#diameter_app.dictionary, TPid, ReqPkt, Fs), @@ -1449,15 +1448,14 @@ fold_record(Rec, R) -> send_R(ReqPkt, EncPkt, - {TPid, Caps, #diameter_app{dictionary = AppDict}}, + {{TPid, _Caps} = TC, #diameter_app{dictionary = AppDict}}, #options{timeout = Timeout}, {Pid, Ref}, SvcName) -> Req = #request{ref = Ref, caller = Pid, handler = self(), - transport = TPid, - caps = Caps, + peer = TC, packet = ReqPkt}, incr(send, EncPkt, TPid, AppDict), @@ -1495,10 +1493,9 @@ failover(SvcName, App, Req, CallOpts) -> handle_answer(SvcName, _, App, {error, Req, Reason}) -> #request{packet = Pkt, - transport = TPid, - caps = Caps} + peer = {_TPid, _Caps} = TC} = Req, - cb(App, handle_error, [Reason, msg(Pkt), SvcName, {TPid, Caps}]); + cb(App, handle_error, [Reason, msg(Pkt), SvcName, TC]); handle_answer(SvcName, SvcOpts, @@ -1511,7 +1508,7 @@ handle_answer(SvcName, DecPkt = errors(Id, diameter_codec:decode({MsgDict, AppDict}, SvcOpts, Pkt)), - #request{transport = TPid} + #request{peer = {TPid, _}} = Req, incr(recv, DecPkt, TPid, AppDict), @@ -1544,12 +1541,11 @@ handle_answer(#diameter_packet{errors = Es} SvcName, App, AE, - #request{transport = TPid, - caps = Caps, + #request{peer = {_TPid, _Caps} = TC, packet = P}) when callback == AE; [] == Es -> - cb(App, handle_answer, [Pkt, msg(P), SvcName, {TPid, Caps}]); + cb(App, handle_answer, [Pkt, msg(P), SvcName, TC]); handle_answer(#diameter_packet{header = H}, SvcName, _, AE, _) -> handle_error(H, SvcName, AE). @@ -1576,7 +1572,7 @@ handle_error(Hdr, SvcName, discard) -> %% resend_request/4 -resend_request({{_,_,App} = Transport, _}, Req, CallOpts, SvcName) -> +resend_request({{_, App} = Transport, _}, Req, CallOpts, SvcName) -> try retransmit(Transport, Req, SvcName, CallOpts#options.timeout) of T -> recv_answer(SvcName, App, CallOpts, T) catch @@ -1719,7 +1715,7 @@ send(Pid, Pkt, Route) -> %% retransmit/4 -retransmit({TPid, Caps, App} +retransmit({{TPid, _Caps} = TC, App} = Transport, #request{packet = ReqPkt} = Req, @@ -1730,7 +1726,7 @@ retransmit({TPid, Caps, App} Pkt = make_retransmit_packet(ReqPkt), - retransmit(cb(App, prepare_retransmit, [Pkt, SvcName, {TPid, Caps}]), + retransmit(cb(App, prepare_retransmit, [Pkt, SvcName, TC]), Transport, Req#request{packet = Pkt}, SvcName, @@ -1762,20 +1758,19 @@ retransmit(discard, _, _, _, _, _) -> retransmit({eval_packet, RC, F}, Transport, Req, SvcName, Timeout, Fs) -> retransmit(RC, Transport, Req, SvcName, Timeout, [F|Fs]); -retransmit(T, {_, _, App}, _, _, _, _) -> +retransmit(T, {_, App}, _, _, _, _) -> ?ERROR({invalid_return, T, prepare_retransmit, App}). resend_request(ReqPkt, - {TPid, Caps, #diameter_app{dictionary = AppDict}}, + {{TPid, _Caps} = TC, #diameter_app{dictionary = AppDict}}, Req0, SvcName, Tmo, Fs) -> EncPkt = encode(AppDict, TPid, ReqPkt, Fs), - Req = Req0#request{transport = TPid, - packet = ReqPkt, - caps = Caps}, + Req = Req0#request{peer = TC, + packet = ReqPkt}, ?LOG(retransmission, EncPkt#diameter_packet.header), incr(TPid, {msg_id(EncPkt, AppDict), send, retransmission}), -- cgit v1.2.3 From 80b0a39add59ee0dda0927ffabfef46677b9e65a Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Mon, 24 Apr 2017 18:02:49 +0200 Subject: Don't compute URI defaults unnecessarily --- lib/diameter/src/base/diameter_types.erl | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/lib/diameter/src/base/diameter_types.erl b/lib/diameter/src/base/diameter_types.erl index 17164ec89a..86b674dd48 100644 --- a/lib/diameter/src/base/diameter_types.erl +++ b/lib/diameter/src/base/diameter_types.erl @@ -523,28 +523,30 @@ scan_uri(Bin, Opts) -> RE, [{capture, [1,2,4,6,8], binary}]), Type = to_atom(A), - {PN0, T0} = defaults(Opts, Type), - PortNr = to_int(PN, PN0), - 0 = PortNr bsr 16, %% assert #diameter_uri{type = Type, fqdn = 'OctetString'(decode, DN, Opts), - port = PortNr, - transport = to_atom(T, T0), + port = portnr(PN, Type, Opts), + transport = transport(T, Opts), protocol = to_atom(P, diameter)}. %% Choose defaults based on the RFC, since 6733 has changed them. -defaults(#{rfc := 3588}, _) -> - {3868, sctp}; -defaults(#{rfc := 6733}, aaa) -> - {3868, tcp}; -defaults(#{rfc := 6733}, aaas) -> - {5658, tcp}. - -to_int(<<>>, N) -> - N; -to_int(B, _) -> + +portnr(<<>>, aaa, #{rfc := 6733}) -> + 3868; +portnr(<<>>, aaas, #{rfc := 6733}) -> + 5868; +portnr(<<>>, _, #{rfc := 3588}) -> + 3868; +portnr(B, _, _) -> binary_to_integer(B). +transport(<<>>, #{rfc := 6733}) -> + tcp; +transport(<<>>, #{rfc := 3588}) -> + sctp; +transport(B, _) -> + to_atom(B). + to_atom(<<>>, A) -> A; to_atom(B, _) -> -- cgit v1.2.3 From 2e222053c23054dafcbd9ced1072667a3d7f065a Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Tue, 25 Apr 2017 09:36:25 +0200 Subject: Simplify diameter_caps construction Replace old macro-based implementation with something more readable. --- lib/diameter/src/base/diameter_capx.erl | 42 +++++++++++++-------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/lib/diameter/src/base/diameter_capx.erl b/lib/diameter/src/base/diameter_capx.erl index 30ac847a54..837125339a 100644 --- a/lib/diameter/src/base/diameter_capx.erl +++ b/lib/diameter/src/base/diameter_capx.erl @@ -94,6 +94,9 @@ recv_CER(CER, Svc, Dict) -> recv_CEA(CEA, Svc, Dict) -> try_it([fun rCEA/3, CEA, Svc, Dict]). +-spec make_caps(#diameter_caps{}, [{atom(), term()}]) + -> tried(#diameter_caps{}). + make_caps(Caps, Opts) -> try_it([fun mk_caps/2, Caps, Opts]). @@ -110,31 +113,20 @@ try_it([Fun | Args]) -> %% mk_caps/2 mk_caps(Caps0, Opts) -> - {Caps, _} = lists:foldl(fun set_cap/2, - {Caps0, #diameter_caps{_ = false}}, - Opts), - Caps. - --define(SC(K,F), - set_cap({K, Val}, {Caps, #diameter_caps{F = false} = C}) -> - {Caps#diameter_caps{F = cap(K, copy(Val))}, - C#diameter_caps{F = true}}). - -?SC('Origin-Host', origin_host); -?SC('Origin-Realm', origin_realm); -?SC('Host-IP-Address', host_ip_address); -?SC('Vendor-Id', vendor_id); -?SC('Product-Name', product_name); -?SC('Origin-State-Id', origin_state_id); -?SC('Supported-Vendor-Id', supported_vendor_id); -?SC('Auth-Application-Id', auth_application_id); -?SC('Inband-Security-Id', inband_security_id); -?SC('Acct-Application-Id', acct_application_id); -?SC('Vendor-Specific-Application-Id', vendor_specific_application_id); -?SC('Firmware-Revision', firmware_revision); - -set_cap({Key, _}, _) -> - ?THROW({duplicate, Key}). + Fields = diameter_gen_base_rfc3588:'#info-'(diameter_base_CER, fields), + Defs = lists:zip(Fields, tl(tuple_to_list(Caps0))), + Unset = maps:from_list([{F, true} || F <- lists:droplast(Fields)]), %% no 'AVP' + {Caps, _} = lists:foldl(fun set_cap/2, {Defs, Unset}, Opts), + #diameter_caps{} = list_to_tuple([diameter_caps | [V || {_,V} <- Caps]]). + +set_cap({F,V}, {Caps, Unset}) -> + case Unset of + #{F := true} -> + {lists:keyreplace(F, 1, Caps, {F, cap(F, copy(V))}), + maps:remove(F, Unset)}; + _ -> + ?THROW({duplicate, F}) + end. cap(K, V) when K == 'Origin-Host'; -- cgit v1.2.3 From cfbe969341068623e609fd25c9c5f56b1fdc9458 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Thu, 27 Apr 2017 22:09:59 +0200 Subject: Adapt test suites to modified encode/decode --- lib/diameter/test/diameter_codec_SUITE.erl | 9 ++++++++- .../diameter_test_unknown.erl | 11 +++++++++-- lib/diameter/test/diameter_codec_test.erl | 23 +++++++++++++++------- lib/diameter/test/diameter_compiler_SUITE.erl | 4 ++-- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/lib/diameter/test/diameter_codec_SUITE.erl b/lib/diameter/test/diameter_codec_SUITE.erl index 0666bb9d89..9f08f49f9f 100644 --- a/lib/diameter/test/diameter_codec_SUITE.erl +++ b/lib/diameter/test/diameter_codec_SUITE.erl @@ -285,7 +285,14 @@ recode(Msg) -> recode(Msg, diameter_gen_base_rfc6733). recode(#diameter_packet{} = Pkt, Dict) -> - diameter_codec:decode(Dict, diameter_codec:encode(Dict, Pkt)); + diameter_codec:decode(Dict, opts(Dict), diameter_codec:encode(Dict, Pkt)); recode(Msg, Dict) -> recode(#diameter_packet{msg = Msg}, Dict). + +opts(Mod) -> + #{dictionary => Mod, + string_decode => false, + strict_mbit => true, + rfc => 6733, + failed_avp => false}. diff --git a/lib/diameter/test/diameter_codec_SUITE_data/diameter_test_unknown.erl b/lib/diameter/test/diameter_codec_SUITE_data/diameter_test_unknown.erl index 50cc6e7eef..700910878c 100644 --- a/lib/diameter/test/diameter_codec_SUITE_data/diameter_test_unknown.erl +++ b/lib/diameter/test/diameter_codec_SUITE_data/diameter_test_unknown.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2016. All Rights Reserved. +%% Copyright Ericsson AB 2010-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -59,7 +59,7 @@ enc(M, #diameter_packet{msg = Vs} = P) -> P#diameter_packet{msg = [M|Vs]}). run(M, Pkt) -> - dec(M, diameter_codec:decode(diameter_test_recv, Pkt)). + dec(M, diameter_codec:decode(diameter_test_recv, opts(M), Pkt)). %% Note that the recv dictionary defines neither XXX nor YYY. dec('AR', #diameter_packet @@ -75,3 +75,10 @@ dec('BR', #diameter_packet errors = [{5001, ?MANDATORY_XXX}, {5008, ?NOT_MANDATORY_YYY}]}) -> ok. + +opts(Mod) -> + #{dictionary => Mod, + string_decode => true, + strict_mbit => true, + rfc => 6733, + failed_avp => false}. diff --git a/lib/diameter/test/diameter_codec_test.erl b/lib/diameter/test/diameter_codec_test.erl index e041500b37..ccb97615da 100644 --- a/lib/diameter/test/diameter_codec_test.erl +++ b/lib/diameter/test/diameter_codec_test.erl @@ -94,7 +94,7 @@ base(T) -> %% Ensure that 'zero' values encode only zeros. base(zero = T, F) -> - B = diameter_types:F(encode, T), + B = diameter_types:F(encode, T, opts()), B = z(B); %% Ensure that we can decode what we encode and vice-versa, and that @@ -106,7 +106,7 @@ base(decode, F) -> [] = run([[fun base_invalid/2, F, V] || V <- Is]). base_decode(F, Eq, Value) -> - d(fun(X,V) -> diameter_types:F(X,V) end, Eq, Value). + d(fun(X,V) -> diameter_types:F(X, V, opts()) end, Eq, Value). base_invalid(F, Value) -> try @@ -207,12 +207,21 @@ avp_decode(Mod, Name, Type, Eq, Value) -> d(fun(X,V) -> avp(Mod, X, V, Name, Type) end, Eq, Value). avp(Mod, decode = X, V, Name, 'Grouped') -> - {Rec, _} = Mod:avp(X, V, Name), + {Rec, _} = Mod:avp(X, V, Name, opts(Mod)), Rec; avp(Mod, decode = X, V, Name, _) -> - Mod:avp(X, V, Name); + Mod:avp(X, V, Name, opts(Mod)); avp(Mod, encode = X, V, Name, _) -> - iolist_to_binary(Mod:avp(X, V, Name)). + iolist_to_binary(Mod:avp(X, V, Name, opts(Mod))). + +opts(Mod) -> + (opts())#{dictionary => Mod}. + +opts() -> + #{string_decode => true, + strict_mbit => true, + rfc => 6733, + failed_avp => false}. %% v/1 @@ -259,8 +268,8 @@ arity(M, Name, AvpName, Rec) -> enum(M, Name, {_,E}) -> B = <>, - B = M:avp(encode, E, Name), - E = M:avp(decode, B, Name). + B = M:avp(encode, E, Name, opts(M)), + E = M:avp(decode, B, Name, opts(M)). retag(import_avps) -> avp_types; retag(import_groups) -> grouped; diff --git a/lib/diameter/test/diameter_compiler_SUITE.erl b/lib/diameter/test/diameter_compiler_SUITE.erl index 7a9ac65ae3..76340d65ff 100644 --- a/lib/diameter/test/diameter_compiler_SUITE.erl +++ b/lib/diameter/test/diameter_compiler_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2016. All Rights Reserved. +%% Copyright Ericsson AB 2010-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -552,7 +552,7 @@ flatten2(_Config) -> T <- [encode, decode], M <- [M2, M3], Ref <- [make_ref()], - RC <- [M:avp(T, Ref, A)], + RC <- [M:avp(T, Ref, A, [])], RC /= {T, Ref}]. 'A1'(T, 'Unsigned32', Ref) -> -- cgit v1.2.3 From 4bfccba1943a21b1f723607f5b8e25773e48d98d Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Fri, 28 Apr 2017 08:59:31 +0200 Subject: Avoid recreating records This old construction is approximately two to four times slower from best (no elements modified) to worst (all modified) case, with the new construction having constant speed. --- lib/diameter/src/base/diameter_lib.erl | 31 ------------------------------ lib/diameter/src/base/diameter_traffic.erl | 18 +++++++++++++---- 2 files changed, 14 insertions(+), 35 deletions(-) diff --git a/lib/diameter/src/base/diameter_lib.erl b/lib/diameter/src/base/diameter_lib.erl index 3928769b5e..58b9a29812 100644 --- a/lib/diameter/src/base/diameter_lib.erl +++ b/lib/diameter/src/base/diameter_lib.erl @@ -37,7 +37,6 @@ ipaddr/1, spawn_opts/2, wait/1, - fold_tuple/3, fold_n/3, for_n/2, log/4]). @@ -340,36 +339,6 @@ down(MRef) when is_reference(MRef) -> receive {'DOWN', MRef, process, _, _} = T -> T end. -%% --------------------------------------------------------------------------- -%% # fold_tuple/3 -%% --------------------------------------------------------------------------- - --spec fold_tuple(N, T0, T) - -> tuple() - when N :: pos_integer(), - T0 :: tuple(), - T :: tuple() - | undefined. - -%% Replace fields in T0 by those of T starting at index N, unless the -%% new value is 'undefined'. -%% -%% eg. fold_tuple(2, Hdr, #diameter_header{end_to_end_id = 42}) - -fold_tuple(_, T, undefined) -> - T; - -fold_tuple(N, T0, T1) -> - {_, T} = lists:foldl(fun(V, {I,_} = IT) -> {I+1, ft(V, IT)} end, - {N, T0}, - lists:nthtail(N-1, tuple_to_list(T1))), - T. - -ft(undefined, {_, T}) -> - T; -ft(Value, {Idx, T}) -> - setelement(Idx, T, Value). - %% --------------------------------------------------------------------------- %% # fold_n/3 %% --------------------------------------------------------------------------- diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index 4a5df722e7..72fb92dcf9 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -1438,11 +1438,21 @@ make_retransmit_header(Hdr) -> Hdr#diameter_header{is_retransmitted = true}. %% fold_record/2 +%% +%% Replace elements in the first record by those in the second that +%% differ from undefined. + +fold_record(Rec0, undefined) -> + Rec0; +fold_record(Rec0, Rec) -> + list_to_tuple(fold(tuple_to_list(Rec0), tuple_to_list(Rec))). -fold_record(undefined, R) -> - R; -fold_record(Rec, R) -> - diameter_lib:fold_tuple(2, Rec, R). +fold([], []) -> + []; +fold([H | T0], [undefined | T]) -> + [H | fold(T0, T)]; +fold([_ | T0], [H | T]) -> + [H | fold(T0, T)]. %% send_R/6 -- cgit v1.2.3 From 494659c32e042937b040f2579a46ec69980ded3a Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Fri, 28 Apr 2017 23:18:20 +0200 Subject: Avoid recreating records As in the parent commit, recreating the options record is relatively costly. --- lib/diameter/src/base/diameter_traffic.erl | 39 ++++++++++++++++++------------ 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index 72fb92dcf9..938a6e7af3 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -1261,27 +1261,36 @@ send_R(SvcName, AppOrAlias, Msg, CallOpts, Caller) -> %% make_options/1 make_options(Options) -> - lists:foldl(fun mo/2, #options{}, Options). + make_opts(Options, false, [], none, 5000). -mo({timeout, T}, Rec) - when is_integer(T), 0 =< T -> - Rec#options{timeout = T}; +%% Do our own recursion since this is faster than a lists:foldl/3 +%% setting elements in an #options{} accumulator. -mo({filter, F}, #options{filter = none} = Rec) -> - Rec#options{filter = F}; -mo({filter, F}, #options{filter = {all, Fs}} = Rec) -> - Rec#options{filter = {all, [F | Fs]}}; -mo({filter, F}, #options{filter = F0} = Rec) -> - Rec#options{filter = {all, [F0, F]}}; +make_opts([], Detach, Extra, Filter, Tmo) -> + #options{detach = Detach, + extra = Extra, + filter = Filter, + timeout = Tmo}; -mo({extra, L}, #options{extra = X} = Rec) +make_opts([{timeout, Tmo} | Rest], Detach, Extra, Filter, _) + when is_integer(Tmo), 0 =< Tmo -> + make_opts(Rest, Detach, Extra, Filter, Tmo); + +make_opts([{filter, F} | Rest], Detach, Extra, none, Tmo) -> + make_opts(Rest, Detach, Extra, F, Tmo); +make_opts([{filter, F} | Rest], Detach, Extra, {all, Fs}, Tmo) -> + make_opts(Rest, Detach, Extra, {all, [F|Fs]}, Tmo); +make_opts([{filter, F} | Rest], Detach, Extra, F0, Tmo) -> + make_opts(Rest, Detach, Extra, {all, [F0, F]}, Tmo); + +make_opts([{extra, L} | Rest], Detach, Extra, Filter, Tmo) when is_list(L) -> - Rec#options{extra = X ++ L}; + make_opts(Rest, Detach, Extra ++ L, Filter, Tmo); -mo(detach, Rec) -> - Rec#options{detach = true}; +make_opts([detach | Rest], _, Extra, Filter, Tmo) -> + make_opts(Rest, true, Extra, Filter, Tmo); -mo(T, _) -> +make_opts([T | _], _, _, _, _) -> ?ERROR({invalid_option, T}). %% --------------------------------------------------------------------------- -- cgit v1.2.3 From 931c9f8f366309e4b43ea29552240b350b3ac22c Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 29 Apr 2017 08:42:50 +0200 Subject: Avoid recreating records In the theme of the previous two commits, creating the required diameter_header of diameter_packet record only once. --- lib/diameter/src/base/diameter_traffic.erl | 60 +++++++++++++++--------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index 938a6e7af3..c1bced4e3b 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -54,6 +54,8 @@ -define(RELAY, ?DIAMETER_DICT_RELAY). -define(BASE, ?DIAMETER_DICT_COMMON). %% Note: the RFC 3588 dictionary +-define(DEFAULT(V, Def), if V == undefined -> Def; true -> V end). + %% Table containing outgoing entries that live and die with %% peer_up/down. The name is historic, since the table used to contain %% information about outgoing requests for which an answer has yet to @@ -1367,43 +1369,39 @@ make_prepare_packet(Mask, #diameter_packet{msg = [#diameter_header{} = Hdr make_prepare_packet(Mask, #diameter_packet{header = Hdr} = Pkt) -> Pkt#diameter_packet{header = make_prepare_header(Mask, Hdr)}; +make_prepare_packet(Mask, [#diameter_header{} = Hdr | Avps]) -> + #diameter_packet{msg = [make_prepare_header(Mask, Hdr) | Avps]}; + make_prepare_packet(Mask, Msg) -> - make_prepare_packet(Mask, #diameter_packet{msg = Msg}). + #diameter_packet{header = make_prepare_header(Mask, undefined), + msg = Msg}. %% make_prepare_header/2 make_prepare_header(Mask, undefined) -> Seq = diameter_session:sequence(Mask), - make_prepare_header(#diameter_header{end_to_end_id = Seq, - hop_by_hop_id = Seq}); - -make_prepare_header(Mask, #diameter_header{end_to_end_id = undefined, - hop_by_hop_id = undefined} - = H) -> - Seq = diameter_session:sequence(Mask), - make_prepare_header(H#diameter_header{end_to_end_id = Seq, - hop_by_hop_id = Seq}); - -make_prepare_header(Mask, #diameter_header{end_to_end_id = undefined} = H) -> - Seq = diameter_session:sequence(Mask), - make_prepare_header(H#diameter_header{end_to_end_id = Seq}); - -make_prepare_header(Mask, #diameter_header{hop_by_hop_id = undefined} = H) -> - Seq = diameter_session:sequence(Mask), - make_prepare_header(H#diameter_header{hop_by_hop_id = Seq}); - -make_prepare_header(_, Hdr) -> - make_prepare_header(Hdr). - -%% make_prepare_header/1 - -make_prepare_header(#diameter_header{version = undefined} = Hdr) -> - make_prepare_header(Hdr#diameter_header{version = ?DIAMETER_VERSION}); - -make_prepare_header(#diameter_header{} = Hdr) -> - Hdr; - -make_prepare_header(T) -> + #diameter_header{version = ?DIAMETER_VERSION, + end_to_end_id = Seq, + hop_by_hop_id = Seq}; + +make_prepare_header(Mask, #diameter_header{version = V, + end_to_end_id = EI, + hop_by_hop_id = HI} + = H) + when EI == undefined; + HI == undefined -> + Id = diameter_session:sequence(Mask), + H#diameter_header{version = ?DEFAULT(V, ?DIAMETER_VERSION), + end_to_end_id = ?DEFAULT(EI, Id), + hop_by_hop_id = ?DEFAULT(HI, Id)}; + +make_prepare_header(_, #diameter_header{version = undefined} = H) -> + H#diameter_header{version = ?DIAMETER_VERSION}; + +make_prepare_header(_, #diameter_header{} = H) -> + H; + +make_prepare_header(_, T) -> ?ERROR({invalid_header, T}). %% make_request_packet/2 -- cgit v1.2.3 From 77052371aacf68ad4698f75d5a06f7cb2420a66c Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sat, 29 Apr 2017 09:44:54 +0200 Subject: Avoid recreating records In this case the diameter_packet of an answer message for encode. The record itself could be avoided, but that requires a new interface in diameter_codec, probably for little gain. --- lib/diameter/src/base/diameter_traffic.erl | 54 +++++++++++++++++------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index c1bced4e3b..9ebb027c4f 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -783,31 +783,20 @@ eval_packet(Pkt, Fs) -> %% the errors field has been explicitly set. Unfortunately, the %% default value is the empty list rather than 'undefined' so use the %% atom 'false' for "set nothing". (This is historical and changing -%% the default value would require modules including diameter.hrl to -%% be recompiled.) -make_answer_packet(#diameter_packet{errors = []} - = Pkt, - #diameter_packet{errors = [_|_] = Es} - = ReqPkt) -> - make_answer_packet(Pkt#diameter_packet{errors = Es}, ReqPkt); - -%% A reply message clears the R and T flags and retains the P flag. -%% The E flag will be set at encode. 6.2 of 3588 requires the same P -%% flag on an answer as on the request. A #diameter_packet{} returned -%% from a handle_request callback can circumvent this by setting its -%% own header values. +%% the default value would impact anyone expecting relying on the old +%% default.) make_answer_packet(#diameter_packet{header = Hdr, msg = Msg, errors = Es, transport_data = TD}, - #diameter_packet{header = ReqHdr}) -> - Hdr0 = ReqHdr#diameter_header{version = ?DIAMETER_VERSION, - is_request = false, - is_error = undefined, - is_retransmitted = false}, - #diameter_packet{header = fold_record(Hdr0, Hdr), + #diameter_packet{header = Hdr0, + errors = Es0}) -> + #diameter_packet{header = make_answer_header(Hdr0, Hdr), msg = Msg, - errors = Es, + errors = case Es0 of + [_|_] when [] == Es -> Es0; + _ -> Es + end, transport_data = TD}; %% Binaries and header/avp lists are sent as-is. @@ -820,9 +809,28 @@ make_answer_packet([#diameter_header{} | _] = Msg, #diameter_packet{msg = Msg, transport_data = TD}; -%% Otherwise, preserve transport_data. -make_answer_packet(Msg, #diameter_packet{transport_data = TD} = Pkt) -> - make_answer_packet(#diameter_packet{msg = Msg, transport_data = TD}, Pkt). +%% Otherwise, only reset the header. +make_answer_packet(Msg, #diameter_packet{header = Hdr, + errors = Es, + transport_data = TD}) -> + #diameter_packet{header = make_answer_header(Hdr, undefined), + msg = Msg, + errors = Es, + transport_data = TD}. + +%% make_answer_header/2 + +%% A reply message clears the R and T flags and retains the P flag. +%% The E flag will be set at encode. 6.2 of 3588 requires the same P +%% flag on an answer as on the request. A #diameter_packet{} returned +%% from a handle_request callback can circumvent this by setting its +%% own header values. +make_answer_header(ReqHdr, Hdr) -> + Hdr0 = ReqHdr#diameter_header{version = ?DIAMETER_VERSION, + is_request = false, + is_error = undefined, + is_retransmitted = false}, + fold_record(Hdr0, Hdr). %% Reply as name and tuple list ... set([_|_] = Ans, Avps, _) -> -- cgit v1.2.3 From 5f6d83776c38539e396bae345914be575aceea8d Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sun, 11 Jun 2017 16:47:12 +0200 Subject: Fix/simplify setting of one Failed-AVP When setting the Result-Code/Failed-AVP of an outgoing answer from an errors list either returned from or not discarded by a handle_request callback, more than the AVP paired with the Result-Code in question could be set in Failed-AVP. RFC 6733: 7.5. Failed-AVP AVP The Failed-AVP AVP (AVP Code 279) is of type Grouped and provides debugging information in cases where a request is rejected or not fully processed due to erroneous information in a specific AVP. The value of the Result-Code AVP will provide information on the reason for the Failed-AVP AVP. A Diameter answer message SHOULD contain an instance of the Failed-AVP AVP that corresponds to the error indicated by the Result-Code AVP. For practical purposes, this Failed-AVP would typically refer to the first AVP processing error that a Diameter node encounters. --- lib/diameter/src/base/diameter_traffic.erl | 143 ++++++++++++++--------------- 1 file changed, 67 insertions(+), 76 deletions(-) diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index 9ebb027c4f..a9e7955912 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -695,31 +695,14 @@ local([Msg], TPid, DictT, Fs, ReqPkt) local(Msg, TPid, {MsgDict, AppDict, Dict0}, Fs, ReqPkt) -> Pkt = encode({MsgDict, AppDict}, TPid, - reset(make_answer_packet(Msg, ReqPkt), MsgDict, Dict0), + make_answer_packet(Msg, ReqPkt, MsgDict, Dict0), Fs), {MsgDict, Pkt}. -%% reset/3 - -%% Header/avps list: send as is. -reset(#diameter_packet{msg = [#diameter_header{} | _]} = Pkt, _, _) -> - Pkt; - -%% No errors to set or errors explicitly ignored. -reset(#diameter_packet{errors = Es} = Pkt, _, _) - when Es == []; - Es == false -> - Pkt; - -%% Otherwise possibly set Result-Code and/or Failed-AVP. -reset(#diameter_packet{msg = Msg, errors = Es} = Pkt, Dict, Dict0) -> - {RC, Failed} = select_error(Msg, Es, Dict0), - Pkt#diameter_packet{msg = reset(Msg, Dict, RC, Failed)}. - %% select_error/3 %% %% Extract the first appropriate RC or {RC, #diameter_avp{}} -%% pair from an errors list, and accumulate all #diameter_avp{}. +%% pair from an errors list. %% %% RFC 6733: %% @@ -734,50 +717,34 @@ reset(#diameter_packet{msg = Msg, errors = Es} = Pkt, Dict, Dict0) -> %% indicated by the Result-Code AVP. For practical purposes, this %% Failed-AVP would typically refer to the first AVP processing error %% that a Diameter node encounters. +%% +%% 3xxx can only be set in an answer setting the E-bit. RFC 6733 also +%% allows 5xxx, RFC 3588 doesn't. -select_error(Msg, Es, Dict0) -> - {RC, Avps} = lists:foldl(fun(T,A) -> select(T, A, Dict0) end, - {is_answer_message(Msg, Dict0), []}, - Es), - {RC, lists:reverse(Avps)}. - -%% Only integer() and {integer(), #diameter_avp{}} are the result of -%% decode. #diameter_avp{} can only be set in a reply for encode. - -select(#diameter_avp{} = A, {RC, As}, _) -> - {RC, [A|As]}; - -select(_, {RC, _} = Acc, _) - when is_integer(RC) -> - Acc; +select_error(E, [{RC, _} = T | Es], Dict0) -> + select(E, RC, T, Es, Dict0); -select({RC, #diameter_avp{} = A}, {IsAns, As} = Acc, Dict0) - when is_integer(RC) -> - case is_result(RC, IsAns, Dict0) of - true -> {RC, [A|As]}; - false -> Acc - end; +select_error(E, [RC | Es], Dict0) -> + select(E, RC, RC, Es, Dict0); -select(RC, {IsAns, As} = Acc, Dict0) - when is_boolean(IsAns), is_integer(RC) -> - case is_result(RC, IsAns, Dict0) of - true -> {RC, As}; - false -> Acc - end. +select_error(_, [] = No, _) -> + No. -%% reset/4 +select(E, RC, T, _, Dict0) + when E, 3000 =< RC, RC < 4000; %% E-bit with 3xxx + E, ?BASE /= Dict0, 5000 =< RC, RC < 6000; %% E-bit with 5xxx + not E, RC < 3000 orelse 4000 =< RC -> %% no E-bit + [T]; -reset(Msg, Dict, RC, Avps) -> - FailedAVP = failed_avp(Msg, Avps, Dict), - ResultCode = rc(Msg, RC, Dict), - set(set(Msg, FailedAVP, Dict), ResultCode, Dict). +select(E, _, _, Es, Dict0) -> + select_error(E, Es, Dict0). %% eval_packet/2 eval_packet(Pkt, Fs) -> lists:foreach(fun(F) -> diameter_lib:eval([F,Pkt]) end, Fs). -%% make_answer_packet/2 +%% make_answer_packet/4 %% Use decode errors to set Result-Code and/or Failed-AVP unless the %% the errors field has been explicitly set. Unfortunately, the @@ -785,37 +752,39 @@ eval_packet(Pkt, Fs) -> %% atom 'false' for "set nothing". (This is historical and changing %% the default value would impact anyone expecting relying on the old %% default.) + make_answer_packet(#diameter_packet{header = Hdr, msg = Msg, errors = Es, transport_data = TD}, #diameter_packet{header = Hdr0, - errors = Es0}) -> + errors = Es0}, + MsgDict, + Dict0) -> #diameter_packet{header = make_answer_header(Hdr0, Hdr), - msg = Msg, - errors = case Es0 of - [_|_] when [] == Es -> Es0; - _ -> Es - end, + msg = reset(Msg, Es0, Es, MsgDict, Dict0), transport_data = TD}; %% Binaries and header/avp lists are sent as-is. -make_answer_packet(Bin, #diameter_packet{transport_data = TD}) +make_answer_packet(Bin, #diameter_packet{transport_data = TD}, _, _) when is_binary(Bin) -> #diameter_packet{bin = Bin, transport_data = TD}; make_answer_packet([#diameter_header{} | _] = Msg, - #diameter_packet{transport_data = TD}) -> + #diameter_packet{transport_data = TD}, + _, + _) -> #diameter_packet{msg = Msg, transport_data = TD}; -%% Otherwise, only reset the header. -make_answer_packet(Msg, #diameter_packet{header = Hdr, - errors = Es, - transport_data = TD}) -> +make_answer_packet(Msg, + #diameter_packet{header = Hdr, + errors = Es, + transport_data = TD}, + MsgDict, + Dict0) -> #diameter_packet{header = make_answer_header(Hdr, undefined), - msg = Msg, - errors = Es, + msg = reset(Msg, [], Es, MsgDict, Dict0), transport_data = TD}. %% make_answer_header/2 @@ -832,6 +801,35 @@ make_answer_header(ReqHdr, Hdr) -> is_retransmitted = false}, fold_record(Hdr0, Hdr). +%% reset/5 + +reset(Msg, [_|_] = Es0, [] = Es, MsgDict, Dict0) -> + reset(Msg, Es, Es0, MsgDict, Dict0); + +reset(Msg, _, Es, _, _) + when Es == false; + Es == [] -> + Msg; + +reset(Msg, _, Es, MsgDict, Dict0) -> + E = is_answer_message(Msg, Dict0), + reset(Msg, select_error(E, Es, Dict0), choose(E, Dict0, MsgDict)). + +%% reset/4 +%% +%% Set Result-Code and/or Failed-AVP (maybe). + +reset(Msg, [], _) -> + Msg; + +reset(Msg, [{RC, Avp}], Dict) -> + set(Msg, rc(Msg, RC, Dict) ++ failed_avp(Msg, [Avp], Dict), Dict); + +reset(Msg, [RC], Dict) -> + set(Msg, rc(Msg, RC, Dict), Dict). + +%% set/3 + %% Reply as name and tuple list ... set([_|_] = Ans, Avps, _) -> Ans ++ Avps; %% Values nearer tail take precedence. @@ -844,11 +842,7 @@ set(Rec, Avps, Dict) -> %% %% Turn the result code into a list if its optional and only set it if %% the arity is 1 or {0,1}. In other cases (which probably shouldn't -%% exist in practise) we can't know what's appropriate. - -rc(_, B, _) - when is_boolean(B) -> - []; +%% exist in practice) we can't know what's appropriate. rc([MsgName | _], RC, Dict) -> K = 'Result-Code', @@ -863,11 +857,8 @@ rc(Rec, RC, Dict) -> %% failed_avp/3 -failed_avp(_, [] = No, _) -> - No; - -failed_avp(Rec, Avps, Dict) -> - [failed(Rec, [{'AVP', Avps}], Dict)]. +failed_avp(Msg, [_|_] = Avps, Dict) -> + [failed(Msg, [{'AVP', Avps}], Dict)]. %% Reply as name and tuple list ... failed([MsgName | Values], FailedAvp, Dict) -> -- cgit v1.2.3 From 9d08b9d8d9d500259eeb808af19f9cf3d8d79fdf Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sun, 30 Apr 2017 19:21:38 +0200 Subject: Restore undocumented Failed-AVP setting convenience The parent commit removed the convenience of setting something like the following in the errors field of the diameter_packet of an answer message. [#diameter_avp{} = A2, {5001, #diameter_avp{} = A1}] This results in Result-Code = 5001 and Failed-AVP = [A1,A2], but is currently undocumented. Probably useful, so restore it. Also accept {RC, [#diameter_avp{}]} at encode, which is probably more useful; eg. [{5001, [A || {5001, A} <- Errors]}] Anyone who wants full control can set errors = false and formulate Result-Code/Failed-AVP themselves. (As opposed to not setting a value explicitly, which results in setting from the decoded errors list. A bit quirky, but documented and historical.) --- lib/diameter/src/base/diameter_traffic.erl | 55 +++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index a9e7955912..cbccf5d006 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -702,7 +702,7 @@ local(Msg, TPid, {MsgDict, AppDict, Dict0}, Fs, ReqPkt) -> %% select_error/3 %% %% Extract the first appropriate RC or {RC, #diameter_avp{}} -%% pair from an errors list. +%% pair from an errors list, along with any leading #diameter_avp{}. %% %% RFC 6733: %% @@ -721,23 +721,33 @@ local(Msg, TPid, {MsgDict, AppDict, Dict0}, Fs, ReqPkt) -> %% 3xxx can only be set in an answer setting the E-bit. RFC 6733 also %% allows 5xxx, RFC 3588 doesn't. -select_error(E, [{RC, _} = T | Es], Dict0) -> - select(E, RC, T, Es, Dict0); +select_error(E, Es, Dict0) -> + select(E, Es, Dict0, []). -select_error(E, [RC | Es], Dict0) -> - select(E, RC, RC, Es, Dict0); +%% select/4 -select_error(_, [] = No, _) -> - No. +select(E, [{RC, _} = T | Es], Dict0, Avps) -> + select(E, RC, T, Es, Dict0, Avps); + +select(E, [#diameter_avp{} = A | Es], Dict0, Avps) -> + select(E, Es, Dict0, [A | Avps]); + +select(E, [RC | Es], Dict0, Avps) -> + select(E, RC, RC, Es, Dict0, Avps); + +select(_, [], _, Avps) -> + Avps. + +%% select/6 -select(E, RC, T, _, Dict0) +select(E, RC, T, _, Dict0, Avps) when E, 3000 =< RC, RC < 4000; %% E-bit with 3xxx E, ?BASE /= Dict0, 5000 =< RC, RC < 6000; %% E-bit with 5xxx not E, RC < 3000 orelse 4000 =< RC -> %% no E-bit - [T]; + [T | Avps]; -select(E, _, _, Es, Dict0) -> - select_error(E, Es, Dict0). +select(E, _, _, Es, Dict0, Avps) -> + select(E, Es, Dict0, Avps). %% eval_packet/2 @@ -817,16 +827,26 @@ reset(Msg, _, Es, MsgDict, Dict0) -> %% reset/4 %% -%% Set Result-Code and/or Failed-AVP (maybe). +%% Set Result-Code and/or Failed-AVP (maybe). Only RC and {RC, AVP} +%% are the result of decode. AVP or {RC, [AVP]} can be set in an +%% answer for encode, as a convenience for injecting additional AVPs +%% into Failed-AVP; eg. 5001 = DIAMETER_AVP_UNSUPPORTED. reset(Msg, [], _) -> Msg; -reset(Msg, [{RC, Avp}], Dict) -> - set(Msg, rc(Msg, RC, Dict) ++ failed_avp(Msg, [Avp], Dict), Dict); +reset(Msg, [{RC, As} | Avps], Dict) + when is_list(As) -> + reset(Msg, [RC | As ++ Avps], Dict); -reset(Msg, [RC], Dict) -> - set(Msg, rc(Msg, RC, Dict), Dict). +reset(Msg, [{RC, Avp} | Avps], Dict) -> + reset(Msg, [RC, Avp | Avps], Dict); + +reset(Msg, [#diameter_avp{} | _] = Avps, Dict) -> + set(Msg, failed_avp(Msg, Avps, Dict), Dict); + +reset(Msg, [RC | Avps], Dict) -> + set(Msg, rc(Msg, RC, Dict) ++ failed_avp(Msg, Avps, Dict), Dict). %% set/3 @@ -857,6 +877,9 @@ rc(Rec, RC, Dict) -> %% failed_avp/3 +failed_avp(_, [] = No, _) -> + No; + failed_avp(Msg, [_|_] = Avps, Dict) -> [failed(Msg, [{'AVP', Avps}], Dict)]. -- cgit v1.2.3 From 84dbccad048374e3eb1ec7372fd177eba022d108 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sun, 30 Apr 2017 10:30:59 +0200 Subject: Add diameter_codec option ordered_encode To allow list-valued messaged to be encoded in the specified order, instead of in the dictionary order by first converting the list to a record. This is not yet exposed in configuration. --- lib/diameter/include/diameter_gen.hrl | 24 ++-- lib/diameter/src/base/diameter_codec.erl | 30 +++-- lib/diameter/src/base/diameter_peer_fsm.erl | 37 +++--- lib/diameter/src/base/diameter_traffic.erl | 172 ++++++++++++++-------------- lib/diameter/src/base/diameter_watchdog.erl | 30 +++-- 5 files changed, 162 insertions(+), 131 deletions(-) diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index 1683d93557..83f87ea4c4 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -43,35 +43,39 @@ %% # encode_avps/3 %% --------------------------------------------------------------------------- --spec encode_avps(parent_name(), parent_record() | avp_values(), term()) +-spec encode_avps(parent_name(), parent_record() | avp_values(), map()) -> iolist() | no_return(). -encode_avps(Name, Vals, Opts) - when is_list(Vals) -> - encode_avps(Name, '#set-'(Vals, newrec(Name)), Opts); - -encode_avps(Name, Rec, Opts) -> +encode_avps(Name, Vals, Opts) -> try - encode(Name, Rec, Opts) + encode(Name, Vals, Opts) catch throw: {?MODULE, Reason} -> diameter_lib:log({encode, error}, ?MODULE, ?LINE, - {Reason, Name, Rec}), + {Reason, Name, Vals}), erlang:error(list_to_tuple(Reason ++ [Name])); error: Reason -> Stack = erlang:get_stacktrace(), diameter_lib:log({encode, failure}, ?MODULE, ?LINE, - {Reason, Name, Rec, Stack}), + {Reason, Name, Vals, Stack}), erlang:error({encode_failure, Reason, Name, Stack}) end. %% encode/3 +encode(Name, Vals, #{ordered_encode := false} = Opts) + when is_list(Vals) -> + lists:map(fun({F,V}) -> encode(Name, F, V, Opts) end, Vals); + +encode(Name, Vals, Opts) + when is_list(Vals) -> + encode(Name, '#set-'(Vals, newrec(Name)), Opts); + encode(Name, Rec, Opts) -> [encode(Name, F, V, Opts) || {F,V} <- '#get-'(Rec)]. @@ -671,4 +675,4 @@ z(Name) -> %% --------------------------------------------------------------------------- empty(AvpName) -> - avp(encode, zero, AvpName, _Opts = []). + avp(encode, zero, AvpName, _Opts = #{}). diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index 52eb10b8c2..a4d816db4e 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -21,6 +21,7 @@ -module(diameter_codec). -export([encode/2, + encode/3, decode/3, decode/4, collect_avps/1, @@ -67,13 +68,22 @@ %%% # encode/2 %%% --------------------------------------------------------------------------- --spec encode(module(), Msg :: term()) +encode(Mod, Msg) -> + encode(Mod, #{ordered_encode => true}, Msg). + +%%% --------------------------------------------------------------------------- +%%% # encode/3 +%%% --------------------------------------------------------------------------- + +-spec encode(module(), + map(), + Msg :: term()) -> #diameter_packet{} | no_return(). -encode(Mod, #diameter_packet{} = Pkt) -> +encode(Mod, Opts, #diameter_packet{} = Pkt) -> try - encode(Mod, _Opts = [], Pkt) + enc(Mod, Opts, Pkt) catch exit: {Reason, Stack, #diameter_header{} = H} = T -> %% Exit with a header in the reason to let the caller @@ -86,18 +96,18 @@ encode(Mod, #diameter_packet{} = Pkt) -> exit({?MODULE, encode, T}) end; -encode(Mod, Msg) -> +encode(Mod, Opts, Msg) -> Seq = diameter_session:sequence(), Hdr = #diameter_header{version = ?DIAMETER_VERSION, end_to_end_id = Seq, hop_by_hop_id = Seq}, - encode(Mod, #diameter_packet{header = Hdr, - msg = Msg}). + encode(Mod, Opts, #diameter_packet{header = Hdr, + msg = Msg}). -%% encode/3 +%% enc/3 -encode(_, Opts, #diameter_packet{msg = [#diameter_header{} = Hdr | As]} - = Pkt) -> +enc(_, Opts, #diameter_packet{msg = [#diameter_header{} = Hdr | As]} + = Pkt) -> try encode_avps(reorder(As), Opts) of Avps -> Bin = list_to_binary(Avps), @@ -126,7 +136,7 @@ encode(_, Opts, #diameter_packet{msg = [#diameter_header{} = Hdr | As]} exit({Reason, diameter_lib:get_stacktrace(), Hdr}) end; -encode(Mod, Opts, #diameter_packet{header = Hdr0, msg = Msg} = Pkt) -> +enc(Mod, Opts, #diameter_packet{header = Hdr0, msg = Msg} = Pkt) -> MsgName = rec2msg(Mod, Msg), {Code, Flags, Aid} = msg_header(Mod, MsgName, Hdr0), diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl index e0bcc565e7..a5dab45684 100644 --- a/lib/diameter/src/base/diameter_peer_fsm.erl +++ b/lib/diameter/src/base/diameter_peer_fsm.erl @@ -130,7 +130,8 @@ %% diameter:call/4. codec :: #{string_decode := boolean(), strict_mbit := boolean(), - rfc := 3588 | 6733}, + rfc := 3588 | 6733, + ordered_encode := false}, strict :: boolean(), ack = false :: boolean(), length_errors :: exit | handle | discard, @@ -252,7 +253,11 @@ i({Ack, WPid, {M, Ref} = T, Opts, {SvcOpts, Nodes, Dict0, Svc}}) -> length_errors = LengthErr, strict = Strictness, incoming_maxlen = Maxlen, - codec = maps:with([string_decode, strict_mbit, rfc], SvcOpts)}. + codec = maps:with([string_decode, + strict_mbit, + rfc, + ordered_encode], + SvcOpts#{ordered_encode => false})}. %% 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 @@ -592,7 +597,8 @@ send_CER(#state{state = {'Wait-Conn-Ack', Tmo}, mode = {connect, Remote}, service = #diameter_service{capabilities = LCaps}, transport = TPid, - dictionary = Dict} + dictionary = Dict, + codec = Opts} = S) -> OH = LCaps#diameter_caps.origin_host, req_send_CER(OH, Remote) @@ -602,7 +608,7 @@ send_CER(#state{state = {'Wait-Conn-Ack', Tmo}, #diameter_packet{header = #diameter_header{end_to_end_id = Eid, hop_by_hop_id = Hid}} = Pkt - = encode(CER, Dict), + = encode(CER, Opts, Dict), incr(send, Pkt, Dict), send(TPid, Pkt), ?LOG(send, 'CER'), @@ -631,15 +637,15 @@ build_CER(#state{service = #diameter_service{capabilities = LCaps}, {ok, CER} = diameter_capx:build_CER(LCaps, Dict), CER. -%% encode/2 +%% encode/3 -encode(Rec, Dict) -> +encode(Rec, Opts, Dict) -> Seq = diameter_session:sequence({_,_} = getr(?SEQUENCE_KEY)), Hdr = #diameter_header{version = ?DIAMETER_VERSION, end_to_end_id = Seq, hop_by_hop_id = Seq}, - diameter_codec:encode(Dict, #diameter_packet{header = Hdr, - msg = Rec}). + diameter_codec:encode(Dict, Opts, #diameter_packet{header = Hdr, + msg = Rec}). %% incoming/2 @@ -915,7 +921,10 @@ handle_request(Name, %% send_answer/3 -send_answer(Type, ReqPkt, #state{transport = TPid, dictionary = Dict} = S) -> +send_answer(Type, ReqPkt, #state{transport = TPid, + dictionary = Dict, + codec = Opts} + = S) -> incr_error(recv, ReqPkt, Dict), #diameter_packet{header = H, @@ -934,7 +943,7 @@ send_answer(Type, ReqPkt, #state{transport = TPid, dictionary = Dict} = S) -> msg = Msg, transport_data = TD}, - AnsPkt = diameter_codec:encode(Dict, Pkt), + AnsPkt = diameter_codec:encode(Dict, Opts, Pkt), incr(send, AnsPkt, Dict), incr_rc(send, AnsPkt, Dict), @@ -1358,8 +1367,9 @@ dpr([], [Reason | _], S) -> -record(opts, {cause, timeout}). -send_dpr(Reason, Opts, #state{dictionary = Dict, - service = #diameter_service{capabilities = Caps}} +send_dpr(Reason, DprOpts, #state{dictionary = Dict, + service = #diameter_service{capabilities = Caps}, + codec = Opts} = S) -> #opts{cause = Cause, timeout = Tmo} = lists:foldl(fun opt/2, @@ -1368,7 +1378,7 @@ send_dpr(Reason, Opts, #state{dictionary = Dict, _ -> ?REBOOT end, timeout = dpa_timeout()}, - Opts), + DprOpts), #diameter_caps{origin_host = {OH, _}, origin_realm = {OR, _}} = Caps, @@ -1376,6 +1386,7 @@ send_dpr(Reason, Opts, #state{dictionary = Dict, Pkt = encode(['DPR', {'Origin-Host', OH}, {'Origin-Realm', OR}, {'Disconnect-Cause', Cause}], + Opts, Dict), send_dpr(false, Pkt, Tmo, S). diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index cbccf5d006..daba440586 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -103,6 +103,7 @@ make_recvdata([SvcName, PeerT, Apps, SvcOpts | _]) -> sequence = Mask, codec = maps:with([string_decode, strict_mbit, + ordered_encode, incoming_maxlen], SvcOpts)}}. @@ -479,11 +480,12 @@ request_cb(T, App, _, _) -> %% send_A/4 -send_A({Caps, Pkt}, TPid, Dict0, _RecvData) -> %% unsupported application +send_A({Caps, Pkt}, TPid, Dict0, RecvData) -> %% unsupported application #diameter_packet{errors = [RC|_]} = Pkt, send_A(answer_message(RC, Caps, Dict0, Pkt), TPid, {Dict0, Dict0}, + RecvData, Pkt, [], []); @@ -492,6 +494,7 @@ send_A({Caps, Pkt, App, {T, EvalPktFs, EvalFs}}, TPid, Dict0, RecvData) -> send_A(answer(T, Caps, Pkt, App, Dict0, RecvData), TPid, {App#diameter_app.dictionary, Dict0}, + RecvData, Pkt, EvalPktFs, EvalFs); @@ -499,10 +502,17 @@ send_A({Caps, Pkt, App, {T, EvalPktFs, EvalFs}}, TPid, Dict0, RecvData) -> send_A(_, _, _, _) -> ok. -%% send_A/6 +%% send_A/7 -send_A(T, TPid, {AppDict, Dict0} = DictT0, ReqPkt, EvalPktFs, EvalFs) -> - {MsgDict, Pkt} = reply(T, TPid, DictT0, EvalPktFs, ReqPkt), +send_A(T, + TPid, + {AppDict, Dict0} + = DictT0, + RecvData, + ReqPkt, + EvalPktFs, + EvalFs) -> + {MsgDict, Pkt} = reply(T, TPid, DictT0, RecvData, EvalPktFs, ReqPkt), incr(send, Pkt, TPid, AppDict), incr_rc(send, Pkt, TPid, {MsgDict, AppDict, Dict0}), %% count outgoing send(TPid, Pkt, _Route = self()), @@ -670,32 +680,43 @@ is_loop(Code, Vid, OH, Dict0, [_ | Avps]) is_loop(Code, Vid, OH, Dict0, Avps) -> is_loop(Code, Vid, list_to_binary(OH), Dict0, Avps). -%% reply/5 +%% reply/6 %% Local answer ... -reply({MsgDict, Ans}, TPid, {AppDict, Dict0}, Fs, ReqPkt) -> - local(Ans, TPid, {MsgDict, AppDict, Dict0}, Fs, ReqPkt); +reply({MsgDict, Ans}, TPid, {AppDict, Dict0}, RecvData, Fs, ReqPkt) -> + local(Ans, TPid, {MsgDict, AppDict, Dict0}, RecvData, Fs, ReqPkt); %% ... or relayed. -reply(#diameter_packet{} = Pkt, _TPid, {AppDict, Dict0}, Fs, _ReqPkt) -> +reply(#diameter_packet{} = Pkt, _TPid, {AppDict, Dict0}, _, Fs, _ReqPkt) -> eval_packet(Pkt, Fs), {msg_dict(AppDict, Dict0, Pkt), Pkt}. -%% local/5 +%% local/6 %% %% Send a locally originating reply. %% Skip the setting of Result-Code and Failed-AVP's below. This is %% undocumented and shouldn't be relied on. -local([Msg], TPid, DictT, Fs, ReqPkt) +local([Msg], TPid, DictT, RecvData, Fs, ReqPkt) when is_list(Msg); is_tuple(Msg) -> - local(Msg, TPid, DictT, Fs, ReqPkt#diameter_packet{errors = []}); - -local(Msg, TPid, {MsgDict, AppDict, Dict0}, Fs, ReqPkt) -> + local(Msg, + TPid, + DictT, + RecvData, + Fs, + ReqPkt#diameter_packet{errors = []}); + +local(Msg, + TPid, + {MsgDict, AppDict, Dict0}, + #recvdata{codec = Opts}, + Fs, + ReqPkt) -> Pkt = encode({MsgDict, AppDict}, TPid, make_answer_packet(Msg, ReqPkt, MsgDict, Dict0), + Opts, Fs), {MsgDict, Pkt}. @@ -1344,7 +1365,11 @@ send_request({{TPid, _Caps} = TC, App} case prepare(cb(App, prepare_request, [Pkt, SvcName, TC]), []) of [Msg | Fs] -> ReqPkt = make_request_packet(Msg, Pkt), - EncPkt = encode(App#diameter_app.dictionary, TPid, ReqPkt, Fs), + EncPkt = encode(App#diameter_app.dictionary, + TPid, + ReqPkt, + SvcOpts, + Fs), T = send_R(ReqPkt, EncPkt, Transport, CallOpts, Caller, SvcName), Ans = recv_answer(SvcName, App, CallOpts, T), handle_answer(SvcName, SvcOpts, App, Ans); @@ -1504,7 +1529,8 @@ send_R(ReqPkt, %% recv_answer/4 -recv_answer(SvcName, App, CallOpts, {TRef, MRef, #request{ref = Ref} = Req}) -> +recv_answer(SvcName, App, CallOpts, {TRef, MRef, #request{ref = Ref} + = Req}) -> %% Matching on TRef below ensures we ignore messages that pertain %% to a previous transport prior to failover. The answer message %% includes the pid of the transport on which it was received, @@ -1611,11 +1637,32 @@ handle_error(Hdr, SvcName, discard) -> %% resend_request/4 -resend_request({{_, App} = Transport, _}, Req, CallOpts, SvcName) -> - try retransmit(Transport, Req, SvcName, CallOpts#options.timeout) of - T -> recv_answer(SvcName, App, CallOpts, T) - catch - ?FAILURE(Reason) -> {error, Req, Reason} +resend_request({{{TPid, _Caps} = TC, App}, SvcOpts}, + Req0, + #options{timeout = Timeout} + = CallOpts, + SvcName) -> + case + undefined == get(TPid) + andalso prepare_retransmit(TC, App, Req0, SvcName) + of + [ReqPkt | Fs] -> + AppDict = App#diameter_app.dictionary, + EncPkt = encode(AppDict, TPid, ReqPkt, SvcOpts, Fs), + Req = Req0#request{peer = TC, + packet = ReqPkt}, + ?LOG(retransmission, EncPkt#diameter_packet.header), + incr(TPid, {msg_id(EncPkt, AppDict), send, retransmission}), + {TRef, MRef} = zend_requezt(TPid, EncPkt, Req, SvcName, Timeout), + recv_answer(SvcName, App, CallOpts, {TRef, MRef, Req}); + false -> + {error, Req0, timeout}; + {discard, Reason} -> + {error, Req0, Reason}; + discard -> + {error, Req0, discarded}; + {error, T} -> + ?ERROR({invalid_return, T, prepare_retransmit, App}) end; resend_request(_, Req, _, _) -> %% no alternate peer @@ -1651,29 +1698,29 @@ msg(#diameter_packet{msg = undefined, bin = Bin}) -> msg(#diameter_packet{msg = Msg}) -> Msg. -%% encode/4 +%% encode/5 -encode(Dict, TPid, Pkt, Fs) -> - P = encode(Dict, TPid, Pkt), +encode(Dict, TPid, Pkt, Opts, Fs) -> + P = encode(Dict, TPid, Opts, Pkt), eval_packet(P, Fs), P. -%% encode/2 +%% encode/4 %% Note that prepare_request can return a diameter_packet containing a %% header or transport_data. Even allow the returned record to contain %% an encoded binary. This isn't the usual case and doesn't properly %% support retransmission but is useful for test. -encode(Dict, TPid, Pkt) +encode(Dict, TPid, Opts, Pkt) when is_atom(Dict) -> - encode({Dict, Dict}, TPid, Pkt); + encode({Dict, Dict}, TPid, Opts, Pkt); %% A message to be encoded. -encode(DictT, TPid, #diameter_packet{bin = undefined} = Pkt) -> +encode(DictT, TPid, Opts, #diameter_packet{bin = undefined} = Pkt) -> {Dict, AppDict} = DictT, try - diameter_codec:encode(Dict, Pkt) + diameter_codec:encode(Dict, Opts, Pkt) catch exit: {diameter_codec, encode, T} = Reason -> incr_error(send, T, TPid, AppDict), @@ -1681,7 +1728,7 @@ encode(DictT, TPid, #diameter_packet{bin = undefined} = Pkt) -> end; %% An encoded binary: just send. -encode(_, _, #diameter_packet{} = Pkt) -> +encode(_, _, _, #diameter_packet{} = Pkt) -> Pkt. %% zend_requezt/5 @@ -1752,70 +1799,21 @@ recv(TPid, Pid, TRef, {LocalTRef, MRef}) -> send(Pid, Pkt, Route) -> Pid ! {send, Pkt, Route}. -%% retransmit/4 +%% prepare_retransmit/4 -retransmit({{TPid, _Caps} = TC, App} - = Transport, - #request{packet = ReqPkt} - = Req, - SvcName, - Timeout) -> - undefined == get(TPid) %% Don't failover to a peer we've - orelse ?THROW(timeout), %% already sent to. +prepare_retransmit({_TPid, _Caps} = TC, App, Req, SvcName) -> + Pkt = make_retransmit_packet(Req#request.packet), - Pkt = make_retransmit_packet(ReqPkt), + case prepare(cb(App, prepare_retransmit, [Pkt, SvcName, TC]), []) of + [Msg | Fs] -> + [make_request_packet(Msg, Pkt) | Fs]; + No -> + No + end. - retransmit(cb(App, prepare_retransmit, [Pkt, SvcName, TC]), - Transport, - Req#request{packet = Pkt}, - SvcName, - Timeout, - []). %% When sending a binary, it's up to prepare_retransmit to modify it %% accordingly. -retransmit({send, Msg}, - Transport, - #request{packet = Pkt} - = Req, - SvcName, - Timeout, - Fs) -> - resend_request(make_request_packet(Msg, Pkt), - Transport, - Req, - SvcName, - Timeout, - Fs); - -retransmit({discard, Reason}, _, _, _, _, _) -> - ?THROW(Reason); - -retransmit(discard, _, _, _, _, _) -> - ?THROW(discarded); - -retransmit({eval_packet, RC, F}, Transport, Req, SvcName, Timeout, Fs) -> - retransmit(RC, Transport, Req, SvcName, Timeout, [F|Fs]); - -retransmit(T, {_, App}, _, _, _, _) -> - ?ERROR({invalid_return, T, prepare_retransmit, App}). - -resend_request(ReqPkt, - {{TPid, _Caps} = TC, #diameter_app{dictionary = AppDict}}, - Req0, - SvcName, - Tmo, - Fs) -> - EncPkt = encode(AppDict, TPid, ReqPkt, Fs), - - Req = Req0#request{peer = TC, - packet = ReqPkt}, - - ?LOG(retransmission, EncPkt#diameter_packet.header), - incr(TPid, {msg_id(EncPkt, AppDict), send, retransmission}), - {TRef, MRef} = zend_requezt(TPid, EncPkt, Req, SvcName, Tmo), - {TRef, MRef, Req}. - %% peer_monitor/2 peer_monitor(TPid, TRef) -> diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl index b827925400..a63425d92a 100644 --- a/lib/diameter/src/base/diameter_watchdog.erl +++ b/lib/diameter/src/base/diameter_watchdog.erl @@ -76,6 +76,7 @@ strict_mbit := boolean(), failed_avp := false, rfc := 3588 | 6733, + ordered_encode := false, incoming_maxlen := diameter:message_length()}, shutdown = false :: boolean()}). @@ -134,7 +135,12 @@ 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, strict_mbit, incoming_maxlen, spawn_opt, rfc], + CodecKeys = [string_decode, + strict_mbit, + incoming_maxlen, + spawn_opt, + rfc, + ordered_encode], #watchdog{parent = Pid, transport = start(T, Opts, SvcOpts, Nodes, Dict0, Svc), @@ -149,7 +155,8 @@ i({Ack, T, Pid, {Opts, suspect => 1, okay => 3}, Opts)), - codec = maps:with(CodecKeys, SvcOpts#{string_decode := false})}. + codec = maps:with(CodecKeys, SvcOpts#{string_decode := false, + ordered_encode => false})}. wait(Ref, Pid) -> receive @@ -502,9 +509,9 @@ getr(Key) -> eraser(Key) -> erase({?MODULE, Key}). -%% encode/3 +%% encode/4 -encode(dwr = M, Dict0, Mask) -> +encode(dwr = M, Dict0, Opts, Mask) -> Msg = getr(M), Seq = diameter_session:sequence(Mask), Hdr = #diameter_header{version = ?DIAMETER_VERSION, @@ -512,10 +519,10 @@ encode(dwr = M, Dict0, Mask) -> hop_by_hop_id = Seq}, Pkt = #diameter_packet{header = Hdr, msg = Msg}, - diameter_codec:encode(Dict0, Pkt); + diameter_codec:encode(Dict0, Opts, Pkt); -encode(dwa, Dict0, #diameter_packet{header = H, transport_data = TD} - = ReqPkt) -> +encode(dwa, Dict0, Opts, #diameter_packet{header = H, transport_data = TD} + = ReqPkt) -> AnsPkt = #diameter_packet{header = H#diameter_header{is_request = false, is_error = undefined, @@ -523,7 +530,7 @@ encode(dwa, Dict0, #diameter_packet{header = H, transport_data = TD} msg = dwa(ReqPkt), transport_data = TD}, - diameter_codec:encode(Dict0, AnsPkt). + diameter_codec:encode(Dict0, Opts, AnsPkt). %% okay/3 @@ -593,9 +600,10 @@ tw({M,F,A}) -> send_watchdog(#watchdog{pending = false, transport = TPid, dictionary = Dict0, - config = #{sequence := Mask}} + config = #{sequence := Mask}, + codec = Opts} = S) -> - #diameter_packet{bin = Bin} = EPkt = encode(dwr, Dict0, Mask), + #diameter_packet{bin = Bin} = EPkt = encode(dwr, Dict0, Opts, Mask), diameter_traffic:incr(send, EPkt, TPid, Dict0), send(TPid, {send, Bin}), ?LOG(send, 'DWR'), @@ -628,7 +636,7 @@ rcv('DWR', Pkt, #watchdog{transport = TPid, transport_data = T, bin = Bin} = EPkt - = encode(dwa, Dict0, Pkt), + = encode(dwa, Dict0, Opts, Pkt), diameter_traffic:incr(send, EPkt, TPid, Dict0), diameter_traffic:incr_rc(send, EPkt, TPid, Dict0), -- cgit v1.2.3 From c59325f2a24e6ca3217c660fa328a00cf440e58d Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sun, 30 Apr 2017 10:08:54 +0200 Subject: Restore diameter_codec:decode/2, update diameter_codec(3) The documentation has been out of date since the string_decode option was added in commit 1590920c. The optionless decode/2 was removed in the commit that removed the use of the process dictionary in decode. --- lib/diameter/doc/src/diameter_codec.xml | 19 ++++++++++--------- lib/diameter/src/base/diameter_codec.erl | 24 ++++++++++++++++++++---- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/lib/diameter/doc/src/diameter_codec.xml b/lib/diameter/doc/src/diameter_codec.xml index 91e96058dd..0117c1c88a 100644 --- a/lib/diameter/doc/src/diameter_codec.xml +++ b/lib/diameter/doc/src/diameter_codec.xml @@ -13,7 +13,8 @@
-20122016 +2012 +2017 Ericsson AB. All Rights Reserved. @@ -53,17 +54,17 @@ communicated to &man_app; callbacks. Similarly, outgoing Diameter messages are encoded into binary() before being passed to the appropriate &man_transport; module for transmission. -The functions in this module implement this encode/decode.

+The functions documented here implement the default encode/decode.

- +

-Calls to this module are made by diameter itself as a consequence of -configuration passed to &mod_start_service;. -The encode/decode functions may also be useful for other purposes (eg. -test) but the diameter user does not need to call them explicitly when +The diameter user does not need to call functions here explicitly when sending and receiving messages using &mod_call; and the callback -interface documented in &man_app;.

-
+interface documented in &man_app;: diameter itself provides encode/decode +as a consequence of configuration passed to &mod_start_service;, and +the results may differ from those returned by the functions documented +here, depending on configuration.

+

The &header; and &packet; records below diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl index a4d816db4e..82fa796e69 100644 --- a/lib/diameter/src/base/diameter_codec.erl +++ b/lib/diameter/src/base/diameter_codec.erl @@ -20,10 +20,8 @@ -module(diameter_codec). --export([encode/2, - encode/3, - decode/3, - decode/4, +-export([encode/2, encode/3, + decode/2, decode/3, decode/4, collect_avps/1, decode_header/1, sequence_numbers/1, @@ -68,6 +66,10 @@ %%% # encode/2 %%% --------------------------------------------------------------------------- +%% The representative encode documented in diameter_codec(3). As of +%% the options that affect encode (eg. ordered_encode), it's no longer +%% *the* encode. + encode(Mod, Msg) -> encode(Mod, #{ordered_encode => true}, Msg). @@ -276,6 +278,20 @@ rec2msg(_, [Name|_]) rec2msg(Mod, Rec) -> Mod:rec2msg(element(1, Rec)). +%%% --------------------------------------------------------------------------- +%%% # decode/2 +%%% --------------------------------------------------------------------------- + +%% The representative default decode documented in diameter_codec(3). +%% As of the options that affect decode (eg. string_decode), it's no +%% longer *the* decode. + +decode(Mod, Pkt) -> + Opts = #{string_decode => true, + strict_mbit => true, + rfc => 6733}, + decode(Mod, Opts, Pkt). + %%% --------------------------------------------------------------------------- %%% # decode/3 %%% --------------------------------------------------------------------------- -- cgit v1.2.3 From 011fa124e88d94dd0d4dfd7261ca0fddc3547114 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Mon, 1 May 2017 23:43:16 +0200 Subject: Refactor handling of incoming requests To simplify the call chains and intermediate terms, that had become a little convoluted over time. --- lib/diameter/src/base/diameter_traffic.erl | 367 +++++++++++++---------------- 1 file changed, 165 insertions(+), 202 deletions(-) diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index daba440586..42164c48c0 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -275,45 +275,40 @@ recv_request(Ack, apps = Apps} = RecvData) -> Ack andalso (TPid ! {handler, self()}), - send_A(recv_R(diameter_service:find_incoming_app(PeerT, TPid, Id, Apps), - TPid, - Pkt, - Dict0, - RecvData), - TPid, - Dict0, - RecvData). - -%% recv_R/5 - -recv_R({#diameter_app{id = Id, dictionary = AppDict} = App, Caps}, - TPid, - Pkt0, - Dict0, - #recvdata{codec = Opts} - = RecvData) -> - incr(recv, Pkt0, TPid, AppDict), - Pkt = errors(Id, diameter_codec:decode(Id, AppDict, Opts, Pkt0)), - incr_error(recv, Pkt, TPid, AppDict), - {Caps, Pkt, App, recv_R(App, TPid, Dict0, Caps, RecvData, Pkt)}; -%% Note that the decode is different depending on whether or not Id is -%% ?APP_ID_RELAY. - -%% DIAMETER_APPLICATION_UNSUPPORTED 3007 -%% A request was sent for an application that is not supported. - -recv_R(#diameter_caps{} - = Caps, - _TPid, - #diameter_packet{errors = Es} - = Pkt, - _Dict0, - _RecvData) -> - {Caps, Pkt#diameter_packet{avps = collect_avps(Pkt), - errors = [3007 | Es]}}; + case diameter_service:find_incoming_app(PeerT, TPid, Id, Apps) of + {#diameter_app{id = Aid, dictionary = AppDict} = App, Caps} -> + incr(recv, Pkt, TPid, AppDict), + DecPkt = decode(Aid, AppDict, RecvData, Pkt), + incr_error(recv, DecPkt, TPid, AppDict), + send_A(recv_R(App, TPid, Dict0, Caps, RecvData, DecPkt), + TPid, + App, + Dict0, + RecvData, + DecPkt, + Caps); + #diameter_caps{} = Caps -> + %% DIAMETER_APPLICATION_UNSUPPORTED 3007 + %% A request was sent for an application that is not + %% supported. + RC = 3007, + Es = Pkt#diameter_packet.errors, + DecPkt = Pkt#diameter_packet{avps = collect_avps(Pkt), + errors = [RC | Es]}, + send_answer(answer_message(RC, Dict0, Caps, DecPkt), + TPid, + Dict0, + Dict0, + Dict0, + RecvData, + DecPkt, + [[]]); + false = No -> %% transport has gone down + No + end. -recv_R(false = No, _, _, _, _) -> %% transport has gone down - No. +decode(Id, Dict, #recvdata{codec = Opts}, Pkt) -> + errors(Id, diameter_codec:decode(Id, Dict, Opts, Pkt)). collect_avps(Pkt) -> case diameter_codec:collect_avps(Pkt) of @@ -323,6 +318,14 @@ collect_avps(Pkt) -> Avps end. +%% send_A/7 + +send_A([T | Fs], TPid, App, Dict0, RecvData, DecPkt, Caps) -> + send_A(T, TPid, App, Dict0, RecvData, DecPkt, Caps, Fs); + +send_A(discard = No, _, _, _, _, _, _) -> + No. + %% recv_R/6 %% Answer errors ourselves ... @@ -332,9 +335,9 @@ recv_R(#diameter_app{options = [_, {request_errors, E} | _]}, _Caps, _RecvData, #diameter_packet{errors = [RC|_]}) %% a detected 3xxx is hd - when E == answer, (Dict0 /= ?BASE orelse 3 == RC div 1000); + when E == answer, Dict0 /= ?BASE orelse 3 == RC div 1000; E == answer_3xxx, 3 == RC div 1000 -> - {{answer_message, rc(RC)}, [], []}; + [{answer_message, rc(RC)}, []]; %% ... or make a handle_request callback. Note that %% Pkt#diameter_packet.msg = undefined in the 3001 case. @@ -426,24 +429,24 @@ errors(_, Pkt) -> %% command code in this case. It will also then ignore Dict and use %% the base encoder. request_cb({reply, _Ans} = T, _App, EvalPktFs, EvalFs) -> - {T, EvalPktFs, EvalFs}; + [T, EvalPktFs | EvalFs]; %% An 3xxx result code, for which the E-bit is set in the header. request_cb({protocol_error, RC}, _App, EvalPktFs, EvalFs) when 3 == RC div 1000 -> - {{answer_message, RC}, EvalPktFs, EvalFs}; + [{answer_message, RC}, EvalPktFs | EvalFs]; request_cb({answer_message, RC} = T, _App, EvalPktFs, EvalFs) when 3 == RC div 1000; 5 == RC div 1000 -> - {T, EvalPktFs, EvalFs}; + [T, EvalPktFs | EvalFs]; %% RFC 3588 says we must reply 3001 to anything unrecognized or %% unsupported. 'noreply' is undocumented (and inappropriately named) %% backwards compatibility for this, protocol_error the documented %% alternative. request_cb(noreply, _App, EvalPktFs, EvalFs) -> - {{answer_message, 3001}, EvalPktFs, EvalFs}; + [{answer_message, 3001}, EvalPktFs | EvalFs]; %% Relay a request to another peer. This is equivalent to doing an %% explicit call/4 with the message in question except that (1) a loop @@ -464,7 +467,7 @@ request_cb({A, Opts}, #diameter_app{id = Id}, EvalPktFs, EvalFs) when A == relay, Id == ?APP_ID_RELAY; A == proxy, Id /= ?APP_ID_RELAY; A == resend -> - {{call, Opts}, EvalPktFs, EvalFs}; + [{call, Opts}, EvalPktFs | EvalFs]; request_cb(discard = No, _, _, _) -> No; @@ -478,80 +481,95 @@ request_cb({eval, RC, F}, App, EvalPktFs, Fs) -> request_cb(T, App, _, _) -> ?ERROR({invalid_return, T, handle_request, App}). -%% send_A/4 - -send_A({Caps, Pkt}, TPid, Dict0, RecvData) -> %% unsupported application - #diameter_packet{errors = [RC|_]} = Pkt, - send_A(answer_message(RC, Caps, Dict0, Pkt), - TPid, - {Dict0, Dict0}, - RecvData, - Pkt, - [], - []); - -send_A({Caps, Pkt, App, {T, EvalPktFs, EvalFs}}, TPid, Dict0, RecvData) -> - send_A(answer(T, Caps, Pkt, App, Dict0, RecvData), - TPid, - {App#diameter_app.dictionary, Dict0}, - RecvData, - Pkt, - EvalPktFs, - EvalFs); - -send_A(_, _, _, _) -> - ok. +%% send_A/8 + +send_A({reply, Ans}, TPid, App, Dict0, RecvData, Pkt, _Caps, Fs) -> + AppDict = App#diameter_app.dictionary, + MsgDict = msg_dict(AppDict, Dict0, Ans), + send_answer(Ans, + TPid, + MsgDict, + AppDict, + Dict0, + RecvData, + Pkt, + Fs); + +send_A({call, Opts}, TPid, App, Dict0, RecvData, Pkt, Caps, Fs) -> + AppDict = App#diameter_app.dictionary, + case resend(Opts, Caps, Pkt, App, Dict0, RecvData) of + #diameter_packet{bin = Bin} = Ans -> %% answer: reset hop by hop id + #diameter_packet{header = #diameter_header{hop_by_hop_id = Id}, + transport_data = TD} + = Pkt, + Reset = diameter_codec:hop_by_hop_id(Id, Bin), + MsgDict = msg_dict(AppDict, Dict0, Ans), + send_answer(Ans#diameter_packet{bin = Reset, + transport_data = TD}, + TPid, + MsgDict, + AppDict, + Dict0, + Fs); + RC -> + send_answer(answer_message(RC, Dict0, Caps, Pkt), + TPid, + Dict0, + AppDict, + Dict0, + RecvData, + Pkt, + Fs) + end; -%% send_A/7 +%% RFC 3588 only allows 3xxx errors in an answer-message. RFC 6733 +%% added the possibility of setting 5xxx. -send_A(T, - TPid, - {AppDict, Dict0} - = DictT0, - RecvData, - ReqPkt, - EvalPktFs, - EvalFs) -> - {MsgDict, Pkt} = reply(T, TPid, DictT0, RecvData, EvalPktFs, ReqPkt), - incr(send, Pkt, TPid, AppDict), - incr_rc(send, Pkt, TPid, {MsgDict, AppDict, Dict0}), %% count outgoing - send(TPid, Pkt, _Route = self()), - lists:foreach(fun diameter_lib:eval/1, EvalFs). +send_A({answer_message, RC} = T, TPid, App, Dict0, RecvData, Pkt, Caps, Fs) -> + Dict0 /= ?BASE orelse 3 == RC div 1000 + orelse ?ERROR({invalid_return, T, handle_request, App}), + send_answer(answer_message(RC, Dict0, Caps, Pkt), + TPid, + Dict0, + App#diameter_app.dictionary, + Dict0, + RecvData, + Pkt, + Fs). -%% answer/6 +%% send_answer/8 -answer({reply, Ans}, _Caps, _Pkt, App, Dict0, _RecvData) -> - {msg_dict(App#diameter_app.dictionary, Dict0, Ans), Ans}; +%% Skip the setting of Result-Code and Failed-AVP's below. This is +%% undocumented and shouldn't be relied on. +send_answer([Ans], TPid, MsgDict, AppDict, Dict0, RecvData, Pkt, Fs) + when [] == Pkt#diameter_packet.errors -> + send_answer(Ans, TPid, MsgDict, AppDict, Dict0, RecvData, Pkt, Fs); +send_answer([Ans], TPid, MsgDict, AppDict, Dict0, RecvData, Pkt0, Fs) -> + Pkt = Pkt0#diameter_packet{errors = []}, + send_answer(Ans, TPid, MsgDict, AppDict, Dict0, RecvData, Pkt, Fs); + +send_answer(Ans, TPid, MsgDict, AppDict, Dict0, RecvData, DecPkt, Fs) -> + Pkt = encode({MsgDict, AppDict}, + TPid, + RecvData#recvdata.codec, + make_answer_packet(Ans, DecPkt, MsgDict, Dict0)), + send_answer(Pkt, TPid, MsgDict, AppDict, Dict0, Fs). -answer({call, Opts}, Caps, Pkt, App, Dict0, RecvData) -> - #diameter_caps{origin_host = {OH,_}} - = Caps, - #diameter_packet{avps = Avps} - = Pkt, - {Code, _Flags, Vid} = Dict0:avp_header('Route-Record'), - resend(is_loop(Code, Vid, OH, Dict0, Avps), - Opts, - Caps, - Pkt, - App, - Dict0, - RecvData); +%% send_answer/6 -%% RFC 3588 only allows 3xxx errors in an answer-message. RFC 6733 -%% added the possibility of setting 5xxx. -answer({answer_message, RC} = T, Caps, Pkt, App, Dict0, _RecvData) -> - Dict0 /= ?BASE orelse 3 == RC div 1000 - orelse ?ERROR({invalid_return, T, handle_request, App}), - answer_message(RC, Caps, Dict0, Pkt). +send_answer(Pkt, TPid, MsgDict, AppDict, Dict0, [EvalPktFs | EvalFs]) -> + eval_packet(Pkt, EvalPktFs), + incr(send, Pkt, TPid, AppDict), + incr_rc(send, Pkt, TPid, {MsgDict, AppDict, Dict0}), %% count outgoing + send(TPid, Pkt, _Route = self()), + lists:foreach(fun diameter_lib:eval/1, EvalFs). %% msg_dict/3 %% %% Return the dictionary defining the message grammar in question: the %% application dictionary or the common dictionary. -msg_dict(AppDict, Dict0, [Msg]) - when is_list(Msg); - is_tuple(Msg) -> +msg_dict(AppDict, Dict0, [Msg]) -> msg_dict(AppDict, Dict0, Msg); msg_dict(AppDict, Dict0, Msg) -> @@ -582,14 +600,10 @@ is_answer_message(Rec, Dict) -> error:_ -> false end. -%% answer_message/4 +%% resend/6 -answer_message(RC, - #diameter_caps{origin_host = {OH,_}, - origin_realm = {OR,_}}, - Dict0, - Pkt) -> - {Dict0, answer_message(OH, OR, RC, Dict0, Pkt)}. +resend(Opts, Caps, Pkt, App, Dict0, RecvData) -> + resend(is_loop(Dict0, Caps, Pkt), Opts, Caps, Pkt, App, Dict0, RecvData). %% resend/7 @@ -599,8 +613,8 @@ answer_message(RC, %% if one is available, but the peer reporting the error has %% identified a configuration problem. -resend(true, _Opts, Caps, Pkt, _App, Dict0, _RecvData) -> - answer_message(3005, Caps, Dict0, Pkt); +resend(true, _Opts, _Caps, _Pkt, _App, _Dict0, _RecvData) -> + 3005; %% 6.1.8. Relaying and Proxying Requests %% @@ -610,11 +624,9 @@ resend(true, _Opts, Caps, Pkt, _App, Dict0, _RecvData) -> resend(false, Opts, - #diameter_caps{origin_host = {_,OH}} - = Caps, + #diameter_caps{origin_host = {_,OH}}, #diameter_packet{header = Hdr0, - avps = Avps} - = Pkt, + avps = Avps}, App, Dict0, #recvdata{service_name = SvcName, @@ -623,7 +635,12 @@ resend(false, Seq = diameter_session:sequence(Mask), Hdr = Hdr0#diameter_header{hop_by_hop_id = Seq}, Msg = [Hdr, Route | Avps], %% reordered at encode - resend(send_request(SvcName, App, Msg, Opts), Caps, Dict0, Pkt). + case send_request(SvcName, App, Msg, Opts) of + #diameter_packet{} = Ans -> + Ans; + _ -> + 3002 %% DIAMETER_UNABLE_TO_DELIVER. + end. %% The incoming request is relayed with the addition of a %% Route-Record. Note the requirement on the return from call/4 below, %% which places a requirement on the value returned by the @@ -640,85 +657,33 @@ resend(false, %% RFC 6.3 says that a relay agent does not modify Origin-Host but %% says nothing about a proxy. Assume it should behave the same way. -%% resend/4 -%% -%% Relay a reply to a relayed request. - -%% Answer from the peer: reset the hop by hop identifier. -resend(#diameter_packet{bin = B} - = Pkt, - _Caps, - _Dict0, - #diameter_packet{header = #diameter_header{hop_by_hop_id = Id}, - transport_data = TD}) -> - Pkt#diameter_packet{bin = diameter_codec:hop_by_hop_id(Id, B), - transport_data = TD}; -%% TODO: counters +%% is_loop/3 -%% Or not: DIAMETER_UNABLE_TO_DELIVER. -resend(_, Caps, Dict0, Pkt) -> - answer_message(3002, Caps, Dict0, Pkt). +is_loop(Dict0, + #diameter_caps{origin_host = {OH,_}}, + #diameter_packet{avps = Avps}) -> + {Code, _Flags, Vid} = Dict0:avp_header('Route-Record'), + is_loop(Code, Vid, OH, Avps). -%% is_loop/5 +%% is_loop/4 %% %% Is there a Route-Record AVP with our Origin-Host? -is_loop(Code, - Vid, - Bin, - _Dict0, - [#diameter_avp{code = Code, vendor_id = Vid, data = Bin} | _]) -> +is_loop(Code, Vid, Bin, [#diameter_avp{code = Code, + vendor_id = Vid, + data = Bin} + | _]) -> true; -is_loop(_, _, _, _, []) -> +is_loop(_, _, _, []) -> false; -is_loop(Code, Vid, OH, Dict0, [_ | Avps]) +is_loop(Code, Vid, OH, [_ | Avps]) when is_binary(OH) -> - is_loop(Code, Vid, OH, Dict0, Avps); - -is_loop(Code, Vid, OH, Dict0, Avps) -> - is_loop(Code, Vid, list_to_binary(OH), Dict0, Avps). + is_loop(Code, Vid, OH, Avps); -%% reply/6 - -%% Local answer ... -reply({MsgDict, Ans}, TPid, {AppDict, Dict0}, RecvData, Fs, ReqPkt) -> - local(Ans, TPid, {MsgDict, AppDict, Dict0}, RecvData, Fs, ReqPkt); - -%% ... or relayed. -reply(#diameter_packet{} = Pkt, _TPid, {AppDict, Dict0}, _, Fs, _ReqPkt) -> - eval_packet(Pkt, Fs), - {msg_dict(AppDict, Dict0, Pkt), Pkt}. - -%% local/6 -%% -%% Send a locally originating reply. - -%% Skip the setting of Result-Code and Failed-AVP's below. This is -%% undocumented and shouldn't be relied on. -local([Msg], TPid, DictT, RecvData, Fs, ReqPkt) - when is_list(Msg); - is_tuple(Msg) -> - local(Msg, - TPid, - DictT, - RecvData, - Fs, - ReqPkt#diameter_packet{errors = []}); - -local(Msg, - TPid, - {MsgDict, AppDict, Dict0}, - #recvdata{codec = Opts}, - Fs, - ReqPkt) -> - Pkt = encode({MsgDict, AppDict}, - TPid, - make_answer_packet(Msg, ReqPkt, MsgDict, Dict0), - Opts, - Fs), - {MsgDict, Pkt}. +is_loop(Code, Vid, OH, Avps) -> + is_loop(Code, Vid, list_to_binary(OH), Avps). %% select_error/3 %% @@ -999,10 +964,14 @@ failed(Rec, FailedAvp, Dict) -> %% Error-Message AVP is not intended to be useful in real-time, and %% SHOULD NOT be expected to be parsed by network entities. -%% answer_message/5 +%% answer_message/4 -answer_message(OH, OR, RC, Dict0, #diameter_packet{avps = Avps, - errors = Es}) -> +answer_message(RC, + Dict0, + #diameter_caps{origin_host = {OH,_}, + origin_realm = {OR,_}}, + #diameter_packet{avps = Avps, + errors = Es}) -> {Code, _, Vid} = Dict0:avp_header('Session-Id'), ['answer-message', {'Origin-Host', OH}, {'Origin-Realm', OR}, @@ -1367,9 +1336,9 @@ send_request({{TPid, _Caps} = TC, App} ReqPkt = make_request_packet(Msg, Pkt), EncPkt = encode(App#diameter_app.dictionary, TPid, - ReqPkt, SvcOpts, - Fs), + ReqPkt), + eval_packet(EncPkt, Fs), T = send_R(ReqPkt, EncPkt, Transport, CallOpts, Caller, SvcName), Ans = recv_answer(SvcName, App, CallOpts, T), handle_answer(SvcName, SvcOpts, App, Ans); @@ -1648,7 +1617,8 @@ resend_request({{{TPid, _Caps} = TC, App}, SvcOpts}, of [ReqPkt | Fs] -> AppDict = App#diameter_app.dictionary, - EncPkt = encode(AppDict, TPid, ReqPkt, SvcOpts, Fs), + EncPkt = encode(AppDict, TPid, SvcOpts, ReqPkt), + eval_packet(EncPkt, Fs), Req = Req0#request{peer = TC, packet = ReqPkt}, ?LOG(retransmission, EncPkt#diameter_packet.header), @@ -1698,13 +1668,6 @@ msg(#diameter_packet{msg = undefined, bin = Bin}) -> msg(#diameter_packet{msg = Msg}) -> Msg. -%% encode/5 - -encode(Dict, TPid, Pkt, Opts, Fs) -> - P = encode(Dict, TPid, Opts, Pkt), - eval_packet(P, Fs), - P. - %% encode/4 %% Note that prepare_request can return a diameter_packet containing a -- cgit v1.2.3 From 7384de812f6399a841c8edf0503402e940fd17a4 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Thu, 4 May 2017 13:31:02 +0200 Subject: Avoid sending answer terms between processes unnecessarily As in commit fb14eac9, but for outgoing answers. --- lib/diameter/src/base/diameter_traffic.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index 42164c48c0..54f39afbf0 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -561,7 +561,7 @@ send_answer(Pkt, TPid, MsgDict, AppDict, Dict0, [EvalPktFs | EvalFs]) -> eval_packet(Pkt, EvalPktFs), incr(send, Pkt, TPid, AppDict), incr_rc(send, Pkt, TPid, {MsgDict, AppDict, Dict0}), %% count outgoing - send(TPid, Pkt, _Route = self()), + send(TPid, z(Pkt), _Route = self()), lists:foreach(fun diameter_lib:eval/1, EvalFs). %% msg_dict/3 -- cgit v1.2.3 From cac106defc5060c5e485480e8003b992482d751d Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sun, 11 Jun 2017 09:33:42 +0200 Subject: Change signature associated with dictionary @custom_type/@codecs To pass the options map through the encode. This is not backwards compatible, and dictionaries supporting @custom_types or @codecs will need to be updated. --- lib/diameter/doc/src/diameter_dict.xml | 13 +++++++------ lib/diameter/src/compiler/diameter_codegen.erl | 5 +++-- lib/diameter/test/diameter_compiler_SUITE.erl | 8 ++++---- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/diameter/doc/src/diameter_dict.xml b/lib/diameter/doc/src/diameter_dict.xml index 9584d682c2..94016d9466 100644 --- a/lib/diameter/doc/src/diameter_dict.xml +++ b/lib/diameter/doc/src/diameter_dict.xml @@ -16,7 +16,8 @@

-20112016 +2011 +2017 Ericsson AB. All Rights Reserved. @@ -307,11 +308,11 @@ The P flag has been deprecated by &the_rfc;.

Specifies AVPs for which module Mod provides encode/decode functions. The section contents consists of AVP names. -For each such name, Mod:Name(encode|decode, Type, Data) is +For each such name, Mod:Name(encode|decode, Type, Data, Opts) is expected to provide encode/decode for values of the AVP, where Name is the name of the AVP, Type is it's type as declared in the -@avp_types section of the dictionary and Data is the value to -encode/decode.

+@avp_types section of the dictionary, Data is the value to +encode/decode, and Opts is a term that is passed through encode/decode.

Example:

@@ -328,8 +329,8 @@ Framed-IP-Address

Like @custom_types but requires the specified module to export -Mod:Type(encode|decode, Name, Data) rather than -Mod:Name(encode|decode, Type, Data).

+Mod:Type(encode|decode, Name, Data, Opts) rather than +Mod:Name(encode|decode, Type, Data, Opts).

Example:

diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl index b6fc78b6cc..5a1e3ba941 100644 --- a/lib/diameter/src/compiler/diameter_codegen.erl +++ b/lib/diameter/src/compiler/diameter_codegen.erl @@ -568,11 +568,12 @@ cs_custom_avp({Mod, Key, Avps}, Dict) -> c_custom_avp(Mod, Key, AvpName, Type) -> {F,A} = custom(Key, AvpName, Type), - {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName), ?VAR('_')], + {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName), ?VAR('Opts')], [], [?APPLY(?A(Mod), ?A(F), [?VAR('T'), ?Atom(A), - ?VAR('Data')])]}. + ?VAR('Data'), + ?VAR('Opts')])]}. custom(custom_types, AvpName, Type) -> {AvpName, Type}; diff --git a/lib/diameter/test/diameter_compiler_SUITE.erl b/lib/diameter/test/diameter_compiler_SUITE.erl index 76340d65ff..dea14e3870 100644 --- a/lib/diameter/test/diameter_compiler_SUITE.erl +++ b/lib/diameter/test/diameter_compiler_SUITE.erl @@ -39,7 +39,7 @@ -export([dict/0]). %% fake dictionary module %% dictionary callbacks for flatten2/1 --export(['A1'/3, 'Unsigned32'/3]). +-export(['A1'/4, 'Unsigned32'/4]). -define(base, "base_rfc3588.dia"). -define(util, diameter_util). @@ -552,13 +552,13 @@ flatten2(_Config) -> T <- [encode, decode], M <- [M2, M3], Ref <- [make_ref()], - RC <- [M:avp(T, Ref, A, [])], + RC <- [M:avp(T, Ref, A, #{})], RC /= {T, Ref}]. -'A1'(T, 'Unsigned32', Ref) -> +'A1'(T, 'Unsigned32', Ref, _Opts) -> {T, Ref}. -'Unsigned32'(T, 'A3', Ref) -> +'Unsigned32'(T, 'A3', Ref, _Opts) -> {T, Ref}. load_forms(Forms) -> -- cgit v1.2.3 From 205521d3927ed6f53c9a6fa3095f8a879bdca929 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Sun, 11 Jun 2017 13:59:02 +0200 Subject: Move (most of) diameter_gen.hrl to diameter_gen.erl To remove the requirement that dictionary modules be recompiled whenever the encode/decode implementation changes. The included diameter_gen.hrl now only contains trivial functions that call info diameter_gen.erl. --- lib/diameter/include/diameter_gen.hrl | 659 +---------------------- lib/diameter/src/base/diameter_gen.erl | 709 +++++++++++++++++++++++++ lib/diameter/src/compiler/diameter_codegen.erl | 25 +- lib/diameter/src/modules.mk | 3 +- lib/diameter/test/diameter_codec_test.erl | 5 +- lib/diameter/test/diameter_compiler_SUITE.erl | 2 +- 6 files changed, 747 insertions(+), 656 deletions(-) create mode 100644 lib/diameter/src/base/diameter_gen.erl diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index 83f87ea4c4..fb6370fe54 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -20,659 +20,36 @@ %% %% This file contains code that's included by encode/decode modules -%% generated by diameter_codegen.erl. This code does most of the work, the -%% generated code being kept simple. +%% generated by diameter_codegen.erl. This code used to do most of the +%% work, but now passes it off to module diameter_gen. %% --define(THROW(T), throw({?MODULE, T})). - -%% Tag common to generated dictionaries. --define(TAG, diameter_gen). - --type parent_name() :: atom(). %% parent = Message or AVP --type parent_record() :: tuple(). %% --type avp_name() :: atom(). --type avp_record() :: tuple(). --type avp_values() :: [{avp_name(), term()}]. - --type non_grouped_avp() :: #diameter_avp{}. --type grouped_avp() :: nonempty_improper_list(#diameter_avp{}, [avp()]). --type avp() :: non_grouped_avp() | grouped_avp(). - -%% --------------------------------------------------------------------------- -%% # encode_avps/3 -%% --------------------------------------------------------------------------- - --spec encode_avps(parent_name(), parent_record() | avp_values(), map()) - -> iolist() - | no_return(). +%% encode_avps/3 encode_avps(Name, Vals, Opts) -> - try - encode(Name, Vals, Opts) - catch - throw: {?MODULE, Reason} -> - diameter_lib:log({encode, error}, - ?MODULE, - ?LINE, - {Reason, Name, Vals}), - erlang:error(list_to_tuple(Reason ++ [Name])); - error: Reason -> - Stack = erlang:get_stacktrace(), - diameter_lib:log({encode, failure}, - ?MODULE, - ?LINE, - {Reason, Name, Vals, Stack}), - erlang:error({encode_failure, Reason, Name, Stack}) - end. - -%% encode/3 - -encode(Name, Vals, #{ordered_encode := false} = Opts) - when is_list(Vals) -> - lists:map(fun({F,V}) -> encode(Name, F, V, Opts) end, Vals); - -encode(Name, Vals, Opts) - when is_list(Vals) -> - encode(Name, '#set-'(Vals, newrec(Name)), Opts); - -encode(Name, Rec, Opts) -> - [encode(Name, F, V, Opts) || {F,V} <- '#get-'(Rec)]. - -%% encode/4 - -encode(Name, AvpName, Values, Opts) -> - enc(Name, AvpName, avp_arity(Name, AvpName), Values, Opts). - -%% enc/5 - -enc(_, AvpName, 1, undefined, _) -> - ?THROW([mandatory_avp_missing, AvpName]); - -enc(Name, AvpName, 1, Value, Opts) -> - enc(Name, AvpName, [Value], Opts); - -enc(_, _, {0,_}, [], _) -> - []; - -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(_, AvpName, {_, Max}, L, _) - when Max < length(L) -> - ?THROW([repeated_avp_excessive_arity, AvpName, Max, L]); + diameter_gen:encode_avps(Name, Vals, Opts#{module => ?MODULE}). -enc(Name, AvpName, _, Values, Opts) -> - enc(Name, AvpName, Values, Opts). - -%% enc/4 - -enc(Name, 'AVP', Values, Opts) -> - [enc_AVP(Name, A, Opts) || A <- Values]; - -enc(_, AvpName, Values, Opts) -> - enc(AvpName, Values, Opts). - -%% env/3 - -enc(AvpName, Values, Opts) -> - H = avp_header(AvpName), - [diameter_codec:pack_data(H, avp(encode, V, AvpName, Opts)) - || V <- Values]. - -%% enc_AVP/3 - -%% No value: assume AVP data is already encoded. The normal case will -%% be when this is passed back from #diameter_packet.errors as a -%% consequence of a failed decode. Any AVP can be encoded this way -%% however, which side-steps any arity checks for known AVP's and -%% could potentially encode something unfortunate. -enc_AVP(_, #diameter_avp{value = undefined} = A, Opts) -> - diameter_codec:pack_avp(A, Opts); - -%% Missing name for value encode. -enc_AVP(_, #diameter_avp{name = N, value = V}, _) - when N == undefined; - N == 'AVP' -> - ?THROW([value_with_nameless_avp, N, V]); - -%% Or not. Ensure that 'AVP' is the appropriate field. Note that if we -%% don't know this AVP at all then the encode will fail. -enc_AVP(Name, #diameter_avp{name = AvpName, value = Data}, Opts) -> - 0 == avp_arity(Name, AvpName) - orelse ?THROW([known_avp_as_AVP, Name, AvpName, Data]), - enc(AvpName, [Data], Opts); - -%% The backdoor ... -enc_AVP(_, {AvpName, Value}, Opts) -> - enc(AvpName, [Value], Opts); - -%% ... and the side door. -enc_AVP(_Name, {_Dict, _AvpName, _Data} = T, Opts) -> - diameter_codec:pack_avp(#diameter_avp{data = T}, Opts). - -%% --------------------------------------------------------------------------- -%% # decode_avps/2 -%% --------------------------------------------------------------------------- - --spec decode_avps(parent_name(), [#diameter_avp{}], map()) - -> {parent_record(), [avp()], Failed} - when Failed :: [{5000..5999, #diameter_avp{}}]. +%% decode_avps/2 decode_avps(Name, Recs, Opts) -> - {Avps, {Rec, Failed}} - = mapfoldl(fun(T,A) -> decode(Name, Opts, T, A) end, - {newrec(Name), []}, - Recs), - {Rec, Avps, Failed ++ missing(Rec, Name, Failed)}. -%% 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. - -newrec(Name) -> - '#new-'(name2rec(Name)). - -%% mapfoldl/3 -%% -%% Like lists:mapfoldl/3, but don't reverse the list. - -mapfoldl(F, Acc, List) -> - mapfoldl(F, Acc, List, []). - -mapfoldl(F, Acc0, [T|Rest], List) -> - {B, Acc} = F(T, Acc0), - mapfoldl(F, Acc, Rest, [B|List]); -mapfoldl(_, Acc, [], List) -> - {List, Acc}. - -%% 3588: -%% -%% DIAMETER_MISSING_AVP 5005 -%% The request did not contain an AVP that is required by the Command -%% Code definition. If this value is sent in the Result-Code AVP, a -%% Failed-AVP AVP SHOULD be included in the message. The Failed-AVP -%% AVP MUST contain an example of the missing AVP complete with the -%% Vendor-Id if applicable. The value field of the missing AVP -%% should be of correct minimum length and contain zeros. - -missing(Rec, Name, Failed) -> - Avps = lists:foldl(fun({_, #diameter_avp{code = C, vendor_id = V}}, A) -> - maps:put({C,V}, true, A) - end, - maps:new(), - Failed), - missing(avp_arity(Name), tl(tuple_to_list(Rec)), Avps, []). - -missing([{Name, Arity} | As], [Value | Vs], Avps, Acc) -> - missing(As, Vs, Avps, case - [H || missing_arity(Arity, Value), - {C,_,V} = H <- [avp_header(Name)], - not maps:is_key({C,V}, Avps)] - of - [H] -> - [{5005, empty_avp(Name, H)} | 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(_, []) -> - false; -has_prefix(N, [_|L]) -> - has_prefix(N-1, L). - -%% empty_avp/2 - -empty_avp(Name, {Code, Flags, VId}) -> - {Name, Type} = avp_name(Code, VId), - #diameter_avp{name = Name, - code = Code, - vendor_id = VId, - is_mandatory = 0 /= (Flags band 2#01000000), - need_encryption = 0 /= (Flags band 2#00100000), - data = empty_value(Name), - type = Type}. - -%% 3588, ch 7: -%% -%% The Result-Code AVP describes the error that the Diameter node -%% encountered in its processing. In case there are multiple errors, -%% the Diameter node MUST report only the first error it encountered -%% (detected possibly in some implementation dependent order). The -%% specific errors that can be described by this AVP are described in -%% the following section. - -%% decode/4 - -decode(Name, Opts, #diameter_avp{code = Code, vendor_id = Vid} = Avp, Acc) -> - decode(Name, Opts, avp_name(Code, Vid), Avp, Acc). - -%% decode/5 - -%% AVP not in dictionary. -decode(Name, Opts, 'AVP', Avp, Acc) -> - decode_AVP(Name, Avp, Opts, Acc); - -%% 6733, 4.4: -%% -%% Receivers of a Grouped AVP that does not have the 'M' (mandatory) -%% bit set and one or more of the encapsulated AVPs within the group -%% has the 'M' (mandatory) bit set MAY simply be ignored if the -%% Grouped AVP itself is unrecognized. The rule applies even if the -%% encapsulated AVP with its 'M' (mandatory) bit set is further -%% encapsulated within other sub-groups, i.e., other Grouped AVPs -%% embedded within the Grouped AVP. -%% -%% The first sentence is slightly mangled, but take it to mean this: -%% -%% An unrecognized AVP of type Grouped that does not set the 'M' bit -%% MAY be ignored even if one of its encapsulated AVPs sets the 'M' -%% bit. -%% -%% The text above is a change from RFC 3588, which instead says this: -%% -%% Further, if any of the AVPs encapsulated within a Grouped AVP has -%% the 'M' (mandatory) bit set, the Grouped AVP itself MUST also -%% include the 'M' bit set. -%% -%% Both of these texts have problems. If the AVP is unknown then its -%% type is unknown since the type isn't sent over the wire, so the -%% 6733 text becomes a non-statement: don't know that the AVP not -%% setting the M-bit is of type Grouped, therefore can't know that its -%% data consists of encapsulated AVPs, therefore can't but ignore that -%% one of these might set the M-bit. It should be no worse if we know -%% the AVP to have type Grouped. -%% -%% Similarly, for the 3588 text: if we receive an AVP that doesn't set -%% the M-bit and don't know that the AVP has type Grouped then we -%% can't realize that its data contains an AVP that sets the M-bit, so -%% can't regard the AVP as erroneous on this account. Again, it should -%% be no worse if the type is known to be Grouped, but in this case -%% the RFC forces us to regard the AVP as erroneous. This is -%% inconsistent, and the 3588 text has never been enforced. -%% -%% So, if an AVP doesn't set the M-bit then we're free to ignore it, -%% regardless of the AVP's type. If we know the type to be Grouped -%% then we must ignore the M-bit on an encapsulated AVP. That means -%% packing such an encapsulated AVP into an 'AVP' field if need be, -%% not regarding the lack of a specific field as an error as is -%% otherwise the case. (The lack of an AVP-specific field being how we -%% defined the RFC's "unrecognized", which is slightly stronger than -%% "not defined".) - -decode(Name, Opts0, {AvpName, Type}, Avp, Acc) -> - #diameter_avp{data = Data, is_mandatory = M} - = Avp, - - %% Whether or not to ignore an M-bit on an encapsulated AVP, or on - %% all AVPs with the service_opt() strict_mbit. - Opts1 = set_strict(Type, M, Opts0), - - %% Whether or not we're decoding within Failed-AVP and should - %% ignore decode errors. - #{dictionary := AppMod, failed_avp := Failed} - = Opts - = set_failed(Name, Opts1), %% Not AvpName or else a failed Failed-AVP - %% decode is packed into 'AVP'. - - %% Reset the dictionary for best-effort decode of Failed-AVP. - Mod = if Failed -> - AppMod; - true -> - ?MODULE - 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. - - try Mod:avp(decode, Data, AvpName, Opts) of - {Rec, As} when Type == 'Grouped' -> - A = Avp#diameter_avp{name = AvpName, - value = Rec, - type = Type}, - {[A|As], pack_avp(Name, A, Opts, Acc)}; - - V when Type /= 'Grouped' -> - A = Avp#diameter_avp{name = AvpName, - value = V, - type = Type}, - {A, pack_avp(Name, A, Opts, Acc)} - catch - throw: {?TAG, {grouped, Error, ComponentAvps}} -> - decode_error(Name, - Error, - ComponentAvps, - Opts, - Avp#diameter_avp{name = AvpName, - data = trim(Avp#diameter_avp.data), - type = Type}, - Acc); - - error: Reason -> - decode_error(Name, - Reason, - Opts, - Avp#diameter_avp{name = AvpName, - data = trim(Avp#diameter_avp.data), - type = Type}, - Acc) - end. - -%% trim/1 -%% -%% Remove any extra bit that was added in diameter_codec to induce a -%% 5014 error. - -trim(#diameter_avp{data = Data} = Avp) -> - Avp#diameter_avp{data = trim(Data)}; - -trim({5014, Bin}) -> - Bin; - -trim(Avps) - when is_list(Avps) -> - lists:map(fun trim/1, Avps); - -trim(Avp) -> - Avp. - -%% decode_error/6 - -decode_error(Name, [_ | Rec], _, #{failed_avp := true} = Opts, Avp, Acc) -> - decode_AVP(Name, Avp#diameter_avp{value = Rec}, Opts, Acc); - -decode_error(Name, _, _, #{failed_avp := true} = Opts, Avp, Acc) -> - decode_AVP(Name, Avp, Opts, Acc); - -decode_error(_, [Error | _], ComponentAvps, _, Avp, Acc) -> - decode_error(Error, Avp, Acc, ComponentAvps); - -decode_error(_, Error, ComponentAvps, _, Avp, Acc) -> - decode_error(Error, Avp, Acc, ComponentAvps). - -%% decode_error/5 - -decode_error(Name, _Reason, #{failed_avp := true} = Opts, Avp, Acc) -> - decode_AVP(Name, Avp, Opts, Acc); - -decode_error(Name, Reason, _, Avp, {Rec, Failed}) -> - Stack = diameter_lib:get_stacktrace(), - diameter_lib:log(decode_error, - ?MODULE, - ?LINE, - {Name, Avp#diameter_avp.name, Stack}), - {Avp, {Rec, [rc(Reason, Avp) | Failed]}}. - -%% decode_error/4 - -decode_error({RC, ErrorData}, Avp, {Rec, Failed}, ComponentAvps) -> - E = Avp#diameter_avp{data = [ErrorData]}, - {[Avp | trim(ComponentAvps)], {Rec, [{RC, E} | Failed]}}. - -%% set_strict/3 - -%% Set false as soon as we see a Grouped AVP that doesn't set the -%% M-bit, to ignore the M-bit on an encapsulated AVP. -set_strict('Grouped', false = M, #{strict_mbit := true} = Opts) -> - Opts#{strict_mbit := M}; -set_strict(_, _, Opts) -> - Opts. - -%% set_failed/2 -%% -%% Set true as soon as we see Failed-AVP. Matching on 'Failed-AVP' -%% assumes that this is the RFC AVP. Strictly, this doesn't need to be -%% the case. - -set_failed('Failed-AVP', #{failed_avp := false} = Opts) -> - Opts#{failed_avp := true}; -set_failed(_, Opts) -> - Opts. - -%% decode_AVP/4 -%% -%% Don't know this AVP: see if it can be packed in an 'AVP' field -%% undecoded. Note that the type field is 'undefined' in this case. - -decode_AVP(Name, Avp, Opts, Acc) -> - {trim(Avp), pack_AVP(Name, Avp, Opts, Acc)}. - -%% rc/1 - -%% diameter_types will raise an error of this form to communicate -%% 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) -> - {RC, Avp#diameter_avp{data = empty_value(AvpName)}}; - -%% 3588: -%% -%% DIAMETER_INVALID_AVP_VALUE 5004 -%% The request contained an AVP with an invalid value in its data -%% portion. A Diameter message indicating this error MUST include -%% the offending AVPs within a Failed-AVP AVP. -rc(_, Avp) -> - {5004, Avp}. - -%% pack_avp/4 - -pack_avp(Name, #diameter_avp{name = AvpName} = Avp, Opts, Acc) -> - pack_avp(Name, avp_arity(Name, AvpName), Avp, Opts, Acc). - -%% pack_avp/5 - -pack_avp(Name, 0, Avp, Opts, Acc) -> - pack_AVP(Name, Avp, Opts, Acc); - -pack_avp(_, Arity, #diameter_avp{name = AvpName} = Avp, _, Acc) -> - pack(Arity, AvpName, Avp, Acc). - -%% pack_AVP/4 - -%% Length failure was induced because of a header/payload length -%% mismatch. The AVP Length is reset to match the received data if -%% this AVP is encoded in an answer message, since the length is -%% computed. -%% -%% Data is a truncated header if command_code = undefined, otherwise -%% payload bytes. The former is padded to the length of a header if -%% the AVP reaches an outgoing encode in diameter_codec. -%% -%% RFC 6733 says that an AVP returned with 5014 can contain a minimal -%% payload for the AVP's type, but in this case we don't know the -%% type. - -pack_AVP(_, #diameter_avp{data = {5014 = RC, Data}} = Avp, _, Acc) -> - {Rec, Failed} = Acc, - {Rec, [{RC, Avp#diameter_avp{data = Data}} | Failed]}; - -pack_AVP(Name, Avp, Opts, Acc) -> - pack_AVP(pack_arity(Name, Opts, Avp), Avp, Acc). - -%% pack_AVP/3 - -pack_AVP(0, #diameter_avp{is_mandatory = M} = Avp, Acc) -> - {Rec, Failed} = Acc, - {Rec, [{if M -> 5001; true -> 5008 end, Avp} | Failed]}; - -pack_AVP(Arity, Avp, Acc) -> - pack(Arity, 'AVP', Avp, Acc). - -%% Give Failed-AVP special treatment since (1) it'll contain any -%% unrecognized mandatory AVP's and (2) the RFC 3588 grammar failed to -%% allow for Failed-AVP in an answer-message. - -pack_arity(Name, - #{strict_mbit := Strict, - failed_avp := Failed}, - #diameter_avp{is_mandatory = M, - name = AvpName}) -> - - %% Not testing just Name /= 'Failed-AVP' means we're changing the - %% packing of AVPs nested within Failed-AVP, but the point of - %% ignoring errors within Failed-AVP is to decode as much as - %% possible, and failing because a mandatory AVP couldn't be - %% packed into a dedicated field defeats that point. - - if Failed == true; - Name == 'Failed-AVP'; - Name == 'answer-message', AvpName == 'Failed-AVP'; - not M; - not Strict -> - avp_arity(Name, 'AVP'); - true -> - 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/4 - -pack(Arity, FieldName, Avp, {Rec, _} = Acc) -> - pack('#get-'(FieldName, Rec), Arity, FieldName, Avp, Acc). - -%% pack/5 - -pack(undefined, 1, 'AVP' = F, Avp, {Rec, Failed}) -> %% unlikely - {'#set-'({F, Avp}, Rec), Failed}; - -pack(undefined, 1, F, #diameter_avp{value = V}, {Rec, Failed}) -> - {'#set-'({F, V}, Rec), Failed}; - -%% 3588: -%% -%% DIAMETER_AVP_OCCURS_TOO_MANY_TIMES 5009 -%% A message was received that included an AVP that appeared more -%% often than permitted in the message definition. The Failed-AVP -%% AVP MUST be included and contain a copy of the first instance of -%% the offending AVP that exceeded the maximum number of occurrences -%% - -pack(_, 1, _, Avp, {Rec, Failed}) -> - {Rec, [{5009, Avp} | Failed]}; - -pack(L, {_, Max}, F, Avp, {Rec, Failed}) -> - case '*' /= Max andalso has_prefix(Max+1, L) of - true -> - {Rec, [{5009, Avp} | Failed]}; - false when F == 'AVP' -> - {'#set-'({F, [Avp | L]}, Rec), Failed}; - false -> - {'#set-'({F, [Avp#diameter_avp.value | L]}, Rec), Failed} - end. - -%% --------------------------------------------------------------------------- -%% # grouped_avp/3 -%% --------------------------------------------------------------------------- - --spec grouped_avp(decode, avp_name(), binary() | {5014, binary()}, term()) - -> {avp_record(), [avp()]}; - (encode, avp_name(), avp_record() | avp_values(), term()) - -> iolist() - | no_return(). - -%% Length error induced by diameter_codec:collect_avps/1: the AVP -%% length in the header was too short (insufficient for the extracted -%% header) or too long (past the end of the message). An empty payload -%% is sufficient according to the RFC text for 5014. -grouped_avp(decode, _Name, {5014 = RC, _Bin}, _) -> - throw({?TAG, {grouped, {RC, []}, []}}); - -grouped_avp(decode, Name, Data, Opts) -> - grouped_decode(Name, diameter_codec:collect_avps(Data), Opts); - -grouped_avp(encode, Name, Data, Opts) -> - encode_avps(Name, Data, Opts). - -%% grouped_decode/2 -%% -%% Note that Grouped is the only AVP type that doesn't just return a -%% decoded value, also returning the list of component diameter_avp -%% records. - -%% Length error in trailing component AVP. -grouped_decode(_Name, {Error, Acc}, _) -> - {5014, Avp} = Error, - throw({?TAG, {grouped, Error, [Avp | Acc]}}); - -%% 7.5. Failed-AVP AVP + diameter_gen:decode_avps(Name, Recs, Opts#{module => ?MODULE}). -%% In the case where the offending AVP is embedded within a Grouped AVP, -%% the Failed-AVP MAY contain the grouped AVP, which in turn contains -%% the single offending AVP. The same method MAY be employed if the -%% grouped AVP itself is embedded in yet another grouped AVP and so on. -%% In this case, the Failed-AVP MAY contain the grouped AVP hierarchy up -%% to the single offending AVP. This enables the recipient to detect -%% the location of the offending AVP when embedded in a group. +%% avp/5 -%% An error in decoding a component AVP throws the first faulty -%% component, which the catch in d/3 wraps in the Grouped AVP in -%% question. A partially decoded record is only used when ignoring -%% errors in Failed-AVP. -grouped_decode(Name, ComponentAvps, Opts) -> - {Rec, Avps, Es} = decode_avps(Name, ComponentAvps, Opts), - [] == Es orelse throw({?TAG, {grouped, [{_,_} = hd(Es) | Rec], Avps}}), - {Rec, Avps}. +avp(T, Data, Name, Opts, Mod) -> + Mod:avp(T, Data, Name, Opts#{module := Mod}). -%% --------------------------------------------------------------------------- -%% # empty_group/1 -%% --------------------------------------------------------------------------- +%% grouped_avp/4 -empty_group(Name) -> - list_to_binary([z(F,A) || {F,A} <- avp_arity(Name)]). +grouped_avp(T, Name, Data, Opts) -> + diameter_gen:grouped_avp(T, Name, Data, Opts). -z(Name, 1) -> - z(Name); -z(_, {0,_}) -> - []; -z(Name, {Min, _}) -> - binary:copy(z(Name), Min). +%% empty_group/2 -z('AVP') -> - <<0:64>>; %% minimal header -z(Name) -> - Bin = diameter_codec:pack_data(avp_header(Name), empty_value(Name)), - Sz = iolist_size(Bin), - <<0:Sz/unit:8>>. +empty_group(Name, Opts) -> + diameter_gen:empty_group(Name, Opts). -%% --------------------------------------------------------------------------- -%% # empty/1 -%% --------------------------------------------------------------------------- +%% empty/2 -empty(AvpName) -> - avp(encode, zero, AvpName, _Opts = #{}). +empty(Name, Opts) -> + diameter_gen:empty(Name, Opts). diff --git a/lib/diameter/src/base/diameter_gen.erl b/lib/diameter/src/base/diameter_gen.erl new file mode 100644 index 0000000000..e832832876 --- /dev/null +++ b/lib/diameter/src/base/diameter_gen.erl @@ -0,0 +1,709 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +%% This file contains code that encode/decode modules generated by +%% diameter_codegen.erl calls to implement the functionality. This +%% code does most of the work, the generated code being kept simple. +%% + +-module(diameter_gen). + +-export([encode_avps/3, + decode_avps/3, + grouped_avp/4, + empty_group/2, + empty/2]). + +-include_lib("diameter/include/diameter.hrl"). + +-define(THROW(T), throw({?MODULE, T})). + +-type parent_name() :: atom(). %% parent = Message or AVP +-type parent_record() :: tuple(). %% +-type avp_name() :: atom(). +-type avp_record() :: tuple(). +-type avp_values() :: [{avp_name(), term()}]. + +-type non_grouped_avp() :: #diameter_avp{}. +-type grouped_avp() :: nonempty_improper_list(#diameter_avp{}, [avp()]). +-type avp() :: non_grouped_avp() | grouped_avp(). + +%% --------------------------------------------------------------------------- +%% # encode_avps/3 +%% --------------------------------------------------------------------------- + +-spec encode_avps(parent_name(), parent_record() | avp_values(), map()) + -> iolist() + | no_return(). + +encode_avps(Name, Vals, #{module := Mod} = Opts) -> + try + encode(Name, Vals, Opts, Mod) + catch + throw: {?MODULE, Reason} -> + diameter_lib:log({encode, error}, + ?MODULE, + ?LINE, + {Reason, Name, Vals, Mod}), + erlang:error(list_to_tuple(Reason ++ [Name])); + error: Reason -> + Stack = erlang:get_stacktrace(), + diameter_lib:log({encode, failure}, + ?MODULE, + ?LINE, + {Reason, Name, Vals, Mod, Stack}), + erlang:error({encode_failure, Reason, Name, Stack}) + end. + +%% encode/4 + +encode(Name, Vals, #{ordered_encode := false} = Opts, Mod) + when is_list(Vals) -> + lists:map(fun({F,V}) -> encode(Name, F, V, Opts, Mod) end, Vals); + +encode(Name, Vals, Opts, Mod) + when is_list(Vals) -> + encode(Name, Mod:'#set-'(Vals, newrec(Mod, Name)), Opts, Mod); + +encode(Name, Rec, Opts, Mod) -> + [encode(Name, F, V, Opts, Mod) || {F,V} <- Mod:'#get-'(Rec)]. + +%% encode/5 + +encode(Name, AvpName, Values, Opts, Mod) -> + enc(Name, AvpName, Mod:avp_arity(Name, AvpName), Values, Opts, Mod). + +%% enc/6 + +enc(_, AvpName, 1, undefined, _, _) -> + ?THROW([mandatory_avp_missing, AvpName]); + +enc(Name, AvpName, 1, Value, Opts, Mod) -> + enc(Name, AvpName, [Value], Opts, Mod); + +enc(_, _, {0,_}, [], _, _) -> + []; + +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(_, AvpName, {_, Max}, L, _, _) + when Max < length(L) -> + ?THROW([repeated_avp_excessive_arity, AvpName, Max, L]); + +enc(Name, AvpName, _, Values, Opts, Mod) -> + enc(Name, AvpName, Values, Opts, Mod). + +%% enc/5 + +enc(Name, 'AVP', Values, Opts, Mod) -> + [enc_AVP(Name, A, Opts, Mod) || A <- Values]; + +enc(_, AvpName, Values, Opts, Mod) -> + enc(AvpName, Values, Opts, Mod). + +%% enc/4 + +enc(AvpName, Values, Opts, Mod) -> + H = Mod:avp_header(AvpName), + [diameter_codec:pack_data(H, Mod:avp(encode, V, AvpName, Opts)) + || V <- Values]. + +%% enc_AVP/4 + +%% No value: assume AVP data is already encoded. The normal case will +%% be when this is passed back from #diameter_packet.errors as a +%% consequence of a failed decode. Any AVP can be encoded this way +%% however, which side-steps any arity checks for known AVP's and +%% could potentially encode something unfortunate. +enc_AVP(_, #diameter_avp{value = undefined} = A, Opts, _) -> + diameter_codec:pack_avp(A, Opts); + +%% Missing name for value encode. +enc_AVP(_, #diameter_avp{name = N, value = V}, _, _) + when N == undefined; + N == 'AVP' -> + ?THROW([value_with_nameless_avp, N, V]); + +%% Or not. Ensure that 'AVP' is the appropriate field. Note that if we +%% don't know this AVP at all then the encode will fail. +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); + +%% The backdoor ... +enc_AVP(_, {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). + +%% --------------------------------------------------------------------------- +%% # decode_avps/3 +%% --------------------------------------------------------------------------- + +-spec decode_avps(parent_name(), [#diameter_avp{}], map()) + -> {parent_record(), [avp()], Failed} + when Failed :: [{5000..5999, #diameter_avp{}}]. + +decode_avps(Name, Recs, #{module := Mod} = Opts) -> + {Avps, {Rec, Failed}} + = mapfoldl(fun(T,A) -> decode(Name, Opts, Mod, T, A) end, + {newrec(Mod, Name), []}, + Recs), + {Rec, Avps, Failed ++ missing(Rec, Name, Failed, Opts, Mod)}. +%% 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. + +%% mapfoldl/3 +%% +%% Like lists:mapfoldl/3, but don't reverse the list. + +mapfoldl(F, Acc, List) -> + mapfoldl(F, Acc, List, []). + +mapfoldl(F, Acc0, [T|Rest], List) -> + {B, Acc} = F(T, Acc0), + mapfoldl(F, Acc, Rest, [B|List]); +mapfoldl(_, Acc, [], List) -> + {List, Acc}. + +%% 3588: +%% +%% DIAMETER_MISSING_AVP 5005 +%% The request did not contain an AVP that is required by the Command +%% Code definition. If this value is sent in the Result-Code AVP, a +%% Failed-AVP AVP SHOULD be included in the message. The Failed-AVP +%% AVP MUST contain an example of the missing AVP complete with the +%% 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(_, []) -> + false; +has_prefix(N, [_|L]) -> + has_prefix(N-1, L). + +%% empty_avp/4 + +empty_avp(Name, {Code, Flags, VId}, Opts, Mod) -> + {Name, Type} = Mod:avp_name(Code, VId), + #diameter_avp{name = Name, + code = Code, + vendor_id = VId, + is_mandatory = 0 /= (Flags band 2#01000000), + need_encryption = 0 /= (Flags band 2#00100000), + data = Mod:empty_value(Name, Opts), + type = Type}. + +%% 3588, ch 7: +%% +%% The Result-Code AVP describes the error that the Diameter node +%% encountered in its processing. In case there are multiple errors, +%% the Diameter node MUST report only the first error it encountered +%% (detected possibly in some implementation dependent order). The +%% specific errors that can be described by this AVP are described in +%% the following section. + +%% decode/5 + +decode(Name, + Opts, + Mod, + #diameter_avp{code = Code, vendor_id = Vid} + = Avp, + Acc) -> + decode(Name, Opts, Mod, Mod:avp_name(Code, Vid), Avp, Acc). + +%% decode/6 + +%% AVP not in dictionary. +decode(Name, Opts, Mod, 'AVP', Avp, Acc) -> + decode_AVP(Name, Avp, Opts, Mod, Acc); + +%% 6733, 4.4: +%% +%% Receivers of a Grouped AVP that does not have the 'M' (mandatory) +%% bit set and one or more of the encapsulated AVPs within the group +%% has the 'M' (mandatory) bit set MAY simply be ignored if the +%% Grouped AVP itself is unrecognized. The rule applies even if the +%% encapsulated AVP with its 'M' (mandatory) bit set is further +%% encapsulated within other sub-groups, i.e., other Grouped AVPs +%% embedded within the Grouped AVP. +%% +%% The first sentence is slightly mangled, but take it to mean this: +%% +%% An unrecognized AVP of type Grouped that does not set the 'M' bit +%% MAY be ignored even if one of its encapsulated AVPs sets the 'M' +%% bit. +%% +%% The text above is a change from RFC 3588, which instead says this: +%% +%% Further, if any of the AVPs encapsulated within a Grouped AVP has +%% the 'M' (mandatory) bit set, the Grouped AVP itself MUST also +%% include the 'M' bit set. +%% +%% Both of these texts have problems. If the AVP is unknown then its +%% type is unknown since the type isn't sent over the wire, so the +%% 6733 text becomes a non-statement: don't know that the AVP not +%% setting the M-bit is of type Grouped, therefore can't know that its +%% data consists of encapsulated AVPs, therefore can't but ignore that +%% one of these might set the M-bit. It should be no worse if we know +%% the AVP to have type Grouped. +%% +%% Similarly, for the 3588 text: if we receive an AVP that doesn't set +%% the M-bit and don't know that the AVP has type Grouped then we +%% can't realize that its data contains an AVP that sets the M-bit, so +%% can't regard the AVP as erroneous on this account. Again, it should +%% be no worse if the type is known to be Grouped, but in this case +%% the RFC forces us to regard the AVP as erroneous. This is +%% inconsistent, and the 3588 text has never been enforced. +%% +%% So, if an AVP doesn't set the M-bit then we're free to ignore it, +%% regardless of the AVP's type. If we know the type to be Grouped +%% then we must ignore the M-bit on an encapsulated AVP. That means +%% packing such an encapsulated AVP into an 'AVP' field if need be, +%% not regarding the lack of a specific field as an error as is +%% otherwise the case. (The lack of an AVP-specific field being how we +%% defined the RFC's "unrecognized", which is slightly stronger than +%% "not defined".) + +decode(Name, Opts0, Mod, {AvpName, Type}, Avp, Acc) -> + #diameter_avp{data = Data, is_mandatory = M} + = Avp, + + %% Whether or not to ignore an M-bit on an encapsulated AVP, or on + %% all AVPs with the service_opt() strict_mbit. + Opts1 = set_strict(Type, M, Opts0), + + %% Whether or not we're decoding within Failed-AVP and should + %% ignore decode errors. + #{dictionary := AppMod, failed_avp := Failed} + = Opts + = set_failed(Name, Opts1), %% Not AvpName or else a failed Failed-AVP + %% decode is packed into 'AVP'. + + %% Reset the dictionary for best-effort decode of Failed-AVP. + 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. + + try avp_decode(Data, AvpName, Opts, DecMod, Mod) of + {Rec, As} when Type == 'Grouped' -> + A = Avp#diameter_avp{name = AvpName, + value = Rec, + type = Type}, + {[A|As], pack_avp(Name, A, Opts, Mod, Acc)}; + + V when Type /= 'Grouped' -> + A = Avp#diameter_avp{name = AvpName, + value = V, + type = Type}, + {A, pack_avp(Name, A, Opts, Mod, Acc)} + catch + throw: {?MODULE, {grouped, Error, ComponentAvps}} -> + decode_error(Name, + Error, + ComponentAvps, + Opts, + Mod, + Avp#diameter_avp{name = AvpName, + data = trim(Avp#diameter_avp.data), + type = Type}, + Acc); + + error: Reason -> + decode_error(Name, + Reason, + Opts, + Mod, + Avp#diameter_avp{name = AvpName, + data = trim(Avp#diameter_avp.data), + type = Type}, + Acc) + end. + +%% avp_decode/5 + +avp_decode(Data, AvpName, Opts, Mod, Mod) -> + Mod:avp(decode, Data, AvpName, Opts); + +avp_decode(Data, AvpName, Opts, Mod, _) -> + Mod:avp(decode, Data, AvpName, Opts, Mod). + +%% trim/1 +%% +%% Remove any extra bit that was added in diameter_codec to induce a +%% 5014 error. + +trim(#diameter_avp{data = Data} = Avp) -> + Avp#diameter_avp{data = trim(Data)}; + +trim({5014, Bin}) -> + Bin; + +trim(Avps) + when is_list(Avps) -> + lists:map(fun trim/1, Avps); + +trim(Avp) -> + Avp. + +%% decode_error/7 + +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) -> + decode_AVP(Name, Avp, Opts, Mod, Acc); + +decode_error(_, [Error | _], ComponentAvps, _, _, Avp, Acc) -> + decode_error(Error, Avp, Acc, ComponentAvps); + +decode_error(_, Error, ComponentAvps, _, _, Avp, Acc) -> + decode_error(Error, Avp, Acc, ComponentAvps). + +%% decode_error/5 + +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}) -> + Stack = diameter_lib:get_stacktrace(), + diameter_lib:log(decode_error, + ?MODULE, + ?LINE, + {Reason, Name, Avp#diameter_avp.name, Mod, Stack}), + {Avp, {Rec, [rc(Reason, Avp, Opts, Mod) | Failed]}}. + +%% decode_error/4 + +decode_error({RC, ErrorData}, Avp, {Rec, Failed}, ComponentAvps) -> + E = Avp#diameter_avp{data = [ErrorData]}, + {[Avp | trim(ComponentAvps)], {Rec, [{RC, E} | Failed]}}. + +%% set_strict/3 + +%% Set false as soon as we see a Grouped AVP that doesn't set the +%% M-bit, to ignore the M-bit on an encapsulated AVP. +set_strict('Grouped', false = M, #{strict_mbit := true} = Opts) -> + Opts#{strict_mbit := M}; +set_strict(_, _, Opts) -> + Opts. + +%% set_failed/2 +%% +%% Set true as soon as we see Failed-AVP. Matching on 'Failed-AVP' +%% assumes that this is the RFC AVP. Strictly, this doesn't need to be +%% the case. + +set_failed('Failed-AVP', #{failed_avp := false} = Opts) -> + Opts#{failed_avp := true}; +set_failed(_, Opts) -> + Opts. + +%% decode_AVP/5 +%% +%% Don't know this AVP: see if it can be packed in an 'AVP' field +%% undecoded. Note that the type field is 'undefined' in this case. + +decode_AVP(Name, Avp, Opts, Mod, Acc) -> + {trim(Avp), pack_AVP(Name, Avp, Opts, Mod, Acc)}. + +%% rc/2 + +%% diameter_types will raise an error of this form to communicate +%% 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)}}; + +%% 3588: +%% +%% DIAMETER_INVALID_AVP_VALUE 5004 +%% The request contained an AVP with an invalid value in its data +%% portion. A Diameter message indicating this error MUST include +%% the offending AVPs within a Failed-AVP AVP. +rc(_, Avp, _, _) -> + {5004, Avp}. + +%% pack_avp/5 + +pack_avp(Name, #diameter_avp{name = AvpName} = Avp, Opts, Mod, Acc) -> + pack_avp(Name, Mod:avp_arity(Name, AvpName), Avp, Opts, Mod, Acc). + +%% pack_avp/6 + +pack_avp(Name, 0, Avp, Opts, Mod, Acc) -> + pack_AVP(Name, Avp, Opts, Mod, Acc); + +pack_avp(_, Arity, #diameter_avp{name = AvpName} = Avp, _Opts, Mod, Acc) -> + pack(Arity, AvpName, Avp, Mod, Acc). + +%% pack_AVP/5 + +%% Length failure was induced because of a header/payload length +%% mismatch. The AVP Length is reset to match the received data if +%% this AVP is encoded in an answer message, since the length is +%% computed. +%% +%% Data is a truncated header if command_code = undefined, otherwise +%% payload bytes. The former is padded to the length of a header if +%% the AVP reaches an outgoing encode in diameter_codec. +%% +%% RFC 6733 says that an AVP returned with 5014 can contain a minimal +%% payload for the AVP's type, but in this case we don't know the +%% type. + +pack_AVP(_, #diameter_avp{data = {5014 = RC, Data}} = Avp, _, _, Acc) -> + {Rec, Failed} = Acc, + {Rec, [{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 + +pack_arity(_, 0, #diameter_avp{is_mandatory = M} = Avp, _, Acc) -> + {Rec, Failed} = Acc, + {Rec, [{if M -> 5001; true -> 5008 end, Avp} | Failed]}; + +pack_arity(_, Arity, Avp, Mod, Acc) -> + pack(Arity, 'AVP', Avp, Mod, Acc). + +%% Give Failed-AVP special treatment since (1) it'll contain any +%% unrecognized mandatory AVP's and (2) the RFC 3588 grammar failed to +%% allow for Failed-AVP in an answer-message. + +pack_arity(Name, + #{strict_mbit := Strict, + failed_avp := Failed}, + Mod, + #diameter_avp{is_mandatory = M, + name = AvpName}) -> + + %% Not testing just Name /= 'Failed-AVP' means we're changing the + %% packing of AVPs nested within Failed-AVP, but the point of + %% ignoring errors within Failed-AVP is to decode as much as + %% possible, and failing because a mandatory AVP couldn't be + %% packed into a dedicated field defeats that point. + + if Failed == true; + Name == 'Failed-AVP'; + Name == 'answer-message', AvpName == 'Failed-AVP'; + not M; + not Strict -> + Mod:avp_arity(Name, 'AVP'); + true -> + 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}; + +%% 3588: +%% +%% DIAMETER_AVP_OCCURS_TOO_MANY_TIMES 5009 +%% A message was received that included an AVP that appeared more +%% often than permitted in the message definition. The Failed-AVP +%% AVP MUST be included and contain a copy of the first instance of +%% the offending AVP that exceeded the maximum number of occurrences +%% + +pack(_, 1, _, Avp, _, {Rec, Failed}) -> + {Rec, [{5009, Avp} | Failed]}; + +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. + +%% --------------------------------------------------------------------------- +%% # grouped_avp/3 +%% --------------------------------------------------------------------------- + +-spec grouped_avp(decode, avp_name(), binary() | {5014, binary()}, term()) + -> {avp_record(), [avp()]}; + (encode, avp_name(), avp_record() | avp_values(), term()) + -> iolist() + | no_return(). + +%% Length error induced by diameter_codec:collect_avps/1: the AVP +%% length in the header was too short (insufficient for the extracted +%% header) or too long (past the end of the message). An empty payload +%% is sufficient according to the RFC text for 5014. +grouped_avp(decode, _Name, {5014 = RC, _Bin}, _) -> + ?THROW({grouped, {RC, []}, []}); + +grouped_avp(decode, Name, Data, Opts) -> + grouped_decode(Name, diameter_codec:collect_avps(Data), Opts); + +grouped_avp(encode, Name, Data, Opts) -> + encode_avps(Name, Data, Opts). + +%% grouped_decode/2 +%% +%% Note that Grouped is the only AVP type that doesn't just return a +%% decoded value, also returning the list of component diameter_avp +%% records. + +%% Length error in trailing component AVP. +grouped_decode(_Name, {Error, Acc}, _) -> + {5014, Avp} = Error, + ?THROW({grouped, Error, [Avp | Acc]}); + +%% 7.5. Failed-AVP AVP + +%% In the case where the offending AVP is embedded within a Grouped AVP, +%% the Failed-AVP MAY contain the grouped AVP, which in turn contains +%% the single offending AVP. The same method MAY be employed if the +%% grouped AVP itself is embedded in yet another grouped AVP and so on. +%% In this case, the Failed-AVP MAY contain the grouped AVP hierarchy up +%% to the single offending AVP. This enables the recipient to detect +%% the location of the offending AVP when embedded in a group. + +%% An error in decoding a component AVP throws the first faulty +%% component, which the catch in d/3 wraps in the Grouped AVP in +%% question. A partially decoded record is only used when ignoring +%% errors in Failed-AVP. +grouped_decode(Name, ComponentAvps, Opts) -> + {Rec, Avps, Es} = decode_avps(Name, ComponentAvps, Opts), + [] == Es orelse ?THROW({grouped, [{_,_} = hd(Es) | Rec], Avps}), + {Rec, Avps}. + +%% --------------------------------------------------------------------------- +%% # empty_group/2 +%% --------------------------------------------------------------------------- + +empty_group(Name, #{module := Mod} = Opts) -> + list_to_binary([z(F, A, Opts, Mod) || {F,A} <- Mod:avp_arity(Name)]). + +z(Name, 1, Opts, Mod) -> + z(Name, Opts, Mod); +z(_, {0,_}, _, _) -> + []; +z(Name, {Min, _}, Opts, Mod) -> + binary:copy(z(Name, Opts, Mod), Min). + +z('AVP', _, _) -> + <<0:64>>; %% minimal header +z(Name, Opts, Mod) -> + Bin = diameter_codec:pack_data(Mod:avp_header(Name), + Mod:empty_value(Name, Opts)), + Sz = iolist_size(Bin), + <<0:Sz/unit:8>>. + +%% --------------------------------------------------------------------------- +%% # empty/2 +%% --------------------------------------------------------------------------- + +empty(Name, #{module := Mod} = Opts) -> + Mod:avp(encode, zero, Name, Opts). + +%% ------------------------------------------------------------------------------ + +newrec(Mod, Name) -> + Mod:'#new-'(Mod:name2rec(Name)). diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl index 5a1e3ba941..2a1c0db381 100644 --- a/lib/diameter/src/compiler/diameter_codegen.erl +++ b/lib/diameter/src/compiler/diameter_codegen.erl @@ -152,6 +152,7 @@ erl_forms(Mod, ParseD) -> {vendor_name, 0}, {decode_avps, 3}, %% in diameter_gen.hrl {encode_avps, 3}, %% + {grouped_avp, 4}, %% {msg_name, 2}, {msg_header, 1}, {rec2msg, 1}, @@ -162,9 +163,8 @@ erl_forms(Mod, ParseD) -> {avp_arity, 2}, {avp_header, 1}, {avp, 4}, - {grouped_avp, 4}, {enumerated_avp, 3}, - {empty_value, 1}, + {empty_value, 2}, {dict, 0}]}, %% diameter.hrl is included for #diameter_avp {?attribute, include_lib, "diameter/include/diameter.hrl"}, @@ -557,10 +557,11 @@ imported_avp(Mod, {AvpName, _, _, _}, _) -> c_imported_avp(Mod, AvpName) -> {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName), ?VAR('Opts')], [], - [?APPLY(Mod, avp, [?VAR('T'), - ?VAR('Data'), - ?Atom(AvpName), - ?VAR('Opts')])]}. + [?CALL(avp, [?VAR('T'), + ?VAR('Data'), + ?Atom(AvpName), + ?VAR('Opts'), + ?ATOM(Mod)])]}. cs_custom_avp({Mod, Key, Avps}, Dict) -> lists:map(fun(N) -> c_custom_avp(Mod, Key, N, orddict:fetch(N, Dict)) end, @@ -720,7 +721,7 @@ v(false, _, _, _) -> %%% ------------------------------------------------------------------------ f_empty_value(ParseD) -> - {?function, empty_value, 1, empty_value(ParseD)}. + {?function, empty_value, 2, empty_value(ParseD)}. empty_value(ParseD) -> Imported = lists:flatmap(fun avps/1, get_value(import_enums, ParseD)), @@ -730,15 +731,17 @@ empty_value(ParseD) -> not lists:keymember(N, 1, Imported)] ++ Imported, lists:map(fun c_empty_value/1, Groups ++ Enums) - ++ [{?clause, [?VAR('Name')], [], [?CALL(empty, [?VAR('Name')])]}]. + ++ [{?clause, [?VAR('Name'), ?VAR('Opts')], + [], + [?CALL(empty, [?VAR('Name'), ?VAR('Opts')])]}]. c_empty_value({Name, _, _, _}) -> - {?clause, [?Atom(Name)], + {?clause, [?Atom(Name), ?VAR('Opts')], [], - [?CALL(empty_group, [?Atom(Name)])]}; + [?CALL(empty_group, [?Atom(Name), ?VAR('Opts')])]}; c_empty_value({Name, _}) -> - {?clause, [?Atom(Name)], + {?clause, [?Atom(Name), ?VAR('_')], [], [?TERM(<<0:32>>)]}. diff --git a/lib/diameter/src/modules.mk b/lib/diameter/src/modules.mk index d8867a5ed2..bb3b234d20 100644 --- a/lib/diameter/src/modules.mk +++ b/lib/diameter/src/modules.mk @@ -1,7 +1,7 @@ # %CopyrightBegin% # -# Copyright Ericsson AB 2010-2016. All Rights Reserved. +# Copyright Ericsson AB 2010-2017. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -39,6 +39,7 @@ RT_MODULES = \ base/diameter_config \ base/diameter_config_sup \ base/diameter_codec \ + base/diameter_gen \ base/diameter_lib \ base/diameter_misc_sup \ base/diameter_peer \ diff --git a/lib/diameter/test/diameter_codec_test.erl b/lib/diameter/test/diameter_codec_test.erl index ccb97615da..b548f85cb8 100644 --- a/lib/diameter/test/diameter_codec_test.erl +++ b/lib/diameter/test/diameter_codec_test.erl @@ -171,7 +171,7 @@ gen(M, avp_types, {Name, Code, Type, _Flags}) -> V = undefined /= VendorId, V = 0 /= Flags band 2#10000000, {Name, Type} = M:avp_name(Code, VendorId), - B = M:empty_value(Name), + B = M:empty_value(Name, #{module => M}), B = z(B), [] = avp_decode(M, Type, Name); @@ -215,7 +215,8 @@ avp(Mod, encode = X, V, Name, _) -> iolist_to_binary(Mod:avp(X, V, Name, opts(Mod))). opts(Mod) -> - (opts())#{dictionary => Mod}. + (opts())#{module => Mod, + dictionary => Mod}. opts() -> #{string_decode => true, diff --git a/lib/diameter/test/diameter_compiler_SUITE.erl b/lib/diameter/test/diameter_compiler_SUITE.erl index dea14e3870..73fe1ef6e0 100644 --- a/lib/diameter/test/diameter_compiler_SUITE.erl +++ b/lib/diameter/test/diameter_compiler_SUITE.erl @@ -552,7 +552,7 @@ flatten2(_Config) -> T <- [encode, decode], M <- [M2, M3], Ref <- [make_ref()], - RC <- [M:avp(T, Ref, A, #{})], + RC <- [M:avp(T, Ref, A, #{module => M})], RC /= {T, Ref}]. 'A1'(T, 'Unsigned32', Ref, _Opts) -> -- cgit v1.2.3 From fd2850798f68c9a3c502ad9d66ef46561816ab6f Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Mon, 12 Jun 2017 21:54:50 +0200 Subject: Let spawn_opt config replace erlang:spawn_opt/2 for request processes By accepting an MFA that is applied to the fun that is otherwise spawned for each incoming request, to allow handler processes to be reused. This is not yet documented and may change, but the motivation is to let spawn be replaced by process pool, from which the MFA selects. A list-valued spawn_opt is equivalent to {erlang, spawn_opt, [Opts]}. --- lib/diameter/src/base/diameter_config.erl | 7 +++++ lib/diameter/src/base/diameter_service.erl | 2 +- lib/diameter/src/base/diameter_traffic.erl | 42 ++++++++++++++++++++++++---- lib/diameter/test/diameter_traffic_SUITE.erl | 7 ++--- 4 files changed, 47 insertions(+), 11 deletions(-) diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl index 1db9b52dfa..99d8c8c6ec 100644 --- a/lib/diameter/src/base/diameter_config.erl +++ b/lib/diameter/src/base/diameter_config.erl @@ -610,6 +610,9 @@ opt({watchdog_timer, Tmo}) -> opt({watchdog_config, L}) -> is_list(L) andalso lists:all(fun wdopt/1, L); +opt({spawn_opt, {M,F,A}}) + when is_atom(M), is_atom(F), is_list(A) -> + true; opt({spawn_opt = K, Opts}) -> if is_list(Opts) -> {value, {K, spawn_opts(Opts)}}; @@ -739,6 +742,10 @@ opt(incoming_maxlen, N) when 0 =< N, N < 1 bsl 24 -> N; +opt(spawn_opt, {M,F,A} = T) + when is_atom(M), is_atom(F), is_list(A) -> + T; + opt(spawn_opt, L) when is_list(L) -> spawn_opts(L); diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl index be50e87179..8e383818ea 100644 --- a/lib/diameter/src/base/diameter_service.erl +++ b/lib/diameter/src/base/diameter_service.erl @@ -114,7 +114,7 @@ incoming_maxlen := diameter:message_length(), strict_mbit := boolean(), string_decode := boolean(), - spawn_opt := list()}}). + spawn_opt := list() | {module(), atom(), list()}}}). %% Record representing an RFC 3539 watchdog process implemented by %% diameter_watchdog. diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index 54f39afbf0..af7ac10f13 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -212,8 +212,9 @@ incr_rc(Dir, Pkt, TPid, Dict0) -> %% --------------------------------------------------------------------------- -spec receive_message(pid(), Route, #diameter_packet{}, module(), RecvData) - -> pid() - | boolean() + -> pid() %% request handler + | boolean() %% answer, known request or not + | discard %% request discarded by MFA when Route :: {Handler, RequestRef, Seqs} | Ack, RecvData :: {[SpawnOpt], #recvdata{}}, @@ -230,9 +231,10 @@ receive_message(TPid, Route, Pkt, Dict0, RecvData) -> %% recv/6 %% Incoming request ... -recv(true, Ack, TPid, Pkt, Dict0, RecvData) +recv(true, Ack, TPid, Pkt, Dict0, T) when is_boolean(Ack) -> - spawn_request(Ack, TPid, Pkt, Dict0, RecvData); + {Opts, RecvData} = T, + spawn_request(Ack, TPid, Pkt, Dict0, RecvData, Opts); %% ... answer to known request ... recv(false, {Pid, Ref, TPid}, _, Pkt, Dict0, _) -> @@ -254,18 +256,46 @@ recv(false, false, TPid, Pkt, _, _) -> incr(TPid, {{unknown, 0}, recv, discarded}), false. -%% spawn_request/5 +%% spawn_request/6 -spawn_request(Ack, TPid, Pkt, Dict0, {Opts, RecvData}) -> +%% An MFA should return a pid() or the atom 'discard'. The latter +%% results in an acknowledgment back to the transport process when +%% appropriate, to ensure that send/recv callbacks can count +%% outstanding requests. Acknowledgement is implicit if the +%% handler process dies (in a handle_request callback for example). +spawn_request(Ack, TPid, Pkt, Dict0, RecvData, {M,F,A}) -> + ReqF = fun() -> + ack(Ack, TPid, recv_request(Ack, TPid, Pkt, Dict0, RecvData)) + end, + ack(Ack, TPid, apply(M, F, [ReqF | A])); + +%% A spawned process acks implicitly when it dies, so there's no need +%% to handle 'discard'. +spawn_request(Ack, TPid, Pkt, Dict0, RecvData, Opts) -> spawn_opt(fun() -> recv_request(Ack, TPid, Pkt, Dict0, RecvData) end, Opts). +%% ack/3 + +ack(Ack, TPid, RC) -> + RC == discard andalso Ack andalso (TPid ! {send, false}), + RC. + %% --------------------------------------------------------------------------- %% recv_request/5 %% --------------------------------------------------------------------------- +-spec recv_request(Ack :: boolean(), + TPid :: pid(), + #diameter_packet{}, + Dict0 :: module(), + #recvdata{}) + -> ok %% answer was sent + | discard %% or not + | false. %% no transport + recv_request(Ack, TPid, #diameter_packet{header = #diameter_header{application_id = Id}} diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl index f2e796005d..7c36b3f82b 100644 --- a/lib/diameter/test/diameter_traffic_SUITE.erl +++ b/lib/diameter/test/diameter_traffic_SUITE.erl @@ -203,8 +203,7 @@ {'Acct-Application-Id', [?DIAMETER_APP_ID_ACCOUNTING]}, {restrict_connections, false}, {string_decode, Decode}, - {incoming_maxlen, 1 bsl 21}, - {spawn_opt, [{min_heap_size, 5000}]} + {incoming_maxlen, 1 bsl 21} | [{application, [{dictionary, D}, {module, ?MODULE}, {answer_errors, callback}]} @@ -466,8 +465,8 @@ add_transports(Config) -> || T == sctp andalso CS]], [{capabilities_cb, fun capx/2}, {pool_size, 8}, - {spawn_opt, [{min_heap_size, 8096}]}, - {applications, apps(rfc3588)}]), + {applications, apps(rfc3588)}] + ++ [{spawn_opt, {erlang, spawn, []}} || CS]), Cs = [?util:connect(CN, [T, {sender, CS}], LRef, -- cgit v1.2.3