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