diff options
Diffstat (limited to 'lib/diameter/src/base/diameter_config.erl')
-rw-r--r-- | lib/diameter/src/base/diameter_config.erl | 232 |
1 files changed, 162 insertions, 70 deletions
diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl index 63d28f25a2..fc5c284bf2 100644 --- a/lib/diameter/src/base/diameter_config.erl +++ b/lib/diameter/src/base/diameter_config.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2012. All Rights Reserved. +%% Copyright Ericsson AB 2010-2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -103,6 +103,10 @@ %% Time to lay low before restarting a dead service. -define(RESTART_SLEEP, 2000). +%% Test for a valid timeout. +-define(IS_UINT32(N), + is_integer(N) andalso 0 =< N andalso 0 == N bsr 32). + %% A minimal diameter_caps for checking for valid capabilities values. -define(EXAMPLE_CAPS, #diameter_caps{origin_host = "TheHost", @@ -113,15 +117,22 @@ -define(VALUES(Rec), tl(tuple_to_list(Rec))). +%% The RFC 3588 common dictionary is used to validate capabilities +%% configuration. That a given transport may use the RFC 6733 +%% dictionary is of no consequence. +-define(BASE, diameter_gen_base_rfc3588). + %%% The return values below assume the server diameter_config is started. %%% The functions will exit if it isn't. %% -------------------------------------------------------------------------- -%% # start_service(SvcName, Opts) -%% -%% Output: ok | {error, Reason} +%% # start_service/2 %% -------------------------------------------------------------------------- +-spec start_service(diameter:service_name(), [diameter:service_opt()]) + -> ok + | {error, term()}. + start_service(SvcName, Opts) when is_list(Opts) -> start_rc(sync(SvcName, {start_service, SvcName, Opts})). @@ -134,22 +145,23 @@ start_rc(timeout) -> {error, application_not_started}. %% -------------------------------------------------------------------------- -%% # stop_service(SvcName) -%% -%% Output: ok +%% # stop_service/1 %% -------------------------------------------------------------------------- +-spec stop_service(diameter:service_name()) + -> ok. + stop_service(SvcName) -> sync(SvcName, {stop_service, SvcName}). %% -------------------------------------------------------------------------- -%% # add_transport(SvcName, {Type, Opts}) -%% -%% Input: Type = connect | listen -%% -%% Output: {ok, Ref} | {error, Reason} +%% # add_transport/2 %% -------------------------------------------------------------------------- +-spec add_transport(diameter:service_name(), {connect|listen, [diameter:transport_opt()]}) + -> {ok, diameter:transport_ref()} + | {error, term()}. + add_transport(SvcName, {T, Opts}) when is_list(Opts), (T == connect orelse T == listen) -> sync(SvcName, {add, SvcName, T, Opts}). @@ -171,6 +183,10 @@ add_transport(SvcName, {T, Opts}) %% Output: ok | {error, Reason} %% -------------------------------------------------------------------------- +-spec remove_transport(diameter:service_name(), diameter:transport_pred()) + -> ok + | {error, term()}. + remove_transport(SvcName, Pred) -> try sync(SvcName, {remove, SvcName, pred(Pred)}) @@ -473,14 +489,16 @@ stop(SvcName) -> %% add/3 +%% Can't check for a single common dictionary since a transport may +%% restrict applications so that that there's one while the service +%% has many. + add(SvcName, Type, Opts) -> - %% Ensure usable capabilities. diameter_service:merge_service/2 - %% depends on this. - lists:foreach(fun(Os) -> - is_list(Os) orelse ?THROW({capabilities, Os}), - ok = encode_CER(Os) - end, - [Os || {capabilities, Os} <- Opts, is_list(Os)]), + %% 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), Ref = make_ref(), T = {Ref, Type, Opts}, @@ -498,6 +516,64 @@ add(SvcName, Type, Opts) -> No end. +transport_opts(Opts) -> + lists:foreach(fun(T) -> opt(T) orelse ?THROW({invalid, T}) end, Opts). + +opt({transport_module, M}) -> + is_atom(M); + +opt({transport_config, _, Tmo}) -> + ?IS_UINT32(Tmo) orelse Tmo == infinity; + +opt({applications, As}) -> + is_list(As); + +opt({capabilities, Os}) -> + is_list(Os) andalso ok == encode_CER(Os); + +opt({capx_timeout, Tmo}) -> + ?IS_UINT32(Tmo); + +opt({length_errors, T}) -> + lists:member(T, [exit, handle, discard]); + +opt({reconnect_timer, Tmo}) -> + ?IS_UINT32(Tmo); + +opt({watchdog_timer, {M,F,A}}) + when is_atom(M), is_atom(F), is_list(A) -> + true; +opt({watchdog_timer, Tmo}) -> + ?IS_UINT32(Tmo); + +opt({watchdog_config, L}) -> + is_list(L) andalso lists:all(fun wdopt/1, L); + +opt({spawn_opt, Opts}) -> + is_list(Opts); + +%% Options that we can't validate. +opt({K, _}) + when K == transport_config; + K == capabilities_cb; + K == disconnect_cb; + K == private -> + true; + +%% Anything else, which is ignored by us. This makes options sensitive +%% to spelling mistakes but arbitrary options are passed by some users +%% as a way to identify transports. (That is, can't just do away with +%% it.) +opt(_) -> + true. + +wdopt({K,N}) -> + (K == okay orelse K == suspect) andalso is_integer(N) andalso 0 =< N; +wdopt(_) -> + false. + +%% start_transport/2 + start_transport(SvcName, T) -> case diameter_service:start_transport(SvcName, T) of {ok, _Pid} -> @@ -541,54 +617,73 @@ stop_transport(SvcName, Refs) -> %% make_config/2 make_config(SvcName, Opts) -> - Apps = init_apps(Opts), + AppOpts = [T || {application, _} = T <- Opts], + Apps = init_apps(AppOpts), + [] == Apps andalso ?THROW(no_apps), %% Use the fact that diameter_caps has the same field names as CER. - Fields = diameter_gen_base_rfc3588:'#info-'(diameter_base_CER) -- ['AVP'], + Fields = ?BASE:'#info-'(diameter_base_CER) -- ['AVP'], - COpts = [T || {K,_} = T <- Opts, lists:member(K, Fields)], - Caps = make_caps(#diameter_caps{}, COpts), + CapOpts = [T || {K,_} = T <- Opts, lists:member(K, Fields)], + Caps = make_caps(#diameter_caps{}, CapOpts), - ok = encode_CER(COpts), + ok = encode_CER(CapOpts), - Os = split(Opts, fun opt/2, [{false, share_peers}, - {false, use_shared_peers}, - {false, monitor}, - {?NOMASK, sequence}, - {nodes, restrict_connections}]), - %% share_peers and use_shared_peers are currently undocumented. + SvcOpts = make_opts((Opts -- AppOpts) -- CapOpts, + [{false, share_peers}, + {false, use_shared_peers}, + {false, monitor}, + {?NOMASK, sequence}, + {nodes, restrict_connections}, + {[], spawn_opt}]), #service{name = SvcName, rec = #diameter_service{applications = Apps, capabilities = Caps}, - options = Os}. + options = SvcOpts}. + +make_opts(Opts, Defs) -> + Known = [{K, get_opt(K, Opts, D)} || {D,K} <- Defs], + Unknown = Opts -- Known, + + [] == Unknown orelse ?THROW({invalid, hd(Unknown)}), -split(Opts, F, Defs) -> - [{K, F(K, get_opt(K, Opts, D))} || {D,K} <- Defs]. + [{K, opt(K,V)} || {K,V} <- Known]. + +opt(spawn_opt, L) -> + is_list(L); opt(K, false = B) when K /= sequence -> B; opt(K, true = B) - when K == share_peer; + when K == share_peers; K == use_shared_peers -> B; -opt(monitor, P) - when is_pid(P) -> - P; - opt(restrict_connections, T) when T == node; - T == nodes; - T == []; - is_atom(hd(T)) -> + T == nodes -> T; -opt(restrict_connections = K, F) -> - try diameter_lib:eval(F) of %% no guarantee that it won't fail later +opt(K, T) + when (K == share_peers + orelse K == use_shared_peers + orelse K == restrict_connections), ([] == T + orelse is_atom(hd(T))) -> + T; + +opt(monitor, P) + when is_pid(P) -> + P; + +opt(K, F) + when K == restrict_connections; + K == share_peers; + K == use_shared_peers -> + try diameter_lib:eval(F) of %% but no guarantee that it won't fail later Nodes when is_list(Nodes) -> F; V -> @@ -608,14 +703,14 @@ opt(sequence = K, F) -> E:R -> ?THROW({value, {K, E, R, ?STACK}}) end; - + opt(K, _) -> ?THROW({value, K}). sequence({H,N} = T) - when 0 =< N, N =< 32, 0 =< H, 0 == H bsr N -> + when 0 =< N, N =< 32, 0 =< H, 0 == H bsr (32-N) -> T; - + sequence(_) -> ?THROW({value, sequence}). @@ -629,7 +724,8 @@ make_caps(Caps, Opts) -> %% Validate types by encoding a CER. encode_CER(Opts) -> - {ok, CER} = diameter_capx:build_CER(make_caps(?EXAMPLE_CAPS, Opts)), + {ok, CER} = diameter_capx:build_CER(make_caps(?EXAMPLE_CAPS, Opts), + ?BASE), Hdr = #diameter_header{version = ?DIAMETER_VERSION, end_to_end_id = 0, @@ -647,24 +743,24 @@ encode_CER(Opts) -> init_apps(Opts) -> lists:foldl(fun app_acc/2, [], lists:reverse(Opts)). -app_acc({application, Opts}, Acc) -> - is_list(Opts) orelse ?THROW({application, Opts}), +app_acc({application, Opts} = T, Acc) -> + is_list(Opts) orelse ?THROW(T), [Dict, Mod] = get_opt([dictionary, module], Opts), Alias = get_opt(alias, Opts, Dict), ModS = get_opt(state, Opts, Alias), - M = get_opt(call_mutates_state, Opts, false), - A = get_opt(answer_errors, Opts, report), + M = get_opt(call_mutates_state, Opts, false, [true]), + A = get_opt(answer_errors, Opts, report, [callback, discard]), + 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 = init_mutable(M), - options = [{answer_errors, init_answers(A)}]} - | Acc]; -app_acc(_, Acc) -> - Acc. + mutable = M, + options = [{answer_errors, A}, + {request_errors, P}]} + | Acc]. init_mod(#diameter_callback{} = R) -> init_mod([diameter_callback, R]); @@ -690,20 +786,16 @@ init_cb(List) -> V <- [proplists:get_value(F, List, D)]], #diameter_callback{} = list_to_tuple([diameter_callback | Values]). -init_mutable(M) - when M == true; - M == false -> - M; -init_mutable(M) -> - ?THROW({call_mutates_state, M}). - -init_answers(A) - when callback == A; - report == A; - discard == A -> - A; -init_answers(A) -> - ?THROW({answer_errors, A}). +%% Retreive and validate. +get_opt(Key, List, Def, Other) -> + init_opt(Key, get_opt(Key, List, Def), [Def|Other]). + +init_opt(_, V, [V|_]) -> + V; +init_opt(Name, V, [_|Vals]) -> + init_opt(Name, V, Vals); +init_opt(Name, V, []) -> + ?THROW({Name, V}). %% Get a single value at the specified key. get_opt(Keys, List) |