diff options
109 files changed, 8342 insertions, 1636 deletions
diff --git a/erts/doc/src/driver_entry.xml b/erts/doc/src/driver_entry.xml index a2efdf3ebc..929c485c36 100644 --- a/erts/doc/src/driver_entry.xml +++ b/erts/doc/src/driver_entry.xml @@ -34,6 +34,29 @@ <lib>driver_entry</lib> <libsummary>The driver-entry structure used by erlang drivers.</libsummary> <description> + <marker id="WARNING"/> + <warning><p><em>Use this functionality with extreme care!</em></p> + <p>A driver callback is executed as a direct extension of the + native code of the VM. Execution is not made in a safe environment. + The VM can <em>not</em> provide the same services as provided when + executing Erlang code, such as preemptive scheduling or memory + protection. If the driver callback function doesn't behave well, + the whole VM will misbehave.</p> + <list> + <item><p>A driver callback that crash will crash the whole VM.</p></item> + <item><p>An erroneously implemented driver callback might cause + a VM internal state inconsistency which may cause a crash of the VM, + or miscellaneous misbehaviors of the VM at any point after the call + to the driver callback.</p></item> + <item><p>A driver callback that do + <seealso marker="erl_driver#lengthy_work">lengthy work</seealso> + before returning will degrade responsiveness of the VM, + and may cause miscellaneous strange behaviors. Such strange behaviors + include, but are not limited to, extreme memory usage, and bad load + balancing between schedulers. Strange behaviors that might occur due + to lengthy work may also vary between OTP releases.</p></item> + </list> + </warning> <p> As of erts version 5.9 (OTP release R15B) the driver interface has been changed with larger types for the callbacks diff --git a/erts/doc/src/erl_driver.xml b/erts/doc/src/erl_driver.xml index 187c263b60..e16fd744c0 100644 --- a/erts/doc/src/erl_driver.xml +++ b/erts/doc/src/erl_driver.xml @@ -34,6 +34,32 @@ <lib>erl_driver</lib> <libsummary>API functions for an Erlang driver</libsummary> <description> + <p>An Erlang driver is a library containing a set of native driver + callback functions that the Erlang VM calls when certain + events occur. There may be multiple instances of a driver, each + instance is associated with an Erlang port.</p> + <marker id="WARNING"/> + <warning><p><em>Use this functionality with extreme care!</em></p> + <p>A driver callback is executed as a direct extension of the + native code of the VM. Execution is not made in a safe environment. + The VM can <em>not</em> provide the same services as provided when + executing Erlang code, such as preemptive scheduling or memory + protection. If the driver callback function doesn't behave well, + the whole VM will misbehave.</p> + <list> + <item><p>A driver callback that crash will crash the whole VM.</p></item> + <item><p>An erroneously implemented driver callback might cause + a VM internal state inconsistency which may cause a crash of the VM, + or miscellaneous misbehaviors of the VM at any point after the call + to the driver callback.</p></item> + <item><p>A driver callback that do <seealso marker="#lengthy_work">lengthy + work</seealso> before returning will degrade responsiveness of the VM, + and may cause miscellaneous strange behaviors. Such strange behaviors + include, but are not limited to, extreme memory usage, and bad load + balancing between schedulers. Strange behaviors that might occur due + to lengthy work may also vary between OTP releases.</p></item> + </list> + </warning> <p>As of erts version 5.5.3 the driver interface has been extended (see <seealso marker="driver_entry#extended_marker">extended marker</seealso>). The extended interface introduce @@ -53,16 +79,12 @@ <p>The driver calls back to the emulator, using the API functions declared in <c>erl_driver.h</c>. They are used for outputting data from the driver, using timers, etc.</p> - <p>A driver is a library with a set of function that the emulator - calls, in response to Erlang functions and message - sending. There may be multiple instances of a driver, each - instance is connected to an Erlang port. Every port has a port - owner process. Communication with the port is normally done - through the port owner process.</p> - <p>Most of the functions take the <c>port</c> handle as an - argument. This identifies the driver instance. Note that this - port handle must be stored by the driver, it is not given when - the driver is called from the emulator (see + <p>Each driver instance is associated with a port. Every port + has a port owner process. Communication with the port is normally + done through the port owner process. Most of the functions take + the <c>port</c> handle as an argument. This identifies the driver + instance. Note that this port handle must be stored by the driver, + it is not given when the driver is called from the emulator (see <seealso marker="driver_entry#emulator">driver_entry</seealso>).</p> <p>Some of the functions take a parameter of type <c>ErlDrvBinary</c>, a driver binary. It should be both @@ -129,6 +151,21 @@ are <em>only</em> thread safe when used in a runtime system with SMP support.</p> </note> + <p><marker id="lengthy_work"/> + As mentioned in the <seealso marker="#WARNING">warning</seealso> text at + the beginning of this document it is of vital importance that a driver callback + does return relatively fast. It is hard to give an exact maximum amount + of time that a driver callback is allowed to work, but as a rule of thumb + a well behaving driver callback should return before a millisecond has + passed. This can be achieved using different approaches. + If you have full control over the code that are to execute in the driver + callback, the best approach is to divide the work into multiple chunks of + work and trigger multiple calls to the + <seealso marker="driver_entry#timeout">timeout callback</seealso> using + zero timeouts. This might, however, not always be possible, e.g. when + calling third party libraries. In this case you typically want to dispatch + the work to another thread. Information about thread primitives can be + found below.</p> </description> <section> diff --git a/erts/doc/src/erl_nif.xml b/erts/doc/src/erl_nif.xml index f484e9eaf7..f00f7b9f46 100644 --- a/erts/doc/src/erl_nif.xml +++ b/erts/doc/src/erl_nif.xml @@ -34,30 +34,6 @@ <lib>erl_nif</lib> <libsummary>API functions for an Erlang NIF library</libsummary> <description> - <note><p>The NIF concept is officially supported from R14B. NIF source code - written for earlier experimental versions might need adaption to run on R14B.</p> - <p>No incompatible changes between <em>R14B</em> and R14A.</p> - <p>Incompatible changes between <em>R14A</em> and R13B04:</p> - <list> - <item>Environment argument removed for <c>enif_alloc</c>, - <c>enif_realloc</c>, <c>enif_free</c>, <c>enif_alloc_binary</c>, - <c>enif_realloc_binary</c>, <c>enif_release_binary</c>, - <c>enif_alloc_resource</c>, <c>enif_release_resource</c>, - <c>enif_is_identical</c> and <c>enif_compare</c>.</item> - <item>Character encoding argument added to <c>enif_get_atom</c> - and <c>enif_make_existing_atom</c>.</item> - <item>Module argument added to <c>enif_open_resource_type</c> - while changing name spaces of resource types from global to module local.</item> - </list> - <p>Incompatible changes between <em>R13B04</em> and R13B03:</p> - <list> - <item>The function prototypes of the NIFs have changed to expect <c>argc</c> and <c>argv</c> - arguments. The arity of a NIF is by that no longer limited to 3.</item> - <item><c>enif_get_data</c> renamed as <c>enif_priv_data</c>.</item> - <item><c>enif_make_string</c> got a third argument for character encoding.</item> - </list> - </note> - <p>A NIF library contains native implementation of some functions of an Erlang module. The native implemented functions (NIFs) are called like any other functions without any difference to the @@ -67,6 +43,57 @@ is to throw an exception. But it can also be used as a fallback implementation if the NIF library is not implemented for some architecture.</p> + <marker id="WARNING"/> + <warning><p><em>Use this functionality with extreme care!</em></p> + <p>A native function is executed as a direct extension of the + native code of the VM. Execution is not made in a safe environment. + The VM can <em>not</em> provide the same services as provided when + executing Erlang code, such as preemptive scheduling or memory + protection. If the native function doesn't behave well, the whole + VM will misbehave.</p> + <list> + <item><p>A native function that crash will crash the whole VM.</p></item> + <item><p>An erroneously implemented native function might cause + a VM internal state inconsistency which may cause a crash of the VM, + or miscellaneous misbehaviors of the VM at any point after the call + to the native function.</p></item> + <item><p>A native function that do <seealso marker="#lengthy_work">lengthy + work</seealso> before returning will degrade responsiveness of the VM, + and may cause miscellaneous strange behaviors. Such strange behaviors + include, but are not limited to, extreme memory usage, and bad load + balancing between schedulers. Strange behaviors that might occur due + to lengthy work may also vary between OTP releases.</p></item> + </list> + </warning> + + <p>The NIF concept is officially supported from R14B. NIF source code + written for earlier experimental versions might need adaption to run on R14B + or later versions:</p> + <list> + <item>No incompatible changes between <em>R14B</em> and R14A.</item> + <item>Incompatible changes between <em>R14A</em> and R13B04: + <list> + <item>Environment argument removed for <c>enif_alloc</c>, + <c>enif_realloc</c>, <c>enif_free</c>, <c>enif_alloc_binary</c>, + <c>enif_realloc_binary</c>, <c>enif_release_binary</c>, + <c>enif_alloc_resource</c>, <c>enif_release_resource</c>, + <c>enif_is_identical</c> and <c>enif_compare</c>.</item> + <item>Character encoding argument added to <c>enif_get_atom</c> + and <c>enif_make_existing_atom</c>.</item> + <item>Module argument added to <c>enif_open_resource_type</c> + while changing name spaces of resource types from global to module local.</item> + </list> + </item> + <item>Incompatible changes between <em>R13B04</em> and R13B03: + <list> + <item>The function prototypes of the NIFs have changed to expect <c>argc</c> and <c>argv</c> + arguments. The arity of a NIF is by that no longer limited to 3.</item> + <item><c>enif_get_data</c> renamed as <c>enif_priv_data</c>.</item> + <item><c>enif_make_string</c> got a third argument for character encoding.</item> + </list> + </item> + </list> + <p>A minimal example of a NIF library can look like this:</p> <p/> <code type="none"> @@ -136,7 +163,23 @@ ok then retrieved by calling <seealso marker="#enif_priv_data">enif_priv_data</seealso>.</p> <p>There is no way to explicitly unload a NIF library. A library will be automatically unloaded when the module code that it belongs to is purged - by the code server.</p> + by the code server.</p> + + <p><marker id="lengthy_work"/> + As mentioned in the <seealso marker="#WARNING">warning</seealso> text at + the beginning of this document it is of vital importance that a native function + does return relatively fast. It is hard to give an exact maximum amount + of time that a native function is allowed to work, but as a rule of thumb + a well behaving native function should return to its caller before a + millisecond has passed. This can be achieved using different approaches. + If you have full control over the code that are to execute in the native + function, the best approach is to divide the work into multiple chunks of + work and call the native function multiple times. This might, however, + not always be possible, e.g. when calling third party libraries. In this + case you typically want to dispatch the work to another thread, return + from the native function, and wait for the result. The thread can send + the result back to the calling thread using message passing. Information + about thread primitives can be found below.</p> </description> <section> <title>FUNCTIONALITY</title> @@ -266,10 +309,6 @@ ok mutable.</p> <p>The library initialization callbacks <c>load</c>, <c>reload</c> and <c>upgrade</c> are all thread-safe even for shared state data.</p> - <p>Avoid doing lengthy work in NIF calls as that may degrade the - responsiveness of the VM. NIFs are called directly by the same scheduler - thread that executed the calling Erlang code. The calling scheduler will thus - be blocked from doing any other work until the NIF returns.</p> </item> </taglist> </section> diff --git a/erts/emulator/beam/erl_db.c b/erts/emulator/beam/erl_db.c index 4c30905495..1ba1048afa 100644 --- a/erts/emulator/beam/erl_db.c +++ b/erts/emulator/beam/erl_db.c @@ -251,7 +251,6 @@ free_dbtable(void *vtb) #endif ASSERT(is_immed(tb->common.heir_data)); erts_db_free(ERTS_ALC_T_DB_TABLE, tb, (void *) tb, sizeof(DbTable)); - ERTS_ETS_MISC_MEM_ADD(-sizeof(DbTable)); } static void schedule_free_dbtable(DbTable* tb) @@ -1423,7 +1422,6 @@ BIF_RETTYPE ets_new_2(BIF_ALIST_2) erts_smp_atomic_init_nob(&init_tb.common.memory_size, 0); tb = (DbTable*) erts_db_alloc(ERTS_ALC_T_DB_TABLE, &init_tb, sizeof(DbTable)); - ERTS_ETS_MISC_MEM_ADD(sizeof(DbTable)); erts_smp_atomic_init_nob(&tb->common.memory_size, erts_smp_atomic_read_nob(&init_tb.common.memory_size)); } @@ -2867,7 +2865,6 @@ void init_db(void) meta_pid_to_tab = (DbTable*) erts_db_alloc(ERTS_ALC_T_DB_TABLE, &init_tb, sizeof(DbTable)); - ERTS_ETS_MISC_MEM_ADD(sizeof(DbTable)); erts_smp_atomic_init_nob(&meta_pid_to_tab->common.memory_size, erts_smp_atomic_read_nob(&init_tb.common.memory_size)); @@ -2899,7 +2896,6 @@ void init_db(void) meta_pid_to_fixed_tab = (DbTable*) erts_db_alloc(ERTS_ALC_T_DB_TABLE, &init_tb, sizeof(DbTable)); - ERTS_ETS_MISC_MEM_ADD(sizeof(DbTable)); erts_smp_atomic_init_nob(&meta_pid_to_fixed_tab->common.memory_size, erts_smp_atomic_read_nob(&init_tb.common.memory_size)); diff --git a/erts/emulator/beam/erl_port_task.c b/erts/emulator/beam/erl_port_task.c index 2c26e1fd45..b6bc59a1c3 100644 --- a/erts/emulator/beam/erl_port_task.c +++ b/erts/emulator/beam/erl_port_task.c @@ -541,7 +541,8 @@ erts_port_task_schedule(Eterm id, #endif } - ASSERT(!enq_port || !(runq->flags & ERTS_RUNQ_FLG_SUSPENDED)); + ASSERT(!enq_port + || !(ERTS_RUNQ_FLGS_GET_NOB(runq) & ERTS_RUNQ_FLG_SUSPENDED)); if (!pp->sched.taskq) pp->sched.taskq = port_taskq_init(port_taskq_alloc(), pp); diff --git a/erts/preloaded/ebin/erlang.beam b/erts/preloaded/ebin/erlang.beam Binary files differindex 66b7a011d6..bf6d6e871b 100644 --- a/erts/preloaded/ebin/erlang.beam +++ b/erts/preloaded/ebin/erlang.beam diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl index 646acf5798..9e814ae54b 100644 --- a/erts/preloaded/src/erlang.erl +++ b/erts/preloaded/src/erlang.erl @@ -1699,7 +1699,8 @@ nodes(_Arg) -> | in | out | binary - | eof. + | eof + | hide. open_port(_PortName,_PortSettings) -> erlang:nif_error(undefined). @@ -1836,7 +1837,7 @@ process_flag(_Flag, _Value) -> {group_leader, GroupLeader :: pid()} | {heap_size, Size :: non_neg_integer()} | {initial_call, mfa()} | - {links, Pids :: [pid()]} | + {links, Pids :: [pid() | port()]} | {last_calls, false | (Calls :: [mfa()])} | {memory, Size :: non_neg_integer()} | {message_que_len, MessageQueueLen :: non_neg_integer()} | diff --git a/lib/common_test/src/Makefile b/lib/common_test/src/Makefile index f7dce195d7..dd2923ece9 100644 --- a/lib/common_test/src/Makefile +++ b/lib/common_test/src/Makefile @@ -73,7 +73,8 @@ MODULES= \ cth_surefire \ ct_netconfc \ ct_conn_log_h \ - cth_conn_log + cth_conn_log \ + ct_groups TARGET_MODULES= $(MODULES:%=$(EBIN)/%) BEAM_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl index 06a8e12f55..b1d709bc75 100644 --- a/lib/common_test/src/ct_config.erl +++ b/lib/common_test/src/ct_config.erl @@ -171,7 +171,7 @@ process_default_configs(Opts) -> lists:flatmap(fun({config,[_|_] = FileOrFiles}) -> case {io_lib:printable_list(FileOrFiles), io_lib:printable_list(hd(FileOrFiles))} of - {true,true} -> + {false,true} -> FileOrFiles; {true,false} -> [FileOrFiles]; diff --git a/lib/common_test/src/ct_conn_log_h.erl b/lib/common_test/src/ct_conn_log_h.erl index bf27238121..d7bd18606b 100644 --- a/lib/common_test/src/ct_conn_log_h.erl +++ b/lib/common_test/src/ct_conn_log_h.erl @@ -64,10 +64,16 @@ do_open_files([],Acc) -> handle_event({_Type, GL, _Msg}, State) when node(GL) /= node() -> {ok, State}; handle_event({_Type,_GL,{Pid,{ct_connection,Action,ConnName},Report}},State) -> + %% NOTE: if the format of this event is changed + %% ({ct_connection,Action,ConnName}) then remember to change + %% test_server_h:report_receiver as well!!! Info = conn_info(Pid,#conn_log{name=ConnName,action=Action}), write_report(now(),Info,Report,State), {ok, State}; handle_event({_Type,_GL,{Pid,Info=#conn_log{},Report}},State) -> + %% NOTE: if the format of this event is changed + %% (Info=#conn_log{}) then remember to change + %% test_server_h:report_receiver as well!!! write_report(now(),conn_info(Pid,Info),Report,State), {ok, State}; handle_event({error_report,_,{Pid,_,[{ct_connection,ConnName}|R]}},State) -> diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl index bec3368869..c1abf27e9f 100644 --- a/lib/common_test/src/ct_framework.erl +++ b/lib/common_test/src/ct_framework.erl @@ -32,8 +32,6 @@ -export([error_in_suite/1, init_per_suite/1, end_per_suite/1, init_per_group/2, end_per_group/2]). --export([make_all_conf/3, make_conf/5]). - -include("ct_event.hrl"). -include("ct_util.hrl"). @@ -876,13 +874,13 @@ get_suite(Mod, all) -> {'EXIT',_} -> get_all(Mod, []); GroupDefs when is_list(GroupDefs) -> - case catch find_groups(Mod, all, all, GroupDefs) of + case catch ct_groups:find_groups(Mod, all, all, GroupDefs) of {error,_} = Error -> %% this makes test_server call error_in_suite as first %% (and only) test case so we can report Error properly [{?MODULE,error_in_suite,[[Error]]}]; ConfTests -> - get_all(Mod, ConfTests) + get_all(Mod, ConfTests) end; _ -> E = "Bad return value from "++atom_to_list(Mod)++":groups/0", @@ -901,7 +899,7 @@ get_suite(Mod, Group={conf,Props,_Init,TCs,_End}) -> {'EXIT',_} -> [Group]; GroupDefs when is_list(GroupDefs) -> - case catch find_groups(Mod, Name, TCs, GroupDefs) of + case catch ct_groups:find_groups(Mod, Name, TCs, GroupDefs) of {error,_} = Error -> %% this makes test_server call error_in_suite as first %% (and only) test case so we can report Error properly @@ -916,12 +914,13 @@ get_suite(Mod, Group={conf,Props,_Init,TCs,_End}) -> %% init/end functions for top groups will be executed case catch ?val(name, element(2, hd(ConfTests))) of Name -> % top group - delete_subs(ConfTests, ConfTests); + ct_groups:delete_subs(ConfTests, ConfTests); _ -> [] end; false -> - ConfTests1 = delete_subs(ConfTests, ConfTests), + ConfTests1 = ct_groups:delete_subs(ConfTests, + ConfTests), case ?val(override, Props) of undefined -> ConfTests1; @@ -930,9 +929,9 @@ get_suite(Mod, Group={conf,Props,_Init,TCs,_End}) -> ORSpec -> ORSpec1 = if is_tuple(ORSpec) -> [ORSpec]; true -> ORSpec end, - search_and_override(ConfTests1, - ORSpec1, Mod) - end + ct_groups:search_and_override(ConfTests1, + ORSpec1, Mod) + end end end; _ -> @@ -976,234 +975,6 @@ get_all_cases1(_, []) -> %%%----------------------------------------------------------------- -find_groups(Mod, Name, TCs, GroupDefs) -> - Found = find(Mod, Name, TCs, GroupDefs, [], GroupDefs, false), - trim(Found). - -find(Mod, all, _TCs, [{Name,Props,Tests} | Gs], Known, Defs, _) - when is_atom(Name), is_list(Props), is_list(Tests) -> - cyclic_test(Mod, Name, Known), - [make_conf(Mod, Name, Props, - find(Mod, all, all, Tests, [Name | Known], Defs, true)) | - find(Mod, all, all, Gs, [], Defs, true)]; - -find(Mod, Name, TCs, [{Name,Props,Tests} | _Gs], Known, Defs, false) - when is_atom(Name), is_list(Props), is_list(Tests) -> - cyclic_test(Mod, Name, Known), - case TCs of - all -> - [make_conf(Mod, Name, Props, - find(Mod, Name, TCs, Tests, [Name | Known], Defs, true))]; - _ -> - Tests1 = [TC || TC <- TCs, - lists:member(TC, Tests) == true], - [make_conf(Mod, Name, Props, Tests1)] - end; - -find(Mod, Name, TCs, [{Name1,Props,Tests} | Gs], Known, Defs, false) - when is_atom(Name1), is_list(Props), is_list(Tests) -> - cyclic_test(Mod, Name1, Known), - [make_conf(Mod,Name1,Props, - find(Mod, Name, TCs, Tests, [Name1 | Known], Defs, false)) | - find(Mod, Name, TCs, Gs, [], Defs, false)]; - -find(Mod, Name, _TCs, [{Name,_Props,_Tests} | _Gs], _Known, _Defs, true) - when is_atom(Name) -> - E = "Duplicate groups named "++atom_to_list(Name)++" in "++ - atom_to_list(Mod)++":groups/0", - throw({error,list_to_atom(E)}); - -find(Mod, Name, all, [{Name1,Props,Tests} | Gs], Known, Defs, true) - when is_atom(Name1), is_list(Props), is_list(Tests) -> - cyclic_test(Mod, Name1, Known), - [make_conf(Mod, Name1, Props, - find(Mod, Name, all, Tests, [Name1 | Known], Defs, true)) | - find(Mod, Name, all, Gs, [], Defs, true)]; - -find(Mod, Name, TCs, [{group,Name1} | Gs], Known, Defs, Found) - when is_atom(Name1) -> - find(Mod, Name, TCs, [expand(Mod, Name1, Defs) | Gs], Known, Defs, Found); - -%% Undocumented remote group feature, use with caution -find(Mod, Name, TCs, [{group, ExtMod, ExtGrp} | Gs], Known, Defs, true) - when is_atom(ExtMod), is_atom(ExtGrp) -> - ExternalDefs = ExtMod:groups(), - ExternalTCs = find(ExtMod, ExtGrp, TCs, [{group, ExtGrp}], - [], ExternalDefs, false), - ExternalTCs ++ find(Mod, Name, TCs, Gs, Known, Defs, true); - -find(Mod, Name, TCs, [{Name1,Tests} | Gs], Known, Defs, Found) - when is_atom(Name1), is_list(Tests) -> - find(Mod, Name, TCs, [{Name1,[],Tests} | Gs], Known, Defs, Found); - -find(Mod, Name, TCs, [_TC | Gs], Known, Defs, false) -> - find(Mod, Name, TCs, Gs, Known, Defs, false); - -find(Mod, Name, TCs, [TC | Gs], Known, Defs, true) when is_atom(TC) -> - [{Mod, TC} | find(Mod, Name, TCs, Gs, Known, Defs, true)]; - -find(Mod, Name, TCs, [{ExternalTC, Case} = TC | Gs], Known, Defs, true) - when is_atom(ExternalTC), - is_atom(Case) -> - [TC | find(Mod, Name, TCs, Gs, Known, Defs, true)]; - -find(Mod, _Name, _TCs, [BadTerm | _Gs], Known, _Defs, _Found) -> - Where = if length(Known) == 0 -> - atom_to_list(Mod)++":groups/0"; - true -> - "group "++atom_to_list(lists:last(Known))++ - " in "++atom_to_list(Mod)++":groups/0" - end, - Term = io_lib:format("~p", [BadTerm]), - E = "Bad term "++lists:flatten(Term)++" in "++Where, - throw({error,list_to_atom(E)}); - -find(_Mod, _Name, _TCs, [], _Known, _Defs, false) -> - ['$NOMATCH']; - -find(_Mod, _Name, _TCs, [], _Known, _Defs, _Found) -> - []. - -delete_subs([{conf, _,_,_,_} = Conf | Confs], All) -> - All1 = delete_conf(Conf, All), - case is_sub(Conf, All1) of - true -> - delete_subs(Confs, All1); - false -> - delete_subs(Confs, All) - end; -delete_subs([_Else | Confs], All) -> - delete_subs(Confs, All); -delete_subs([], All) -> - All. - -delete_conf({conf,Props,_,_,_}, Confs) -> - Name = ?val(name, Props), - [Conf || Conf = {conf,Props0,_,_,_} <- Confs, - Name =/= ?val(name, Props0)]. - -is_sub({conf,Props,_,_,_}=Conf, [{conf,_,_,Tests,_} | Confs]) -> - Name = ?val(name, Props), - case lists:any(fun({conf,Props0,_,_,_}) -> - case ?val(name, Props0) of - N when N == Name -> - true; - _ -> - false - end; - (_) -> - false - end, Tests) of - true -> - true; - false -> - is_sub(Conf, Tests) or is_sub(Conf, Confs) - end; - -is_sub(Conf, [_TC | Tests]) -> - is_sub(Conf, Tests); - -is_sub(_Conf, []) -> - false. - -trim(['$NOMATCH' | Tests]) -> - trim(Tests); - -trim([{conf,Props,Init,Tests,End} | Confs]) -> - case trim(Tests) of - [] -> - trim(Confs); - Trimmed -> - [{conf,Props,Init,Trimmed,End} | trim(Confs)] - end; - -trim([TC | Tests]) -> - [TC | trim(Tests)]; - -trim([]) -> - []. - -cyclic_test(Mod, Name, Names) -> - case lists:member(Name, Names) of - true -> - E = "Cyclic reference to group "++atom_to_list(Name)++ - " in "++atom_to_list(Mod)++":groups/0", - throw({error,list_to_atom(E)}); - false -> - ok - end. - -expand(Mod, Name, Defs) -> - case lists:keysearch(Name, 1, Defs) of - {value,Def} -> - Def; - false -> - E = "Invalid group "++atom_to_list(Name)++ - " in "++atom_to_list(Mod)++":groups/0", - throw({error,list_to_atom(E)}) - end. - -make_all_conf(Dir, Mod, _Props) -> - case code:is_loaded(Mod) of - false -> - code:load_abs(filename:join(Dir,atom_to_list(Mod))); - _ -> - ok - end, - make_all_conf(Mod). - -make_all_conf(Mod) -> - case catch apply(Mod, groups, []) of - {'EXIT',_} -> - {error,{invalid_group_definition,Mod}}; - GroupDefs when is_list(GroupDefs) -> - case catch find_groups(Mod, all, all, GroupDefs) of - {error,_} = Error -> - %% this makes test_server call error_in_suite as first - %% (and only) test case so we can report Error properly - [{?MODULE,error_in_suite,[[Error]]}]; - [] -> - {error,{invalid_group_spec,Mod}}; - ConfTests -> - [{conf,Props,Init,all,End} || - {conf,Props,Init,_,End} - <- delete_subs(ConfTests, ConfTests)] - end - end. - -make_conf(Dir, Mod, Name, Props, TestSpec) -> - case code:is_loaded(Mod) of - false -> - code:load_abs(filename:join(Dir,atom_to_list(Mod))); - _ -> - ok - end, - make_conf(Mod, Name, Props, TestSpec). - -make_conf(Mod, Name, Props, TestSpec) -> - case code:is_loaded(Mod) of - false -> - code:load_file(Mod); - _ -> - ok - end, - {InitConf,EndConf,ExtraProps} = - case erlang:function_exported(Mod,init_per_group,2) of - true -> - {{Mod,init_per_group},{Mod,end_per_group},[]}; - false -> - ct_logs:log("TEST INFO", "init_per_group/2 and " - "end_per_group/2 missing for group " - "~p in ~p, using default.", - [Name,Mod]), - {{?MODULE,init_per_group}, - {?MODULE,end_per_group}, - [{suite,Mod}]} - end, - {conf,[{name,Name}|Props++ExtraProps],InitConf,TestSpec,EndConf}. - -%%%----------------------------------------------------------------- - get_all(Mod, ConfTests) -> case catch apply(Mod, all, []) of {'EXIT',_} -> @@ -1218,133 +989,24 @@ get_all(Mod, ConfTests) -> [{?MODULE,error_in_suite,[[{error,What}]]}]; SeqsAndTCs -> %% expand group references in all() using ConfTests - case catch expand_groups(SeqsAndTCs, ConfTests, Mod) of + case catch ct_groups:expand_groups(SeqsAndTCs, + ConfTests, + Mod) of {error,_} = Error -> [{?MODULE,error_in_suite,[[Error]]}]; Tests -> - delete_subs(Tests, Tests) + ct_groups:delete_subs(Tests, Tests) end end; Skip = {skip,_Reason} -> Skip; _ -> Reason = - list_to_atom("Bad return value from "++atom_to_list(Mod)++":all/0"), + list_to_atom("Bad return value from "++ + atom_to_list(Mod)++":all/0"), [{?MODULE,error_in_suite,[[{error,Reason}]]}] end. -expand_groups([H | T], ConfTests, Mod) -> - [expand_groups(H, ConfTests, Mod) | expand_groups(T, ConfTests, Mod)]; -expand_groups([], _ConfTests, _Mod) -> - []; -expand_groups({group,Name}, ConfTests, Mod) -> - expand_groups({group,Name,default,[]}, ConfTests, Mod); -expand_groups({group,Name,default}, ConfTests, Mod) -> - expand_groups({group,Name,default,[]}, ConfTests, Mod); -expand_groups({group,Name,ORProps}, ConfTests, Mod) when is_list(ORProps) -> - expand_groups({group,Name,ORProps,[]}, ConfTests, Mod); -expand_groups({group,Name,ORProps,SubORSpec}, ConfTests, Mod) -> - FindConf = - fun(Conf = {conf,Props,Init,Ts,End}) -> - case ?val(name, Props) of - Name when ORProps == default -> - [Conf]; - Name -> - [{conf,[{name,Name}|ORProps],Init,Ts,End}]; - _ -> - [] - end - end, - case lists:flatmap(FindConf, ConfTests) of - [] -> - throw({error,invalid_ref_msg(Name, Mod)}); - Matching when SubORSpec == [] -> - Matching; - Matching -> - override_props(Matching, SubORSpec, Name,Mod) - end; -expand_groups(SeqOrTC, _ConfTests, _Mod) -> - SeqOrTC. - -%% search deep for the matching conf test and modify it and any -%% sub tests according to the override specification -search_and_override([Conf = {conf,Props,Init,Tests,End}], ORSpec, Mod) -> - Name = ?val(name, Props), - case lists:keysearch(Name, 1, ORSpec) of - {value,{Name,default}} -> - [Conf]; - {value,{Name,ORProps}} -> - [{conf,[{name,Name}|ORProps],Init,Tests,End}]; - {value,{Name,default,[]}} -> - [Conf]; - {value,{Name,default,SubORSpec}} -> - override_props([Conf], SubORSpec, Name,Mod); - {value,{Name,ORProps,SubORSpec}} -> - override_props([{conf,[{name,Name}|ORProps], - Init,Tests,End}], SubORSpec, Name,Mod); - _ -> - [{conf,Props,Init,search_and_override(Tests,ORSpec,Mod),End}] - end. - -%% Modify the Tests element according to the override specification -override_props([{conf,Props,Init,Tests,End} | Confs], SubORSpec, Name,Mod) -> - {Subs,SubORSpec1} = override_sub_props(Tests, [], SubORSpec, Mod), - [{conf,Props,Init,Subs,End} | override_props(Confs, SubORSpec1, Name,Mod)]; -override_props([], [], _,_) -> - []; -override_props([], SubORSpec, Name,Mod) -> - Es = [invalid_ref_msg(Name, element(1,Spec), Mod) || Spec <- SubORSpec], - throw({error,Es}). - -override_sub_props([], New, ORSpec, _) -> - {?rev(New),ORSpec}; -override_sub_props([T = {conf,Props,Init,Tests,End} | Ts], - New, ORSpec, Mod) -> - Name = ?val(name, Props), - case lists:keysearch(Name, 1, ORSpec) of - {value,Spec} -> % group found in spec - Props1 = - case element(2, Spec) of - default -> Props; - ORProps -> [{name,Name} | ORProps] - end, - case catch element(3, Spec) of - Undef when Undef == [] ; 'EXIT' == element(1, Undef) -> - override_sub_props(Ts, [{conf,Props1,Init,Tests,End} | New], - lists:keydelete(Name, 1, ORSpec), Mod); - SubORSpec when is_list(SubORSpec) -> - case override_sub_props(Tests, [], SubORSpec, Mod) of - {Subs,[]} -> - override_sub_props(Ts, [{conf,Props1,Init, - Subs,End} | New], - lists:keydelete(Name, 1, ORSpec), - Mod); - {_,NonEmptySpec} -> - Es = [invalid_ref_msg(Name, element(1, GrRef), - Mod) || GrRef <- NonEmptySpec], - throw({error,Es}) - end; - BadGrSpec -> - throw({error,{invalid_form,BadGrSpec}}) - end; - _ -> % not a group in spec - override_sub_props(Ts, [T | New], ORSpec, Mod) - end; -override_sub_props([TC | Ts], New, ORSpec, Mod) -> - override_sub_props(Ts, [TC | New], ORSpec, Mod). - -invalid_ref_msg(Name, Mod) -> - E = "Invalid reference to group "++ - atom_to_list(Name)++" in "++ - atom_to_list(Mod)++":all/0", - list_to_atom(E). - -invalid_ref_msg(Name0, Name1, Mod) -> - E = "Invalid reference to group "++ - atom_to_list(Name1)++" from "++atom_to_list(Name0)++ - " in "++atom_to_list(Mod)++":all/0", - list_to_atom(E). - %%!============================================================ %%! The support for sequences by means of using sequences/0 %%! will be removed in OTP R15. The code below is only kept diff --git a/lib/common_test/src/ct_groups.erl b/lib/common_test/src/ct_groups.erl new file mode 100644 index 0000000000..24ca3826a8 --- /dev/null +++ b/lib/common_test/src/ct_groups.erl @@ -0,0 +1,599 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2012. 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 +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%%% @doc Common Test Framework callback module. +%%% +%%% <p>This module contains CT internal help functions for searching +%%% through groups specification trees and producing resulting +%%% tests.</p> + +-module(ct_groups). + +-export([find_groups/4]). +-export([make_all_conf/3, make_all_conf/4, make_conf/5]). +-export([delete_subs/2]). +-export([expand_groups/3, search_and_override/3]). + +-define(val(Key, List), proplists:get_value(Key, List)). +-define(val(Key, List, Def), proplists:get_value(Key, List, Def)). +-define(rev(L), lists:reverse(L)). + +find_groups(Mod, GrNames, TCs, GroupDefs) when is_atom(GrNames) ; + (length(GrNames) == 1) -> + find_groups1(Mod, GrNames, TCs, GroupDefs); + +find_groups(Mod, Groups, TCs, GroupDefs) when Groups /= [] -> + lists:append([find_groups1(Mod, [GrNames], TCs, GroupDefs) || + GrNames <- Groups]); + +find_groups(_Mod, [], _TCs, _GroupDefs) -> + []. + +%% GrNames == atom(): Single group name, perform full search +%% GrNames == list(): List of groups, find all matching paths +%% GrNames == [list()]: Search path terminated by last group in GrNames +find_groups1(Mod, GrNames, TCs, GroupDefs) -> + {GrNames1,FindAll} = + case GrNames of + Name when is_atom(Name), Name /= all -> + {[Name],true}; + [Path] when is_list(Path) -> + {Path,false}; + Path -> + {Path,true} + end, + TCs1 = if (is_atom(TCs) and (TCs /= all)) or is_tuple(TCs) -> + [TCs]; + true -> + TCs + end, + Found = find(Mod, GrNames1, TCs1, GroupDefs, [], + GroupDefs, FindAll), + [Conf || Conf <- Found, Conf /= 'NOMATCH']. + +%% Locate all groups +find(Mod, all, all, [{Name,Props,Tests} | Gs], Known, Defs, _) + when is_atom(Name), is_list(Props), is_list(Tests) -> + cyclic_test(Mod, Name, Known), + trim(make_conf(Mod, Name, Props, + find(Mod, all, all, Tests, [Name | Known], + Defs, true))) ++ + find(Mod, all, all, Gs, Known, Defs, true); + +%% Locate particular TCs in all groups +find(Mod, all, TCs, [{Name,Props,Tests} | Gs], Known, Defs, _) + when is_atom(Name), is_list(Props), is_list(Tests) -> + cyclic_test(Mod, Name, Known), + Tests1 = rm_unwanted_tcs(Tests, TCs, []), + trim(make_conf(Mod, Name, Props, + find(Mod, all, TCs, Tests1, [Name | Known], + Defs, true))) ++ + find(Mod, all, TCs, Gs, Known, Defs, true); + +%% Found next group is in search path +find(Mod, [Name|GrNames]=SPath, TCs, [{Name,Props,Tests} | Gs], Known, + Defs, FindAll) when is_atom(Name), is_list(Props), is_list(Tests) -> + cyclic_test(Mod, Name, Known), + Tests1 = rm_unwanted_tcs(Tests, TCs, GrNames), + trim(make_conf(Mod, Name, Props, + find(Mod, GrNames, TCs, Tests1, [Name|Known], + Defs, FindAll))) ++ + find(Mod, SPath, TCs, Gs, Known, Defs, FindAll); + +%% Group path terminated, stop the search +find(Mod, [], TCs, Tests, _Known, _Defs, false) -> + Cases = lists:flatmap(fun(TC) when is_atom(TC), TCs == all -> + [{Mod,TC}]; + ({group,_}) -> + []; + ({_,_}=TC) when TCs == all -> + [TC]; + (TC) -> + if is_atom(TC) -> + Tuple = {Mod,TC}, + case lists:member(Tuple, TCs) of + true -> + [Tuple]; + false -> + case lists:member(TC, TCs) of + true -> [{Mod,TC}]; + false -> [] + end + end; + true -> + [] + end + end, Tests), + if Cases == [] -> ['NOMATCH']; + true -> Cases + end; + +%% No more groups +find(_Mod, [_|_], _TCs, [], _Known, _Defs, _) -> + ['NOMATCH']; + +%% Found group not next in search path +find(Mod, GrNames, TCs, [{Name,Props,Tests} | Gs], Known, + Defs, FindAll) when is_atom(Name), is_list(Props), is_list(Tests) -> + cyclic_test(Mod, Name, Known), + Tests1 = rm_unwanted_tcs(Tests, TCs, GrNames), + trim(make_conf(Mod, Name, Props, + find(Mod, GrNames, TCs, Tests1, [Name|Known], + Defs, FindAll))) ++ + find(Mod, GrNames, TCs, Gs, Known, Defs, FindAll); + +%% A nested group defined on top level found +find(Mod, GrNames, TCs, [{group,Name1} | Gs], Known, Defs, FindAll) + when is_atom(Name1) -> + find(Mod, GrNames, TCs, [expand(Mod, Name1, Defs) | Gs], Known, + Defs, FindAll); + +%% Undocumented remote group feature, use with caution +find(Mod, GrNames, TCs, [{group, ExtMod, ExtGrp} | Gs], Known, + Defs, FindAll) when is_atom(ExtMod), is_atom(ExtGrp) -> + ExternalDefs = ExtMod:groups(), + ExternalTCs = find(ExtMod, ExtGrp, TCs, [{group, ExtGrp}], + [], ExternalDefs, FindAll), + ExternalTCs ++ find(Mod, GrNames, TCs, Gs, Known, Defs, FindAll); + +%% Group definition without properties, add an empty property list +find(Mod, GrNames, TCs, [{Name1,Tests} | Gs], Known, Defs, FindAll) + when is_atom(Name1), is_list(Tests) -> + find(Mod, GrNames, TCs, [{Name1,[],Tests} | Gs], Known, Defs, FindAll); + +%% Save, and keep searching +find(Mod, GrNames, TCs, [{ExternalTC, Case} = TC | Gs], Known, + Defs, FindAll) when is_atom(ExternalTC), + is_atom(Case) -> + [TC | find(Mod, GrNames, TCs, Gs, Known, Defs, FindAll)]; + +%% Save test case +find(Mod, GrNames, all, [TC | Gs], Known, + Defs, FindAll) when is_atom(TC) -> + [{Mod,TC} | find(Mod, GrNames, all, Gs, Known, Defs, FindAll)]; + +%% Save test case +find(Mod, GrNames, all, [{M,TC} | Gs], Known, + Defs, FindAll) when is_atom(M), M /= group, is_atom(TC) -> + [{M,TC} | find(Mod, GrNames, all, Gs, Known, Defs, FindAll)]; + +%% Check if test case should be saved +find(Mod, GrNames, TCs, [TC | Gs], Known, + Defs, FindAll) when is_atom(TC) orelse + ((size(TC) == 2) and (hd(TC) /= group)) -> + Case = + if is_atom(TC) -> + Tuple = {Mod,TC}, + case lists:member(Tuple, TCs) of + true -> + Tuple; + false -> + case lists:member(TC, TCs) of + true -> {Mod,TC}; + false -> [] + end + end; + true -> + case lists:member(TC, TCs) of + true -> {Mod,TC}; + false -> [] + end + end, + if Case == [] -> + find(Mod, GrNames, TCs, Gs, Known, Defs, FindAll); + true -> + [Case | find(Mod, GrNames, TCs, Gs, Known, Defs, FindAll)] + end; + +%% Unexpeted term in group list +find(Mod, _GrNames, _TCs, [BadTerm | _Gs], Known, _Defs, _FindAll) -> + Where = if length(Known) == 0 -> + atom_to_list(Mod)++":groups/0"; + true -> + "group "++atom_to_list(lists:last(Known))++ + " in "++atom_to_list(Mod)++":groups/0" + end, + Term = io_lib:format("~p", [BadTerm]), + E = "Bad term "++lists:flatten(Term)++" in "++Where, + throw({error,list_to_atom(E)}); + +%% No more groups +find(_Mod, _GrNames, _TCs, [], _Known, _Defs, _) -> + []. + +%%%----------------------------------------------------------------- + +%% We have to always search bottom up to only remove a branch +%% if there's 'NOMATCH' in the leaf (otherwise, the branch should +%% be kept) + +trim({conf,Props,Init,Tests,End}) -> + try trim(Tests) of + [] -> []; + Tests1 -> [{conf,Props,Init,Tests1,End}] + catch + throw:_ -> [] + end; + +trim(Tests) when is_list(Tests) -> + %% we need to compare the result of trimming each test on this + %% level, and only let a 'NOMATCH' fail the level if no + %% successful sub group can be found + Tests1 = + lists:flatmap(fun(Test) -> + IsConf = case Test of + {conf,_,_,_,_} -> + true; + _ -> + false + end, + try trim_test(Test) of + [] -> []; + Test1 when IsConf -> [{conf,Test1}]; + Test1 -> [Test1] + catch + throw:_ -> ['NOMATCH'] + end + end, Tests), + case lists:keymember(conf, 1, Tests1) of + true -> % at least one successful group + lists:flatmap(fun({conf,Test}) -> [Test]; + ('NOMATCH') -> []; % ignore any 'NOMATCH' + (Test) -> [Test] + end, Tests1); + false -> + case lists:member('NOMATCH', Tests1) of + true -> + throw('NOMATCH'); + false -> + Tests1 + end + end. + +trim_test({conf,Props,Init,Tests,End}) -> + case trim(Tests) of + [] -> + []; + Tests1 -> + {conf,Props,Init,Tests1,End} + end; + +trim_test('NOMATCH') -> + throw('NOMATCH'); + +trim_test(Test) -> + Test. + +%% GrNames is [] if the terminating group has been found. From +%% that point, all specified test should be included (as well as +%% sub groups for deeper search). +rm_unwanted_tcs(Tests, all, []) -> + Tests; + +rm_unwanted_tcs(Tests, TCs, []) -> + sort_tests(lists:flatmap(fun(Test) when is_tuple(Test), + (size(Test) > 2) -> + [Test]; + (Test={group,_}) -> + [Test]; + (Test={_M,TC}) -> + case lists:member(TC, TCs) of + true -> [Test]; + false -> [] + end; + (Test) when is_atom(Test) -> + case lists:keysearch(Test, 2, TCs) of + {value,_} -> + [Test]; + _ -> + case lists:member(Test, TCs) of + true -> [Test]; + false -> [] + end + end; + (Test) -> [Test] + end, Tests), TCs); + +rm_unwanted_tcs(Tests, _TCs, _) -> + [Test || Test <- Tests, not is_atom(Test)]. + +%% make sure the order of tests is according to the order in TCs +sort_tests(Tests, TCs) when is_list(TCs)-> + lists:sort(fun(T1, T2) -> + case {is_tc(T1),is_tc(T2)} of + {true,true} -> + (position(T1, TCs) =< + position(T2, TCs)); + {false,true} -> + (position(T2, TCs) == (length(TCs)+1)); + _ -> true + + end + end, Tests); +sort_tests(Tests, _) -> + Tests. + +is_tc(T) when is_atom(T) -> true; +is_tc({group,_}) -> false; +is_tc({_M,T}) when is_atom(T) -> true; +is_tc(_) -> false. + +position(T, TCs) -> + position(T, TCs, 1). + +position(T, [T|_TCs], Pos) -> + Pos; +position(T, [{_,T}|_TCs], Pos) -> + Pos; +position({M,T}, [T|_TCs], Pos) when M /= group -> + Pos; +position(T, [_|TCs], Pos) -> + position(T, TCs, Pos+1); +position(_, [], Pos) -> + Pos. + +%%%----------------------------------------------------------------- + +delete_subs([{conf, _,_,_,_} = Conf | Confs], All) -> + All1 = delete_conf(Conf, All), + case is_sub(Conf, All1) of + true -> + delete_subs(Confs, All1); + false -> + delete_subs(Confs, All) + end; +delete_subs([_Else | Confs], All) -> + delete_subs(Confs, All); +delete_subs([], All) -> + All. + +delete_conf({conf,Props,_,_,_}, Confs) -> + Name = ?val(name, Props), + [Conf || Conf = {conf,Props0,_,_,_} <- Confs, + Name =/= ?val(name, Props0)]. + +is_sub({conf,Props,_,_,_}=Conf, [{conf,_,_,Tests,_} | Confs]) -> + Name = ?val(name, Props), + case lists:any(fun({conf,Props0,_,_,_}) -> + case ?val(name, Props0) of + N when N == Name -> + true; + _ -> + false + end; + (_) -> + false + end, Tests) of + true -> + true; + false -> + is_sub(Conf, Tests) orelse is_sub(Conf, Confs) + end; + +is_sub(Conf, [_TC | Tests]) -> + is_sub(Conf, Tests); + +is_sub(_Conf, []) -> + false. + + +cyclic_test(Mod, Name, Names) -> + case lists:member(Name, Names) of + true -> + E = "Cyclic reference to group "++atom_to_list(Name)++ + " in "++atom_to_list(Mod)++":groups/0", + throw({error,list_to_atom(E)}); + false -> + ok + end. + +expand(Mod, Name, Defs) -> + case lists:keysearch(Name, 1, Defs) of + {value,Def} -> + Def; + false -> + E = "Invalid group "++atom_to_list(Name)++ + " in "++atom_to_list(Mod)++":groups/0", + throw({error,list_to_atom(E)}) + end. + +make_all_conf(Dir, Mod, Props, TestSpec) -> + case code:is_loaded(Mod) of + false -> + code:load_abs(filename:join(Dir,atom_to_list(Mod))); + _ -> + ok + end, + make_all_conf(Mod, Props, TestSpec). + +make_all_conf(Mod, Props, TestSpec) -> + case catch apply(Mod, groups, []) of + {'EXIT',_} -> + exit({invalid_group_definition,Mod}); + GroupDefs when is_list(GroupDefs) -> + case catch find_groups(Mod, all, TestSpec, GroupDefs) of + {error,_} = Error -> + %% this makes test_server call error_in_suite as first + %% (and only) test case so we can report Error properly + [{ct_framework,error_in_suite,[[Error]]}]; + [] -> + exit({invalid_group_spec,Mod}); + _ConfTests -> + make_conf(Mod, all, Props, TestSpec) + end + end. + +make_conf(Dir, Mod, Name, Props, TestSpec) -> + case code:is_loaded(Mod) of + false -> + code:load_abs(filename:join(Dir,atom_to_list(Mod))); + _ -> + ok + end, + make_conf(Mod, Name, Props, TestSpec). + +make_conf(Mod, Name, Props, TestSpec) -> + case code:is_loaded(Mod) of + false -> + code:load_file(Mod); + _ -> + ok + end, + {InitConf,EndConf,ExtraProps} = + case erlang:function_exported(Mod,init_per_group,2) of + true -> + {{Mod,init_per_group},{Mod,end_per_group},[]}; + false -> + ct_logs:log("TEST INFO", "init_per_group/2 and " + "end_per_group/2 missing for group " + "~p in ~p, using default.", + [Name,Mod]), + {{ct_framework,init_per_group}, + {ct_framework,end_per_group}, + [{suite,Mod}]} + end, + {conf,[{name,Name}|Props++ExtraProps],InitConf,TestSpec,EndConf}. + +%%%----------------------------------------------------------------- + +expand_groups([H | T], ConfTests, Mod) -> + [expand_groups(H, ConfTests, Mod) | expand_groups(T, ConfTests, Mod)]; +expand_groups([], _ConfTests, _Mod) -> + []; +expand_groups({group,Name}, ConfTests, Mod) -> + expand_groups({group,Name,default,[]}, ConfTests, Mod); +expand_groups({group,Name,default}, ConfTests, Mod) -> + expand_groups({group,Name,default,[]}, ConfTests, Mod); +expand_groups({group,Name,ORProps}, ConfTests, Mod) when is_list(ORProps) -> + expand_groups({group,Name,ORProps,[]}, ConfTests, Mod); +expand_groups({group,Name,ORProps,SubORSpec}, ConfTests, Mod) -> + FindConf = + fun(Conf = {conf,Props,Init,Ts,End}) -> + case ?val(name, Props) of + Name when ORProps == default -> + [Conf]; + Name -> + Props1 = case ?val(suite, Props) of + undefined -> + ORProps; + SuiteName -> + [{suite,SuiteName}|ORProps] + end, + [{conf,[{name,Name}|Props1],Init,Ts,End}]; + _ -> + [] + end + end, + case lists:flatmap(FindConf, ConfTests) of + [] -> + throw({error,invalid_ref_msg(Name, Mod)}); + Matching when SubORSpec == [] -> + Matching; + Matching -> + override_props(Matching, SubORSpec, Name,Mod) + end; +expand_groups(SeqOrTC, _ConfTests, _Mod) -> + SeqOrTC. + +%% search deep for the matching conf test and modify it and any +%% sub tests according to the override specification +search_and_override([Conf = {conf,Props,Init,Tests,End}], ORSpec, Mod) -> + InsProps = fun(GrName, undefined, Ps) -> + [{name,GrName} | Ps]; + (GrName, Suite, Ps) -> + [{name,GrName}, {suite,Suite} | Ps] + end, + Name = ?val(name, Props), + Suite = ?val(suite, Props), + case lists:keysearch(Name, 1, ORSpec) of + {value,{Name,default}} -> + [Conf]; + {value,{Name,ORProps}} -> + [{conf,InsProps(Name,Suite,ORProps),Init,Tests,End}]; + {value,{Name,default,[]}} -> + [Conf]; + {value,{Name,default,SubORSpec}} -> + override_props([Conf], SubORSpec, Name,Mod); + {value,{Name,ORProps,SubORSpec}} -> + override_props([{conf,InsProps(Name,Suite,ORProps), + Init,Tests,End}], SubORSpec, Name,Mod); + _ -> + [{conf,Props,Init,search_and_override(Tests,ORSpec,Mod),End}] + end. + +%% Modify the Tests element according to the override specification +override_props([{conf,Props,Init,Tests,End} | Confs], SubORSpec, Name,Mod) -> + {Subs,SubORSpec1} = override_sub_props(Tests, [], SubORSpec, Mod), + [{conf,Props,Init,Subs,End} | override_props(Confs, SubORSpec1, Name,Mod)]; +override_props([], [], _,_) -> + []; +override_props([], SubORSpec, Name,Mod) -> + Es = [invalid_ref_msg(Name, element(1,Spec), Mod) || Spec <- SubORSpec], + throw({error,Es}). + +override_sub_props([], New, ORSpec, _) -> + {?rev(New),ORSpec}; +override_sub_props([T = {conf,Props,Init,Tests,End} | Ts], + New, ORSpec, Mod) -> + Name = ?val(name, Props), + Suite = ?val(suite, Props), + case lists:keysearch(Name, 1, ORSpec) of + {value,Spec} -> % group found in spec + Props1 = + case element(2, Spec) of + default -> Props; + ORProps when Suite == undefined -> [{name,Name} | ORProps]; + ORProps -> [{name,Name}, {suite,Suite} | ORProps] + end, + case catch element(3, Spec) of + Undef when Undef == [] ; 'EXIT' == element(1, Undef) -> + override_sub_props(Ts, [{conf,Props1,Init,Tests,End} | New], + lists:keydelete(Name, 1, ORSpec), Mod); + SubORSpec when is_list(SubORSpec) -> + case override_sub_props(Tests, [], SubORSpec, Mod) of + {Subs,[]} -> + override_sub_props(Ts, [{conf,Props1,Init, + Subs,End} | New], + lists:keydelete(Name, 1, ORSpec), + Mod); + {_,NonEmptySpec} -> + Es = [invalid_ref_msg(Name, element(1, GrRef), + Mod) || GrRef <- NonEmptySpec], + throw({error,Es}) + end; + BadGrSpec -> + throw({error,{invalid_form,BadGrSpec}}) + end; + _ -> % not a group in spec + override_sub_props(Ts, [T | New], ORSpec, Mod) + end; +override_sub_props([TC | Ts], New, ORSpec, Mod) -> + override_sub_props(Ts, [TC | New], ORSpec, Mod). + +invalid_ref_msg(Name, Mod) -> + E = "Invalid reference to group "++ + atom_to_list(Name)++" in "++ + atom_to_list(Mod)++":all/0", + list_to_atom(E). + +invalid_ref_msg(Name0, Name1, Mod) -> + E = "Invalid reference to group "++ + atom_to_list(Name1)++" from "++atom_to_list(Name0)++ + " in "++atom_to_list(Mod)++":all/0", + list_to_atom(E). diff --git a/lib/common_test/src/ct_netconfc.erl b/lib/common_test/src/ct_netconfc.erl index 52fe9599ce..294b82bff6 100644 --- a/lib/common_test/src/ct_netconfc.erl +++ b/lib/common_test/src/ct_netconfc.erl @@ -968,7 +968,7 @@ close_session(Client) -> %% @end %%---------------------------------------------------------------------- close_session(Client, Timeout) -> - call(Client,{send_rpc_op, close_session, [], Timeout}). + call(Client,{send_rpc_op, close_session, [], Timeout}, true). %%---------------------------------------------------------------------- @@ -1121,17 +1121,38 @@ close(Client) -> %% Internal functions %%---------------------------------------------------------------------- call(Client, Msg) -> - call(Client, Msg, infinity). -call(Client, Msg, Timeout) -> + call(Client, Msg, infinity, false). +call(Client, Msg, Timeout) when is_integer(Timeout); Timeout==infinity -> + call(Client, Msg, Timeout, false); +call(Client, Msg, WaitStop) when is_boolean(WaitStop) -> + call(Client, Msg, infinity, WaitStop). +call(Client, Msg, Timeout, WaitStop) -> case get_handle(Client) of {ok,Pid} -> case ct_gen_conn:call(Pid,Msg,Timeout) of - {error,{process_down,Client,noproc}} -> + {error,{process_down,Pid,noproc}} -> {error,no_such_client}; - {error,{process_down,Client,normal}} -> + {error,{process_down,Pid,normal}} when WaitStop -> + %% This will happen when server closes connection + %% before clien received rpc-reply on + %% close-session. + ok; + {error,{process_down,Pid,normal}} -> {error,closed}; - {error,{process_down,Client,Reason}} -> + {error,{process_down,Pid,Reason}} -> {error,{closed,Reason}}; + Other when WaitStop -> + MRef = erlang:monitor(process,Pid), + receive + {'DOWN',MRef,process,Pid,Normal} when Normal==normal; + Normal==noproc -> + Other; + {'DOWN',MRef,process,Pid,Reason} -> + {error,{{closed,Reason},Other}} + after Timeout -> + erlang:demonitor(MRef, [flush]), + {error,{timeout,Other}} + end; Other -> Other end; diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index ed7778c25a..eb05c90ba8 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -1286,7 +1286,8 @@ run_dir(Opts = #opts{logdir = LogDir, reformat_result(catch do_run(tests(Dir2, Mod), [], Opts1, StartOpts)); _ -> - reformat_result(catch do_run(tests(Dir2, Mod, GsAndCs), + reformat_result(catch do_run(tests(Dir2, Mod, + GsAndCs), [], Opts1, StartOpts)) end; @@ -1295,7 +1296,8 @@ run_dir(Opts = #opts{logdir = LogDir, [_,_|_] when GsAndCs /= [] -> exit({error,multiple_suites_and_cases}); [{Dir2,Mod}] when GsAndCs /= [] -> - reformat_result(catch do_run(tests(Dir2, Mod, GsAndCs), + reformat_result(catch do_run(tests(Dir2, Mod, + GsAndCs), [], Opts1, StartOpts)); DirMods -> reformat_result(catch do_run(tests(DirMods), @@ -1553,17 +1555,36 @@ groups_and_cases(Gs, Cs) when ((Gs == undefined) or (Gs == [])) and ((Cs == undefined) or (Cs == [])) -> []; groups_and_cases(Gs, Cs) when Gs == undefined ; Gs == [] -> - [ensure_atom(C) || C <- listify(Cs)]; -groups_and_cases(Gs, Cs) when Cs == undefined ; Cs == [] -> - [{ensure_atom(G),all} || G <- listify(Gs)]; -groups_and_cases(G, Cs) when is_atom(G) -> - [{G,[ensure_atom(C) || C <- listify(Cs)]}]; -groups_and_cases([G], Cs) -> - [{ensure_atom(G),[ensure_atom(C) || C <- listify(Cs)]}]; -groups_and_cases([_,_|_] , Cs) when Cs =/= [] -> - {error,multiple_groups_and_cases}; -groups_and_cases(_Gs, _Cs) -> - {error,incorrect_group_or_case_option}. + if (Cs == all) or (Cs == [all]) or (Cs == ["all"]) -> all; + true -> [ensure_atom(C) || C <- listify(Cs)] + end; +groups_and_cases(GOrGs, Cs) when (is_atom(GOrGs) orelse + (is_list(GOrGs) andalso + (is_atom(hd(GOrGs)) orelse + (is_list(hd(GOrGs)) andalso + is_atom(hd(hd(GOrGs))))))) -> + if (Cs == undefined) or (Cs == []) or + (Cs == all) or (Cs == [all]) or (Cs == ["all"]) -> + [{GOrGs,all}]; + true -> + [{GOrGs,[ensure_atom(C) || C <- listify(Cs)]}] + end; +groups_and_cases(Gs, Cs) when is_integer(hd(hd(Gs))) -> + %% if list of strings, this comes from 'ct_run -group G1 G2 ...' and + %% we need to parse the strings + Gs1 = + if (Gs == [all]) or (Gs == ["all"]) -> + all; + true -> + lists:map(fun(G) -> + {ok,Ts,_} = erl_scan:string(G++"."), + {ok,Term} = erl_parse:parse_term(Ts), + Term + end, Gs) + end, + groups_and_cases(Gs1, Cs); +groups_and_cases(Gs, Cs) -> + {error,{incorrect_group_or_case_option,Gs,Cs}}. tests(TestDir, Suites, []) when is_list(TestDir), is_integer(hd(TestDir)) -> [{?testdir(TestDir,Suites),ensure_atom(Suites),all}]; @@ -1703,11 +1724,15 @@ compile_and_run(Tests, Skip, Opts, Args) -> SavedErrors = save_make_errors(SuiteMakeErrors), ct_repeat:log_loop_info(Args), - {Tests1,Skip1} = final_tests(Tests,Skip,SavedErrors), - - ReleaseSh = proplists:get_value(release_shell, Args), - ct_util:set_testdata({release_shell,ReleaseSh}), - possibly_spawn(ReleaseSh == true, Tests1, Skip1, Opts); + try final_tests(Tests,Skip,SavedErrors) of + {Tests1,Skip1} -> + ReleaseSh = proplists:get_value(release_shell, Args), + ct_util:set_testdata({release_shell,ReleaseSh}), + possibly_spawn(ReleaseSh == true, Tests1, Skip1, Opts) + catch + _:BadFormat -> + {error,BadFormat} + end; false -> io:nl(), ct_util:stop(clean), @@ -1977,22 +2002,21 @@ final_tests1([{TestDir,Suite,GrsOrCs}|Tests], Final, Skip, Bad) when %% for now, only flat group defs are allowed as %% start options and test spec terms fun({all,all}) -> - ct_framework:make_all_conf(TestDir, - Suite, []); + [ct_groups:make_conf(TestDir, Suite, all, [], all)]; ({skipped,Group,TCs}) -> - [ct_framework:make_conf(TestDir, Suite, - Group, [skipped], TCs)]; - ({GrSpec = {Group,_},TCs}) -> + [ct_groups:make_conf(TestDir, Suite, + Group, [skipped], TCs)]; + ({GrSpec = {GroupName,_},TCs}) -> Props = [{override,GrSpec}], - [ct_framework:make_conf(TestDir, Suite, - Group, Props, TCs)]; - ({GrSpec = {Group,_,_},TCs}) -> + [ct_groups:make_conf(TestDir, Suite, + GroupName, Props, TCs)]; + ({GrSpec = {GroupName,_,_},TCs}) -> Props = [{override,GrSpec}], - [ct_framework:make_conf(TestDir, Suite, - Group, Props, TCs)]; - ({Group,TCs}) -> - [ct_framework:make_conf(TestDir, Suite, - Group, [], TCs)]; + [ct_groups:make_conf(TestDir, Suite, + GroupName, Props, TCs)]; + ({GroupOrGroups,TCs}) -> + [ct_groups:make_conf(TestDir, Suite, + GroupOrGroups, [], TCs)]; (TC) -> [TC] end, GrsOrCs), @@ -2004,12 +2028,12 @@ final_tests1([], Final, Skip, _Bad) -> {lists:reverse(Final),Skip}. final_skip([{TestDir,Suite,{all,all},Reason}|Skips], Final) -> - SkipConf = ct_framework:make_conf(TestDir, Suite, all, [], all), + SkipConf = ct_groups:make_conf(TestDir, Suite, all, [], all), Skip = {TestDir,Suite,SkipConf,Reason}, final_skip(Skips, [Skip|Final]); final_skip([{TestDir,Suite,{Group,TCs},Reason}|Skips], Final) -> - Conf = ct_framework:make_conf(TestDir, Suite, Group, [], TCs), + Conf = ct_groups:make_conf(TestDir, Suite, Group, [], TCs), Skip = {TestDir,Suite,Conf,Reason}, final_skip(Skips, [Skip|Final]); @@ -2282,9 +2306,11 @@ add_jobs([{TestDir,all,_}|Tests], Skip, Opts, CleanUp) -> wait_for_idle(), add_jobs(Tests, Skip, Opts, CleanUp) end; -add_jobs([{TestDir,[Suite],all}|Tests], Skip, Opts, CleanUp) when is_atom(Suite) -> +add_jobs([{TestDir,[Suite],all}|Tests], Skip, + Opts, CleanUp) when is_atom(Suite) -> add_jobs([{TestDir,Suite,all}|Tests], Skip, Opts, CleanUp); -add_jobs([{TestDir,Suites,all}|Tests], Skip, Opts, CleanUp) when is_list(Suites) -> +add_jobs([{TestDir,Suites,all}|Tests], Skip, + Opts, CleanUp) when is_list(Suites) -> Name = get_name(TestDir) ++ ".suites", case catch test_server_ctrl:add_module_with_skip(Name, Suites, skiplist(TestDir,Skip)) of @@ -2299,7 +2325,8 @@ add_jobs([{TestDir,Suite,all}|Tests], Skip, Opts, CleanUp) -> ok -> Name = get_name(TestDir) ++ "." ++ atom_to_list(Suite), case catch test_server_ctrl:add_module_with_skip(Name, [Suite], - skiplist(TestDir,Skip)) of + skiplist(TestDir, + Skip)) of {'EXIT',_} -> CleanUp; _ -> @@ -2322,15 +2349,24 @@ add_jobs([{TestDir,Suite,Confs}|Tests], Skip, Opts, CleanUp) when GrTestName = case Confs of [Conf] -> - "." ++ atom_to_list(Group(Conf)) ++ TCTestName(TestCases(Conf)); + case Group(Conf) of + GrName when is_atom(GrName) -> + "." ++ atom_to_list(GrName) ++ + TCTestName(TestCases(Conf)); + _ -> + ".groups" ++ TCTestName(TestCases(Conf)) + end; _ -> ".groups" end, TestName = get_name(TestDir) ++ "." ++ atom_to_list(Suite) ++ GrTestName, case maybe_interpret(Suite, init_per_group, Opts) of ok -> - case catch test_server_ctrl:add_conf_with_skip(TestName, Suite, Confs, - skiplist(TestDir,Skip)) of + case catch test_server_ctrl:add_conf_with_skip(TestName, + Suite, + Confs, + skiplist(TestDir, + Skip)) of {'EXIT',_} -> CleanUp; _ -> @@ -2342,18 +2378,21 @@ add_jobs([{TestDir,Suite,Confs}|Tests], Skip, Opts, CleanUp) when end; %% test case -add_jobs([{TestDir,Suite,[Case]}|Tests], Skip, Opts, CleanUp) when is_atom(Case) -> +add_jobs([{TestDir,Suite,[Case]}|Tests], + Skip, Opts, CleanUp) when is_atom(Case) -> add_jobs([{TestDir,Suite,Case}|Tests], Skip, Opts, CleanUp); -add_jobs([{TestDir,Suite,Cases}|Tests], Skip, Opts, CleanUp) when is_list(Cases) -> +add_jobs([{TestDir,Suite,Cases}|Tests], + Skip, Opts, CleanUp) when is_list(Cases) -> Cases1 = lists:map(fun({GroupName,_}) when is_atom(GroupName) -> GroupName; (Case) -> Case end, Cases), case maybe_interpret(Suite, Cases1, Opts) of ok -> - Name = get_name(TestDir) ++ "." ++ atom_to_list(Suite) ++ ".cases", + Name = get_name(TestDir) ++ "." ++ atom_to_list(Suite) ++ ".cases", case catch test_server_ctrl:add_cases_with_skip(Name, Suite, Cases1, - skiplist(TestDir,Skip)) of + skiplist(TestDir, + Skip)) of {'EXIT',_} -> CleanUp; _ -> @@ -2369,7 +2408,8 @@ add_jobs([{TestDir,Suite,Case}|Tests], Skip, Opts, CleanUp) when is_atom(Case) - Name = get_name(TestDir) ++ "." ++ atom_to_list(Suite) ++ "." ++ atom_to_list(Case), case catch test_server_ctrl:add_case_with_skip(Name, Suite, Case, - skiplist(TestDir,Skip)) of + skiplist(TestDir, + Skip)) of {'EXIT',_} -> CleanUp; _ -> @@ -2404,7 +2444,8 @@ skiplist(Dir, [{Dir,all,Cmt}|Skip]) -> %% we need to turn 'all' into list of modules since %% test_server doesn't do skips on Dir level Ss = filelib:wildcard(filename:join(Dir, "*_SUITE.beam")), - [{list_to_atom(filename:basename(S,".beam")),Cmt} || S <- Ss] ++ skiplist(Dir,Skip); + [{list_to_atom(filename:basename(S,".beam")),Cmt} || S <- Ss] ++ + skiplist(Dir,Skip); skiplist(Dir, [{Dir,S,Cmt}|Skip]) -> [{S,Cmt} | skiplist(Dir, Skip)]; skiplist(Dir, [{Dir,S,C,Cmt}|Skip]) -> @@ -2464,8 +2505,10 @@ run_make(Targets, TestDir0, Mod, UserInclude) -> FileTest = fun(F, suites) -> is_suite(F); (F, helpmods) -> not is_suite(F) end, - Files = lists:flatmap(fun({F,out_of_date}) -> - case FileTest(F, Targets) of + Files = + lists:flatmap(fun({F,out_of_date}) -> + case FileTest(F, + Targets) of true -> [F]; false -> [] end; @@ -2812,11 +2855,14 @@ opts2args(EnvStartOpts) -> lists:flatmap(fun({exit_status,ExitStatusOpt}) when is_atom(ExitStatusOpt) -> [{exit_status,[atom_to_list(ExitStatusOpt)]}]; ({halt_with,{HaltM,HaltF}}) -> - [{halt_with,[atom_to_list(HaltM),atom_to_list(HaltF)]}]; + [{halt_with,[atom_to_list(HaltM), + atom_to_list(HaltF)]}]; ({interactive_mode,true}) -> [{shell,[]}]; - ({config,CfgFiles}) -> - [{ct_config,[CfgFiles]}]; + ({config,CfgFile}) when is_integer(hd(CfgFile)) -> + [{ct_config,[CfgFile]}]; + ({config,CfgFiles}) when is_list(hd(CfgFiles)) -> + [{ct_config,CfgFiles}]; ({userconfig,{CBM,CfgStr=[X|_]}}) when is_integer(X) -> [{userconfig,[atom_to_list(CBM),CfgStr]}]; ({userconfig,{CBM,CfgStrs}}) when is_list(CfgStrs) -> @@ -2834,6 +2880,12 @@ opts2args(EnvStartOpts) -> end, UserCfg), [_LastAnd|StrsR] = lists:reverse(lists:flatten(Strs)), [{userconfig,lists:reverse(StrsR)}]; + ({group,G}) when is_atom(G) -> + [{group,[atom_to_list(G)]}]; + ({group,Gs}) when is_list(Gs) -> + LOfGStrs = [lists:flatten(io_lib:format("~w",[G])) || + G <- Gs], + [{group,LOfGStrs}]; ({testcase,Case}) when is_atom(Case) -> [{'case',[atom_to_list(Case)]}]; ({testcase,Cases}) -> diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index 0221b87d49..202d8f9373 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -1028,20 +1028,24 @@ insert_groups(Node,Dir,Suite,Group,Cases,Tests,MergeTests) insert_groups(Node,Dir,Suite,[Group],Cases,Tests,MergeTests); insert_groups(Node,Dir,Suite,Groups,Cases,Tests,false) when ((Cases == all) or is_list(Cases)) and is_list(Groups) -> - Groups1 = [{Gr,Cases} || Gr <- Groups], + Groups1 = [if is_list(Gr) -> % preserve group path + {[Gr],Cases}; + true -> + {Gr,Cases} end || Gr <- Groups], append({{Node,Dir},[{Suite,Groups1}]},Tests); insert_groups(Node,Dir,Suite,Groups,Cases,Tests,true) when ((Cases == all) or is_list(Cases)) and is_list(Groups) -> + Groups1 = [if is_list(Gr) -> % preserve group path + {[Gr],Cases}; + true -> + {Gr,Cases} end || Gr <- Groups], case lists:keysearch({Node,Dir},1,Tests) of {value,{{Node,Dir},[{all,_}]}} -> Tests; {value,{{Node,Dir},Suites0}} -> - Suites1 = insert_groups1(Suite, - [{Gr,Cases} || Gr <- Groups], - Suites0), + Suites1 = insert_groups1(Suite,Groups1,Suites0), insert_in_order({{Node,Dir},Suites1},Tests); false -> - Groups1 = [{Gr,Cases} || Gr <- Groups], insert_in_order({{Node,Dir},[{Suite,Groups1}]},Tests) end; insert_groups(Node,Dir,Suite,Groups,Case,Tests, MergeTests) @@ -1064,13 +1068,13 @@ insert_groups1(Suite,Groups,Suites0) -> insert_groups2(_Groups,all) -> all; -insert_groups2([Group={GrName,Cases}|Groups],GrAndCases) -> - case lists:keysearch(GrName,1,GrAndCases) of - {value,{GrName,all}} -> +insert_groups2([Group={Gr,Cases}|Groups],GrAndCases) -> + case lists:keysearch(Gr,1,GrAndCases) of + {value,{Gr,all}} -> GrAndCases; - {value,{GrName,Cases0}} -> + {value,{Gr,Cases0}} -> Cases1 = insert_in_order(Cases,Cases0), - insert_groups2(Groups,insert_in_order({GrName,Cases1},GrAndCases)); + insert_groups2(Groups,insert_in_order({Gr,Cases1},GrAndCases)); false -> insert_groups2(Groups,insert_in_order(Group,GrAndCases)) end; diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile index 3de33688da..df816f9a61 100644 --- a/lib/common_test/test/Makefile +++ b/lib/common_test/test/Makefile @@ -55,7 +55,8 @@ MODULES= \ ct_system_error_SUITE \ ct_snmp_SUITE \ ct_group_leader_SUITE \ - ct_cover_SUITE + ct_cover_SUITE \ + ct_groups_search_SUITE ERL_FILES= $(MODULES:%=%.erl) diff --git a/lib/common_test/test/ct_config_SUITE.erl b/lib/common_test/test/ct_config_SUITE.erl index 1e1df908db..d92be9ec6e 100644 --- a/lib/common_test/test/ct_config_SUITE.erl +++ b/lib/common_test/test/ct_config_SUITE.erl @@ -88,8 +88,8 @@ require(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), run_test(config_static_SUITE, Config, - [{config, filename:join(DataDir, "config/shadow.txt")}, - {config, filename:join(DataDir, "config/config.txt")}], + [{config, [filename:join(DataDir, "config/shadow.txt"), + filename:join(DataDir, "config/config.txt")]}], ["config_static_SUITE"]). install_config(Config) when is_list(Config) -> @@ -174,6 +174,7 @@ run_test(Name, Config, CTConfig, SuiteNames)-> Joiner = fun(Suite) -> filename:join(DataDir, "config/test/"++Suite) end, Suites = lists:map(Joiner, SuiteNames), {Opts,ERPid} = setup_env({suite,Suites}, Config, CTConfig), + ok = ct_test_support:run(Opts, Config), TestEvents = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(Name, diff --git a/lib/common_test/test/ct_groups_search_SUITE.erl b/lib/common_test/test/ct_groups_search_SUITE.erl new file mode 100644 index 0000000000..6b1c1f4634 --- /dev/null +++ b/lib/common_test/test/ct_groups_search_SUITE.erl @@ -0,0 +1,1245 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2012. 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 +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%%%------------------------------------------------------------------- +%%% File: +%%% +%%% Description: +%%% +%%% +%%% The suites used for the test are located in the data directory. +%%% +%%% The group(s) and case(s) are specified according to this: +%%% +%%% Tests = ct_groups:find_groups(Mod, GroupPaths, TestCases, GroupDef) +%%% +%%% GroupPaths = GroupPath | [GroupPath] +%%% GroupPath = atom() | [atom()] +%%% +%%% CT will find all paths that include GroupPath. GroupPath can be a +%%% single group, or a list of groups along the path to TestCases. +%%% If GroupPath is the latter, the last group in the list must be +%%% the "terminating" group in the path, or it will be impossible to +%%% execute test cases in higher level groups *only*, as in this case: +%%% groups() -> [{g1,[],[tc1,{g2,[],[tc2]}]}]. +%%% Compare: find_groups(x, g1, all, groups()), and +%%% find_groups(x, [[g1]], all, groups()) +%%% +%%% Some examples: +%%% +%%% GroupPaths = g1, means find all paths with g1 included +%%% GroupPaths = [g1], -''- +%%% GroupPaths = [g1,g2], search twice - once for g1 and once for g2 +%%% GroupPaths = [[g1,g2]], find cases under group g1 and sub group g2 +%%% GroupPaths = [[g1,g2],[g1,g3]], find cases for g1-g2 AND g1-g3 +%%% +%%% TestCases = all | atom() | [atom()] +%%% +%%%------------------------------------------------------------------- + +-module(ct_groups_search_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("common_test/src/ct_util.hrl"). + + +-define(eh, ct_test_support_eh). + +-define(M1, groups_search_dummy_1_SUITE). +-define(M2, groups_search_dummy_2_SUITE). + +%%-------------------------------------------------------------------- +%% TEST SERVER CALLBACK FUNCTIONS +%%-------------------------------------------------------------------- + +init_per_suite(Config) -> + DataDir = proplists:get_value(data_dir, Config), + code:add_patha(DataDir), + M1Erl = filename:join(DataDir, atom_to_list(?M1)++".erl"), + M2Erl = filename:join(DataDir, atom_to_list(?M2)++".erl"), + {ok,?M1} = compile:file(M1Erl, [{outdir,DataDir}]), + {ok,?M2} = compile:file(M2Erl, [{outdir,DataDir}]), + {module,?M1} = code:load_file(?M1), + {module,?M2} = code:load_file(?M2), + + Config1 = ct_test_support:init_per_suite(Config), + Config1. + +end_per_suite(Config) -> + ct_test_support:end_per_suite(Config). + +init_per_testcase(TestCase, Config) -> + ct_test_support:init_per_testcase(TestCase, Config). + +end_per_testcase(TestCase, Config) -> + ct_test_support:end_per_testcase(TestCase, Config). + +suite() -> [{ct_hooks,[ts_install_cth]}]. + +groups() -> + [ + {find_groups,[],[all_groups, + testcases_in_all_groups, + all_in_top_group1, + all_in_top_group2, + all_in_sub_group1, + all_in_sub_group2, + testcase_in_top_group1, + testcase_in_top_group2, + testcase_in_sub_group1, + testcase_in_sub_group2, + testcase_in_top_groups1, + testcase_in_top_groups2, + testcase_in_top_groups3, + testcase_in_top_groups4, + testcase_in_top_groups5, + testcase_in_top_groups6, + testcase_in_top_groups7, + testcase_in_sub_groups1, + testcase_in_sub_groups2, + testcase_in_sub_groups3, + testcase_in_sub_groups4, + testcase_in_sub_groups5, + testcase_in_sub_groups6, + testcase_in_sub_groups7, + testcase_in_sub_groups8, + testcase_in_sub_groups9, + testcase_in_sub_groups10, + testcase_in_sub_groups11, + testcase_in_sub_groups12, + testcase_in_sub_groups13, + bad_testcase_in_sub_groups1]}, + + {run_groups,[sequence],[run_groups_with_options, + run_groups_with_testspec]} + ]. + +all() -> + [{group,find_groups,[parallel]}, + {group,run_groups}]. + + + +%%-------------------------------------------------------------------- +%% TEST CASES CHECKING RETURN VALUE ONLY +%%-------------------------------------------------------------------- + +all_groups(_) -> + GPath = all, TCs = all, + + Found = ct_groups:find_groups(?M1, GPath, TCs, groups1()), + + Top1 = ct_groups:find_groups(?M1, top1, TCs, groups1()), + Top2 = ct_groups:find_groups(?M1, top2, TCs, groups1()), + + All = Top1 ++ Top2 ++ [{conf,[{name,sub2}], + {?M1,init_per_group}, + [{?M1,sub2_tc1},{?M1,sub2_tc2}], + {?M1,end_per_group}}], + + All = Found, + + {?M1,GPath,TCs,Top1++Top2}. + +%%%----------------------------------------------------------------- +%%% +testcases_in_all_groups(_) -> + GPath = all, TCs = [tc3,sub_tc2], + + Found = ct_groups:find_groups(?M2, GPath, TCs, groups2()), + + [Top1 = + {conf,[{name,top1}],{?M2,init_per_group}, + [{?M2,tc3}, + {conf,[{name,sub11}], + {?M2,init_per_group},[{?M2,tc3},{?M2,sub_tc2}], + {?M2,end_per_group}}, + {conf,[{name,sub12}], + {?M2,init_per_group}, + [{?M2,tc3},{?M2,sub_tc2}, + {conf,[{name,sub121}], + {?M2,init_per_group},[{?M2,tc3},{?M2,sub_tc2}], + {?M2,end_per_group}}], + {?M2,end_per_group}}], + {?M2,end_per_group}}, + + Top2 = + {conf,[{name,top2}],{?M2,init_per_group}, + [{?M2,tc3}, + {conf,[{name,sub21}], + {?M2,init_per_group}, + [{?M2,tc3},{?M2,sub_tc2}, + {conf,[{name,sub2xx}], + {?M2,init_per_group},[{?M2,tc3},{?M2,sub_tc2}], + {?M2,end_per_group}}], + {?M2,end_per_group}}, + + {conf,[{name,sub22}], + {?M2,init_per_group}, + [{?M2,tc3},{?M2,sub_tc2}, + {conf,[{name,sub221}], + {?M2,init_per_group},[{?M2,tc3},{?M2,sub_tc2}], + {?M2,end_per_group}}, + {conf,[{name,sub2xx}], + {?M2,init_per_group},[{?M2,tc3},{?M2,sub_tc2}], + {?M2,end_per_group}}], + {?M2,end_per_group}}], + {?M2,end_per_group}}, + + {conf,[{name,sub21}], + {?M2,init_per_group}, + [{?M2,tc3},{?M2,sub_tc2}, + {conf,[{name,sub2xx}], + {?M2,init_per_group},[{?M2,tc3},{?M2,sub_tc2}],{?M2,end_per_group}}], + {?M2,end_per_group}}, + + {conf,[{name,sub22}], + {?M2,init_per_group}, + [{?M2,tc3},{?M2,sub_tc2}, + {conf,[{name,sub221}], + {?M2,init_per_group},[{?M2,tc3},{?M2,sub_tc2}],{?M2,end_per_group}}, + {conf,[{name,sub2xx}], + {?M2,init_per_group},[{?M2,tc3},{?M2,sub_tc2}],{?M2,end_per_group}}], + {?M2,end_per_group}}, + + {conf,[{name,sub221}], + {?M2,init_per_group},[{?M2,tc3},{?M2,sub_tc2}],{?M2,end_per_group}}, + + {conf,[{name,sub2xx}], + {?M2,init_per_group},[{?M2,tc3},{?M2,sub_tc2}],{?M2,end_per_group}}] + + = Found, + + {?M2,GPath,TCs,[Top1,Top2]}. + +%%%----------------------------------------------------------------- +%%% +all_in_top_group1(_) -> + GPath= top1, TCs = all, + + Found = ct_groups:find_groups(?M1, GPath, TCs, groups1()), + + [{conf,[{name,top1}], + {?M1,init_per_group}, + [{?M1,top1_tc1},{?M1,top1_tc2}, + {conf,[{name,sub1}], + {?M1,init_per_group}, + [{?M1,sub1_tc1},{?M1,sub1_tc2}], + {?M1,end_per_group}}], + {?M1,end_per_group}}] = Found, + + {?M1,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +all_in_top_group2(_) -> + GPath= top2, TCs = all, + + Found = ct_groups:find_groups(?M1, GPath, TCs, groups1()), + + [{conf,[{name,top2}], + {?M1,init_per_group}, + [{conf,[{name,sub2}], + {?M1,init_per_group}, + [{?M1,sub2_tc1},{?M1,sub2_tc2}], + {?M1,end_per_group}}, + {?M1,top2_tc1},{?M1,top2_tc2}], + {?M1,end_per_group}}] = Found, + + {?M1,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +all_in_sub_group1(_) -> + GPath = sub1, TCs = all, + + Found = ct_groups:find_groups(?M1, GPath, TCs, groups1()), + + [{conf,[{name,top1}], + {?M1,init_per_group}, + [{conf,[{name,sub1}], + {?M1,init_per_group}, + [{?M1,sub1_tc1},{?M1,sub1_tc2}], + {?M1,end_per_group}}], + {?M1,end_per_group}}] = Found, + + {?M1,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +all_in_sub_group2(_) -> + GPath = sub2, TCs = all, + + Found = ct_groups:find_groups(?M1, GPath, TCs, groups1()), + + [Top2 = + {conf,[{name,top2}], + {?M1,init_per_group}, + [{conf,[{name,sub2}], + {?M1,init_per_group}, + [{?M1,sub2_tc1},{?M1,sub2_tc2}], + {?M1,end_per_group}}], + {?M1,end_per_group}}, + + {conf,[{name,sub2}], + {?M1,init_per_group}, + [{?M1,sub2_tc1},{?M1,sub2_tc2}], + {?M1,end_per_group}}] = Found, + + {?M1,GPath,TCs,Top2}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_top_group1(_) -> + GPath = top1, TCs = [top1_tc2], + + Found = ct_groups:find_groups(?M1, GPath, TCs, groups1()), + + [{conf,[{name,top1}], + {?M1,init_per_group}, + [{?M1,top1_tc2}], + {?M1,end_per_group}}] = Found, + + {?M1,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_top_group2(_) -> + GPath = top2, TCs = [top2_tc2], + + Found = ct_groups:find_groups(?M1, GPath, TCs, groups1()), + + [{conf,[{name,top2}], + {?M1,init_per_group}, + [{?M1,top2_tc2}], + {?M1,end_per_group}}] = Found, + + {?M1,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_sub_group1(_) -> + GPath = sub1, TCs = [sub1_tc2], + + Found = ct_groups:find_groups(?M1, GPath, TCs, groups1()), + + [{conf,[{name,top1}], + {?M1,init_per_group}, + [{conf,[{name,sub1}], + {?M1,init_per_group}, + [{?M1,sub1_tc2}], + {?M1,end_per_group}}], + {?M1,end_per_group}}] = Found, + + {?M1,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_sub_group2(_) -> + GPath = sub2, TCs = [sub2_tc2], + + Found = ct_groups:find_groups(?M1, GPath, TCs, groups1()), + + [Top2 = + {conf,[{name,top2}], + {?M1,init_per_group}, + [{conf,[{name,sub2}], + {?M1,init_per_group}, + [{?M1,sub2_tc2}], + {?M1,end_per_group}}], + {?M1,end_per_group}}, + + {conf,[{name,sub2}], + {?M1,init_per_group}, + [{?M1,sub2_tc2}], + {?M1,end_per_group}}] = Found, + + {?M1,GPath,TCs,Top2}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_top_groups1(_) -> + GPath = [top1,top2], TCs = all, + + Found = ct_groups:find_groups(?M2, GPath, TCs, groups2()), + + [{conf,[{name,top1}], + {?M2,init_per_group}, + [{?M2,top1_tc1},{?M2,top_tc2},{?M2,tc3}, + {conf,[{name,sub11}], + {?M2,init_per_group}, + [{?M2,sub11_tc1},{?M2,sub_tc2},{?M2,tc3}], + {?M2,end_per_group}}, + {conf,[{name,sub12}], + {?M2,init_per_group}, + [{?M2,sub12_tc1},{?M2,sub_tc2},{?M2,tc3}, + {conf,[{name,sub121}], + {?M2,init_per_group}, + [{?M2,sub121_tc1},{?M2,sub_tc2},{?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}], + {?M2,end_per_group}}, + + {conf,[{name,top2}], + {?M2,init_per_group}, + [{conf,[{name,sub21}], + {?M2,init_per_group}, + [{?M2,sub21_tc1},{?M2,sub_tc2},{?M2,tc3}, + {conf,[{name,sub2xx}], + {?M2,init_per_group}, + [{?M2,sub2xx_tc1},{?M2,sub_tc2},{?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}, + {?M2,top2_tc1},{?M2,top_tc2},{?M2,tc3}, + {conf,[{name,sub22}], + {?M2,init_per_group}, + [{conf,[{name,sub221}], + {?M2,init_per_group}, + [{?M2,sub221_tc1},{?M2,sub_tc2},{?M2,tc3}], + {?M2,end_per_group}}, + {?M2,sub22_tc1},{?M2,sub_tc2},{?M2,tc3}, + {conf,[{name,sub2xx}], + {?M2,init_per_group}, + [{?M2,sub2xx_tc1},{?M2,sub_tc2},{?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}], + {?M2,end_per_group}}] = Found, + + {?M2,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_top_groups2(_) -> + GPath = [top1,top2], TCs = tc3, + + Found = ct_groups:find_groups(?M2, GPath, TCs, groups2()), + + [{conf,[{name,top1}], + {?M2,init_per_group}, + [{?M2,tc3}, + {conf,[{name,sub11}], + {?M2,init_per_group}, + [{?M2,tc3}], + {?M2,end_per_group}}, + {conf,[{name,sub12}], + {?M2,init_per_group}, + [{?M2,tc3}, + {conf,[{name,sub121}], + {?M2,init_per_group}, + [{?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}], + {?M2,end_per_group}}, + + {conf,[{name,top2}], + {?M2,init_per_group}, + [{?M2,tc3}, + {conf,[{name,sub21}], + {?M2,init_per_group}, + [{?M2,tc3}, + {conf,[{name,sub2xx}], + {?M2,init_per_group}, + [{?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}, + + {conf,[{name,sub22}], + {?M2,init_per_group}, + [{?M2,tc3}, + {conf,[{name,sub221}], + {?M2,init_per_group}, + [{?M2,tc3}], + {?M2,end_per_group}}, + {conf,[{name,sub2xx}], + {?M2,init_per_group}, + [{?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}], + {?M2,end_per_group}}] = Found, + + {?M2,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_top_groups3(_) -> + GPath = [top1,top2], TCs = top1_tc1, + + Found = ct_groups:find_groups(?M2, GPath, TCs, groups2()), + + [{conf,[{name,top1}], + {?M2,init_per_group}, + [{?M2,top1_tc1}], + {?M2,end_per_group}}] = Found, + + {?M2,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_top_groups4(_) -> + GPath = [top1,top2], TCs = sub2xx_tc1, + + Found = ct_groups:find_groups(?M2, GPath, TCs, groups2()), + + [{conf,[{name,top2}], + {?M2,init_per_group}, + [{conf,[{name,sub21}], + {?M2,init_per_group}, + [{conf,[{name,sub2xx}], + {?M2,init_per_group}, + [{?M2,sub2xx_tc1}], + {?M2,end_per_group}}], + {?M2,end_per_group}}, + {conf,[{name,sub22}], + {?M2,init_per_group}, + [{conf,[{name,sub2xx}], + {?M2,init_per_group}, + [{?M2,sub2xx_tc1}], + {?M2,end_per_group}}], + {?M2,end_per_group}}], + {?M2,end_per_group}}] = Found, + + {?M2,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_top_groups5(_) -> + GPath = [top1,top2], TCs = [sub21_tc1,sub22_tc1], + + Found = ct_groups:find_groups(?M2, [top1,top2], [sub21_tc1,sub22_tc1], + groups2()), + + [{conf,[{name,top2}], + {?M2,init_per_group}, + [{conf,[{name,sub21}], + {?M2,init_per_group}, + [{?M2,sub21_tc1}], + {?M2,end_per_group}}, + {conf,[{name,sub22}], + {?M2,init_per_group}, + [{?M2,sub22_tc1}], + {?M2,end_per_group}}], + {?M2,end_per_group}}] = Found, + + {?M2,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_top_groups6(_) -> + GPath = [[top1],[top2]], TCs = tc3, + + Found = ct_groups:find_groups(?M2, GPath, TCs, groups2()), + + [{conf,[{name,top1}], + {?M2,init_per_group}, + [{?M2,tc3}], + {?M2,end_per_group}}, + {conf,[{name,top2}], + {?M2,init_per_group}, + [{?M2,tc3}], + {?M2,end_per_group}}] = Found, + + {?M2,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_top_groups7(_) -> + GPath = [[top1],[top2]], TCs = all, + + Found = ct_groups:find_groups(?M2, GPath, TCs, groups2()), + + [{conf,[{name,top1}], + {?M2,init_per_group}, + [{?M2,top1_tc1}, + {?M2,top_tc2}, + {?M2,tc3}], + {?M2,end_per_group}}, + {conf,[{name,top2}], + {?M2,init_per_group}, + [{?M2,top2_tc1}, + {?M2,top_tc2}, + {?M2,tc3}], + {?M2,end_per_group}}] = Found, + + {?M2,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_sub_groups1(_) -> + GPath = [sub121], TCs = tc3, + + Found = ct_groups:find_groups(?M2, sub121, tc3, groups2()), + Found = ct_groups:find_groups(?M2, [sub121], tc3, groups2()), + + [{conf,[{name,top1}], + {?M2,init_per_group}, + [{conf,[{name,sub12}], + {?M2,init_per_group}, + [{conf,[{name,sub121}], + {?M2,init_per_group}, + [{?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}], + {?M2,end_per_group}}] = Found, + + {?M2,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_sub_groups2(_) -> + GPath = sub12, TCs = tc3, + + Found = ct_groups:find_groups(?M2, sub12, tc3, groups2()), + Found = ct_groups:find_groups(?M2, [sub12], tc3, groups2()), + + [{conf,[{name,top1}], + {?M2,init_per_group}, + [{conf,[{name,sub12}], + {?M2,init_per_group}, + [{?M2,tc3}, + {conf,[{name,sub121}], + {?M2,init_per_group}, + [{?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}], + {?M2,end_per_group}}] = Found, + + FoundX = ct_groups:find_groups(?M2, [[sub12]], tc3, groups2()), + + [{conf,[{name,top1}], + {?M2,init_per_group}, + [{conf,[{name,sub12}], + {?M2,init_per_group}, + [{?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}] = FoundX, + + {?M2,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_sub_groups3(_) -> + GPath = [sub121,sub221], TCs = all, + + Found = ct_groups:find_groups(?M2, GPath, TCs, groups2()), + + [Top1 = + {conf,[{name,top1}], + {?M2,init_per_group}, + [{conf,[{name,sub12}], + {?M2,init_per_group}, + [{conf,[{name,sub121}], + {?M2,init_per_group}, + [{?M2,sub121_tc1}, + {?M2,sub_tc2}, + {?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}], + {?M2,end_per_group}}, + + Top2 = + {conf,[{name,top2}], + {?M2,init_per_group}, + [{conf,[{name,sub22}], + {?M2,init_per_group}, + [{conf,[{name,sub221}], + {?M2,init_per_group}, + [{?M2,sub221_tc1}, + {?M2,sub_tc2}, + {?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}], + {?M2,end_per_group}}, + + {conf,[{name,sub22}], + {?M2,init_per_group}, + [{conf,[{name,sub221}], + {?M2,init_per_group}, + [{?M2,sub221_tc1}, + {?M2,sub_tc2}, + {?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}, + + {conf,[{name,sub221}], + {?M2,init_per_group}, + [{?M2,sub221_tc1}, + {?M2,sub_tc2}, + {?M2,tc3}], + {?M2,end_per_group}}] = Found, + + {?M2,GPath,TCs,[Top1,Top2]}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_sub_groups4(_) -> + GPath = [top1,sub21], TCs = sub_tc2, + + Found = ct_groups:find_groups(?M2, GPath, TCs, groups2()), + + [Top1 = + {conf,[{name,top1}], + {?M2,init_per_group}, + [{conf,[{name,sub11}], + {?M2,init_per_group}, + [{?M2,sub_tc2}], + {?M2,end_per_group}}, + {conf,[{name,sub12}], + {?M2,init_per_group}, + [{?M2,sub_tc2}, + {conf,[{name,sub121}], + {?M2,init_per_group}, + [{?M2,sub_tc2}], + {?M2,end_per_group}}], + {?M2,end_per_group}}], + {?M2,end_per_group}}, + + Top2 = + {conf,[{name,top2}], + {?M2,init_per_group}, + [{conf,[{name,sub21}], + {?M2,init_per_group}, + [{?M2,sub_tc2}, + {conf,[{name,sub2xx}], + {?M2,init_per_group}, + [{?M2,sub_tc2}], + {?M2,end_per_group}}], + {?M2,end_per_group}}], + {?M2,end_per_group}}, + + {conf,[{name,sub21}], + {?M2,init_per_group}, + [{?M2,sub_tc2}, + {conf,[{name,sub2xx}], + {?M2,init_per_group}, + [{?M2,sub_tc2}], + {?M2,end_per_group}}], + {?M2,end_per_group}}] = Found, + + {?M2,GPath,TCs,[Top1,Top2]}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_sub_groups5(_) -> + GPath = [[top1,sub12]], TCs = sub12_tc1, + + Found = ct_groups:find_groups(?M2, GPath, TCs, groups2()), + + [{conf,[{name,top1}], + {?M2,init_per_group}, + [{conf,[{name,sub12}], + {?M2,init_per_group}, + [{?M2,sub12_tc1}], + {?M2,end_per_group}}], + {?M2,end_per_group}}] = Found, + + {?M2,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_sub_groups6(_) -> + GPath = [[top1,sub12]], TCs = [sub_tc2], + + Found = ct_groups:find_groups(?M2, GPath, TCs, groups2()), + + [{conf,[{name,top1}], + {?M2,init_per_group}, + [{conf,[{name,sub12}], + {?M2,init_per_group}, + [{?M2,sub_tc2}], + {?M2,end_per_group}}], + {?M2,end_per_group}}] = Found, + + {?M2,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_sub_groups7(_) -> + GPath = [[top1,sub12]], TCs = [sub12_tc1,sub_tc2], + + Found = ct_groups:find_groups(?M2, GPath, TCs, groups2()), + + [{conf,[{name,top1}], + {?M2,init_per_group}, + [{conf,[{name,sub12}], + {?M2,init_per_group}, + [{?M2,sub12_tc1}, + {?M2,sub_tc2}], + {?M2,end_per_group}}], + {?M2,end_per_group}}] = Found, + + {?M2,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_sub_groups8(_) -> + GPath = [[top2,sub22]], TCs = [sub22_tc1,sub_tc2], + + Found = ct_groups:find_groups(?M2, GPath, TCs, groups2()), + + [{conf,[{name,top2}], + {?M2,init_per_group}, + [{conf,[{name,sub22}], + {?M2,init_per_group}, + [{?M2,sub22_tc1}, + {?M2,sub_tc2}], + {?M2,end_per_group}}], + {?M2,end_per_group}}] = Found, + + {?M2,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_sub_groups9(_) -> + GPath = [[sub2xx]], TCs = tc3, + + Found = ct_groups:find_groups(?M2, sub2xx, tc3, groups2()), + Found = ct_groups:find_groups(?M2, [[sub2xx]], tc3, groups2()), + + [Top2 = + {conf,[{name,top2}], + {?M2,init_per_group}, + [{conf,[{name,sub21}], + {?M2,init_per_group}, + [{conf,[{name,sub2xx}], + {?M2,init_per_group}, + [{?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}, + {conf,[{name,sub22}], + {?M2,init_per_group}, + [{conf,[{name,sub2xx}], + {?M2,init_per_group}, + [{?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}], + {?M2,end_per_group}}, + + {conf,[{name,sub21}], + {?M2,init_per_group}, + [{conf,[{name,sub2xx}], + {?M2,init_per_group}, + [{?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}, + + {conf,[{name,sub22}], + {?M2,init_per_group}, + [{conf,[{name,sub2xx}], + {?M2,init_per_group}, + [{?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}, + + {conf,[{name,sub2xx}], + {?M2,init_per_group}, + [{?M2,tc3}], + {?M2,end_per_group}}] = Found, + + {?M2,GPath,TCs,Top2}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_sub_groups10(_) -> + GPath = [[sub22,sub2xx]], TCs = tc3, + + Found = ct_groups:find_groups(?M2, GPath, TCs, groups2()), + + [Top2 = + {conf,[{name,top2}], + {?M2,init_per_group}, + [{conf,[{name,sub22}], + {?M2,init_per_group}, + [{conf,[{name,sub2xx}], + {?M2,init_per_group}, + [{?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}], + {?M2,end_per_group}}, + + {conf,[{name,sub22}], + {?M2,init_per_group}, + [{conf,[{name,sub2xx}], + {?M2,init_per_group}, + [{?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}] = Found, + + {?M2,GPath,TCs,Top2}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_sub_groups11(_) -> + GPath = [[top1,sub12,sub121]], TCs = all, + + Found = ct_groups:find_groups(?M2, GPath, TCs, groups2()), + + [{conf,[{name,top1}], + {?M2,init_per_group}, + [{conf,[{name,sub12}], + {?M2,init_per_group}, + [{conf,[{name,sub121}], + {?M2,init_per_group}, + [{?M2,sub121_tc1}, + {?M2,sub_tc2}, + {?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}], + {?M2,end_per_group}}] = Found, + + {?M2,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_sub_groups12(_) -> + GPath = [[top2,sub2xx]], TCs = [sub2xx_tc1,tc3], + + Found = ct_groups:find_groups(?M2, GPath, TCs, groups2()), + + [{conf,[{name,top2}], + {?M2,init_per_group}, + [{conf,[{name,sub21}], + {?M2,init_per_group}, + [{conf,[{name,sub2xx}], + {?M2,init_per_group}, + [{?M2,sub2xx_tc1}, + {?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}, + {conf,[{name,sub22}], + {?M2,init_per_group}, + [{conf,[{name,sub2xx}], + {?M2,init_per_group}, + [{?M2,sub2xx_tc1}, + {?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}], + {?M2,end_per_group}}] = Found, + + {?M2,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +testcase_in_sub_groups13(_) -> + GPath = [[top2,sub22,sub2xx]], TCs = [top2_tc1,sub2xx_tc1,tc3], + + Found = ct_groups:find_groups(?M2, GPath, TCs, groups2()), + + [{conf,[{name,top2}], + {?M2,init_per_group}, + [{conf,[{name,sub22}], + {?M2,init_per_group}, + [{conf,[{name,sub2xx}], + {?M2,init_per_group}, + [{?M2,sub2xx_tc1}, + {?M2,tc3}], + {?M2,end_per_group}}], + {?M2,end_per_group}}], + {?M2,end_per_group}}] = Found, + + {?M2,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +bad_testcase_in_sub_groups1(_) -> + GPath = [sub2xx], TCs = [top2_tc1], + + Found = ct_groups:find_groups(?M2, GPath, TCs, groups2()), + + [] = Found, + + {?M2,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% +bad_testcase_in_sub_groups2(_) -> + GPath = [sub12,sub2xx], TCs = [top1_tc1,top2_tc1], + + Found = ct_groups:find_groups(?M2, GPath, TCs, groups2()), + + [] = Found, + + {?M2,GPath,TCs,Found}. + +%%%----------------------------------------------------------------- +%%% CASES EXECUTING THE TESTS +%%%----------------------------------------------------------------- + +run_groups_with_options(Config) -> + DataDir = ?config(data_dir, Config), + + {M1All,M1Rest,M2All,M2Rest} = get_all_groups_and_cases(Config), + + M1AllGrs = lists:flatmap(fun({Path,_,_}) when is_atom(hd(Path)) -> Path; + ({Path,_,_}) when is_list(hd(Path)) -> Path; + ({Path,_,_}) -> [Path] + end, M1All), + + %% ct:pal("NOW RUNNING M1 TEST: ~p", [M1All]), + + {OptsM11,ERPidM11} = setup([{dir,DataDir},{suite,?M1}, + {group,M1AllGrs},{label,m1_all_cases}], Config), + M1AllGrInfo = {M1AllGrs,lists:flatten([Found || {_,_,Found} <- M1All])}, + ok = execute(m1_all_cases, M1AllGrInfo, OptsM11, ERPidM11, Config), + + lists:foldl( + fun({GrPath,TCs,Found}, N) -> + TestName = list_to_atom("m1_spec_cases_" ++ integer_to_list(N)), + %% ct:pal("NOW RUNNING M1 TEST ~p: ~p + ~p", + %% [TestName,GrPath,TCs]), + {OptsM12,ERPidM12} = setup([{dir,DataDir},{suite,?M1}, + {group,GrPath},{testcase,TCs}, + {label,TestName}], Config), + ok = execute(TestName, {GrPath,TCs,Found}, + OptsM12, ERPidM12, Config), + N+1 + end, 1, M1Rest), + + %% ct:pal("NOW RUNNING M2 TEST: ~p", [M2All]), + + M2AllGrs = lists:flatmap(fun({Path,_,_}) when is_atom(hd(Path)) -> Path; + ({Path,_,_}) when is_list(hd(Path)) -> Path; + ({Path,_,_}) -> [Path] + end, M2All), + + + {OptsM21,ERPidM21} = setup([{dir,DataDir},{suite,?M2}, + {group,M2AllGrs},{testcase,all}, + {label,m2_all_cases}], Config), + M2AllGrInfo = {M2AllGrs,lists:flatten([Found || {_,_,Found} <- M2All])}, + ok = execute(m2_all_cases, M2AllGrInfo, OptsM21, ERPidM21, Config), + + lists:foldl( + fun({GrPath,TCs,Found}, N) -> + TestName = list_to_atom("m2_spec_cases_" ++ integer_to_list(N)), + %% ct:pal("NOW RUNNING M2 TEST ~p: ~p + ~p", [TestName,GrPath,TCs]), + {OptsM22,ERPidM22} = setup([{dir,DataDir},{suite,?M2}, + {group,GrPath},{testcase,TCs}, + {label,TestName}], Config), + ok = execute(TestName, {GrPath,TCs,Found}, + OptsM22, ERPidM22, Config), + N+1 + end, 1, M2Rest), + ok. + + +%%%----------------------------------------------------------------- +%%% +run_groups_with_testspec(Config) -> + Name = run_groups_with_testspec, + DataDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + + {M1All,M1Rest,M2All,M2Rest} = get_all_groups_and_cases(Config), + + M1AllGrs = lists:flatmap(fun({Path,_,_}) when is_atom(hd(Path)) -> Path; + ({Path,_,_}) when is_list(hd(Path)) -> Path; + ({Path,_,_}) -> [Path] + end, M1All), + M1AllTerm = {groups,DataDir,?M1,M1AllGrs}, + + M1RestTerms = lists:map( + fun({GrPath,TCs,_}) -> + {groups,DataDir,?M1,GrPath,{cases,TCs}} + end, M1Rest), + + M2AllGrs = lists:flatmap(fun({Path,_,_}) when is_atom(hd(Path)) -> Path; + ({Path,_,_}) when is_list(hd(Path)) -> Path; + ({Path,_,_}) -> [Path] + end, M2All), + M2AllTerm = {groups,DataDir,?M2,M2AllGrs,{cases,all}}, + + M2RestTerms = lists:map( + fun({GrPath,TCs,_}) -> + {groups,DataDir,?M2,GrPath,{cases,TCs}} + end, M2Rest), + + GroupTerms = lists:flatten([M1AllTerm, + M1RestTerms, + M2AllTerm, + M2RestTerms]), + + TestSpec = [{merge_tests,false}, + {label,Name}] ++ GroupTerms, + + ct:pal("Here's the test spec:~n~p", [TestSpec]), + + TestSpecName = ct_test_support:write_testspec(TestSpec, PrivDir, + "groups_search_spec"), + + {Opts,ERPid} = setup([{spec,TestSpecName}], Config), + GroupInfo = + [{M1AllTerm,lists:flatten([Found || {_,_,Found} <- M1All])} | + M1Rest] ++ + [{M2AllTerm,lists:flatten([Found || {_,_,Found} <- M2All])} | + M2Rest], + ok = execute(Name, GroupInfo, Opts, ERPid, Config). + +%%%----------------------------------------------------------------- +%%% HELP FUNCTIONS +%%%----------------------------------------------------------------- + +groups1() -> + [{top1,[],[top1_tc1,top1_tc2,{sub1,[],[sub1_tc1,sub1_tc2]}]}, + {top2,[],[{group,sub2},top2_tc1,top2_tc2]}, + {sub2,[],[sub2_tc1,sub2_tc2]}]. + +groups2() -> + [{top1,[],[top1_tc1,top_tc2,tc3, + {sub11,[],[sub11_tc1,sub_tc2,tc3]}, + {sub12,[],[sub12_tc1,sub_tc2,tc3, + {sub121,[],[sub121_tc1,sub_tc2,tc3]}]}]}, + {top2,[],[{group,sub21},top2_tc1,top_tc2,tc3,{group,sub22}]}, + {sub21,[],[sub21_tc1,sub_tc2,tc3,{group,sub2xx}]}, + {sub22,[],[{group,sub221},sub22_tc1,sub_tc2,tc3,{group,sub2xx}]}, + {sub221,[],[sub221_tc1,sub_tc2,tc3]}, + {sub2xx,[],[sub2xx_tc1,sub_tc2,tc3]}]. + +get_all_groups_and_cases(Config) -> + {value,{_,_,FindGrTCs}} = lists:keysearch(find_groups, 1, groups()), + + MGTFs = [apply(?MODULE, TC, [Config]) || TC <- FindGrTCs], + + ct:pal("Extracted data from ~p test cases", [length(MGTFs)]), + + lists:foldr(fun({M,Gs,TCs,F}, + {M11,M12,M21,M22}) -> + case {M,Gs,TCs} of + {?M1,all,_} -> {M11,[{Gs,TCs,F}|M12],M21,M22}; + {?M1,_,all} -> {[{Gs,all,F}|M11],M12,M21,M22}; + {?M1,_,_} -> {M11,[{Gs,TCs,F}|M12],M21,M22}; + {?M2,all,_} -> {M11,M12,M21,[{Gs,TCs,F}|M22]}; + {?M2,_,all} -> {M11,M12,[{Gs,all,F}|M21],M22}; + {?M2,_,_} -> {M11,M12,M21,[{Gs,TCs,F}|M22]} + end + end, {[],[],[],[]}, MGTFs). + +%%%----------------------------------------------------------------- + +setup(Test, Config) -> + Opts0 = ct_test_support:get_opts(Config), + Level = ?config(trace_level, Config), + EvHArgs = [{cbm,ct_test_support},{trace_level,Level}], + Opts = Opts0 ++ [{event_handler,{?eh,EvHArgs}}|Test], + ERPid = ct_test_support:start_event_receiver(Config), + {Opts,ERPid}. + +execute(Name, TestParams, Opts, ERPid, Config) -> + ok = ct_test_support:run(Opts, Config), + Events = ct_test_support:get_events(ERPid, Config), + Events1 = reformat(Events, ?eh), + ct_test_support:log_events(Name, + Events1, + ?config(priv_dir, Config), + Opts), + verify_events(Name, TestParams, Events1). + +reformat(Events, EH) -> + ct_test_support:reformat(Events, EH). + +%%%----------------------------------------------------------------- +%%% TEST EVENTS +verify_events(Name, Params, Events) -> + %% 2 tests (ct:run_test + script_start) is default + verify_events(Name, Params, Events, 2). + +verify_events(_, _, _, 0) -> + ok; +verify_events(Name, Params, Events, N) -> + test_events(Name, Params, Events), + verify_events(Name, Params, Events, N-1). + +%%%----------------------------------------------------------------- +%%% check run_groups_with_options + +test_events(TestName, {GrPath,Found}, Events) -> + test_events(TestName, {GrPath,all,Found}, Events); + +test_events(TestName, {GrPath,TCs,Found}, Events) + when TestName /= run_groups_with_testspec -> + try check_events(Events, flatten_tests(Found)) of + ok -> ok + catch + throw:Reason -> + ct:pal("Test failed for ~p with group path ~p and cases ~p" + "~nReason: ~p", [TestName,GrPath,TCs,Reason]), + throw(failed) + end; + +%%%----------------------------------------------------------------- +%%% check run_groups_with_testspec + +test_events(run_groups_with_testspec, Params, Events) -> + AllFound = lists:flatmap(fun({_All,Found}) when is_tuple(Found) -> + [Found]; + ({_All,Found}) -> + Found; + ({_Gr,_TCs,Found}) when is_tuple(Found) -> + [Found]; + ({_Gr,_TCs,Found}) -> + Found + end, Params), + try check_events(Events, flatten_tests(AllFound)) of + ok -> ok + catch + throw:Reason -> + ct:pal("Test failed for run_groups_with_testspec." + "~nReason: ~p", [Reason]), + throw(failed) + end. + +flatten_tests({conf,[{name,G}|_],{Mod,_I},Tests,_E}) -> + lists:flatten([{group,Mod,G} | flatten_tests(Tests)]); +flatten_tests([{conf,[{name,G}|_],{Mod,_I},Tests,_E} | Confs]) -> + lists:flatten([{group,Mod,G} | flatten_tests(Tests)]) ++ + lists:flatten(flatten_tests(Confs)); +flatten_tests([{_Mod,_TC} = Case | Tests]) -> + lists:flatten([Case | flatten_tests(Tests)]); +flatten_tests([]) -> + []. + +check_events([{_,tc_start,{Mod,{init_per_group,G,_}}} | Evs], + [{group,Mod,G} | Check]) -> + check_events(Evs, Check); +check_events([{_,tc_start,{Mod,TC}} | Evs], + [{Mod,TC} | Check]) when is_atom(TC) -> + check_events(Evs, Check); +check_events([{_,tc_start,{Mod,{init_per_group,G,_}}} | _Evs], Check) -> + ct:pal("CHECK FAILED!~nGroup ~p in ~p not found in ~p.", + [G,Mod,Check]), + throw({test_not_found,{Mod,G}}); +check_events([{_,tc_start,{Mod,TC}} | _Evs], Check) + when is_atom(TC), TC /= init_per_suite, TC /= end_per_suite -> + ct:pal("CHECK FAILED!~nCase ~p in ~p not found in ~p.", + [TC,Mod,Check]), + throw({test_not_found,{Mod,TC}}); +check_events([Group | Evs], Check) when is_list(Group) -> + Check1 = check_events(Group, Check), + check_events(Evs, Check1); +check_events(_, []) -> + ok; +check_events([Elem | Evs], Check) when is_tuple(Elem) -> + check_events(Evs, Check); +check_events([], Check = [_|_]) -> + ct:pal("CHECK FAILED!~nTests remain: ~p", [Check]), + throw({tests_remain,Check}); +check_events([Wut | _],_) -> + throw({unexpected,Wut}). + diff --git a/lib/common_test/test/ct_groups_search_SUITE_data/groups_search_dummy_1_SUITE.erl b/lib/common_test/test/ct_groups_search_SUITE_data/groups_search_dummy_1_SUITE.erl new file mode 100644 index 0000000000..6f6922e686 --- /dev/null +++ b/lib/common_test/test/ct_groups_search_SUITE_data/groups_search_dummy_1_SUITE.erl @@ -0,0 +1,83 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2011. 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 +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(groups_search_dummy_1_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + + +all() -> + [{group,top1}, + {group,top2}]. + +groups() -> + [{top1,[],[top1_tc1,top1_tc2,{sub1,[],[sub1_tc1,sub1_tc2]}]}, + {top2,[],[{group,sub2},top2_tc1,top2_tc2]}, + {sub2,[],[sub2_tc1,sub2_tc2]}]. + +%%%----------------------------------------------------------------- +%%% CONFIG FUNCS +%%%----------------------------------------------------------------- + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, _Config) -> + ok. + +init_per_group(_, Config) -> + Config. + +end_per_group(_, _Config) -> + ok. + +%%%----------------------------------------------------------------- +%%% TEST CASES +%%%----------------------------------------------------------------- + +top1_tc1(_) -> + ok. + +top1_tc2(_) -> + ok. + +sub1_tc1(_) -> + ok. + +sub1_tc2(_) -> + ok. + +top2_tc1(_) -> + ok. + +top2_tc2(_) -> + ok. + +sub2_tc1(_) -> + ok. + +sub2_tc2(_) -> + ok. diff --git a/lib/common_test/test/ct_groups_search_SUITE_data/groups_search_dummy_2_SUITE.erl b/lib/common_test/test/ct_groups_search_SUITE_data/groups_search_dummy_2_SUITE.erl new file mode 100644 index 0000000000..ac3c000079 --- /dev/null +++ b/lib/common_test/test/ct_groups_search_SUITE_data/groups_search_dummy_2_SUITE.erl @@ -0,0 +1,102 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2011. 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 +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(groups_search_dummy_2_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + + +all() -> + [{group,top1}, + {group,top2}]. + +groups() -> + [{top1,[],[top1_tc1,top_tc2,tc3, + {sub11,[],[sub11_tc1,sub_tc2,tc3]}, + {sub12,[],[sub12_tc1,sub_tc2,tc3, + {sub121,[],[sub121_tc1,sub_tc2,tc3]}]}]}, + + {top2,[],[{group,sub21},top2_tc1,top_tc2,tc3,{group,sub22}]}, + {sub21,[],[sub21_tc1,sub_tc2,tc3,{group,sub2xx}]}, + {sub22,[],[{group,sub221},sub22_tc1,sub_tc2,tc3,{group,sub2xx}]}, + {sub221,[],[sub221_tc1,sub_tc2,tc3]}, + {sub2xx,[],[sub2xx_tc1,sub_tc2,tc3]}]. + +%%%----------------------------------------------------------------- +%%% CONFIG FUNCS +%%%----------------------------------------------------------------- + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, _Config) -> + ok. + +init_per_group(_, Config) -> + Config. + +end_per_group(_, _Config) -> + ok. + +%%%------------------------------------------------------------------ +%%% TEST CASES +%%%------------------------------------------------------------------ + +top1_tc1(_) -> + ok. + +top_tc2(_) -> + ok. + +tc3(_) -> + ok. + +sub_tc2(_) -> + ok. + +sub11_tc1(_) -> + ok. + +sub12_tc1(_) -> + ok. + +sub121_tc1(_) -> + ok. + +top2_tc1(_) -> + ok. + +sub21_tc1(_) -> + ok. + +sub22_tc1(_) -> + ok. + +sub221_tc1(_) -> + ok. + +sub2xx_tc1(_) -> + ok. diff --git a/lib/common_test/test/ct_netconfc_SUITE.erl b/lib/common_test/test/ct_netconfc_SUITE.erl index 30084a6228..3042a924fe 100644 --- a/lib/common_test/test/ct_netconfc_SUITE.erl +++ b/lib/common_test/test/ct_netconfc_SUITE.erl @@ -113,7 +113,7 @@ reformat(Events, EH) -> %%%----------------------------------------------------------------- %%% TEST EVENTS %%%----------------------------------------------------------------- -events_to_check(Test,Config) -> +events_to_check(default,Config) -> {module,_} = code:load_abs(filename:join(?config(data_dir,Config), netconfc1_SUITE)), TCs = netconfc1_SUITE:all(), diff --git a/lib/diameter/doc/src/.gitignore b/lib/diameter/doc/src/.gitignore index feeb378fd8..5776e1cc76 100644 --- a/lib/diameter/doc/src/.gitignore +++ b/lib/diameter/doc/src/.gitignore @@ -1,2 +1,3 @@ /depend.mk +/seehere.ent diff --git a/lib/diameter/doc/src/Makefile b/lib/diameter/doc/src/Makefile index d1d5e8f869..8ad38ba0d5 100644 --- a/lib/diameter/doc/src/Makefile +++ b/lib/diameter/doc/src/Makefile @@ -34,7 +34,8 @@ XML_REF_FILES = $(XML_REF1_FILES) $(XML_REF3_FILES) $(XML_REF4_FILES) XML_FILES = $(BOOK_FILES) $(XML_APPLICATION_FILES) \ $(XML_REF_FILES) \ - $(XML_PART_FILES) $(XML_CHAPTER_FILES) + $(XML_PART_FILES) $(XML_CHAPTER_FILES) \ + seealso.ent INTERNAL_HTML_FILES = $(TECHNICAL_DESCR_FILES:%.xml=$(HTMLDIR)/%.html) @@ -93,7 +94,7 @@ html: gifs $(HTML_REF_MAN_FILE) clean clean_docs: clean_pdf clean_html clean_man rm -f errs core *~ - rm -f depend.mk + rm -f depend.mk seehere.ent clean_pdf: rm -f $(PDFDIR)/* @@ -170,7 +171,9 @@ release_docs_spec: $(LOCAL)docs release_spec: -depend.mk: depend.sed $(XML_REF_FILES) $(XML_CHAPTER_FILES) Makefile +depend.mk: depend.sed Makefile seealso.ent \ + $(XML_REF_FILES) $(XML_CHAPTER_FILES) + sed -f seehere.sed seealso.ent > seehere.ent (for f in $(XML_REF_FILES) $(XML_CHAPTER_FILES); do \ sed -f $< $$f | sed "s@%FILE%@`basename $$f .xml`@g"; \ done) \ diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml index 80863f8eff..bc42b75c7a 100644 --- a/lib/diameter/doc/src/diameter.xml +++ b/lib/diameter/doc/src/diameter.xml @@ -1,5 +1,10 @@ <?xml version="1.0" encoding="latin1" ?> -<!DOCTYPE erlref SYSTEM "erlref.dtd"> +<!DOCTYPE erlref SYSTEM "erlref.dtd" [ + <!ENTITY % also SYSTEM "seealso.ent" > + <!ENTITY % here SYSTEM "seehere.ent" > + %also; + %here; +]> <erlref> <header> @@ -49,15 +54,12 @@ Diameter protocol as defined in RFC 3588.</p> <p> Basic usage consists of creating a representation of a -locally implemented Diameter node and its capabilities with <seealso -marker="#start_service">start_service/2</seealso>, adding transport -capability using <seealso -marker="#add_transport">add_transport/2</seealso> and sending Diameter -requests and receiving Diameter answers with <seealso -marker="#call">call/4</seealso>. +locally implemented Diameter node and its capabilities with +&start_service;, adding transport capability using +&add_transport; and sending Diameter +requests and receiving Diameter answers with &call;. Incoming Diameter requests are communicated as callbacks to a -<seealso -marker="diameter_app">diameter_app(3)</seealso> callback modules as +&man_app; callback modules as specified in the service configuration.</p> <p> @@ -90,7 +92,7 @@ in this module.</p> <item> <p> Types corresponding to RFC 3588 AVP Data Formats. -Defined in <seealso marker="diameter_dict#DATA_TYPES">diameter_dict(4)</seealso>.</p> +Defined in &dict_data_types;.</p> <marker id="application_alias"/> </item> @@ -100,7 +102,7 @@ Defined in <seealso marker="diameter_dict#DATA_TYPES">diameter_dict(4)</seealso> <p> A name identifying a Diameter application in service configuration. -Passed to <seealso marker="#call">call/4</seealso> when sending requests +Passed to &call; when sending requests defined by the application.</p> <marker id="application_module"/> @@ -110,23 +112,22 @@ defined by the application.</p> | [Mod | ExtraArgs] | #diameter_callback{}</c></tag> <item> -<code> +<pre> Mod = atom() ExtraArgs = list() -</code> +</pre> <p> -A module implementing the callback interface defined in <seealso -marker="diameter_app">diameter_app(3)</seealso>, along with any +A module implementing the callback interface defined in &man_app;, +along with any extra arguments to be appended to those documented for the interface. Note that extra arguments specific to an outgoing request can be -specified to <seealso marker="#call">call/4</seealso>, in which case +specified to &call;, in which case those are are appended to any module-specific extra arguments.</p> <p> Specifying a <c>#diameter_callback{}</c> record allows individual -functions to be configured in place of the usual <seealso -marker="diameter_app">diameter_app(3)</seealso> callbacks. +functions to be configured in place of the usual &man_app; callbacks. See that module for details.</p> <marker id="application_opt"/> @@ -141,7 +142,7 @@ Has one the following types.</p> <taglist> -<tag><c>{alias, <seealso marker="#application_alias">application_alias()</seealso>}</c></tag> +<tag><c>{alias, &application_alias;}</c></tag> <item> <p> An unique identifier for the application in the scope of the @@ -156,17 +157,15 @@ unspecified.</p> The name of an encode/decode module for the Diameter messages defined by the application. These modules are generated from a specification file whose format is -documented in <seealso -marker="diameter_dict">diameter_dict(4)</seealso>.</p> +documented in &man_dict;.</p> </item> -<tag><c>{module, <seealso marker="#application_module">application_module()</seealso>}</c></tag> +<tag><c>{module, &application_module;}</c></tag> <item> <p> The callback module with which messages of the Diameter application are handled. -See <seealso marker="diameter_app">diameter_app(3)</seealso> for -the required interface and semantics.</p> +See &man_app; for the required interface and semantics.</p> </item> <tag><c>{state, term()}</c></tag> @@ -174,7 +173,7 @@ the required interface and semantics.</p> <p> The initial callback state. The prevailing state is passed to some -<seealso marker="diameter_app">diameter_app(3)</seealso> +&man_app; callbacks, which can then return a new state. Defaults to the value of the <c>alias</c> option if unspecified.</p> </item> @@ -182,14 +181,13 @@ Defaults to the value of the <c>alias</c> option if unspecified.</p> <tag><c>{call_mutates_state, true|false}</c></tag> <item> <p> -Specifies whether or not the <seealso -marker="diameter_app#pick_peer">pick_peer/4</seealso> +Specifies whether or not the &app_pick_peer; application callback can modify the application state, Defaults to <c>false</c> if unspecified.</p> <note> <p> -<seealso marker="diameter_app#pick_peer">pick_peer</seealso> callbacks +&app_pick_peer; callbacks are serialized when these are allowed to modify state, which is a potential performance bottleneck. A simple Diameter client may suffer no ill effects from using mutable @@ -203,10 +201,8 @@ probably avoid it.</p> <p> Determines the manner in which incoming answer messages containing decode errors are handled. -If <c>callback</c> then errors result in a <seealso -marker="diameter_app#handle_answer">handle_answer/4</seealso> -callback in the same fashion as for <seealso -marker="diameter_app#handle_request">handle_request/3</seealso>, with +If <c>callback</c> then errors result in a &app_handle_answer; +callback in the same fashion as for &app_handle_request;, with errors communicated in the <c>errors</c> field of the <c>#diameter_packet{}</c> record passed to the callback. If <c>report</c> then an answer containing errors is discarded @@ -214,7 +210,7 @@ without a callback and a warning report is written to the log. If <c>discard</c> then an answer containing errors is silently discarded without a callback. In both the <c>report</c> and <c>discard</c> cases the return value -for the <seealso marker="#call">call/4</seealso> invocation in +for the &call; invocation in question is as if a callback had taken place and returned <c>{error, failure}</c>.</p> @@ -231,7 +227,7 @@ Defaults to <c>report</c> if unspecified.</p> <item> <p> -Options available to <seealso marker="#call">call/4</seealso> when +Options available to &call; when sending an outgoing Diameter request. Has one of the following types.</p> @@ -247,18 +243,18 @@ itself. Multiple options append to the argument list.</p> </item> -<tag><c>{filter, <seealso marker="#peer_filter">peer_filter()</seealso>}</c></tag> +<tag><c>{filter, &peer_filter;}</c></tag> <item> <p> A filter to apply to the list of available peers before passing them to -the <seealso marker="diameter_app#pick_peer">pick_peer/4</seealso> +the &app_pick_peer; callback for the application in question. Multiple options are equivalent a single <c>all</c> filter on the corresponding list of filters. Defaults to <c>none</c>.</p> </item> -<tag><c>{timeout, <seealso marker="diameter_dict#DATA_TYPES">Unsigned32()</seealso>}</c></tag> +<tag><c>{timeout, &dict_Unsigned32;}</c></tag> <item> <p> The number of milliseconds after which the request should @@ -269,21 +265,17 @@ Defaults to 5000.</p> <tag><c>detach</c></tag> <item> <p> -Causes <seealso marker="#call">call/4</seealso> to return <c>ok</c> as +Causes &call; to return <c>ok</c> as soon as the request in question has been encoded instead of waiting for and returning -the result from a subsequent -<seealso marker="diameter_app#handle_answer">handle_answer/4</seealso> -or <seealso -marker="diameter_app#handle_error">handle_error/4</seealso> -callback.</p> +the result from a subsequent &app_handle_answer; or +&app_handle_error; callback.</p> </item> </taglist> <p> -An invalid option will cause <seealso marker="#call">call/4</seealso> -to fail.</p> +An invalid option will cause &call; to fail.</p> <marker id="capability"/> </item> @@ -300,9 +292,9 @@ Has one of the following types.</p> <taglist> -<tag><c>{'Origin-Host', <seealso marker="diameter_dict#DATA_TYPES">DiameterIdentity()</seealso>}</c></tag> -<tag><c>{'Origin-Realm', <seealso marker="diameter_dict#DATA_TYPES">DiameterIdentity()</seealso>}</c></tag> -<tag><c>{'Host-IP-Address', [<seealso marker="diameter_dict#DATA_TYPES">Address()</seealso>]}</c></tag> +<tag><c>{'Origin-Host', &dict_DiameterIdentity;}</c></tag> +<tag><c>{'Origin-Realm', &dict_DiameterIdentity;}</c></tag> +<tag><c>{'Host-IP-Address', [&dict_Address;]}</c></tag> <item> <p> An address list is available to the start function of a @@ -312,24 +304,23 @@ Host-IP-Address need not be specified if the transport start function returns an address list.</p> </item> -<tag><c>{'Vendor-Id', <seealso marker="diameter_dict#DATA_TYPES">Unsigned32()</seealso>}</c></tag> -<tag><c>{'Product-Name', <seealso marker="diameter_dict#DATA_TYPES">UTF8String()</seealso>}</c></tag> -<tag><c>{'Origin-State-Id', <seealso marker="diameter_dict#DATA_TYPES">Unsigned32()</seealso>}</c></tag> +<tag><c>{'Vendor-Id', &dict_Unsigned32;}</c></tag> +<tag><c>{'Product-Name', &dict_UTF8String;}</c></tag> +<tag><c>{'Origin-State-Id', &dict_Unsigned32;}</c></tag> <item> <p> Origin-State-Id is optional but will be included in outgoing messages sent by diameter itself: CER/CEA, DWR/DWA and DPR/DPA. Setting a value of <c>0</c> (zero) is equivalent to not setting a value as documented in RFC 3588. -The function <seealso -marker="#origin_state_id">origin_state_id/0</seealso> +The function &origin_state_id; can be used as to retrieve a value that is computed when the diameter application is started.</p> </item> -<tag><c>{'Supported-Vendor-Id', [<seealso marker="diameter_dict#DATA_TYPES">Unsigned32()</seealso>]}</c></tag> -<tag><c>{'Auth-Application-Id', [<seealso marker="diameter_dict#DATA_TYPES">Unsigned32()</seealso>]}</c></tag> -<tag><c>{'Inband-Security-Id', [<seealso marker="diameter_dict#DATA_TYPES">Unsigned32()</seealso>]}</c></tag> +<tag><c>{'Supported-Vendor-Id', [&dict_Unsigned32;]}</c></tag> +<tag><c>{'Auth-Application-Id', [&dict_Unsigned32;]}</c></tag> +<tag><c>{'Inband-Security-Id', [&dict_Unsigned32;]}</c></tag> <item> <p> Inband-Security-Id defaults to the empty list, which is equivalent to a @@ -338,9 +329,9 @@ If 1 (= TLS) is specified then TLS is selected if the CER/CEA received from the peer offers it.</p> </item> -<tag><c>{'Acct-Application-Id', [<seealso marker="diameter_dict#DATA_TYPES">Unsigned32()</seealso>]}</c></tag> -<tag><c>{'Vendor-Specific-Application-Id', [<seealso marker="diameter_dict#DATA_TYPES">Grouped()</seealso>]}</c></tag> -<tag><c>{'Firmware-Revision', <seealso marker="diameter_dict#DATA_TYPES">Unsigned32()</seealso>}</c></tag> +<tag><c>{'Acct-Application-Id', [&dict_Unsigned32;]}</c></tag> +<tag><c>{'Vendor-Specific-Application-Id', [&dict_Grouped;]}</c></tag> +<tag><c>{'Firmware-Revision', &dict_Unsigned32;}</c></tag> </taglist> @@ -357,7 +348,7 @@ It is an error to specify duplicate tuples.</p> An expression that can be evaluated as a function in the following sense.</p> -<code> +<pre> eval([{M,F,A} | T]) -> apply(M, F, T ++ A); eval([[F|A] | T]) -> @@ -366,10 +357,10 @@ eval([F|A]) -> apply(F, A); eval(F) -> eval([F]). -</code> +</pre> <p> -Applying an <c><seealso marker="#evaluable">evaluable()</seealso></c> +Applying an <c>&evaluable;</c> <c>E</c> to an argument list <c>A</c> is meant in the sense of <c>eval([E|A])</c>.</p> @@ -380,10 +371,7 @@ situations in which the fun is not short-lived and code is to be upgraded at runtime since any processes retaining such a fun will have a reference to old code. In particular, such a value is typically inappropriate in -configuration passed to <seealso -marker="#start_service">start_service/2</seealso> or -<seealso -marker="#add_transport">add_transport/2</seealso>.</p> +configuration passed to &start_service; or &add_transport;.</p> </warning> <marker id="peer_filter"/> @@ -392,10 +380,8 @@ marker="#add_transport">add_transport/2</seealso>.</p> <tag><c>peer_filter() = term()</c></tag> <item> <p> -A filter passed to <seealso marker="#call">call/4</seealso> -in order to select candidate peers for a -<seealso marker="diameter_app#pick_peer">pick_peer/4</seealso> -callback. +A filter passed to &call; in order to select candidate peers for a +&app_pick_peer; callback. Has one of the following types.</p> <taglist> @@ -426,42 +412,42 @@ or any peer if the request does not contain a <c>Destination-Realm</c> AVP.</p> </item> -<tag><c>{host, any|<seealso marker="diameter_dict#DATA_TYPES">DiameterIdentity()</seealso>}</c></tag> +<tag><c>{host, any|&dict_DiameterIdentity;}</c></tag> <item> <p> Matches only those peers whose <c>Origin-Host</c> has the specified value, or all peers if the atom <c>any</c>.</p> </item> -<tag><c>{realm, any|<seealso marker="diameter_dict#DATA_TYPES">DiameterIdentity()</seealso></c></tag> +<tag><c>{realm, any|&dict_DiameterIdentity;</c></tag> <item> <p> Matches only those peers whose <c>Origin-Realm</c> has the specified value, or all peers if the atom <c>any</c>.</p> </item> -<tag><c>{eval, <seealso marker="#evaluable">evaluable()</seealso>}</c></tag> +<tag><c>{eval, &evaluable;}</c></tag> <item> <p> -Matches only those peers for which the specified <c><seealso -marker="#evaluable">evaluable()</seealso></c> returns +Matches only those peers for which the specified +<c>&evaluable;</c> returns <c>true</c> on the connection's <c>diameter_caps</c> record. Any other return value or exception is equivalent to <c>false</c>.</p> </item> -<tag><c>{neg, <seealso marker="#peer_filter">peer_filter()</seealso>}</c></tag> +<tag><c>{neg, &peer_filter;}</c></tag> <item> <p> Matches only those peers not matched by the specified filter.</p> </item> -<tag><c>{all, [<seealso marker="#peer_filter">peer_filter()</seealso>]}</c></tag> +<tag><c>{all, [&peer_filter;]}</c></tag> <item> <p> Matches only those peers matched by each filter in the specified list.</p> </item> -<tag><c>{any, [<seealso marker="#peer_filter">peer_filter()</seealso>]}</c></tag> +<tag><c>{any, [&peer_filter;]}</c></tag> <item> <p> Matches only those peers matched by at least one filter in the @@ -477,15 +463,12 @@ that matches no peer.</p> <note> <p> The <c>host</c> and <c>realm</c> filters examine the -outgoing request as passed to <seealso marker="#call">call/4</seealso>, -assuming that this is a record- or list-valued <c><seealso -marker="diameter_app#message">diameter_app:message()</seealso></c>, +outgoing request as passed to &call;, +assuming that this is a record- or list-valued <c>&app_message;</c>, and that the message contains at most one of each AVP. -If this is not the case then the <c>{host|realm, <seealso -marker="diameter_dict#DATA_TYPES">DiameterIdentity()</seealso>}</c> +If this is not the case then the <c>{host|realm, &dict_DiameterIdentity;}</c> filters must be used to achieve the desired result. -An empty <c><seealso -marker="diameter_dict#DATA_TYPES">DiameterIdentity()</seealso></c> +An empty <c>&dict_DiameterIdentity;</c> (which should not be typical) matches all hosts/realms for the purposes of filtering.</p> </note> @@ -504,7 +487,7 @@ candidates list.</p> <item> <p> An event message sent to processes that have subscribed to these using -<seealso marker="#subscribe">subscribe/1</seealso>.</p> +&subscribe;.</p> <p> The <c>info</c> field of the event record can have one of the @@ -512,16 +495,27 @@ following types.</p> <taglist> +<tag><c>start</c></tag> +<tag><c>stop</c></tag> + +<item> +<p> +The service is being started or stopped. +No event precedes a <c>start</c> event. +No event follows a <c>stop</c> event and this event +implies the termination of all transport processes.</p> +</item> + <tag><c>{up, Ref, Peer, Config, Pkt}</c></tag> <tag><c>{up, Ref, Peer, Config}</c></tag> <tag><c>{down, Ref, Peer, Config}</c></tag> <item> -<code> -Ref = <seealso marker="#transport_ref">transport_ref()</seealso> -Peer = <seealso marker="diameter_app#peer">diameter_app:peer()</seealso> -Config = {connect|listen, [<seealso marker="#transport_opt">transport_opt()</seealso>]} +<pre> +Ref = &transport_ref; +Peer = &app_peer; +Config = {connect|listen, [&transport_opt;]} Pkt = #diameter_packet{} -</code> +</pre> <p> The RFC 3539 watchdog state machine has @@ -535,7 +529,8 @@ connectivity.</p> <p> Note that a single <c>up</c>/<c>down</c> event for a given peer -corresponds to one <seealso marker="diameter_app#peer_up">peer_up/peer_down</seealso> +corresponds to one +<seealso marker="diameter_app#Mod:peer_up-3">peer_up/peer_down</seealso> callback for each of the Diameter applications negotiated during capablilities exchange. That is, the event communicates connectivity with the @@ -545,25 +540,23 @@ respect to individual Diameter applications.</p> <tag><c>{reconnect, Ref, Opts}</c></tag> <item> -<code> -Ref = <seealso marker="#transport_ref">transport_ref()</seealso> -Opts = [<seealso marker="#transport_opt">transport_opt()</seealso>] -</code> +<pre> +Ref = &transport_ref; +Opts = [&transport_opt;] +</pre> <p> A connecting transport is attempting to establish/reestablish a -transport connection with a peer following <seealso -marker="#reconnect_timer">reconnect_timer</seealso> or -<seealso marker="#watchdog_timer">watchdog_timer</seealso> -expiry.</p> +transport connection with a peer following &reconnect_timer; or +&watchdog_timer; expiry.</p> </item> <tag><c>{closed, Ref, Reason, Config}</c></tag> <item> -<code> -Ref = <seealso marker="#transport_ref">transport_ref()</seealso> -Config = {connect|listen, [<seealso marker="#transport_opt">transport_opt()</seealso>]} -</code> +<pre> +Ref = &transport_ref; +Config = {connect|listen, [&transport_opt;]} +</pre> <p> Capabilities exchange has failed. @@ -573,13 +566,13 @@ Capabilities exchange has failed. <tag><c>{'CER', Result, Caps, Pkt}</c></tag> <item> -<code> +<pre> Result = ResultCode | {capabilities_cb, CB, ResultCode|discard} Caps = #diameter_caps{} Pkt = #diameter_packet{} ResultCode = integer() -CB = <seealso marker="#evaluable">evaluable()</seealso> -</code> +CB = &evaluable; +</pre> <p> An incoming CER has been answered with the indicated result code or @@ -593,11 +586,11 @@ contains the rejecting callback.</p> <tag><c>{'CER', Caps, {ResultCode, Pkt}}</c></tag> <item> -<code> +<pre> ResultCode = integer() Caps = #diameter_caps{} Pkt = #diameter_packet{} -</code> +</pre> <p> An incoming CER contained errors and has been answered with the @@ -606,14 +599,21 @@ indicated result code. <c>Pkt</c> contains the CER in question.</p> </item> +<tag><c>{'CER', timeout}</c></tag> +<item> +<p> +An expected CER was not received within &capx_timeout; of +connection establishment.</p> +</item> + <tag><c>{'CEA', Result, Caps, Pkt}</c></tag> <item> -<code> +<pre> Result = integer() | atom() | {capabilities_cb, CB, ResultCode|discard} Caps = #diameter_caps{} Pkt = #diameter_packet{} ResultCode = integer() -</code> +</pre> <p> An incoming CEA has been rejected for the indicated reason. @@ -628,10 +628,10 @@ contains the rejecting callback.</p> <tag><c>{'CEA', Caps, Pkt}</c></tag> <item> -<code> +<pre> Caps = #diameter_caps{} Pkt = #diameter_packet{} -</code> +</pre> <p> An incoming CEA contained errors and has been rejected. @@ -639,17 +639,24 @@ An incoming CEA contained errors and has been rejected. <c>Pkt</c> contains the CEA in question.</p> </item> +<tag><c>{'CEA', timeout}</c></tag> +<item> +<p> +An expected CEA was not received within &capx_timeout; +of connection establishment.</p> +</item> + </taglist> </item> <tag><c>{watchdog, Ref, PeerRef, {From, To}, Config}</c></tag> <item> -<code> -Ref = <seealso marker="#transport_ref">transport_ref()</seealso> -PeerRef = <seealso marker="diameter_app#peer_ref">diameter_app:peer_ref()</seealso> +<pre> +Ref = &transport_ref; +PeerRef = &app_peer_ref; From, To = initial | okay | suspect | down | reopen Config = {connect|listen, [transport_opt()]} -</code> +</pre> <p> An RFC 3539 watchdog state machine has changed state.</p> @@ -667,8 +674,7 @@ info fields of forms other than the above.</p> <tag><c>service_name() = term()</c></tag> <item> <p> -The name of a service as passed to <seealso -marker="#start_service">start_service/2</seealso> and with which the +The name of a service as passed to &start_service; and with which the service is identified. There can be at most one service with a given name on a given node. Note that <seealso marker="erts:erlang#make_ref-0">erlang:make_ref/0</seealso> @@ -680,24 +686,21 @@ can be used to generate a service name that is somewhat unique.</p> <tag><c>service_opt()</c></tag> <item> <p> -An option passed to <seealso -marker="#start_service">start_service/2</seealso>. -Can be any <c><seealso marker="#capability">capability()</seealso></c> as -well as the following.</p> +An option passed to &start_service;. +Can be any <c>&capability;</c> as well as the following.</p> <taglist> -<tag><c>{application, [<seealso marker="#application_opt">application_opt()</seealso>]}</c></tag> +<tag><c>{application, [&application_opt;]}</c></tag> <item> <p> Defines a Diameter application supported by the service.</p> <p> -A service must configure one <c>application</c> for each Diameter +A service must configure one tuple for each Diameter application it intends to support. -For an outgoing Diameter request, the relevant <c><seealso -marker="#application_alias">application_alias()</seealso></c> is -passed to <seealso marker="#call">call/4</seealso>, while for an +For an outgoing Diameter request, the relevant <c>&application_alias;</c> is +passed to &call;, while for an incoming request the application identifier in the message header determines the application, the identifier being specified in the application's <seealso marker="diameter_dict">dictionary</seealso> @@ -708,7 +711,7 @@ file.</p> | node | nodes | [node()] - | diameter:evaluable()}</c></tag> + | evaluable()}</c></tag> <item> <p> Specifies the degree to which multiple transport connections to the @@ -718,10 +721,10 @@ same peer are accepted by the service.</p> If type <c>[node()]</c> then a connection is rejected if another already exists on any of the specified nodes. Values of type <c>false</c>, <c>node</c>, <c>nodes</c> or -<c>diameter:evaluable()</c> are equivalent to values <c>[]</c>, -<c>[node()]</c>, <c>[node()|nodes()]</c> and the evaluated value, -respectively, evaluation of each expression taking place whenever a -new connection is to be established. +&evaluable; are equivalent to +values <c>[]</c>, <c>[node()]</c>, <c>[node()|nodes()]</c> and the +evaluated value, respectively, evaluation of each expression taking +place whenever a new connection is to be established. Note that <c>false</c> allows an unlimited number of connections to be established with the same peer.</p> @@ -733,20 +736,18 @@ by their own peer and watchdog state machines.</p> Defaults to <c>nodes</c>.</p> </item> -<tag><c>{sequence, {H,N} | <seealso - marker="diameter#evaluable">diameter:evaluable()</seealso>}</c></tag> +<tag><c>{sequence, {H,N} | &evaluable;}</c></tag> <item> <p> Specifies a constant value <c>H</c> for the topmost <c>32-N</c> bits of of 32-bit End-to-End and Hop-by-Hop identifiers generated by the service, either explicity or as a return value of a function -to be evaluated at <seealso -marker="diameter#start_service">diameter:start_service/2</seealso>. +to be evaluated at &start_service;. In particular, an identifier <c>Id</c> is mapped to a new identifier as follows.</p> -<code> +<pre> (H bsl N) bor (Id band ((1 bsl N) - 1)) -</code> +</pre> <p> Note that RFC 3588 requires that End-to-End identifiers remain unique for a period of at least 4 minutes and that this and the call rate @@ -770,144 +771,192 @@ Defaults to <c>{0,32}</c>.</p> <tag><c>transport_opt()</c></tag> <item> <p> -An option passed to <seealso -marker="#add_transport">add_transport/2</seealso>. +An option passed to &add_transport;. Has one of the following types.</p> <taglist> -<tag><c>{transport_module, atom()}</c></tag> +<marker id="applications"/> +<tag><c>{applications, [&application_alias;]}</c></tag> <item> <p> -A module implementing a transport process as defined in <seealso -marker="diameter_transport">diameter_transport(3)</seealso>. -Defaults to <c>diameter_tcp</c> if unspecified.</p> +The list of Diameter applications to which the transport should be +restricted. +Defaults to all applications configured on the service in question. +Applications not configured on the service in question are ignored.</p> +</item> +<marker id="capabilities"/> +<tag><c>{capabilities, [&capability;]}</c></tag> +<item> <p> -Multiple <c>transport_module</c> and <c>transport_config</c> -options are allowed. -The order of these is significant in this case (and only in this case), -a <c>transport_module</c> being paired with the first -<c>transport_config</c> following it in the options list, or the -default value for trailing modules. -Transport starts will be attempted with each of the -modules in order until one establishes a connection within the -corresponding timeout (see below) or all fail.</p> +AVP's used to construct outgoing CER/CEA messages. +Values take precedence over any specified on the service in +question.</p> + +<p> +Specifying a capability as a transport option +may be particularly appropriate for Inband-Security-Id, in case +TLS is desired over TCP as implemented by &man_tcp;.</p> </item> -<tag><c>{transport_config, term()}</c></tag> -<tag><c>{transport_config, term(), <seealso marker="diameter_dict#DATA_TYPES">Unsigned32()</seealso>}</c></tag> +<marker id="capabilities_cb"/> +<tag><c>{capabilities_cb, &evaluable;}</c></tag> <item> <p> -A term passed as the third argument to the <seealso -marker="diameter_transport#start">start/3</seealso> function of -the relevant <c>transport_module</c> in order to start a transport process. -Defaults to the empty list if unspecified.</p> +A callback invoked upon reception of CER/CEA during capabilities +exchange in order to ask whether or not the connection should +be accepted. +Applied to the <c>&transport_ref;</c> and +<c>#diameter_caps{}</c> record of the connection.</p> <p> -The 3-tuple form additionally specifies an interval, in milliseconds, -after which a started transport process should be terminated if it has -not yet established a connection. -For example, the following options on a connecting transport -request a connection with one peer over SCTP or another -(typically the same) over TCP.</p> +The return value can have one of the following types.</p> -<code> -{transport_module, diameter_sctp} -{transport_config, SctpOpts, 5000} -{transport_module, diameter_tcp} -{transport_config, TcpOpts} -</code> +<taglist> +<tag><c>ok</c></tag> +<item> +<p> +Accept the connection.</p> +</item> +<tag><c>integer()</c></tag> +<item> <p> -To listen on both SCTP and TCP, define one transport for each.</p> +Causes an incoming CER to be answered with the specified Result-Code.</p> </item> -<tag><c>{applications, [<seealso marker="#application_alias">application_alias()</seealso>]}</c></tag> +<tag><c>discard</c></tag> <item> <p> -The list of Diameter applications to which the transport should be -restricted. -Defaults to all applications configured on the service in question. -Applications not configured on the service in question are ignored.</p> +Causes an incoming CER to be discarded without CEA being sent.</p> </item> -<tag><c>{capabilities, [<seealso marker="#capability">capability()</seealso>]}</c></tag> +<tag><c>unknown</c></tag> <item> <p> -AVP's used to construct outgoing CER/CEA messages. -Values take precedence over any specified on the service in -question.</p> +Equivalent to returning <c>3010</c>, DIAMETER_UNKNOWN_PEER.</p> +</item> +</taglist> <p> -Specifying a capability as a transport option -may be particularly appropriate for Inband-Security-Id, in case -TLS is desired over TCP as implemented by -<seealso marker="diameter_tcp">diameter_tcp(3)</seealso>.</p> +Returning anything but <c>ok</c> or a 2xxx series result +code causes the transport connection to be broken. +Multiple &capabilities_cb; +options can be specified, in which +case the corresponding callbacks are applied until either all return +<c>ok</c> or one does not.</p> </item> -<tag><c>{capabilities_cb, <seealso marker="#evaluable">evaluable()</seealso>}</c></tag> +<marker id="capx_timeout"/> +<tag><c>{capx_timeout, &dict_Unsigned32;}</c></tag> <item> <p> -A callback invoked upon reception of CER/CEA during capabilities -exchange in order to ask whether or not the connection should -be accepted. -Applied to the relevant <c><seealso -marker="#transport_ref">transport_ref()</seealso></c> and the -<c>#diameter_caps{}</c> record of the connection. -Returning <c>ok</c> accepts the connection. -Returning <c>integer()</c> causes an incoming -CER to be answered with the specified Result-Code. -Returning <c>discard</c> causes an incoming CER to -be discarded. -Returning <c>unknown</c> is equivalent to returning <c>3010</c>, -DIAMETER_UNKNOWN_PEER. -Returning anything but <c>ok</c> or a 2xxx series result -code causes the transport connection to be broken.</p> +The number of milliseconds after which a transport process having an +established transport connection will be terminated if the expected +capabilities exchange message (CER or CEA) is not received from the peer. +For a connecting transport, the timing reconnection attempts is +governed by &watchdog_timer; or +&reconnect_timer; expiry. +For a listening transport, the peer determines the timing.</p> <p> -Multiple <c>capabilities_cb</c> options can be specified, in which -case the corresponding callbacks are applied until either all return -<c>ok</c> or one does not.</p> +Defaults to 10000.</p> +</item> -<marker id="watchdog_timer"/> +<marker id="disconnect_cb"/> +<tag><c>{disconnect_cb, &evaluable;}</c></tag> + +<item> +<p> +A callback invoked prior to terminating the transport process of a +transport connection having watchdog state <c>OKAY</c>. +Applied to <c>Reason=transport|service|application</c> and the +<c>&transport_ref;</c> and +<c>&app_peer;</c> +in question, <c>Reason</c> indicating whether the the diameter +application is being stopped, the service in question is being stopped +at &stop_service; or +the transport in question is being removed at &remove_transport;, +respectively.</p> + +<p> +The return value can have one of the following types.</p> + +<taglist> +<tag><c>{dpr, [option()]}</c></tag> +<item> +<p> +Causes Disconnect-Peer-Request to be sent to the peer, the transport +process being terminated following reception of +Disconnect-Peer-Answer or timeout. +An <c>option()</c> can be one of the following.</p> + +<taglist> +<tag><c>{cause, 0|rebooting|1|busy|2|goaway}</c></tag> +<item> +<p> +The Disconnect-Cause to send, <c>REBOOTING</c>, <c>BUSY</c> and +<c>DO_NOT_WANT_TO_TALK_TO_YOU</c> respectively. +Defaults to <c>rebooting</c> for <c>Reason=service|application</c> and +<c>goaway</c> for <c>Reason=transport</c>.</p> </item> -<tag><c>{watchdog_timer, TwInit}</c></tag> +<tag><c>{timeout, &dict_Unsigned32;}</c></tag> <item> -<code> -TwInit = <seealso marker="diameter_dict#DATA_TYPES">Unsigned32()</seealso> - | {M,F,A} -</code> +<p> +The number of milliseconds after which the transport process is +terminated if DPA has not been received. +Defaults to 1000.</p> +</item> +</taglist> +</item> +<tag><c>dpr</c></tag> +<item> <p> -The RFC 3539 watchdog timer. -An integer value is interpreted as the RFC's TwInit in milliseconds, -a jitter of ± 2 seconds being added at each rearming of the -timer to compute the RFC's Tw. -An MFA is expected to return the RFC's Tw directly, with jitter -applied, allowing the jitter calculation to be performed by -the callback.</p> +Equivalent to <c>{dpr, []}</c>.</p> +</item> +<tag><c>close</c></tag> +<item> <p> -An integer value must be at least 6000 as required by RFC 3539. -Defaults to 30000 if unspecified.</p> +Causes the transport process to be terminated without +Disconnect-Peer-Request being sent to the peer.</p> +</item> -<marker id="reconnect_timer"/> +<tag><c>ignore</c></tag> +<item> +<p> +Equivalent to not having configured the callback.</p> </item> +</taglist> +<p> +Multiple &disconnect_cb; +options can be specified, in which +case the corresponding callbacks are applied until one of them returns +a value other than <c>ignore</c>. +All callbacks returning <c>ignore</c> is equivalent to not having +configured them.</p> + +<p> +Defaults to a single callback returning <c>dpr</c>.</p> +</item> + +<marker id="reconnect_timer"/> <tag><c>{reconnect_timer, Tc}</c></tag> <item> -<code> -Tc = <seealso marker="diameter_dict#DATA_TYPES">Unsigned32()</seealso> -</code> +<pre> +Tc = &dict_Unsigned32; +</pre> <p> For a connecting transport, the RFC 3588 Tc timer, in milliseconds. Note that this timer determines the frequency with which a transport will attempt to establish a connection with its peer only <em>before</em> an initial connection is established: once there is an initial -connection it's watchdog_timer that determines the frequency of -reconnection attempts, as required by RFC 3539.</p> +connection it's &watchdog_timer; that determines the +frequency of reconnection attempts, as required by RFC 3539.</p> <p> For a listening transport, the timer specifies the time after which a @@ -915,24 +964,92 @@ previously connected peer will be forgotten: a connection after this time is regarded as an initial connection rather than a reestablishment, causing the RFC 3539 state machine to pass to state OKAY rather than REOPEN. -Note that these semantics are not goverened by the RFC and -that a listening transport's <c>reconnect_timer</c> should be greater +Note that these semantics are not governed by the RFC and +that a listening transport's &reconnect_timer; should be greater than its peer's Tw plus jitter.</p> <p> Defaults to 30000 for a connecting transport and 60000 for a listening transport.</p> +</item> + +<marker id="transport_config"/> +<tag><c>{transport_config, term()}</c></tag> +<tag><c>{transport_config, term(), &dict_Unsigned32;}</c></tag> +<item> +<p> +A term passed as the third argument to the &transport_start; function of +the relevant &transport_module; in order to +start a transport process. +Defaults to the empty list if unspecified.</p> +<p> +The 3-tuple form additionally specifies an interval, in milliseconds, +after which a started transport process should be terminated if it has +not yet established a connection. +For example, the following options on a connecting transport +request a connection with one peer over SCTP or another +(typically the same) over TCP.</p> + +<pre> +{transport_module, diameter_sctp} +{transport_config, SctpOpts, 5000} +{transport_module, diameter_tcp} +{transport_config, TcpOpts} +</pre> + +<p> +To listen on both SCTP and TCP, define one transport for each.</p> +</item> + +<marker id="transport_module"/> +<tag><c>{transport_module, atom()}</c></tag> +<item> +<p> +A module implementing a transport process as defined in &man_transport;. +Defaults to <c>diameter_tcp</c> if unspecified.</p> + +<p> +Multiple <c>transport_module</c> and &transport_config; +options are allowed. +The order of these is significant in this case (and only in this case), +a <c>transport_module</c> being paired with the first +&transport_config; +following it in the options list, or the default value for trailing +modules. +Transport starts will be attempted with each of the +modules in order until one establishes a connection within the +corresponding timeout (see below) or all fail.</p> +</item> + +<marker id="watchdog_timer"/> +<tag><c>{watchdog_timer, TwInit}</c></tag> +<item> +<pre> +TwInit = &dict_Unsigned32; + | {M,F,A} +</pre> + +<p> +The RFC 3539 watchdog timer. +An integer value is interpreted as the RFC's TwInit in milliseconds, +a jitter of ± 2 seconds being added at each rearming of the +timer to compute the RFC's Tw. +An MFA is expected to return the RFC's Tw directly, with jitter +applied, allowing the jitter calculation to be performed by +the callback.</p> + +<p> +An integer value must be at least 6000 as required by RFC 3539. +Defaults to 30000 if unspecified.</p> </item> </taglist> <p> Unrecognized options are silently ignored but are returned unmodified -by <seealso -marker="#service_info">service_info/2</seealso> and can be referred to -in predicate functions passed to <seealso -marker="#remove_transport">remove_transport/2</seealso>.</p> +by &service_info; and can be referred to +in predicate functions passed to &remove_transport;.</p> <marker id="transport_ref"/> </item> @@ -940,8 +1057,7 @@ marker="#remove_transport">remove_transport/2</seealso>.</p> <tag><c>transport_ref() = reference()</c></tag> <item> <p> -An reference returned by <seealso -marker="#add_transport">add_transport/2</seealso> that +An reference returned by &add_transport; that identifies the configuration.</p> </item> @@ -949,7 +1065,6 @@ identifies the configuration.</p> </section> -<marker id="add_transport"/> <funcs> <!-- ===================================================================== --> @@ -959,9 +1074,9 @@ identifies the configuration.</p> -> {ok, Ref} | {error, Reason}</name> <fsummary>Add transport capability to a service.</fsummary> <type> -<v>SvcName = <seealso marker="#service_name">service_name()</seealso></v> -<v>Opt = <seealso marker="#transport_opt">transport_opt()</seealso></v> -<v>Ref = <seealso marker="#transport_ref">transport_ref()</seealso></v> +<v>SvcName = &service_name;</v> +<v>Opt = &transport_opt;</v> +<v>Ref = &transport_ref;</v> <v>Reason = term()</v> </type> <desc> @@ -980,8 +1095,7 @@ one peer, an listening transport potentially with many.</p> The diameter application takes responsibility for exchanging CER/CEA with the peer. Upon successful completion of capabilities exchange the service -calls each relevant application module's <seealso -marker="diameter_app#peer_up">peer_up/3</seealso> callback +calls each relevant application module's &app_peer_up; callback after which the caller can exchange Diameter messages with the peer over the transport. In addition to CER/CEA, the service takes responsibility for the @@ -1000,7 +1114,6 @@ been configured: a service can be started after configuring its transports.</p> </note> -<marker id="call"/> </desc> </func> @@ -1010,11 +1123,11 @@ its transports.</p> <name>call(SvcName, App, Request, [Opt]) -> Answer | ok | {error, Reason}</name> <fsummary>Send a Diameter request message.</fsummary> <type> -<v>SvcName = <seealso marker="#service_name">service_name()</seealso></v> -<v>App = <seealso marker="#application_alias">application_alias()</seealso></v> -<v>Request = <seealso marker="diameter_app#message">diameter_app:message()</seealso></v> +<v>SvcName = &service_name;</v> +<v>App = &application_alias;</v> +<v>Request = &app_message;</v> <v>Answer = term()</v> -<v>Opt = <seealso marker="#call_opt">call_opt()</seealso></v> +<v>Opt = &call_opt;</v> </type> <desc> <p> @@ -1023,37 +1136,29 @@ Send a Diameter request message.</p> <p> <c>App</c> specifies the Diameter application in which the request is defined and callbacks to the corresponding callback module -will follow as described below and in <seealso -marker="diameter_app">diameter_app(3)</seealso>. +will follow as described below and in &man_app;. Unless the <c>detach</c> option is specified, the call returns either when an answer message is received from the peer or an error occurs. In the answer case, the return value is as returned by a -<seealso -marker="diameter_app#handle_answer">handle_answer/4</seealso> -callback. +&app_handle_answer; callback. In the error case, whether or not the error is returned directly -by diameter or from a <seealso -marker="diameter_app#handle_error">handle_error/4</seealso> +by diameter or from a &app_handle_error; callback depends on whether or not the outgoing request is successfully encoded for transmission to the peer, the cases being documented below.</p> <p> If there are no suitable peers, or if -<seealso marker="diameter_app#pick_peer">pick_peer/4</seealso> +&app_pick_peer; rejects them by returning <c>false</c>, then <c>{error,no_connection}</c> is returned. -Otherwise <seealso marker="diameter_app#pick_peer">pick_peer/4</seealso> -is followed by a -<seealso -marker="diameter_app#prepare_request">prepare_request/3</seealso> -callback, the message is encoded and then sent.</p> +Otherwise &app_pick_peer; is followed by a +&app_prepare_request; callback, the message is encoded and then sent.</p> <p> There are several error cases which may prevent an answer from being received and passed to a -<seealso marker="diameter_app#handle_answer">handle_answer/4</seealso> -callback:</p> +&app_handle_answer; callback:</p> <list> @@ -1068,16 +1173,14 @@ is returned.</p> <p> If the request is successfully encoded and sent but the answer times out then a -<seealso marker="diameter_app#handle_error">handle_error/4</seealso> -callback takes place with <c>Reason = timeout</c>.</p> +&app_handle_error; callback takes place with <c>Reason = timeout</c>.</p> </item> <item> <p> If the request is successfully encoded and sent but the service in question is stopped before an answer is received then a -<seealso marker="diameter_app#handle_error">handle_error/4</seealso> -callback takes place with <c>Reason = cancel</c>.</p> +&app_handle_error; callback takes place with <c>Reason = cancel</c>.</p> </item> <item> @@ -1086,18 +1189,11 @@ If the transport connection with the peer goes down after the request has been sent but before an answer has been received then an attempt is made to resend the request to an alternate peer. If no such peer is available, or if the subsequent -<seealso marker="diameter_app#pick_peer">pick_peer/4</seealso> -callback rejects the candidates, then a -<seealso marker="diameter_app#handle_error">handle_error/4</seealso> -callback takes place with <c>Reason = failover</c>. -If a peer is selected then a -<seealso -marker="diameter_app#prepare_retransmit">prepare_retransmit/3</seealso> +&app_pick_peer; callback rejects the candidates, then a +&app_handle_error; callback takes place with <c>Reason = failover</c>. +If a peer is selected then a &app_prepare_retransmit; callback takes place, after which the semantics are the same as -following an initial -<seealso marker="diameter_app#prepare_request"> -prepare_request/3</seealso> -callback.</p> +following an initial &app_prepare_request; callback.</p> </item> <item> @@ -1124,14 +1220,13 @@ Note that <c>{error,encode}</c> is the only return value which guarantees that the request has <em>not</em> been sent over the transport connection.</p> -<marker id="origin_state_id"/> </desc> </func> <!-- ===================================================================== --> <func> -<name>origin_state_id() -> <seealso marker="diameter_dict#DATA_TYPES">Unsigned32()</seealso></name> +<name>origin_state_id() -> &dict_Unsigned32;</name> <fsummary>Returns a reasonable Origin-State-Id.</fsummary> <desc> <p> @@ -1140,26 +1235,26 @@ outgoing messages.</p> <p> The value returned is the number of seconds since 19680120T031408Z, -the first value that can be encoded as a Diameter <c><seealso marker="diameter_dict#DATA_TYPES">Time()</seealso></c>, +the first value that can be encoded as a Diameter <c>&dict_Time;</c>, at the time the diameter application was started.</p> -<marker id="remove_transport"/> </desc> </func> <!-- ===================================================================== --> <func> -<name>remove_transport(SvcName, Pred) -> ok</name> +<name>remove_transport(SvcName, Pred) -> ok | {error, Reason}</name> <fsummary>Remove previously added transports.</fsummary> <type> -<v>SvcName = <seealso marker="#service_name">service_name()</seealso></v> -<v>Pred = Fun | MFA | <seealso marker="#transport_ref">transport_ref()</seealso> | list() | true | false</v> +<v>SvcName = &service_name;</v> +<v>Pred = Fun | MFA | &transport_ref; | list() | true | false</v> <v></v> -<v>Fun = fun((<seealso marker="#transport_ref">transport_ref()</seealso>, connect|listen, list()) -> boolean())</v> -<v> | fun((<seealso marker="#transport_ref">transport_ref()</seealso>, list()) -> boolean())</v> +<v>Fun = fun((&transport_ref;, connect|listen, list()) -> boolean())</v> +<v> | fun((&transport_ref;, list()) -> boolean())</v> <v> | fun((list()) -> boolean())</v> <v>MFA = {atom(), atom(), list()}</v> +<v>Reason = term()</v> </type> <desc> <p> @@ -1169,12 +1264,11 @@ Remove previously added transports.</p> <c>Pred</c> determines which transports to remove. An arity-3-valued <c>Pred</c> removes all transports for which <c>Pred(Ref, Type, Opts)</c> returns <c>true</c>, where <c>Type</c> and -<c>Opts</c> are as passed to <seealso -marker="#add_transport">add_transport/2</seealso> and <c>Ref</c> is +<c>Opts</c> are as passed to &add_transport; and <c>Ref</c> is as returned by it. The remaining forms are equivalent to an arity-3 fun as follows.</p> -<code> +<pre> Pred = fun(transport_ref(), list()): fun(Ref, _, Opts) -> Pred(Ref, Opts) end Pred = fun(list()): fun(_, _, Opts) -> Pred(Opts) end Pred = transport_ref(): fun(Ref, _, _) -> Pred == Ref end @@ -1182,20 +1276,15 @@ Pred = list(): fun(_, _, Opts) -> [] == Pred -- Opts end Pred = true: fun(_, _, _) -> true end Pred = false: fun(_, _, _) -> false end Pred = {M,F,A}: fun(Ref, Type, Opts) -> apply(M, F, [Ref, Type, Opts | A]) end -</code> +</pre> <p> -Removing a transport causes all associated transport connections to -be broken. -A DPR message with -Disconnect-Cause <c>DO_NOT_WANT_TO_TALK_TO_YOU</c> will be sent -to each connected peer before disassociating the transport configuration -from the service and terminating the transport upon reception of -DPA or timeout.</p> - -<!-- TODO: document the timeout value, possibly make configurable. --> +Removing a transport causes the corresponding transport processes to +be terminated. +Whether or not a DPR message is sent to a peer is +controlled by value of &disconnect_cb; +configured on the transport.</p> -<marker id="service_info"/> </desc> </func> @@ -1205,7 +1294,7 @@ DPA or timeout.</p> <name>service_info(SvcName, Info) -> term()</name> <fsummary>Return information about a started service.</fsummary> <type> -<v>SvcName = <seealso marker="#service_name">service_name()</seealso></v> +<v>SvcName = &service_name;</v> <v>Info = Item | [Info]</v> <v>Item = atom()</v> </type> @@ -1236,15 +1325,13 @@ returned.</p> <tag><c>'Firmware-Revision'</c></tag> <item> <p> -Return a capability value as configured with <seealso -marker="#start_service">start_service/2</seealso>.</p> +Return a capability value as configured with &start_service;.</p> </item> <tag><c>applications</c></tag> <item> <p> -Return the list of applications as configured with <seealso -marker="#start_service">start_service/2</seealso>. +Return the list of applications as configured with &start_service;. </p> </item> @@ -1252,23 +1339,21 @@ marker="#start_service">start_service/2</seealso>. <item> <p> Return a tagged list of all capabilities values as configured with -<seealso -marker="#start_service">start_service/2</seealso>.</p> +&start_service;.</p> </item> <tag><c>transport</c></tag> <item> <p> Return a list containing one entry for each of the service's transport -as configured with <seealso -marker="#add_transport">add_transport/2</seealso>. +as configured with &add_transport;. Each entry is a tagged list containing both configuration and information about established peer connections. An example return value with for a client service with Origin-Host "client.example.com" configured with a single transport connected to "server.example.com" might look as follows.</p> -<code> +<pre> [[{ref,#Ref<0.0.0.93>}, {type,connect}, {options,[{transport_module,diameter_tcp}, @@ -1313,23 +1398,18 @@ An example return value with for a client service with Origin-Host {{{0,258,0},recv,{'Result-Code',2001}},3}, {{{0,280,1},recv},2}, {{{0,280,0},send},2}]}]] -</code> +</pre> <p> -Here <c>ref</c> is a <c><seealso -marker="#transport_ref">transport_ref()</seealso></c> and <c>options</c> -the corresponding <c><seealso -marker="#transport_opt">transport_opt()</seealso></c> list passed to <seealso -marker="#add_transport">add_transport/2</seealso>. +Here <c>ref</c> is a <c>&transport_ref;</c> and <c>options</c> +the corresponding <c>&transport_opt;</c> list passed to +&add_transport;. The <c>watchdog</c> entry shows the state of a connection's RFC 3539 watchdog state machine. -The <c>peer</c> entry identifies the <c><seealso -marker="diameter_app#peer_ref">diameter_app:peer_ref()</seealso></c> for -which there will have been <seealso -marker="diameter_app#peer_up">peer_up</seealso> callbacks for the +The <c>peer</c> entry identifies the <c>&app_peer_ref;</c> for +which there will have been &app_peer_up; callbacks for the Diameter applications identified by the <c>apps</c> entry, -<c>common</c> being the <c><seealso -marker="#application_alias">application_alias()</seealso></c>. +<c>common</c> being the <c>&application_alias;</c>. The <c>caps</c> entry identifies the capabilities sent by the local node and received from the peer during capabilities exchange. The <c>port</c> entry displays socket-level information about the @@ -1348,12 +1428,12 @@ during the lifetime of the transport configuration.</p> <p> A listening transport presents its information slightly differently -since there may be multiple accepted connections for the same <c><seealso -marker="#transport_ref">transport_ref()</seealso></c>. +since there may be multiple accepted connections for the same +<c>&transport_ref;</c>. The <c>transport</c> info returned by a server with a single client connection might look as follows.</p> -<code> +<pre> [[{ref,#Ref<0.0.0.61>}, {type,listen}, {options,[{transport_module,diameter_tcp}, @@ -1400,7 +1480,7 @@ connection might look as follows.</p> {{{0,280,0},send},5}, {{{0,257,1},recv},1}, {{{0,257,0},send},1}]}]] -</code> +</pre> <p> The information presented here is as in the <c>connect</c> case except @@ -1419,7 +1499,7 @@ connections and for which Diameter-level statistics are accumulated only for the lifetime of the transport connection. A return value for the server above might look as follows.</p> -<code> +<pre> [[{ref,#Ref<0.0.0.61>}, {type,accept}, {options,[{transport_module,diameter_tcp}, @@ -1465,7 +1545,7 @@ A return value for the server above might look as follows.</p> {{{0,280,0},send},66}, {{{0,257,1},recv},1}, {{{0,257,0},send},1}]}]] -</code> +</pre> <p> Note that there may be multiple entries with the same <c>ref</c>, in @@ -1476,41 +1556,37 @@ contrast to <c>transport</c> info.</p> <item> <p> Return a <c>{{Counter, Ref}, non_neg_integer()}</c> list of counter values. -<c>Ref</c> can be either a <c><seealso -marker="#transport_ref">transport_ref()</seealso></c> -or a <c><seealso -marker="diameter_app#peer_ref">diameter_app:peer_ref()</seealso></c>. +<c>Ref</c> can be either a <c>&transport_ref;</c> +or a <c>&app_peer_ref;</c>. Entries for the latter are folded into corresponding entries for the former as peer connections go down. -Entries for both are removed at <seealso -marker="#remove_transport">remove_transport/2</seealso>. +Entries for both are removed at &remove_transport;. The Diameter-level statistics returned by <c>transport</c> and <c>connections</c> info are based upon these entries.</p> </item> -<tag><c><seealso marker="diameter_app#peer_ref">diameter_app:peer_ref()</seealso></c></tag> +<tag><c>&app_peer_ref;</c></tag> <item> <p> Return transport configuration associated with a single peer, as -passed to <seealso marker="#add_transport">add_transport/2</seealso>. +passed to &add_transport;. The returned list is empty if the peer is unknown. Otherwise it contains the <c>ref</c>, <c>type</c> and <c>options</c> tuples as in <c>transport</c> and <c>connections</c> info above. For example:</p> -<code> +<pre> [{ref,#Ref<0.0.0.61>}, {type,accept}, {options,[{transport_module,diameter_tcp}, {transport_config,[{reuseaddr,true}, {ip,{127,0,0,1}}, {port,3868}]}]}] -</code> +</pre> </item> </taglist> -<marker id="services"/> </desc> </func> @@ -1520,23 +1596,22 @@ For example:</p> <name>services() -> [SvcName]</name> <fsummary>Return the list of started services.</fsummary> <type> -<v>SvcName = <seealso marker="#service_name">service_name()</seealso></v> +<v>SvcName = &service_name;</v> </type> <desc> <p> Return the list of started services.</p> -<marker id="session_id"/> </desc> </func> <!-- ===================================================================== --> <func> -<name>session_id(Ident) -> <seealso marker="diameter_dict#DATA_TYPES">OctetString()</seealso></name> +<name>session_id(Ident) -> &dict_OctetString;</name> <fsummary>Return a value for a Session-Id AVP.</fsummary> <type> -<v>Ident = <seealso marker="diameter_dict#DATA_TYPES">DiameterIdentity()</seealso></v> +<v>Ident = &dict_DiameterIdentity;</v> </type> <desc> <p> @@ -1547,7 +1622,6 @@ The value has the form required by section 8.8 of RFC 3588. Ident should be the Origin-Host of the peer from which the message containing the returned value will be sent.</p> -<marker id="start"/> </desc> </func> @@ -1564,7 +1638,6 @@ The diameter application must be started before starting a service. In a production system this is typically accomplished by a boot file, not by calling <c>start/0</c> explicitly.</p> -<marker id="start_service"/> </desc> </func> @@ -1573,8 +1646,8 @@ file, not by calling <c>start/0</c> explicitly.</p> <name>start_service(SvcName, Options) -> ok | {error, Reason}</name> <fsummary>Start a Diameter service.</fsummary> <type> -<v>SvcName = <seealso marker="#service_name">service_name()</seealso></v> -<v>Options = [<seealso marker="#service_opt">service_opt()</seealso>]</v> +<v>SvcName = &service_name;</v> +<v>Options = [&service_opt;]</v> <v>Reason = term()</v> </type> <desc> @@ -1584,8 +1657,7 @@ Start a diameter service.</p> <p> A service defines a locally-implemented Diameter node, specifying the capabilities to be advertised during capabilities exchange. -Transports are added to a service using <seealso -marker="#add_transport">add_transport/2</seealso>. +Transports are added to a service using &add_transport;. </p> <note> @@ -1596,7 +1668,6 @@ capabilities and restrict its supported Diameter applications so necessarily the case.</p> </note> -<marker id="stop_service"/> </desc> </func> @@ -1611,7 +1682,6 @@ Stop the diameter application.</p> <p> </p> -<marker id="stop_service"/> </desc> </func> @@ -1620,7 +1690,7 @@ Stop the diameter application.</p> <name>stop_service(SvcName) -> ok | {error, Reason}</name> <fsummary>Stop a Diameter service.</fsummary> <type> -<v>SvcName = <seealso marker="#service_name">service_name()</seealso></v> +<v>SvcName = &service_name;</v> <v>Reason = term()</v> </type> <desc> @@ -1630,17 +1700,15 @@ Stop a diameter service.</p> <p> Stopping a service causes all associated transport connections to be broken. -A DPR message with be sent as in the case of <seealso -marker="#remove_transport">remove_transport/2</seealso>.</p> +A DPR message with be sent as in the case of &remove_transport;.</p> <note> <p> -Stopping a transport does not remove any associated transports: -<seealso marker="#remove_transport">remove_transport/2</seealso> must +Stopping a service does not remove any associated transports: +&remove_transport; must be called to remove transport configuration.</p> </note> -<marker id="subscribe"/> </desc> </func> @@ -1650,12 +1718,11 @@ be called to remove transport configuration.</p> <name>subscribe(SvcName) -> true</name> <fsummary>Subscribe to event messages.</fsummary> <type> -<v>SvcName = <seealso marker="#service_name">service_name()</seealso></v> +<v>SvcName = &service_name;</v> </type> <desc> <p> -Subscribe to <c><seealso -marker="#service_event">service_event()</seealso></c> messages from +Subscribe to <c>&service_event;</c> messages from a service.</p> <p> @@ -1664,7 +1731,6 @@ that does not yet exist. Doing so before adding transports is required to guarantee the reception of all related events.</p> -<marker id="unsubscribe"/> </desc> </func> @@ -1674,7 +1740,7 @@ reception of all related events.</p> <name>unsubscribe(SvcName) -> true</name> <fsummary>Unsubscribe to event messages.</fsummary> <type> -<v>SvcName = <seealso marker="#service_name">service_name()</seealso></v> +<v>SvcName = &service_name;</v> </type> <desc> <p> @@ -1691,9 +1757,7 @@ Unsubscribe to event messages from a service.</p> <title>SEE ALSO</title> <p> -<seealso marker="diameter_app">diameter_app(3)</seealso>, -<seealso marker="diameter_transport">diameter_transport(3)</seealso>, -<seealso marker="diameter_dict">diameter_dict(4)</seealso></p> +&man_app;, &man_transport;, &man_dict;</p> </section> diff --git a/lib/diameter/doc/src/diameter_app.xml b/lib/diameter/doc/src/diameter_app.xml index 9d8a6568eb..304c69ebda 100644 --- a/lib/diameter/doc/src/diameter_app.xml +++ b/lib/diameter/doc/src/diameter_app.xml @@ -1,5 +1,10 @@ <?xml version="1.0" encoding="latin1" ?> -<!DOCTYPE erlref SYSTEM "erlref.dtd"> +<!DOCTYPE erlref SYSTEM "erlref.dtd" [ + <!ENTITY % also SYSTEM "seealso.ent" > + <!ENTITY % here SYSTEM "seehere.ent" > + %also; + %here; +]> <erlref> <header> @@ -41,15 +46,13 @@ Callback module of a Diameter application.</modulesummary> <description> <p> -A diameter service as started by <seealso -marker="diameter#start_service">diameter:start_service/2</seealso> +A diameter service as started by &mod_start_service; configures one of more Diameter applications, each of whose configuration specifies a callback that handles messages specific to the application. The messages and AVPs of the application are defined in a dictionary file whose format is documented in -<seealso marker="diameter_dict">diameter_dict(4)</seealso> -while the callback module is documented here. +&man_dict; while the callback module is documented here. The callback module implements the Diameter application-specific functionality of a service.</p> @@ -60,26 +63,24 @@ The functions themselves are of three distinct flavours:</p> <list> <item> <p> -<seealso marker="#peer_up">peer_up/3</seealso> and -<seealso marker="#peer_down">peer_down/3</seealso> signal the +&peer_up; and &peer_down; signal the attainment or loss of connectivity with a Diameter peer.</p> </item> <item> <p> -<seealso marker="#pick_peer">pick_peer/4</seealso>, -<seealso marker="#prepare_request">prepare_request/3</seealso>, -<seealso marker="#prepare_retransmit">prepare_retransmit/3</seealso>, -<seealso marker="#handle_answer">handle_answer/4</seealso> -and <seealso marker="#handle_error">handle_error/4</seealso> are (or may -be) called as a consequence of a call to <seealso -marker="diameter#call">diameter:call/4</seealso> to send an outgoing +&pick_peer;, +&prepare_request;, +&prepare_retransmit;, +&handle_answer; +and &handle_error; are (or may be) called as a consequence of a call +to &mod_call; to send an outgoing Diameter request message.</p> </item> <item> <p> -<seealso marker="#handle_request">handle_request/3</seealso> +&handle_request; is called in response to an incoming Diameter request message.</p> </item> @@ -92,10 +93,9 @@ is called in response to an incoming Diameter request message.</p> The arities given for the the callback functions here assume no extra arguments. All functions will also be passed any extra arguments configured with -the callback module itself when calling <seealso -marker="diameter#start_service">diameter:start_service/2</seealso> +the callback module itself when calling &mod_start_service; and, for the call-specific callbacks, any extra arguments passed to -<seealso marker="diameter#call">diameter:call/4</seealso>.</p> +&mod_call;.</p> </note> <!-- ===================================================================== --> @@ -128,7 +128,7 @@ mandatory values as the bare value.</p> <item> <p> The representation of a Diameter message as passed to -<seealso marker="diameter#call">diameter:call/4</seealso>. +&mod_call;. The record representation is as outlined in <seealso marker="diameter_dict#MESSAGE_RECORDS">diameter_dict(4)</seealso>: @@ -145,7 +145,7 @@ whose head is a <c>#diameter_header{}</c> record and whose tail is a list of <c>#diameter_avp{}</c> records. This representation is used by diameter itself when relaying requests as directed by the return value of a -<seealso marker="#handle_request">handle_request/3</seealso> +&handle_request; callback. It differs from the other other two in that it bypasses the checks for messages that do not agree with their definitions in the dictionary in @@ -174,9 +174,7 @@ A term identifying a transport connection with a Diameter peer.</p> <marker id="peer"/> -<tag><c>peer() = - {<seealso marker="#peer_ref">peer_ref()</seealso>, - <seealso marker="#capabilities">capabilities()</seealso>}</c></tag> +<tag><c>peer() = {&peer_ref;, &capabilities;}</c></tag> <item> <p> A tuple representing a Diameter peer connection.</p> @@ -188,13 +186,9 @@ A tuple representing a Diameter peer connection.</p> <item> <p> The state maintained by the application callback functions -<seealso marker="#peer_up">peer_up/3</seealso>, -<seealso marker="#peer_down">peer_down/3</seealso> and (optionally) -<seealso marker="#pick_peer">pick_peer/4</seealso>. +&peer_up;, &peer_down; and (optionally) &pick_peer;. The initial state is configured in the call to -<seealso -marker="diameter#start_service">diameter:start_service/2</seealso> -that configures the application on a service. +&mod_start_service; that configures the application on a service. Callback functions returning a state are evaluated in a common service-specific process while those not returning state are evaluated in a request-specific @@ -215,9 +209,9 @@ process.</p> <name>Mod:peer_up(SvcName, Peer, State) -> NewState</name> <fsummary>Invoked when a transport connection has been established</fsummary> <type> -<v>SvcName = <seealso marker="diameter#service_name">diameter:service_name()</seealso></v> -<v>Peer = <seealso marker="#peer">peer()</seealso></v> -<v>State = NewState = <seealso marker="#state">state()</seealso></v> +<v>SvcName = &mod_service_name;</v> +<v>Peer = &peer;</v> +<v>State = NewState = &state;</v> </type> <desc> <p> @@ -238,14 +232,10 @@ new peer_ref().</p> <note> <p> There is no requirement that a callback return before incoming -requests are received: <seealso -marker="#handle_request">handle_request/3</seealso> callbacks must be -handled independently of <seealso -marker="#peer_up">peer_up/3</seealso> and <seealso -marker="#peer_down">peer_down/3</seealso>.</p> +requests are received: &handle_request; callbacks must be +handled independently of &peer_up; and &peer_down;.</p> </note> -<marker id="peer_down"/> </desc> </func> @@ -253,21 +243,18 @@ marker="#peer_down">peer_down/3</seealso>.</p> <name>Mod:peer_down(SvcName, Peer, State) -> NewState</name> <fsummary>Invoked when a transport connection has been lost.</fsummary> <type> -<v>SvcName = <seealso marker="diameter#service_name">diameter:service_name()</seealso></v> -<v>Peer = <seealso marker="#peer">peer()</seealso></v> -<v>State = NewState = <seealso marker="#state">state()</seealso></v> +<v>SvcName = &mod_service_name;</v> +<v>Peer = &peer;</v> +<v>State = NewState = &state;</v> </type> <desc> <p> Invoked to signal that a peer connection is no longer available -following a previous call to <seealso -marker="#peer_up">peer_up/3</seealso>. +following a previous call to &peer_up;. In particular, that the RFC 3539 watchdog state machine for the connection has left state <c>OKAY</c> and the peer will no longer be a -candidate in <seealso marker="#pick_peer">pick_peer()</seealso> -callbacks.</p> +candidate in &pick_peer; callbacks.</p> -<marker id="pick_peer"/> </desc> </func> @@ -276,16 +263,15 @@ callbacks.</p> -> Selection | false</name> <fsummary>Select a target peer for an outgoing request.</fsummary> <type> -<v>Candidates = [<seealso marker="#peer">peer()</seealso>]</v> -<v>SvcName = <seealso marker="diameter#service_name">diameter:service_name()</seealso></v> -<v>State = NewState = <seealso marker="#state">state()</seealso></v> +<v>Candidates = [&peer;]</v> +<v>SvcName = &mod_service_name;</v> +<v>State = NewState = &state;</v> <v>Selection = {ok, Peer} | {Peer, NewState}</v> -<v>Peer = <seealso marker="#peer">peer()</seealso> | false</v> +<v>Peer = &peer; | false</v> </type> <desc> <p> -Invoked as a consequence of a call to <seealso -marker="diameter#call">diameter:call/4</seealso> to select a destination +Invoked as a consequence of a call to &mod_call; to select a destination peer for an outgoing request. The return value indicates the selected peer.</p> @@ -293,39 +279,34 @@ The return value indicates the selected peer.</p> The candidate list contains only those peers that have advertised support for the Diameter application in question during capabilities exchange, that have not be excluded by a <c>filter</c> option in -the call to <seealso marker="diameter#call">diameter:call/4</seealso> +the call to &mod_call; and whose watchdog state machine is in the <c>OKAY</c> state. The order of the elements is unspecified except that any peers whose Origin-Host and Origin-Realm matches that of the outgoing request (in the sense of a <c>{filter, {all, [host, realm]}}</c> -option to <seealso marker="diameter#call">diameter:call/4</seealso>) +option to &mod_call;) will be placed at the head of the list.</p> <p> A callback that returns a peer() will be followed by a -<seealso marker="#prepare_request">prepare_request/3</seealso> +&prepare_request; callback and, if the latter indicates that the request should be sent, -by either <seealso marker="#handle_answer">handle_answer/4</seealso> -or <seealso marker="#handle_error">handle_error/4</seealso> depending +by either &handle_answer; +or &handle_error; depending on whether or not an answer message is received from the peer. -If the transport becomes unavailable after <seealso -marker="prepare_request">prepare_request/3</seealso> then a new <seealso -marker="#pick_peer">pick_peer/4</seealso> callback may take place to -failover to an alternate peer, after which <seealso -marker="#prepare_retransmit">prepare_retransmit/3</seealso> takes the -place of <seealso -marker="prepare_request">prepare_request/3</seealso> in resending the +If the transport becomes unavailable after &prepare_request; then a +new &pick_peer; callback may take place to +failover to an alternate peer, after which &prepare_retransmit; takes the +place of &prepare_request; in resending the request. -There is no guarantee that a <seealso -marker="#pick_peer">pick_peer/4</seealso> callback to select +There is no guarantee that a &pick_peer; callback to select an alternate peer will be followed by any additional callbacks since a retransmission to an alternate peer is abandoned if an answer is received from a previously selected peer.</p> <p> Returning <c>false</c> or <c>{false, NewState}</c> causes <c>{error, -no_connection}</c> to be returned from <seealso -marker="diameter#call">diameter:call/4</seealso>.</p> +no_connection}</c> to be returned from &mod_call;.</p> <p> The return values <c>false</c> and <c>{false, State}</c> (that is, @@ -335,16 +316,14 @@ The return values <c>false</c> and <c>{false, State}</c> (that is, <note> <p> The return value <c>{Peer, NewState}</c> is only allowed if -the Diameter application in question was configured with the <seealso -marker="diameter#application_opt">diameter:application_opt()</seealso> -<c>{call_mutates_state, true}</c>. +the Diameter application in question was configured with the +&mod_application_opt; <c>{call_mutates_state, true}</c>. Otherwise, the <c>State</c> argument is always the intial value as configured on the application, not any subsequent -value returned by a <seealso marker="#peer_up">peer_up/3</seealso> -or <seealso marker="#peer_down">peer_down/3</seealso> callback.</p> +value returned by a &peer_up; +or &peer_down; callback.</p> </note> -<marker id="prepare_request"/> </desc> </func> @@ -353,15 +332,13 @@ or <seealso marker="#peer_down">peer_down/3</seealso> callback.</p> <name>Mod:prepare_request(Packet, SvcName, Peer) -> Action</name> <fsummary>Return a request for encoding and transport.</fsummary> <type> -<v>Packet = <seealso marker="#packet">packet()</seealso></v> -<v>SvcName = <seealso marker="diameter#service_name">diameter:service_name()</seealso></v> -<v>Peer = <seealso marker="#peer">peer()</seealso></v> +<v>Packet = &packet;</v> +<v>SvcName = &mod_service_name;</v> +<v>Peer = &peer;</v> <v>Action = Send | Discard | {eval_packet, Action, PostF}</v> -<v>Send = {send, <seealso marker="#packet">packet()</seealso> - | <seealso marker="#message">message()</seealso>}</v> +<v>Send = {send, &packet; | &message;}</v> <v>Discard = {discard, Reason} | discard</v> -<v>PostF = - <seealso marker="diameter#evaluable">diameter:evaluable()</seealso>}</v> +<v>PostF = &mod_evaluable;}</v> </type> <desc> <p> @@ -371,18 +348,17 @@ to modify the outgoing request. Many implementations may simply want to return <c>{send, Packet}</c></p> <p> -A returned <seealso marker="#packet">packet()</seealso> should set the +A returned &packet; should set the request to be encoded in its <c>msg</c> field and can set the <c>transport_data</c> field in order to pass information to the transport process. -Extra arguments passed to <seealso -marker="diameter#call">diameter:call/4</seealso> can be used to +Extra arguments passed to &mod_call; can be used to communicate transport (or any other) data to the callback.</p> <p> -A returned <seealso marker="#packet">packet()</seealso> can set +A returned &packet; can set the <c>header</c> field to a -<c>#diameter_header{}</c> in order to specify values that should +<c>#diameter_header{}</c> to specify values that should be preserved in the outgoing request, values otherwise being those in the header record contained in <c>Packet</c>. A returned <c>length</c>, <c>cmd_code</c> or <c>application_id</c> is @@ -396,13 +372,11 @@ The return value is ignored.</p> <p> Returning <c>{discard, Reason}</c> causes the request to be aborted -and the <seealso -marker="diameter#call">diameter:call/4</seealso> for which the +and the &mod_call; for which the callback has taken place to return <c>{error, Reason}</c>. Returning <c>discard</c> is equivalent to returning <c>{discard, discarded}</c>.</p> -<marker id="prepare_retransmit"/> </desc> </func> @@ -410,35 +384,29 @@ discarded}</c>.</p> <name>Mod:prepare_retransmit(Packet, SvcName, Peer) -> Action</name> <fsummary>Return a request for encoding and retransmission.</fsummary> <type> -<v>Packet = <seealso marker="#packet">packet()</seealso></v> -<v>SvcName = <seealso marker="diameter#service_name">diameter:service_name()</seealso></v> -<v>Peer = <seealso marker="#peer">peer()</seealso></v> +<v>Packet = &packet;</v> +<v>SvcName = &mod_service_name;</v> +<v>Peer = &peer;</v> <v>Action = Send | Discard | {eval_packet, Action, PostF}</v> -<v>Send = {send, <seealso marker="#packet">packet()</seealso> - | <seealso marker="#message">message()</seealso>}</v> +<v>Send = {send, &packet; | &message;}</v> <v>Discard = {discard, Reason} | discard</v> -<v>PostF = - <seealso marker="diameter#evaluable">diameter:evaluable()</seealso>}</v> +<v>PostF = &mod_evaluable;}</v> </type> <desc> <p> Invoked to return a request for encoding and retransmission. -Has the same role as <seealso -marker="#prepare_request">prepare_request/3</seealso> in the case that +Has the same role as &prepare_request; in the case that a peer connection is lost an an alternate peer selected but the -argument <seealso marker="#packet">packet()</seealso> is as returned -by the initial <seealso -marker="#prepare_request">prepare_request/3</seealso>.</p> +argument &packet; is as returned +by the initial &prepare_request;.</p> <p> Returning <c>{discard, Reason}</c> causes the request to be aborted -and a <seealso -marker="#handle_error">handle_error/4</seealso> callback to +and a &handle_error; callback to take place with <c>Reason</c> as initial argument. Returning <c>discard</c> is equivalent to returning <c>{discard, discarded}</c>.</p> -<marker id="handle_answer"/> </desc> </func> @@ -446,52 +414,43 @@ discarded}</c>.</p> <name>Mod:handle_answer(Packet, Request, SvcName, Peer) -> Result</name> <fsummary>Receive an answer message from a peer.</fsummary> <type> -<v>Packet = <seealso marker="#packet">packet()</seealso></v> -<v>Request = <seealso marker="#message">message()</seealso></v> -<v>SvcName = <seealso marker="diameter#service_name">diameter:service_name()</seealso></v> -<v>Peer = <seealso marker="#peer">peer()</seealso></v> +<v>Packet = &packet;</v> +<v>Request = &message;</v> +<v>SvcName = &mod_service_name;</v> +<v>Peer = &peer;</v> <v>Result = term()</v> </type> <desc> <p> Invoked when an answer message is received from a peer. -The return value is returned from <seealso -marker="diameter#call">diameter:call/4</seealso> unless the +The return value is returned from &mod_call; unless the <c>detach</c> option was specified.</p> <p> The decoded answer record and undecoded binary are in the <c>msg</c> and <c>bin</c> fields of the argument -<seealso marker="#packet">packet()</seealso> respectively. +&packet; respectively. <c>Request</c> is the outgoing request message as was returned from -<seealso marker="#prepare_request">prepare_request/3</seealso> or -<seealso - marker="#prepare_retransmit">prepare_retransmit/3</seealso>.</p> +&prepare_request; or &prepare_retransmit;.</p> <p> -For any given call to <seealso -marker="diameter#call">diameter:call/4</seealso> there is at most one -<seealso marker="#handle_answer">handle_answer/4</seealso> callback: any +For any given call to &mod_call; there is at most one +&handle_answer; callback: any duplicate answer (due to retransmission or otherwise) is discarded. -Similarly, only one of <seealso -marker="#handle_answer">handle_answer/4</seealso> or -<seealso marker="#handle_error">handle_error/4</seealso> is -called.</p> +Similarly, only one of &handle_answer; or +&handle_error; is called.</p> <p> By default, an incoming answer message that cannot be successfully decoded causes the request process to fail, causing -<seealso marker="diameter#call">diameter:call/4</seealso> +&mod_call; to return <c>{error, failure}</c> unless the <c>detach</c> option was specified. -In particular, there is no <seealso -marker="#handle_error">handle_error/4</seealso> callback in this +In particular, there is no &handle_error; callback in this case. -The <seealso -marker="diameter#application_opt">diameter:application_opt()</seealso> +The &mod_application_opt; <c>answer_errors</c> can be set to change this behaviour.</p> -<marker id="handle_error"/> </desc> </func> @@ -500,29 +459,26 @@ marker="diameter#application_opt">diameter:application_opt()</seealso> <fsummary>Return an error from a outgoing request.</fsummary> <type> <v>Reason = timeout | failover | term()</v> -<v>Request = <seealso marker="#message">message()</seealso></v> -<v>SvcName = <seealso marker="diameter#service_name">diameter:service_name()</seealso></v> -<v>Peer = <seealso marker="#peer">peer()</seealso></v> +<v>Request = &message;</v> +<v>SvcName = &mod_service_name;</v> +<v>Peer = &peer;</v> <v>Result = term()</v> </type> <desc> <p> Invoked when an error occurs before an answer message is received in response to an outgoing request. -The return value is returned from <seealso -marker="diameter#call">diameter:call/4</seealso> unless the +The return value is returned from &mod_call; unless the <c>detach</c> option was specified.</p> <p> Reason <c>timeout</c> indicates that an answer message has not been -received within the time specified with the corresponding <seealso -marker="diameter#call_opt">diameter:call_opt()</seealso>. +received within the time specified with the corresponding &mod_call_opt;. Reason <c>failover</c> indicates that the transport connection to the peer to which the request has been sent has become unavailable and that not alternate peer was not selected.</p> -<marker id="handle_request"/> </desc> </func> @@ -530,24 +486,23 @@ not selected.</p> <name>Mod:handle_request(Packet, SvcName, Peer) -> Action</name> <fsummary>Receive an incoming request.</fsummary> <type> -<v>Packet = <seealso marker="#packet">packet()</seealso></v> +<v>Packet = &packet;</v> <v>SvcName = term()</v> -<v>Peer = <seealso marker="#peer">peer()</seealso></v> +<v>Peer = &peer;</v> <v>Action = Reply | {relay, [Opt]} | discard | {eval|eval_packet, Action, PostF}</v> -<v>Reply = {reply, <seealso marker="#message">message()</seealso>} +<v>Reply = {reply, &packet; | &message;} | {protocol_error, 3000..3999}</v> -<v>Opt = <seealso marker="diameter#call_opt">diameter:call_opt()</seealso></v> -<v>PostF = <seealso marker="diameter#evaluable">diameter:evaluable()</seealso></v> +<v>Opt = &mod_call_opt;</v> +<v>PostF = &mod_evaluable;</v> </type> <desc> <p> Invoked when a request message is received from a peer. The application in which the callback takes place (that is, the -callback module as configured with <seealso -marker="diameter#start_service">diameter:start_service/2</seealso>) +callback module as configured with &mod_start_service;) is determined by the Application Identifier in the header of the incoming request message, the selected module being the one whose corresponding <seealso @@ -556,19 +511,19 @@ itself as defining either the application in question or the Relay application.</p> <p> -The argument <seealso marker="#packet">packet()</seealso> has the following signature.</p> +The argument &packet; has the following signature.</p> -<code> +<pre> #diameter_packet{header = #diameter_header{}, avps = [#diameter_avp{}], msg = record() | undefined, - errors = [<seealso marker="diameter_dict#DATA_TYPES">Unsigned32()</seealso> | {<seealso marker="diameter_dict#DATA_TYPES">Unsigned32()</seealso>, #diameter_avp{}}], + errors = [&dict_Unsigned32; | {&dict_Unsigned32;, #diameter_avp{}}], bin = binary(), transport_data = term()} -</code> +</pre> <p> -The <c>msg</c> field will be <c>undefined</c> only in case the request has +The <c>msg</c> field will be <c>undefined</c> in case the request has been received in the relay application. Otherwise it contains the record representing the request as outlined in <seealso @@ -579,8 +534,8 @@ The <c>errors</c> field specifies any Result-Code's identifying errors that were encountered in decoding the request. In this case diameter will set both Result-Code and Failed-AVP AVP's in a returned -answer <seealso marker="#message">message()</seealso> before sending it to the peer: -the returned <seealso marker="#message">message()</seealso> need only set any other required AVP's. +answer &message; before sending it to the peer: +the returned &message; need only set any other required AVP's. Note that the errors detected by diameter are all of the 5xxx series (Permanent Failures). The <c>errors</c> list is empty if the request has been received in @@ -590,19 +545,24 @@ the relay application.</p> The <c>transport_data</c> field contains an arbitrary term passed into diameter from the transport module in question, or the atom <c>undefined</c> if the transport specified no data. -The term is preserved in the <seealso marker="#packet">packet()</seealso> containing any answer message -sent back to the transport process unless another value is explicitly -specified.</p> +The term is preserved if a &message; is returned but must be set +explicitly in a returned &packet;.</p> <p> The semantics of each of the possible return values are as follows.</p> <taglist> -<tag><c>{reply, <seealso marker="#message">message()</seealso>}</c></tag> +<tag><c>{reply, &packet; | &message;}</c></tag> <item> <p> -Send the specified answer message to the peer.</p> +Send the specified answer message to the peer. +In the case of a &packet;, the +message to be sent must be set in the +<c>msg</c> field and the <c>header</c> field can be set to a +<c>#diameter_header{}</c> to specify values that should be +preserved in the outgoing answer, appropriate values otherwise +being set by diameter.</p> </item> <tag><c>{protocol_error, 3000..3999}</c></tag> @@ -611,9 +571,9 @@ Send the specified answer message to the peer.</p> Send an answer message to the peer containing the specified protocol error. Equivalent to</p> -<code> +<pre> {reply, ['answer-message' | Avps] -</code> +</pre> <p> where <c>Avps</c> sets the Origin-Host, Origin-Realm, the specified Result-Code and (if the request sent one) Session-Id AVP's.</p> @@ -632,23 +592,22 @@ Relay a request to another peer in the role of a Diameter relay agent. If a routing loop is detected then the request is answered with 3005 (DIAMETER_LOOP_DETECTED). Otherwise a Route-Record AVP (containing the sending peer's Origin-Host) is -added to the request and <seealso marker="#pick_peer">pick_peer/4</seealso> -and subsequent callbacks take place just as if <seealso -marker="diameter#call">diameter:call/4</seealso> had been called +added to the request and &pick_peer; +and subsequent callbacks take place just as if &mod_call; had been called explicitly. The End-to-End Identifier of the incoming request is preserved in the header of the relayed request.</p> <p> The returned <c>Opts</c> should not specify <c>detach</c>. -A subsequent <seealso marker="#handle_answer">handle_answer/4</seealso> +A subsequent &handle_answer; callback for the relayed request must return its first argument, the <c>#diameter_packet{}</c> record containing the answer message. Note that the <c>extra</c> option can be specified to supply arguments that can distinguish the relay case from others if so desired. Any other return value (for example, from a -<seealso marker="#handle_error">handle_error/4</seealso> callback) +&handle_error; callback) causes the request to be answered with 3002 (DIAMETER_UNABLE_TO_DELIVER).</p> </item> diff --git a/lib/diameter/doc/src/diameter_compile.xml b/lib/diameter/doc/src/diameter_compile.xml index 7a6ca48798..eb6de80c11 100644 --- a/lib/diameter/doc/src/diameter_compile.xml +++ b/lib/diameter/doc/src/diameter_compile.xml @@ -1,5 +1,10 @@ <?xml version="1.0" encoding="iso-8859-1" ?> -<!DOCTYPE comref SYSTEM "comref.dtd"> +<!DOCTYPE comref SYSTEM "comref.dtd" [ + <!ENTITY % also SYSTEM "seealso.ent" > + <!ENTITY % here SYSTEM "seehere.ent" > + %also; + %here; +]> <comref> <header> @@ -122,7 +127,7 @@ Returns 0 on success, non-zero on failure.</p> <title>SEE ALSO</title> <p> -<seealso marker="diameter_dict">diameter_dict(4)</seealso></p> +&man_dict;</p> </section> diff --git a/lib/diameter/doc/src/diameter_dict.xml b/lib/diameter/doc/src/diameter_dict.xml index 98adebf145..4a6cccc276 100644 --- a/lib/diameter/doc/src/diameter_dict.xml +++ b/lib/diameter/doc/src/diameter_dict.xml @@ -1,5 +1,10 @@ <?xml version="1.0" encoding="latin1" ?> -<!DOCTYPE erlref SYSTEM "fileref.dtd"> +<!DOCTYPE erlref SYSTEM "fileref.dtd" [ + <!ENTITY % also SYSTEM "seealso.ent" > + <!ENTITY % here SYSTEM "seehere.ent" > + %also; + %here; +]> <fileref> <header> @@ -40,8 +45,7 @@ under the License. <description> <p> -A diameter service as configured with <seealso -marker="diameter#start_service">diameter:start_service/2</seealso> +A diameter service as configured with &mod_start_service; specifies one or more supported Diameter applications. Each Diameter application specifies a dictionary module that knows how to encode and decode its messages and AVPs. @@ -58,8 +62,7 @@ resulting dictionaries modules on a service.</p> The codec generation also results in a hrl file that defines records for the messages and grouped AVPs defined for the application, these records being what a user of the diameter application sends and receives. -(Modulo other available formats as discussed in <seealso -marker="diameter_app">diameter_app(3)</seealso>.) +(Modulo other available formats as discussed in &man_app;.) These records and the underlying Erlang data types corresponding to Diameter data formats are discussed in <seealso marker="#MESSAGE_RECORDS">MESSAGE RECORDS</seealso> and <seealso @@ -125,9 +128,9 @@ is used to identify the relevant dictionary module.</p> <p> Example:</p> -<code> +<pre> @id 16777231 -</code> +</pre> </item> @@ -146,9 +149,9 @@ with existing modules in the system.</p> <p> Example:</p> -<code> +<pre> @name etsi_e2 -</code> +</pre> </item> @@ -169,9 +172,9 @@ different Diameter applications.</p> <p> Example:</p> -<code> +<pre> @prefix etsi_e2 -</code> +</pre> </item> @@ -189,9 +192,9 @@ The section has empty content.</p> <p> Example:</p> -<code> +<pre> @vendor 13019 ETSI -</code> +</pre> </item> @@ -205,13 +208,13 @@ The section content consists of AVP names.</p> <p> Example:</p> -<code> +<pre> @avp_vendor_id 2937 WWW-Auth Domain-Index Region-Set -</code> +</pre> </item> @@ -244,9 +247,9 @@ All dictionaries should typically inherit RFC3588 AVPs from <p> Example:</p> -<code> +<pre> @inherits diameter_gen_base_rfc3588 -</code> +</pre> </item> @@ -268,12 +271,12 @@ none are to be set.</p> <p> Example:</p> -<code> +<pre> @avp_types Location-Information 350 Grouped MV Requested-Information 353 Enumerated V -</code> +</pre> <warning> <p> @@ -298,11 +301,11 @@ encode/decode.</p> <p> Example:</p> -<code> +<pre> @custom_types rfc4005_avps Framed-IP-Address -</code> +</pre> </item> <tag><c>@codecs Mod</c></tag> @@ -315,11 +318,11 @@ Like <c>@custom_types</c> but requires the specified module to export <p> Example:</p> -<code> +<pre> @codecs rfc4005_avps Framed-IP-Address -</code> +</pre> </item> <tag><c>@messages</c></tag> @@ -330,7 +333,7 @@ The section content consists of definitions of the form specified in section 3.2 of RFC 3588, "Command Code ABNF specification".</p> <!-- RFC 4740 RTR/RTA --> -<code> +<pre> @messages RTR ::= < Diameter Header: 287, REQ, PXY > @@ -363,7 +366,7 @@ RTA ::= < Diameter Header: 287, PXY > * [ Proxy-Info ] * [ Route-Record ] * [ AVP ] -</code> +</pre> </item> @@ -378,14 +381,14 @@ section 4.4 of RFC 3588, "Grouped AVP Values".</p> <p> Example:</p> -<code> +<pre> @grouped SIP-Deregistration-Reason ::= < AVP Header: 383 > { SIP-Reason-Code } [ SIP-Reason-Info ] * [ AVP ] -</code> +</pre> <p> Specifying a Vendor-Id in the definition of a grouped AVP is @@ -408,14 +411,14 @@ otherwise defined in another dictionary.</p> <p> Example:</p> -<code> +<pre> @enum SIP-Reason-Code PERMANENT_TERMINATION 0 NEW_SIP_SERVER_ASSIGNED 1 SIP_SERVER_CHANGE 2 REMOVE_SIP_SERVER 3 -</code> +</pre> </item> <tag><c>@end</c></tag> @@ -450,22 +453,22 @@ contained in the message or grouped AVP in the order specified in the definition in question. For example, the grouped AVP</p> -<code> +<pre> SIP-Deregistration-Reason ::= < AVP Header: 383 > { SIP-Reason-Code } [ SIP-Reason-Info ] * [ AVP ] -</code> +</pre> <p> will result in the following record definition given an empty prefix.</p> -<code> +<pre> -record('SIP-Deregistration-Reason' {'SIP-Reason-Code', 'SIP-Reason-Info', 'AVP'}). -</code> +</pre> <p> The values encoded in the fields of generated records depends on the @@ -488,11 +491,9 @@ types being described below.</p> The data formats defined in sections 4.2 ("Basic AVP Data Formats") and 4.3 ("Derived AVP Data Formats") of RFC 3588 are encoded as values of the types defined here. -Values are passed to <seealso -marker="diameter#call">diameter:call/4</seealso> +Values are passed to &mod_call; in a request record when sending a request, returned in a resulting -answer record and passed to a <seealso -marker="diameter_app#handle_request">handle_request</seealso> +answer record and passed to a &app_handle_request; callback upon reception of an incoming request.</p> <p> @@ -507,7 +508,7 @@ callback upon reception of an incoming request.</p> <marker id="Float64"/> <marker id="Grouped"/> -<code> +<pre> OctetString() = [0..255] Integer32() = -2147483647..2147483647 Integer64() = -9223372036854775807..9223372036854775807 @@ -516,7 +517,7 @@ Unsigned64() = 0..18446744073709551615 Float32() = '-infinity' | float() | infinity Float64() = '-infinity' | float() | infinity Grouped() = record() -</code> +</pre> <p> On encode, an OctetString() can be specified as an iolist(), @@ -530,10 +531,10 @@ section.</p> <em>Derived AVP Data Formats</em></p> <marker id="Address"/> -<code> +<pre> Address() = OctetString() | tuple() -</code> +</pre> <p> On encode, an OctetString() IPv4 address is parsed in the usual @@ -545,7 +546,7 @@ An IPv6 tuple() has length 8 and contains values of type 0..65535. The tuple representation is used on decode.</p> <marker id="Time"/> -<code> +<pre> Time() = {date(), time()} where @@ -559,7 +560,7 @@ where Hour = 0..23 Minute = 0..59 Second = 0..59 -</code> +</pre> <p> Additionally, values that can be encoded are @@ -569,9 +570,9 @@ In particular, only values between <c>{{1968,1,20},{3,14,8}}</c> and <c>{{2104,2,26},{9,42,23}}</c> (both inclusive) can be encoded.</p> <marker id="UTF8String"/> -<code> +<pre> UTF8String() = [integer()] -</code> +</pre> <p> List elements are the UTF-8 encodings of the individual characters @@ -579,15 +580,15 @@ in the string. Invalid codepoints will result in encode/decode failure.</p> <marker id="DiameterIdentity"/> -<code> +<pre> DiameterIdentity() = OctetString() -</code> +</pre> <p> A value must have length at least 1.</p> <marker id="DiameterURI"/> -<code> +<pre> DiameterURI() = OctetString() | #diameter_URI{type = Type, fqdn = FQDN, @@ -602,7 +603,7 @@ where Port = integer() Transport = sctp | tcp Protocol = diameter | radius | 'tacacs+' -</code> +</pre> <p> On encode, fields port, transport and protocol default to 3868, sctp @@ -612,9 +613,9 @@ section 4.3 of RFC 3588. The record representation is used on decode.</p> <marker id="Enumerated"/> -<code> +<pre> Enumerated() = Integer32() -</code> +</pre> <p> On encode, values can be specified using the macros defined in a @@ -622,10 +623,10 @@ dictionary's hrl file.</p> <marker id="IPFilterRule"/> <marker id="QoSFilterRule"/> -<code> +<pre> IPFilterRule() = OctetString() QoSFilterRule() = OctetString() -</code> +</pre> <p> Values of these types are not currently parsed by diameter.</p> @@ -639,9 +640,7 @@ Values of these types are not currently parsed by diameter.</p> <title>SEE ALSO</title> <p> -<seealso marker="diameterc">diameterc(1)</seealso>, -<seealso marker="diameter">diameter(3)</seealso>, -<seealso marker="diameter_app">diameter_app(3)</seealso></p> +&man_compile;, &man_main;, &man_app;</p> </section> diff --git a/lib/diameter/doc/src/diameter_sctp.xml b/lib/diameter/doc/src/diameter_sctp.xml index 955169349c..a023a9bc08 100644 --- a/lib/diameter/doc/src/diameter_sctp.xml +++ b/lib/diameter/doc/src/diameter_sctp.xml @@ -1,5 +1,10 @@ <?xml version="1.0" encoding="latin1" ?> -<!DOCTYPE erlref SYSTEM "erlref.dtd"> +<!DOCTYPE erlref SYSTEM "erlref.dtd" [ + <!ENTITY % also SYSTEM "seealso.ent" > + <!ENTITY % here SYSTEM "seehere.ent" > + %also; + %here; +]> <erlref> <header> @@ -38,12 +43,12 @@ under the License. <description> <p> -This module implements diameter transport over SCTP using gen_sctp. +This module implements diameter transport over SCTP using <seealso +marker="kernel:gen_sctp">gen_sctp</seealso>. It can be specified as the value of a transport_module option to -<seealso -marker="diameter#add_transport">diameter:add_transport/2</seealso> +&mod_add_transport; and implements the behaviour documented in -<seealso marker="diameter_transport">diameter_transport(3)</seealso>.</p> +&man_transport;.</p> <marker id="start"/> </description> @@ -58,7 +63,7 @@ and implements the behaviour documented in <fsummary>Start a transport process.</fsummary> <type> <v>Type = connect | accept</v> -<v>Ref = <seealso marker="diameter#transport_ref">diameter:transport_ref()</seealso></v> +<v>Ref = &mod_transport_ref;</v> <v>Svc = #diameter_service{}</v> <v>Opt = {raddr, <seealso marker="kernel:inet#type-ip_address">inet:ip_address()</seealso>} | {rport, integer()} | term()</v> <v>Pid = pid()</v> @@ -68,8 +73,7 @@ and implements the behaviour documented in <desc> <p> -The start function required by <seealso -marker="diameter_transport#start">diameter_transport(3)</seealso>.</p> +The start function required by &man_transport;.</p> <p> The only diameter_sctp-specific argument is the options list. @@ -114,16 +118,13 @@ diameter_sctp uses the <c>transport_data</c> field of the <c>#diameter_packet{}</c> record to communicate the stream on which an inbound message has been received, or on which an outbound message should be sent: the value will be of the form <c>{stream, Id}</c> -on an inbound message passed to a <seealso -marker="diameter_app#handle_request">handle_request</seealso> or <seealso -marker="diameter_app#handle_answer">handle_answer</seealso> callback. +on an inbound message passed to a &app_handle_request; or +&app_handle_answer; callback. For an outbound message, either <c>undefined</c> (explicitly or by receiving the outbound message as a <c>binary()</c>) or a tuple -should be set in the return value of <seealso -marker="diameter_app#handle_request">handle_request</seealso> +should be set in the return value of &app_handle_request; (typically by retaining the value passed into this function) -or <seealso -marker="diameter_app#prepare_request">prepare_request</seealso>. +or &app_prepare_request;. The value <c>undefined</c> uses a "next outbound stream" id and increments this modulo the total number outbound streams. That is, successive values of <c>undefined</c> cycle through all @@ -144,7 +145,8 @@ outbound streams.</p> <title>SEE ALSO</title> <p> -<seealso marker="diameter_transport">diameter_transport(3)</seealso>, +&man_main;, +&man_transport;, <seealso marker="kernel:gen_sctp">gen_sctp(3)</seealso>, <seealso marker="kernel:inet">inet(3)</seealso></p> diff --git a/lib/diameter/doc/src/diameter_tcp.xml b/lib/diameter/doc/src/diameter_tcp.xml index 3ffcebfd90..be8a938115 100644 --- a/lib/diameter/doc/src/diameter_tcp.xml +++ b/lib/diameter/doc/src/diameter_tcp.xml @@ -1,5 +1,10 @@ <?xml version="1.0" encoding="latin1" ?> -<!DOCTYPE erlref SYSTEM "erlref.dtd"> +<!DOCTYPE erlref SYSTEM "erlref.dtd" [ + <!ENTITY % also SYSTEM "seealso.ent" > + <!ENTITY % here SYSTEM "seehere.ent" > + %also; + %here; +]> <erlref> <header> @@ -41,10 +46,9 @@ under the License. This module implements diameter transport over TCP using <seealso marker="kernel:gen_tcp">gen_tcp</seealso>. It can be specified as the value of a <c>transport_module</c> option to -<seealso -marker="diameter#add_transport">diameter:add_transport/2</seealso> +&mod_add_transport; and implements the behaviour documented in -<seealso marker="diameter_transport">diameter_transport(3)</seealso>. +&man_transport;. TLS security is supported, both as an upgrade following capabilities exchange as specified by RFC 3588 and at connection establishment as in the current draft standard.</p> @@ -66,7 +70,7 @@ before configuring TLS capability on diameter transports.</p> <fsummary>Start a transport process.</fsummary> <type> <v>Type = connect | accept</v> -<v>Ref = <seealso marker="diameter#transport_ref">diameter:transport_ref()</seealso></v> +<v>Ref = &mod_transport_ref;</v> <v>Svc = #diameter_service{}</v> <v>Opt = OwnOpt | SslOpt | TcpOpt</v> <v>Pid = pid()</v> @@ -81,8 +85,7 @@ before configuring TLS capability on diameter transports.</p> <desc> <p> -The start function required by <seealso -marker="diameter_transport#start">diameter_transport(3)</seealso>.</p> +The start function required by &man_transport;.</p> <p> The only diameter_tcp-specific argument is the options list. @@ -115,10 +118,8 @@ Note that the option <c>ip</c> specifies the local address.</p> An <c>ssl_options</c> list must be specified if and only if the transport in question has set <c>Inband-Security-Id</c> to 1 (<c>TLS</c>), as -specified to either <seealso -marker="diameter#start_service">start_service/2</seealso> or -<seealso -marker="diameter#add_transport">add_transport/2</seealso>, +specified to either &mod_start_service; or +&mod_add_transport;, so that the transport process will receive notification of whether or not to commence with a TLS handshake following capabilities exchange. @@ -149,8 +150,8 @@ The returned local address list has length one.</p> <title>SEE ALSO</title> <p> -<seealso marker="diameter">diameter(3)</seealso>, -<seealso marker="diameter_transport">diameter_transport(3)</seealso>, +&man_main;, +&man_transport;, <seealso marker="kernel:gen_tcp">gen_tcp(3)</seealso>, <seealso marker="kernel:inet">inet(3)</seealso>, <seealso marker="ssl:ssl">ssl(3)</seealso></p> diff --git a/lib/diameter/doc/src/diameter_transport.xml b/lib/diameter/doc/src/diameter_transport.xml index d9b36a1e09..0507af63a8 100644 --- a/lib/diameter/doc/src/diameter_transport.xml +++ b/lib/diameter/doc/src/diameter_transport.xml @@ -1,5 +1,10 @@ <?xml version="1.0" encoding="latin1" ?> -<!DOCTYPE erlref SYSTEM "erlref.dtd"> +<!DOCTYPE erlref SYSTEM "erlref.dtd" [ + <!ENTITY % also SYSTEM "seealso.ent" > + <!ENTITY % here SYSTEM "seehere.ent" > + %also; + %here; +]> <erlref> <header> @@ -38,8 +43,7 @@ under the License. <description> <p> -A module specified as a <c>transport_module</c> to <seealso -marker="diameter#add_transport">diameter:add_transport/2</seealso> +A module specified as a <c>transport_module</c> to &mod_add_transport; must implement the interface documented here. The interface consists of a function with which diameter starts a transport process and a message interface with which @@ -59,7 +63,7 @@ parent).</p> <fsummary>Start a transport process.</fsummary> <type> <v>Type = connect | accept</v> -<v>Ref = <seealso marker="diameter#transport_ref">diameter:transport_ref()</seealso></v> +<v>Ref = &mod_transport_ref;</v> <v>Svc = #diameter_service{}</v> <v>Config = term()</v> <v>Pid = pid()</v> @@ -69,8 +73,7 @@ parent).</p> <desc> <p> Start a transport process. -Called by diameter as a consequence of a call to <seealso -marker="diameter#add_transport">diameter:add_transport/2</seealso> in +Called by diameter as a consequence of a call to &mod_add_transport; in order to establish or accept a transport connection respectively. A transport process maintains a connection with a single remote peer.</p> @@ -82,22 +85,18 @@ In the latter case, transport processes are started as required to accept connections from multiple peers.</p> <p> -Ref is the value that was returned from the call to <seealso -marker="diameter#add_transport">diameter:add_transport/2</seealso> +Ref is the value that was returned from the call to &mod_add_transport; that has lead to starting of a transport process.</p> <p> -<c>Svc</c> contains the capabilities passed to <seealso -marker="diameter#start_service">diameter:start_service/2</seealso> and -<seealso -marker="diameter#add_transport">diameter:add_transport/2</seealso>, +<c>Svc</c> contains the capabilities passed to &mod_start_service; and +&mod_add_transport;, values passed to the latter overriding those passed to the former.</p> <p> <c>Config</c> is as passed in <c>transport_config</c> tuple in the -<seealso marker="diameter#transport_opt">diameter:transport_opt()</seealso> -list passed to <seealso -marker="diameter#add_transport">diameter:add_transport/2</seealso>.</p> +&mod_transport_opt; +list passed to &mod_add_transport;.</p> <p> The start function should use the <c>Host-IP-Address</c> list and/or @@ -149,9 +148,9 @@ contains the binary to send.</p> <tag><c>{diameter, {close, Pid}}</c></tag> <item> <p> -A request to close the transport connection. -The transport process should terminate after closing the -connection. +A request to terminate the transport process after having received DPA +in response to DPR. +The transport process should exit. <c>Pid</c> is the pid() of the parent process.</p> </item> @@ -245,8 +244,7 @@ A transport must exit if a handshake is not successful.</p> <title>SEE ALSO</title> <p> -<seealso marker="diameter_tcp">diameter_tcp(3)</seealso>, -<seealso marker="diameter_sctp">diameter_sctp(3)</seealso></p> +&man_tcp;, &man_sctp;</p> </section> diff --git a/lib/diameter/doc/src/files.mk b/lib/diameter/doc/src/files.mk index 79d53abceb..89ec1031e6 100644 --- a/lib/diameter/doc/src/files.mk +++ b/lib/diameter/doc/src/files.mk @@ -2,7 +2,7 @@ # %CopyrightBegin% # -# Copyright Ericsson AB 2010-2011. All Rights Reserved. +# Copyright Ericsson AB 2010-2012. 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 diff --git a/lib/diameter/doc/src/notes.xml b/lib/diameter/doc/src/notes.xml index e57958ac09..b89d84a4f6 100644 --- a/lib/diameter/doc/src/notes.xml +++ b/lib/diameter/doc/src/notes.xml @@ -1,11 +1,17 @@ <?xml version="1.0" encoding="latin1" ?> -<!DOCTYPE chapter SYSTEM "chapter.dtd"> +<!DOCTYPE chapter SYSTEM "chapter.dtd" [ + <!ENTITY % also SYSTEM "seealso.ent" > + <!ENTITY % here SYSTEM "seehere.ent" > + %also; + %here; +]> <chapter> <header> <copyright> <year>2011</year> +<year>2012</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -136,6 +142,33 @@ first.</p> </section> +<section><title>Diameter 1.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix fault in sending of 'closed' events.</p> + <p> + The fault made it possible for the 'closed' event not to + be sent following a failed capabilities exchange.</p> + <p> + Own Id: OTP-9824</p> + </item> + <item> + <p> + Fix faulty diameterc -name/-prefix.</p> + <p> + A minor blunder when introducing the new dictionary + parser in diameter-1.0 broke these options.</p> + <p> + Own Id: OTP-9826</p> + </item> + </list> + </section> + +</section> + <section><title>Diameter 1.0</title> <section><title>Fixed Bugs and Malfunctions</title> @@ -395,8 +428,7 @@ Known issues or limitations:</p> Some agent-related functionality is not entirely complete. In particular, support for proxy agents, that advertise specific Diameter applications but otherwise relay messages in much the same -way as relay agents (for which a <seealso -marker="diameter_app#handle_request">handle_request/3</seealso> +way as relay agents (for which a &handle_request; callback can return a <c>relay</c> tuple), will be completed in an upcoming release. There may also be more explicit support for redirect agents, although @@ -428,8 +460,7 @@ could likely be expanded upon.</p> <item> <p> -The function <seealso -marker="diameter#service_info">diameter:service_info/2</seealso> +The function &service_info; can be used to retrieve information about a started service (statistics, information about connected peers, etc) but this is not yet documented and both the input and output may change diff --git a/lib/diameter/doc/src/seealso.ent b/lib/diameter/doc/src/seealso.ent new file mode 100644 index 0000000000..6f67630220 --- /dev/null +++ b/lib/diameter/doc/src/seealso.ent @@ -0,0 +1,112 @@ +<?xml version="1.0" encoding="iso-8859-1" ?> + +<!-- + +%CopyrightBegin% + +Copyright Ericsson AB 2012. 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 +compliance with the License. You should have received a copy of the +Erlang Public License along with this software. If not, it can be +retrieved online at http://www.erlang.org/. + +Software distributed under the License is distributed on an "AS IS" +basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +the License for the specific language governing rights and limitations +under the License. + +%CopyrightEnd% + +--> + +<!-- + +Entities for cross references: less to type, easier to read and +less error prone. + +Additional intra-document entities are generated by seehere.sed. +That each definition is on a single (hideously long) line is +significant. + +--> + +<!-- diameter --> + +<!ENTITY mod_add_transport '<seealso marker="diameter#add_transport-2">diameter:add_transport/2</seealso>'> +<!ENTITY mod_call '<seealso marker="diameter#call-4">diameter:call/4</seealso>'> +<!ENTITY mod_origin_state_id '<seealso marker="diameter#origin_state_id-0">diameter:origin_state_id/0</seealso>'> +<!ENTITY mod_remove_transport '<seealso marker="diameter#remove_transport-2">diameter:remove_transport/2</seealso>'> +<!ENTITY mod_service_info '<seealso marker="diameter#service_info-2">diameter:service_info/2</seealso>'> +<!ENTITY mod_services '<seealso marker="diameter#services-0">diameter:services/0</seealso>'> +<!ENTITY mod_start_service '<seealso marker="diameter#start_service-2">diameter:start_service/2</seealso>'> +<!ENTITY mod_stop_service '<seealso marker="diameter#stop_service-1">diameter:stop_service/1</seealso>'> +<!ENTITY mod_subscribe '<seealso marker="diameter#subscribe-1">diameter:subscribe/1</seealso>'> + +<!ENTITY mod_application_alias '<seealso marker="diameter#application_alias">diameter:application_alias()</seealso>'> +<!ENTITY mod_application_module '<seealso marker="diameter#application_module">diameter:application_module()</seealso>'> +<!ENTITY mod_application_opt '<seealso marker="diameter#application_opt">diameter:application_opt()</seealso>'> +<!ENTITY mod_call_opt '<seealso marker="diameter#call_opt">diameter:call_opt()</seealso>'> +<!ENTITY mod_capability '<seealso marker="diameter#capability">diameter:capability()</seealso>'> +<!ENTITY mod_evaluable '<seealso marker="diameter#evaluable">diameter:evaluable()</seealso>'> +<!ENTITY mod_peer_filter '<seealso marker="diameter#peer_filter">diameter:peer_filter()</seealso>'> +<!ENTITY mod_service_event '<seealso marker="diameter#service_event">diameter:service_event()</seealso>'> +<!ENTITY mod_service_name '<seealso marker="diameter#service_name">diameter:service_name()</seealso>'> +<!ENTITY mod_service_opt '<seealso marker="diameter#service_opt">diameter:service_opt()</seealso>'> +<!ENTITY mod_transport_opt '<seealso marker="diameter#transport_opt">diameter:transport_opt()</seealso>'> +<!ENTITY mod_transport_ref '<seealso marker="diameter#transport_ref">diameter:transport_ref()</seealso>'> + +<!ENTITY capabilities_cb '<seealso marker="#capabilities_cb">capabilities_cb</seealso>'> +<!ENTITY capx_timeout '<seealso marker="#capx_timeout">capx_timeout</seealso>'> +<!ENTITY disconnect_cb '<seealso marker="#disconnect_cb">disconnect_cb</seealso>'> +<!ENTITY transport_config '<seealso marker="#transport_config">transport_config</seealso>'> +<!ENTITY transport_module '<seealso marker="#transport_module">transport_module</seealso>'> +<!ENTITY reconnect_timer '<seealso marker="#reconnect_timer">reconnect_timer</seealso>'> +<!ENTITY watchdog_timer '<seealso marker="#watchdog_timer">watchdog_timer</seealso>'> + +<!-- diameter_app --> + +<!ENTITY app_handle_answer '<seealso marker="diameter_app#Mod:handle_answer-4">handle_answer/4</seealso>'> +<!ENTITY app_handle_request '<seealso marker="diameter_app#Mod:handle_request-3">handle_request/3</seealso>'> +<!ENTITY app_handle_error '<seealso marker="diameter_app#Mod:handle_error-4">handle_error/4</seealso>'> +<!ENTITY app_peer_down '<seealso marker="diameter_app#Mod:peer_down-3">peer_up/3</seealso>'> +<!ENTITY app_peer_up '<seealso marker="diameter_app#Mod:peer_up-3">peer_up/3</seealso>'> +<!ENTITY app_pick_peer '<seealso marker="diameter_app#Mod:pick_peer-4">pick_peer/4</seealso>'> +<!ENTITY app_prepare_retransmit '<seealso marker="diameter_app#Mod:prepare_retransmit-3">prepare_retransmit/3</seealso>'> +<!ENTITY app_prepare_request '<seealso marker="diameter_app#Mod:prepare_request-3">prepare_request/3</seealso>'> + +<!ENTITY app_capabilities '<seealso marker="diameter_app#capabilities">diameter_app:capabilities()</seealso>'> +<!ENTITY app_message '<seealso marker="diameter_app#message">diameter_app:message()</seealso>'> +<!ENTITY app_packet '<seealso marker="diameter_app#packet">diameter_app:packet()</seealso>'> +<!ENTITY app_peer '<seealso marker="diameter_app#peer">diameter_app:peer()</seealso>'> +<!ENTITY app_peer_ref '<seealso marker="diameter_app#peer_ref">diameter_app:peer_ref()</seealso>'> +<!ENTITY app_state '<seealso marker="diameter_app#state">diameter_app:state()</seealso>'> + +<!-- diameter_dict --> + +<!ENTITY dict_data_types '<seealso marker="diameter_dict#DATA_TYPES">diameter_dict(4)</seealso>'> + +<!ENTITY dict_Address '<seealso marker="diameter_dict#DATA_TYPES">Address()</seealso>'> +<!ENTITY dict_DiameterIdentity '<seealso marker="diameter_dict#DATA_TYPES">DiameterIdentity()</seealso>'> +<!ENTITY dict_Grouped '<seealso marker="diameter_dict#DATA_TYPES">Grouped()</seealso>'> +<!ENTITY dict_OctetString '<seealso marker="diameter_dict#DATA_TYPES">OctetString()</seealso>'> +<!ENTITY dict_Time '<seealso marker="diameter_dict#DATA_TYPES">Time()</seealso>'> +<!ENTITY dict_UTF8String '<seealso marker="diameter_dict#DATA_TYPES">UTF8String()</seealso>'> +<!ENTITY dict_Unsigned32 '<seealso marker="diameter_dict#DATA_TYPES">Unsigned32()</seealso>'> + +<!-- diameter_transport --> + +<!ENTITY transport_start + '<seealso marker="diameter_transport#Mod:start-3">start/3</seealso>'> + +<!-- reference pages --> + +<!ENTITY man_compile '<seealso marker="diameterc">diameterc(1)</seealso>'> +<!ENTITY man_main '<seealso marker="diameter">diameter(3)</seealso>'> +<!ENTITY man_app '<seealso marker="diameter_app">diameter_app(3)</seealso>'> +<!ENTITY man_dict '<seealso marker="diameter_dict">diameter_dict(4)</seealso>'> +<!ENTITY man_transport + '<seealso marker="diameter_transport">diameter_transport(3)</seealso>'> +<!ENTITY man_sctp '<seealso marker="diameter_sctp">diameter_sctp(3)</seealso>'> +<!ENTITY man_tcp '<seealso marker="diameter_tcp">diameter_tcp(3)</seealso>'> diff --git a/lib/diameter/doc/src/seehere.sed b/lib/diameter/doc/src/seehere.sed new file mode 100644 index 0000000000..c62a783d40 --- /dev/null +++ b/lib/diameter/doc/src/seehere.sed @@ -0,0 +1,35 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 2012. 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 +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% + +# +# Map entities for inter-document references to ones for +# intra-document references like this: +# +# <!ENTITY aaa_xxx '<seealso marker="bbb#yyy">ccc:zzz</seealso>'> +# +# ===> +# +# <!ENTITY xxx '<seealso marker="#yyy">zzz</seealso>'> +# + +/<!ENTITY/!d +/#/!d +/"#/d +s@ [^_]*_@ @ +s@"[^#]*#@"#@ +s@>[^:]*:@>@ diff --git a/lib/diameter/src/base/diameter.appup.src b/lib/diameter/src/base/diameter.appup.src index 9b2a7d18ab..5655f98c1b 100644 --- a/lib/diameter/src/base/diameter.appup.src +++ b/lib/diameter/src/base/diameter.appup.src @@ -20,30 +20,38 @@ {"%VSN%", [ - {"0.9", [{restart_application, diameter}]}, - {"0.10", [{restart_application, diameter}]}, - {"1.0", [{restart_application, diameter}]}, - {"1.1", [%% new code - {add_module, diameter_transport}, - %% modified code - {load, diameter_sctp}, - {load, diameter_stats}, - {load, diameter_service}, - {load, diameter_config}, - {load, diameter_codec}, - {load, diameter_watchdog}, - {load, diameter_peer}, - {load, diameter_peer_fsm}, - {load, diameter}, - %% unmodified but including modified diameter.hrl - {load, diameter_callback}, - {load, diameter_capx}, - {load, diameter_types}]} + {"0.9", [{restart_application, diameter}]}, + {"0.10", [{restart_application, diameter}]}, + {"1.0", [{restart_application, diameter}]}, + {"1.1", [{restart_application, diameter}]}, + {"1.2", [{load, diameter}, + {load, diameter_capx}, + {load, diameter_codec}, + {load, diameter_peer}, + {load, diameter_reg}, + %% order significant from here + {load, diameter_session}, + {load, diameter_peer_fsm}, + {load, diameter_service}, + {load, diameter_watchdog}, + {load, diameter_config}]}, + {"1.2.1", [{load, diameter}, + {load, diameter_capx}, + {load, diameter_peer}, + {load, diameter_reg}, + %% order significant from here + {load, diameter_session}, + {load, diameter_peer_fsm}, + {load, diameter_service}, + {load, diameter_watchdog}, + {load, diameter_config}]} ], [ - {"0.9", [{restart_application, diameter}]}, - {"0.10", [{restart_application, diameter}]}, - {"1.0", [{restart_application, diameter}]}, - {"1.1", [{restart_application, diameter}]} + {"0.9", [{restart_application, diameter}]}, + {"0.10", [{restart_application, diameter}]}, + {"1.0", [{restart_application, diameter}]}, + {"1.1", [{restart_application, diameter}]}, + {"1.2", [{restart_application, diameter}]}, + {"1.2.1", [{restart_application, diameter}]} ] }. diff --git a/lib/diameter/src/base/diameter.erl b/lib/diameter/src/base/diameter.erl index 3e3a6be0ef..8f9901907a 100644 --- a/lib/diameter/src/base/diameter.erl +++ b/lib/diameter/src/base/diameter.erl @@ -330,6 +330,8 @@ call(SvcName, App, Message) -> | {applications, [app_alias()]} | {capabilities, [capability()]} | {capabilities_cb, evaluable()} + | {capx_timeout, 'Unsigned32'()} + | {disconnect_cb, evaluable()} | {watchdog_timer, 'Unsigned32'() | {module(), atom(), list()}} | {reconnect_timer, 'Unsigned32'()} | {private, any()}. diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl index 3f4945f7a6..c4320fcb99 100644 --- a/lib/diameter/src/base/diameter_peer_fsm.erl +++ b/lib/diameter/src/base/diameter_peer_fsm.erl @@ -48,15 +48,19 @@ -include("diameter_internal.hrl"). -include("diameter_gen_base_rfc3588.hrl"). +%% Values of Disconnect-Cause in DPR. -define(GOAWAY, ?'DIAMETER_BASE_DISCONNECT-CAUSE_DO_NOT_WANT_TO_TALK_TO_YOU'). -define(REBOOT, ?'DIAMETER_BASE_DISCONNECT-CAUSE_REBOOTING'). +-define(BUSY, ?'DIAMETER_BASE_DISCONNECT-CAUSE_BUSY'). -define(NO_INBAND_SECURITY, 0). -define(TLS, 1). %% Keys in process dictionary. -define(CB_KEY, cb). %% capabilities callback +-define(DPR_KEY, dpr). %% disconnect callback -define(DWA_KEY, dwa). %% outgoing DWA +-define(REF_KEY, ref). %% transport_ref() -define(Q_KEY, q). %% transport start queue -define(START_KEY, start). %% start of connected transport -define(SEQUENCE_KEY, mask). %% mask for sequence numbers @@ -68,28 +72,40 @@ %% A 2xxx series Result-Code. Not necessarily 2001. -define(IS_SUCCESS(N), 2 == (N) div 1000). +%% Guards. +-define(IS_UINT32(N), (is_integer(N) andalso 0 =< N andalso 0 == N bsr 32)). +-define(IS_TIMEOUT(N), ?IS_UINT32(N)). +-define(IS_CAUSE(N), N == ?REBOOT; N == rebooting; + N == ?GOAWAY; N == goaway; + N == ?BUSY; N == busy). + %% RFC 3588: %% %% Timeout An application-defined timer has expired while waiting %% for some event. %% -define(EVENT_TIMEOUT, 10000). +%% Default timeout for reception of CER/CEA. -%% How long to wait for a DPA in response to DPR before simply -%% aborting. Used to distinguish between shutdown and not but there's -%% not really any need. Stopping a service will require a timeout if -%% the peer doesn't answer DPR so the value should be short-ish. +%% Default timeout for DPA in response to DPR. A bit short but the +%% timeout used to be hardcoded. (So it could be worse.) -define(DPA_TIMEOUT, 1000). +-type uint32() :: diameter:'Unsigned32'(). + -record(state, - {state = 'Wait-Conn-Ack' %% state of RFC 3588 Peer State Machine - :: 'Wait-Conn-Ack' | recv_CER | 'Wait-CEA' | 'Open', + {state %% of RFC 3588 Peer State Machine + :: 'Wait-Conn-Ack' %% old code + | {'Wait-Conn-Ack', uint32()} + | recv_CER + | 'Wait-CEA' %% old code + | {'Wait-CEA', uint32(), uint32()} + | 'Open', mode :: accept | connect | {connect, reference()}, - parent :: pid(), - transport :: pid(), + parent :: pid(), %% watchdog process + transport :: pid(), %% transport process service :: #diameter_service{}, - dpr = false :: false | {diameter:'Unsigned32'(), - diameter:'Unsigned32'()}}). + dpr = false :: false | {uint32(), uint32()}}). %% | hop by hop and end to end identifiers %% There are non-3588 states possible as a consequence of 5.6.1 of the @@ -163,19 +179,24 @@ i({WPid, Type, Opts, #diameter_service{} = Svc}) -> %% from old code i({WPid, Type, Opts, {?NOMASK, [node() | nodes()], Svc}}); i({WPid, T, Opts, {Mask, Nodes, #diameter_service{applications = Apps, - capabilities = Caps} + capabilities = LCaps} = Svc}}) -> [] /= Apps orelse ?ERROR({no_apps, T, Opts}), - putr(?DWA_KEY, dwa(Caps)), + putr(?DWA_KEY, dwa(LCaps)), {M, Ref} = T, diameter_stats:reg(Ref), - {[Ts], Rest} = proplists:split(Opts, [capabilities_cb]), - putr(?CB_KEY, {Ref, [F || {_,F} <- Ts]}), + {[Cs,Ds], Rest} = proplists:split(Opts, [capabilities_cb, disconnect_cb]), + putr(?CB_KEY, {Ref, [F || {_,F} <- Cs]}), + putr(?DPR_KEY, [F || {_, F} <- Ds]), + putr(?REF_KEY, Ref), putr(?SEQUENCE_KEY, Mask), putr(?RESTRICT_KEY, Nodes), erlang:monitor(process, WPid), {TPid, Addrs} = start_transport(T, Rest, Svc), - #state{parent = WPid, + Tmo = proplists:get_value(capx_timeout, Opts, ?EVENT_TIMEOUT), + ?IS_TIMEOUT(Tmo) orelse ?ERROR({invalid, {capx_timeout, Tmo}}), + #state{state = {'Wait-Conn-Ack', Tmo}, + parent = WPid, transport = TPid, mode = M, service = svc(Svc, Addrs)}. @@ -188,8 +209,8 @@ i({WPid, T, Opts, {Mask, Nodes, #diameter_service{applications = Apps, %% watchdog start (start/2) succeeds regardless so as not to crash the %% service. -start_transport(T, Opts, #diameter_service{capabilities = Caps} = Svc) -> - Addrs0 = Caps#diameter_caps.host_ip_address, +start_transport(T, Opts, #diameter_service{capabilities = LCaps} = Svc) -> + Addrs0 = LCaps#diameter_caps.host_ip_address, start_transport(Addrs0, {T, Opts, Svc}). start_transport(Addrs0, T) -> @@ -212,9 +233,9 @@ svc(Svc, []) -> svc(Svc, Addrs) -> readdr(Svc, Addrs). -readdr(#diameter_service{capabilities = Caps0} = Svc, Addrs) -> - Caps = Caps0#diameter_caps{host_ip_address = Addrs}, - Svc#diameter_service{capabilities = Caps}. +readdr(#diameter_service{capabilities = LCaps0} = Svc, Addrs) -> + LCaps = LCaps0#diameter_caps{host_ip_address = Addrs}, + Svc#diameter_service{capabilities = LCaps}. %% The 4-tuple Data returned from diameter_peer:start/1 identifies the %% transport module/config use to start the transport process in @@ -313,13 +334,17 @@ eraser(Key) -> %% transition/2 +%% Started in old code. +transition(T, #state{state = 'Wait-Conn-Ack' = PS} = S) -> + transition(T, S#state{state = {PS, ?EVENT_TIMEOUT}}); + %% Connection to peer. transition({diameter, {TPid, connected, Remote}}, #state{transport = TPid, state = PS, mode = M} = S) -> - 'Wait-Conn-Ack' = PS, %% assert + {'Wait-Conn-Ack', _} = PS, %% assert connect = M, %% keep_transport(TPid), send_CER(S#state{mode = {M, Remote}}); @@ -331,11 +356,11 @@ transition({diameter, {TPid, connected}}, mode = M, parent = Pid} = S) -> - 'Wait-Conn-Ack' = PS, %% assert + {'Wait-Conn-Ack', Tmo} = PS, %% assert accept = M, %% keep_transport(TPid), Pid ! {accepted, self()}, - start_timer(S#state{state = recv_CER}); + start_timer(Tmo, S#state{state = recv_CER}); %% Connection established after receiving a connection_timeout %% message. This may be followed by an incoming message which arrived @@ -349,7 +374,7 @@ transition({diameter, {_, connected, _}}, _) -> %% Connection has timed out: start an alternate. transition({connection_timeout = T, TPid}, #state{transport = TPid, - state = 'Wait-Conn-Ack'} + state = {'Wait-Conn-Ack', _}} = S) -> exit(TPid, {shutdown, T}), start_next(S); @@ -364,7 +389,7 @@ transition({diameter, {recv, Pkt}}, S) -> %% Timeout when still in the same state ... transition({timeout, PS}, #state{state = PS}) -> - stop; + {stop, {capx(PS), timeout}}; %% ... or not. transition({timeout, _}, _) -> @@ -375,25 +400,19 @@ transition({send, Msg}, #state{transport = TPid}) -> send(TPid, Msg), ok; -%% Request for graceful shutdown. -transition({shutdown, Pid}, #state{parent = Pid, dpr = false} = S) -> - dpr(?GOAWAY, S); -transition({shutdown, Pid}, #state{parent = Pid}) -> - ok; - -%% Application shutdown. -transition(shutdown, #state{dpr = false} = S) -> - dpr(?REBOOT, S); -transition(shutdown, _) -> %% DPR already send: ensure expected timeout - dpa_timer(), +%% Messages from old (diameter_service) code. +transition(shutdown = T, #state{parent = Pid} = S) -> + transition({T, Pid, service}, S); %% Reason irrelevant: old code has no cb + +%% Request for graceful shutdown at remove_transport, stop_service of +%% application shutdown. +transition({shutdown = T, Pid}, S) -> + transition({T, Pid, transport}, S); +transition({shutdown, Pid, Reason}, #state{parent = Pid, dpr = false} = S) -> + dpr(Reason, S); +transition({shutdown, Pid, _}, #state{parent = Pid}) -> ok; -%% Request to close the transport connection. -transition({close = T, Pid}, #state{parent = Pid, - transport = TPid}) -> - diameter_peer:close(TPid), - {stop, T}; - %% DPA reception has timed out. transition(dpa_timeout, _) -> stop; @@ -425,6 +444,11 @@ transition({state, Pid}, #state{state = S, transport = TPid}) -> %% Crash on anything unexpected. +capx(recv_CER) -> + 'CER'; +capx({'Wait-CEA', _, _}) -> + 'CEA'. + %% start_next/1 start_next(#state{service = Svc0} = S) -> @@ -440,18 +464,23 @@ start_next(#state{service = Svc0} = S) -> %% send_CER/1 -send_CER(#state{mode = {connect, Remote}, - service = #diameter_service{capabilities = Caps}, +send_CER(#state{state = {'Wait-Conn-Ack', Tmo}, + mode = {connect, Remote}, + service = #diameter_service{capabilities = LCaps}, transport = TPid} = S) -> - OH = Caps#diameter_caps.origin_host, + OH = LCaps#diameter_caps.origin_host, req_send_CER(OH, Remote) orelse - close({already_connected, Remote, Caps}, S), + close({already_connected, Remote, LCaps}, S), CER = build_CER(S), ?LOG(send, 'CER'), - send(TPid, encode(CER)), - start_timer(S#state{state = 'Wait-CEA'}). + #diameter_packet{header = #diameter_header{end_to_end_id = Eid, + hop_by_hop_id = Hid}} + = Pkt + = encode(CER), + send(TPid, Pkt), + start_timer(Tmo, S#state{state = {'Wait-CEA', Hid, Eid}}). %% Register ourselves as connecting to the remote endpoint in %% question. This isn't strictly necessary since a peer implementing @@ -463,16 +492,16 @@ send_CER(#state{mode = {connect, Remote}, req_send_CER(OriginHost, Remote) -> register_everywhere({?MODULE, connection, OriginHost, {remote, Remote}}). -%% start_timer/1 +%% start_timer/2 -start_timer(#state{state = PS} = S) -> - erlang:send_after(?EVENT_TIMEOUT, self(), {timeout, PS}), +start_timer(Tmo, #state{state = PS} = S) -> + erlang:send_after(Tmo, self(), {timeout, PS}), S. %% build_CER/1 -build_CER(#state{service = #diameter_service{capabilities = Caps}}) -> - {ok, CER} = diameter_capx:build_CER(Caps), +build_CER(#state{service = #diameter_service{capabilities = LCaps}}) -> + {ok, CER} = diameter_capx:build_CER(LCaps), CER. %% encode/1 @@ -482,10 +511,8 @@ encode(Rec) -> Hdr = #diameter_header{version = ?DIAMETER_VERSION, end_to_end_id = Seq, hop_by_hop_id = Seq}, - Pkt = #diameter_packet{header = Hdr, - msg = Rec}, - #diameter_packet{bin = Bin} = diameter_codec:encode(?BASE, Pkt), - Bin. + diameter_codec:encode(?BASE, #diameter_packet{header = Hdr, + msg = Rec}). sequence() -> case getr(?SEQUENCE_KEY) of @@ -553,7 +580,14 @@ discard(Reason, F, A) -> %% rcv/3 %% Incoming CEA. -rcv('CEA', Pkt, #state{state = 'Wait-CEA'} = S) -> +rcv('CEA', + #diameter_packet{header = #diameter_header{end_to_end_id = Eid, + hop_by_hop_id = Hid}} + = Pkt, + #state{state = {'Wait-CEA' = T, Hid, Eid}} + = S) -> + handle_CEA(Pkt, S#state{state = T}); +rcv('CEA', Pkt, #state{state = 'Wait-CEA'} = S) -> %% old code handle_CEA(Pkt, S); %% Incoming CER @@ -573,16 +607,16 @@ rcv(N, Pkt, S) N == 'DPR' -> handle_request(N, Pkt, S); -%% DPA even though we haven't sent DPR: ignore. -rcv('DPA', _Pkt, #state{dpr = false}) -> - ok; - -%% DPA in response to DPR. We could check the sequence numbers but -%% don't bother, just close. -rcv('DPA' = N, _Pkt, #state{transport = TPid}) -> +%% DPA in response to DPR and with the expected identifiers. +rcv('DPA' = N, + #diameter_packet{header = #diameter_header{end_to_end_id = Eid, + hop_by_hop_id = Hid}}, + #state{transport = TPid, + dpr = {Hid, Eid}}) -> diameter_peer:close(TPid), {stop, N}; +%% Ignore anything else, an unsolicited DPA in particular. rcv(_, _, _) -> ok. @@ -800,8 +834,8 @@ a('CER', #diameter_caps{vendor_id = Vid, {'Product-Name', Name}, {'Origin-State-Id', OSI}]; -a('DPR', #diameter_caps{origin_host = Host, - origin_realm = Realm}) -> +a('DPR', #diameter_caps{origin_host = {Host, _}, + origin_realm = {Realm, _}}) -> ['DPA', {'Origin-Host', Host}, {'Origin-Realm', Realm}]. @@ -909,7 +943,9 @@ rejected(N) %% open/5 -open(Pkt, SupportedApps, Caps, {Type, IS}, #state{parent = Pid} = S) -> +open(Pkt, SupportedApps, Caps, {Type, IS}, #state{parent = Pid, + service = Svc} + = S) -> #diameter_caps{origin_host = {_,_} = H, inband_security_id = {LS,_}} = Caps, @@ -917,7 +953,9 @@ open(Pkt, SupportedApps, Caps, {Type, IS}, #state{parent = Pid} = S) -> tls_ack(lists:member(?TLS, LS), Caps, Type, IS, S), Pid ! {open, self(), H, {Caps, SupportedApps, Pkt}}, - S#state{state = 'Open'}. + %% Replace capabilities record with local/remote pairs. + S#state{state = 'Open', + service = Svc#diameter_service{capabilities = Caps}}. %% We've advertised TLS support: tell the transport the result %% and expect a reply when the handshake is complete. @@ -970,24 +1008,113 @@ dwa(#diameter_caps{origin_host = OH, {'Origin-State-Id', OSI}]. %% dpr/2 +%% +%% The RFC isn't clear on whether DPR should be send in a non-Open +%% state. The Peer State Machine transitions it documents aren't +%% exhaustive (no Stop in Wait-I-CEA for example) so assume it's up to +%% the implementation and transition to Closed (ie. die) if we haven't +%% yet reached Open. + +%% Connection is open, DPR has not been sent. +dpr(Reason, #state{state = 'Open', + dpr = false, + service = #diameter_service{capabilities = Caps}} + = S) -> + case getr(?DPR_KEY) of + CBs when is_list(CBs) -> + Ref = getr(?REF_KEY), + Peer = {self(), Caps}, + dpr(CBs, [Reason, Ref, Peer], S); + undefined -> %% started in old code + send_dpr(Reason, [], S) + end; -dpr(Cause, #state{transport = TPid, - service = #diameter_service{capabilities = Caps}} - = S) -> - #diameter_caps{origin_host = OH, - origin_realm = OR} +%% Connection is open, DPR already sent. +dpr(_, #state{state = 'Open'}) -> + ok; + +%% Connection not open. +dpr(_Reason, _S) -> + stop. + +%% dpr/3 +%% +%% Note that an implementation that wants to do something +%% transport_module-specific can lookup the pid of the transport +%% process and contact it. (eg. diameter:service_info/2) + +dpr([CB|Rest], [Reason | _] = Args, S) -> + try diameter_lib:eval([CB | Args]) of + {dpr, Opts} when is_list(Opts) -> + send_dpr(Reason, Opts, S); + dpr -> + send_dpr(Reason, [], S); + close = T -> + {stop, {disconnect_cb, T}}; + ignore -> + dpr(Rest, Args, S); + T -> + No = {disconnect_cb, T}, + diameter_lib:error_report(invalid, No), + {stop, No} + catch + E:R -> + No = {disconnect_cb, E, R, ?STACK}, + diameter_lib:error_report(failure, No), + {stop, No} + end; + +dpr([], [Reason | _], S) -> + send_dpr(Reason, [], S). + +-record(opts, {cause, timeout = ?DPA_TIMEOUT}). + +send_dpr(Reason, Opts, #state{transport = TPid, + service = #diameter_service{capabilities = Caps}} + = S) -> + #opts{cause = Cause, timeout = Tmo} + = lists:foldl(fun opt/2, + #opts{cause = case Reason of + transport -> ?GOAWAY; + _ -> ?REBOOT + end, + timeout = ?DPA_TIMEOUT}, + Opts), + #diameter_caps{origin_host = {OH, _}, + origin_realm = {OR, _}} = Caps, - Bin = encode(['DPR', {'Origin-Host', OH}, + #diameter_packet{header = #diameter_header{end_to_end_id = Eid, + hop_by_hop_id = Hid}} + = Pkt + = encode(['DPR', {'Origin-Host', OH}, {'Origin-Realm', OR}, {'Disconnect-Cause', Cause}]), - send(TPid, Bin), - dpa_timer(), + send(TPid, Pkt), + dpa_timer(Tmo), ?LOG(send, 'DPR'), - S#state{dpr = diameter_codec:sequence_numbers(Bin)}. - -dpa_timer() -> - erlang:send_after(?DPA_TIMEOUT, self(), dpa_timeout). + S#state{dpr = {Hid, Eid}}. + +opt({timeout, Tmo}, Rec) + when ?IS_TIMEOUT(Tmo) -> + Rec#opts{timeout = Tmo}; +opt({cause, Cause}, Rec) + when ?IS_CAUSE(Cause) -> + Rec#opts{cause = cause(Cause)}; +opt(T, _) -> + ?ERROR({invalid_option, T}). + +cause(rebooting) -> ?REBOOT; +cause(goaway) -> ?GOAWAY; +cause(busy) -> ?BUSY; +cause(N) + when ?IS_CAUSE(N) -> + N; +cause(N) -> + ?ERROR({invalid_cause, N}). + +dpa_timer(Tmo) -> + erlang:send_after(Tmo, self(), dpa_timeout). %% register_everywhere/1 %% diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl index cffba4fc94..a4a0b80348 100644 --- a/lib/diameter/src/base/diameter_service.erl +++ b/lib/diameter/src/base/diameter_service.erl @@ -494,7 +494,7 @@ handle_call({info, Item}, _From, S) -> {reply, service_info(Item, S), S}; handle_call(stop, _From, S) -> - shutdown(S), + shutdown(service, S), {stop, normal, ok, S}; %% The server currently isn't guaranteed to be dead when the caller %% gets the reply. We deal with this in the call to the server, @@ -681,9 +681,10 @@ upgrade_insert(#state{service = #diameter_service{pid = Pid}} = S) -> %%% --------------------------------------------------------------------------- terminate(Reason, #state{service_name = Name} = S) -> + send_event(Name, stop), ets:delete(?STATE_TABLE, Name), shutdown == Reason %% application shutdown - andalso shutdown(S). + andalso shutdown(application, S). %%% --------------------------------------------------------------------------- %%% # code_change(FromVsn, State, Extra) @@ -766,44 +767,48 @@ mod_state(Alias, ModS) -> %%% # shutdown/2 %%% --------------------------------------------------------------------------- -shutdown(Refs, #state{peerT = PeerT}) -> - ets:foldl(fun(P,ok) -> s(P, Refs), ok end, ok, PeerT). +%% remove_transport: ask watchdogs to terminate their transport. +shutdown(Refs, #state{peerT = PeerT}) + when is_list(Refs) -> + ets:foldl(fun(P,ok) -> sp(P, Refs), ok end, ok, PeerT); -s(#peer{ref = Ref, pid = Pid}, Refs) -> - s(lists:member(Ref, Refs), Pid); - -s(true, Pid) -> - Pid ! {shutdown, self()}; %% 'DOWN' will cleanup as usual -s(false, _) -> - ok. - -%%% --------------------------------------------------------------------------- -%%% # shutdown/1 -%%% --------------------------------------------------------------------------- - -shutdown(#state{peerT = PeerT}) -> +%% application/service shutdown: ask transports to terminate themselves. +shutdown(Reason, #state{peerT = PeerT}) -> %% A transport might not be alive to receive the shutdown request %% but give those that are a chance to shutdown gracefully. - wait(fun st/2, PeerT), + shutdown(conn, Reason, PeerT), %% Kill the watchdogs explicitly in case there was no transport. - wait(fun sw/2, PeerT). + shutdown(peer, Reason, PeerT). -wait(Fun, T) -> - diameter_lib:wait(ets:foldl(Fun, [], T)). +%% sp/2 -st(#peer{op_state = {OS,_}} = P, Acc) -> - st(P#peer{op_state = OS}, Acc); -st(#peer{op_state = ?STATE_UP, conn = Pid}, Acc) -> - Pid ! shutdown, - [Pid | Acc]; -st(#peer{}, Acc) -> - Acc. +sp(#peer{ref = Ref, pid = Pid}, Refs) -> + lists:member(Ref, Refs) + andalso (Pid ! {shutdown, self()}). %% 'DOWN' cleans up -sw(#peer{pid = Pid}, Acc) +%% shutdown/3 + +shutdown(Who, Reason, T) -> + diameter_lib:wait(ets:foldl(fun(X,A) -> shutdown(Who, X, Reason, A) end, + [], + T)). + +shutdown(conn = Who, #peer{op_state = {OS,_}} = P, Reason, Acc) -> + shutdown(Who, P#peer{op_state = OS}, Reason, Acc); + +shutdown(conn, + #peer{pid = Pid, op_state = ?STATE_UP, conn = TPid}, + Reason, + Acc) -> + TPid ! {shutdown, Pid, Reason}, + [TPid | Acc]; + +shutdown(peer, #peer{pid = Pid}, _Reason, Acc) when is_pid(Pid) -> exit(Pid, shutdown), [Pid | Acc]; -sw(#peer{}, Acc) -> + +shutdown(_, #peer{}, _, Acc) -> Acc. %%% --------------------------------------------------------------------------- @@ -857,6 +862,7 @@ i(SvcName) -> lists:foreach(fun(T) -> start_fsm(T,S) end, CL), init_shared(S), + send_event(SvcName, start), S. cfg_acc({SvcName, #diameter_service{applications = Apps} = Rec, Opts}, @@ -2171,15 +2177,13 @@ reply([Msg], Dict, TPid, Fs, Pkt) reply(Msg, Dict, TPid, Fs, Pkt#diameter_packet{errors = []}); %% No errors or a diameter_header/avp list. -reply(Msg, Dict, TPid, Fs, #diameter_packet{errors = Es, - transport_data = TD} - = ReqPkt) +reply(Msg, Dict, TPid, Fs, #diameter_packet{errors = Es} = ReqPkt) when [] == Es; is_record(hd(Msg), diameter_header) -> Pkt = diameter_codec:encode(Dict, make_answer_packet(Msg, ReqPkt)), eval_packet(Pkt, Fs), incr(send, Pkt, Dict, TPid), %% count result codes in sent answers - send(TPid, Pkt#diameter_packet{transport_data = TD}); + send(TPid, Pkt); %% Or not: set Result-Code and Failed-AVP AVP's. reply(Msg, Dict, TPid, Fs, #diameter_packet{errors = [H|_] = Es} = Pkt) -> @@ -2194,23 +2198,36 @@ eval_packet(Pkt, Fs) -> %% make_answer_packet/2 +%% A reply message clears the R and T flags and retains the P flag. +%% The E flag will be set at encode. 6.2 of 3588 requires the same P +%% flag on an answer as on the request. A #diameter_packet{} returned +%% from a handle_request callback can circumvent this by setting its +%% own header values. +make_answer_packet(#diameter_packet{header = Hdr, + msg = Msg, + transport_data = TD}, + #diameter_packet{header = ReqHdr}) -> + Hdr0 = ReqHdr#diameter_header{version = ?DIAMETER_VERSION, + is_request = false, + is_error = undefined, + is_retransmitted = false}, + #diameter_packet{header = fold_record(Hdr0, Hdr), + msg = Msg, + transport_data = TD}; + %% Binaries and header/avp lists are sent as-is. -make_answer_packet(Bin, _) +make_answer_packet(Bin, #diameter_packet{transport_data = TD}) when is_binary(Bin) -> - #diameter_packet{bin = Bin}; -make_answer_packet([#diameter_header{} | _] = Msg, _) -> - #diameter_packet{msg = Msg}; - -%% Otherwise a reply message clears the R and T flags and retains the -%% P flag. The E flag will be set at encode. 6.2 of 3588 requires the -%% same P flag on an answer as on the request. -make_answer_packet(Msg, #diameter_packet{header = ReqHdr}) -> - Hdr = ReqHdr#diameter_header{version = ?DIAMETER_VERSION, - is_request = false, - is_error = undefined, - is_retransmitted = false}, - #diameter_packet{header = Hdr, - msg = Msg}. + #diameter_packet{bin = Bin, + transport_data = TD}; +make_answer_packet([#diameter_header{} | _] = Msg, + #diameter_packet{transport_data = TD}) -> + #diameter_packet{msg = Msg, + transport_data = TD}; + +%% Otherwise, preserve transport_data. +make_answer_packet(Msg, #diameter_packet{transport_data = TD} = Pkt) -> + make_answer_packet(#diameter_packet{msg = Msg, transport_data = TD}, Pkt). %% rc/1 @@ -3214,9 +3231,6 @@ peer_acc(ConnT, Acc, #peer{pid = Pid, | info_conn(ConnT, TPid, WS /= ?WD_DOWN)], Acc). -info_conn(ConnT, [TPid], B) -> - info_conn(ConnT, TPid, B); - info_conn(ConnT, TPid, true) when is_pid(TPid) -> try ets:lookup(ConnT, TPid) of diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl index d814f1afe2..243ad0a986 100644 --- a/lib/diameter/src/base/diameter_watchdog.erl +++ b/lib/diameter/src/base/diameter_watchdog.erl @@ -48,18 +48,19 @@ -record(watchdog, {%% PCB - Peer Control Block; see RFC 3539, Appendix A status = initial :: initial | okay | suspect | down | reopen, - pending = false :: boolean(), + pending = false :: boolean(), %% DWA tw :: 6000..16#FFFFFFFF | {module(), atom(), list()}, %% {M,F,A} -> integer() >= 0 num_dwa = 0 :: -1 | non_neg_integer(), %% number of DWAs received during reopen %% end PCB - parent = self() :: pid(), - transport :: pid() | undefined, + parent = self() :: pid(), %% service process + transport :: pid() | undefined, %% peer_fsm process tref :: reference(), %% reference for current watchdog timer message_data, %% term passed into diameter_service with message sequence :: diameter:sequence(), %% mask - restrict :: {diameter:restriction(), boolean()}}). + restrict :: {diameter:restriction(), boolean()}, + shutdown = false :: boolean()}). %% start/2 %% @@ -168,7 +169,8 @@ handle_info(T, S) -> handle_info(T, upgrade(S)). upgrade(S) -> - #watchdog{} = list_to_tuple(tuple_to_list(S) ++ [?NOMASK, {nodes, true}]). + #watchdog{} = list_to_tuple(tuple_to_list(S) + ++ [?NOMASK, {nodes, true}, false]). event(#watchdog{status = T}, #watchdog{status = T}) -> ok; @@ -225,9 +227,10 @@ transition({shutdown, Pid}, #watchdog{parent = Pid, down = S, %% sanity check stop; transition({shutdown = T, Pid}, #watchdog{parent = Pid, - transport = TPid}) -> + transport = TPid} + = S) -> TPid ! {T, self()}, - ok; + S#watchdog{shutdown = true}; %% Parent process has died, transition({'DOWN', _, process, Pid, _Reason}, @@ -301,7 +304,10 @@ transition({open = P, TPid, _Hosts, T}, transition({'DOWN', _, process, TPid, _}, #watchdog{transport = TPid, - status = initial}) -> + status = S, + shutdown = D}) + when S == initial; + D -> stop; transition({'DOWN', _, process, TPid, _}, @@ -481,6 +487,14 @@ throwaway(S) -> throw({?MODULE, throwaway, S}). %% rcv/2 +%% +%% The lack of Hop-by-Hop and End-to-End Identifiers checks in a +%% received DWA is intentional. The purpose of the message is to +%% demonstrate life but a peer that consistently bungles it by sending +%% the wrong identifiers causes the connection to toggle between OPEN +%% and SUSPECT, with failover and failback as result, despite there +%% being no real problem with connectivity. Thus, relax and accept any +%% incoming DWA as being in response to an outgoing DWR. %% INITIAL Receive DWA Pending = FALSE %% Throwaway() INITIAL diff --git a/lib/diameter/test/diameter_compiler_SUITE.erl b/lib/diameter/test/diameter_compiler_SUITE.erl index 4b792b5426..79bf9d32db 100644 --- a/lib/diameter/test/diameter_compiler_SUITE.erl +++ b/lib/diameter/test/diameter_compiler_SUITE.erl @@ -31,8 +31,8 @@ %% testcases -export([format/1, format/2, replace/1, replace/2, - generate/1, generate/4, generate/0, - examples/1, examples/0]). + generate/1, generate/4, + examples/1]). -export([dict/0]). %% fake dictionary module @@ -339,7 +339,7 @@ %% =========================================================================== suite() -> - [{timetrap, {minutes, 2}}]. + [{timetrap, {minutes, 10}}]. all() -> [format, @@ -407,9 +407,6 @@ re({RE, Repl}, Bin) -> %% %% Ensure success when generating code and compiling. -generate() -> - [{timetrap, {seconds, 2*length(?REPLACE)}}]. - generate(Config) -> Bin = proplists:get_value(base, Config), Rs = lists:zip(?REPLACE, lists:seq(1, length(?REPLACE))), @@ -436,9 +433,6 @@ generate(Mods, Bin, N, Mode) -> %% %% Compile dictionaries extracted from various standards. -examples() -> - [{timetrap, {seconds, 3*length(?EXAMPLES)}}]. - examples(_Config) -> Dir = filename:join([code:lib_dir(diameter, examples), "dict"]), [D || D <- ?EXAMPLES, _ <- [examples(?S(D), Dir)]]. diff --git a/lib/diameter/test/diameter_dpr_SUITE.erl b/lib/diameter/test/diameter_dpr_SUITE.erl new file mode 100644 index 0000000000..9252650bf7 --- /dev/null +++ b/lib/diameter/test/diameter_dpr_SUITE.erl @@ -0,0 +1,196 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2012. 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 +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%% Tests of the disconnect_cb configuration. +%% + +-module(diameter_dpr_SUITE). + +-export([suite/0, + all/0, + groups/0, + init_per_group/2, + end_per_group/2]). + +%% testcases +-export([start/1, + connect/1, + remove_transport/1, + stop_service/1, + check/1, + stop/1]). + +%% disconnect_cb +-export([disconnect/5]). + +-include("diameter.hrl"). + +%% =========================================================================== + +-define(util, diameter_util). + +-define(ADDR, {127,0,0,1}). + +-define(CLIENT, "CLIENT"). +-define(SERVER, "SERVER"). + +-define(DICT_COMMON, ?DIAMETER_DICT_COMMON). +-define(APP_ID, ?DICT_COMMON:id()). + +%% Config for diameter:start_service/2. +-define(SERVICE(Host), + [{'Origin-Host', Host}, + {'Origin-Realm', "erlang.org"}, + {'Host-IP-Address', [?ADDR]}, + {'Vendor-Id', hd(Host)}, %% match this in disconnect/5 + {'Product-Name', "OTP/diameter"}, + {'Acct-Application-Id', [?APP_ID]}, + {restrict_connections, false}, + {application, [{dictionary, ?DICT_COMMON}, + {module, #diameter_callback{_ = false}}]}]). + +%% Disconnect reasons that diameter passes as the first argument of a +%% function configured as disconnect_cb. +-define(REASONS, [transport, service, application]). + +%% Valid values for Disconnect-Cause. +-define(CAUSES, [0, rebooting, 1, busy, 2, goaway]). + +%% Establish one client connection for element of this list, +%% configured with disconnect/5 as disconnect_cb and returning the +%% specified value. +-define(RETURNS, + [[close, {dpr, [{cause, invalid}]}], [ignore, close], []] + ++ [[{dpr, [{timeout, 5000}, {cause, T}]}] || T <- ?CAUSES]). + +%% =========================================================================== + +suite() -> + [{timetrap, {seconds, 60}}]. + +all() -> + [{group, R} || R <- ?REASONS]. + +%% The group determines how transports are terminated: by remove_transport, +%% stop_service or application stop. +groups() -> + Ts = tc(), + [{R, [], Ts} || R <- ?REASONS]. + +init_per_group(Name, Config) -> + [{group, Name} | Config]. + +end_per_group(_, _) -> + ok. + +tc() -> + [start, connect, remove_transport, stop_service, check, stop]. + +%% =========================================================================== +%% start/stop testcases + +start(_Config) -> + ok = diameter:start(), + ok = diameter:start_service(?SERVER, ?SERVICE(?SERVER)), + ok = diameter:start_service(?CLIENT, ?SERVICE(?CLIENT)). + +connect(Config) -> + Pid = spawn(fun init/0), %% process for disconnect_cb to bang + Grp = group(Config), + LRef = ?util:listen(?SERVER, tcp), + Refs = [?util:connect(?CLIENT, tcp, LRef, opts(RCs, {Grp, Pid})) + || RCs <- ?RETURNS], + ?util:write_priv(Config, config, [Pid | Refs]). + +%% Remove all the client transports only in the transport group. +remove_transport(Config) -> + transport == group(Config) + andalso (ok = diameter:remove_transport(?CLIENT, true)). + +%% Stop the service only in the service group. +stop_service(Config) -> + service == group(Config) + andalso (ok = diameter:stop_service(?CLIENT)). + +%% Check for callbacks and stop the service. (Not the other way around +%% for the timing reason explained below.) +check(Config) -> + Grp = group(Config), + [Pid | Refs] = ?util:read_priv(Config, config), + Pid ! self(), %% ask for dictionary + Dict = receive {Pid, D} -> D end, %% get it + check(Refs, ?RETURNS, Grp, Dict). %% check for callbacks + +stop(_Config) -> + ok = diameter:stop(). + +%% Whether or not there are callbacks after diameter:stop() depends on +%% timing as long as the server runs on the same node: a server +%% transport could close the connection before the client has chance +%% to apply its callback. Therefore, just check that there haven't +%% been any callbacks yet. +check(_, _, application, Dict) -> + [] = dict:to_list(Dict); + +check([], [], _, _) -> + ok; + +check([Ref | Refs], CBs, Grp, Dict) -> + check1(Ref, hd(CBs), Grp, Dict), + check(Refs, tl(CBs), Grp, Dict). + +check1(Ref, [ignore | RCs], Reason, Dict) -> + check1(Ref, RCs, Reason, Dict); + +check1(Ref, [_|_], Reason, Dict) -> + {ok, Reason} = dict:find(Ref, Dict); %% callback with expected reason + +check1(Ref, [], _, Dict) -> + error = dict:find(Ref, Dict). %% no callback + +%% ---------------------------------------- + +group(Config) -> + {group, Grp} = lists:keyfind(group, 1, Config), + Grp. + +%% Configure the callback with the group name (= disconnect reason) as +%% extra argument. +opts(RCs, T) -> + [{disconnect_cb, {?MODULE, disconnect, [T, RC]}} || RC <- RCs]. + +%% Match the group name with the disconnect reason to ensure the +%% callback is being called as expected. +disconnect(Reason, Ref, Peer, {Reason, Pid}, RC) -> + io:format("disconnect: ~p ~p~n", [Ref, Reason]), + {_, #diameter_caps{vendor_id = {$C,$S}}} = Peer, + Pid ! {Reason, Ref}, + RC. + +init() -> + exit(recv(dict:new())). + +recv(Dict) -> + receive + Pid when is_pid(Pid) -> + Pid ! {self(), Dict}; + {Reason, Ref} -> + recv(dict:store(Ref, Reason, Dict)) + end. diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl index 5744ff0307..c157b0e304 100644 --- a/lib/diameter/test/diameter_traffic_SUITE.erl +++ b/lib/diameter/test/diameter_traffic_SUITE.erl @@ -38,6 +38,7 @@ result_codes/1, send_ok/1, send_nok/1, + send_bad_answer/1, send_arbitrary/1, send_unknown/1, send_unknown_mandatory/1, @@ -182,14 +183,14 @@ suite() -> all() -> [start, start_services, add_transports, result_codes] - ++ [{group, name([E,C]), P} || E <- ?ENCODINGS, - C <- ?CONNECTIONS, - P <- [[], [parallel]]] + ++ [{group, ?util:name([E,C]), P} || E <- ?ENCODINGS, + C <- ?CONNECTIONS, + P <- [[], [parallel]]] ++ [remove_transports, stop_services, stop]. groups() -> Ts = tc(), - [{name([E,C]), [], Ts} || E <- ?ENCODINGS, C <- ?CONNECTIONS]. + [{?util:name([E,C]), [], Ts} || E <- ?ENCODINGS, C <- ?CONNECTIONS]. init_per_group(Name, Config) -> [{group, Name} | Config]. @@ -208,6 +209,7 @@ end_per_testcase(_, _) -> tc() -> [send_ok, send_nok, + send_bad_answer, send_arbitrary, send_unknown, send_unknown_mandatory, @@ -308,6 +310,14 @@ send_nok(Config) -> #'diameter_base_answer-message'{'Result-Code' = ?INVALID_AVP_BITS} = call(Config, Req). +%% Send an accounting ACR that the server tries to answer with an +%% inappropriate header, resulting in no answer being sent and the +%% request timing out. +send_bad_answer(Config) -> + Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, + {'Accounting-Record-Number', 2}], + {error, timeout} = call(Config, Req). + %% Send an ASR with an arbitrary AVP and expect success and the same %% AVP in the reply. send_arbitrary(Config) -> @@ -549,7 +559,7 @@ call(Config, Req) -> call(Config, Req, Opts) -> Name = proplists:get_value(testcase, Config), - [Encoding, Client] = name(proplists:get_value(group, Config)), + [Encoding, Client] = ?util:name(proplists:get_value(group, Config)), diameter:call(?CLIENT, dict(Req), req(Req, Encoding), @@ -589,17 +599,6 @@ set(Dict, E, FV, Rec) set(_, _, _, Rec) -> Rec. -%% Contruct and deconstruct names to work around group names being -%% restricted to atoms. - -name(Names) - when is_list(Names) -> - ?A(string:join([?L(A) || A <- Names], ",")); - -name(A) - when is_atom(A) -> - [?A(S) || S <- string:tokens(?L(A), ",")]. - %% =========================================================================== %% diameter callbacks @@ -770,6 +769,21 @@ request(#diameter_base_accounting_ACR{'Accounting-Record-Number' = 0}, request(#diameter_base_accounting_ACR{'Session-Id' = SId, 'Accounting-Record-Type' = RT, + 'Accounting-Record-Number' = 2 = RN}, + #diameter_caps{origin_host = {OH, _}, + origin_realm = {OR, _}}) -> + Ans = ['ACA', {'Result-Code', ?SUCCESS}, + {'Session-Id', SId}, + {'Origin-Host', OH}, + {'Origin-Realm', OR}, + {'Accounting-Record-Type', RT}, + {'Accounting-Record-Number', RN}], + + {reply, #diameter_packet{header = #diameter_header{is_error = true},%% not + msg = Ans}}; + +request(#diameter_base_accounting_ACR{'Session-Id' = SId, + 'Accounting-Record-Type' = RT, 'Accounting-Record-Number' = RN}, #diameter_caps{origin_host = {OH, _}, origin_realm = {OR, _}}) -> diff --git a/lib/diameter/test/diameter_util.erl b/lib/diameter/test/diameter_util.erl index 890d24f6f8..5af4ad9ba5 100644 --- a/lib/diameter/test/diameter_util.erl +++ b/lib/diameter/test/diameter_util.erl @@ -24,7 +24,8 @@ %% %% generic --export([consult/2, +-export([name/1, + consult/2, run/1, fold/3, foldl/3, @@ -45,6 +46,21 @@ -define(L, atom_to_list). + +%% --------------------------------------------------------------------------- +%% name/2 +%% +%% Contruct and deconstruct lists of atoms as atoms to work around +%% group names in common_test being restricted to atoms. + +name(Names) + when is_list(Names) -> + list_to_atom(string:join([atom_to_list(A) || A <- Names], ",")); + +name(A) + when is_atom(A) -> + [list_to_atom(S) || S <- string:tokens(atom_to_list(A), ",")]. + %% --------------------------------------------------------------------------- %% consult/2 %% diff --git a/lib/diameter/test/modules.mk b/lib/diameter/test/modules.mk index 7f163536fb..5898e125ae 100644 --- a/lib/diameter/test/modules.mk +++ b/lib/diameter/test/modules.mk @@ -2,7 +2,7 @@ # %CopyrightBegin% # -# Copyright Ericsson AB 2010-2011. All Rights Reserved. +# Copyright Ericsson AB 2010-2012. 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 @@ -39,7 +39,8 @@ MODULES = \ diameter_traffic_SUITE \ diameter_relay_SUITE \ diameter_tls_SUITE \ - diameter_failover_SUITE + diameter_failover_SUITE \ + diameter_dpr_SUITE HRL_FILES = \ diameter_ct.hrl diff --git a/lib/diameter/vsn.mk b/lib/diameter/vsn.mk index 48e6596e72..c9f74ffcec 100644 --- a/lib/diameter/vsn.mk +++ b/lib/diameter/vsn.mk @@ -18,7 +18,7 @@ # %CopyrightEnd% APPLICATION = diameter -DIAMETER_VSN = 1.2 +DIAMETER_VSN = 1.3 PRE_VSN = APP_VSN = "$(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN)" diff --git a/lib/erl_docgen/priv/xsl/Makefile b/lib/erl_docgen/priv/xsl/Makefile index 1510387d72..58589672b8 100644 --- a/lib/erl_docgen/priv/xsl/Makefile +++ b/lib/erl_docgen/priv/xsl/Makefile @@ -42,7 +42,8 @@ XSL_FILES = \ db_pdf_params.xsl \ db_html.xsl \ db_html_params.xsl \ - db_man.xsl + db_man.xsl \ + db_eix.xsl # ---------------------------------------------------- diff --git a/lib/erl_docgen/priv/xsl/db_html.xsl b/lib/erl_docgen/priv/xsl/db_html.xsl index 4bc5abb364..ab5f24c406 100644 --- a/lib/erl_docgen/priv/xsl/db_html.xsl +++ b/lib/erl_docgen/priv/xsl/db_html.xsl @@ -578,8 +578,22 @@ <xsl:param name="curModule"/> <html> <head> - <link rel="stylesheet" href="{$topdocdir}/otp_doc.css" type="text/css"/> - <title>Erlang -- <xsl:value-of select="header/title"/></title> + <xsl:choose> + <xsl:when test="string-length($stylesheet) > 0"> + <link rel="stylesheet" href="{$topdocdir}/{$stylesheet}" type="text/css"/> + </xsl:when> + <xsl:otherwise> + <link rel="stylesheet" href="{$topdocdir}/otp_doc.css" type="text/css"/> + </xsl:otherwise> + </xsl:choose> + <xsl:choose> + <xsl:when test="string-length($winprefix) > 0"> + <title><xsl:value-of select="$winprefix"/> -- <xsl:value-of select="header/title"/></title> + </xsl:when> + <xsl:otherwise> + <title>Erlang -- <xsl:value-of select="header/title"/></title> + </xsl:otherwise> + </xsl:choose> </head> <body bgcolor="white" text="#000000" link="#0000ff" vlink="#ff00ff" alink="#ff0000"> @@ -719,7 +733,14 @@ <xsl:template name="menu_top"> - <img alt="Erlang logo" src="{$topdocdir}/erlang-logo.png"/> + <xsl:choose> + <xsl:when test="string-length($logo) > 0"> + <img alt="Erlang logo" src="{$topdocdir}/{$logo}"/> + </xsl:when> + <xsl:otherwise> + <img alt="Erlang logo" src="{$topdocdir}/erlang-logo.png"/> + </xsl:otherwise> + </xsl:choose> <br/> <small> <xsl:if test="boolean(/book/parts/part)"> @@ -731,7 +752,14 @@ <xsl:if test="boolean(/book/releasenotes)"> <a href="release_notes.html">Release Notes</a><br/> </xsl:if> - <a href="{$pdfdir}/{$appname}-{$appver}.pdf">PDF</a><br/> + <xsl:choose> + <xsl:when test="string-length($pdfname) > 0"> + <a href="{$pdfdir}/{$pdfname}.pdf">PDF</a><br/> + </xsl:when> + <xsl:otherwise> + <a href="{$pdfdir}/{$appname}-{$appver}.pdf">PDF</a><br/> + </xsl:otherwise> + </xsl:choose> <a href="{$topdocdir}/index.html">Top</a> </small> </xsl:template> diff --git a/lib/erl_docgen/priv/xsl/db_pdf.xsl b/lib/erl_docgen/priv/xsl/db_pdf.xsl index da96052462..7de5af2a49 100644 --- a/lib/erl_docgen/priv/xsl/db_pdf.xsl +++ b/lib/erl_docgen/priv/xsl/db_pdf.xsl @@ -650,7 +650,7 @@ <fo:flow flow-name="xsl-region-body"> <fo:block xsl:use-attribute-sets="cover.logo"> - <fo:external-graphic src="{$docgen}/priv/images/erlang-logo.gif"/> + <fo:external-graphic src="{$logo}"/> </fo:block> <fo:block xsl:use-attribute-sets="cover.title" id="cover-page"> <xsl:apply-templates/> diff --git a/lib/erl_docgen/priv/xsl/db_pdf_params.xsl b/lib/erl_docgen/priv/xsl/db_pdf_params.xsl index 4d9c08d0c3..4e61f1f476 100644 --- a/lib/erl_docgen/priv/xsl/db_pdf_params.xsl +++ b/lib/erl_docgen/priv/xsl/db_pdf_params.xsl @@ -87,7 +87,7 @@ <xsl:attribute-set name="cover.title"> <xsl:attribute name="border-before-style">solid</xsl:attribute> <xsl:attribute name="border-before-width">10pt</xsl:attribute> - <xsl:attribute name="border-color">#960003</xsl:attribute> + <xsl:attribute name="border-color"><xsl:value-of select="$pdfcolor"/></xsl:attribute> <xsl:attribute name="font-size">2.3em</xsl:attribute> <xsl:attribute name="padding-before">0.5em</xsl:attribute> <xsl:attribute name="text-align">end</xsl:attribute> @@ -101,7 +101,7 @@ <xsl:attribute-set name="cover.inner.copyright"> <xsl:attribute name="border-before-style">solid</xsl:attribute> <xsl:attribute name="border-before-width">1pt</xsl:attribute> - <xsl:attribute name="border-color">#960003</xsl:attribute> + <xsl:attribute name="border-color"><xsl:value-of select="$pdfcolor"/></xsl:attribute> <xsl:attribute name="font-weight">bold</xsl:attribute> <xsl:attribute name="padding-before">0.5em</xsl:attribute> <xsl:attribute name="space-before">200mm</xsl:attribute> @@ -160,7 +160,7 @@ <xsl:attribute-set name="h1"> <xsl:attribute name="border-after-style">solid</xsl:attribute> <xsl:attribute name="border-after-width">1pt</xsl:attribute> - <xsl:attribute name="border-color">#960003</xsl:attribute> + <xsl:attribute name="border-color"><xsl:value-of select="$pdfcolor"/></xsl:attribute> <xsl:attribute name="break-before">page</xsl:attribute> <xsl:attribute name="font-family">sans-serif</xsl:attribute> <xsl:attribute name="font-size">1.83em</xsl:attribute> @@ -226,7 +226,7 @@ <xsl:attribute-set name="page-header"> <xsl:attribute name="border-after-style">solid</xsl:attribute> <xsl:attribute name="border-after-width">2pt</xsl:attribute> - <xsl:attribute name="border-color">#960003</xsl:attribute> + <xsl:attribute name="border-color"><xsl:value-of select="$pdfcolor"/></xsl:attribute> <xsl:attribute name="font-family">sans-serif</xsl:attribute> <xsl:attribute name="font-size">0.9em</xsl:attribute> <xsl:attribute name="font-weight">bold</xsl:attribute> diff --git a/lib/erl_docgen/vsn.mk b/lib/erl_docgen/vsn.mk index b05df254a6..2599dc0ff7 100644 --- a/lib/erl_docgen/vsn.mk +++ b/lib/erl_docgen/vsn.mk @@ -1,2 +1,2 @@ -ERL_DOCGEN_VSN = 0.3.2 +ERL_DOCGEN_VSN = 0.3.3 diff --git a/lib/inets/doc/src/httpd.xml b/lib/inets/doc/src/httpd.xml index 7e21229fcf..8497d91549 100644 --- a/lib/inets/doc/src/httpd.xml +++ b/lib/inets/doc/src/httpd.xml @@ -178,7 +178,13 @@ <p>Note that this option is only used when the option <c>socket_type</c> has the value <c>ip_comm</c>. </p> </item> - + <marker id="prop_minimum_bytes_per_second"></marker> + <tag>{minimum_bytes_per_second, integer()}</tag> + <item> + <p>If given, sets a minimum bytes per second value for connections.</p> + <p>If the value is not reached, the socket will close for that connection.</p> + <p>The option is good for reducing the risk of "slow dos" attacks.</p> + </item> </taglist> <marker id="props_api_modules"></marker> diff --git a/lib/inets/src/http_server/httpd_conf.erl b/lib/inets/src/http_server/httpd_conf.erl index 747118431e..a97bbd9b25 100644 --- a/lib/inets/src/http_server/httpd_conf.erl +++ b/lib/inets/src/http_server/httpd_conf.erl @@ -483,7 +483,7 @@ validate_properties(Properties) -> case mandatory_properties(Properties) of ok -> %% Second, check that property dependency are ok - {ok, validate_properties2(Properties)}; + {ok, check_minimum_bytes_per_second(validate_properties2(Properties))}; Error -> throw(Error) end. @@ -522,7 +522,18 @@ validate_properties2(Properties) -> throw(Error) end end. - +check_minimum_bytes_per_second(Properties) -> + case proplists:get_value(minimum_bytes_per_second, Properties, false) of + false -> + Properties; + Nr -> + case is_integer(Nr) of + false -> + throw({error, {minimum_bytes_per_second, is_not_integer}}); + _ -> + Properties + end + end. mandatory_properties(ConfigList) -> a_must(ConfigList, [server_name, port, server_root, document_root]). diff --git a/lib/inets/src/http_server/httpd_request_handler.erl b/lib/inets/src/http_server/httpd_request_handler.erl index b62c10bbc7..5e0bd39cb3 100644 --- a/lib/inets/src/http_server/httpd_request_handler.erl +++ b/lib/inets/src/http_server/httpd_request_handler.erl @@ -44,7 +44,9 @@ timeout, %% infinity | integer() > 0 timer, %% ref() - Request timer headers, %% #http_request_h{} - body %% binary() + body, %% binary() + data, %% The total data received in bits, checked after 10s + byte_limit %% Bit limit per second before kick out }). %%==================================================================== @@ -98,7 +100,6 @@ init([Manager, ConfigDB, AcceptTimeout]) -> [{socket_type, SocketType}, {socket, Socket}]), TimeOut = httpd_util:lookup(ConfigDB, keep_alive_timeout, 150000), - Then = erlang:now(), ?hdrd("negotiate", []), @@ -139,12 +140,11 @@ continue_init(Manager, ConfigDB, SocketType, Socket, TimeOut) -> mfa = MFA}, ?hdrt("activate request timeout", []), - NewState = activate_request_timeout(State), ?hdrt("set socket options (binary, packet & active)", []), http_transport:setopts(SocketType, Socket, [binary, {packet, 0}, {active, once}]), - + NewState = data_receive_counter(activate_request_timeout(State), httpd_util:lookup(ConfigDB, minimum_bytes_per_second, false)), ?hdrt("init done", []), gen_server:enter_loop(?MODULE, [], NewState). @@ -205,16 +205,25 @@ handle_info({Proto, Socket, Data}, ?hdrd("received data", [{data, Data}, {proto, Proto}, {socket, Socket}, {socket_type, SockType}, {mfa, MFA}]), - + %% case (catch Module:Function([Data | Args])) of PROCESSED = (catch Module:Function([Data | Args])), - + NewDataSize = case State#state.byte_limit of + undefined -> + undefined; + _ -> + State#state.data + byte_size(Data) + end, ?hdrt("data processed", [{processing_result, PROCESSED}]), - case PROCESSED of {ok, Result} -> ?hdrd("data processed", [{result, Result}]), - NewState = cancel_request_timeout(State), + NewState = case NewDataSize of + undefined -> + cancel_request_timeout(State); + _ -> + set_new_data_size(cancel_request_timeout(State), NewDataSize) + end, handle_http_msg(Result, NewState); {error, {uri_too_long, MaxSize}, Version} -> @@ -239,7 +248,12 @@ handle_info({Proto, Socket, Data}, NewMFA -> ?hdrd("data processed - reactivate socket", [{new_mfa, NewMFA}]), http_transport:setopts(SockType, Socket, [{active, once}]), - {noreply, State#state{mfa = NewMFA}} + case NewDataSize of + undefined -> + {noreply, State#state{mfa = NewMFA}}; + _ -> + {noreply, State#state{mfa = NewMFA, data = NewDataSize}} + end end; %% Error cases @@ -263,7 +277,22 @@ handle_info(timeout, #state{mod = ModData} = State) -> error_log("The client did not send the whole request before the " "server side timeout", ModData), {stop, normal, State#state{response_sent = true}}; - +handle_info(check_data_first, #state{data = Data, byte_limit = Byte_Limit} = State) -> + case Data >= (Byte_Limit*3) of + true -> + erlang:send_after(1000, self(), check_data), + {noreply, State#state{data = 0}}; + _ -> + {stop, normal, State#state{response_sent = true}} + end; +handle_info(check_data, #state{data = Data, byte_limit = Byte_Limit} = State) -> + case Data >= Byte_Limit of + true -> + erlang:send_after(1000, self(), check_data), + {noreply, State#state{data = 0}}; + _ -> + {stop, normal, State#state{response_sent = true}} + end; %% Default case handle_info(Info, #state{mod = ModData} = State) -> Error = lists:flatten( @@ -311,6 +340,8 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +set_new_data_size(State, NewData) -> + State#state{data = NewData}. await_socket_ownership_transfer(AcceptTimeout) -> receive {socket_ownership_transfered, SocketType, Socket} -> @@ -603,7 +634,14 @@ activate_request_timeout(#state{timeout = Time} = State) -> ?hdrt("activate request timeout", [{time, Time}]), Ref = erlang:send_after(Time, self(), timeout), State#state{timer = Ref}. - +data_receive_counter(State, Byte_limit) -> + case Byte_limit of + false -> + State#state{data = 0}; + Nr -> + erlang:send_after(3000, self(), check_data_first), + State#state{data = 0, byte_limit = Nr} + end. cancel_request_timeout(#state{timer = undefined} = State) -> State; cancel_request_timeout(#state{timer = Timer} = State) -> diff --git a/lib/inets/src/inets_app/inets.appup.src b/lib/inets/src/inets_app/inets.appup.src index 2adb2a0fc8..ffd0ed622f 100644 --- a/lib/inets/src/inets_app/inets.appup.src +++ b/lib/inets/src/inets_app/inets.appup.src @@ -18,8 +18,14 @@ {"%VSN%", [ + {"5.9.1", + [ + {load_module, httpd_request_handler, soft_purge, soft_purge, []} + ] + }, {"5.9", [ + {load_module, httpd_request_handler, soft_purge, soft_purge, []}, {load_module, tftp, soft_purge, soft_purge, [inets_service]}, {load_module, inets_service, soft_purge, soft_purge, []}, {load_module, httpc, soft_purge, soft_purge, [httpc_manager]}, @@ -29,6 +35,7 @@ }, {"5.8.1", [ + {load_module, httpd_request_handler, soft_purge, soft_purge, []}, {load_module, tftp, soft_purge, soft_purge, [inets_service]}, {load_module, inets_service, soft_purge, soft_purge, []}, @@ -64,8 +71,14 @@ } ], [ + {"5.9.1", + [ + {load_module, httpd_request_handler, soft_purge, soft_purge, []} + ] + }, {"5.9", [ + {load_module, httpd_request_handler, soft_purge, soft_purge, []}, {load_module, tftp, soft_purge, soft_purge, [inets_service]}, {load_module, inets_service, soft_purge, soft_purge, []}, {load_module, httpc, soft_purge, soft_purge, [httpc_manager]}, @@ -75,6 +88,7 @@ }, {"5.8.1", [ + {load_module, httpd_request_handler, soft_purge, soft_purge, []}, {load_module, tftp, soft_purge, soft_purge, [inets_service]}, {load_module, inets_service, soft_purge, soft_purge, []}, diff --git a/lib/inets/test/httpd_basic_SUITE.erl b/lib/inets/test/httpd_basic_SUITE.erl index 7a476ea14a..523cf9d38c 100644 --- a/lib/inets/test/httpd_basic_SUITE.erl +++ b/lib/inets/test/httpd_basic_SUITE.erl @@ -34,7 +34,8 @@ all() -> [ uri_too_long_414, header_too_long_413, - escaped_url_in_error_body + escaped_url_in_error_body, + slowdose ]. groups() -> @@ -278,7 +279,18 @@ escaped_url_in_error_body(Config) when is_list(Config) -> inets:stop(httpd, Pid), tsp("escaped_url_in_error_body -> done"), ok. - +slowdose(doc) -> + ["Testing minimum bytes per second option"]; +slowdose(Config) when is_list(Config) -> + HttpdConf = ?config(httpd_conf, Config), + {ok, Pid} = inets:start(httpd, [{port, 0}, {minimum_bytes_per_second, 200}|HttpdConf]), + Info = httpd:info(Pid), + Port = proplists:get_value(port, Info), + {ok, Socket} = gen_tcp:connect("localhost", Port, []), + receive + after 6000 -> + {error, closed} = gen_tcp:send(Socket, "Hey") + end. find_URL_path([]) -> ""; find_URL_path(["URL", URL | _]) -> diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index 949eceea7f..0c7cb5e7c2 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -18,7 +18,7 @@ # %CopyrightEnd% APPLICATION = inets -INETS_VSN = 5.9.1 +INETS_VSN = 5.9.2 PRE_VSN = APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java index deac528133..b985f8aa50 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java @@ -358,7 +358,7 @@ public class OtpEpmd { } public static String[] lookupNames() throws IOException { - return lookupNames(InetAddress.getLocalHost()); + return lookupNames(InetAddress.getByName(null)); } public static String[] lookupNames(final InetAddress address) diff --git a/lib/public_key/asn1/AuthenticationFramework.asn1 b/lib/public_key/asn1/AuthenticationFramework.asn1 new file mode 100644 index 0000000000..3754486473 --- /dev/null +++ b/lib/public_key/asn1/AuthenticationFramework.asn1 @@ -0,0 +1,367 @@ +AuthenticationFramework {joint-iso-itu-t ds(5) module(1) + authenticationFramework(7) 6} DEFINITIONS ::= +BEGIN + +-- EXPORTS All +-- The types and values defined in this module are exported for use in the other ASN.1 modules contained +-- within the Directory Specifications, and for the use of other applications which will use them to access +-- Directory services. Other applications may use them for their own purposes, but this will not constrain +-- extensions and modifications needed to maintain or improve the Directory service. +IMPORTS + id-at, id-nf, id-oc, informationFramework, selectedAttributeTypes, + basicAccessControl, certificateExtensions + FROM UsefulDefinitions {joint-iso-itu-t ds(5) module(1) + usefulDefinitions(0) 6} + Name, ATTRIBUTE, OBJECT-CLASS, NAME-FORM, top + FROM InformationFramework informationFramework + UniqueIdentifier, octetStringMatch, commonName, UnboundedDirectoryString + FROM SelectedAttributeTypes selectedAttributeTypes + certificateExactMatch, certificatePairExactMatch, certificateListExactMatch, + KeyUsage, GeneralNames, CertificatePoliciesSyntax, + algorithmIdentifierMatch, CertPolicyId + FROM CertificateExtensions certificateExtensions; + +-- parameterized types +ENCRYPTED{ToBeEnciphered} ::= + BIT STRING + (CONSTRAINED BY { + -- shall be the result of applying an encipherment procedure + -- to the BER-encoded octets of a value of --ToBeEnciphered}) + +HASH{ToBeHashed} ::= SEQUENCE { + algorithmIdentifier AlgorithmIdentifier{{SupportedAlgorithms}}, + hashValue + BIT STRING + (CONSTRAINED BY { + -- shall be the result of applying a hashing procedure to the DER-encoded octets + -- of a value of -- ToBeHashed}) +} + +ENCRYPTED-HASH{ToBeSigned} ::= + BIT STRING + (CONSTRAINED BY { + -- shall be the result of applying a hashing procedure to the DER-encoded (see 6.1) octets + -- of a value of --ToBeSigned -- and then applying an encipherment procedure to those octets --}) + +SIGNATURE{ToBeSigned} ::= SEQUENCE { + algorithmIdentifier AlgorithmIdentifier{{SupportedAlgorithms}}, + encrypted ENCRYPTED-HASH{ToBeSigned} +} + +SIGNED{ToBeSigned} ::= SEQUENCE { + toBeSigned ToBeSigned, + COMPONENTS OF SIGNATURE{ToBeSigned} +} + +-- public-key certificate definition +Certificate ::= SIGNED{CertificateContent} + +CertificateContent ::= SEQUENCE { + version [0] Version DEFAULT v1, + serialNumber CertificateSerialNumber, + signature AlgorithmIdentifier{{SupportedAlgorithms}}, + issuer Name, + validity Validity, + subject Name, + subjectPublicKeyInfo SubjectPublicKeyInfo, + issuerUniqueIdentifier [1] IMPLICIT UniqueIdentifier OPTIONAL, + -- if present, version shall be v2 or v3 + subjectUniqueIdentifier [2] IMPLICIT UniqueIdentifier OPTIONAL, + -- if present, version shall be v2 or v3 + extensions [3] Extensions OPTIONAL + -- If present, version shall be v3 +} + +Version ::= INTEGER {v1(0), v2(1), v3(2)} + +CertificateSerialNumber ::= INTEGER + +AlgorithmIdentifier{ALGORITHM:SupportedAlgorithms} ::= SEQUENCE { + algorithm ALGORITHM.&id({SupportedAlgorithms}), + parameters ALGORITHM.&Type({SupportedAlgorithms}{@algorithm}) OPTIONAL +} + +-- Definition of the following information object set is deferred, perhaps to standardized +-- profiles or to protocol implementation conformance statements. The set is required to +-- specify a table constraint on the parameters component of AlgorithmIdentifier. +SupportedAlgorithms ALGORITHM ::= + {...} + +Validity ::= SEQUENCE {notBefore Time, + notAfter Time +} + +SubjectPublicKeyInfo ::= SEQUENCE { + algorithm AlgorithmIdentifier{{SupportedAlgorithms}}, + subjectPublicKey BIT STRING +} + +Time ::= CHOICE {utcTime UTCTime, + generalizedTime GeneralizedTime +} + +Extensions ::= SEQUENCE OF Extension + +-- For those extensions where ordering of individual extensions within the SEQUENCE is significant, the +-- specification of those individual extensions shall include the rules for the significance of the order therein +Extension ::= SEQUENCE { + extnId EXTENSION.&id({ExtensionSet}), + critical BOOLEAN DEFAULT FALSE, + extnValue + OCTET STRING + (CONTAINING EXTENSION.&ExtnType({ExtensionSet}{@extnId}) + ENCODED BY + der) +} + +der OBJECT IDENTIFIER ::= + {joint-iso-itu-t asn1(1) ber-derived(2) distinguished-encoding(1)} + +ExtensionSet EXTENSION ::= + {...} + +EXTENSION ::= CLASS {&id OBJECT IDENTIFIER UNIQUE, + &ExtnType +}WITH SYNTAX {SYNTAX &ExtnType + IDENTIFIED BY &id +} + +ALGORITHM ::= CLASS {&Type OPTIONAL, + &id OBJECT IDENTIFIER UNIQUE +}WITH SYNTAX {[&Type] + IDENTIFIED BY &id +} + +-- other PKI certificate constructs +Certificates ::= SEQUENCE { + userCertificate Certificate, + certificationPath ForwardCertificationPath OPTIONAL +} + +CertificationPath ::= SEQUENCE { + userCertificate Certificate, + theCACertificates SEQUENCE OF CertificatePair OPTIONAL +} + +ForwardCertificationPath ::= SEQUENCE OF CrossCertificates + +CrossCertificates ::= SET OF Certificate + +PkiPath ::= SEQUENCE OF Certificate + +-- certificate revocation list (CRL) +CertificateList ::= + SIGNED{CertificateListContent} + +CertificateListContent ::= SEQUENCE { + version Version OPTIONAL, + -- if present, version shall be v2 + signature AlgorithmIdentifier{{SupportedAlgorithms}}, + issuer Name, + thisUpdate Time, + nextUpdate Time OPTIONAL, + revokedCertificates + SEQUENCE OF + SEQUENCE {serialNumber CertificateSerialNumber, + revocationDate Time, + crlEntryExtensions Extensions OPTIONAL} OPTIONAL, + crlExtensions [0] Extensions OPTIONAL +} + +-- PKI object classes +pkiUser OBJECT-CLASS ::= { + SUBCLASS OF {top} + KIND auxiliary + MAY CONTAIN {userCertificate} + ID id-oc-pkiUser +} + +pkiCA OBJECT-CLASS ::= { + SUBCLASS OF {top} + KIND auxiliary + MAY CONTAIN + {cACertificate | certificateRevocationList | authorityRevocationList | + crossCertificatePair} + ID id-oc-pkiCA +} + +cRLDistributionPoint OBJECT-CLASS ::= { + SUBCLASS OF {top} + KIND structural + MUST CONTAIN {commonName} + MAY CONTAIN + {certificateRevocationList | authorityRevocationList | deltaRevocationList} + ID id-oc-cRLDistributionPoint +} + +cRLDistPtNameForm NAME-FORM ::= { + NAMES cRLDistributionPoint + WITH ATTRIBUTES {commonName} + ID id-nf-cRLDistPtNameForm +} + +deltaCRL OBJECT-CLASS ::= { + SUBCLASS OF {top} + KIND auxiliary + MAY CONTAIN {deltaRevocationList} + ID id-oc-deltaCRL +} + +cpCps OBJECT-CLASS ::= { + SUBCLASS OF {top} + KIND auxiliary + MAY CONTAIN {certificatePolicy | certificationPracticeStmt} + ID id-oc-cpCps +} + +pkiCertPath OBJECT-CLASS ::= { + SUBCLASS OF {top} + KIND auxiliary + MAY CONTAIN {pkiPath} + ID id-oc-pkiCertPath +} + +-- PKI directory attributes +userCertificate ATTRIBUTE ::= { + WITH SYNTAX Certificate + EQUALITY MATCHING RULE certificateExactMatch + ID id-at-userCertificate +} + +cACertificate ATTRIBUTE ::= { + WITH SYNTAX Certificate + EQUALITY MATCHING RULE certificateExactMatch + ID id-at-cAcertificate +} + +crossCertificatePair ATTRIBUTE ::= { + WITH SYNTAX CertificatePair + EQUALITY MATCHING RULE certificatePairExactMatch + ID id-at-crossCertificatePair +} + +CertificatePair ::= SEQUENCE { + forward [0] Certificate OPTIONAL, + reverse [1] Certificate OPTIONAL + -- at least one of the pair shall be present +} +(WITH COMPONENTS { + ..., + forward PRESENT + } | WITH COMPONENTS { + ..., + reverse PRESENT + }) + +certificateRevocationList ATTRIBUTE ::= { + WITH SYNTAX CertificateList + EQUALITY MATCHING RULE certificateListExactMatch + ID id-at-certificateRevocationList +} + +authorityRevocationList ATTRIBUTE ::= { + WITH SYNTAX CertificateList + EQUALITY MATCHING RULE certificateListExactMatch + ID id-at-authorityRevocationList +} + +deltaRevocationList ATTRIBUTE ::= { + WITH SYNTAX CertificateList + EQUALITY MATCHING RULE certificateListExactMatch + ID id-at-deltaRevocationList +} + +supportedAlgorithms ATTRIBUTE ::= { + WITH SYNTAX SupportedAlgorithm + EQUALITY MATCHING RULE algorithmIdentifierMatch + ID id-at-supportedAlgorithms +} + +SupportedAlgorithm ::= SEQUENCE { + algorithmIdentifier AlgorithmIdentifier{{SupportedAlgorithms}}, + intendedUsage [0] KeyUsage OPTIONAL, + intendedCertificatePolicies [1] CertificatePoliciesSyntax OPTIONAL +} + +certificationPracticeStmt ATTRIBUTE ::= { + WITH SYNTAX InfoSyntax + ID id-at-certificationPracticeStmt +} + +InfoSyntax ::= CHOICE { + content UnboundedDirectoryString, + pointer SEQUENCE {name GeneralNames, + hash HASH{HashedPolicyInfo} OPTIONAL} +} + +POLICY ::= TYPE-IDENTIFIER + +HashedPolicyInfo ::= POLICY.&Type({Policies}) + +Policies POLICY ::= + {...} -- Defined by implementors + +certificatePolicy ATTRIBUTE ::= { + WITH SYNTAX PolicySyntax + ID id-at-certificatePolicy +} + +PolicySyntax ::= SEQUENCE { + policyIdentifier PolicyID, + policySyntax InfoSyntax +} + +PolicyID ::= CertPolicyId + +pkiPath ATTRIBUTE ::= {WITH SYNTAX PkiPath + ID id-at-pkiPath +} + +userPassword ATTRIBUTE ::= { + WITH SYNTAX OCTET STRING(SIZE (0..MAX)) + EQUALITY MATCHING RULE octetStringMatch + ID id-at-userPassword +} + +-- object identifier assignments +-- object classes +id-oc-cRLDistributionPoint OBJECT IDENTIFIER ::= + {id-oc 19} + +id-oc-pkiUser OBJECT IDENTIFIER ::= {id-oc 21} + +id-oc-pkiCA OBJECT IDENTIFIER ::= {id-oc 22} + +id-oc-deltaCRL OBJECT IDENTIFIER ::= {id-oc 23} + +id-oc-cpCps OBJECT IDENTIFIER ::= {id-oc 30} + +id-oc-pkiCertPath OBJECT IDENTIFIER ::= {id-oc 31} + +-- name forms +id-nf-cRLDistPtNameForm OBJECT IDENTIFIER ::= {id-nf 14} + +-- directory attributes +id-at-userPassword OBJECT IDENTIFIER ::= {id-at 35} + +id-at-userCertificate OBJECT IDENTIFIER ::= {id-at 36} + +id-at-cAcertificate OBJECT IDENTIFIER ::= {id-at 37} + +id-at-authorityRevocationList OBJECT IDENTIFIER ::= {id-at 38} + +id-at-certificateRevocationList OBJECT IDENTIFIER ::= {id-at 39} + +id-at-crossCertificatePair OBJECT IDENTIFIER ::= {id-at 40} + +id-at-supportedAlgorithms OBJECT IDENTIFIER ::= {id-at 52} + +id-at-deltaRevocationList OBJECT IDENTIFIER ::= {id-at 53} + +id-at-certificationPracticeStmt OBJECT IDENTIFIER ::= {id-at 68} + +id-at-certificatePolicy OBJECT IDENTIFIER ::= {id-at 69} + +id-at-pkiPath OBJECT IDENTIFIER ::= {id-at 70} + +END -- AuthenticationFramework diff --git a/lib/public_key/asn1/InformationFramework.asn1 b/lib/public_key/asn1/InformationFramework.asn1 new file mode 100644 index 0000000000..4aed43a39e --- /dev/null +++ b/lib/public_key/asn1/InformationFramework.asn1 @@ -0,0 +1,682 @@ +InformationFramework {joint-iso-itu-t ds(5) module(1) informationFramework(1) + 6} DEFINITIONS ::= +BEGIN + +-- EXPORTS All +-- The types and values defined in this module are exported for use in the other ASN.1 modules contained +-- within the Directory Specifications, and for the use of other applications which will use them to access +-- Directory services. Other applications may use them for their own purposes, but this will not constrain +-- extensions and modifications needed to maintain or improve the Directory service. +IMPORTS + -- from ITU-T Rec. X.501 | ISO/IEC 9594-2 + directoryAbstractService, id-ar, id-at, id-mr, id-nf, id-oa, id-oc, + id-sc, selectedAttributeTypes, serviceAdministration + FROM UsefulDefinitions {joint-iso-itu-t ds(5) module(1) + usefulDefinitions(0) 6} + SearchRule + FROM ServiceAdministration serviceAdministration + -- from ITU-T Rec. X.511 | ISO/IEC 9594-3 + TypeAndContextAssertion + FROM DirectoryAbstractService directoryAbstractService + -- from ITU-T Rec. X.520 | ISO/IEC 9594-6 + booleanMatch, commonName, generalizedTimeMatch, generalizedTimeOrderingMatch, + integerFirstComponentMatch, integerMatch, integerOrderingMatch, + objectIdentifierFirstComponentMatch, UnboundedDirectoryString + FROM SelectedAttributeTypes selectedAttributeTypes; + +-- attribute data types +Attribute{ATTRIBUTE:SupportedAttributes} ::= SEQUENCE { + type ATTRIBUTE.&id({SupportedAttributes}), + values + SET SIZE (0..MAX) OF ATTRIBUTE.&Type({SupportedAttributes}{@type}), + valuesWithContext + SET SIZE (1..MAX) OF + SEQUENCE {value ATTRIBUTE.&Type({SupportedAttributes}{@type}), + contextList SET SIZE (1..MAX) OF Context} OPTIONAL +} + +AttributeType ::= ATTRIBUTE.&id + +AttributeValue ::= ATTRIBUTE.&Type + +Context ::= SEQUENCE { + contextType CONTEXT.&id({SupportedContexts}), + contextValues + SET SIZE (1..MAX) OF CONTEXT.&Type({SupportedContexts}{@contextType}), + fallback BOOLEAN DEFAULT FALSE +} + +AttributeValueAssertion ::= SEQUENCE { + type ATTRIBUTE.&id({SupportedAttributes}), + assertion + ATTRIBUTE.&equality-match.&AssertionType + ({SupportedAttributes}{@type}), + assertedContexts + CHOICE {allContexts [0] NULL, + selectedContexts [1] SET SIZE (1..MAX) OF ContextAssertion + } OPTIONAL +} + +ContextAssertion ::= SEQUENCE { + contextType CONTEXT.&id({SupportedContexts}), + contextValues + SET SIZE (1..MAX) OF + CONTEXT.&Assertion({SupportedContexts}{@contextType}) +} + +AttributeTypeAssertion ::= SEQUENCE { + type ATTRIBUTE.&id({SupportedAttributes}), + assertedContexts SEQUENCE SIZE (1..MAX) OF ContextAssertion OPTIONAL +} + +-- Definition of the following information object set is deferred, perhaps to standardized +-- profiles or to protocol implementation conformance statements. The set is required to +-- specify a table constraint on the values component of Attribute, the value component +-- of AttributeTypeAndValue, and the assertion component of AttributeValueAssertion. +SupportedAttributes ATTRIBUTE ::= + {objectClass | aliasedEntryName, ...} + +-- Definition of the following information object set is deferred, perhaps to standardized +-- profiles or to protocol implementation conformance statements. The set is required to +-- specify a table constraint on the context specifications +SupportedContexts CONTEXT ::= + {...} + +-- naming data types +Name ::= CHOICE { -- only one possibility for now --rdnSequence RDNSequence +} + +RDNSequence ::= SEQUENCE OF RelativeDistinguishedName + +DistinguishedName ::= RDNSequence + +RelativeDistinguishedName ::= + SET SIZE (1..MAX) OF AttributeTypeAndDistinguishedValue + +AttributeTypeAndDistinguishedValue ::= SEQUENCE { + type ATTRIBUTE.&id({SupportedAttributes}), + value ATTRIBUTE.&Type({SupportedAttributes}{@type}), + primaryDistinguished BOOLEAN DEFAULT TRUE, + valuesWithContext + SET SIZE (1..MAX) OF + SEQUENCE {distingAttrValue + [0] ATTRIBUTE.&Type({SupportedAttributes}{@type}) + OPTIONAL, + contextList SET SIZE (1..MAX) OF Context} OPTIONAL +} + +-- subtree data types +SubtreeSpecification ::= SEQUENCE { + base [0] LocalName DEFAULT {}, + COMPONENTS OF ChopSpecification, + specificationFilter [4] Refinement OPTIONAL +} + +-- empty sequence specifies whole administrative area +LocalName ::= RDNSequence + +ChopSpecification ::= SEQUENCE { + specificExclusions + [1] SET SIZE (1..MAX) OF + CHOICE {chopBefore [0] LocalName, + chopAfter [1] LocalName} OPTIONAL, + minimum [2] BaseDistance DEFAULT 0, + maximum [3] BaseDistance OPTIONAL +} + +BaseDistance ::= INTEGER(0..MAX) + +Refinement ::= CHOICE { + item [0] OBJECT-CLASS.&id, + and [1] SET SIZE (1..MAX) OF Refinement, + or [2] SET SIZE (1..MAX) OF Refinement, + not [3] Refinement +} + +-- OBJECT-CLASS information object class specification +OBJECT-CLASS ::= CLASS { + &Superclasses OBJECT-CLASS OPTIONAL, + &kind ObjectClassKind DEFAULT structural, + &MandatoryAttributes ATTRIBUTE OPTIONAL, + &OptionalAttributes ATTRIBUTE OPTIONAL, + &id OBJECT IDENTIFIER UNIQUE +} +WITH SYNTAX { + [SUBCLASS OF &Superclasses] + [KIND &kind] + [MUST CONTAIN &MandatoryAttributes] + [MAY CONTAIN &OptionalAttributes] + ID &id +} + +ObjectClassKind ::= ENUMERATED {abstract(0), structural(1), auxiliary(2)} + +-- object classes +top OBJECT-CLASS ::= { + KIND abstract + MUST CONTAIN {objectClass} + ID id-oc-top +} + +alias OBJECT-CLASS ::= { + SUBCLASS OF {top} + MUST CONTAIN {aliasedEntryName} + ID id-oc-alias +} + +parent OBJECT-CLASS ::= {KIND abstract + ID id-oc-parent +} + +child OBJECT-CLASS ::= {KIND auxiliary + ID id-oc-child +} + +-- ATTRIBUTE information object class specification +ATTRIBUTE ::= CLASS { + &derivation ATTRIBUTE OPTIONAL, + &Type OPTIONAL, -- either &Type or &derivation required + &equality-match MATCHING-RULE OPTIONAL, + &ordering-match MATCHING-RULE OPTIONAL, + &substrings-match MATCHING-RULE OPTIONAL, + &single-valued BOOLEAN DEFAULT FALSE, + &collective BOOLEAN DEFAULT FALSE, + &dummy BOOLEAN DEFAULT FALSE, + -- operational extensions + &no-user-modification BOOLEAN DEFAULT FALSE, + &usage AttributeUsage DEFAULT userApplications, + &id OBJECT IDENTIFIER UNIQUE +} +WITH SYNTAX { + [SUBTYPE OF &derivation] + [WITH SYNTAX &Type] + [EQUALITY MATCHING RULE &equality-match] + [ORDERING MATCHING RULE &ordering-match] + [SUBSTRINGS MATCHING RULE &substrings-match] + [SINGLE VALUE &single-valued] + [COLLECTIVE &collective] + [DUMMY &dummy] + [NO USER MODIFICATION &no-user-modification] + [USAGE &usage] + ID &id +} + +AttributeUsage ::= ENUMERATED { + userApplications(0), directoryOperation(1), distributedOperation(2), + dSAOperation(3)} + +-- attributes +objectClass ATTRIBUTE ::= { + WITH SYNTAX OBJECT IDENTIFIER + EQUALITY MATCHING RULE objectIdentifierMatch + ID id-at-objectClass +} + +aliasedEntryName ATTRIBUTE ::= { + WITH SYNTAX DistinguishedName + EQUALITY MATCHING RULE distinguishedNameMatch + SINGLE VALUE TRUE + ID id-at-aliasedEntryName +} + +-- MATCHING-RULE information object class specification +MATCHING-RULE ::= CLASS { + &ParentMatchingRules MATCHING-RULE OPTIONAL, + &AssertionType OPTIONAL, + &uniqueMatchIndicator ATTRIBUTE OPTIONAL, + &id OBJECT IDENTIFIER UNIQUE +} +WITH SYNTAX { + [PARENT &ParentMatchingRules] + [SYNTAX &AssertionType] + [UNIQUE-MATCH-INDICATOR &uniqueMatchIndicator] + ID &id +} + +-- matching rules +objectIdentifierMatch MATCHING-RULE ::= { + SYNTAX OBJECT IDENTIFIER + ID id-mr-objectIdentifierMatch +} + +distinguishedNameMatch MATCHING-RULE ::= { + SYNTAX DistinguishedName + ID id-mr-distinguishedNameMatch +} + +MAPPING-BASED-MATCHING{SelectedBy, BOOLEAN:combinable, MappingResult, + OBJECT IDENTIFIER:matchingRule} ::= CLASS { + &selectBy SelectedBy OPTIONAL, + &ApplicableTo ATTRIBUTE, + &subtypesIncluded BOOLEAN DEFAULT TRUE, + &combinable BOOLEAN(combinable), + &mappingResults MappingResult OPTIONAL, + &userControl BOOLEAN DEFAULT FALSE, + &exclusive BOOLEAN DEFAULT TRUE, + &matching-rule MATCHING-RULE.&id(matchingRule), + &id OBJECT IDENTIFIER UNIQUE +} +WITH SYNTAX { + [SELECT BY &selectBy] + APPLICABLE TO &ApplicableTo + [SUBTYPES INCLUDED &subtypesIncluded] + COMBINABLE &combinable + [MAPPING RESULTS &mappingResults] + [USER CONTROL &userControl] + [EXCLUSIVE &exclusive] + MATCHING RULE &matching-rule + ID &id +} + +-- NAME-FORM information object class specification +NAME-FORM ::= CLASS { + &namedObjectClass OBJECT-CLASS, + &MandatoryAttributes ATTRIBUTE, + &OptionalAttributes ATTRIBUTE OPTIONAL, + &id OBJECT IDENTIFIER UNIQUE +} +WITH SYNTAX { + NAMES &namedObjectClass + WITH ATTRIBUTES &MandatoryAttributes + [AND OPTIONALLY &OptionalAttributes] + ID &id +} + +-- STRUCTURE-RULE class and DIT structure rule data types +DITStructureRule ::= SEQUENCE { + ruleIdentifier RuleIdentifier, + -- shall be unique within the scope of the subschema + nameForm NAME-FORM.&id, + superiorStructureRules SET SIZE (1..MAX) OF RuleIdentifier OPTIONAL +} + +RuleIdentifier ::= INTEGER + +STRUCTURE-RULE ::= CLASS { + &nameForm NAME-FORM, + &SuperiorStructureRules STRUCTURE-RULE OPTIONAL, + &id RuleIdentifier +} +WITH SYNTAX { + NAME FORM &nameForm + [SUPERIOR RULES &SuperiorStructureRules] + ID &id +} + +-- DIT content rule data type and CONTENT-RULE class +DITContentRule ::= SEQUENCE { + structuralObjectClass OBJECT-CLASS.&id, + auxiliaries SET SIZE (1..MAX) OF OBJECT-CLASS.&id OPTIONAL, + mandatory [1] SET SIZE (1..MAX) OF ATTRIBUTE.&id OPTIONAL, + optional [2] SET SIZE (1..MAX) OF ATTRIBUTE.&id OPTIONAL, + precluded [3] SET SIZE (1..MAX) OF ATTRIBUTE.&id OPTIONAL +} + +CONTENT-RULE ::= CLASS { + &structuralClass OBJECT-CLASS.&id UNIQUE, + &Auxiliaries OBJECT-CLASS OPTIONAL, + &Mandatory ATTRIBUTE OPTIONAL, + &Optional ATTRIBUTE OPTIONAL, + &Precluded ATTRIBUTE OPTIONAL +} +WITH SYNTAX { + STRUCTURAL OBJECT-CLASS &structuralClass + [AUXILIARY OBJECT-CLASSES &Auxiliaries] + [MUST CONTAIN &Mandatory] + [MAY CONTAIN &Optional] + [MUST-NOT CONTAIN &Precluded] +} + +CONTEXT ::= CLASS { + &Type , + &DefaultValue OPTIONAL, + &Assertion OPTIONAL, + &absentMatch BOOLEAN DEFAULT TRUE, + &id OBJECT IDENTIFIER UNIQUE +} +WITH SYNTAX { + WITH SYNTAX &Type + [DEFAULT-VALUE &DefaultValue] + [ASSERTED AS &Assertion] + [ABSENT-MATCH &absentMatch] + ID &id +} + +DITContextUse ::= SEQUENCE { + attributeType ATTRIBUTE.&id, + mandatoryContexts [1] SET SIZE (1..MAX) OF CONTEXT.&id OPTIONAL, + optionalContexts [2] SET SIZE (1..MAX) OF CONTEXT.&id OPTIONAL +} + +DIT-CONTEXT-USE-RULE ::= CLASS { + &attributeType ATTRIBUTE.&id UNIQUE, + &Mandatory CONTEXT OPTIONAL, + &Optional CONTEXT OPTIONAL +} +WITH SYNTAX { + ATTRIBUTE TYPE &attributeType + [MANDATORY CONTEXTS &Mandatory] + [OPTIONAL CONTEXTS &Optional] +} + +FRIENDS ::= CLASS { + &anchor ATTRIBUTE.&id UNIQUE, + &Friends ATTRIBUTE +}WITH SYNTAX {ANCHOR &anchor + FRIENDS &Friends +} + +-- system schema information objects +-- object classes +subentry OBJECT-CLASS ::= { + SUBCLASS OF {top} + KIND structural + MUST CONTAIN {commonName | subtreeSpecification} + ID id-sc-subentry +} + +subentryNameForm NAME-FORM ::= { + NAMES subentry + WITH ATTRIBUTES {commonName} + ID id-nf-subentryNameForm +} + +subtreeSpecification ATTRIBUTE ::= { + WITH SYNTAX SubtreeSpecification + USAGE directoryOperation + ID id-oa-subtreeSpecification +} + +administrativeRole ATTRIBUTE ::= { + WITH SYNTAX OBJECT-CLASS.&id + EQUALITY MATCHING RULE objectIdentifierMatch + USAGE directoryOperation + ID id-oa-administrativeRole +} + +createTimestamp ATTRIBUTE ::= { + WITH SYNTAX GeneralizedTime + -- as per 46.3 b) or c) of ITU-T Rec. X.680 | ISO/IEC 8824-1 + EQUALITY MATCHING RULE generalizedTimeMatch + ORDERING MATCHING RULE generalizedTimeOrderingMatch + SINGLE VALUE TRUE + NO USER MODIFICATION TRUE + USAGE directoryOperation + ID id-oa-createTimestamp +} + +modifyTimestamp ATTRIBUTE ::= { + WITH SYNTAX GeneralizedTime + -- as per 46.3 b) or c) of ITU-T Rec. X.680 | ISO/IEC 8824-1 + EQUALITY MATCHING RULE generalizedTimeMatch + ORDERING MATCHING RULE generalizedTimeOrderingMatch + SINGLE VALUE TRUE + NO USER MODIFICATION TRUE + USAGE directoryOperation + ID id-oa-modifyTimestamp +} + +subschemaTimestamp ATTRIBUTE ::= { + WITH SYNTAX GeneralizedTime + -- as per 46.3 b) or c) of ITU-T Rec. X.680 | ISO/IEC 8824-1 + EQUALITY MATCHING RULE generalizedTimeMatch + ORDERING MATCHING RULE generalizedTimeOrderingMatch + SINGLE VALUE TRUE + NO USER MODIFICATION TRUE + USAGE directoryOperation + ID id-oa-subschemaTimestamp +} + +creatorsName ATTRIBUTE ::= { + WITH SYNTAX DistinguishedName + EQUALITY MATCHING RULE distinguishedNameMatch + SINGLE VALUE TRUE + NO USER MODIFICATION TRUE + USAGE directoryOperation + ID id-oa-creatorsName +} + +modifiersName ATTRIBUTE ::= { + WITH SYNTAX DistinguishedName + EQUALITY MATCHING RULE distinguishedNameMatch + SINGLE VALUE TRUE + NO USER MODIFICATION TRUE + USAGE directoryOperation + ID id-oa-modifiersName +} + +subschemaSubentryList ATTRIBUTE ::= { + WITH SYNTAX DistinguishedName + EQUALITY MATCHING RULE distinguishedNameMatch + SINGLE VALUE TRUE + NO USER MODIFICATION TRUE + USAGE directoryOperation + ID id-oa-subschemaSubentryList +} + +accessControlSubentryList ATTRIBUTE ::= { + WITH SYNTAX DistinguishedName + EQUALITY MATCHING RULE distinguishedNameMatch + NO USER MODIFICATION TRUE + USAGE directoryOperation + ID id-oa-accessControlSubentryList +} + +collectiveAttributeSubentryList ATTRIBUTE ::= { + WITH SYNTAX DistinguishedName + EQUALITY MATCHING RULE distinguishedNameMatch + NO USER MODIFICATION TRUE + USAGE directoryOperation + ID id-oa-collectiveAttributeSubentryList +} + +contextDefaultSubentryList ATTRIBUTE ::= { + WITH SYNTAX DistinguishedName + EQUALITY MATCHING RULE distinguishedNameMatch + NO USER MODIFICATION TRUE + USAGE directoryOperation + ID id-oa-contextDefaultSubentryList +} + +serviceAdminSubentryList ATTRIBUTE ::= { + WITH SYNTAX DistinguishedName + EQUALITY MATCHING RULE distinguishedNameMatch + NO USER MODIFICATION TRUE + USAGE directoryOperation + ID id-oa-serviceAdminSubentryList +} + +hasSubordinates ATTRIBUTE ::= { + WITH SYNTAX BOOLEAN + EQUALITY MATCHING RULE booleanMatch + SINGLE VALUE TRUE + NO USER MODIFICATION TRUE + USAGE directoryOperation + ID id-oa-hasSubordinates +} + +accessControlSubentry OBJECT-CLASS ::= { + KIND auxiliary + ID id-sc-accessControlSubentry +} + +collectiveAttributeSubentry OBJECT-CLASS ::= { + KIND auxiliary + ID id-sc-collectiveAttributeSubentry +} + +collectiveExclusions ATTRIBUTE ::= { + WITH SYNTAX OBJECT IDENTIFIER + EQUALITY MATCHING RULE objectIdentifierMatch + USAGE directoryOperation + ID id-oa-collectiveExclusions +} + +contextAssertionSubentry OBJECT-CLASS ::= { + KIND auxiliary + MUST CONTAIN {contextAssertionDefaults} + ID id-sc-contextAssertionSubentry +} + +contextAssertionDefaults ATTRIBUTE ::= { + WITH SYNTAX TypeAndContextAssertion + EQUALITY MATCHING RULE objectIdentifierFirstComponentMatch + USAGE directoryOperation + ID id-oa-contextAssertionDefault +} + +serviceAdminSubentry OBJECT-CLASS ::= { + KIND auxiliary + MUST CONTAIN {searchRules} + ID id-sc-serviceAdminSubentry +} + +searchRules ATTRIBUTE ::= { + WITH SYNTAX SearchRuleDescription + EQUALITY MATCHING RULE integerFirstComponentMatch + USAGE directoryOperation + ID id-oa-searchRules +} + +SearchRuleDescription ::= SEQUENCE { + COMPONENTS OF SearchRule, + name [28] SET SIZE (1..MAX) OF UnboundedDirectoryString OPTIONAL, + description [29] UnboundedDirectoryString OPTIONAL +} + +hierarchyLevel ATTRIBUTE ::= { + WITH SYNTAX HierarchyLevel + EQUALITY MATCHING RULE integerMatch + ORDERING MATCHING RULE integerOrderingMatch + SINGLE VALUE TRUE + NO USER MODIFICATION TRUE + USAGE directoryOperation + ID id-oa-hierarchyLevel +} + +HierarchyLevel ::= INTEGER + +hierarchyBelow ATTRIBUTE ::= { + WITH SYNTAX HierarchyBelow + EQUALITY MATCHING RULE booleanMatch + SINGLE VALUE TRUE + NO USER MODIFICATION TRUE + USAGE directoryOperation + ID id-oa-hierarchyBelow +} + +HierarchyBelow ::= BOOLEAN + +hierarchyParent ATTRIBUTE ::= { + WITH SYNTAX DistinguishedName + EQUALITY MATCHING RULE distinguishedNameMatch + SINGLE VALUE TRUE + USAGE directoryOperation + ID id-oa-hierarchyParent +} + +hierarchyTop ATTRIBUTE ::= { + WITH SYNTAX DistinguishedName + EQUALITY MATCHING RULE distinguishedNameMatch + SINGLE VALUE TRUE + USAGE directoryOperation + ID id-oa-hierarchyTop +} + +-- object identifier assignments +-- object classes +id-oc-top OBJECT IDENTIFIER ::= + {id-oc 0} + +id-oc-alias OBJECT IDENTIFIER ::= {id-oc 1} + +id-oc-parent OBJECT IDENTIFIER ::= {id-oc 28} + +id-oc-child OBJECT IDENTIFIER ::= {id-oc 29} + +-- attributes +id-at-objectClass OBJECT IDENTIFIER ::= {id-at 0} + +id-at-aliasedEntryName OBJECT IDENTIFIER ::= {id-at 1} + +-- matching rules +id-mr-objectIdentifierMatch OBJECT IDENTIFIER ::= {id-mr 0} + +id-mr-distinguishedNameMatch OBJECT IDENTIFIER ::= {id-mr 1} + +-- operational attributes +id-oa-excludeAllCollectiveAttributes OBJECT IDENTIFIER ::= + {id-oa 0} + +id-oa-createTimestamp OBJECT IDENTIFIER ::= {id-oa 1} + +id-oa-modifyTimestamp OBJECT IDENTIFIER ::= {id-oa 2} + +id-oa-creatorsName OBJECT IDENTIFIER ::= {id-oa 3} + +id-oa-modifiersName OBJECT IDENTIFIER ::= {id-oa 4} + +id-oa-administrativeRole OBJECT IDENTIFIER ::= {id-oa 5} + +id-oa-subtreeSpecification OBJECT IDENTIFIER ::= {id-oa 6} + +id-oa-collectiveExclusions OBJECT IDENTIFIER ::= {id-oa 7} + +id-oa-subschemaTimestamp OBJECT IDENTIFIER ::= {id-oa 8} + +id-oa-hasSubordinates OBJECT IDENTIFIER ::= {id-oa 9} + +id-oa-subschemaSubentryList OBJECT IDENTIFIER ::= {id-oa 10} + +id-oa-accessControlSubentryList OBJECT IDENTIFIER ::= {id-oa 11} + +id-oa-collectiveAttributeSubentryList OBJECT IDENTIFIER ::= {id-oa 12} + +id-oa-contextDefaultSubentryList OBJECT IDENTIFIER ::= {id-oa 13} + +id-oa-contextAssertionDefault OBJECT IDENTIFIER ::= {id-oa 14} + +id-oa-serviceAdminSubentryList OBJECT IDENTIFIER ::= {id-oa 15} + +id-oa-searchRules OBJECT IDENTIFIER ::= {id-oa 16} + +id-oa-hierarchyLevel OBJECT IDENTIFIER ::= {id-oa 17} + +id-oa-hierarchyBelow OBJECT IDENTIFIER ::= {id-oa 18} + +id-oa-hierarchyParent OBJECT IDENTIFIER ::= {id-oa 19} + +id-oa-hierarchyTop OBJECT IDENTIFIER ::= {id-oa 20} + +-- subentry classes +id-sc-subentry OBJECT IDENTIFIER ::= {id-sc 0} + +id-sc-accessControlSubentry OBJECT IDENTIFIER ::= {id-sc 1} + +id-sc-collectiveAttributeSubentry OBJECT IDENTIFIER ::= {id-sc 2} + +id-sc-contextAssertionSubentry OBJECT IDENTIFIER ::= {id-sc 3} + +id-sc-serviceAdminSubentry OBJECT IDENTIFIER ::= {id-sc 4} + +-- Name forms +id-nf-subentryNameForm OBJECT IDENTIFIER ::= {id-nf 16} + +-- administrative roles +id-ar-autonomousArea OBJECT IDENTIFIER ::= {id-ar 1} + +id-ar-accessControlSpecificArea OBJECT IDENTIFIER ::= {id-ar 2} + +id-ar-accessControlInnerArea OBJECT IDENTIFIER ::= {id-ar 3} + +id-ar-subschemaAdminSpecificArea OBJECT IDENTIFIER ::= {id-ar 4} + +id-ar-collectiveAttributeSpecificArea OBJECT IDENTIFIER ::= {id-ar 5} + +id-ar-collectiveAttributeInnerArea OBJECT IDENTIFIER ::= {id-ar 6} + +id-ar-contextDefaultSpecificArea OBJECT IDENTIFIER ::= {id-ar 7} + +id-ar-serviceSpecificArea OBJECT IDENTIFIER ::= {id-ar 8} + +END -- InformationFramework diff --git a/lib/public_key/asn1/Makefile b/lib/public_key/asn1/Makefile index 4bd043ee5d..957c332cad 100644 --- a/lib/public_key/asn1/Makefile +++ b/lib/public_key/asn1/Makefile @@ -40,7 +40,8 @@ RELSYSDIR = $(RELEASE_PATH)/lib/public_key-$(VSN) ASN_TOP = OTP-PUB-KEY PKCS-FRAME ASN_MODULES = PKIX1Explicit88 PKIX1Implicit88 PKIX1Algorithms88 \ - PKIXAttributeCertificate PKCS-1 PKCS-3 PKCS-8 PKCS5v2-0 OTP-PKIX + PKIXAttributeCertificate PKCS-1 PKCS-3 PKCS-7 PKCS-8 PKCS-10 PKCS5v2-0 OTP-PKIX \ + InformationFramework ASN_ASNS = $(ASN_MODULES:%=%.asn1) ASN_ERLS = $(ASN_TOP:%=%.erl) ASN_HRLS = $(ASN_TOP:%=%.hrl) @@ -112,9 +113,12 @@ OTP-PUB-KEY.asn1db: PKIX1Algorithms88.asn1 \ PKIXAttributeCertificate.asn1 \ PKCS-1.asn1\ PKCS-3.asn1\ + PKCS-7.asn1\ + PKCS-10.asn1\ + InformationFramework.asn1\ OTP-PKIX.asn1 $(EBIN)/PKCS-FRAME.beam: PKCS-FRAME.erl PKCS-FRAME.hrl -PKCS-FRAME.erl PKCS-FRAME.hrl: PKCS-FRAME.asn1db -PKCS-FRAME.asn1db: PKCS-8.asn1\ - PKCS5v2-0.asn1
\ No newline at end of file +PKCS-FRAME.erl PKCS-FRAME.hrl: PKCS-FRAME.asn1db +PKCS-FRAME.asn1db: PKCS5v2-0.asn1\ + PKCS-8.asn1\ diff --git a/lib/public_key/asn1/OTP-PUB-KEY.set.asn b/lib/public_key/asn1/OTP-PUB-KEY.set.asn index 5c76d13115..f8fb318c93 100644 --- a/lib/public_key/asn1/OTP-PUB-KEY.set.asn +++ b/lib/public_key/asn1/OTP-PUB-KEY.set.asn @@ -6,3 +6,5 @@ PKIX1Algorithms88.asn1 PKCS-1.asn1 PKCS-3.asn1 DSS.asn1 +PKCS-7.asn1 +PKCS-10.asn1 diff --git a/lib/public_key/asn1/PKCS-10.asn1 b/lib/public_key/asn1/PKCS-10.asn1 new file mode 100644 index 0000000000..333104d230 --- /dev/null +++ b/lib/public_key/asn1/PKCS-10.asn1 @@ -0,0 +1,70 @@ +PKCS-10 {iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) +pkcs-10(10) modules(1) pkcs-10(1)} + +-- $Revision: 1.3 $ -- + +DEFINITIONS IMPLICIT TAGS ::= + +BEGIN + +-- EXPORTS All -- +-- All types and values defined in this module are exported for use +-- in other ASN.1 modules. + +IMPORTS + +--informationFramework, authenticationFramework +-- FROM UsefulDefinitions {joint-iso-itu-t(2) ds(5) module(1) +-- usefulDefinitions(0) 3} + + ATTRIBUTE + FROM InformationFramework informationFramework + + Name + FROM PKIX1Explicit88 --InformationFramework informationFramework + + ALGORITHM + FROM PKCS-7; --AuthenticationFramework authenticationFramework; + +-- Certificate requests + +CertificationRequestInfo ::= SEQUENCE { + version INTEGER { v1(0) } (v1,...), + subject Name, + subjectPKInfo SubjectPublicKeyInfo-PKCS-10{{ PKInfoAlgorithms }}, + attributes [0] Attributes{{ CRIAttributes }} +} + +SubjectPublicKeyInfo-PKCS-10 {ALGORITHM: IOSet} ::= SEQUENCE { + algorithm AlgorithmIdentifierPKCS-10{{IOSet}}, + subjectPublicKey BIT STRING +} + +PKInfoAlgorithms ALGORITHM ::= { + ... -- add any locally defined algorithms here -- } + +Attributes { ATTRIBUTE:IOSet } ::= SET OF AttributePKCS-10{{ IOSet }} + +CRIAttributes ATTRIBUTE ::= { +... -- add any locally defined attributes here -- } + +AttributePKCS-10 { ATTRIBUTE:IOSet } ::= SEQUENCE { + type ATTRIBUTE.&id({IOSet}), + values SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{@type}) +} + +CertificationRequest ::= SEQUENCE { + certificationRequestInfo CertificationRequestInfo, + signatureAlgorithm AlgorithmIdentifierPKCS-10{{ SignatureAlgorithms }}, + signature BIT STRING +} + +AlgorithmIdentifierPKCS-10 {ALGORITHM:IOSet } ::= SEQUENCE { + algorithm ALGORITHM.&id({IOSet}), + parameters ALGORITHM.&Type({IOSet}{@algorithm}) OPTIONAL +} + +SignatureAlgorithms ALGORITHM ::= { + ... -- add any locally defined algorithms here -- } + +END diff --git a/lib/public_key/asn1/PKCS-7.asn1 b/lib/public_key/asn1/PKCS-7.asn1 new file mode 100644 index 0000000000..a6dfd57d80 --- /dev/null +++ b/lib/public_key/asn1/PKCS-7.asn1 @@ -0,0 +1,387 @@ +PKCS-7 {iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-7(7) + modules(0) pkcs-7(1)} + +DEFINITIONS EXPLICIT TAGS ::= +BEGIN + +-- +-- 3. Definitions +-- + +-- EXPORTS All; + +IMPORTS + +informationFramework, authenticationFramework + FROM UsefulDefinitions {joint-iso-itu-t ds(5) module(1) + usefulDefinitions(0) 3} + + ATTRIBUTE + FROM InformationFramework informationFramework + + Name, Certificate, CertificateSerialNumber, + CertificateList, Time + FROM PKIX1Explicit88; -- AuthenticationFramework authenticationFramework; + +-- contentType, messageDigest, signingTime +-- , counterSignature +-- FROM PKCS-9 {iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) +-- pkcs-9(9) modules(0) pkcs-9(1)}; +-- +-- 6. Useful types +-- + +-- inlined from AuthenticationFramework + +ALGORITHM ::= CLASS {&Type OPTIONAL, + &id OBJECT IDENTIFIER UNIQUE +}WITH SYNTAX {[&Type] + IDENTIFIED BY &id +} + +-- inlined from PKCS-9 + +pkcs-9 OBJECT IDENTIFIER ::= {iso(1) member-body(2) us(840) + rsadsi(113549) pkcs(1) 9} + +contentType ATTRIBUTE ::= { + WITH SYNTAX ContentType +-- EQUALITY MATCHING RULE objectIdentifierMatch + SINGLE VALUE TRUE + ID pkcs-9-at-contentType +} + +pkcs-9-at-contentType OBJECT IDENTIFIER ::= {pkcs-9 3} +pkcs-9-at-messageDigest OBJECT IDENTIFIER ::= {pkcs-9 4} +pkcs-9-at-signingTime OBJECT IDENTIFIER ::= {pkcs-9 5} +pkcs-9-at-counterSignature OBJECT IDENTIFIER ::= {pkcs-9 6} + +counterSignature ATTRIBUTE ::= { + WITH SYNTAX SignerInfo + ID pkcs-9-at-counterSignature +} +messageDigest ATTRIBUTE ::= { + WITH SYNTAX MessageDigest +-- EQUALITY MATCHING RULE octetStringMatch + SINGLE VALUE TRUE + ID pkcs-9-at-messageDigest +} + +MessageDigest ::= OCTET STRING + +signingTime ATTRIBUTE ::= { + WITH SYNTAX SigningTime +-- EQUALITY MATCHING RULE signingTimeMatch + SINGLE VALUE TRUE + ID pkcs-9-at-signingTime +} + +SigningTime ::= Time -- imported from ISO/IEC 9594-8 + + +-- Also defined in X.509 +-- Redeclared here as a parameterized type +AlgorithmIdentifierPKSC-7 {ALGORITHM:IOSet} ::= SEQUENCE { + algorithm ALGORITHM.&id({IOSet}), + parameters ALGORITHM.&Type({IOSet}{@algorithm}) OPTIONAL +} + +-- Also defined in X.501 +-- Redeclared here as a parameterized type +AttributePKCS-7 { ATTRIBUTE:IOSet } ::= SEQUENCE { + type ATTRIBUTE.&id({IOSet}), + values SET SIZE (1..MAX) OF ATTRIBUTE.&Type({IOSet}{@type}) +} + +CertificateRevocationLists ::= + SET OF CertificateList + +Certificates ::= + SEQUENCE OF Certificate + +CRLSequence ::= + SEQUENCE OF CertificateList + +ContentEncryptionAlgorithmIdentifier ::= + AlgorithmIdentifierPKSC-7 {{ContentEncryptionAlgorithms}} + +ContentEncryptionAlgorithms ALGORITHM ::= { + ... -- add any application-specific algorithms here +} + +DigestAlgorithmIdentifier ::= + AlgorithmIdentifierPKSC-7 {{DigestAlgorithms}} + +DigestAlgorithms ALGORITHM ::= { + ... -- add any application-specific algorithms here +} + +DigestEncryptionAlgorithmIdentifier ::= + AlgorithmIdentifierPKSC-7 {{DigestEncryptionAlgorithms}} + +DigestEncryptionAlgorithms ALGORITHM ::= { + ... -- add any application-specific algorithms here +} + +ExtendedCertificateOrCertificate ::= CHOICE { + certificate Certificate, -- X.509 + extendedCertificate [0] IMPLICIT ExtendedCertificate -- PKCS#6 +} + +ExtendedCertificate ::= Certificate -- cheating + +ExtendedCertificatesAndCertificates ::= + SET OF ExtendedCertificateOrCertificate + +IssuerAndSerialNumber ::= SEQUENCE { + issuer Name, + serialNumber CertificateSerialNumber +} + +KeyEncryptionAlgorithmIdentifier ::= + AlgorithmIdentifierPKSC-7 {{KeyEncryptionAlgorithms}} + +KeyEncryptionAlgorithms ALGORITHM ::= { + ... -- add any application-specific algorithms here +} + +-- +-- 7. General syntax +-- + +ContentInfo ::= SEQUENCE { +-- contentType ContentType, + contentType CONTENTS.&id({Contents}), + content [0] EXPLICIT CONTENTS.&Type({Contents}{@contentType}) +OPTIONAL +} + +CONTENTS ::= TYPE-IDENTIFIER + +Contents CONTENTS ::= { + {Data IDENTIFIED BY data} | + {SignedData IDENTIFIED BY signedData} | + {EnvelopedData IDENTIFIED BY envelopedData} | + {SignedAndEnvelopedData IDENTIFIED BY signedAndEnvelopedData} | + {DigestedData IDENTIFIED BY digestedData} | + {EncryptedData IDENTIFIED BY encryptedData}, + ... -- add any application-specific types/contents here +} + +ContentType ::= CONTENTS.&id({Contents}) + +-- +-- 8. Data content type +-- + +Data ::= OCTET STRING + +-- +-- 9. Signed-data content type +-- + +SignedData ::= SEQUENCE { +-- version INTEGER {sdVer1(1), sdVer2(2)} (sdVer1 | sdVer2), + version INTEGER {sdVer1(1), sdVer2(2)}, + digestAlgorithms + DigestAlgorithmIdentifiers, + contentInfo ContentInfo, + certificates CHOICE { + certSet [0] IMPLICIT ExtendedCertificatesAndCertificates, + certSequence [2] IMPLICIT Certificates + } OPTIONAL, + crls CHOICE { + crlSet [1] IMPLICIT CertificateRevocationLists, + crlSequence [3] IMPLICIT CRLSequence + } OPTIONAL, + signerInfos SignerInfos +} (WITH COMPONENTS { ..., version (sdVer1), + digestAlgorithms (WITH COMPONENTS { ..., daSet PRESENT }), + certificates (WITH COMPONENTS { ..., certSequence ABSENT }), + crls (WITH COMPONENTS { ..., crlSequence ABSENT }), + signerInfos (WITH COMPONENTS { ..., siSet PRESENT }) + } | + WITH COMPONENTS { ..., version (sdVer2), + digestAlgorithms (WITH COMPONENTS { ..., daSequence PRESENT }), + certificates (WITH COMPONENTS { ..., certSet ABSENT }), + crls (WITH COMPONENTS { ..., crlSet ABSENT }), + signerInfos (WITH COMPONENTS { ..., siSequence PRESENT }) +}) + +SignerInfos ::= CHOICE { + siSet SET OF SignerInfo, + siSequence SEQUENCE OF SignerInfo +} + +DigestAlgorithmIdentifiers ::= CHOICE { + daSet SET OF DigestAlgorithmIdentifier, + daSequence SEQUENCE OF DigestAlgorithmIdentifier +} + +SignerInfo ::= SEQUENCE { +-- version INTEGER {siVer1(1), siVer2(2)} (siVer1 | siVer2), + version INTEGER {siVer1(1), siVer2(2)}, + issuerAndSerialNumber + IssuerAndSerialNumber, + digestAlgorithm DigestAlgorithmIdentifier, + authenticatedAttributes CHOICE { + aaSet [0] IMPLICIT SET OF AttributePKCS-7 {{Authenticated}}, + aaSequence [2] EXPLICIT SEQUENCE OF AttributePKCS-7 {{Authenticated}} + -- Explicit because easier to compute digest on sequence of attributes and then reuse + -- encoded sequence in aaSequence. + } OPTIONAL, + digestEncryptionAlgorithm + DigestEncryptionAlgorithmIdentifier, + encryptedDigest EncryptedDigest, + unauthenticatedAttributes CHOICE { + uaSet [1] IMPLICIT SET OF AttributePKCS-7 {{Unauthenticated}}, + uaSequence [3] IMPLICIT SEQUENCE OF AttributePKCS-7 {{Unauthenticated}} + } OPTIONAL +} (WITH COMPONENTS { ..., version (siVer1), + authenticatedAttributes (WITH COMPONENTS { ..., aaSequence ABSENT }), + unauthenticatedAttributes (WITH COMPONENTS { ..., uaSequence ABSENT }) +} | WITH COMPONENTS { ..., version (siVer2), + authenticatedAttributes (WITH COMPONENTS { ..., aaSet ABSENT }), + unauthenticatedAttributes (WITH COMPONENTS { ..., uaSet ABSENT }) +}) + +Authenticated ATTRIBUTE ::= { + contentType | + messageDigest, + ..., -- add application-specific attributes here + signingTime +} + +Unauthenticated ATTRIBUTE ::= { + contentType | + messageDigest, + ..., -- add application-specific attributes here + counterSignature +-- ..., add application-specific attributes here +-- counterSignature +} + +EncryptedDigest ::= OCTET STRING + +DigestInfo ::= SEQUENCE { + digestAlgorithm DigestAlgorithmIdentifier, + digest Digest +} + +Digest ::= OCTET STRING + +-- +-- 10. Enveloped-data content type +-- + +EnvelopedData ::= SEQUENCE { +-- version INTEGER {edVer0(0), edVer1(1)} (edVer0 | edVer1), + version INTEGER {edVer0(0), edVer1(1)}, + recipientInfos RecipientInfos, + encryptedContentInfo + EncryptedContentInfo +} (WITH COMPONENTS { ..., version (edVer0), + recipientInfos (WITH COMPONENTS { ..., riSet PRESENT }) +} | WITH COMPONENTS { ..., version (edVer1), + recipientInfos (WITH COMPONENTS { ..., riSequence PRESENT }) +}) + +RecipientInfos ::= CHOICE { + riSet SET OF RecipientInfo, + riSequence SEQUENCE OF RecipientInfo +} + +EncryptedContentInfo ::= SEQUENCE { + contentType ContentType, + contentEncryptionAlgorithm + ContentEncryptionAlgorithmIdentifier, + encryptedContent + [0] IMPLICIT EncryptedContent OPTIONAL +} + +EncryptedContent ::= OCTET STRING + +RecipientInfo ::= SEQUENCE { +-- version INTEGER {riVer0(0)} (riVer0), + version INTEGER {riVer0(0)}, + issuerAndSerialNumber + IssuerAndSerialNumber, + keyEncryptionAlgorithm + KeyEncryptionAlgorithmIdentifier, + encryptedKey EncryptedKey +} + +EncryptedKey ::= OCTET STRING + +-- +-- 11. Signed-and-enveloped-data content type +-- + +SignedAndEnvelopedData ::= SEQUENCE { +-- version INTEGER {seVer1(1), seVer2(2)} (seVer1 | seVer2), + version INTEGER {seVer1(1), seVer2(2)}, + recipientInfos RecipientInfos, + digestAlgorithms + DigestAlgorithmIdentifiers, + encryptedContentInfo + EncryptedContentInfo, + certificates CHOICE { + certSet [0] IMPLICIT ExtendedCertificatesAndCertificates, + certSequence [2] IMPLICIT Certificates + } OPTIONAL, + crls CHOICE { + crlSet [1] IMPLICIT CertificateRevocationLists, + crlSequence [3] IMPLICIT CRLSequence + } OPTIONAL, + signerInfos SignerInfos +} (WITH COMPONENTS { ..., version (seVer1), + recipientInfos (WITH COMPONENTS { ..., riSet PRESENT }), + digestAlgorithms (WITH COMPONENTS { ..., daSet PRESENT }), + certificates (WITH COMPONENTS { ..., certSequence ABSENT }), + crls (WITH COMPONENTS { ..., crlSequence ABSENT }), + signerInfos (WITH COMPONENTS { ..., siSet PRESENT }) +} | + WITH COMPONENTS { ..., version (seVer2), + recipientInfos (WITH COMPONENTS { ..., riSequence PRESENT }), + digestAlgorithms (WITH COMPONENTS { ..., daSequence PRESENT }), + certificates (WITH COMPONENTS { ..., certSet ABSENT }), + crls (WITH COMPONENTS { ..., crlSet ABSENT }), + signerInfos (WITH COMPONENTS { ..., siSequence PRESENT }) +}) + +-- +-- 12. Digested-data content type +--pbeWithSHAAnd3-KeyTripleDES-CBC + +DigestedData ::= SEQUENCE { +-- version INTEGER {ddVer0(0)} (ddVer0), + version INTEGER {ddVer0(0)}, + digestAlgorithm DigestAlgorithmIdentifier, + contentInfo ContentInfo, + digest Digest +} + +-- +-- 13. Encrypted-data content type +-- + +EncryptedData ::= SEQUENCE { +-- version INTEGER {edVer0(0)} (edVer0), + version INTEGER {edVer0(0)}, + encryptedContentInfo EncryptedContentInfo +} + +-- +-- 14. Object Identifiers +-- + +pkcs-7 OBJECT IDENTIFIER ::= + { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 7 } +data OBJECT IDENTIFIER ::= { pkcs-7 1 } +signedData OBJECT IDENTIFIER ::= { pkcs-7 2 } +envelopedData OBJECT IDENTIFIER ::= { pkcs-7 3 } +signedAndEnvelopedData OBJECT IDENTIFIER ::= { pkcs-7 4 } +digestedData OBJECT IDENTIFIER ::= { pkcs-7 5 } +encryptedData OBJECT IDENTIFIER ::= { pkcs-7 6 } + +END diff --git a/lib/public_key/asn1/PKIX1Explicit88.asn1 b/lib/public_key/asn1/PKIX1Explicit88.asn1 index 03e9da3e05..91758d7269 100644 --- a/lib/public_key/asn1/PKIX1Explicit88.asn1 +++ b/lib/public_key/asn1/PKIX1Explicit88.asn1 @@ -206,13 +206,12 @@ DomainComponent ::= IA5String -- Legacy attributes -pkcs-9 OBJECT IDENTIFIER ::= - { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 9 } - id-emailAddress AttributeType ::= { pkcs-9 1 } EmailAddress ::= IA5String (SIZE (1..ub-emailaddress-length)) +-- Legacy attributes + -- naming data types -- Name ::= CHOICE { -- only one possibility for now -- diff --git a/lib/public_key/asn1/SelectedAttributeTypes.asn1 b/lib/public_key/asn1/SelectedAttributeTypes.asn1 new file mode 100644 index 0000000000..3ef7077370 --- /dev/null +++ b/lib/public_key/asn1/SelectedAttributeTypes.asn1 @@ -0,0 +1,1575 @@ +SelectedAttributeTypes {joint-iso-itu-t ds(5) module(1) + selectedAttributeTypes(5) 6} DEFINITIONS ::= +BEGIN + +-- EXPORTS All +-- The types and values defined in this module are exported for use in the other ASN.1 modules contained +-- within the Directory Specifications, and for the use of other applications which will use them to access +-- Directory services. Other applications may use them for their own purposes, but this will not constrain +-- extensions and modifications needed to maintain or improve the Directory service. +IMPORTS + -- from ITU-T Rec. X.501 | ISO/IEC 9594-2 + directoryAbstractService, id-at, id-avc, id-cat, id-mr, id-not, id-pr, + informationFramework, serviceAdministration + FROM UsefulDefinitions {joint-iso-itu-t ds(5) module(1) + usefulDefinitions(0) 6} + Attribute{}, ATTRIBUTE, AttributeType, AttributeValueAssertion, CONTEXT, + ContextAssertion, DistinguishedName, distinguishedNameMatch, + MAPPING-BASED-MATCHING{}, MATCHING-RULE, OBJECT-CLASS, + objectIdentifierMatch, SupportedAttributes + FROM InformationFramework informationFramework + AttributeCombination, ContextCombination, MRMapping + FROM ServiceAdministration serviceAdministration + -- from ITU-T Rec. X.511 | ISO/IEC 9594-3 + FilterItem, HierarchySelections, SearchControlOptions, ServiceControlOptions + FROM DirectoryAbstractService directoryAbstractService + -- from ITU-T Rec. X.411 | ISO/IEC 10021-4 + G3FacsimileNonBasicParameters + FROM MTSAbstractService {joint-iso-itu-t mhs(6) mts(3) modules(0) + mts-abstract-service(1) version-1999(1)}; + +/*from IETF RFC 3727 + +The following import is provided for information only (see 7.2.16), it is not referenced by any ASN.1 construct within these Directory Specifications. Note that the ASN.1 module in RFC 3727 imports from the InformationFramework module of edition 4 of ITU-T Rec. X.501 | ISO/IEC 9594-2. A specification importing from both these Directory Specifications and from RFC 3727 should take corrective actions, e.g., by making a copy of the ASN.1 module of +RFC 3727 and then update the IMPORT statement. + + allComponentsMatch, componentFilterMatch, directoryComponentsMatch, presentMatch, rdnMatch + FROM ComponentMatching {iso(1) 2 36 79672281 xed(3) module (0) + component-matching(4)} */ +-- Directory string type +UnboundedDirectoryString ::= CHOICE { + teletexString TeletexString(SIZE (1..MAX)), + printableString PrintableString(SIZE (1..MAX)), + bmpString BMPString(SIZE (1..MAX)), + universalString UniversalString(SIZE (1..MAX)), + uTF8String UTF8String(SIZE (1..MAX)) +} + +DirectoryString{INTEGER:maxSize} ::= CHOICE { + teletexString TeletexString(SIZE (1..maxSize)), + printableString PrintableString(SIZE (1..maxSize)), + bmpString BMPString(SIZE (1..maxSize)), + universalString UniversalString(SIZE (1..maxSize)), + uTF8String UTF8String(SIZE (1..maxSize)) +} + +-- Attribute types +knowledgeInformation ATTRIBUTE ::= { + WITH SYNTAX UnboundedDirectoryString + EQUALITY MATCHING RULE caseIgnoreMatch + ID id-at-knowledgeInformation +} + +name ATTRIBUTE ::= { + WITH SYNTAX UnboundedDirectoryString + EQUALITY MATCHING RULE caseIgnoreMatch + SUBSTRINGS MATCHING RULE caseIgnoreSubstringsMatch + ID id-at-name +} + +commonName ATTRIBUTE ::= { + SUBTYPE OF name + WITH SYNTAX UnboundedDirectoryString + ID id-at-commonName +} + +surname ATTRIBUTE ::= { + SUBTYPE OF name + WITH SYNTAX UnboundedDirectoryString + ID id-at-surname +} + +givenName ATTRIBUTE ::= { + SUBTYPE OF name + WITH SYNTAX UnboundedDirectoryString + ID id-at-givenName +} + +initials ATTRIBUTE ::= { + SUBTYPE OF name + WITH SYNTAX UnboundedDirectoryString + ID id-at-initials +} + +generationQualifier ATTRIBUTE ::= { + SUBTYPE OF name + WITH SYNTAX UnboundedDirectoryString + ID id-at-generationQualifier +} + +uniqueIdentifier ATTRIBUTE ::= { + WITH SYNTAX UniqueIdentifier + EQUALITY MATCHING RULE bitStringMatch + ID id-at-uniqueIdentifier +} + +UniqueIdentifier ::= BIT STRING + +dnQualifier ATTRIBUTE ::= { + WITH SYNTAX PrintableString + EQUALITY MATCHING RULE caseIgnoreMatch + ORDERING MATCHING RULE caseIgnoreOrderingMatch + SUBSTRINGS MATCHING RULE caseIgnoreSubstringsMatch + ID id-at-dnQualifier +} + +serialNumber ATTRIBUTE ::= { + WITH SYNTAX PrintableString(SIZE (1..MAX)) + EQUALITY MATCHING RULE caseIgnoreMatch + SUBSTRINGS MATCHING RULE caseIgnoreSubstringsMatch + ID id-at-serialNumber +} + +pseudonym ATTRIBUTE ::= { + SUBTYPE OF name + WITH SYNTAX UnboundedDirectoryString + ID id-at-pseudonym +} + +uUIDPair ATTRIBUTE ::= { + WITH SYNTAX UUIDPair + EQUALITY MATCHING RULE uUIDPairMatch + ID id-at-uuidpair +} + +UUIDPair ::= SEQUENCE {issuerUUID UUID, + subjectUUID UUID +} + +UUID ::= OCTET STRING(SIZE (16)) -- UUID format only + + +countryName ATTRIBUTE ::= { + SUBTYPE OF name + WITH SYNTAX CountryName + SINGLE VALUE TRUE + ID id-at-countryName +} + +CountryName ::= PrintableString(SIZE (2)) -- ISO 3166 codes only + + +localityName ATTRIBUTE ::= { + SUBTYPE OF name + WITH SYNTAX UnboundedDirectoryString + ID id-at-localityName +} + +collectiveLocalityName ATTRIBUTE ::= { + SUBTYPE OF localityName + COLLECTIVE TRUE + ID id-at-collectiveLocalityName +} + +stateOrProvinceName ATTRIBUTE ::= { + SUBTYPE OF name + WITH SYNTAX UnboundedDirectoryString + ID id-at-stateOrProvinceName +} + +collectiveStateOrProvinceName ATTRIBUTE ::= { + SUBTYPE OF stateOrProvinceName + COLLECTIVE TRUE + ID id-at-collectiveStateOrProvinceName +} + +streetAddress ATTRIBUTE ::= { + WITH SYNTAX UnboundedDirectoryString + EQUALITY MATCHING RULE caseIgnoreMatch + SUBSTRINGS MATCHING RULE caseIgnoreSubstringsMatch + ID id-at-streetAddress +} + +collectiveStreetAddress ATTRIBUTE ::= { + SUBTYPE OF streetAddress + COLLECTIVE TRUE + ID id-at-collectiveStreetAddress +} + +houseIdentifier ATTRIBUTE ::= { + WITH SYNTAX UnboundedDirectoryString + EQUALITY MATCHING RULE caseIgnoreMatch + SUBSTRINGS MATCHING RULE caseIgnoreSubstringsMatch + ID id-at-houseIdentifier +} + +organizationName ATTRIBUTE ::= { + SUBTYPE OF name + WITH SYNTAX UnboundedDirectoryString + ID id-at-organizationName +} + +collectiveOrganizationName ATTRIBUTE ::= { + SUBTYPE OF organizationName + COLLECTIVE TRUE + ID id-at-collectiveOrganizationName +} + +organizationalUnitName ATTRIBUTE ::= { + SUBTYPE OF name + WITH SYNTAX UnboundedDirectoryString + ID id-at-organizationalUnitName +} + +collectiveOrganizationalUnitName ATTRIBUTE ::= { + SUBTYPE OF organizationalUnitName + COLLECTIVE TRUE + ID id-at-collectiveOrganizationalUnitName +} + +title ATTRIBUTE ::= { + SUBTYPE OF name + WITH SYNTAX UnboundedDirectoryString + ID id-at-title +} + +description ATTRIBUTE ::= { + WITH SYNTAX UnboundedDirectoryString + EQUALITY MATCHING RULE caseIgnoreMatch + SUBSTRINGS MATCHING RULE caseIgnoreSubstringsMatch + ID id-at-description +} + +searchGuide ATTRIBUTE ::= {WITH SYNTAX Guide + ID id-at-searchGuide +} + +Guide ::= SET { + objectClass [0] OBJECT-CLASS.&id OPTIONAL, + criteria [1] Criteria +} + +Criteria ::= CHOICE { + type [0] CriteriaItem, + and [1] SET OF Criteria, + or [2] SET OF Criteria, + not [3] Criteria +} + +CriteriaItem ::= CHOICE { + equality [0] AttributeType, + substrings [1] AttributeType, + greaterOrEqual [2] AttributeType, + lessOrEqual [3] AttributeType, + approximateMatch [4] AttributeType +} + +enhancedSearchGuide ATTRIBUTE ::= { + WITH SYNTAX EnhancedGuide + ID id-at-enhancedSearchGuide +} + +EnhancedGuide ::= SEQUENCE { + objectClass [0] OBJECT-CLASS.&id, + criteria [1] Criteria, + subset + [2] INTEGER {baseObject(0), oneLevel(1), wholeSubtree(2)} DEFAULT oneLevel +} + +businessCategory ATTRIBUTE ::= { + WITH SYNTAX UnboundedDirectoryString + EQUALITY MATCHING RULE caseIgnoreMatch + SUBSTRINGS MATCHING RULE caseIgnoreSubstringsMatch + ID id-at-businessCategory +} + +postalAddress ATTRIBUTE ::= { + WITH SYNTAX PostalAddress + EQUALITY MATCHING RULE caseIgnoreListMatch + SUBSTRINGS MATCHING RULE caseIgnoreListSubstringsMatch + ID id-at-postalAddress +} + +PostalAddress ::= SEQUENCE SIZE (1..MAX) OF UnboundedDirectoryString + +collectivePostalAddress ATTRIBUTE ::= { + SUBTYPE OF postalAddress + COLLECTIVE TRUE + ID id-at-collectivePostalAddress +} + +postalCode ATTRIBUTE ::= { + WITH SYNTAX UnboundedDirectoryString + EQUALITY MATCHING RULE caseIgnoreMatch + SUBSTRINGS MATCHING RULE caseIgnoreSubstringsMatch + ID id-at-postalCode +} + +collectivePostalCode ATTRIBUTE ::= { + SUBTYPE OF postalCode + COLLECTIVE TRUE + ID id-at-collectivePostalCode +} + +postOfficeBox ATTRIBUTE ::= { + WITH SYNTAX UnboundedDirectoryString + EQUALITY MATCHING RULE caseIgnoreMatch + SUBSTRINGS MATCHING RULE caseIgnoreSubstringsMatch + ID id-at-postOfficeBox +} + +collectivePostOfficeBox ATTRIBUTE ::= { + SUBTYPE OF postOfficeBox + COLLECTIVE TRUE + ID id-at-collectivePostOfficeBox +} + +physicalDeliveryOfficeName ATTRIBUTE ::= { + WITH SYNTAX UnboundedDirectoryString + EQUALITY MATCHING RULE caseIgnoreMatch + SUBSTRINGS MATCHING RULE caseIgnoreSubstringsMatch + ID id-at-physicalDeliveryOfficeName +} + +collectivePhysicalDeliveryOfficeName ATTRIBUTE ::= { + SUBTYPE OF physicalDeliveryOfficeName + COLLECTIVE TRUE + ID id-at-collectivePhysicalDeliveryOfficeName +} + +telephoneNumber ATTRIBUTE ::= { + WITH SYNTAX TelephoneNumber + EQUALITY MATCHING RULE telephoneNumberMatch + SUBSTRINGS MATCHING RULE telephoneNumberSubstringsMatch + ID id-at-telephoneNumber +} + +TelephoneNumber ::= PrintableString(SIZE (1..ub-telephone-number)) + +-- String complying with ITU-T Rec. E.123 only +ub-telephone-number INTEGER ::= + 32 + +collectiveTelephoneNumber ATTRIBUTE ::= { + SUBTYPE OF telephoneNumber + COLLECTIVE TRUE + ID id-at-collectiveTelephoneNumber +} + +telexNumber ATTRIBUTE ::= { + WITH SYNTAX TelexNumber + ID id-at-telexNumber +} + +TelexNumber ::= SEQUENCE { + telexNumber PrintableString(SIZE (1..ub-telex-number)), + countryCode PrintableString(SIZE (1..ub-country-code)), + answerback PrintableString(SIZE (1..ub-answerback)) +} + +ub-telex-number INTEGER ::= 14 + +ub-country-code INTEGER ::= 4 + +ub-answerback INTEGER ::= 8 + +collectiveTelexNumber ATTRIBUTE ::= { + SUBTYPE OF telexNumber + COLLECTIVE TRUE + ID id-at-collectiveTelexNumber +} + +facsimileTelephoneNumber ATTRIBUTE ::= { + WITH SYNTAX FacsimileTelephoneNumber + EQUALITY MATCHING RULE facsimileNumberMatch + SUBSTRINGS MATCHING RULE facsimileNumberSubstringsMatch + ID id-at-facsimileTelephoneNumber +} + +FacsimileTelephoneNumber ::= SEQUENCE { + telephoneNumber TelephoneNumber, + parameters G3FacsimileNonBasicParameters OPTIONAL +} + +collectiveFacsimileTelephoneNumber ATTRIBUTE ::= { + SUBTYPE OF facsimileTelephoneNumber + COLLECTIVE TRUE + ID id-at-collectiveFacsimileTelephoneNumber +} + +x121Address ATTRIBUTE ::= { + WITH SYNTAX X121Address + EQUALITY MATCHING RULE numericStringMatch + SUBSTRINGS MATCHING RULE numericStringSubstringsMatch + ID id-at-x121Address +} + +X121Address ::= NumericString(SIZE (1..ub-x121-address)) + +-- String as defined by ITU-T Rec. X.121 +ub-x121-address INTEGER ::= 15 + +internationalISDNNumber ATTRIBUTE ::= { + WITH SYNTAX InternationalISDNNumber + EQUALITY MATCHING RULE numericStringMatch + SUBSTRINGS MATCHING RULE numericStringSubstringsMatch + ID id-at-internationalISDNNumber +} + +InternationalISDNNumber ::= + NumericString(SIZE (1..ub-international-isdn-number)) + +-- String complying with ITU-T Rec. E.164 only +ub-international-isdn-number INTEGER ::= + 16 + +collectiveInternationalISDNNumber ATTRIBUTE ::= { + SUBTYPE OF internationalISDNNumber + COLLECTIVE TRUE + ID id-at-collectiveInternationalISDNNumber +} + +registeredAddress ATTRIBUTE ::= { + SUBTYPE OF postalAddress + WITH SYNTAX PostalAddress + ID id-at-registeredAddress +} + +destinationIndicator ATTRIBUTE ::= { + WITH SYNTAX DestinationIndicator + EQUALITY MATCHING RULE caseIgnoreMatch + SUBSTRINGS MATCHING RULE caseIgnoreSubstringsMatch + ID id-at-destinationIndicator +} + +DestinationIndicator ::= PrintableString(SIZE (1..MAX)) + +-- alphabetical characters only +communicationsService ATTRIBUTE ::= { + WITH SYNTAX CommunicationsService + EQUALITY MATCHING RULE objectIdentifierMatch + ID id-at-communicationsService +} + +CommunicationsService ::= OBJECT IDENTIFIER + +communicationsNetwork ATTRIBUTE ::= { + WITH SYNTAX CommunicationsNetwork + EQUALITY MATCHING RULE objectIdentifierMatch + SINGLE VALUE TRUE + ID id-at-communicationsNetwork +} + +CommunicationsNetwork ::= OBJECT IDENTIFIER + +preferredDeliveryMethod ATTRIBUTE ::= { + WITH SYNTAX PreferredDeliveryMethod + SINGLE VALUE TRUE + ID id-at-preferredDeliveryMethod +} + +PreferredDeliveryMethod ::= + SEQUENCE OF + INTEGER {any-delivery-method(0), mhs-delivery(1), physical-delivery(2), + telex-delivery(3), teletex-delivery(4), g3-facsimile-delivery(5), + g4-facsimile-delivery(6), ia5-terminal-delivery(7), + videotex-delivery(8), telephone-delivery(9)} + +presentationAddress ATTRIBUTE ::= { + WITH SYNTAX PresentationAddress + EQUALITY MATCHING RULE presentationAddressMatch + SINGLE VALUE TRUE + ID id-at-presentationAddress +} + +PresentationAddress ::= SEQUENCE { + pSelector [0] OCTET STRING OPTIONAL, + sSelector [1] OCTET STRING OPTIONAL, + tSelector [2] OCTET STRING OPTIONAL, + nAddresses [3] SET SIZE (1..MAX) OF OCTET STRING +} + +supportedApplicationContext ATTRIBUTE ::= { + WITH SYNTAX OBJECT IDENTIFIER + EQUALITY MATCHING RULE objectIdentifierMatch + ID id-at-supportedApplicationContext +} + +protocolInformation ATTRIBUTE ::= { + WITH SYNTAX ProtocolInformation + EQUALITY MATCHING RULE protocolInformationMatch + ID id-at-protocolInformation +} + +ProtocolInformation ::= SEQUENCE { + nAddress OCTET STRING, + profiles SET OF OBJECT IDENTIFIER +} + +distinguishedName ATTRIBUTE ::= { + WITH SYNTAX DistinguishedName + EQUALITY MATCHING RULE distinguishedNameMatch + ID id-at-distinguishedName +} + +member ATTRIBUTE ::= {SUBTYPE OF distinguishedName + ID id-at-member +} + +uniqueMember ATTRIBUTE ::= { + WITH SYNTAX NameAndOptionalUID + EQUALITY MATCHING RULE uniqueMemberMatch + ID id-at-uniqueMember +} + +NameAndOptionalUID ::= SEQUENCE { + dn DistinguishedName, + uid UniqueIdentifier OPTIONAL +} + +owner ATTRIBUTE ::= {SUBTYPE OF distinguishedName + ID id-at-owner +} + +roleOccupant ATTRIBUTE ::= { + SUBTYPE OF distinguishedName + ID id-at-roleOccupant +} + +seeAlso ATTRIBUTE ::= {SUBTYPE OF distinguishedName + ID id-at-seeAlso +} + +dmdName ATTRIBUTE ::= { + SUBTYPE OF name + WITH SYNTAX UnboundedDirectoryString + ID id-at-dmdName +} + +-- Attributes for tag-based identification +tagOid ATTRIBUTE ::= { + WITH SYNTAX OBJECT IDENTIFIER + EQUALITY MATCHING RULE objectIdentifierMatch + SINGLE VALUE TRUE + ID id-at-tagOid +} + +uiiFormat ATTRIBUTE ::= { + WITH SYNTAX UnboundedDirectoryString + SINGLE VALUE TRUE + ID id-at-uiiFormat +} + +uiiInUrn ATTRIBUTE ::= { + WITH SYNTAX UTF8String + EQUALITY MATCHING RULE caseExactMatch + SINGLE VALUE TRUE + ID id-at-uiiInUrn +} + +contentUri ATTRIBUTE ::= { + WITH SYNTAX UnboundedDirectoryString + ID id-at-contentUri +} + +-- Notification attributes +dSAProblem ATTRIBUTE ::= { + WITH SYNTAX OBJECT IDENTIFIER + EQUALITY MATCHING RULE objectIdentifierMatch + ID id-not-dSAProblem +} + +searchServiceProblem ATTRIBUTE ::= { + WITH SYNTAX OBJECT IDENTIFIER + EQUALITY MATCHING RULE objectIdentifierMatch + SINGLE VALUE TRUE + ID id-not-searchServiceProblem +} + +serviceType ATTRIBUTE ::= { + WITH SYNTAX OBJECT IDENTIFIER + EQUALITY MATCHING RULE objectIdentifierMatch + SINGLE VALUE TRUE + ID id-not-serviceType +} + +attributeTypeList ATTRIBUTE ::= { + WITH SYNTAX OBJECT IDENTIFIER + EQUALITY MATCHING RULE objectIdentifierMatch + ID id-not-attributeTypeList +} + +matchingRuleList ATTRIBUTE ::= { + WITH SYNTAX OBJECT IDENTIFIER + EQUALITY MATCHING RULE objectIdentifierMatch + ID id-not-matchingRuleList +} + +filterItem ATTRIBUTE ::= { + WITH SYNTAX FilterItem + ID id-not-filterItem +} + +attributeCombinations ATTRIBUTE ::= { + WITH SYNTAX AttributeCombination + ID id-not-attributeCombinations +} + +contextTypeList ATTRIBUTE ::= { + WITH SYNTAX OBJECT IDENTIFIER + EQUALITY MATCHING RULE objectIdentifierMatch + ID id-not-contextTypeList +} + +contextList ATTRIBUTE ::= { + WITH SYNTAX ContextAssertion + ID id-not-contextList +} + +contextCombinations ATTRIBUTE ::= { + WITH SYNTAX ContextCombination + ID id-not-contextCombinations +} + +hierarchySelectList ATTRIBUTE ::= { + WITH SYNTAX HierarchySelections + SINGLE VALUE TRUE + ID id-not-hierarchySelectList +} + +searchControlOptionsList ATTRIBUTE ::= { + WITH SYNTAX SearchControlOptions + SINGLE VALUE TRUE + ID id-not-searchControlOptionsList +} + +serviceControlOptionsList ATTRIBUTE ::= { + WITH SYNTAX ServiceControlOptions + SINGLE VALUE TRUE + ID id-not-serviceControlOptionsList +} + +multipleMatchingLocalities ATTRIBUTE ::= { + WITH SYNTAX MultipleMatchingLocalities + ID id-not-multipleMatchingLocalities +} + +MultipleMatchingLocalities ::= SEQUENCE { + matchingRuleUsed MATCHING-RULE.&id OPTIONAL, + attributeList SEQUENCE OF AttributeValueAssertion +} + +proposedRelaxation ATTRIBUTE ::= { + WITH SYNTAX MRMappings + ID id-not-proposedRelaxation +} + +MRMappings ::= SEQUENCE OF MRMapping + +appliedRelaxation ATTRIBUTE ::= { + WITH SYNTAX OBJECT IDENTIFIER + EQUALITY MATCHING RULE objectIdentifierMatch + ID id-not-appliedRelaxation +} + +-- Matching rules +caseExactMatch MATCHING-RULE ::= { + SYNTAX UnboundedDirectoryString + ID id-mr-caseExactMatch +} + +caseIgnoreMatch MATCHING-RULE ::= { + SYNTAX UnboundedDirectoryString + ID id-mr-caseIgnoreMatch +} + +caseExactOrderingMatch MATCHING-RULE ::= { + SYNTAX UnboundedDirectoryString + ID id-mr-caseExactOrderingMatch +} + +caseIgnoreOrderingMatch MATCHING-RULE ::= { + SYNTAX UnboundedDirectoryString + ID id-mr-caseIgnoreOrderingMatch +} + +caseExactSubstringsMatch MATCHING-RULE ::= { + SYNTAX SubstringAssertion -- only the PrintableString choice + ID id-mr-caseExactSubstringsMatch +} + +caseIgnoreSubstringsMatch MATCHING-RULE ::= { + SYNTAX SubstringAssertion + ID id-mr-caseIgnoreSubstringsMatch +} + +SubstringAssertion ::= + SEQUENCE OF + CHOICE {initial [0] UnboundedDirectoryString, + any [1] UnboundedDirectoryString, + final [2] UnboundedDirectoryString, + control Attribute{{SupportedAttributes}} + } -- Used to specify interpretation of the following items + +-- at most one initial and one final component +numericStringMatch MATCHING-RULE ::= { + SYNTAX NumericString + ID id-mr-numericStringMatch +} + +numericStringOrderingMatch MATCHING-RULE ::= { + SYNTAX NumericString + ID id-mr-numericStringOrderingMatch +} + +numericStringSubstringsMatch MATCHING-RULE ::= { + SYNTAX SubstringAssertion + ID id-mr-numericStringSubstringsMatch +} + +caseIgnoreListMatch MATCHING-RULE ::= { + SYNTAX CaseIgnoreList + ID id-mr-caseIgnoreListMatch +} + +CaseIgnoreList ::= SEQUENCE OF UnboundedDirectoryString + +caseIgnoreListSubstringsMatch MATCHING-RULE ::= { + SYNTAX SubstringAssertion + ID id-mr-caseIgnoreListSubstringsMatch +} + +storedPrefixMatch MATCHING-RULE ::= { + SYNTAX UnboundedDirectoryString + ID id-mr-storedPrefixMatch +} + +booleanMatch MATCHING-RULE ::= {SYNTAX BOOLEAN + ID id-mr-booleanMatch +} + +integerMatch MATCHING-RULE ::= {SYNTAX INTEGER + ID id-mr-integerMatch +} + +integerOrderingMatch MATCHING-RULE ::= { + SYNTAX INTEGER + ID id-mr-integerOrderingMatch +} + +bitStringMatch MATCHING-RULE ::= { + SYNTAX BIT STRING + ID id-mr-bitStringMatch +} + +octetStringMatch MATCHING-RULE ::= { + SYNTAX OCTET STRING + ID id-mr-octetStringMatch +} + +octetStringOrderingMatch MATCHING-RULE ::= { + SYNTAX OCTET STRING + ID id-mr-octetStringOrderingMatch +} + +octetStringSubstringsMatch MATCHING-RULE ::= { + SYNTAX OctetSubstringAssertion + ID id-mr-octetStringSubstringsMatch +} + +OctetSubstringAssertion ::= + SEQUENCE OF + CHOICE {initial [0] OCTET STRING, + any [1] OCTET STRING, + final [2] OCTET STRING} + +-- at most one initial and one final component +telephoneNumberMatch MATCHING-RULE ::= { + SYNTAX TelephoneNumber + ID id-mr-telephoneNumberMatch +} + +telephoneNumberSubstringsMatch MATCHING-RULE ::= { + SYNTAX SubstringAssertion + ID id-mr-telephoneNumberSubstringsMatch +} + +presentationAddressMatch MATCHING-RULE ::= { + SYNTAX PresentationAddress + ID id-mr-presentationAddressMatch +} + +uniqueMemberMatch MATCHING-RULE ::= { + SYNTAX NameAndOptionalUID + ID id-mr-uniqueMemberMatch +} + +protocolInformationMatch MATCHING-RULE ::= { + SYNTAX OCTET STRING + ID id-mr-protocolInformationMatch +} + +facsimileNumberMatch MATCHING-RULE ::= { + SYNTAX TelephoneNumber + ID id-mr-facsimileNumberMatch +} + +facsimileNumberSubstringsMatch MATCHING-RULE ::= { + SYNTAX SubstringAssertion + ID id-mr-facsimileNumberSubstringsMatch +} + +uUIDPairMatch MATCHING-RULE ::= {SYNTAX UUIDPair + ID id-mr-uuidpairmatch +} + +uTCTimeMatch MATCHING-RULE ::= {SYNTAX UTCTime + ID id-mr-uTCTimeMatch +} + +uTCTimeOrderingMatch MATCHING-RULE ::= { + SYNTAX UTCTime + ID id-mr-uTCTimeOrderingMatch +} + +generalizedTimeMatch MATCHING-RULE ::= { + SYNTAX GeneralizedTime + -- as per 46.3 b) or c) of ITU-T Rec. X.680 | ISO/IEC 8824-1 + ID id-mr-generalizedTimeMatch +} + +generalizedTimeOrderingMatch MATCHING-RULE ::= { + SYNTAX GeneralizedTime + -- as per 46.3 b) or c) of ITU-T Rec. X.680 | ISO/IEC 8824-1 + ID id-mr-generalizedTimeOrderingMatch +} + +systemProposedMatch MATCHING-RULE ::= {ID id-mr-systemProposedMatch +} + +integerFirstComponentMatch MATCHING-RULE ::= { + SYNTAX INTEGER + ID id-mr-integerFirstComponentMatch +} + +objectIdentifierFirstComponentMatch MATCHING-RULE ::= { + SYNTAX OBJECT IDENTIFIER + ID id-mr-objectIdentifierFirstComponentMatch +} + +directoryStringFirstComponentMatch MATCHING-RULE ::= { + SYNTAX UnboundedDirectoryString + ID id-mr-directoryStringFirstComponentMatch +} + +wordMatch MATCHING-RULE ::= { + SYNTAX UnboundedDirectoryString + ID id-mr-wordMatch +} + +keywordMatch MATCHING-RULE ::= { + SYNTAX UnboundedDirectoryString + ID id-mr-keywordMatch +} + +generalWordMatch MATCHING-RULE ::= { + SYNTAX SubstringAssertion + ID id-mr-generalWordMatch +} + +sequenceMatchType ATTRIBUTE ::= { + WITH SYNTAX SequenceMatchType + SINGLE VALUE TRUE + ID id-cat-sequenceMatchType +} -- defaulting to sequenceExact + +SequenceMatchType ::= ENUMERATED { + sequenceExact(0), sequenceDeletion(1), sequenceRestrictedDeletion(2), + sequencePermutation(3), sequencePermutationAndDeletion(4), + sequenceProviderDefined(5)} + +wordMatchTypes ATTRIBUTE ::= { + WITH SYNTAX WordMatchTypes + SINGLE VALUE TRUE + ID id-cat-wordMatchType +} -- defaulting to wordExact + +WordMatchTypes ::= ENUMERATED { + wordExact(0), wordTruncated(1), wordPhonetic(2), wordProviderDefined(3) +} + +characterMatchTypes ATTRIBUTE ::= { + WITH SYNTAX CharacterMatchTypes + SINGLE VALUE TRUE + ID id-cat-characterMatchTypes +} + +CharacterMatchTypes ::= ENUMERATED { + characterExact(0), characterCaseIgnore(1), characterMapped(2)} + +selectedContexts ATTRIBUTE ::= { + WITH SYNTAX ContextAssertion + ID id-cat-selectedContexts +} + +approximateStringMatch MATCHING-RULE ::= {ID id-mr-approximateStringMatch +} + +ignoreIfAbsentMatch MATCHING-RULE ::= {ID id-mr-ignoreIfAbsentMatch +} + +nullMatch MATCHING-RULE ::= {ID id-mr-nullMatch +} + +ZONAL-MATCHING ::= + MAPPING-BASED-MATCHING{ZonalSelect, TRUE, ZonalResult, zonalMatch.&id} + +ZonalSelect ::= SEQUENCE OF AttributeType + +ZonalResult ::= ENUMERATED { + cannot-select-mapping(0), zero-mappings(2), multiple-mappings(3)} + +zonalMatch MATCHING-RULE ::= { + UNIQUE-MATCH-INDICATOR multipleMatchingLocalities + ID id-mr-zonalMatch +} + +-- Contexts +languageContext CONTEXT ::= { + WITH SYNTAX LanguageContextSyntax + ID id-avc-language +} + +LanguageContextSyntax ::= PrintableString(SIZE (2..3)) -- ISO 639-2 codes only + + +temporalContext CONTEXT ::= { + WITH SYNTAX TimeSpecification + ASSERTED AS TimeAssertion + ID id-avc-temporal +} + +TimeSpecification ::= SEQUENCE { + time + CHOICE {absolute + SEQUENCE {startTime [0] GeneralizedTime OPTIONAL, + endTime [1] GeneralizedTime OPTIONAL}, + periodic SET SIZE (1..MAX) OF Period}, + notThisTime BOOLEAN DEFAULT FALSE, + timeZone TimeZone OPTIONAL +} + +Period ::= SEQUENCE { + timesOfDay [0] SET SIZE (1..MAX) OF DayTimeBand OPTIONAL, + days + [1] CHOICE {intDay SET OF INTEGER, + bitDay + BIT STRING {sunday(0), monday(1), tuesday(2), wednesday(3), + thursday(4), friday(5), saturday(6)}, + dayOf XDayOf} OPTIONAL, + weeks + [2] CHOICE {allWeeks NULL, + intWeek SET OF INTEGER, + bitWeek + BIT STRING {week1(0), week2(1), week3(2), week4(3), week5(4)} + } OPTIONAL, + months + [3] CHOICE {allMonths NULL, + intMonth SET OF INTEGER, + bitMonth + BIT STRING {january(0), february(1), march(2), april(3), + may(4), june(5), july(6), august(7), + september(8), october(9), november(10), + december(11)}} OPTIONAL, + years [4] SET OF INTEGER(1000..MAX) OPTIONAL +} + +XDayOf ::= CHOICE { + first [1] NamedDay, + second [2] NamedDay, + third [3] NamedDay, + fourth [4] NamedDay, + fifth [5] NamedDay +} + +NamedDay ::= CHOICE { + intNamedDays + ENUMERATED {sunday(1), monday(2), tuesday(3), wednesday(4), thursday(5), + friday(6), saturday(7)}, + bitNamedDays + BIT STRING {sunday(0), monday(1), tuesday(2), wednesday(3), thursday(4), + friday(5), saturday(6)} +} + +DayTimeBand ::= SEQUENCE { + startDayTime [0] DayTime DEFAULT {hour 0}, + endDayTime [1] DayTime DEFAULT {hour 23, minute 59, second 59} +} + +DayTime ::= SEQUENCE { + hour [0] INTEGER(0..23), + minute [1] INTEGER(0..59) DEFAULT 0, + second [2] INTEGER(0..59) DEFAULT 0 +} + +TimeZone ::= INTEGER(-12..12) + +TimeAssertion ::= CHOICE { + now NULL, + at GeneralizedTime, + between + SEQUENCE {startTime [0] GeneralizedTime, + endTime [1] GeneralizedTime OPTIONAL, + entirely BOOLEAN DEFAULT FALSE} +} + +localeContext CONTEXT ::= { + WITH SYNTAX LocaleContextSyntax + ID id-avc-locale +} + +LocaleContextSyntax ::= CHOICE { + localeID1 OBJECT IDENTIFIER, + localeID2 UnboundedDirectoryString +} + +ldapAttributeOptionContext CONTEXT ::= { + WITH SYNTAX AttributeOptionList + ASSERTED AS AttributeOptionList + ABSENT-MATCH FALSE + ID id-avc-ldapAttributeOption +} + +AttributeOptionList ::= SEQUENCE OF UTF8String + +-- Object identifier assignments +-- object identifiers assigned in other modules are shown in comments +-- Attributes +-- id-at-objectClass OBJECT IDENTIFIER ::= {id-at 0} +-- id-at-aliasedEntryName OBJECT IDENTIFIER ::= {id-at 1} +-- id-at-encryptedAliasedEntryName OBJECT IDENTIFIER ::= {id-at 1 2} +id-at-knowledgeInformation OBJECT IDENTIFIER ::= + {id-at 2} + +id-at-commonName OBJECT IDENTIFIER ::= {id-at 3} + +-- id-at-encryptedCommonName OBJECT IDENTIFIER ::= {id-at 3 2} +id-at-surname OBJECT IDENTIFIER ::= + {id-at 4} + +-- id-at-encryptedSurname OBJECT IDENTIFIER ::= {id-at 4 2} +id-at-serialNumber OBJECT IDENTIFIER ::= + {id-at 5} + +-- id-at-encryptedSerialNumbe r OBJECT IDENTIFIER ::= {id-at 5 2} +id-at-countryName OBJECT IDENTIFIER ::= + {id-at 6} + +-- id-at-encryptedCountryName OBJECT IDENTIFIER ::= {id-at 6 2} +id-at-localityName OBJECT IDENTIFIER ::= + {id-at 7} + +-- id-at-encryptedLocalityName OBJECT IDENTIFIER ::= {id-at 7 2} +id-at-collectiveLocalityName OBJECT IDENTIFIER ::= + {id-at 7 1} + +-- id-at-encryptedCollectiveLocalityName OBJECT IDENTIFIER ::= {id-at 7 1 2} +id-at-stateOrProvinceName OBJECT IDENTIFIER ::= + {id-at 8} + +-- id-at-encryptedStateOrProvinceName OBJECT IDENTIFIER ::= {id-at 8 2} +id-at-collectiveStateOrProvinceName OBJECT IDENTIFIER ::= + {id-at 8 1} + +-- id-at-encryptedCollectiveStateOrProvinceName OBJECT IDENTIFIER ::= {id-at 8 1 2} +id-at-streetAddress OBJECT IDENTIFIER ::= + {id-at 9} + +-- id-at-encryptedStreetAddress OBJECT IDENTIFIER ::= {id-at 9 2} +id-at-collectiveStreetAddress OBJECT IDENTIFIER ::= + {id-at 9 1} + +-- id-at-encryptedCollectiveStreetAddress OBJECT IDENTIFIER ::= {id-at 9 1 2} +id-at-organizationName OBJECT IDENTIFIER ::= + {id-at 10} + +-- id-at-encryptedOrganizationName OBJECT IDENTIFIER ::= {id-at 10 2} +id-at-collectiveOrganizationName OBJECT IDENTIFIER ::= + {id-at 10 1} + +-- id-at-encryptedCollectiveOrganizationName OBJECT IDENTIFIER ::= {id-at 10 1 2} +id-at-organizationalUnitName OBJECT IDENTIFIER ::= + {id-at 11} + +-- id-at-encryptedOrganizationalUnitName OBJECT IDENTIFIER ::= {id-at 11 2} +id-at-collectiveOrganizationalUnitName OBJECT IDENTIFIER ::= + {id-at 11 1} + +-- id-at-encryptedCollectiveOrganizationalUnitNam OBJECT IDENTIFIER ::= {id-at 11 1 2} +id-at-title OBJECT IDENTIFIER ::= + {id-at 12} + +-- id-at-encryptedTitle OBJECT IDENTIFIER ::= {id-at 12 2} +id-at-description OBJECT IDENTIFIER ::= + {id-at 13} + +-- id-at-encryptedDescription OBJECT IDENTIFIER ::= {id-at 13 2} +id-at-searchGuide OBJECT IDENTIFIER ::= + {id-at 14} + +-- id-at-encryptedSearchGuide OBJECT IDENTIFIER ::= {id-at 14 2} +id-at-businessCategory OBJECT IDENTIFIER ::= + {id-at 15} + +-- id-at-encryptedBusinessCategory OBJECT IDENTIFIER ::= {id-at 15 2} +id-at-postalAddress OBJECT IDENTIFIER ::= + {id-at 16} + +-- id-at-encryptedPostalAddress OBJECT IDENTIFIER ::= {id-at 16 2} +id-at-collectivePostalAddress OBJECT IDENTIFIER ::= + {id-at 16 1} + +-- id-at-encryptedCollectivePostalAddress OBJECT IDENTIFIER ::= {id-at 16 1 2} +id-at-postalCode OBJECT IDENTIFIER ::= + {id-at 17} + +-- id-at-encryptedPostalCode OBJECT IDENTIFIER ::= {id-at 17 2} +id-at-collectivePostalCode OBJECT IDENTIFIER ::= + {id-at 17 1} + +-- id-at-encryptedCollectivePostalCode OBJECT IDENTIFIER ::= {id-at 17 1 2} +id-at-postOfficeBox OBJECT IDENTIFIER ::= + {id-at 18} + +id-at-collectivePostOfficeBox OBJECT IDENTIFIER ::= {id-at 18 1} + +-- id-at-encryptedPostOfficeBox OBJECT IDENTIFIER ::= {id-at 18 2} +-- id-at-encryptedCollectivePostOfficeBox OBJECT IDENTIFIER ::= {id-at 18 1 2} +id-at-physicalDeliveryOfficeName OBJECT IDENTIFIER ::= + {id-at 19} + +id-at-collectivePhysicalDeliveryOfficeName OBJECT IDENTIFIER ::= {id-at 19 1} + +-- id-at-encryptedPhysicalDeliveryOfficeName OBJECT IDENTIFIER ::= {id-at 19 2} +-- id-at-encryptedCollectivePhysicalDeliveryOfficeName OBJECT IDENTIFIER ::= {id-at 19 1 2} +id-at-telephoneNumber OBJECT IDENTIFIER ::= + {id-at 20} + +-- id-at-encryptedTelephoneNumber OBJECT IDENTIFIER ::= {id-at 20 2} +id-at-collectiveTelephoneNumber OBJECT IDENTIFIER ::= + {id-at 20 1} + +-- id-at-encryptedCollectiveTelephoneNumber OBJECT IDENTIFIER ::= {id-at 20 1 2} +id-at-telexNumber OBJECT IDENTIFIER ::= + {id-at 21} + +-- id-at-encryptedTelexNumber OBJECT IDENTIFIER ::= {id-at 21 2} +id-at-collectiveTelexNumber OBJECT IDENTIFIER ::= + {id-at 21 1} + +-- id-at-encryptedCollectiveTelexNumber OBJECT IDENTIFIER ::= {id-at 21 1 2} +-- id-at-teletexTerminalIdentifier OBJECT IDENTIFIER ::= {id-at 22} +-- id-at-encryptedTeletexTerminalIdentifier OBJECT IDENTIFIER ::= {id-at 22 2} +-- id-at-collectiveTeletexTerminalIdentifier OBJECT IDENTIFIER ::= {id-at 22 1} +-- id-at-encryptedCollectiveTeletexTerminalIdentifier OBJECT IDENTIFIER ::= {id-at 22 1 2} +id-at-facsimileTelephoneNumber OBJECT IDENTIFIER ::= + {id-at 23} + +-- id-at-encryptedFacsimileTelephoneNumber OBJECT IDENTIFIER ::= {id-at 23 2} +id-at-collectiveFacsimileTelephoneNumber OBJECT IDENTIFIER ::= + {id-at 23 1} + +-- id-at-encryptedCollectiveFacsimileTelephoneNumber OBJECT IDENTIFIER ::= {id-at 23 1 2} +id-at-x121Address OBJECT IDENTIFIER ::= + {id-at 24} + +-- id-at-encryptedX121Address OBJECT IDENTIFIER ::= {id-at 24 2} +id-at-internationalISDNNumber OBJECT IDENTIFIER ::= + {id-at 25} + +-- id-at-encryptedInternationalISDNNumber OBJECT IDENTIFIER ::= {id-at 25 2} +id-at-collectiveInternationalISDNNumber OBJECT IDENTIFIER ::= + {id-at 25 1} + +-- id-at-encryptedCollectiveInternationalISDNNumber OBJECT IDENTIFIER ::= {id-at 25 1 2} +id-at-registeredAddress OBJECT IDENTIFIER ::= + {id-at 26} + +-- id-at-encryptedRegisteredAddress OBJECT IDENTIFIER ::= {id-at 26 2} +id-at-destinationIndicator OBJECT IDENTIFIER ::= + {id-at 27} + +-- id-at-encryptedDestinationIndicator OBJECT IDENTIFIER ::= {id-at 27 2} +id-at-preferredDeliveryMethod OBJECT IDENTIFIER ::= + {id-at 28} + +-- id-at-encryptedPreferredDeliveryMethod OBJECT IDENTIFIER ::= {id-at 28 2} +id-at-presentationAddress OBJECT IDENTIFIER ::= + {id-at 29} + +-- id-at-encryptedPresentationAddress OBJECT IDENTIFIER ::= {id-at 29 2} +id-at-supportedApplicationContext OBJECT IDENTIFIER ::= + {id-at 30} + +-- id-at-encryptedSupportedApplicationContext OBJECT IDENTIFIER ::= {id-at 30 2} +id-at-member OBJECT IDENTIFIER ::= + {id-at 31} + +-- id-at-encryptedMember OBJECT IDENTIFIER ::= {id-at 31 2} +id-at-owner OBJECT IDENTIFIER ::= + {id-at 32} + +-- id-at-encryptedOwner OBJECT IDENTIFIER ::= {id-at 32 2} +id-at-roleOccupant OBJECT IDENTIFIER ::= + {id-at 33} + +-- id-at-encryptedRoleOccupant OBJECT IDENTIFIER ::= {id-at 33 2} +id-at-seeAlso OBJECT IDENTIFIER ::= + {id-at 34} + +-- id-at-encryptedSeeAlso OBJECT IDENTIFIER ::= {id-at 34 2} +-- id-at-userPassword OBJECT IDENTIFIER ::= {id-at 35} X.509|Part8 +-- id-at-encryptedUserPassword OBJECT IDENTIFIER ::= {id-at 35 2} +-- id-at-userCertificate OBJECT IDENTIFIER ::= {id-at 36} X.509|Part8 +-- id-at-encryptedUserCertificate OBJECT IDENTIFIER ::= {id-at 36 2} +-- id-at-cACertificate OBJECT IDENTIFIER ::= {id-at 37} X.509|Part8 +-- id-at-encryptedCACertificate OBJECT IDENTIFIER ::= {id-at 37 2} +-- id-at-authorityRevocationList OBJECT IDENTIFIER ::= {id-at 38} X.509|Part8 +-- id-at-encryptedAuthorityRevocationList OBJECT IDENTIFIER ::= {id-at 38 2} +-- id-at-certificateRevocationList OBJECT IDENTIFIER ::= {id-at 39} X.509|Part8 +-- id-at-encryptedCertificateRevocationList OBJECT IDENTIFIER ::= {id-at 39 2} +-- id-at-crossCertificatePair OBJECT IDENTIFIER ::= {id-at 40} X.509|Part8 +-- id-at-encryptedCrossCertificatePair OBJECT IDENTIFIER ::= {id-at 40 2} +id-at-name OBJECT IDENTIFIER ::= + {id-at 41} + +id-at-givenName OBJECT IDENTIFIER ::= {id-at 42} + +-- id-at-encryptedGivenName OBJECT IDENTIFIER ::= {id-at 42 2} +id-at-initials OBJECT IDENTIFIER ::= + {id-at 43} + +-- id-at-encryptedInitials OBJECT IDENTIFIER ::= {id-at 43 2} +id-at-generationQualifier OBJECT IDENTIFIER ::= + {id-at 44} + +-- id-at-encryptedGenerationQualifier OBJECT IDENTIFIER ::= {id-at 44 2} +id-at-uniqueIdentifier OBJECT IDENTIFIER ::= + {id-at 45} + +-- id-at-encryptedUniqueIdentifier OBJECT IDENTIFIER ::= {id-at 45 2} +id-at-dnQualifier OBJECT IDENTIFIER ::= + {id-at 46} + +-- id-at-encryptedDnQualifier OBJECT IDENTIFIER ::= {id-at 46 2} +id-at-enhancedSearchGuide OBJECT IDENTIFIER ::= + {id-at 47} + +-- id-at-encryptedEnhancedSearchGuide OBJECT IDENTIFIER ::= {id-at 47 2} +id-at-protocolInformation OBJECT IDENTIFIER ::= + {id-at 48} + +-- id-at-encryptedProtocolInformation OBJECT IDENTIFIER ::= {id-at 48 2} +id-at-distinguishedName OBJECT IDENTIFIER ::= + {id-at 49} + +-- id-at-encryptedDistinguishedName OBJECT IDENTIFIER ::= {id-at 49 2} +id-at-uniqueMember OBJECT IDENTIFIER ::= + {id-at 50} + +-- id-at-encryptedUniqueMember OBJECT IDENTIFIER ::= {id-at 50 2} +id-at-houseIdentifier OBJECT IDENTIFIER ::= + {id-at 51} + +-- id-at-encryptedHouseIdentifier OBJECT IDENTIFIER ::= {id-at 51 2} +-- id-at-supportedAlgorithms OBJECT IDENTIFIER ::= {id-at 52} X.509|Part8 +-- id-at-encryptedSupportedAlgorithms OBJECT IDENTIFIER ::= {id-at 52 2} +-- id-at-deltaRevocationList OBJECT IDENTIFIER ::= {id-at 53} X.509|Part8 +-- id-at-encryptedDeltaRevocationList OBJECT IDENTIFIER ::= {id-at 53 2} +id-at-dmdName OBJECT IDENTIFIER ::= + {id-at 54} + +-- id-at-encryptedDmdName OBJECT IDENTIFIER ::= {id-at 54 2} +-- id-at-clearance OBJECT IDENTIFIER ::= {id-at 55} +-- id-at-encryptedClearance OBJECT IDENTIFIER ::= {id-at 55 2} +-- id-at-defaultDirQop OBJECT IDENTIFIER ::= {id-at 56} +-- id-at-encryptedDefaultDirQop OBJECT IDENTIFIER ::= {id-at 56 2} +-- id-at-attributeIntegrityInfo OBJECT IDENTIFIER ::= {id-at 57} +-- id-at-encryptedAttributeIntegrityInfo OBJECT IDENTIFIER ::= {id-at 57 2} +-- id-at-attributeCertificate OBJECT IDENTIFIER ::= {id-at 58} X.509|Part8 +-- id-at-encryptedAttributeCertificate OBJECT IDENTIFIER ::= {id-at 58 2} +-- id-at-attributeCertificateRevocationList OBJECT IDENTIFIER ::= {id-at 59} X.509|Part8 +-- id-at-encryptedAttributeCertificateRevocationList OBJECT IDENTIFIER ::= {id-at 59 2} +-- id-at-confKeyInfo OBJECT IDENTIFIER ::= {id-at 60} +-- id-at-encryptedConfKeyInfo OBJECT IDENTIFIER ::= {id-at 60 2} +-- id-at-aACertificate OBJECT IDENTIFIER ::= {id-at 61} X.509|Part8 +-- id-at-attributeDescriptorCertificate OBJECT IDENTIFIER ::= {id-at 62} X.509|Part8 +-- id-at-attributeAuthorityRevocationList OBJECT IDENTIFIER ::= {id-at 63} X.509|Part8 +-- id-at-family-information OBJECT IDENTIFIER ::= {id-at 64} +id-at-pseudonym OBJECT IDENTIFIER ::= + {id-at 65} + +id-at-communicationsService OBJECT IDENTIFIER ::= {id-at 66} + +id-at-communicationsNetwork OBJECT IDENTIFIER ::= {id-at 67} + +-- id-at-certificationPracticeStmt OBJECT IDENTIFIER ::= {id-at 68} X.509|Part8 +-- id-at-certificatePolicy OBJECT IDENTIFIER ::= {id-at 69} X.509|Part8 +-- id-at-pkiPath OBJECT IDENTIFIER ::= {id-at 70} X.509|Part8 +-- id-at-privPolicy OBJECT IDENTIFIER ::= {id-at 71} X.509|Part8 +-- id-at-role OBJECT IDENTIFIER ::= {id-at 72} X.509|Part8 +-- id-at-delegationPath OBJECT IDENTIFIER ::= {id-at 73} X.509|Part8 +-- id-at-protPrivPolicy OBJECT IDENTIFIER ::= {id-at 74} X.509|Part8 +-- id-at-xMLPrivilegeInfo OBJECT IDENTIFIER ::= {id-at 75} X.509|Part8 +-- id-at-xmlPrivPolicy OBJECT IDENTIFIER ::= {id-at 76} X.509|Part8 +id-at-uuidpair OBJECT IDENTIFIER ::= + {id-at 77} + +id-at-tagOid OBJECT IDENTIFIER ::= {id-at 78} + +id-at-uiiFormat OBJECT IDENTIFIER ::= {id-at 79} + +id-at-uiiInUrn OBJECT IDENTIFIER ::= {id-at 80} + +id-at-contentUri OBJECT IDENTIFIER ::= {id-at 81} + +-- id-at-permission OBJECT IDENTIFIER ::= {id-at 82} X.509|Part8 +-- Control attributes +id-cat-sequenceMatchType OBJECT IDENTIFIER ::= + {id-cat 1} + +id-cat-wordMatchType OBJECT IDENTIFIER ::= {id-cat 2} + +id-cat-characterMatchTypes OBJECT IDENTIFIER ::= {id-cat 3} + +id-cat-selectedContexts OBJECT IDENTIFIER ::= {id-cat 4} + +-- Notification attributes +id-not-dSAProblem OBJECT IDENTIFIER ::= {id-not 0} + +id-not-searchServiceProblem OBJECT IDENTIFIER ::= {id-not 1} + +id-not-serviceType OBJECT IDENTIFIER ::= {id-not 2} + +id-not-attributeTypeList OBJECT IDENTIFIER ::= {id-not 3} + +id-not-matchingRuleList OBJECT IDENTIFIER ::= {id-not 4} + +id-not-filterItem OBJECT IDENTIFIER ::= {id-not 5} + +id-not-attributeCombinations OBJECT IDENTIFIER ::= {id-not 6} + +id-not-contextTypeList OBJECT IDENTIFIER ::= {id-not 7} + +id-not-contextList OBJECT IDENTIFIER ::= {id-not 8} + +id-not-contextCombinations OBJECT IDENTIFIER ::= {id-not 9} + +id-not-hierarchySelectList OBJECT IDENTIFIER ::= {id-not 10} + +id-not-searchControlOptionsList OBJECT IDENTIFIER ::= {id-not 11} + +id-not-serviceControlOptionsList OBJECT IDENTIFIER ::= {id-not 12} + +id-not-multipleMatchingLocalities OBJECT IDENTIFIER ::= {id-not 13} + +id-not-proposedRelaxation OBJECT IDENTIFIER ::= {id-not 14} + +id-not-appliedRelaxation OBJECT IDENTIFIER ::= {id-not 15} + +-- Problem definitions +id-pr-targetDsaUnavailable OBJECT IDENTIFIER ::= + {id-pr 1} + +id-pr-dataSourceUnavailable OBJECT IDENTIFIER ::= {id-pr 2} + +id-pr-unidentifiedOperation OBJECT IDENTIFIER ::= {id-pr 3} + +id-pr-unavailableOperation OBJECT IDENTIFIER ::= {id-pr 4} + +id-pr-searchAttributeViolation OBJECT IDENTIFIER ::= {id-pr 5} + +id-pr-searchAttributeCombinationViolation OBJECT IDENTIFIER ::= {id-pr 6} + +id-pr-searchValueNotAllowed OBJECT IDENTIFIER ::= {id-pr 7} + +id-pr-missingSearchAttribute OBJECT IDENTIFIER ::= {id-pr 8} + +id-pr-searchValueViolation OBJECT IDENTIFIER ::= {id-pr 9} + +id-pr-attributeNegationViolation OBJECT IDENTIFIER ::= {id-pr 10} + +id-pr-searchValueRequired OBJECT IDENTIFIER ::= {id-pr 11} + +id-pr-invalidSearchValue OBJECT IDENTIFIER ::= {id-pr 12} + +id-pr-searchContextViolation OBJECT IDENTIFIER ::= {id-pr 13} + +id-pr-searchContextCombinationViolation OBJECT IDENTIFIER ::= {id-pr 14} + +id-pr-missingSearchContext OBJECT IDENTIFIER ::= {id-pr 15} + +id-pr-searchContextValueViolation OBJECT IDENTIFIER ::= {id-pr 16} + +id-pr-searchContextValueRequired OBJECT IDENTIFIER ::= {id-pr 17} + +id-pr-invalidContextSearchValue OBJECT IDENTIFIER ::= {id-pr 18} + +id-pr-unsupportedMatchingRule OBJECT IDENTIFIER ::= {id-pr 19} + +id-pr-attributeMatchingViolation OBJECT IDENTIFIER ::= {id-pr 20} + +id-pr-unsupportedMatchingUse OBJECT IDENTIFIER ::= {id-pr 21} + +id-pr-matchingUseViolation OBJECT IDENTIFIER ::= {id-pr 22} + +id-pr-hierarchySelectForbidden OBJECT IDENTIFIER ::= {id-pr 23} + +id-pr-invalidHierarchySelect OBJECT IDENTIFIER ::= {id-pr 24} + +id-pr-unavailableHierarchySelect OBJECT IDENTIFIER ::= {id-pr 25} + +id-pr-invalidSearchControlOptions OBJECT IDENTIFIER ::= {id-pr 26} + +id-pr-invalidServiceControlOptions OBJECT IDENTIFIER ::= {id-pr 27} + +id-pr-searchSubsetViolation OBJECT IDENTIFIER ::= {id-pr 28} + +id-pr-unmatchedKeyAttributes OBJECT IDENTIFIER ::= {id-pr 29} + +id-pr-ambiguousKeyAttributes OBJECT IDENTIFIER ::= {id-pr 30} + +id-pr-unavailableRelaxationLevel OBJECT IDENTIFIER ::= {id-pr 31} + +id-pr-emptyHierarchySelection OBJECT IDENTIFIER ::= {id-pr 32} + +id-pr-administratorImposedLimit OBJECT IDENTIFIER ::= {id-pr 33} + +id-pr-permanentRestriction OBJECT IDENTIFIER ::= {id-pr 34} + +id-pr-temporaryRestriction OBJECT IDENTIFIER ::= {id-pr 35} + +id-pr-relaxationNotSupported OBJECT IDENTIFIER ::= {id-pr 36} + +-- Matching rules +-- id-mr-objectIdentifierMatch OBJECT IDENTIFIER ::= {id-mr 0} X.501|Part2 +-- id-mr-distinguishedNameMatch OBJECT IDENTIFIER ::= {id-mr 1} X.501|Part2 +id-mr-caseIgnoreMatch OBJECT IDENTIFIER ::= + {id-mr 2} + +id-mr-caseIgnoreOrderingMatch OBJECT IDENTIFIER ::= {id-mr 3} + +id-mr-caseIgnoreSubstringsMatch OBJECT IDENTIFIER ::= {id-mr 4} + +id-mr-caseExactMatch OBJECT IDENTIFIER ::= {id-mr 5} + +id-mr-caseExactOrderingMatch OBJECT IDENTIFIER ::= {id-mr 6} + +id-mr-caseExactSubstringsMatch OBJECT IDENTIFIER ::= {id-mr 7} + +id-mr-numericStringMatch OBJECT IDENTIFIER ::= {id-mr 8} + +id-mr-numericStringOrderingMatch OBJECT IDENTIFIER ::= {id-mr 9} + +id-mr-numericStringSubstringsMatch OBJECT IDENTIFIER ::= {id-mr 10} + +id-mr-caseIgnoreListMatch OBJECT IDENTIFIER ::= {id-mr 11} + +id-mr-caseIgnoreListSubstringsMatch OBJECT IDENTIFIER ::= {id-mr 12} + +id-mr-booleanMatch OBJECT IDENTIFIER ::= {id-mr 13} + +id-mr-integerMatch OBJECT IDENTIFIER ::= {id-mr 14} + +id-mr-integerOrderingMatch OBJECT IDENTIFIER ::= {id-mr 15} + +id-mr-bitStringMatch OBJECT IDENTIFIER ::= {id-mr 16} + +id-mr-octetStringMatch OBJECT IDENTIFIER ::= {id-mr 17} + +id-mr-octetStringOrderingMatch OBJECT IDENTIFIER ::= {id-mr 18} + +id-mr-octetStringSubstringsMatch OBJECT IDENTIFIER ::= {id-mr 19} + +id-mr-telephoneNumberMatch OBJECT IDENTIFIER ::= {id-mr 20} + +id-mr-telephoneNumberSubstringsMatch OBJECT IDENTIFIER ::= {id-mr 21} + +id-mr-presentationAddressMatch OBJECT IDENTIFIER ::= {id-mr 22} + +id-mr-uniqueMemberMatch OBJECT IDENTIFIER ::= {id-mr 23} + +id-mr-protocolInformationMatch OBJECT IDENTIFIER ::= {id-mr 24} + +id-mr-uTCTimeMatch OBJECT IDENTIFIER ::= {id-mr 25} + +id-mr-uTCTimeOrderingMatch OBJECT IDENTIFIER ::= {id-mr 26} + +id-mr-generalizedTimeMatch OBJECT IDENTIFIER ::= {id-mr 27} + +id-mr-generalizedTimeOrderingMatch OBJECT IDENTIFIER ::= {id-mr 28} + +id-mr-integerFirstComponentMatch OBJECT IDENTIFIER ::= {id-mr 29} + +id-mr-objectIdentifierFirstComponentMatch OBJECT IDENTIFIER ::= {id-mr 30} + +id-mr-directoryStringFirstComponentMatch OBJECT IDENTIFIER ::= {id-mr 31} + +id-mr-wordMatch OBJECT IDENTIFIER ::= {id-mr 32} + +id-mr-keywordMatch OBJECT IDENTIFIER ::= {id-mr 33} + +-- id-mr-certificateExactMatch OBJECT IDENTIFIER ::= {id-mr 34} X.509|Part8 +-- id-mr-certificateMatch OBJECT IDENTIFIER ::= {id-mr 35} X.509|Part8 +-- id-mr-certificatePairExactMatch OBJECT IDENTIFIER ::= {id-mr 36} X.509|Part8 +-- id-mr-certificatePairMatch OBJECT IDENTIFIER ::= {id-mr 37} X.509|Part8 +-- id-mr-certificateListExactMatch OBJECT IDENTIFIER ::= {id-mr 38} X.509|Part8 +-- id-mr-certificateListMatch OBJECT IDENTIFIER ::= {id-mr 39} X.509|Part8 +-- id-mr-algorithmIdentifierMatch OBJECT IDENTIFIER ::= {id-mr 40} X.509|Part8 +id-mr-storedPrefixMatch OBJECT IDENTIFIER ::= + {id-mr 41} + +-- id-mr-attributeCertificateMatch OBJECT IDENTIFIER ::= {id-mr 42} X.509|Part8 +-- id-mr-readerAndKeyIDMatch OBJECT IDENTIFIER ::= {id-mr 43} +-- id-mr-attributeIntegrityMatch OBJECT IDENTIFIER ::= {id-mr 44} +-- id-mr-attributeCertificateExactMatch OBJECT IDENTIFIER ::= {id-mr 45} X.509|Part8 +-- id-mr-holderIssuerMatch OBJECT IDENTIFIER ::= {id-mr 46} X.509|Part8 +id-mr-systemProposedMatch OBJECT IDENTIFIER ::= + {id-mr 47} + +id-mr-generalWordMatch OBJECT IDENTIFIER ::= {id-mr 48} + +id-mr-approximateStringMatch OBJECT IDENTIFIER ::= {id-mr 49} + +id-mr-ignoreIfAbsentMatch OBJECT IDENTIFIER ::= {id-mr 50} + +id-mr-nullMatch OBJECT IDENTIFIER ::= {id-mr 51} + +id-mr-zonalMatch OBJECT IDENTIFIER ::= {id-mr 52} + +-- id-mr-authAttIdMatch OBJECT IDENTIFIER ::= {id-mr 53} X.509|Part8 +-- id-mr-roleSpecCertIdMatch OBJECT IDENTIFIER ::= {id-mr 54} X.509|Part8 +-- id-mr-basicAttConstraintsMatch OBJECT IDENTIFIER ::= {id-mr 55} X.509|Part8 +-- id-mr-delegatedNameConstraintsMatch OBJECT IDENTIFIER ::= {id-mr 56} X.509|Part8 +-- id-mr-timeSpecMatch OBJECT IDENTIFIER ::= {id-mr 57} X.509|Part8 +-- id-mr-attDescriptorMatch OBJECT IDENTIFIER ::= {id-mr 58} X.509|Part8 +-- id-mr-acceptableCertPoliciesMatch OBJECT IDENTIFIER ::= {id-mr 59} X.509|Part8 +-- id-mr-policyMatch OBJECT IDENTIFIER ::= {id-mr 60} X.509|Part8 +-- id-mr-delegationPathMatch OBJECT IDENTIFIER ::= {id-mr 61} X.509|Part8 +-- id-mr-pkiPathMatch OBJECT IDENTIFIER ::= {id-mr 62} X.509|Part8 +id-mr-facsimileNumberMatch OBJECT IDENTIFIER ::= + {id-mr 63} + +id-mr-facsimileNumberSubstringsMatch OBJECT IDENTIFIER ::= {id-mr 64} + +-- id-mr-enhancedCertificateMatch OBJECT IDENTIFIER ::= {id-mr 65} X.509|Part8 +-- id-mr-sOAIdentifierMatch OBJECT IDENTIFIER ::= {id-mr 66} X.509|Part8 +-- id-mr-extensionPresenceMatch OBJECT IDENTIFIER ::= {id-mr 67} X.509|Part8 +id-mr-uuidpairmatch OBJECT IDENTIFIER ::= + {id-mr 68} + +-- id-mr-dualStringMatch OBJECT IDENTIFIER ::= {id-mr 69} X.509|Part8 +-- contexts +id-avc-language OBJECT IDENTIFIER ::= + {id-avc 0} + +id-avc-temporal OBJECT IDENTIFIER ::= {id-avc 1} + +id-avc-locale OBJECT IDENTIFIER ::= {id-avc 2} + +-- id-avc-attributeValueSecurityLabelContext OBJECT IDENTIFIER ::= {id-avc 3} +-- id-avc-attributeValueIntegrityInfoContext OBJECT IDENTIFIER ::= {id-avc 4} +id-avc-ldapAttributeOption OBJECT IDENTIFIER ::= + {id-avc 5} + +END -- SelectedAttributeTypes diff --git a/lib/public_key/asn1/UsefulDefinitions.asn1 b/lib/public_key/asn1/UsefulDefinitions.asn1 new file mode 100644 index 0000000000..a200aac6e2 --- /dev/null +++ b/lib/public_key/asn1/UsefulDefinitions.asn1 @@ -0,0 +1,234 @@ +UsefulDefinitions {joint-iso-itu-t ds(5) module(1) usefulDefinitions(0) 3} +DEFINITIONS ::= +BEGIN + +-- EXPORTS All - +-- The types and values defined in this module are exported for use in the other ASN.1 modules contained +-- within the Directory Specifications, and for the use of other applications which will use them to access +-- Directory services. Other applications may use them for their own purposes, but this will not constrain +-- extensions and modifications needed to maintain or improve the Directory service. +ID ::= OBJECT IDENTIFIER + +ds ID ::= {joint-iso-itu-t ds(5)} + +-- categories of information object +module ID ::= {ds 1} + +serviceElement ID ::= {ds 2} + +applicationContext ID ::= {ds 3} + +attributeType ID ::= {ds 4} + +attributeSyntax ID ::= {ds 5} + +objectClass ID ::= {ds 6} + +-- attributeSet ID ::= {ds 7} +algorithm ID ::= {ds 8} + +abstractSyntax ID ::= {ds 9} + +-- object ID ::= {ds 10} +-- port ID ::= {ds 11} +dsaOperationalAttribute ID ::= + {ds 12} + +matchingRule ID ::= {ds 13} + +knowledgeMatchingRule ID ::= {ds 14} + +nameForm ID ::= {ds 15} + +group ID ::= {ds 16} + +subentry ID ::= {ds 17} + +operationalAttributeType ID ::= {ds 18} + +operationalBinding ID ::= {ds 19} + +schemaObjectClass ID ::= {ds 20} + +schemaOperationalAttribute ID ::= {ds 21} + +administrativeRoles ID ::= {ds 23} + +accessControlAttribute ID ::= {ds 24} + +rosObject ID ::= {ds 25} + +contract ID ::= {ds 26} + +package ID ::= {ds 27} + +accessControlSchemes ID ::= {ds 28} + +certificateExtension ID ::= {ds 29} + +managementObject ID ::= {ds 30} + +attributeValueContext ID ::= {ds 31} + +-- securityExchange ID ::= {ds 32} +idmProtocol ID ::= {ds 33} + +problem ID ::= {ds 34} + +notification ID ::= {ds 35} + +matchingRestriction ID ::= + {ds 36} -- None are currently defined by this specification + +controlAttributeType ID ::= {ds 37} + +-- modules +usefulDefinitions ID ::= {module usefulDefinitions(0) 3} + +informationFramework ID ::= {module informationFramework(1) 3} + +directoryAbstractService ID ::= {module directoryAbstractService(2) 3} + +distributedOperations ID ::= {module distributedOperations(3) 3} + +protocolObjectIdentifiers ID ::= {module protocolObjectIdentifiers(4) 3} + +selectedAttributeTypes ID ::= {module selectedAttributeTypes(5) 3} + +selectedObjectClasses ID ::= {module selectedObjectClasses(6) 3} + +authenticationFramework ID ::= {module authenticationFramework(7) 3} + +algorithmObjectIdentifiers ID ::= {module algorithmObjectIdentifiers(8) 3} + +directoryObjectIdentifiers ID ::= {module directoryObjectIdentifiers(9) 3} + +upperBounds ID ::= {module upperBounds(10) 3} + +dap ID ::= {module dap(11) 3} + +dsp ID ::= {module dsp(12) 3} + +distributedDirectoryOIDs ID ::= {module distributedDirectoryOIDs(13) 3} + +directoryShadowOIDs ID ::= {module directoryShadowOIDs(14) 3} + +directoryShadowAbstractService ID ::= + {module directoryShadowAbstractService(15) 3} + +disp ID ::= {module disp(16) 3} + +dop ID ::= {module dop(17) 3} + +opBindingManagement ID ::= {module opBindingManagement(18) 3} + +opBindingOIDs ID ::= {module opBindingOIDs(19) 3} + +hierarchicalOperationalBindings ID ::= + {module hierarchicalOperationalBindings(20) 3} + +dsaOperationalAttributeTypes ID ::= {module dsaOperationalAttributeTypes(22) 3} + +schemaAdministration ID ::= {module schemaAdministration(23) 3} + +basicAccessControl ID ::= {module basicAccessControl(24) 3} + +directoryOperationalBindingTypes ID ::= + {module directoryOperationalBindingTypes(25) 3} + +certificateExtensions ID ::= {module certificateExtensions(26) 0} + +directoryManagement ID ::= {module directoryManagement(27) 1} + +enhancedSecurity ID ::= {module enhancedSecurity(28) 1} + +iDMProtocolSpecification ID ::= {module iDMProtocolSpecification(30) 4} + +directoryIDMProtocols ID ::= {module directoryIDMProtocols(31) 4} + +-- directorySecurityExchanges ID ::= {module directorySecurityExchanges (29) 1} +-- synonyms +id-oc ID ::= + objectClass + +id-at ID ::= attributeType + +id-as ID ::= abstractSyntax + +id-mr ID ::= matchingRule + +id-nf ID ::= nameForm + +id-sc ID ::= subentry + +id-oa ID ::= operationalAttributeType + +id-ob ID ::= operationalBinding + +id-doa ID ::= dsaOperationalAttribute + +id-kmr ID ::= knowledgeMatchingRule + +id-soc ID ::= schemaObjectClass + +id-soa ID ::= schemaOperationalAttribute + +id-ar ID ::= administrativeRoles + +id-aca ID ::= accessControlAttribute + +id-ac ID ::= applicationContext + +id-rosObject ID ::= rosObject + +id-contract ID ::= contract + +id-package ID ::= package + +id-acScheme ID ::= accessControlSchemes + +id-ce ID ::= certificateExtension + +id-mgt ID ::= managementObject + +id-idm ID ::= idmProtocol + +id-avc ID ::= attributeValueContext + +-- id-se ID ::= securityExchange +id-pr ID ::= problem + +id-not ID ::= notification + +id-mre ID ::= matchingRestriction + +id-cat ID ::= controlAttributeType + +-- obsolete module identifiers +-- usefulDefinition ID ::= {module 0} +-- informationFramework ID ::= {module 1} +-- directoryAbstractService ID ::= {module 2} +-- distributedOperations ID ::= {module 3} +-- protocolObjectIdentifiers ID ::= {module 4} +-- selectedAttributeTypes ID ::= {module 5} +-- selectedObjectClasses ID ::= {module 6} +-- authenticationFramework ID ::= {module 7} +-- algorithmObjectIdentifiers ID ::= {module 8} +-- directoryObjectIdentifiers ID ::= {module 9} +-- upperBounds ID ::= {module 10} +-- dap ID ::= {module 11} +-- dsp ID ::= {module 12} +-- distributedDirectoryObjectIdentifiers ID ::= {module 13} +-- unused module identifiers +-- directoryShadowOIDs ID ::= {module 14} +-- directoryShadowAbstractService ID ::= {module 15} +-- disp ID ::= {module 16} +-- dop ID ::= {module 17} +-- opBindingManagement ID ::= {module 18} +-- opBindingOIDs ID ::= {module 19} +-- hierarchicalOperationalBindings ID ::= {module 20} +-- dsaOperationalAttributeTypes ID ::= {module 22} +-- schemaAdministration ID ::= {module 23} +-- basicAccessControl ID ::= {module 24} +-- operationalBindingOIDs ID ::= {module 25} +END -- UsefulDefinitions diff --git a/lib/public_key/doc/src/cert_records.xml b/lib/public_key/doc/src/cert_records.xml index ad4f5812cb..edef664245 100644 --- a/lib/public_key/doc/src/cert_records.xml +++ b/lib/public_key/doc/src/cert_records.xml @@ -5,7 +5,7 @@ <header> <copyright> <year>2008</year> - <year>2011</year> + <year>2012</year> <holder>Ericsson AB, All Rights Reserved</holder> </copyright> <legalnotice> @@ -37,7 +37,10 @@ <p>This chapter briefly describes erlang records derived from asn1 specifications used to handle X509 certificates. The intent is to describe the data types and not to specify the meaning of each - component for this we refer you to RFC 5280. + component for this we refer you to <url + href="http://www.ietf.org/rfc/rfc5280.txt">RFC 5280</url>. Also + descirbed is <p>CertificationRequest</p> that is defined by <url + href=http://www.rsa.com/rsalabs/node.asp?id=2124">PKCS-10</url>. </p> <p>Use the following include directive to get access to the @@ -630,6 +633,40 @@ oid names see table below. Ex: ?'id-dsa-with-sha1'</p> aACompromise </c></p> </section> - + + <section> + <marker id="PKCS10"></marker> + <title>PKCS#10 Certification Request</title> + <code> +#'CertificationRequest'{ + certificationRequestInfo #'CertificationRequestInfo'{}, + signatureAlgorithm #'CertificationRequest_signatureAlgorithm'{}}. + signature {0, binary()} - asn1 compact bitstring + } + +#'CertificationRequestInfo'{ + version atom(), + subject {rdnSequence, [#AttributeTypeAndValue'{}]} , + subjectPKInfo #'CertificationRequestInfo_subjectPKInfo'{}, + attributes [#AttributeTypeAndValue'{}] + } + +#'CertificationRequestInfo_subjectPKInfo'{ + algorithm #'CertificationRequestInfo_subjectPKInfo_algorithm'{} + subjectPublicKey {0, binary()} - asn1 compact bitstring + } + +#'CertificationRequestInfo_subjectPKInfo_algorithm'{ + algorithm = oid(), + parameters = asn1_der_encoded() +} + +#'CertificationRequest_signatureAlgorithm'{ + algorithm = oid(), + parameters = asn1_der_encoded() + } + </code> + </section> + </section> </chapter> diff --git a/lib/public_key/doc/src/introduction.xml b/lib/public_key/doc/src/introduction.xml index a21fcf3576..b1d1114a6c 100644 --- a/lib/public_key/doc/src/introduction.xml +++ b/lib/public_key/doc/src/introduction.xml @@ -1,11 +1,11 @@ -<?xml version="1.0" encoding="latin1" ?> +<?xml version="1.0" encoding="iso-8859-1" ?> <!DOCTYPE chapter SYSTEM "chapter.dtd"> <chapter> <header> <copyright> <year>2008</year> - <year>2011</year> + <year>2012</year> <holder>Ericsson AB, All Rights Reserved</holder> </copyright> <legalnotice> @@ -37,15 +37,15 @@ <section> <title>Purpose</title> <p> This application provides an API to public key infrastructure - from RFC 3280 (X.509 certificates) and public key formats defined - by the PKCS-standard.</p> + from <url href="http://www.ietf.org/rfc/rfc5280.txt">RFC + 5280</url> (X.509 certificates) and public key formats defined by + the <url href=http://www.rsa.com/rsalabs/node.asp?id=2124"> PKCS-standard</url></p> </section> <section> <title>Prerequisites</title> - <p>It is assumed that the reader is familiar with the Erlang - programming language, concepts of OTP and has a basic understanding - of the concepts of using public keys.</p> + <p>It is assumed that the reader has a basic understanding + of the concepts of using public keys and digital certificates.</p> </section> <section> diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml index 5c227557f2..2ec1fcff9d 100644 --- a/lib/public_key/doc/src/public_key.xml +++ b/lib/public_key/doc/src/public_key.xml @@ -34,7 +34,7 @@ <modulesummary> API module for public key infrastructure.</modulesummary> <description> <p>This module provides functions to handle public key infrastructure - from RFC 5280 - X.509 certificates and some parts of the PKCS-standard. + from <url href="http://www.ietf.org/rfc/rfc5280.txt">RFC 5280</url>- X.509 certificates and some parts of the PKCS-standard. </p> </description> @@ -61,7 +61,7 @@ <p><code>string = [bytes()]</code></p> <p><code>pki_asn1_type() = 'Certificate' | 'RSAPrivateKey'| 'RSAPublicKey' - 'DSAPrivateKey' | 'DSAPublicKey' | 'DHParameter' | 'SubjectPublicKeyInfo'| 'PrivateKeyInfo'</code></p> + 'DSAPrivateKey' | 'DSAPublicKey' | 'DHParameter' | 'SubjectPublicKeyInfo'| 'PrivateKeyInfo' | 'CertificationRequest'</code></p> <p><code>pem_entry () = {pki_asn1_type(), binary(), %% DER or encrypted DER not_encrypted | cipher_info()} </code></p> diff --git a/lib/public_key/include/public_key.hrl b/lib/public_key/include/public_key.hrl index 2475295974..2dfdbbb8f3 100644 --- a/lib/public_key/include/public_key.hrl +++ b/lib/public_key/include/public_key.hrl @@ -78,7 +78,7 @@ -type dsa_public_key() :: {integer(), #'Dss-Parms'{}}. -type pki_asn1_type() :: 'Certificate' | 'RSAPrivateKey' | 'RSAPublicKey' | 'DSAPrivateKey' | 'DSAPublicKey' | 'DHParameter' - | 'SubjectPublicKeyInfo'. + | 'SubjectPublicKeyInfo' | 'CertificationRequest'. -type pem_entry() :: {pki_asn1_type(), binary(), %% DER or Encrypted DER not_encrypted | {Cipher :: string(), Salt :: binary()}}. -type asn1_type() :: atom(). %% see "OTP-PUB-KEY.hrl diff --git a/lib/public_key/src/pubkey_pem.erl b/lib/public_key/src/pubkey_pem.erl index 910473d629..4012825f20 100644 --- a/lib/public_key/src/pubkey_pem.erl +++ b/lib/public_key/src/pubkey_pem.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2011. All Rights Reserved. +%% Copyright Ericsson AB 2008-2012. 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 @@ -194,7 +194,12 @@ pem_start('SubjectPublicKeyInfo') -> pem_start('DSAPrivateKey') -> <<"-----BEGIN DSA PRIVATE KEY-----">>; pem_start('DHParameter') -> - <<"-----BEGIN DH PARAMETERS-----">>. + <<"-----BEGIN DH PARAMETERS-----">>; +pem_start('CertificationRequest') -> + <<"-----BEGIN CERTIFICATE REQUEST-----">>; +pem_start('ContentInfo') -> + <<"-----BEGIN PKCS7-----">>. + pem_end(<<"-----BEGIN CERTIFICATE-----">>) -> <<"-----END CERTIFICATE-----">>; pem_end(<<"-----BEGIN RSA PRIVATE KEY-----">>) -> @@ -211,6 +216,10 @@ pem_end(<<"-----BEGIN PRIVATE KEY-----">>) -> <<"-----END PRIVATE KEY-----">>; pem_end(<<"-----BEGIN ENCRYPTED PRIVATE KEY-----">>) -> <<"-----END ENCRYPTED PRIVATE KEY-----">>; +pem_end(<<"-----BEGIN CERTIFICATE REQUEST-----">>) -> + <<"-----END CERTIFICATE REQUEST-----">>; +pem_end(<<"-----BEGIN PKCS7-----">>) -> + <<"-----END PKCS7-----">>; pem_end(_) -> undefined. @@ -229,7 +238,11 @@ asn1_type(<<"-----BEGIN DH PARAMETERS-----">>) -> asn1_type(<<"-----BEGIN PRIVATE KEY-----">>) -> 'PrivateKeyInfo'; asn1_type(<<"-----BEGIN ENCRYPTED PRIVATE KEY-----">>) -> - 'EncryptedPrivateKeyInfo'. + 'EncryptedPrivateKeyInfo'; +asn1_type(<<"-----BEGIN CERTIFICATE REQUEST-----">>) -> + 'CertificationRequest'; +asn1_type(<<"-----BEGIN PKCS7-----">>) -> + 'ContentInfo'. pem_decrypt() -> <<"Proc-Type: 4,ENCRYPTED">>. diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl index f2f30dad6e..2b83bc0a5c 100644 --- a/lib/public_key/test/public_key_SUITE.erl +++ b/lib/public_key/test/public_key_SUITE.erl @@ -111,7 +111,7 @@ all() -> groups() -> [{pem_decode_encode, [], [dsa_pem, rsa_pem, encrypted_pem, - dh_pem, cert_pem]}, + dh_pem, cert_pem, pkcs10_pem]}, {ssh_public_key_decode_encode, [], [ssh_rsa_public_key, ssh_dsa_public_key, ssh_rfc4716_rsa_comment, ssh_rfc4716_dsa_comment, ssh_rfc4716_rsa_subject, ssh_known_hosts, @@ -249,7 +249,42 @@ dh_pem(Config) when is_list(Config) -> DHParameter = public_key:pem_entry_decode(Entry), Entry = public_key:pem_entry_encode('DHParameter', DHParameter). - + +%%-------------------------------------------------------------------- + +pkcs10_pem(doc) -> + [""]; +pkcs10_pem(suite) -> + []; +pkcs10_pem(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + [{'CertificationRequest', DerPKCS10, not_encrypted} = Entry] = + erl_make_certs:pem_to_der(filename:join(Datadir, "req.pem")), + + erl_make_certs:der_to_pem(filename:join(Datadir, "new_req.pem"), [Entry]), + + PKCS10 = public_key:der_decode('CertificationRequest', DerPKCS10), + PKCS10 = public_key:pem_entry_decode(Entry), + + Entry = public_key:pem_entry_encode('CertificationRequest', PKCS10). + +%%-------------------------------------------------------------------- +pkcs7_pem(doc) -> + [""]; +pkcs7_pem(suite) -> + []; +pkcs7_pem(Config) when is_list(Config) -> + Datadir = ?config(data_dir, Config), + [{'ContentInfo', DerPKCS7, not_encrypted} = Entry] = + erl_make_certs:pem_to_der(filename:join(Datadir, "pkcs7_cert.pem")), + + erl_make_certs:der_to_pem(filename:join(Datadir, "new_pkcs7_cert.pem"), [Entry]), + + PKCS7 = public_key:der_decode('ContentInfo', DerPKCS7), + PKCS7 = public_key:pem_entry_decode(Entry), + + Entry = public_key:pem_entry_encode('ContentInfo', PKCS7). + %%-------------------------------------------------------------------- cert_pem(doc) -> [""]; diff --git a/lib/public_key/test/public_key_SUITE_data/pkcs7_cert.pem b/lib/public_key/test/public_key_SUITE_data/pkcs7_cert.pem new file mode 100644 index 0000000000..9b450a22c5 --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/pkcs7_cert.pem @@ -0,0 +1,23 @@ +-----BEGIN PKCS7----- +MIID6QYJKoZIhvcNAQcCoIID2jCCA9YCAQExADALBgkqhkiG9w0BBwGgggO8MIID +uDCCAyGgAwIBAgIBAjANBgkqhkiG9w0BAQUFADCBgzEOMAwGA1UEAxMFb3RwQ0Ex +EzARBgNVBAsTCkVybGFuZyBPVFAxFDASBgNVBAoTC0VyaWNzc29uIEFCMQswCQYD +VQQGEwJTRTESMBAGA1UEBxMJU3RvY2tob2xtMSUwIwYJKoZIhvcNAQkBFhZwZXRl +ckBlcml4LmVyaWNzc29uLnNlMB4XDTA4MDEwOTA4MjkzMFoXDTE3MTExNzA4Mjkz +MFowgYQxDzANBgNVBAMTBnNlcnZlcjETMBEGA1UECxMKRXJsYW5nIE9UUDEUMBIG +A1UEChMLRXJpY3Nzb24gQUIxCzAJBgNVBAYTAlNFMRIwEAYDVQQHEwlTdG9ja2hv +bG0xJTAjBgkqhkiG9w0BCQEWFnBldGVyQGVyaXguZXJpY3Nzb24uc2UwgZ8wDQYJ +KoZIhvcNAQEBBQADgY0AMIGJAoGBAKR20HPrkDGdiavHUyWwFEQwta2dmtF2eQZZ +i9Xk68UJYbuU7CikHs2srkrwzj0OPIqbp/xOBNzJ7Kch0o4yO6vcEAiSCJ6AB4uS +M742hrYW4qXgc18K6PqTwSuKr94sn3qQuo4hF/ymCxLrnSicrNpzGOz9A0Lf2+Vk +6hV0BtdHAgMBAAGjggE3MIIBMzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAdBgNV +HQ4EFgQUi19l/qhEwHP/CUeaEjWy4GhOBRIwgbMGA1UdIwSBqzCBqIAUBquANDqk +uHayvZ0uKOVtkd59AZuhgYykgYkwgYYxETAPBgNVBAMTCGVybGFuZ0NBMRMwEQYD +VQQLEwpFcmxhbmcgT1RQMRQwEgYDVQQKEwtFcmljc3NvbiBBQjESMBAGA1UEBxMJ +U3RvY2tob2xtMQswCQYDVQQGEwJTRTElMCMGCSqGSIb3DQEJARYWcGV0ZXJAZXJp +eC5lcmljc3Nvbi5zZYIBATAhBgNVHREEGjAYgRZwZXRlckBlcml4LmVyaWNzc29u +LnNlMCEGA1UdEgQaMBiBFnBldGVyQGVyaXguZXJpY3Nzb24uc2UwDQYJKoZIhvcN +AQEFBQADgYEAzHGutrGMSeC3Di7Z8d65SM7jZLrkkusmL+D2oPVIOGrfZbVuyfDK +U/nImm99z+lhC/N3JEEpB6PgAYSskfVdBL3LoxbUTaCn/+G3A/G8NfRVIYyANTBe +NW6ueNpjnauLzcwpyXpu3vp1VBg8wBePtGTBIbRHRgtwwHRXAddE/WuhADEA +-----END PKCS7----- diff --git a/lib/public_key/vsn.mk b/lib/public_key/vsn.mk index c8165fa247..b8af89d040 100644 --- a/lib/public_key/vsn.mk +++ b/lib/public_key/vsn.mk @@ -1 +1 @@ -PUBLIC_KEY_VSN = 0.16 +PUBLIC_KEY_VSN = 0.17 diff --git a/lib/runtime_tools/src/dyntrace.erl b/lib/runtime_tools/src/dyntrace.erl index b4579fd5ce..f7dbef6929 100644 --- a/lib/runtime_tools/src/dyntrace.erl +++ b/lib/runtime_tools/src/dyntrace.erl @@ -105,7 +105,7 @@ available() -> user_trace_s1(_Message) -> erlang:nif_error(nif_not_loaded). --spec user_trace_i4s4(iolist(), +-spec user_trace_i4s4(binary() | undefined, integer_maybe(), integer_maybe(), integer_maybe(), integer_maybe(), iolist_maybe(), iolist_maybe(), @@ -115,7 +115,7 @@ user_trace_s1(_Message) -> user_trace_i4s4(_, _, _, _, _, _, _, _, _) -> erlang:nif_error(nif_not_loaded). --spec user_trace_n(n_probe_label(), iolist(), +-spec user_trace_n(n_probe_label(), binary() | undefined, integer_maybe(), integer_maybe(), integer_maybe(), integer_maybe(), iolist_maybe(), iolist_maybe(), diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index 9de1dd1dad..aac4b462a2 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -193,7 +193,10 @@ (simply passed on to the transport protocol).</p></item> <tag><c><![CDATA[{ip_v6_disabled, boolean()}]]></c></tag> <item> - <p>Determines if SSH shall use IPv6 or not.</p></item> + <p>Determines if SSH shall use IPv6 or not.</p></item> + <tag><c><![CDATA[{idle_time, timeout()}]]></c></tag> + <item> + <p>Sets a timeout on connection when no channels are active, default is infinity</p></item> </taglist> </desc> </func> diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src index 8914aeffdb..6ba32e018f 100644 --- a/lib/ssh/src/ssh.appup.src +++ b/lib/ssh/src/ssh.appup.src @@ -19,10 +19,7 @@ {"%VSN%", [ - {<<"2.1.1">>, [{load_module, ssh_connection_handler, soft_purge, soft_purge, []}, - {load_module, ssh_connection_manager, soft_purge, soft_purge, []}, - {load_module, ssh_auth, soft_purge, soft_purge, []}, - {load_module, ssh, soft_purge, soft_purge, []}]}, + {<<"2.1.1">>, [{restart_application, ssh}]}, {<<"2.1">>, [{load_module, ssh_sftpd_file_api, soft_purge, soft_purge, []}, {load_module, ssh_connection, soft_purge, soft_purge, []}, {load_module, ssh_connection_manager, soft_purge, soft_purge, []}, @@ -35,10 +32,7 @@ {<<"1\\.*">>, [{restart_application, ssh}]} ], [ - {<<"2.1.1">>, [{load_module, ssh_connection_handler, soft_purge, soft_purge, []}, - {load_module, ssh_connection_manager, soft_purge, soft_purge, []}, - {load_module, ssh_auth, soft_purge, soft_purge, []}, - {load_module, ssh, soft_purge, soft_purge, []}]}, + {<<"2.1.1">>, [{restart_application, ssh}]}, {<<"2.1">>,[{load_module, ssh_sftpd_file_api, soft_purge, soft_purge, []}, {load_module, ssh_connection, soft_purge, soft_purge, []}, {load_module, ssh_connection_manager, soft_purge, soft_purge, []}, diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index d09f6cf34b..a569298056 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -79,7 +79,7 @@ connect(Host, Port, Options, Timeout) -> DisableIpv6 = proplists:get_value(ip_v6_disabled, SshOptions, false), Inet = inetopt(DisableIpv6), do_connect(Host, Port, [Inet | SocketOptions], - [{host, Host} | SshOptions], Timeout, DisableIpv6) + [{user_pid, self()}, {host, Host} | fix_idle_time(SshOptions)], Timeout, DisableIpv6) end. do_connect(Host, Port, SocketOptions, SshOptions, Timeout, DisableIpv6) -> @@ -91,30 +91,39 @@ do_connect(Host, Port, SocketOptions, SshOptions, Timeout, DisableIpv6) -> {ok, ConnectionSup} -> {ok, Manager} = ssh_connection_sup:connection_manager(ConnectionSup), - receive - {Manager, is_connected} -> - {ok, Manager}; - %% When the connection fails - %% ssh_connection_sup:connection_manager - %% might return undefined as the connection manager - %% could allready have terminated, so we will not - %% match the Manager in this case - {_, not_connected, {error, econnrefused}} when DisableIpv6 == false -> - do_connect(Host, Port, proplists:delete(inet6, SocketOptions), - SshOptions, Timeout, true); - {_, not_connected, {error, Reason}} -> - {error, Reason}; - {_, not_connected, Other} -> - {error, Other} - after Timeout -> - ssh_connection_manager:stop(Manager), - {error, timeout} - end + msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout) catch exit:{noproc, _} -> {error, ssh_not_started} end. - +msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout) -> + receive + {Manager, is_connected} -> + {ok, Manager}; + %% When the connection fails + %% ssh_connection_sup:connection_manager + %% might return undefined as the connection manager + %% could allready have terminated, so we will not + %% match the Manager in this case + {_, not_connected, {error, econnrefused}} when DisableIpv6 == false -> + do_connect(Host, Port, proplists:delete(inet6, SocketOptions), + SshOptions, Timeout, true); + {_, not_connected, {error, Reason}} -> + {error, Reason}; + {_, not_connected, Other} -> + {error, Other}; + {From, user_password} -> + Pass = io:get_password(), + From ! Pass, + msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout); + {From, question} -> + Answer = io:get_line(""), + From ! Answer, + msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout) + after Timeout -> + ssh_connection_manager:stop(Manager), + {error, timeout} + end. %%-------------------------------------------------------------------- %% Function: close(ConnectionRef) -> ok %% @@ -237,6 +246,13 @@ shell(Host, Port, Options) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +fix_idle_time(SshOptions) -> + case proplists:get_value(idle_time, SshOptions) of + undefined -> + [{idle_time, infinity}|SshOptions]; + _ -> + SshOptions + end. start_daemon(Host, Port, Options, Inet) -> case handle_options(Options) of {error, _Reason} = Error -> @@ -346,6 +362,8 @@ handle_option([{pref_public_key_algs, _} = Opt | Rest], SocketOptions, SshOption handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{quiet_mode, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); +handle_option([{idle_time, _} = Opt | Rest], SocketOptions, SshOptions) -> + handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, [handle_inet_option(Opt) | SocketOptions], SshOptions). @@ -421,6 +439,8 @@ handle_ssh_option({shell, Value} = Opt) when is_function(Value) -> handle_ssh_option({quiet_mode, Value} = Opt) when Value == true; Value == false -> Opt; +handle_ssh_option({idle_time, Value} = Opt) when is_integer(Value), Value > 0 -> + Opt; handle_ssh_option(Opt) -> throw({error, {eoptions, Opt}}). diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 27e44df554..c436793dc4 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -71,7 +71,7 @@ password_msg([#ssh{opts = Opts, io_cb = IoCb, ssh_bits:install_messages(userauth_passwd_messages()), Password = case proplists:get_value(password, Opts) of undefined -> - user_interaction(IoCb); + user_interaction(IoCb, Ssh); PW -> PW end, @@ -89,10 +89,10 @@ password_msg([#ssh{opts = Opts, io_cb = IoCb, Ssh) end. -user_interaction(ssh_no_io) -> +user_interaction(ssh_no_io, _) -> not_ok; -user_interaction(IoCb) -> - IoCb:read_password("ssh password: "). +user_interaction(IoCb, Ssh) -> + IoCb:read_password("ssh password: ", Ssh). %% See RFC 4256 for info on keyboard-interactive @@ -401,11 +401,11 @@ keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) -> %% Special case/fallback for just one prompt %% (assumed to be the password prompt) case proplists:get_value(password, Opts) of - undefined -> keyboard_interact(IoCb, Name, Instr, PromptInfos); + undefined -> keyboard_interact(IoCb, Name, Instr, PromptInfos, Opts); PW -> [PW] end; undefined -> - keyboard_interact(IoCb, Name, Instr, PromptInfos); + keyboard_interact(IoCb, Name, Instr, PromptInfos, Opts); KbdInteractFun -> Prompts = lists:map(fun({Prompt, _Echo}) -> Prompt end, PromptInfos), @@ -419,15 +419,15 @@ keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) -> end end. -keyboard_interact(IoCb, Name, Instr, Prompts) -> +keyboard_interact(IoCb, Name, Instr, Prompts, Opts) -> if Name /= "" -> IoCb:format("~s", [Name]); true -> ok end, if Instr /= "" -> IoCb:format("~s", [Instr]); true -> ok end, - lists:map(fun({Prompt, true}) -> IoCb:read_line(Prompt); - ({Prompt, false}) -> IoCb:read_password(Prompt) + lists:map(fun({Prompt, true}) -> IoCb:read_line(Prompt, Opts); + ({Prompt, false}) -> IoCb:read_password(Prompt, Opts) end, Prompts). diff --git a/lib/ssh/src/ssh_connection_manager.erl b/lib/ssh/src/ssh_connection_manager.erl index 513b0f86c7..0c1eee5186 100644 --- a/lib/ssh/src/ssh_connection_manager.erl +++ b/lib/ssh/src/ssh_connection_manager.erl @@ -62,6 +62,7 @@ latest_channel_id = 0, opts, channel_args, + idle_timer_ref, % timerref connected }). @@ -203,6 +204,8 @@ init([client, Opts]) -> ChannelPid = proplists:get_value(channel_pid, Opts), self() ! {start_connection, client, [Parent, Address, Port, SocketOpts, Options]}, + TimerRef = get_idle_time(Options), + {ok, #state{role = client, client = ChannelPid, connection_state = #connection{channel_cache = Cache, @@ -211,6 +214,7 @@ init([client, Opts]) -> connection_supervisor = Parent, requests = []}, opts = Opts, + idle_timer_ref = TimerRef, connected = false}}. %%-------------------------------------------------------------------- @@ -230,6 +234,13 @@ handle_call({request, ChannelPid, ChannelId, Type, Data}, From, State0) -> %% channel is sent later when reply arrives from the connection %% handler. lists:foreach(fun send_msg/1, Replies), + SshOpts = proplists:get_value(ssh_opts, State0#state.opts), + case proplists:get_value(idle_time, SshOpts) of + infinity -> + ok; + _IdleTime -> + erlang:send_after(5000, self(), {check_cache, [], []}) + end, {noreply, State}; handle_call({request, ChannelId, Type, Data}, From, State0) -> @@ -358,7 +369,7 @@ handle_call({open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data}, recv_packet_size = MaxPacketSize}, ssh_channel:cache_update(Cache, Channel), State = add_request(true, ChannelId, From, State1), - {noreply, State}; + {noreply, remove_timer_ref(State)}; handle_call({send_window, ChannelId}, _From, #state{connection_state = @@ -403,6 +414,13 @@ handle_call({close, ChannelId}, _, send_msg({connection_reply, Pid, ssh_connection:channel_close_msg(Id)}), ssh_channel:cache_update(Cache, Channel#channel{sent_close = true}), + SshOpts = proplists:get_value(ssh_opts, State#state.opts), + case proplists:get_value(idle_time, SshOpts) of + infinity -> + ok; + _IdleTime -> + erlang:send_after(5000, self(), {check_cache, [], []}) + end, {reply, ok, State}; undefined -> {reply, ok, State} @@ -523,7 +541,10 @@ handle_info({start_connection, client, Pid ! {self(), not_connected, Reason}, {stop, {shutdown, normal}, State} end; - +handle_info({check_cache, _ , _}, + #state{connection_state = + #connection{channel_cache = Cache}} = State) -> + {noreply, check_cache(State, Cache)}; handle_info({ssh_cm, _Sender, Msg}, State0) -> %% Backwards compatibility! State = cm_message(Msg, State0), @@ -621,6 +642,45 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +get_idle_time(SshOptions) -> + case proplists:get_value(idle_time, SshOptions) of + infinity -> + infinity; + _IdleTime -> %% We dont want to set the timeout on first connect + undefined + end. +check_cache(State, Cache) -> + %% Check the number of entries in Cache + case proplists:get_value(size, ets:info(Cache)) of + 0 -> + Opts = proplists:get_value(ssh_opts, State#state.opts), + case proplists:get_value(idle_time, Opts) of + infinity -> + State; + undefined -> + State; + Time -> + case State#state.idle_timer_ref of + undefined -> + TimerRef = erlang:send_after(Time, self(), {'EXIT', [], "Timeout"}), + State#state{idle_timer_ref=TimerRef}; + _ -> + State + end + end; + _ -> + State + end. +remove_timer_ref(State) -> + case State#state.idle_timer_ref of + infinity -> %% If the timer is not activated + State; + undefined -> %% If we already has cancelled the timer + State; + TimerRef -> %% Timer is active + erlang:cancel_timer(TimerRef), + State#state{idle_timer_ref = undefined} + end. channel_data(Id, Type, Data, Connection0, ConnectionPid, From, State) -> case ssh_connection:channel_data(Id, Type, Data, Connection0, ConnectionPid, From) of @@ -718,7 +778,7 @@ handle_channel_down(ChannelPid, #state{connection_state = (_,Acc) -> Acc end, [], Cache), - {{replies, []}, State}. + {{replies, []}, check_cache(State, Cache)}. update_sys(Cache, Channel, Type, ChannelPid) -> ssh_channel:cache_update(Cache, diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl index 1dbd097423..17a7cebb4a 100644 --- a/lib/ssh/src/ssh_io.erl +++ b/lib/ssh/src/ssh_io.erl @@ -23,37 +23,52 @@ -module(ssh_io). --export([yes_no/1, read_password/1, read_line/1, format/2]). +-export([yes_no/2, read_password/2, read_line/2, format/2]). -import(lists, [reverse/1]). +-include("ssh.hrl"). +read_line(Prompt, Ssh) -> + format("~s", [listify(Prompt)]), + proplists:get_value(user_pid, Ssh) ! {self(), question}, + receive + Answer -> + Answer + end. -read_line(Prompt) when is_list(Prompt) -> - io:get_line(list_to_atom(Prompt)); -read_line(Prompt) when is_atom(Prompt) -> - io:get_line(Prompt). - -read_ln(Prompt) -> - trim(read_line(Prompt)). - -yes_no(Prompt) -> +yes_no(Prompt, Ssh) -> io:format("~s [y/n]?", [Prompt]), - case read_ln('') of - "y" -> yes; - "n" -> no; - "Y" -> yes; - "N" -> no; - _ -> - io:format("please answer y or n\n"), - yes_no(Prompt) + proplists:get_value(user_pid, Ssh#ssh.opts) ! {self(), question}, + receive + Answer -> + case trim(Answer) of + "y" -> yes; + "n" -> no; + "Y" -> yes; + "N" -> no; + y -> yes; + n -> no; + _ -> + io:format("please answer y or n\n"), + yes_no(Prompt, Ssh) + end end. -read_password(Prompt) -> +read_password(Prompt, Ssh) -> format("~s", [listify(Prompt)]), - case io:get_password() of - "" -> - read_password(Prompt); - Pass -> Pass + case is_list(Ssh) of + false -> + proplists:get_value(user_pid, Ssh#ssh.opts) ! {self(), user_password}; + _ -> + proplists:get_value(user_pid, Ssh) ! {self(), user_password} + end, + receive + Answer -> + case Answer of + "" -> + read_password(Prompt, Ssh); + Pass -> Pass + end end. listify(A) when is_atom(A) -> diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 1f912c9bdf..7f6e7d9946 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -133,7 +133,7 @@ kex_dh_gex_messages() -> ]. yes_no(Ssh, Prompt) -> - (Ssh#ssh.io_cb):yes_no(Prompt). + (Ssh#ssh.io_cb):yes_no(Prompt, Ssh). connect(ConnectionSup, Address, Port, SocketOpts, Opts) -> Timeout = proplists:get_value(connect_timeout, Opts, infinity), diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index 7a641c92c1..5fec7f0cd7 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -42,14 +42,15 @@ all() -> {group, dsa_pass_key}, {group, rsa_pass_key}, {group, internal_error}, + {group, idle_time}, daemon_already_started, server_password_option, server_userpassword_option, close]. groups() -> - [{dsa_key, [], [send, exec, exec_compressed, shell, known_hosts]}, - {rsa_key, [], [send, exec, exec_compressed, shell, known_hosts]}, + [{dsa_key, [], [send, exec, exec_compressed, shell, known_hosts, idle_time]}, + {rsa_key, [], [send, exec, exec_compressed, shell, known_hosts, idle_time]}, {dsa_pass_key, [], [pass_phrase]}, {rsa_pass_key, [], [pass_phrase]}, {internal_error, [], [internal_error]} @@ -234,7 +235,27 @@ exec_compressed(Config) when is_list(Config) -> ssh:stop_daemon(Pid). %%-------------------------------------------------------------------- +idle_time(doc) -> + ["Idle timeout test"]; +idle_time(Config) -> + SystemDir = filename:join(?config(priv_dir, Config), system), + UserDir = ?config(priv_dir, Config), + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {user_dir, UserDir}, + {failfun, fun ssh_test_lib:failfun/2}]), + ConnectionRef = + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user_dir, UserDir}, + {user_interaction, false}]), + {ok, Id} = ssh_connection:session_channel(ConnectionRef, 1000), + ssh_connection:close(ConnectionRef, Id), + receive + after 10000 -> + {error,channel_closed} = ssh_connection:session_channel(ConnectionRef, 1000) + end, + ssh:stop_daemon(Pid). +%%-------------------------------------------------------------------- shell(doc) -> ["Test that ssh:shell/2 works"]; shell(Config) when is_list(Config) -> diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src index 76550fa04b..c118c129e8 100644 --- a/lib/ssl/src/ssl.appup.src +++ b/lib/ssl/src/ssl.appup.src @@ -1,14 +1,21 @@ %% -*- erlang -*- {"%VSN%", [ - {"5.0.1", [{restart_application, ssl}]}, - {"5.0", [{restart_application, ssl}]}, + {"5.1", [ + {load_module, ssl_connection, soft_purge, soft_purge, []} + ] + }, + {<<"5.0\\*">>, [{restart_application, ssl}]}, {<<"4\\.*">>, [{restart_application, ssl}]}, {<<"3\\.*">>, [{restart_application, ssl}]} ], [ - {"5.0.1", [{restart_application, ssl}]}, - {"5.0", [{restart_application, ssl}]}, + {"5.1", [ + {load_module, ssl_connection, soft_purge, soft_purge, []} + ] + }, + {"5.1", [{restart_application, ssl}]}, + {<<"5.0\\*">>, [{restart_application, ssl}]}, {<<"4\\.*">>, [{restart_application, ssl}]}, {<<"3\\.*">>, [{restart_application, ssl}]} ]}. diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 407ec14b0a..87cf49d07d 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -2516,9 +2516,9 @@ handle_unrecv_data(StateName, #state{socket = Socket, transport_cb = Transport} handle_close_alert(Data, StateName, State) end. -handle_close_alert(Data, StateName, State) -> - case next_tls_record(Data, State) of - #ssl_tls{type = ?ALERT, fragment = EncAlerts} -> +handle_close_alert(Data, StateName, State0) -> + case next_tls_record(Data, State0) of + {#ssl_tls{type = ?ALERT, fragment = EncAlerts}, State} -> [Alert|_] = decode_alerts(EncAlerts), handle_normal_shutdown(Alert, StateName, State); _ -> diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index e381b73c27..bc8b8fd039 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -1 +1 @@ -SSL_VSN = 5.1 +SSL_VSN = 5.1.1 diff --git a/lib/test_server/src/erl2html2.erl b/lib/test_server/src/erl2html2.erl index 6891e87e48..9c459c05d4 100644 --- a/lib/test_server/src/erl2html2.erl +++ b/lib/test_server/src/erl2html2.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2011. All Rights Reserved. +%% Copyright Ericsson AB 1997-2012. 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 @@ -18,19 +18,9 @@ %% %%%------------------------------------------------------------------ -%%% Purpose:Convert Erlang files to html. (Pretty faaast... :-) +%%% Purpose:Convert Erlang files to html. %%%------------------------------------------------------------------ -%-------------------------------------------------------------------- -% Some stats (Sparc5@110Mhz): -% 4109 lines (erl_parse.erl): 3.00 secs -% 1847 lines (application_controller.erl): 0.57 secs -% 3160 lines (test_server.erl): 1.00 secs -% 1199 lines (ts_estone.erl): 0.35 secs -% -% Avg: ~4.5e-4s/line, or ~0.45s/1000 lines, or ~2200 lines/sec. -%-------------------------------------------------------------------- - -module(erl2html2). -export([convert/2, convert/3]). @@ -52,134 +42,141 @@ convert(File, Dest) -> "<body bgcolor=\"white\" text=\"black\"" " link=\"blue\" vlink=\"purple\" alink=\"red\">\n"], convert(File, Dest, Header). - + + convert(File, Dest, Header) -> - case file:read_file(File) of - {ok, Bin} -> - Code=binary_to_list(Bin), - statistics(runtime), - {Html1, Lines} = root(Code, [], 1), - Html = [Header, - "<pre>\n", Html1, "</pre>\n", - footer(Lines),"</body>\n</html>\n"], - file:write_file(Dest, Html); - {error, Reason} -> - {error, Reason} + %% statistics(runtime), + case parse_file(File) of + {ok,Functions} -> + %% {_, Time1} = statistics(runtime), + %% io:format("Parsed file in ~.2f Seconds.~n",[Time1/1000]), + case file:open(File,[raw,{read_ahead,10000}]) of + {ok,SFd} -> + case file:open(Dest,[write,raw]) of + {ok,DFd} -> + file:write(DFd,[Header,"<pre>\n"]), + Lines = build_html(SFd,DFd,Functions), + file:write(DFd,["</pre>\n",footer(), + "</body>\n</html>\n"]), + %% {_, Time2} = statistics(runtime), + %% io:format("Converted ~p lines in ~.2f Seconds.~n", + %% [Lines, Time2/1000]), + file:close(SFd), + file:close(DFd), + ok; + Error -> + Error + end; + Error -> + Error + end; + Error -> + Error end. -root([], Res, Line) -> - {Res, Line}; -root([Char0|Code], Res, Line0) -> - Char = [Char0], - case Char of - "-" -> - {Match, Line1, NewCode0, AttName} = - read_to_char(Line0+1, Code, [], [$(, $.]), - {_, Line2, NewCode, Stuff} = read_to_char(Line1, NewCode0, [], $\n), - NewRes = [Res,linenum(Line0),"-<b>",AttName, - "</b>",Match, Stuff, "\n"], - root(NewCode, NewRes, Line2); - "%" -> - {_, Line, NewCode, Stuff} = read_to_char(Line0+1, Code, [], $\n), - NewRes = [Res,linenum(Line0),"<i>%",Stuff,"</i>\n"], - root(NewCode, NewRes, Line); - "\n" -> - root(Code, [Res,linenum(Line0), "\n"], Line0+1); - " " -> - {_, Line, NewCode, Stuff} = read_to_char(Line0+1, Code, [], $\n), - root(NewCode, [Res,linenum(Line0)," ",Stuff, "\n"], - Line); - "\t" -> - {_, Line, NewCode, Stuff} = read_to_char(Line0+1, Code, [], $\n), - root(NewCode, [Res,linenum(Line0),"\t",Stuff, "\n"], - Line); - [Chr|_] when Chr>96, Chr<123 -> - %% Assumed to be function/clause start. - %% FIXME: This will trivially generate non-unique anchors - %% (one for each clause) --- which is illegal HTML. - {_, Line1, NewCode0, FName0} = read_to_char(Line0+1, Code, [], $(), - {_, Line2, NewCode, Stuff} = - read_to_char(Line1,NewCode0, [], $\n), - FuncName = [[Chr],FName0], - NewRes=[Res,"<a name=",FuncName,">", - linenum(Line0),"<b>",FuncName,"</b></a>", - "(",Stuff, "\n"], - root(NewCode, NewRes, Line2); - Chr -> - {_, Line, NewCode, Stuff} = read_to_char(Line0+1, Code, [], $\n), - root(NewCode, [Res,linenum(Line0),Chr,Stuff, "\n"], - Line) +%%%----------------------------------------------------------------- +%%% Parse the input file to get the line numbers for all function +%%% definitions. This will be used when creating link targets for each +%%% function in build_html/5. +%%% +%%% All function clauses are also marked in order to allow +%%% possibly_enhance/2 to write these in bold. +parse_file(File) -> + case epp:open(File, [], []) of + {ok,Epp} -> + Forms = parse_file(Epp,File,false), + epp:close(Epp), + {ok,Forms}; + {error,E} -> + {error,E} end. -read_to_char(Line0, [], Res, _Chr) -> - {nomatch, Line0, [], Res}; -read_to_char(Line0, [Char|Code], Res, Chr) -> - case Char of - Chr -> {Char, Line0, Code, Res}; - _ when is_list(Chr) -> - case lists:member(Char,Chr) of - true -> - {Char, Line0, Code, Res}; - false -> - {Line,NewCode,NewRes} = maybe_convert(Line0,Code,Res,Char), - read_to_char(Line, NewCode, NewRes, Chr) + +parse_file(Epp,File,InCorrectFile) -> + case epp:parse_erl_form(Epp) of + {ok,Form} -> + case Form of + {attribute,_,file,{File,_}} -> + parse_file(Epp,File,true); + {attribute,_,file,{_OtherFile,_}} -> + parse_file(Epp,File,false); + {function,L,F,A,[_|C]} when InCorrectFile -> + Clauses = [{clause,CL} || {clause,CL,_,_,_} <- C], + [{atom_to_list(F),A,L} | Clauses] ++ + parse_file(Epp,File,true); + _ -> + parse_file(Epp,File,InCorrectFile) end; - _ -> - {Line,NewCode,NewRes} = maybe_convert(Line0,Code,Res,Char), - read_to_char(Line,NewCode, NewRes, Chr) + {error,_E} -> + parse_file(Epp,File,InCorrectFile); + {eof,_Location} -> + [] end. -maybe_convert(Line0,Code,Res,Chr) -> - case Chr of - %% Quoted stuff should not have the highlighting like normal code - %% FIXME: unbalanced quotes (e.g. in comments) will cause trouble with - %% highlighting and line numbering in the rest of the module. - $" -> - {_, Line1, NewCode, Stuff0} = read_to_char(Line0, Code, [], $"), - {Line2,Stuff} = add_linenumbers(Line1,lists:flatten(Stuff0),[]), - {Line2,NewCode,[Res,$",Stuff,$"]}; - %% These chars have meaning in HTML, and *must* *not* be - %% written as themselves. - $& -> - {Line0, Code, [Res,"&"]}; - $< -> - {Line0, Code, [Res,"<"]}; - $> -> - {Line0, Code, [Res,">"]}; - %% Everything else is simply copied. - OtherChr -> - {Line0, Code, [Res,OtherChr]} - end. +%%%----------------------------------------------------------------- +%%% Add a link target for each line and one for each function definition. +build_html(SFd,DFd,Functions) -> + build_html(SFd,DFd,file:read_line(SFd),1,Functions,false). -add_linenumbers(Line,[Chr|Chrs],Res) -> - case Chr of - $\n -> add_linenumbers(Line+1,Chrs,[Res,$\n,linenum(Line)]); - _ -> add_linenumbers(Line,Chrs,[Res,Chr]) - end; -add_linenumbers(Line,[],Res) -> - {Line,Res}. +build_html(SFd,DFd,{ok,Str},L,[{F,A,L}|Functions],_IsFuncDef) -> + FALink = http_uri:encode(F++"-"++integer_to_list(A)), + file:write(DFd,["<a name=\"",FALink,"\"/>"]), + build_html(SFd,DFd,{ok,Str},L,Functions,true); +build_html(SFd,DFd,{ok,Str},L,[{clause,L}|Functions],_IsFuncDef) -> + build_html(SFd,DFd,{ok,Str},L,Functions,true); +build_html(SFd,DFd,{ok,Str},L,Functions,IsFuncDef) -> + LStr = line_number(L), + Str1 = line(Str,IsFuncDef), + file:write(DFd,[LStr,Str1]), + build_html(SFd,DFd,file:read_line(SFd),L+1,Functions,false); +build_html(_SFd,_DFd,eof,L,_Functions,_IsFuncDef) -> + L. -%% Make nicely indented line numbers. -linenum(Line) -> - Num = integer_to_list(Line), - A = case Line rem 10 of - 0 -> "<a name=\"" ++ Num ++"\"></a>"; - _ -> [] - end, +line_number(L) -> + LStr = integer_to_list(L), Pred = - case length(Num) of + case length(LStr) of Length when Length < 5 -> lists:duplicate(5-Length,$\s); _ -> [] end, - [A,Pred,integer_to_list(Line),":"]. + ["<a name=\"",LStr,"\"/>",Pred,LStr,": "]. + +line(Str,IsFuncDef) -> + Str1 = htmlize(Str), + possibly_enhance(Str1,IsFuncDef). + +%%%----------------------------------------------------------------- +%%% Substitute special characters that should not appear in HTML +htmlize([$<|Str]) -> + [$&,$l,$t,$;|htmlize(Str)]; +htmlize([$>|Str]) -> + [$&,$g,$t,$;|htmlize(Str)]; +htmlize([$&|Str]) -> + [$&,$a,$m,$p,$;|htmlize(Str)]; +htmlize([$"|Str]) -> + [$&,$q,$u,$o,$t,$;|htmlize(Str)]; +htmlize([Ch|Str]) -> + [Ch|htmlize(Str)]; +htmlize([]) -> + []. + +%%%----------------------------------------------------------------- +%%% Write comments in italic and function definitions in bold. +possibly_enhance(Str,true) -> + case lists:splitwith(fun($() -> false; (_) -> true end, Str) of + {_,[]} -> Str; + {F,A} -> ["<b>",F,"</b>",A] + end; +possibly_enhance([$%|_]=Str,_) -> + ["<i>",Str--"\n","</i>","\n"]; +possibly_enhance([$-|_]=Str,_) -> + possibly_enhance(Str,true); +possibly_enhance(Str,false) -> + Str. -footer(_Lines) -> +%%%----------------------------------------------------------------- +%%% End of the file +footer() -> "". -%% {_, Time} = statistics(runtime), -%% io:format("Converted ~p lines in ~.2f Seconds.~n", -%% [Lines, Time/1000]), -%% S = "<i>The transformation of this file (~p lines) took ~.2f seconds</i>", -%% F = lists:flatten(io_lib:format(S, [Lines, Time/1000])), -%% ["<hr size=1>",F,"<br>\n"]. diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl index 1d3394e0ca..14cdfd391a 100644 --- a/lib/test_server/src/test_server.erl +++ b/lib/test_server/src/test_server.erl @@ -724,9 +724,11 @@ call_end_conf(Mod,Func,TCPid,TCExitReason,Loc,Conf,TVal) -> Data = {Mod,Func,TCPid,TCExitReason,Loc}, EndConfProc = fun() -> + process_flag(trap_exit,true), % to catch timetraps Supervisor = self(), EndConfApply = fun() -> + timetrap(TVal), case catch apply(Mod,end_per_testcase,[Func,Conf]) of {'EXIT',Why} -> timer:sleep(1), @@ -745,14 +747,14 @@ call_end_conf(Mod,Func,TCPid,TCExitReason,Loc,Conf,TVal) -> {Pid,end_conf} -> Starter ! {self(),{call_end_conf,Data,ok}}; {'EXIT',Pid,Reason} -> - Starter ! {self(),{call_end_conf,Data,{error,Reason}}} - after TVal -> - exit(Pid, kill), group_leader() ! {printout,12, "WARNING! ~p:end_per_testcase(~p, ~p)" - " failed!\n\tReason: timetrap timeout" - " after ~w ms!\n", [Mod,Func,Conf,TVal]}, - Starter ! {self(),{call_end_conf,Data,{error,timeout}}} + " failed!\n\tReason: ~p\n", + [Mod,Func,Conf,Reason]}, + Starter ! {self(),{call_end_conf,Data,{error,Reason}}}; + {'EXIT',_OtherPid,Reason} -> + %% Probably the parent - not much to do about that + exit(Reason) end end, spawn_link(EndConfProc). diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl index 7b2ebcefc0..bc08c12089 100644 --- a/lib/test_server/src/test_server_ctrl.erl +++ b/lib/test_server/src/test_server_ctrl.erl @@ -1796,7 +1796,7 @@ start_minor_log_file1(Mod, Func, LogDir, AbsName, MFA) -> lists:member(no_src, get(test_server_logopts))} of {true,false} -> print(Lev, "<a href=\"~s#~s\">source code for ~p:~p/1</a>\n", - [SrcListing,Func,Mod,Func]); + [SrcListing,atom_to_list(Func)++"-1",Mod,Func]); _ -> ok end, diff --git a/lib/test_server/src/test_server_h.erl b/lib/test_server/src/test_server_h.erl index fdeee59326..78daba855d 100644 --- a/lib/test_server/src/test_server_h.erl +++ b/lib/test_server/src/test_server_h.erl @@ -131,6 +131,11 @@ report_receiver(warning_msg, _) -> kernel; report_receiver(warning_report, _) -> kernel; report_receiver(info, _) -> kernel; report_receiver(info_msg, _) -> kernel; +report_receiver(info_report,Tuple) + when is_tuple(Tuple) andalso + (element(1,Tuple)==ct_connection orelse + element(1,Tuple)==conn_log) -> + none; report_receiver(info_report, _) -> kernel; report_receiver(_, _) -> none. diff --git a/lib/test_server/src/test_server_sup.erl b/lib/test_server/src/test_server_sup.erl index ba5bb9f5d2..c7553cccb5 100644 --- a/lib/test_server/src/test_server_sup.erl +++ b/lib/test_server/src/test_server_sup.erl @@ -568,16 +568,11 @@ format_loc1({Mod,Func,Line}) -> {false,[$E,$T,$I,$U,$S,$_|_]} -> io_lib:format("{~s,~w,<a href=\"~s~s#~w\">~w</a>}", [ModStr,Func,downcase(ModStr),?src_listing_ext, - round_to_10(Line),Line]); + Line,Line]); _ -> io_lib:format("{~s,~w,~w}",[ModStr,Func,Line]) end. -round_to_10(N) when (N rem 10) == 0 -> - N; -round_to_10(N) -> - trunc(N/10)*10. - downcase(S) -> downcase(S, []). downcase([Uc|Rest], Result) when $A =< Uc, Uc =< $Z -> downcase(Rest, [Uc-$A+$a|Result]); diff --git a/lib/test_server/test/Makefile b/lib/test_server/test/Makefile index a3f9820d7f..afccc28662 100644 --- a/lib/test_server/test/Makefile +++ b/lib/test_server/test/Makefile @@ -26,7 +26,8 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk MODULES= \ test_server_SUITE \ - test_server_test_lib + test_server_test_lib \ + erl2html2_SUITE ERL_FILES= $(MODULES:%=%.erl) diff --git a/lib/test_server/test/erl2html2_SUITE.erl b/lib/test_server/test/erl2html2_SUITE.erl new file mode 100644 index 0000000000..96175413a1 --- /dev/null +++ b/lib/test_server/test/erl2html2_SUITE.erl @@ -0,0 +1,254 @@ +%%%------------------------------------------------------------------- +%%% @author Siri Hansen <[email protected]> +%%% @copyright (C) 2012, Siri Hansen +%%% @doc +%%% +%%% @end +%%% Created : 15 Nov 2012 by Siri Hansen <[email protected]> +%%%------------------------------------------------------------------- +-module(erl2html2_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + + +-define(HEADER, + ["<!DOCTYPE HTML PUBLIC", + "\"-//W3C//DTD HTML 3.2 Final//EN\">\n", + "<!-- autogenerated by 'erl2html2' -->\n", + "<html>\n", + "<head><title>Module ", Src, "</title>\n", + "<meta http-equiv=\"cache-control\" ", + "content=\"no-cache\">\n", + "</head>\n", + "<body bgcolor=\"white\" text=\"black\" ", + "link=\"blue\" vlink=\"purple\" alink=\"red\">\n"]). + +%%-------------------------------------------------------------------- +%% @spec suite() -> Info +%% Info = [tuple()] +%% @end +%%-------------------------------------------------------------------- +suite() -> + [{timetrap,{seconds,30}}, + {ct_hooks,[ts_install_cth,test_server_test_lib]}]. + +%%-------------------------------------------------------------------- +%% @spec init_per_suite(Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + Config. + +%%-------------------------------------------------------------------- +%% @spec end_per_suite(Config0) -> void() | {save_config,Config1} +%% Config0 = Config1 = [tuple()] +%% @end +%%-------------------------------------------------------------------- +end_per_suite(_Config) -> + ok. + +%%-------------------------------------------------------------------- +%% @spec init_per_group(GroupName, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +init_per_group(_GroupName, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% @spec end_per_group(GroupName, Config0) -> +%% void() | {save_config,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%% @end +%%-------------------------------------------------------------------- +end_per_group(_GroupName, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% @spec init_per_testcase(TestCase, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +init_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% @spec end_per_testcase(TestCase, Config0) -> +%% void() | {save_config,Config1} | {fail,Reason} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +end_per_testcase(_TestCase, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% @spec groups() -> [Group] +%% Group = {GroupName,Properties,GroupsAndTestCases} +%% GroupName = atom() +%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] +%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] +%% TestCase = atom() +%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}} +%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | +%% repeat_until_any_ok | repeat_until_any_fail +%% N = integer() | forever +%% @end +%%-------------------------------------------------------------------- +groups() -> + []. + +%%-------------------------------------------------------------------- +%% @spec all() -> GroupsAndTestCases | {skip,Reason} +%% GroupsAndTestCases = [{group,GroupName} | TestCase] +%% GroupName = atom() +%% TestCase = atom() +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +all() -> + [m1]. + +%%-------------------------------------------------------------------- +%% @spec TestCase() -> Info +%% Info = [tuple()] +%% @end +%%-------------------------------------------------------------------- +m1() -> + []. + +%%-------------------------------------------------------------------- +%% @spec TestCase(Config0) -> +%% ok | exit() | {skip,Reason} | {comment,Comment} | +%% {save_config,Config1} | {skip_and_save,Reason,Config1} +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% Comment = term() +%% @end +%%-------------------------------------------------------------------- +m1(Config) -> + {Src,Dst} = convert_module("m1",Config), + {true,L} = check_line_numbers(Src,Dst), + ok = check_link_targets(Src,Dst,L,[{baz,0}]), + ok. + +convert_module(Mod,Config) -> + DataDir = ?config(data_dir,Config), + PrivDir = ?config(priv_dir,Config), + Src = filename:join(DataDir,Mod++".erl"), + Dst = filename:join(PrivDir,Mod++".erl.html"), + io:format("<a href=\"~s\">~s</a>\n",[Src,filename:basename(Src)]), + ok = erl2html2:convert(Src, Dst, "<html><body>"), + io:format("<a href=\"~s\">~s</a>\n",[Dst,filename:basename(Dst)]), + {Src,Dst}. + +%% Check that there are the same number of lines in each file, and +%% that all line numbers are displayed in the dst file. +check_line_numbers(Src,Dst) -> + {ok,SFd} = file:open(Src,[read]), + {ok,DFd} = file:open(Dst,[read]), + {ok,SN} = count_src_lines(SFd,0), + ok = file:close(SFd), + {ok,DN} = read_dst_line_numbers(DFd), + ok = file:close(DFd), + {SN == DN,SN}. + +count_src_lines(Fd,N) -> + case io:get_line(Fd,"") of + eof -> + {ok,N}; + {error,Reason} -> + {error,Reason,N}; + _Line -> + count_src_lines(Fd,N+1) + end. + +read_dst_line_numbers(Fd) -> + "<html><body><pre>\n" = io:get_line(Fd,""), + read_dst_line_numbers(Fd,0). +read_dst_line_numbers(Fd,Last) when is_integer(Last) -> + case io:get_line(Fd,"") of + eof -> + {ok,Last}; + {error,Reason} -> + {error,Reason,Last}; + "</pre>"++_ -> + {ok,Last}; + "</body>"++_ -> + {ok,Last}; + Line -> + %% erlang:display(Line), + Num = check_line_number(Last,Line,Line), + read_dst_line_numbers(Fd,Num) + end. + +check_line_number(Last,Line,OrigLine) -> + case Line of + "<a name="++_ -> + [$>|Rest] = lists:dropwhile(fun($>) -> false; (_) -> true end,Line), + check_line_number(Last,Rest,OrigLine); + _ -> + [N |_] = string:tokens(Line,":"), +% erlang:display(N), + Num = + try list_to_integer(string:strip(N)) + catch _:_ -> ct:fail({no_line_number_after,Last,OrigLine}) + end, + if Num == Last+1 -> + Num; + true -> + ct:fail({unexpected_integer,Num,Last}) + end + end. + + +%% Check that there is one link target for each line and one for each +%% function. +%% The test module has -compile(export_all), so all functions are +%% found by listing the exported ones. +check_link_targets(Src,Dst,L,RmFncs) -> + Mod = list_to_atom(filename:basename(filename:rootname(Src))), + Exports = Mod:module_info(exports)--[{module_info,0},{module_info,1}|RmFncs], + {ok,{[],L},_} = xmerl_sax_parser:file(Dst, + [{event_fun,fun sax_event/3}, + {event_state,{Exports,0}}]), + ok. + +sax_event(Event,_Loc,State) -> + sax_event(Event,State). + +sax_event({startElement,_Uri,"a",_QN,Attrs},{Exports,PrevLine}) -> + {_,_,"name",Name} = lists:keyfind("name",3,Attrs), + case catch list_to_integer(Name) of + Line when is_integer(Line) -> + case PrevLine + 1 of + Line -> +% erlang:display({found_line,Line}), + {Exports,Line}; + Other -> + ct:fail({unexpected_line_number_target,Other}) + end; + {'EXIT',_} -> + {match,[FStr,AStr]} = + re:run(Name,"^(.*)-([0-9]+)$",[{capture,all_but_first,list}]), + F = list_to_atom(http_uri:decode(FStr)), + A = list_to_integer(AStr), +% erlang:display({found_fnc,F,A}), + A = proplists:get_value(F,Exports), + {lists:delete({F,A},Exports),PrevLine} + end; +sax_event(_,State) -> + State. diff --git a/lib/test_server/test/erl2html2_SUITE_data/Makefile.src b/lib/test_server/test/erl2html2_SUITE_data/Makefile.src new file mode 100644 index 0000000000..942ac0584b --- /dev/null +++ b/lib/test_server/test/erl2html2_SUITE_data/Makefile.src @@ -0,0 +1,2 @@ +all: + erlc -Iinclude m1.erl
\ No newline at end of file diff --git a/lib/test_server/test/erl2html2_SUITE_data/header1.hrl b/lib/test_server/test/erl2html2_SUITE_data/header1.hrl new file mode 100644 index 0000000000..53d1b79ac5 --- /dev/null +++ b/lib/test_server/test/erl2html2_SUITE_data/header1.hrl @@ -0,0 +1,4 @@ +baz() -> + ok. + +-define(MACRO_DEFINING_A_FUNCTION,quux() -> ok). diff --git a/lib/test_server/test/erl2html2_SUITE_data/include/header2.hrl b/lib/test_server/test/erl2html2_SUITE_data/include/header2.hrl new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/test_server/test/erl2html2_SUITE_data/include/header2.hrl diff --git a/lib/test_server/test/erl2html2_SUITE_data/m1.erl b/lib/test_server/test/erl2html2_SUITE_data/m1.erl new file mode 100644 index 0000000000..156f1d0a51 --- /dev/null +++ b/lib/test_server/test/erl2html2_SUITE_data/m1.erl @@ -0,0 +1,46 @@ +%% Comment with <html> code & </html> +%% and also some "quotes" and 'single quotes' + +-module(m1). + +-compile(export_all). + +-include("header1.hrl"). +-include("header2.hrl"). + +-define(MACRO1,value). + +%%% Comment +foo(x) -> + %% Comment + ok_x; +foo(y) -> + %% Second clause + ok_y. + +'quoted_foo'() -> + ok. + +'quoted_foo_with_"_and_/'() -> + ok. + +'quoted_foo_with_(_and_)'() -> + ok. + +'quoted_foo_with_<_and_>'() -> + ok. + +bar() -> + do_something(), +ok. % indentation error, OTP-9710 + +%% Function inside macro definition +?MACRO_DEFINING_A_FUNCTION. + +%% Two function one one line +quuux() -> ok. quuuux() -> ok. + +%% do_something/0 does something +do_something() -> + ?MACRO1. +%% comments after last line diff --git a/lib/test_server/test/test_server_SUITE.erl b/lib/test_server/test/test_server_SUITE.erl index d20528d43b..95a3423fef 100644 --- a/lib/test_server/test/test_server_SUITE.erl +++ b/lib/test_server/test/test_server_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2011. All Rights Reserved. +%% Copyright Ericsson AB 2010-2012. 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 @@ -79,7 +79,8 @@ groups() -> all() -> [test_server_SUITE, test_server_parallel01_SUITE, test_server_conf02_SUITE, test_server_conf01_SUITE, - test_server_skip_SUITE, test_server_shuffle01_SUITE]. + test_server_skip_SUITE, test_server_shuffle01_SUITE, + test_server_break_SUITE]. %%-------------------------------------------------------------------- @@ -115,6 +116,10 @@ test_server_conf02_SUITE(Config) -> run_test_server_tests("test_server_conf02_SUITE", 26, 0, 12, 12, 0, 0, 0, 0, 26, Config). +test_server_break_SUITE(Config) -> + D = run_test_server_tests("test_server_break_SUITE", 8, 2, 6, + 4, 0, 0, 0, 2, 6, Config), + D. run_test_server_tests(SuiteName, NCases, NFail, NExpected, NSucc, NUsrSkip, NAutoSkip, @@ -139,9 +144,11 @@ run_test_server_tests(SuiteName, NCases, NFail, NExpected, NSucc, rpc:call(Node,test_server_ctrl, stop, []), {ok,Data} = test_server_test_lib:parse_suite( - hd(filelib:wildcard( - filename:join([WorkDir,SuiteName++".logs", - "run*","suite.log"])))), + lists:last( + lists:sort( + filelib:wildcard( + filename:join([WorkDir,SuiteName++".logs", + "run*","suite.log"]))))), check([{"Number of cases",NCases,Data#suite.n_cases}, {"Number failed",NFail,Data#suite.n_cases_failed}, {"Number expected",NExpected,Data#suite.n_cases_expected}, diff --git a/lib/test_server/test/test_server_SUITE_data/Makefile.src b/lib/test_server/test/test_server_SUITE_data/Makefile.src index 332b855df6..ec8ddd78b0 100644 --- a/lib/test_server/test/test_server_SUITE_data/Makefile.src +++ b/lib/test_server/test/test_server_SUITE_data/Makefile.src @@ -4,4 +4,5 @@ all: erlc test_server_conf01_SUITE.erl erlc test_server_shuffle01_SUITE.erl erlc test_server_conf02_SUITE.erl - erlc test_server_skip_SUITE.erl
\ No newline at end of file + erlc test_server_skip_SUITE.erl + erlc test_server_break_SUITE.erl
\ No newline at end of file diff --git a/lib/test_server/test/test_server_SUITE_data/test_server_break_SUITE.erl b/lib/test_server/test/test_server_SUITE_data/test_server_break_SUITE.erl new file mode 100644 index 0000000000..70e30a3334 --- /dev/null +++ b/lib/test_server/test/test_server_SUITE_data/test_server_break_SUITE.erl @@ -0,0 +1,148 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2012. 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 +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(test_server_break_SUITE). + +-export([all/1, init_per_suite/1, end_per_suite/1]). +-export([init_per_testcase/2, end_per_testcase/2]). +-export([break_in_init_tc/1, + break_in_tc/1, + break_in_end_tc/1, + break_in_end_tc_after_fail/1, + break_in_end_tc_after_abort/1, + check_all_breaks/1]). + +-include_lib("test_server/include/test_server.hrl"). + +all(suite) -> + [break_in_init_tc, + break_in_tc, + break_in_end_tc, + break_in_end_tc_after_fail, + break_in_end_tc_after_abort, + check_all_breaks]. %must be the last test - checks result of previous tests + +init_per_suite(Config) -> + spawn(fun break_and_continue_sup/0), + Config. + +end_per_suite(Config) -> + ok. + +init_per_testcase(Case,Config) when Case==break_in_init_tc -> + Config1 = init_timetrap(500,Config), + break_and_check(Case), + Config1; +init_per_testcase(Case,Config) when Case==check_all_breaks -> + init_timetrap({seconds,20},Config); +init_per_testcase(_Case,Config) -> + init_timetrap(500,Config). + +init_timetrap(T,Config) -> + Dog = ?t:timetrap(T), + [{watchdog, Dog}|Config]. + +end_per_testcase(Case,Config) when Case==break_in_end_tc; + Case==break_in_end_tc_after_fail; + Case==break_in_end_tc_after_abort -> + break_and_check(Case), + cancel_timetrap(Config); +end_per_testcase(_Case,Config) -> + cancel_timetrap(Config). + +cancel_timetrap(Config) -> + Dog=?config(watchdog, Config), + ?t:timetrap_cancel(Dog), + ok. + + +%%%----------------------------------------------------------------- +%%% Test cases + +break_in_init_tc(Config) when is_list(Config) -> + ok. + +break_in_tc(Config) when is_list(Config) -> + break_and_check(break_in_tc), + ok. + +break_in_end_tc(Config) when is_list(Config) -> + ok. + +break_in_end_tc_after_fail(Config) when is_list(Config) -> + ?t:fail(test_case_should_fail). + +break_in_end_tc_after_abort(Config) when is_list(Config) -> + ?t:adjusted_sleep(2000). % will cause a timetrap timeout + +%%%----------------------------------------------------------------- +%%% Internal functions + +%% This test case checks that all breaks in previous test cases was +%% also continued, and that the break lasted as long as expected. +%% The reason for this is that some of the breaks above are in +%% end_per_testcase, and failures there will only produce a warning, +%% not an error - so this is to catch the error for real. +check_all_breaks(Config) -> + break_and_continue_sup ! {done,self()}, + receive {Breaks,Continued} -> + check_all_breaks(Breaks,Continued) + end. + +check_all_breaks([{From,Case,T,Start}|Breaks],[{From,End}|Continued]) -> + Diff = timer:now_diff(End,Start), + DiffSec = round(Diff/1000000), + TSec = round(T/1000000), + if DiffSec==TSec -> + ?t:format("Break in ~p successfully continued after ~p second(s)~n", + [Case,DiffSec]), + check_all_breaks(Breaks,Continued); + true -> + ?t:format("Faulty duration of break in ~p: continued after ~p second(s)~n", + [Case,DiffSec]), + ?t:fail({faulty_diff,Case,DiffSec,TSec}) + end; +check_all_breaks([],[]) -> + ok; +check_all_breaks(Breaks,Continued) -> + %% This is probably a case of a missing continue - i.e. a break + %% has been started, but it was never continued. + ?t:fail({no_match_in_breaks_and_continued,Breaks,Continued}). + +break_and_check(Case) -> + break_and_continue_sup ! {break,Case,1000,self()}, + ?t:break(atom_to_list(Case)), + break_and_continue_sup ! {continued,self()}, + ok. + +break_and_continue_sup() -> + register(break_and_continue_sup,self()), + break_and_continue_loop([],[]). + +break_and_continue_loop(Breaks,Continued) -> + receive + {break,Case,T,From} -> + Start = now(), + {RealT,_} = timer:tc(?t,adjusted_sleep,[T]), + ?t:continue(), + break_and_continue_loop([{From,Case,RealT,Start}|Breaks],Continued); + {continued,From} -> + break_and_continue_loop(Breaks,[{From,now()}|Continued]); + {done,From} -> + From ! {lists:reverse(Breaks),lists:reverse(Continued)} + end. diff --git a/lib/wx/src/wx.erl b/lib/wx/src/wx.erl index 7d62305048..2a4b18d101 100644 --- a/lib/wx/src/wx.erl +++ b/lib/wx/src/wx.erl @@ -102,12 +102,18 @@ new() -> %% @doc Starts a wx server. %% Option may be {debug, Level}, see debug/1. --spec new([Option]) -> wx_object() when Option :: {debug, list() | atom()}. +%% Or {silent_start, Bool}, which causes error messages at startup to +%% be suppressed. The latter can be used as a silent test of whether +%% wx is properly installed or not. +-spec new([Option]) -> wx_object() when Option :: {debug, list() | atom()} | + {silent_start, boolean()}. new(Options) when is_list(Options) -> - #wx_env{port=Port} = wxe_server:start(), - put(opengl_port, Port), Debug = proplists:get_value(debug, Options, 0), - debug(Debug), + SilentStart = proplists:get_value(silent_start, Options, false), + Level = calc_level(Debug), + #wx_env{port=Port} = wxe_server:start(SilentStart andalso Level =:= 0), + put(opengl_port, Port), + set_debug(Level), null(). %% @doc Stops a wx server. @@ -282,13 +288,16 @@ release_memory(Bin) when is_binary(Bin) -> -spec debug(Level | [Level]) -> ok when Level :: none | verbose | trace | driver | integer(). -debug(none) -> debug(0); -debug(verbose) -> debug(1); -debug(trace) -> debug(2); -debug(driver) -> debug(16); -debug([]) -> debug(0); +debug(Debug) -> + Level = calc_level(Debug), + set_debug(Level). -debug(List) when is_list(List) -> +calc_level(none) -> calc_level(0); +calc_level(verbose) -> calc_level(1); +calc_level(trace) -> calc_level(2); +calc_level(driver) -> calc_level(16); +calc_level([]) -> calc_level(0); +calc_level(List) when is_list(List) -> {Drv,Erl} = lists:foldl(fun(verbose, {Drv,_Erl}) -> {Drv,1}; @@ -297,8 +306,11 @@ debug(List) when is_list(List) -> (driver, {_Drv,Erl}) -> {16, Erl} end, {0,0}, List), - debug(Drv + Erl); -debug(Level) when is_integer(Level) -> + Drv + Erl; +calc_level(Level) when is_integer(Level) -> + Level. + +set_debug(Level) when is_integer(Level) -> case get(?WXE_IDENTIFIER) of undefined -> erlang:error({wxe,unknown_port}); #wx_env{debug=Old} when Old =:= Level -> ok; diff --git a/lib/wx/src/wxe_master.erl b/lib/wx/src/wxe_master.erl index ac6e4a56e6..b98a7c793e 100644 --- a/lib/wx/src/wxe_master.erl +++ b/lib/wx/src/wxe_master.erl @@ -28,7 +28,7 @@ -behaviour(gen_server). %% API --export([start/0, init_port/0, init_opengl/0]). +-export([start/1, init_port/1, init_opengl/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -47,20 +47,20 @@ %% API %%==================================================================== %%-------------------------------------------------------------------- -%% Function: start_link() -> {ok,Pid} | ignore | {error,Error} +%% Function: start(SilentStart) -> {ok,Pid} | ignore | {error,Error} %% Description: Starts the server %%-------------------------------------------------------------------- -start() -> - gen_server:start({local, ?MODULE}, ?MODULE, [], []). +start(SilentStart) -> + gen_server:start({local, ?MODULE}, ?MODULE, [SilentStart], []). %%-------------------------------------------------------------------- -%% Function: init_port() -> {UserPort,CallBackPort} | error(Error) +%% Function: init_port(SilentStart) -> {UserPort,CallBackPort} | error(Error) %% Description: Creates the port %%-------------------------------------------------------------------- -init_port() -> +init_port(SilentStart) -> case whereis(?MODULE) of undefined -> - case start() of + case start(SilentStart) of {ok,Pid} -> Pid; {error,{already_started,Pid}} -> Pid; {error, {Reason,Stack}} -> @@ -93,14 +93,17 @@ init_opengl() -> %% {stop, Reason} %% Description: Initiates the server %%-------------------------------------------------------------------- -init([]) -> +init([SilentStart]) -> DriverName = ?DRIVER, - PrivDir = wxe_util:priv_dir(?DRIVER), + PrivDir = wxe_util:priv_dir(?DRIVER, SilentStart), erlang:group_leader(whereis(init), self()), case catch erlang:system_info(smp_support) of true -> ok; _ -> - error_logger:format("WX ERROR: SMP emulator required (start with erl -smp)", []), + wxe_util:opt_error_log(SilentStart, + "WX ERROR: SMP emulator required" + " (start with erl -smp)", + []), erlang:error(not_smp) end, @@ -114,7 +117,9 @@ init([]) -> case erl_ddll:load_driver(PrivDir,DriverName) of ok -> ok; {error, What} -> - error_logger:format("WX Failed loading ~p@~p ~n", [DriverName,PrivDir]), + wxe_util:opt_error_log(SilentStart, + "WX Failed loading ~p@~p ~n", + [DriverName,PrivDir]), Str = erl_ddll:format_error(What), erlang:error({load_driver,Str}) end, @@ -210,4 +215,3 @@ debug_ping(Port) -> _R = (catch erlang:port_call(Port, 0, [])), %% io:format("Erlang ping ~p ~n", [_R]), debug_ping(Port). - diff --git a/lib/wx/src/wxe_server.erl b/lib/wx/src/wxe_server.erl index 6e982c97f6..689fe16a70 100644 --- a/lib/wx/src/wxe_server.erl +++ b/lib/wx/src/wxe_server.erl @@ -29,7 +29,7 @@ -behaviour(gen_server). %% API --export([start/0, stop/0, register_me/1, set_debug/2, invoke_callback/1]). +-export([start/1, stop/0, register_me/1, set_debug/2, invoke_callback/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -49,13 +49,13 @@ %% API %%==================================================================== %%-------------------------------------------------------------------- -%% Function: start() -> #wx_env{} +%% Function: start(SilentStart) -> #wx_env{} %% Description: Starts the server %%-------------------------------------------------------------------- -start() -> +start(SilentStart) -> case get(?WXE_IDENTIFIER) of undefined -> - case gen_server:start(?MODULE, [], []) of + case gen_server:start(?MODULE, [SilentStart], []) of {ok, Pid} -> {ok, Port} = gen_server:call(Pid, get_port, infinity), wx:set_env(Env = #wx_env{port=Port,sv=Pid}), @@ -69,7 +69,7 @@ start() -> Env; false -> %% Ok we got an old wx env, someone forgot erase(?WXE_IDENTIFIER), %% to call wx:destroy() - start() + start(SilentStart) end end. @@ -88,8 +88,8 @@ set_debug(Pid, Level) -> %% gen_server callbacks %%==================================================================== -init([]) -> - {Port,CBPort} = wxe_master:init_port(), +init([SilentStart]) -> + {Port,CBPort} = wxe_master:init_port(SilentStart), put(?WXE_IDENTIFIER, #wx_env{port=Port,sv=self()}), {ok,#state{port=Port, cb_port=CBPort, users=gb_trees:empty(), cb=gb_trees:empty(), cb_cnt=1}}. diff --git a/lib/wx/src/wxe_util.erl b/lib/wx/src/wxe_util.erl index 02bca62486..3022b7f3f2 100644 --- a/lib/wx/src/wxe_util.erl +++ b/lib/wx/src/wxe_util.erl @@ -32,7 +32,7 @@ get_const/1,colour_bin/1,datetime_bin/1, to_bool/1,from_bool/1]). --export([wxgl_dl/0, priv_dir/1]). +-export([wxgl_dl/0, priv_dir/2, opt_error_log/3]). -include("wxe.hrl"). @@ -205,7 +205,7 @@ check_previous() -> wxgl_dl() -> DynLib0 = "erl_gl", - PrivDir = priv_dir(DynLib0), + PrivDir = priv_dir(DynLib0, false), DynLib = case os:type() of {win32,_} -> DynLib0 ++ ".dll"; @@ -214,7 +214,7 @@ wxgl_dl() -> end, filename:join(PrivDir, DynLib). -priv_dir(Driver0) -> +priv_dir(Driver0, Silent) -> {file, Path} = code:is_loaded(?MODULE), Priv = case filelib:is_regular(Path) of true -> @@ -229,13 +229,13 @@ priv_dir(Driver0) -> _ -> Driver0 ++ ".so" end, - case file:read_file_info(filename:join(Priv, Driver)) of {ok, _} -> Priv; {error, _} -> - error_logger:format("ERROR: Could not find \'~s\' in: ~s~n", - [Driver, Priv]), + opt_error_log(Silent, + "ERROR: Could not find \'~s\' in: ~s~n", + [Driver, Priv]), erlang:error({load_driver, "No driver found"}) end. @@ -244,3 +244,7 @@ strip(Src, Src) -> strip([H|R], Src) -> [H| strip(R, Src)]. +opt_error_log(false, Format, Args) -> + error_logger:format(Format, Args); +opt_error_log(true, _Format, _Args) -> + ok. diff --git a/lib/wx/test/wx_basic_SUITE.erl b/lib/wx/test/wx_basic_SUITE.erl index 46c72bb453..20115a41fb 100644 --- a/lib/wx/test/wx_basic_SUITE.erl +++ b/lib/wx/test/wx_basic_SUITE.erl @@ -47,7 +47,7 @@ end_per_testcase(Func,Config) -> suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> - [create_window, several_apps, wx_api, wx_misc, + [silent_start, create_window, several_apps, wx_api, wx_misc, data_types, wx_object]. groups() -> @@ -62,6 +62,25 @@ end_per_group(_GroupName, Config) -> %% The test cases +%% test silent start of wx +silent_start(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo); +silent_start(_Config) -> + ?mr(wx_ref, wx:new([])), + wx:destroy(), + + ?mr(wx_ref, wx:new([{silent_start, true}])), + wx:destroy(), + + ?mr(wx_ref, wx:new([{silent_start, true}, {debug, verbose}])), + wx:destroy(), + + ?mr(wx_ref, wx:new([{silent_start, false}])), + wx:destroy(), + + ?mr('EXIT', catch wx:new([{silent_start, foo}])), + + ok. + %% create and test creating a window create_window(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo); create_window(Config) -> diff --git a/make/otp.mk.in b/make/otp.mk.in index ed2fd8a70b..f2c2cee767 100644 --- a/make/otp.mk.in +++ b/make/otp.mk.in @@ -209,6 +209,22 @@ TEXDIR = . SPECDIR = $(DOCDIR)/specs +ifeq ($(CSS_FILE),) +CSS_FILE = otp_doc.css +endif +ifeq ($(WINPREFIX),) +WINPREFIX = Erlang +endif +ifeq ($(HTMLLOGO),) +HTMLLOGO_FILE = erlang-logo.png +endif +ifeq ($(PDFLOGO),) +PDFLOGO_FILE = $(DOCGEN)/priv/images/erlang-logo.gif +endif +ifeq ($(PDFCOLOR),) +PDFCOLOR = \#960003 +endif + # HTML & GIF files that always are generated and must be delivered SGML_COLL_FILES = $(SGML_APPLICATION_FILES) $(SGML_PART_FILES) XML_COLL_FILES = $(XML_APPLICATION_FILES) $(XML_PART_FILES) @@ -250,12 +266,10 @@ $(MAN1DIR)/%.1: %.xml date=`date +"%B %e %Y"`; \ xsltproc --output "$@" --stringparam company "Ericsson AB" --stringparam docgen "$(DOCGEN)" --stringparam gendate "$$date" --stringparam appname "$(APPLICATION)" --stringparam appver "$(VSN)" --xinclude -path $(DOCGEN)/priv/dtd -path $(DOCGEN)/priv/dtd_man_entities $(DOCGEN)/priv/xsl/db_man.xsl $< - $(MAN2DIR)/%.2: %.xml date=`date +"%B %e %Y"`; \ xsltproc --output "$@" --stringparam company "Ericsson AB" --stringparam docgen "$(DOCGEN)" --stringparam gendate "$$date" --stringparam appname "$(APPLICATION)" --stringparam appver "$(VSN)" --xinclude -path $(DOCGEN)/priv/dtd -path $(DOCGEN)/priv/dtd_man_entities $(DOCGEN)/priv/xsl/db_man.xsl $< - ifneq ($(wildcard $(SPECDIR)),) $(MAN3DIR)/%.3: %.xml $(SPECDIR)/specs_%.xml date=`date +"%B %e %Y"`; \ diff --git a/make/otp_release_targets.mk b/make/otp_release_targets.mk index 67243b5ffd..0be0a2de56 100644 --- a/make/otp_release_targets.mk +++ b/make/otp_release_targets.mk @@ -42,17 +42,34 @@ $(HTMLDIR)/index.html: $(XML_FILES) $(SPECS_FILES) --stringparam gendate "$$date" \ --stringparam appname "$(APPLICATION)" \ --stringparam appver "$(VSN)" \ + --stringparam stylesheet "$(CSS_FILE)" \ + --stringparam winprefix "$(WINPREFIX)" \ + --stringparam logo "$(HTMLLOGO_FILE)" \ + --stringparam pdfname "$(PDFNAME)" \ -path $(DOCGEN)/priv/dtd \ -path $(DOCGEN)/priv/dtd_html_entities \ $(DOCGEN)/priv/xsl/db_html.xsl book.xml + endif $(HTMLDIR)/users_guide.html: $(XML_FILES) date=`date +"%B %e %Y"`; \ - $(XSLTPROC) --noout --stringparam outdir $(HTMLDIR) --stringparam docgen "$(DOCGEN)" --stringparam topdocdir "$(TOPDOCDIR)" \ + $(XSLTPROC) --noout \ + --stringparam outdir $(HTMLDIR) \ + --stringparam docgen "$(DOCGEN)" \ + --stringparam topdocdir "$(TOPDOCDIR)" \ --stringparam pdfdir "$(PDFDIR)" \ - --stringparam gendate "$$date" --stringparam appname "$(APPLICATION)" --stringparam appver "$(VSN)" --xinclude \ - -path $(DOCGEN)/priv/dtd -path $(DOCGEN)/priv/dtd_html_entities $(DOCGEN)/priv/xsl/db_html.xsl book.xml + --stringparam gendate "$$date" \ + --stringparam appname "$(APPLICATION)" \ + --stringparam appver "$(VSN)" \ + --stringparam stylesheet "$(CSS_FILE)" \ + --stringparam winprefix "$(WINPREFIX)" \ + --stringparam logo "$(HTMLLOGO_FILE)" \ + --stringparam pdfname "$(PDFNAME)" \ + --xinclude \ + -path $(DOCGEN)/priv/dtd \ + -path $(DOCGEN)/priv/dtd_html_entities \ + $(DOCGEN)/priv/xsl/db_html.xsl book.xml %.fo: $(XML_FILES) $(SPECS_FILES) date=`date +"%B %e %Y"`; \ @@ -61,6 +78,8 @@ $(HTMLDIR)/users_guide.html: $(XML_FILES) --stringparam gendate "$$date" \ --stringparam appname "$(APPLICATION)" \ --stringparam appver "$(VSN)" \ + --stringparam logo "$(PDFLOGO_FILE)" \ + --stringparam pdfcolor "$(PDFCOLOR)" \ --xinclude $(TOP_SPECS_PARAM) \ -path $(DOCGEN)/priv/dtd \ -path $(DOCGEN)/priv/dtd_html_entities \ @@ -77,8 +96,13 @@ ifneq ($(XML_FILES),) $(HTMLDIR)/$(APPLICATION).eix: $(XML_FILES) $(SPECS_FILES) date=`date +"%B %e %Y"`; \ $(XSLTPROC) --stringparam docgen "$(DOCGEN)" \ - --stringparam gendate "$$date" --stringparam appname "$(APPLICATION)" --stringparam appver "$(VSN)" --xinclude $(TOP_SPECS_PARAM) \ - -path $(DOCGEN)/priv/dtd -path $(DOCGEN)/priv/dtd_html_entities $(DOCGEN)/priv/xsl/db_eix.xsl book.xml > $@ + --stringparam gendate "$$date" \ + --stringparam appname "$(APPLICATION)" \ + --stringparam appver "$(VSN)" \ + -xinclude $(TOP_SPECS_PARAM) \ + -path $(DOCGEN)/priv/dtd \ + -path $(DOCGEN)/priv/dtd_html_entities \ + $(DOCGEN)/priv/xsl/db_eix.xsl book.xml > $@ docs: $(HTMLDIR)/$(APPLICATION).eix diff --git a/system/doc/top/Makefile b/system/doc/top/Makefile index 9b305eb13b..673ba44c94 100644 --- a/system/doc/top/Makefile +++ b/system/doc/top/Makefile @@ -163,17 +163,34 @@ $(MAN_INDEX): $(MAN_INDEX_SCRIPT) $(HTMLDIR)/highlights.html: highlights.xml date=`date +"%B %e %Y"`; \ - $(XSLTPROC) --output $(@) --stringparam docgen "$(DOCGEN)" --stringparam topdocdir "$(TOPDOCDIR)" \ - --stringparam pdfdir "$(PDFREFDIR)" --stringparam gendate "$$date" --stringparam appname "$(APPLICATION)" \ - --stringparam appver "$(VSN)" -path $(DOCGEN)/priv/dtd -path $(DOCGEN)/priv/dtd_html_entities \ + $(XSLTPROC) --output $(@) \ + --stringparam docgen "$(DOCGEN)" \ + --stringparam topdocdir "$(TOPDOCDIR)" \ + --stringparam pdfdir "$(PDFREFDIR)" \ + --stringparam gendate "$$date" \ + --stringparam appname "$(APPLICATION)" \ + --stringparam appver "$(VSN)" \ + --stringparam stylesheet "$(CSS_FILE)" \ + --stringparam winprefix "$(WINPREFIX)" \ + --stringparam logo "$(HTMLLOGO_FILE)" \ + -path $(DOCGEN)/priv/dtd \ + -path $(DOCGEN)/priv/dtd_html_entities \ $(DOCGEN)/priv/xsl/db_html.xsl $< $(HTMLDIR)/incompatible.html: incompatible.xml date=`date +"%B %e %Y"`; \ - $(XSLTPROC) --output $(@) --stringparam docgen "$(DOCGEN)" --stringparam topdocdir "$(TOPDOCDIR)" \ - --stringparam pdfdir "$(PDFREFDIR)" --stringparam gendate "$$date" --stringparam appname "$(APPLICATION)" \ - --stringparam appver "$(VSN)" -path $(DOCGEN)/priv/dtd -path $(DOCGEN)/priv/dtd_html_entities \ + $(XSLTPROC) --output $(@) --stringparam docgen "$(DOCGEN)" \ + --stringparam topdocdir "$(TOPDOCDIR)" \ + --stringparam pdfdir "$(PDFREFDIR)" \ + --stringparam gendate "$$date" \ + --stringparam appname "$(APPLICATION)" \ + --stringparam appver "$(VSN)" \ + --stringparam stylesheet "$(CSS_FILE)" \ + --stringparam winprefix "$(WINPREFIX)" \ + --stringparam logo "$(HTMLLOGO_FILE)" \ + -path $(DOCGEN)/priv/dtd \ + -path $(DOCGEN)/priv/dtd_html_entities \ $(DOCGEN)/priv/xsl/db_html.xsl $< #-------------------------------------------------------------------------- diff --git a/system/doc/tutorial/port_driver.c b/system/doc/tutorial/port_driver.c index d428d08ff3..37de67310f 100644 --- a/system/doc/tutorial/port_driver.c +++ b/system/doc/tutorial/port_driver.c @@ -19,7 +19,8 @@ static void example_drv_stop(ErlDrvData handle) driver_free((char*)handle); } -static void example_drv_output(ErlDrvData handle, char *buff, int bufflen) +static void example_drv_output(ErlDrvData handle, char *buff, + ErlDrvSizeT bufflen) { example_data* d = (example_data*)handle; char fn = buff[0], arg = buff[1], res; @@ -32,7 +33,7 @@ static void example_drv_output(ErlDrvData handle, char *buff, int bufflen) } ErlDrvEntry example_driver_entry = { - NULL, /* F_PTR init, N/A */ + NULL, /* F_PTR init, called when driver is loaded */ example_drv_start, /* L_PTR start, called when port is opened */ example_drv_stop, /* F_PTR stop, called when port is closed */ example_drv_output, /* F_PTR output, called when erlang has sent */ @@ -40,9 +41,30 @@ ErlDrvEntry example_driver_entry = { NULL, /* F_PTR ready_output, called when output descriptor ready */ "example_drv", /* char *driver_name, the argument to open_port */ NULL, /* F_PTR finish, called when unloaded */ + NULL, /* void *handle, Reserved by VM */ NULL, /* F_PTR control, port_command callback */ NULL, /* F_PTR timeout, reserved */ - NULL /* F_PTR outputv, reserved */ + NULL, /* F_PTR outputv, reserved */ + NULL, /* F_PTR ready_async, only for async drivers */ + NULL, /* F_PTR flush, called when port is about + to be closed, but there is data in driver + queue */ + NULL, /* F_PTR call, much like control, sync call + to driver */ + NULL, /* F_PTR event, called when an event selected + by driver_event() occurs. */ + ERL_DRV_EXTENDED_MARKER, /* int extended marker, Should always be + set to indicate driver versioning */ + ERL_DRV_EXTENDED_MAJOR_VERSION, /* int major_version, should always be + set to this value */ + ERL_DRV_EXTENDED_MINOR_VERSION, /* int minor_version, should always be + set to this value */ + 0, /* int driver_flags, see documentation */ + NULL, /* void *handle2, reserved for VM use */ + NULL, /* F_PTR process_exit, called when a + monitored process dies */ + NULL /* F_PTR stop_select, called to close an + event object */ }; DRIVER_INIT(example_drv) /* must match name in driver_entry */ |