diff options
Diffstat (limited to 'lib')
149 files changed, 8404 insertions, 3632 deletions
diff --git a/lib/asn1/c_src/asn1_erl_nif.c b/lib/asn1/c_src/asn1_erl_nif.c index d5aaadb89b..da43af3405 100644 --- a/lib/asn1/c_src/asn1_erl_nif.c +++ b/lib/asn1/c_src/asn1_erl_nif.c @@ -1,7 +1,7 @@ /* * %CopyrightBegin% * - * Copyright Ericsson AB 2002-2017. All Rights Reserved. + * Copyright Ericsson AB 2002-2018. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -949,6 +949,12 @@ static int ber_decode_value(ErlNifEnv* env, ERL_NIF_TERM *value, unsigned char * unsigned char *tmp_out_buff; ERL_NIF_TERM term = 0, curr_head = 0; + /* Recursion depth limitation, borrow a signed int: maybe_ret */ + maybe_ret = (int) (ErlNifSInt) ((char *)value - (char *)ib_index); + maybe_ret = maybe_ret < 0 ? -maybe_ret : maybe_ret; + if (maybe_ret >= sizeof(void *) * 8192) /* 8 k pointer words */ + return ASN1_ERROR; + if (((in_buf[*ib_index]) & 0x80) == ASN1_SHORT_DEFINITE_LENGTH) { len = in_buf[*ib_index]; } else if (in_buf[*ib_index] == ASN1_INDEFINITE_LENGTH) { @@ -993,7 +999,7 @@ static int ber_decode_value(ErlNifEnv* env, ERL_NIF_TERM *value, unsigned char * while (*ib_index < end_index) { if ((maybe_ret = ber_decode(env, &term, in_buf, ib_index, - in_buf_len)) <= ASN1_ERROR + end_index )) <= ASN1_ERROR ) return maybe_ret; curr_head = enif_make_list_cell(env, term, curr_head); diff --git a/lib/asn1/doc/src/notes.xml b/lib/asn1/doc/src/notes.xml index bb15c9ff5f..b54076acdb 100644 --- a/lib/asn1/doc/src/notes.xml +++ b/lib/asn1/doc/src/notes.xml @@ -32,6 +32,44 @@ <p>This document describes the changes made to the asn1 application.</p> +<section><title>Asn1 5.0.5.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Handle erroneous length during decode (BER only) without + crashing.</p> + <p> + Own Id: OTP-15470 Aux Id: ERIERL-278 </p> + </item> + </list> + </section> + +</section> + +<section><title>Asn1 5.0.5.1</title> + + <section><title>Known Bugs and Problems</title> + <list> + <item> + <p>A bug in ASN.1 BER decoding has been fixed. When + decoding a recursively enclosed term the length was not + propagated to that term decoding, so if the length of the + enclosed term was longer than the enclosing that error + was not dectected</p> <p>A hard coded C stack limitation + for decoding recursive ASN.1 terms has been introduced. + This is currently set to 8 kWords giving a nesting depth + of about 1000 levels. Deeper terms can not be decoded, + which should not be much of a real world limitation.</p> + <p> + Own Id: OTP-14440 Aux Id: ERIERL-220 </p> + </item> + </list> + </section> + +</section> + <section><title>Asn1 5.0.5</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/asn1/test/asn1_SUITE.erl b/lib/asn1/test/asn1_SUITE.erl index bfeffa969f..9a705d3543 100644 --- a/lib/asn1/test/asn1_SUITE.erl +++ b/lib/asn1/test/asn1_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2017. All Rights Reserved. +%% Copyright Ericsson AB 2001-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -63,7 +63,8 @@ groups() -> constraint_equivalence]}, {ber, Parallel, - [ber_choiceinseq, + [ber_decode_invalid_length, + ber_choiceinseq, % Uses 'SOpttest' ber_optional, tagdefault_automatic]}, @@ -132,7 +133,8 @@ groups() -> testSeq2738, % Uses 'Constructed' {group, [], [constructed, - ber_decode_error]}, + ber_decode_error, + otp_14440]}, testSeqSetIndefinite, testChoiceIndefinite, per_open_type, @@ -665,6 +667,19 @@ module_test(M0, Config, Rule, Opts) -> end end. +ber_decode_invalid_length(_Config) -> + Bin = <<48,129,157,48,0,2,1,2,164,0,48,129,154,49,24,48,22,6, + 3,85,4,10,19,15,69,120,97,109,112,108,101,32,67,111, + 109,112,97,110,121,49,29,48,27,6,9,42,134,72,134,247, + 13,1,9,1,22,14,99,97,64,101,120,97,109,112,108,101,46, + 99,111,109,49,13,48,11,6,3,85,4,7,19,4,79,117,108,117, + 49,26,48,24,6,3,85,4,8,19,17,80,111,104,106,111,105, + 115,45,80,111,104,106,97,110,109,97,97,49,11,48,9,6,3, + 85,4,6,19,2,70,73,49,19,48,17,6,3,85,4,3,19,10,69,120, + 97,109,112,108,101,32,67,65,49,11,48,16,6,3,85,4,11, + 19,9,84,101>>, + {'EXIT',{error,{asn1,{invalid_value,12}}}} = (catch asn1rt_nif:decode_ber_tlv(Bin)), + ok. ber_choiceinseq(Config) -> test(Config, fun ber_choiceinseq/3, [ber]). @@ -737,6 +752,36 @@ ber_decode_error(Config, Rule, Opts) -> asn1_test_lib:compile("Constructed", Config, [Rule|Opts]), ber_decode_error:run(Opts). +otp_14440(_Config) -> + Args = " -pa \"" ++ filename:dirname(code:which(?MODULE)) ++ "\"", + {ok,N} = slave:start(hostname(), otp_14440, Args), + Result = rpc:call(N, ?MODULE, otp_14440_decode, []), + io:format("Decode result = ~p~n", [Result]), + case Result of + {exit,{error,{asn1,{invalid_value,5}}}} -> + ok = slave:stop(N); + %% We get this if stack depth limit kicks in: + {exit,{error,{asn1,{unknown,_}}}} -> + ok = slave:stop(N); + _ -> + _ = slave:stop(N), + ?t:fail(Result) + end. +%% +otp_14440_decode() -> + Data = + iolist_to_binary( + lists:duplicate( + 32, list_to_binary(lists:duplicate(1024, 16#7f)))), + try asn1rt_nif:decode_ber_tlv(Data) of + Result -> + {unexpected_return,Result} + catch + Class:Reason -> + {Class,Reason} + end. + + h323test(Config) -> test(Config, fun h323test/3). h323test(Config, Rule, Opts) -> Files = ["H235-SECURITY-MESSAGES", "H323-MESSAGES", @@ -1351,7 +1396,7 @@ xref_export_all(_Config) -> {ok,_} = xref:q(S, Def), {ok,Unused} = xref:q(S, "X - Called - range (closure E | Called)"), xref:stop(S), - case Unused of + case Unused -- [{?MODULE,otp_14440_decode,0}] of [] -> ok; [_|_] -> @@ -1386,3 +1431,11 @@ all_called_1([F|T]) when is_atom(F) -> L ++ all_called_1(T); all_called_1([]) -> []. + +hostname() -> + hostname(atom_to_list(node())). + +hostname([$@ | Hostname]) -> + list_to_atom(Hostname); +hostname([_C | Cs]) -> + hostname(Cs). diff --git a/lib/asn1/vsn.mk b/lib/asn1/vsn.mk index 39dfe8f4fb..6febddafe4 100644 --- a/lib/asn1/vsn.mk +++ b/lib/asn1/vsn.mk @@ -1 +1 @@ -ASN1_VSN = 5.0.5 +ASN1_VSN = 5.0.5.2 diff --git a/lib/common_test/doc/src/common_test_app.xml b/lib/common_test/doc/src/common_test_app.xml index a3b3f927eb..5fa87901f6 100644 --- a/lib/common_test/doc/src/common_test_app.xml +++ b/lib/common_test/doc/src/common_test_app.xml @@ -72,14 +72,15 @@ <fsummary>Returns the list of all test case groups and test cases in the module.</fsummary> <type> - <v>Tests = [TestCase | {group,GroupName} | {group,GroupName,Properties} | {group,GroupName,Properties,SubGroups}]</v> + <v>Tests = [TestCase | {testcase,TestCase,TCRepeatProps} | {group,GroupName} | {group,GroupName,Properties} | {group,GroupName,Properties,SubGroups}]</v> <v>TestCase = atom()</v> + <v>TCRepeatProps = [{repeat,N} | {repeat_until_ok,N} | {repeat_until_fail,N}]</v> <v>GroupName = atom()</v> - <v>Properties = [parallel | sequence | Shuffle | {RepeatType,N}] | default</v> + <v>Properties = [parallel | sequence | Shuffle | {GroupRepeatType,N}] | default</v> <v>SubGroups = [{GroupName,Properties} | {GroupName,Properties,SubGroups}]</v> <v>Shuffle = shuffle | {shuffle,Seed}</v> <v>Seed = {integer(),integer(),integer()}</v> - <v>RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v> + <v>GroupRepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v> <v>N = integer() | forever</v> <v>Reason = term()</v> </type> @@ -91,7 +92,8 @@ test suite module to be executed. This list also specifies the order the cases and groups are executed by <c>Common Test</c>. A test case is represented by an atom, - the name of the test case function. A test case group is + the name of the test case function, or a <c>testcase</c> tuple + indicating that the test case shall be repeated. A test case group is represented by a <c>group</c> tuple, where <c>GroupName</c>, an atom, is the name of the group (defined in <seealso marker="#Module:groups-0"><c>groups/0</c></seealso>). @@ -121,12 +123,13 @@ <v>GroupDefs = [Group]</v> <v>Group = {GroupName,Properties,GroupsAndTestCases}</v> <v>GroupName = atom()</v> - <v>Properties = [parallel | sequence | Shuffle | {RepeatType,N}]</v> - <v>GroupsAndTestCases = [Group | {group,GroupName} | TestCase]</v> + <v>Properties = [parallel | sequence | Shuffle | {GroupRepeatType,N}]</v> + <v>GroupsAndTestCases = [Group | {group,GroupName} | TestCase | {testcase,TestCase,TCRepeatProps}]</v> <v>TestCase = atom()</v> + <v>TCRepeatProps = [{repeat,N} | {repeat_until_ok,N} | {repeat_until_fail,N}]</v> <v>Shuffle = shuffle | {shuffle,Seed}</v> <v>Seed = {integer(),integer(),integer()}</v> - <v>RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v> + <v>GroupRepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v> <v>N = integer() | forever</v> </type> diff --git a/lib/common_test/doc/src/ct_hooks.xml b/lib/common_test/doc/src/ct_hooks.xml index 954be0ffba..03dfceaa1f 100644 --- a/lib/common_test/doc/src/ct_hooks.xml +++ b/lib/common_test/doc/src/ct_hooks.xml @@ -109,6 +109,131 @@ </func> <func> + <name>Module:post_groups(SuiteName, GroupDefs) -> NewGroupDefs</name> + <fsummary>Called after groups/0.</fsummary> + <type> + <v>SuiteName = atom()</v> + <v>GroupDefs = NewGroupDefs = [Group]</v> + <v>Group = {GroupName,Properties,GroupsAndTestCases}</v> + <v>GroupName = atom()</v> + <v>Properties = [parallel | sequence | Shuffle | {GroupRepeatType,N}]</v> + <v>GroupsAndTestCases = [Group | {group,GroupName} | TestCase | {testcase,TestCase,TCRepeatProps}]</v> + <v>TestCase = atom()</v> + <v>TCRepeatProps = [{repeat,N} | {repeat_until_ok,N} | {repeat_until_fail,N}]</v> + <v>Shuffle = shuffle | {shuffle,Seed}</v> + <v>Seed = {integer(),integer(),integer()}</v> + <v>GroupRepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v> + <v>N = integer() | forever</v> + </type> + <desc> + <p>OPTIONAL</p> + + <p>This function is called after + <seealso marker="common_test#Module:groups-0"><c>groups/0</c></seealso>. + It is used to modify the test group definitions, for + instance to add or remove groups or change group properties.</p> + + <p><c>GroupDefs</c> is what + <seealso marker="common_test#Module:groups-0"><c>groups/0</c></seealso> + returned, that is, a list of group definitions.</p> + + <p><c>NewGroupDefs</c> is the possibly modified version of this list.</p> + + <p>This function is called only if the CTH is added before + <c>init_per_suite</c> is run. For details, see section + <seealso marker="ct_hooks_chapter#scope">CTH Scope</seealso> + in the User's Guide.</p> + + <p>Notice that for CTHs that are installed by means of the + <seealso marker="common_test#Module:suite-0"><c>suite/0</c></seealso> + function, <c>post_groups/2</c> is called before + the <seealso marker="#Module:init-2"><c>init/2</c></seealso> + hook function. However, for CTHs that are installed by means + of the CT start flag, + the <seealso marker="#Module:init-2"><c>init/2</c></seealso> + function is called first.</p> + + <note> + <p>Prior to each test execution, Common Test does a + simulated test run in order to count test suites, groups + and cases for logging purposes. This causes + the <c>post_groups/2</c> hook function to always be called + twice. For this reason, side effects are best avoided in + this callback.</p> + </note> + </desc> + </func> + + <func> + <name>Module:post_all(SuiteName, Return, GroupDefs) -> NewReturn</name> + <fsummary>Called after all/0.</fsummary> + <type> + <v>SuiteName = atom()</v> + <v>Return = NewReturn = Tests | {skip,Reason}</v> + <v>Tests = [TestCase | {testcase,TestCase,TCRepeatProps} | {group,GroupName} | {group,GroupName,Properties} | {group,GroupName,Properties,SubGroups}]</v> + <v>TestCase = atom()</v> + <v>TCRepeatProps = [{repeat,N} | {repeat_until_ok,N} | {repeat_until_fail,N}]</v> + <v>GroupName = atom()</v> + <v>Properties = GroupProperties | default</v> + <v>SubGroups = [{GroupName,Properties} | {GroupName,Properties,SubGroups}]</v> + <v>Shuffle = shuffle | {shuffle,Seed}</v> + <v>Seed = {integer(),integer(),integer()}</v> + <v>GroupRepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | repeat_until_any_ok | repeat_until_any_fail</v> + <v>N = integer() | forever</v> + <v>GroupDefs = NewGroupDefs = [Group]</v> + <v>Group = {GroupName,GroupProperties,GroupsAndTestCases}</v> + <v>GroupProperties = [parallel | sequence | Shuffle | {GroupRepeatType,N}]</v> + <v>GroupsAndTestCases = [Group | {group,GroupName} | TestCase]</v> + <v>Reason = term()</v> + </type> + <desc> + <p>OPTIONAL</p> + + <p>This function is called after + <seealso marker="common_test#Module:all-0"><c>all/0</c></seealso>. + It is used to modify the set of test cases and test group to + be executed, for instance to add or remove test cases and + groups, change group properties, or even skip all tests in + the suite.</p> + + <p><c>Return</c> is what + <seealso marker="common_test#Module:all-0"><c>all/0</c></seealso> + returned, that is, a list of test cases and groups to be + executed, or a tuple <c>{skip,Reason}</c>.</p> + + <p><c>GroupDefs</c> is what + <seealso marker="common_test#Module:groups-0"><c>groups/0</c></seealso> + or the <c>post_groups/2</c> hook returned, that is, a list + of group definitions.</p> + + <p><c>NewReturn</c> is the possibly modified version of <c>Return</c>.</p> + + <p>This function is called only if the CTH is added before + <c>init_per_suite</c> is run. For details, see section + <seealso marker="ct_hooks_chapter#scope">CTH Scope</seealso> + in the User's Guide.</p> + + <p>Notice that for CTHs that are installed by means of the + <seealso marker="common_test#Module:suite-0"><c>suite/0</c></seealso> + function, <c>post_all/2</c> is called before + the <seealso marker="#Module:init-2"><c>init/2</c></seealso> + hook function. However, for CTHs that are installed by means + of the CT start flag, + the <seealso marker="#Module:init-2"><c>init/2</c></seealso> + function is called first.</p> + + <note> + <p>Prior to each test execution, Common Test does a + simulated test run in order to count test suites, groups + and cases for logging purposes. This causes + the <c>post_all/3</c> hook function to always be called + twice. For this reason, side effects are best avoided in + this callback.</p> + </note> + </desc> + </func> + + <func> <name>Module:pre_init_per_suite(SuiteName, InitData, CTHState) -> Result</name> <fsummary>Called before init_per_suite.</fsummary> <type> diff --git a/lib/common_test/doc/src/notes.xml b/lib/common_test/doc/src/notes.xml index 7e909b24cd..ef8b70f1a3 100644 --- a/lib/common_test/doc/src/notes.xml +++ b/lib/common_test/doc/src/notes.xml @@ -33,6 +33,25 @@ <file>notes.xml</file> </header> +<section><title>Common_Test 1.15.4.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + The status of a test case which failed with timetrap + timeout in <c>end_per_testcase</c> could not be modified + by returning <c>{fail,Reason}</c> from a + <c>post_end_per_testcase</c> hook function. This is now + corrected.</p> + <p> + Own Id: OTP-15584 Aux Id: ERIERL-282 </p> + </item> + </list> + </section> + +</section> + <section><title>Common_Test 1.15.4</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/common_test/doc/src/write_test_chapter.xml b/lib/common_test/doc/src/write_test_chapter.xml index 82dc06834f..5eed748b08 100644 --- a/lib/common_test/doc/src/write_test_chapter.xml +++ b/lib/common_test/doc/src/write_test_chapter.xml @@ -455,8 +455,10 @@ GroupDefs = [GroupDef] GroupDef = {GroupName,Properties,GroupsAndTestCases} GroupName = atom() - GroupsAndTestCases = [GroupDef | {group,GroupName} | TestCase] - TestCase = atom()</pre> + GroupsAndTestCases = [GroupDef | {group,GroupName} | TestCase | + {testcase,TestCase,TCRepeatProps}] + TestCase = atom() + TCRepeatProps = [{repeat,N} | {repeat_until_ok,N} | {repeat_until_fail,N}]</pre> <p><c>GroupName</c> is the name of the group and must be unique within the test suite module. Groups can be nested, by including a group definition @@ -464,11 +466,11 @@ <c>Properties</c> is the list of execution properties for the group. The possible values are as follows:</p> <pre> - Properties = [parallel | sequence | Shuffle | {RepeatType,N}] + Properties = [parallel | sequence | Shuffle | {GroupRepeatType,N}] Shuffle = shuffle | {shuffle,Seed} Seed = {integer(),integer(),integer()} - RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | - repeat_until_any_ok | repeat_until_any_fail + GroupRepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | + repeat_until_any_ok | repeat_until_any_fail N = integer() | forever</pre> <p><em>Explanations:</em></p> @@ -481,8 +483,8 @@ Dependencies Between Test Cases and Suites.</p></item> <tag><c>shuffle</c></tag> <item><p>The cases in the group are executed in random order.</p></item> - <tag><c>repeat</c></tag> - <item><p>Orders <c>Common Test</c> to repeat execution of the cases in the + <tag><c>repeat, repeat_until_*</c></tag> + <item><p>Orders <c>Common Test</c> to repeat execution of all the cases in the group a given number of times, or until any, or all, cases fail or succeed.</p></item> </taglist> @@ -496,7 +498,7 @@ <c>{group,GroupName}</c> to the <c>all/0</c> list.</p> <p><em>Example:</em></p> <pre> - all() -> [testcase1, {group,group1}, testcase2, {group,group2}].</pre> + all() -> [testcase1, {group,group1}, {testcase,testcase2,[{repeat,10}]}, {group,group2}].</pre> <p>Execution properties with a group tuple in <c>all/0</c>: <c>{group,GroupName,Properties}</c> can also be specified. diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl index 6066470233..0437d6786f 100644 --- a/lib/common_test/src/ct_framework.erl +++ b/lib/common_test/src/ct_framework.erl @@ -696,9 +696,16 @@ end_tc(Mod,IPTC={init_per_testcase,_Func},_TCPid,Result,Args,Return) -> end end; -end_tc(Mod,Func0,TCPid,Result,Args,Return) -> +end_tc(Mod,Func00,TCPid,Result,Args,Return) -> %% in case Mod == ct_framework, lookup the suite name Suite = get_suite_name(Mod, Args), + {OnlyCleanup,Func0} = + case Func00 of + {cleanup,F0} -> + {true,F0}; + _ -> + {false,Func00} + end, {Func,FuncSpec,HookFunc} = case Func0 of {end_per_testcase_not_run,F} -> @@ -742,6 +749,8 @@ end_tc(Mod,Func0,TCPid,Result,Args,Return) -> case HookFunc of undefined -> {ok,Result}; + _ when OnlyCleanup -> + {ok,Result}; _ -> case ct_hooks:end_tc(Suite,HookFunc,Args,Result,Return) of '$ct_no_change' -> @@ -752,6 +761,8 @@ end_tc(Mod,Func0,TCPid,Result,Args,Return) -> end, FinalResult = case get('$test_server_framework_test') of + _ when OnlyCleanup -> + Result1; undefined -> %% send sync notification so that event handlers may print %% in the log file before it gets closed @@ -1055,21 +1066,41 @@ group_or_func(Func, _Config) -> %%% should be returned. get_suite(Mod, all) -> - case catch apply(Mod, groups, []) of - {'EXIT',_} -> - get_all(Mod, []); - GroupDefs when is_list(GroupDefs) -> - 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) - end; - _ -> + case safe_apply_groups_0(Mod,{ok,[]}) of + {ok,GroupDefs} -> + try ct_groups:find_groups(Mod, all, all, GroupDefs) of + ConfTests when is_list(ConfTests) -> + get_all(Mod, ConfTests) + catch + throw:{error,Error} -> + [{?MODULE,error_in_suite,[[{error,Error}]]}]; + _:Error -> + S = erlang:get_stacktrace(), + [{?MODULE,error_in_suite,[[{error,{Error,S}}]]}] + end; + {error,{bad_return,_Bad}} -> E = "Bad return value from "++atom_to_list(Mod)++":groups/0", - [{?MODULE,error_in_suite,[[{error,list_to_atom(E)}]]}] + [{?MODULE,error_in_suite,[[{error,list_to_atom(E)}]]}]; + {error,{bad_hook_return,Bad}} -> + E = "Bad return value from post_groups/2 hook function", + [{?MODULE,error_in_suite,[[{error,{list_to_atom(E),Bad}}]]}]; + {error,{failed,ExitReason}} -> + case ct_util:get_testdata({error_in_suite,Mod}) of + undefined -> + ErrStr = io_lib:format("~n*** ERROR *** " + "~w:groups/0 failed: ~p~n", + [Mod,ExitReason]), + io:format(?def_gl, ErrStr, []), + %% save the error info so it doesn't get printed twice + ct_util:set_testdata_async({{error_in_suite,Mod}, + ExitReason}); + _ExitReason -> + ct_util:delete_testdata({error_in_suite,Mod}) + end, + Reason = list_to_atom(atom_to_list(Mod)++":groups/0 failed"), + [{?MODULE,error_in_suite,[[{error,Reason}]]}]; + {error,What} -> + [{?MODULE,error_in_suite,[[{error,What}]]}] end; %%!============================================================ @@ -1079,54 +1110,75 @@ get_suite(Mod, all) -> %% group get_suite(Mod, Group={conf,Props,_Init,TCs,_End}) -> - Name = ?val(name, Props), - case catch apply(Mod, groups, []) of - {'EXIT',_} -> - [Group]; - GroupDefs when is_list(GroupDefs) -> - 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 - [{?MODULE,error_in_suite,[[Error]]}]; - [] -> - []; - ConfTests -> - case lists:member(skipped, Props) of - true -> - %% a *subgroup* specified *only* as skipped (and not - %% as an explicit test) should not be returned, or - %% init/end functions for top groups will be executed - case catch ?val(name, element(2, hd(ConfTests))) of - Name -> % top group - ct_groups:delete_subs(ConfTests, ConfTests); - _ -> - [] - end; - false -> - ConfTests1 = ct_groups:delete_subs(ConfTests, - ConfTests), - case ?val(override, Props) of - undefined -> - ConfTests1; - [] -> - ConfTests1; - ORSpec -> - ORSpec1 = if is_tuple(ORSpec) -> [ORSpec]; - true -> ORSpec end, - ct_groups:search_and_override(ConfTests1, - ORSpec1, Mod) - end - end - end; - _ -> + case safe_apply_groups_0(Mod,{ok,[Group]}) of + {ok,GroupDefs} -> + Name = ?val(name, Props), + try ct_groups:find_groups(Mod, Name, TCs, GroupDefs) of + [] -> + []; + ConfTests when is_list(ConfTests) -> + case lists:member(skipped, Props) of + true -> + %% a *subgroup* specified *only* as skipped (and not + %% as an explicit test) should not be returned, or + %% init/end functions for top groups will be executed + try ?val(name, element(2, hd(ConfTests))) of + Name -> % top group + ct_groups:delete_subs(ConfTests, ConfTests); + _ -> [] + catch + _:_ -> [] + end; + false -> + ConfTests1 = ct_groups:delete_subs(ConfTests, + ConfTests), + case ?val(override, Props) of + undefined -> + ConfTests1; + [] -> + ConfTests1; + ORSpec -> + ORSpec1 = if is_tuple(ORSpec) -> [ORSpec]; + true -> ORSpec end, + ct_groups:search_and_override(ConfTests1, + ORSpec1, Mod) + end + end + catch + throw:{error,Error} -> + [{?MODULE,error_in_suite,[[{error,Error}]]}]; + _:Error -> + S = erlang:get_stacktrace(), + [{?MODULE,error_in_suite,[[{error,{Error,S}}]]}] + end; + {error,{bad_return,_Bad}} -> E = "Bad return value from "++atom_to_list(Mod)++":groups/0", - [{?MODULE,error_in_suite,[[{error,list_to_atom(E)}]]}] + [{?MODULE,error_in_suite,[[{error,list_to_atom(E)}]]}]; + {error,{bad_hook_return,Bad}} -> + E = "Bad return value from post_groups/2 hook function", + [{?MODULE,error_in_suite,[[{error,{list_to_atom(E),Bad}}]]}]; + {error,{failed,ExitReason}} -> + case ct_util:get_testdata({error_in_suite,Mod}) of + undefined -> + ErrStr = io_lib:format("~n*** ERROR *** " + "~w:groups/0 failed: ~p~n", + [Mod,ExitReason]), + io:format(?def_gl, ErrStr, []), + %% save the error info so it doesn't get printed twice + ct_util:set_testdata_async({{error_in_suite,Mod}, + ExitReason}); + _ExitReason -> + ct_util:delete_testdata({error_in_suite,Mod}) + end, + Reason = list_to_atom(atom_to_list(Mod)++":groups/0 failed"), + [{?MODULE,error_in_suite,[[{error,Reason}]]}]; + {error,What} -> + [{?MODULE,error_in_suite,[[{error,What}]]}] end; %% testcase get_suite(Mod, Name) -> - get_seq(Mod, Name). + get_seq(Mod, Name). %%%----------------------------------------------------------------- @@ -1160,21 +1212,49 @@ get_all_cases1(_, []) -> %%%----------------------------------------------------------------- -get_all(Mod, ConfTests) -> - case catch apply(Mod, all, []) of - {'EXIT',{undef,[{Mod,all,[],_} | _]}} -> +get_all(Mod, ConfTests) -> + case safe_apply_all_0(Mod) of + {ok,AllTCs} -> + %% expand group references using ConfTests + try ct_groups:expand_groups(AllTCs, ConfTests, Mod) of + {error,_} = Error -> + [{?MODULE,error_in_suite,[[Error]]}]; + Tests0 -> + Tests = ct_groups:delete_subs(Tests0, Tests0), + expand_tests(Mod, Tests) + catch + throw:{error,Error} -> + [{?MODULE,error_in_suite,[[{error,Error}]]}]; + _:Error -> + S = erlang:get_stacktrace(), + [{?MODULE,error_in_suite,[[{error,{Error,S}}]]}] + end; + Skip = {skip,_Reason} -> + Skip; + {error,undef} -> + Reason = + case code:which(Mod) of + non_existing -> + list_to_atom( + atom_to_list(Mod)++ + " cannot be compiled or loaded"); + _ -> + list_to_atom( + atom_to_list(Mod)++":all/0 is missing") + end, + %% this makes test_server call error_in_suite as first + %% (and only) test case so we can report Reason properly + [{?MODULE,error_in_suite,[[{error,Reason}]]}]; + {error,{bad_return,_Bad}} -> Reason = - case code:which(Mod) of - non_existing -> - list_to_atom(atom_to_list(Mod)++ - " can not be compiled or loaded"); - _ -> - list_to_atom(atom_to_list(Mod)++":all/0 is missing") - end, - %% this makes test_server call error_in_suite as first - %% (and only) test case so we can report Reason properly + list_to_atom("Bad return value from "++ + atom_to_list(Mod)++":all/0"), [{?MODULE,error_in_suite,[[{error,Reason}]]}]; - {'EXIT',ExitReason} -> + {error,{bad_hook_return,Bad}} -> + Reason = + list_to_atom("Bad return value from post_all/3 hook function"), + [{?MODULE,error_in_suite,[[{error,{Reason,Bad}}]]}]; + {error,{failed,ExitReason}} -> case ct_util:get_testdata({error_in_suite,Mod}) of undefined -> ErrStr = io_lib:format("~n*** ERROR *** " @@ -1191,28 +1271,8 @@ get_all(Mod, ConfTests) -> %% this makes test_server call error_in_suite as first %% (and only) test case so we can report Reason properly [{?MODULE,error_in_suite,[[{error,Reason}]]}]; - AllTCs when is_list(AllTCs) -> - case catch save_seqs(Mod,AllTCs) of - {error,What} -> - [{?MODULE,error_in_suite,[[{error,What}]]}]; - SeqsAndTCs -> - %% expand group references in all() using ConfTests - case catch ct_groups:expand_groups(SeqsAndTCs, - ConfTests, - Mod) of - {error,_} = Error -> - [{?MODULE,error_in_suite,[[Error]]}]; - 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"), - [{?MODULE,error_in_suite,[[{error,Reason}]]}] + {error,What} -> + [{?MODULE,error_in_suite,[[{error,What}]]}] end. %%!============================================================ @@ -1570,3 +1630,75 @@ get_html_wrapper(TestName, PrintLabel, Cwd, TableCols, Encoding) -> %%% @spec get_log_dir() -> {ok,LogDir} get_log_dir() -> ct_logs:get_log_dir(true). + +%%%----------------------------------------------------------------- +%%% Call all and group callbacks and post_* hooks with error handling +safe_apply_all_0(Mod) -> + try apply(Mod, all, []) of + AllTCs0 when is_list(AllTCs0) -> + try save_seqs(Mod,AllTCs0) of + SeqsAndTCs when is_list(SeqsAndTCs) -> + all_hook(Mod,SeqsAndTCs) + catch throw:{error,What} -> + {error,What} + end; + {skip,_}=Skip -> + all_hook(Mod,Skip); + Bad -> + {error,{bad_return,Bad}} + catch + _:Reason -> + handle_callback_crash(Reason,erlang:get_stacktrace(),Mod,all,{error,undef}) + end. + +all_hook(Mod, All) -> + case ct_hooks:all(Mod, All) of + AllTCs when is_list(AllTCs) -> + {ok,AllTCs}; + {skip,_}=Skip -> + Skip; + {fail,Reason} -> + {error,Reason}; + Bad -> + {error,{bad_hook_return,Bad}} + end. + +safe_apply_groups_0(Mod,Default) -> + try apply(Mod, groups, []) of + GroupDefs when is_list(GroupDefs) -> + case ct_hooks:groups(Mod, GroupDefs) of + GroupDefs1 when is_list(GroupDefs1) -> + {ok,GroupDefs1}; + {fail,Reason} -> + {error,Reason}; + Bad -> + {error,{bad_hook_return,Bad}} + end; + Bad -> + {error,{bad_return,Bad}} + catch + _:Reason -> + handle_callback_crash(Reason,erlang:get_stacktrace(), + Mod,groups,Default) + end. + +handle_callback_crash(undef,[{Mod,Func,[],_}|_],Mod,Func,Default) -> + case ct_hooks:Func(Mod, []) of + [] -> + Default; + List when is_list(List) -> + {ok,List}; + {fail,Reason} -> + {error,Reason}; + Bad -> + {error,{bad_hook_return,Bad}} + end; +handle_callback_crash(Reason,Stacktrace,_Mod,_Func,_Default) -> + {error,{failed,{Reason,Stacktrace}}}. + +expand_tests(Mod, [{testcase,Case,[Prop]}|Tests]) -> + [{repeat,{Mod,Case},Prop}|expand_tests(Mod,Tests)]; +expand_tests(Mod,[Test|Tests]) -> + [Test|expand_tests(Mod,Tests)]; +expand_tests(_Mod,[]) -> + []. diff --git a/lib/common_test/src/ct_groups.erl b/lib/common_test/src/ct_groups.erl index 2235365a0e..b734455f15 100644 --- a/lib/common_test/src/ct_groups.erl +++ b/lib/common_test/src/ct_groups.erl @@ -103,23 +103,34 @@ find(Mod, [], TCs, Tests, _Known, _Defs, false) -> [{Mod,TC}]; ({group,_}) -> []; + ({testcase,TC,[Prop]}) when is_atom(TC), TC ==all -> + [{repeat,{Mod,TC},Prop}]; ({_,_}=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 + (TC) when is_atom(TC) -> + Tuple = {Mod,TC}, + case lists:member(Tuple, TCs) of + true -> + [Tuple]; + false -> + case lists:member(TC, TCs) of + true -> [Tuple]; + false -> [] + end + end; + ({testcase,TC,[Prop]}) when is_atom(TC) -> + Tuple = {Mod,TC}, + case lists:member(Tuple, TCs) of + true -> + [{repeat,Tuple,Prop}]; + false -> + case lists:member(TC, TCs) of + true -> [{repeat,Tuple,Prop}]; + false -> [] + end + end; + (_) -> + [] end, Tests), if Cases == [] -> ['NOMATCH']; true -> Cases @@ -174,12 +185,19 @@ 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)]; +%% Save test case +find(Mod, GrNames, all, [{testcase,TC,[Prop]} | Gs], Known, + Defs, FindAll) when is_atom(TC) -> + [{repeat,{Mod,TC},Prop} | 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 (element(1,TC) /= group)) -> +find(Mod, GrNames, TCs, [TC | Gs], Known, Defs, FindAll) + when is_atom(TC) orelse + ((size(TC) == 3) andalso (element(1,TC) == testcase)) orelse + ((size(TC) == 2) and (element(1,TC) /= group)) -> Case = - if is_atom(TC) -> + case TC of + _ when is_atom(TC) -> Tuple = {Mod,TC}, case lists:member(Tuple, TCs) of true -> @@ -190,7 +208,18 @@ find(Mod, GrNames, TCs, [TC | Gs], Known, false -> [] end end; - true -> + {testcase,TC0,[Prop]} when is_atom(TC0) -> + Tuple = {Mod,TC0}, + case lists:member(Tuple, TCs) of + true -> + {repeat,Tuple,Prop}; + false -> + case lists:member(TC0, TCs) of + true -> {repeat,{Mod,TC0},Prop}; + false -> [] + end + end; + _ -> case lists:member(TC, TCs) of true -> {Mod,TC}; false -> [] @@ -291,12 +320,22 @@ modify_tc_list(GrSpecTs, TSCs, []) -> modify_tc_list1(GrSpecTs, TSCs); modify_tc_list(GrSpecTs, _TSCs, _) -> - [Test || Test <- GrSpecTs, not is_atom(Test)]. + [Test || Test <- GrSpecTs, not is_atom(Test), element(1,Test)=/=testcase]. modify_tc_list1(GrSpecTs, TSCs) -> %% remove all cases in group tc list that should not be executed GrSpecTs1 = - lists:flatmap(fun(Test) when is_tuple(Test), + lists:flatmap(fun(Test={testcase,TC,_}) -> + case lists:keysearch(TC, 2, TSCs) of + {value,_} -> + [Test]; + _ -> + case lists:member(TC, TSCs) of + true -> [Test]; + false -> [] + end + end; + (Test) when is_tuple(Test), (size(Test) > 2) -> [Test]; (Test={group,_}) -> diff --git a/lib/common_test/src/ct_hooks.erl b/lib/common_test/src/ct_hooks.erl index f0592a40be..903710963c 100644 --- a/lib/common_test/src/ct_hooks.erl +++ b/lib/common_test/src/ct_hooks.erl @@ -26,6 +26,8 @@ %% API Exports -export([init/1]). +-export([groups/2]). +-export([all/2]). -export([init_tc/3]). -export([end_tc/5]). -export([terminate/1]). @@ -41,7 +43,8 @@ opts = [], prio = ctfirst }]). --record(ct_hook_config, {id, module, prio, scope, opts = [], state = []}). +-record(ct_hook_config, {id, module, prio, scope, opts = [], + state = [], groups = []}). %% ------------------------------------------------------------------------- %% API Functions @@ -54,6 +57,47 @@ init(Opts) -> call(get_builtin_hooks(Opts) ++ get_new_hooks(Opts, undefined), ok, init, []). +%% Call the post_groups/2 hook callback +groups(Mod, Groups) -> + Info = try proplists:get_value(ct_hooks, Mod:suite(), []) of + CTHooks when is_list(CTHooks) -> + [{?config_name,CTHooks}]; + CTHook when is_atom(CTHook) -> + [{?config_name,[CTHook]}] + catch _:_ -> + %% since this might be the first time Mod:suite() + %% is called, and it might just fail or return + %% something bad, we allow any failure here - it + %% will be catched later if there is something + %% really wrong. + [{?config_name,[]}] + end, + case call(fun call_generic/3, Info ++ [{'$ct_groups',Groups}], [post_groups, Mod]) of + [{'$ct_groups',NewGroups}] -> + NewGroups; + Other -> + Other + end. + +%% Call the post_all/3 hook callback +all(Mod, Tests) -> + Info = try proplists:get_value(ct_hooks, Mod:suite(), []) of + CTHooks when is_list(CTHooks) -> + [{?config_name,CTHooks}]; + CTHook when is_atom(CTHook) -> + [{?config_name,[CTHook]}] + catch _:_ -> + %% just allow any failure here - it will be catched + %% later if there is something really wrong. + [{?config_name,[]}] + end, + case call(fun call_generic/3, Info ++ [{'$ct_all',Tests}], [post_all, Mod]) of + [{'$ct_all',NewTests}] -> + NewTests; + Other -> + Other + end. + %% @doc Called after all suites are done. -spec terminate(Hooks :: term()) -> ok. @@ -88,6 +132,7 @@ init_tc(Mod, init_per_suite, Config) -> [{?config_name,[]}] end, call(fun call_generic/3, Config ++ Info, [pre_init_per_suite, Mod]); + init_tc(Mod, end_per_suite, Config) -> call(fun call_generic/3, Config, [pre_end_per_suite, Mod]); init_tc(Mod, {init_per_group, GroupName, Properties}, Config) -> @@ -163,7 +208,7 @@ call_id(#ct_hook_config{ module = Mod, opts = Opts} = Hook, Config, Scope) -> {Config, Hook#ct_hook_config{ id = Id, scope = scope(Scope)}}. call_init(#ct_hook_config{ module = Mod, opts = Opts, id = Id, prio = P} = Hook, - Config,_Meta) -> + Config, _Meta) -> case Mod:init(Id, Opts) of {ok, NewState} when P =:= undefined -> {Config, Hook#ct_hook_config{ state = NewState, prio = 0 } }; @@ -194,6 +239,18 @@ call_generic(Hook, Value, Meta) -> call_generic_fallback(Hook, Value, Meta) -> do_call_generic(Hook, Value, Meta, true). +do_call_generic(#ct_hook_config{ module = Mod} = Hook, + [{'$ct_groups',Groups}], [post_groups | Args], Fallback) -> + NewGroups = catch_apply(Mod, post_groups, Args ++ [Groups], + Groups, Fallback), + {[{'$ct_groups',NewGroups}], Hook#ct_hook_config{ groups = NewGroups } }; + +do_call_generic(#ct_hook_config{ module = Mod, groups = Groups} = Hook, + [{'$ct_all',Tests}], [post_all | Args], Fallback) -> + NewTests = catch_apply(Mod, post_all, Args ++ [Tests, Groups], + Tests, Fallback), + {[{'$ct_all',NewTests}], Hook}; + do_call_generic(#ct_hook_config{ module = Mod, state = State} = Hook, Value, [Function | Args], Fallback) -> {NewValue, NewState} = catch_apply(Mod, Function, Args ++ [Value, State], @@ -228,6 +285,12 @@ call([{Hook, call_id, NextFun} | Rest], Config, Meta, Hooks) -> Rest ++ [{NewId, call_init}]}; ExistingHook when is_tuple(ExistingHook) -> {Hooks, Rest}; + _ when hd(Meta)=:=post_groups; hd(Meta)=:=post_all -> + %% If CTH is started because of a call from + %% groups/2 or all/2, CTH:init/1 must not be + %% called (the suite scope should be used). + {Hooks ++ [NewHook], + Rest ++ [{NewId,NextFun}]}; _ -> {Hooks ++ [NewHook], Rest ++ [{NewId, call_init}, {NewId,NextFun}]} @@ -237,8 +300,8 @@ call([{Hook, call_id, NextFun} | Rest], Config, Meta, Hooks) -> Trace = erlang:get_stacktrace(), ct_logs:log("Suite Hook","Failed to start a CTH: ~tp:~tp", [Error,{Reason,Trace}]), - call([], {fail,"Failed to start CTH" - ", see the CT Log for details"}, Meta, Hooks) + call([], {fail,"Failed to start CTH, " + "see the CT Log for details"}, Meta, Hooks) end; call([{HookId, call_init} | Rest], Config, Meta, Hooks) -> call([{HookId, fun call_init/3} | Rest], Config, Meta, Hooks); @@ -278,6 +341,10 @@ scope([pre_init_per_suite, SuiteName|_]) -> [post_end_per_suite, SuiteName]; scope([post_init_per_suite, SuiteName|_]) -> [post_end_per_suite, SuiteName]; +scope([post_groups, SuiteName|_]) -> + [post_groups, SuiteName]; +scope([post_all, SuiteName|_]) -> + [post_all, SuiteName]; scope(init) -> none. @@ -364,6 +431,7 @@ resort(Calls,Hooks,[F|_R]) when F == pre_end_per_testcase; F == pre_end_per_suite; F == post_end_per_suite -> lists:reverse(resort(Calls,Hooks)); + resort(Calls,Hooks,_Meta) -> resort(Calls,Hooks). diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl index fb6a095b57..9f9f4ceaf7 100644 --- a/lib/common_test/src/ct_logs.erl +++ b/lib/common_test/src/ct_logs.erl @@ -151,7 +151,7 @@ close(Info, StartDir) -> ok; CacheBin -> %% save final version of the log cache to file - _ = file:write_file(?log_cache_name,CacheBin), + write_log_cache(CacheBin), put(ct_log_cache,undefined) end end, @@ -2032,7 +2032,7 @@ update_all_runs_in_cache(AllRunsData) -> %% read from file as long as this logger process is alive put(ct_log_cache,term_to_binary(LogCache)); _ -> - file:write_file(?log_cache_name,term_to_binary(LogCache)) + write_log_cache(term_to_binary(LogCache)) end; SavedLogCache -> update_all_runs_in_cache(AllRunsData,binary_to_term(SavedLogCache)) @@ -2046,7 +2046,7 @@ update_all_runs_in_cache(AllRunsData, LogCache) -> %% read from file as long as this logger process is alive put(ct_log_cache,term_to_binary(LogCache1)); _ -> - file:write_file(?log_cache_name,term_to_binary(LogCache1)) + write_log_cache(term_to_binary(LogCache1)) end. sort_all_runs(Dirs) -> @@ -2690,7 +2690,7 @@ update_tests_in_cache(TempData,LogCache=#log_cache{tests=Tests}) -> {_Pid,_Pid} -> put(ct_log_cache,CacheBin); _ -> - file:write_file(?log_cache_name,CacheBin) + write_log_cache(CacheBin) end. %% @@ -3438,3 +3438,9 @@ unexpected_io(Pid, _Category, _Importance, Content, CtLogFd, EscChars) -> Data = io_lib:format("~ts", [lists:foldl(IoFun, [], Content)]), test_server_io:print_unexpected(Data), ok. + +write_log_cache(LogCacheBin) when is_binary(LogCacheBin) -> + TmpFile = ?log_cache_name++".tmp", + _ = file:write_file(TmpFile,LogCacheBin), + _ = file:rename(TmpFile,?log_cache_name), + ok. diff --git a/lib/common_test/src/test_server.erl b/lib/common_test/src/test_server.erl index e56106408f..c6916ea815 100644 --- a/lib/common_test/src/test_server.erl +++ b/lib/common_test/src/test_server.erl @@ -384,8 +384,8 @@ run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit,TimetrapData}) -> {Result,DetFail,ProcBef,ProcAft}. -type tc_status() :: 'starting' | 'running' | 'init_per_testcase' | - 'end_per_testcase' | {'framework',atom(),atom()} | - 'tc'. + 'end_per_testcase' | {'framework',{atom(),atom(),list}} | + 'tc'. -record(st, { ref :: reference(), @@ -652,8 +652,8 @@ handle_tc_exit({testcase_aborted,{user_timetrap_error,_}=Msg,_}, St) -> #st{config=Config,mf={Mod,Func},pid=Pid} = St, spawn_fw_call(Mod, Func, Config, Pid, Msg, unknown, self()), St; -handle_tc_exit(Reason, #st{status={framework,FwMod,FwFunc}, - config=Config,pid=Pid}=St) -> +handle_tc_exit(Reason, #st{status={framework,{FwMod,FwFunc,_}=FwMFA}, + config=Config,mf={Mod,Func},pid=Pid}=St) -> R = case Reason of {timetrap_timeout,TVal,_} -> {timetrap,TVal}; @@ -665,7 +665,7 @@ handle_tc_exit(Reason, #st{status={framework,FwMod,FwFunc}, Other end, Error = {framework_error,R}, - spawn_fw_call(FwMod, FwFunc, Config, Pid, Error, unknown, self()), + spawn_fw_call(Mod, Func, Config, Pid, {Error,FwMFA}, unknown, self()), St; handle_tc_exit(Reason, #st{status=tc,config=Config0,mf={Mod,Func},pid=Pid}=St) when is_list(Config0) -> @@ -849,36 +849,68 @@ spawn_fw_call(Mod,EPTC={end_per_testcase,Func},EndConf,Pid, "WARNING: end_per_testcase failed!</font>", {died,W} end, - try do_end_tc_call(Mod,EPTC,{Pid,Report,[EndConf]}, Why) of - _ -> ok - catch - _:FwEndTCErr -> - exit({fw_notify_done,end_tc,FwEndTCErr}) - end, - FailLoc = proplists:get_value(tc_fail_loc, EndConf), + FailLoc0 = proplists:get_value(tc_fail_loc, EndConf), + {RetVal1,FailLoc} = + try do_end_tc_call(Mod,EPTC,{Pid,Report,[EndConf]}, Why) of + Why -> + {RetVal,FailLoc0}; + {failed,_} = R -> + {R,[{Mod,Func}]}; + R -> + {R,FailLoc0} + catch + _:FwEndTCErr -> + exit({fw_notify_done,end_tc,FwEndTCErr}) + end, %% finished, report back (if end_per_testcase fails, a warning %% should be printed as part of the comment) SendTo ! {self(),fw_notify_done, - {Time,RetVal,FailLoc,[],Warn}} + {Time,RetVal1,FailLoc,[],Warn}} end, spawn_link(FwCall); -spawn_fw_call(FwMod,FwFunc,_,_Pid,{framework_error,FwError},_,SendTo) -> +spawn_fw_call(Mod,Func,Conf,Pid,{{framework_error,FwError}, + {FwMod,FwFunc,[A1,A2|_]}=FwMFA},_,SendTo) -> FwCall = fun() -> ct_util:mark_process(), - test_server_sup:framework_call(report, [framework_error, - {{FwMod,FwFunc}, - FwError}]), + Time = + case FwError of + {timetrap,TVal} -> + TVal/1000; + _ -> + died + end, + {Ret,Loc,WarnOrError} = + cleanup_after_fw_error(Mod,Func,Conf,Pid,FwError,FwMFA), Comment = - lists:flatten( - io_lib:format("<font color=\"red\">" - "WARNING! ~w:~tw failed!</font>", - [FwMod,FwFunc])), + case WarnOrError of + warn -> + group_leader() ! + {printout,12, + "WARNING! ~w:~tw(~w,~tw,...) failed!\n" + " Reason: ~tp\n", + [FwMod,FwFunc,A1,A2,FwError]}, + lists:flatten( + io_lib:format("<font color=\"red\">" + "WARNING! ~w:~tw(~w,~tw,...) " + "failed!</font>", + [FwMod,FwFunc,A1,A2])); + error -> + group_leader() ! + {printout,12, + "Error! ~w:~tw(~w,~tw,...) failed!\n" + " Reason: ~tp\n", + [FwMod,FwFunc,A1,A2,FwError]}, + lists:flatten( + io_lib:format("<font color=\"red\">" + "ERROR! ~w:~tw(~w,~tw,...) " + "failed!</font>", + [FwMod,FwFunc,A1,A2])) + end, %% finished, report back SendTo ! {self(),fw_notify_done, - {died,{error,{FwMod,FwFunc,FwError}}, - {FwMod,FwFunc},[],Comment}} + {Time,Ret,Loc,[],Comment}} end, spawn_link(FwCall); @@ -901,17 +933,185 @@ spawn_fw_call(Mod,Func,CurrConf,Pid,Error,Loc,SendTo) -> FwErrorNotifyErr}) end, Conf = [{tc_status,{failed,Error}}|CurrConf], - try do_end_tc_call(Mod,EndTCFunc,{Pid,Error,[Conf]},Error) of - _ -> ok - catch - _:FwEndTCErr -> - exit({fw_notify_done,end_tc,FwEndTCErr}) - end, + {Time,RetVal,Loc1} = + try do_end_tc_call(Mod,EndTCFunc,{Pid,Error,[Conf]},Error) of + Error -> + {died, Error, Loc}; + {failed,Reason} = NewReturn -> + fw_error_notify(Mod,Func1,Conf,Reason), + {died, NewReturn, [{Mod,Func}]}; + NewReturn -> + T = case Error of + {timetrap_timeout,TT} -> TT; + _ -> 0 + end, + {T, NewReturn, Loc} + catch + _:FwEndTCErr -> + exit({fw_notify_done,end_tc,FwEndTCErr}) + end, %% finished, report back - SendTo ! {self(),fw_notify_done,{died,Error,Loc,[],undefined}} + SendTo ! {self(),fw_notify_done,{Time,RetVal,Loc1,[],undefined}} end, spawn_link(FwCall). +cleanup_after_fw_error(_Mod,_Func,Conf,Pid,FwError, + {FwMod,FwFunc=init_tc, + [Mod,{init_per_testcase,Func}=IPTC|_]}) -> + %% Failed during pre_init_per_testcase, the test must be skipped + Skip = {auto_skip,{failed,{FwMod,FwFunc,FwError}}}, + try begin do_end_tc_call(Mod,IPTC, {Pid,Skip,[Conf]}, FwError), + do_init_tc_call(Mod,{end_per_testcase_not_run,Func}, + [Conf],{ok,[Conf]}), + do_end_tc_call(Mod,{end_per_testcase_not_run,Func}, + {Pid,Skip,[Conf]}, FwError) end of + _ -> ok + catch + _:FwEndTCErr -> + exit({fw_notify_done,end_tc,FwEndTCErr}) + end, + {Skip,{FwMod,FwFunc},error}; +cleanup_after_fw_error(_Mod,_Func,Conf,Pid,FwError, + {FwMod,FwFunc=end_tc,[Mod,{init_per_testcase,Func}|_]}) -> + %% Failed during post_init_per_testcase, the test must be skipped + Skip = {auto_skip,{failed,{FwMod,FwFunc,FwError}}}, + try begin do_init_tc_call(Mod,{end_per_testcase_not_run,Func}, + [Conf],{ok,[Conf]}), + do_end_tc_call(Mod,{end_per_testcase_not_run,Func}, + {Pid,Skip,[Conf]}, FwError) end of + _ -> ok + catch + _:FwEndTCErr -> + exit({fw_notify_done,end_tc,FwEndTCErr}) + end, + {Skip,{FwMod,FwFunc},error}; +cleanup_after_fw_error(_Mod,_Func,Conf,Pid,FwError, + {FwMod,FwFunc=init_tc,[Mod,{end_per_testcase,Func}|_]}) -> + %% Failed during pre_end_per_testcase. Warn about it. + {RetVal,Loc} = + case {proplists:get_value(tc_status, Conf), + proplists:get_value(tc_fail_loc, Conf, unknown)} of + {undefined,_} -> + {{failed,{FwMod,FwFunc,FwError}},{FwMod,FwFunc}}; + {E = {failed,_Reason},unknown} -> + {E,[{Mod,Func}]}; + {Result,FailLoc} -> + {Result,FailLoc} + end, + try begin do_end_tc_call(Mod,{end_per_testcase_not_run,Func}, + {Pid,RetVal,[Conf]}, FwError) end of + _ -> ok + catch + _:FwEndTCErr -> + exit({fw_notify_done,end_tc,FwEndTCErr}) + end, + {RetVal,Loc,warn}; +cleanup_after_fw_error(Mod,Func,Conf,Pid,FwError, + {FwMod,FwFunc=end_tc,[Mod,{end_per_testcase,Func}|_]}) -> + %% Failed during post_end_per_testcase. Warn about it. + {RetVal,Report,Loc} = + case {proplists:get_value(tc_status, Conf), + proplists:get_value(tc_fail_loc, Conf, unknown)} of + {undefined,_} -> + {{failed,{FwMod,FwFunc,FwError}}, + {{FwMod,FwError},FwError}, + {FwMod,FwFunc}}; + {E = {failed,_Reason},unknown} -> + {E,{Mod,Func,E},[{Mod,Func}]}; + {Result,FailLoc} -> + {Result,{Mod,Func,Result},FailLoc} + end, + try begin do_end_tc_call(Mod,{cleanup,{end_per_testcase_not_run,Func}}, + {Pid,RetVal,[Conf]}, FwError) end of + _ -> ok + catch + _:FwEndTCErr -> + exit({fw_notify_done,end_tc,FwEndTCErr}) + end, + test_server_sup:framework_call(report,[framework_error,Report]), + {RetVal,Loc,warn}; +cleanup_after_fw_error(Mod,Func,Conf,Pid,FwError,{FwMod,FwFunc=init_tc,_}) + when Func =:= init_per_suite; Func =:=init_per_group -> + %% Failed during pre_init_per_suite or pre_init_per_group + RetVal = {failed,{FwMod,FwFunc,FwError}}, + try do_end_tc_call(Mod,Func,{Pid,RetVal,[Conf]},FwError) of + _ -> ok + catch + _:FwEndTCErr -> + exit({fw_notify_done,end_tc,FwEndTCErr}) + end, + {RetVal,{FwMod,FwFunc},error}; +cleanup_after_fw_error(Mod,Func,Conf,Pid,FwError,{FwMod,FwFunc=end_tc,_}) + when Func =:= init_per_suite; Func =:=init_per_group -> + %% Failed during post_init_per_suite or post_init_per_group + RetVal = {failed,{FwMod,FwFunc,FwError}}, + try do_end_tc_call(Mod,{cleanup,Func},{Pid,RetVal,[Conf]},FwError) of + _ -> ok + catch + _:FwEndTCErr -> + exit({fw_notify_done,end_tc,FwEndTCErr}) + end, + ReportFunc = + case Func of + init_per_group -> + case proplists:get_value(tc_group_properties,Conf) of + undefined -> + {Func,unknown,[]}; + GProps -> + Name = proplists:get_value(name,GProps), + {Func,Name,proplists:delete(name,GProps)} + end; + _ -> + Func + end, + test_server_sup:framework_call(report,[framework_error, + {Mod,ReportFunc,RetVal}]), + {RetVal,{FwMod,FwFunc},error}; +cleanup_after_fw_error(Mod,Func,Conf,Pid,FwError,{FwMod,FwFunc=init_tc,_}) + when Func =:= end_per_suite; Func =:=end_per_group -> + %% Failed during pre_end_per_suite or pre_end_per_group + RetVal = {failed,{FwMod,FwFunc,FwError}}, + try do_end_tc_call(Mod,Func,{Pid,RetVal,[Conf]},FwError) of + _ -> ok + catch + _:FwEndTCErr -> + exit({fw_notify_done,end_tc,FwEndTCErr}) + end, + {RetVal,{FwMod,FwFunc},error}; +cleanup_after_fw_error(Mod,Func,Conf,Pid,FwError,{FwMod,FwFunc=end_tc,_}) + when Func =:= end_per_suite; Func =:=end_per_group -> + %% Failed during post_end_per_suite or post_end_per_group + RetVal = {failed,{FwMod,FwFunc,FwError}}, + try do_end_tc_call(Mod,{cleanup,Func},{Pid,RetVal,[Conf]},FwError) of + _ -> ok + catch + _:FwEndTCErr -> + exit({fw_notify_done,end_tc,FwEndTCErr}) + end, + ReportFunc = + case Func of + end_per_group -> + case proplists:get_value(tc_group_properties,Conf) of + undefined -> + {Func,unknown,[]}; + GProps -> + Name = proplists:get_value(name,GProps), + {Func,Name,proplists:delete(name,GProps)} + end; + _ -> + Func + end, + test_server_sup:framework_call(report,[framework_error, + {Mod,ReportFunc,RetVal}]), + {RetVal,{FwMod,FwFunc},error}; +cleanup_after_fw_error(_Mod,_Func,_Conf,_Pid,FwError,{FwMod,FwFunc,_}) -> + %% This is unexpected + test_server_sup:framework_call(report, + [framework_error, + {{FwMod,FwFunc}, + FwError}]), + {FwError,{FwMod,FwFunc},error}. + %% The job proxy process forwards messages between the test case %% process on a shielded node (and its descendants) and the job process. %% @@ -1087,6 +1287,9 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) -> EndConf1 = user_callback(TCCallback, Mod, Func, 'end', EndConf), + %% save updated config in controller loop + set_tc_state(tc, EndConf1), + %% We can't handle fails or skips here EndConf2 = case do_init_tc_call(Mod,{end_per_testcase,Func}, diff --git a/lib/common_test/src/test_server_ctrl.erl b/lib/common_test/src/test_server_ctrl.erl index 8ef28b3343..3bb7b6263c 100644 --- a/lib/common_test/src/test_server_ctrl.erl +++ b/lib/common_test/src/test_server_ctrl.erl @@ -1443,6 +1443,8 @@ remove_conf([C={Mod,error_in_suite,_}|Cases], NoConf, Repeats) -> true -> remove_conf(Cases, [C|NoConf], Repeats) end; +remove_conf([C={repeat,_,_}|Cases], NoConf, _Repeats) -> + remove_conf(Cases, [C|NoConf], true); remove_conf([C|Cases], NoConf, Repeats) -> remove_conf(Cases, [C|NoConf], Repeats); remove_conf([], NoConf, true) -> @@ -2060,6 +2062,14 @@ add_init_and_end_per_suite([SkipCase|Cases], LastMod, LastRef, FwMod) [SkipCase|add_init_and_end_per_suite(Cases, LastMod, LastRef, FwMod)]; add_init_and_end_per_suite([{conf,_,_,_}=Case|Cases], LastMod, LastRef, FwMod) -> [Case|add_init_and_end_per_suite(Cases, LastMod, LastRef, FwMod)]; +add_init_and_end_per_suite([{repeat,{Mod,_},_}=Case|Cases], LastMod, LastRef, FwMod) + when Mod =/= LastMod, Mod =/= FwMod -> + {PreCases, NextMod, NextRef} = + do_add_init_and_end_per_suite(LastMod, LastRef, Mod, FwMod), + PreCases ++ [Case|add_init_and_end_per_suite(Cases, NextMod, + NextRef, FwMod)]; +add_init_and_end_per_suite([{repeat,_,_}=Case|Cases], LastMod, LastRef, FwMod) -> + [Case|add_init_and_end_per_suite(Cases, LastMod, LastRef, FwMod)]; add_init_and_end_per_suite([{Mod,_}=Case|Cases], LastMod, LastRef, FwMod) when Mod =/= LastMod, Mod =/= FwMod -> {PreCases, NextMod, NextRef} = @@ -2137,7 +2147,7 @@ do_add_init_and_end_per_suite(LastMod, LastRef, Mod, FwMod) -> %% let's call a "fake" end_per_suite if it exists case erlang:function_exported(FwMod, end_per_suite, 1) of true -> - [{conf,LastRef,[{suite,Mod}], + [{conf,LastRef,[{suite,LastMod}], {FwMod,end_per_suite}}|Init]; false -> [{conf,LastRef,[],{LastMod,end_per_suite}}|Init] @@ -2925,6 +2935,29 @@ run_test_cases_loop([{conf,_Ref,_Props,_X}=Conf|_Cases0], Config, _TimetrapData, _Mode, _Status) -> erlang:error(badarg, [Conf,Config]); +run_test_cases_loop([{repeat,Case,{RepeatType,N}}|Cases0], Config, + TimeTrapData, Mode, Status) -> + Ref = make_ref(), + Parallel = check_prop(parallel, Mode) =/= false, + Sequence = check_prop(sequence, Mode) =/= false, + RepeatStop = RepeatType=:=repeat_until_fail + orelse RepeatType=:=repeat_until_ok, + + if Parallel andalso RepeatStop -> + %% Cannot check results of test case during parallal + %% execution, so only RepeatType=:=repeat is allowed in + %% combination with parallel groups. + erlang:error({illegal_combination,{parallel,RepeatType}}); + Sequence andalso RepeatStop -> + %% Sequence is stop on fail + skip rest, so only + %% RepeatType=:=repeat makes sense inside a sequence. + erlang:error({illegal_combination,{sequence,RepeatType}}); + true -> + Mode1 = [{Ref,[{repeat,{RepeatType,1,N}}],?now}|Mode], + run_test_cases_loop([Case | Cases0], Config, TimeTrapData, + Mode1, Status) + end; + run_test_cases_loop([{Mod,Case}|Cases], Config, TimetrapData, Mode, Status) -> ActualCfg = case get(test_server_create_priv_dir) of @@ -2937,7 +2970,7 @@ run_test_cases_loop([{Mod,Case}|Cases], Config, TimetrapData, Mode, Status) -> run_test_cases_loop([{Mod,Case,[ActualCfg]}|Cases], Config, TimetrapData, Mode, Status); -run_test_cases_loop([{Mod,Func,Args}|Cases], Config, TimetrapData, Mode, Status) -> +run_test_cases_loop([{Mod,Func,Args}=Case|Cases], Config, TimetrapData, Mode0, Status) -> {Num,RunInit} = case FwMod = get_fw_mod(?MODULE) of Mod when Func == error_in_suite -> @@ -2947,6 +2980,14 @@ run_test_cases_loop([{Mod,Func,Args}|Cases], Config, TimetrapData, Mode, Status) run_init} end, + Mode = + case Mode0 of + [{_,[{repeat,{_,_,_}}],_}|RestMode] -> + RestMode; + _ -> + Mode0 + end, + %% check the current execution mode and save info about the case if %% detected that printouts to common log files is handled later @@ -2974,36 +3015,42 @@ run_test_cases_loop([{Mod,Func,Args}|Cases], Config, TimetrapData, Mode, Status) if is_tuple(RetVal) -> element(1,RetVal); true -> undefined end, - {Failed,Status1} = + {Result,Failed,Status1} = case RetTag of Skip when Skip==skip; Skip==skipped -> - {false,update_status(skipped, Mod, Func, Status)}; + {skipped,false,update_status(skipped, Mod, Func, Status)}; Fail when Fail=='EXIT'; Fail==failed -> - {true,update_status(failed, Mod, Func, Status)}; + {failed,true,update_status(failed, Mod, Func, Status)}; _ when Time==died, RetVal=/=ok -> - {true,update_status(failed, Mod, Func, Status)}; + {failed,true,update_status(failed, Mod, Func, Status)}; _ -> - {false,update_status(ok, Mod, Func, Status)} + {ok,false,update_status(ok, Mod, Func, Status)} end, case check_prop(sequence, Mode) of false -> + {Cases1,Mode1} = + check_repeat_testcase(Case,Result,Cases,Mode0), stop_minor_log_file(), - run_test_cases_loop(Cases, Config, TimetrapData, Mode, Status1); + run_test_cases_loop(Cases1, Config, TimetrapData, Mode1, Status1); Ref -> %% the case is in a sequence; we must check the result and %% determine if the following cases should run or be skipped if not Failed -> % proceed with next case + {Cases1,Mode1} = + check_repeat_testcase(Case,Result,Cases,Mode0), stop_minor_log_file(), - run_test_cases_loop(Cases, Config, TimetrapData, Mode, Status1); + run_test_cases_loop(Cases1, Config, TimetrapData, Mode1, Status1); true -> % skip rest of cases in sequence print(minor, "~n*** ~tw failed.~n" " Skipping all other cases in sequence.", [Func]), + {Cases1,Mode1} = + check_repeat_testcase(Case,Result,Cases,Mode0), Reason = {failed,{Mod,Func}}, - Cases2 = skip_cases_upto(Ref, Cases, Reason, tc, + Cases2 = skip_cases_upto(Ref, Cases1, Reason, tc, Mode, auto_skip_case), stop_minor_log_file(), - run_test_cases_loop(Cases2, Config, TimetrapData, Mode, Status1) + run_test_cases_loop(Cases2, Config, TimetrapData, Mode1, Status1) end end; %% the test case is being executed in parallel with the main process (and @@ -3012,7 +3059,8 @@ run_test_cases_loop([{Mod,Func,Args}|Cases], Config, TimetrapData, Mode, Status) %% io from Pid will be buffered by the test_server_io process and %% handled later, so we have to save info about the case queue_test_case_io(undefined, Pid, Num+1, Mod, Func), - run_test_cases_loop(Cases, Config, TimetrapData, Mode, Status) + {Cases1,Mode1} = check_repeat_testcase(Case,ok,Cases,Mode0), + run_test_cases_loop(Cases1, Config, TimetrapData, Mode1, Status) end; %% TestSpec processing finished @@ -3450,9 +3498,19 @@ modify_cases_upto1(Ref, {skip,Reason,FType,Mode,SkipType}, T, Orig, Alt) end; -%% next is some other case, ignore or copy -modify_cases_upto1(Ref, {skip,_,_,_,_}=Op, [_Other|T], Orig, Alt) -> +%% next is a repeated test case +modify_cases_upto1(Ref, {skip,Reason,_,Mode,SkipType}=Op, + [{repeat,{_M,_F}=MF,_Repeat}|T], Orig, Alt) -> + modify_cases_upto1(Ref, Op, T, Orig, [{SkipType,{MF,Reason},Mode}|Alt]); + +%% next is an already skipped case, ignore or copy +modify_cases_upto1(Ref, {skip,_,_,_,_}=Op, [{SkipType,_,_}|T], Orig, Alt) + when SkipType=:=skip_case; SkipType=:=auto_skip_case -> modify_cases_upto1(Ref, Op, T, Orig, Alt); + +%% next is some other case, mark as skipped or copy +modify_cases_upto1(Ref, {skip,Reason,_,Mode,SkipType}=Op, [Other|T], Orig, Alt) -> + modify_cases_upto1(Ref, Op, T, Orig, [{SkipType,{Other,Reason},Mode}|Alt]); modify_cases_upto1(Ref, CopyOp, [C|T], Orig, Alt) -> modify_cases_upto1(Ref, CopyOp, T, [C|Orig], [C|Alt]). @@ -3840,6 +3898,10 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, {died,{timetrap_timeout,TimetrapTimeout}} -> progress(failed, Num, Mod, Func, GrName, Loc, timetrap_timeout, TimetrapTimeout, Comment, Style); + {died,Reason={auto_skip,_Why}} -> + %% died in init_per_testcase or in a hook in this context + progress(skip, Num, Mod, Func, GrName, Loc, Reason, + Time, Comment, Style); {died,{Skip,Reason}} when Skip==skip; Skip==skipped -> %% died in init_per_testcase progress(skip, Num, Mod, Func, GrName, Loc, Reason, @@ -4795,6 +4857,14 @@ collect_cases({make,InitMFA,CaseList,FinMFA}, St0, Mode) -> {error,_Reason} = Error -> Error end; +collect_cases({repeat,{Module, Case}, Repeat}, St, Mode) -> + case catch collect_case([Case], St#cc{mod=Module}, [], Mode) of + {ok, [{Module,Case}], _} -> + {ok, [{repeat,{Module, Case}, Repeat}], St}; + Other -> + {error,Other} + end; + collect_cases({Module, Cases}, St, Mode) when is_list(Cases) -> case (catch collect_case(Cases, St#cc{mod=Module}, [], Mode)) of Result = {ok,_,_} -> @@ -5758,3 +5828,42 @@ encoding(File) -> E -> E end. + +check_repeat_testcase(Case,Result,Cases, + [{Ref,[{repeat,RepeatData0}],StartTime}|Mode0]) -> + case do_update_repeat_data(Result,RepeatData0) of + false -> + {Cases,Mode0}; + RepeatData -> + {[Case|Cases],[{Ref,[{repeat,RepeatData}],StartTime}|Mode0]} + end; +check_repeat_testcase(_,_,Cases,Mode) -> + {Cases,Mode}. + +do_update_repeat_data(_,{RT,N,N}) when is_integer(N) -> + report_repeat_testcase(N,N), + report_stop_repeat_testcase(done,{RT,N}), + false; +do_update_repeat_data(ok,{repeat_until_ok=RT,M,N}) -> + report_repeat_testcase(M,N), + report_stop_repeat_testcase(RT,{RT,N}), + false; +do_update_repeat_data(failed,{repeat_until_fail=RT,M,N}) -> + report_repeat_testcase(M,N), + report_stop_repeat_testcase(RT,{RT,N}), + false; +do_update_repeat_data(_,{RT,M,N}) when is_integer(M) -> + report_repeat_testcase(M,N), + {RT,M+1,N}; +do_update_repeat_data(_,{_,M,N}=RepeatData) -> + report_repeat_testcase(M,N), + RepeatData. + +report_stop_repeat_testcase(Reason,RepVal) -> + print(minor, "~n*** Stopping test case repeat operation: ~w", [Reason]), + print(1, "Stopping test case repeat operation: ~w", [RepVal]). + +report_repeat_testcase(M,forever) -> + print(minor, "~n=== Repeated test case: ~w of infinity", [M]); +report_repeat_testcase(M,N) -> + print(minor, "~n=== Repeated test case: ~w of ~w", [M,N]). diff --git a/lib/common_test/src/test_server_node.erl b/lib/common_test/src/test_server_node.erl index b3b6ae3d92..cc285a8b0d 100644 --- a/lib/common_test/src/test_server_node.erl +++ b/lib/common_test/src/test_server_node.erl @@ -654,9 +654,19 @@ find_release({unix,linux}, Rel) -> find_release(_, _) -> none. find_rel_linux(Rel) -> - case suse_release() of - none -> []; - SuseRel -> find_rel_suse(Rel, SuseRel) + try + case ubuntu_release() of + none -> none; + [UbuntuRel |_] -> throw(find_rel_ubuntu(Rel, UbuntuRel)) + end, + case suse_release() of + none -> none; + SuseRel -> throw(find_rel_suse(Rel, SuseRel)) + end, + [] + catch + throw:Result -> + Result end. find_rel_suse(Rel, SuseRel) -> @@ -733,6 +743,93 @@ suse_release(Fd) -> end end. +find_rel_ubuntu(_Rel, UbuntuRel) when is_integer(UbuntuRel), UbuntuRel < 16 -> + []; +find_rel_ubuntu(Rel, UbuntuRel) when is_integer(UbuntuRel) -> + Root = "/usr/local/otp/releases/ubuntu", + lists:foldl(fun (ChkUbuntuRel, Acc) -> + find_rel_ubuntu_aux1(Rel, Root++integer_to_list(ChkUbuntuRel)) + ++ Acc + end, + [], + lists:seq(16, UbuntuRel)). + +find_rel_ubuntu_aux1(Rel, RootWc) -> + case erlang:system_info(wordsize) of + 4 -> + find_rel_ubuntu_aux2(Rel, RootWc++"_32"); + 8 -> + find_rel_ubuntu_aux2(Rel, RootWc++"_64") ++ + find_rel_ubuntu_aux2(Rel, RootWc++"_32") + end. + +find_rel_ubuntu_aux2(Rel, RootWc) -> + RelDir = filename:dirname(RootWc), + Pat = filename:basename(RootWc ++ "_" ++ Rel) ++ ".*", + case file:list_dir(RelDir) of + {ok,Dirs} -> + case lists:filter(fun(Dir) -> + case re:run(Dir, Pat, [unicode]) of + nomatch -> false; + _ -> true + end + end, Dirs) of + [] -> + []; + [R|_] -> + [filename:join([RelDir,R,"bin","erl"])] + end; + _ -> + [] + end. + +ubuntu_release() -> + case file:open("/etc/lsb-release", [read]) of + {ok,Fd} -> + try + ubuntu_release(Fd, undefined, undefined) + after + file:close(Fd) + end; + {error,_} -> none + end. + +ubuntu_release(_Fd, DistrId, Rel) when DistrId /= undefined, + Rel /= undefined -> + Ubuntu = case DistrId of + "Ubuntu" -> true; + "ubuntu" -> true; + _ -> false + end, + case Ubuntu of + false -> none; + true -> Rel + end; +ubuntu_release(Fd, DistroId, Rel) -> + case io:get_line(Fd, '') of + eof -> + none; + Line when is_list(Line) -> + case re:run(Line, "^DISTRIB_ID=(\\w+)$", + [{capture,all_but_first,list}]) of + {match,[NewDistroId]} -> + ubuntu_release(Fd, NewDistroId, Rel); + nomatch -> + case re:run(Line, "^DISTRIB_RELEASE=(\\d+(?:\\.\\d+)*)$", + [{capture,all_but_first,list}]) of + {match,[RelList]} -> + NewRel = lists:map(fun (N) -> + list_to_integer(N) + end, + string:lexemes(RelList, ".")), + ubuntu_release(Fd, DistroId, NewRel); + nomatch -> + ubuntu_release(Fd, DistroId, Rel) + end + end + end. + + unpack(Bin) -> {One,Term} = split_binary(Bin, 1), case binary_to_list(One) of diff --git a/lib/common_test/src/test_server_sup.erl b/lib/common_test/src/test_server_sup.erl index 6ddbf1ad27..fae977e40c 100644 --- a/lib/common_test/src/test_server_sup.erl +++ b/lib/common_test/src/test_server_sup.erl @@ -770,7 +770,7 @@ framework_call(Callback,Func,Args,DefaultReturn) -> end, case SetTcState of true -> - test_server:set_tc_state({framework,Mod,Func}); + test_server:set_tc_state({framework,{Mod,Func,Args}}); false -> ok end, diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile index ecd1f727a2..e510b74d6a 100644 --- a/lib/common_test/test/Makefile +++ b/lib/common_test/test/Makefile @@ -74,7 +74,9 @@ MODULES= \ ct_SUITE \ ct_keep_logs_SUITE \ ct_unicode_SUITE \ - ct_auto_clean_SUITE + ct_auto_clean_SUITE \ + ct_util_SUITE \ + ct_tc_repeat_SUITE ERL_FILES= $(MODULES:%=%.erl) HRL_FILES= test_server_test_lib.hrl diff --git a/lib/common_test/test/ct_auto_compile_SUITE.erl b/lib/common_test/test/ct_auto_compile_SUITE.erl index dface99b8f..f88f13c889 100644 --- a/lib/common_test/test/ct_auto_compile_SUITE.erl +++ b/lib/common_test/test/ct_auto_compile_SUITE.erl @@ -169,7 +169,7 @@ test_events(ac_flag) -> {?eh,start_info,{1,1,3}}, {?eh,tc_start,{ct_framework,error_in_suite}}, {?eh,tc_done,{ct_framework,error_in_suite, - {failed,{error,'bad_SUITE can not be compiled or loaded'}}}}, + {failed,{error,'bad_SUITE cannot be compiled or loaded'}}}}, {?eh,tc_start,{dummy_SUITE,init_per_suite}}, {?eh,tc_done,{dummy_SUITE,init_per_suite,ok}}, {?eh,test_stats,{1,1,{1,0}}}, @@ -186,7 +186,7 @@ test_events(ac_spec) -> {?eh,start_info,{1,1,3}}, {?eh,tc_start,{ct_framework,error_in_suite}}, {?eh,tc_done,{ct_framework,error_in_suite, - {failed,{error,'bad_SUITE can not be compiled or loaded'}}}}, + {failed,{error,'bad_SUITE cannot be compiled or loaded'}}}}, {?eh,tc_start,{dummy_SUITE,init_per_suite}}, {?eh,tc_done,{dummy_SUITE,init_per_suite,ok}}, {?eh,test_stats,{1,1,{1,0}}}, diff --git a/lib/common_test/test/ct_error_SUITE.erl b/lib/common_test/test/ct_error_SUITE.erl index 7468ebe9d9..d31bd26273 100644 --- a/lib/common_test/test/ct_error_SUITE.erl +++ b/lib/common_test/test/ct_error_SUITE.erl @@ -648,33 +648,35 @@ test_events(cfg_error) -> {?eh,tc_start,{cfg_error_11_SUITE,end_per_suite}}, {?eh,tc_done,{cfg_error_11_SUITE,end_per_suite,ok}}, {?eh,tc_start,{cfg_error_12_SUITE,tc1}}, - {?eh,tc_done,{ct_framework,init_tc,{framework_error,{timetrap,500}}}}, - {?eh,test_stats,{13,8,{0,19}}}, + {?eh,tc_done,{cfg_error_12_SUITE,tc1, + {auto_skipped, + {failed,{ct_framework,init_tc,{timetrap,500}}}}}}, + {?eh,test_stats,{13,7,{0,20}}}, {?eh,tc_start,{cfg_error_12_SUITE,tc2}}, {?eh,tc_done,{cfg_error_12_SUITE,tc2,{failed, {cfg_error_12_SUITE,end_per_testcase, {timetrap_timeout,500}}}}}, - {?eh,test_stats,{14,8,{0,19}}}, + {?eh,test_stats,{14,7,{0,20}}}, {?eh,tc_start,{cfg_error_12_SUITE,tc3}}, {?eh,tc_done,{cfg_error_12_SUITE,tc3,ok}}, - {?eh,test_stats,{15,8,{0,19}}}, + {?eh,test_stats,{15,7,{0,20}}}, {?eh,tc_start,{cfg_error_12_SUITE,tc4}}, {?eh,tc_done,{cfg_error_12_SUITE,tc4,{failed, {cfg_error_12_SUITE,end_per_testcase, {timetrap_timeout,500}}}}}, - {?eh,test_stats,{16,8,{0,19}}}, + {?eh,test_stats,{16,7,{0,20}}}, {?eh,tc_start,{cfg_error_13_SUITE,init_per_suite}}, {?eh,tc_done,{cfg_error_13_SUITE,init_per_suite,ok}}, {?eh,tc_start,{cfg_error_13_SUITE,tc1}}, {?eh,tc_done,{cfg_error_13_SUITE,tc1,ok}}, - {?eh,test_stats,{17,8,{0,19}}}, + {?eh,test_stats,{17,7,{0,20}}}, {?eh,tc_start,{cfg_error_13_SUITE,end_per_suite}}, {?eh,tc_done,{cfg_error_13_SUITE,end_per_suite,ok}}, {?eh,tc_start,{cfg_error_14_SUITE,init_per_suite}}, {?eh,tc_done,{cfg_error_14_SUITE,init_per_suite,ok}}, {?eh,tc_start,{cfg_error_14_SUITE,tc1}}, {?eh,tc_done,{cfg_error_14_SUITE,tc1,ok}}, - {?eh,test_stats,{18,8,{0,19}}}, + {?eh,test_stats,{18,7,{0,20}}}, {?eh,tc_start,{cfg_error_14_SUITE,end_per_suite}}, {?eh,tc_done,{cfg_error_14_SUITE,end_per_suite, {comment, @@ -728,25 +730,30 @@ test_events(lib_error) -> {lib_error_1_SUITE,no_lines_throw,{failed,{error,{thrown,catch_me_if_u_can}}}}}, {?eh,test_stats,{0,8,{0,0}}}, {?eh,tc_start,{lib_error_1_SUITE,init_tc_error}}, - {?eh,tc_done,{ct_framework,init_tc, - {framework_error,{{badmatch,[1,2]},'_'}}}}, - {?eh,test_stats,{0,9,{0,0}}}, + {?eh,tc_done,{lib_error_1_SUITE,init_tc_error, + {auto_skipped, + {failed, + {ct_framework,init_tc, + {{badmatch,[1,2]},'_'}}}}}}, + {?eh,test_stats,{0,8,{0,1}}}, {?eh,tc_start,{lib_error_1_SUITE,init_tc_exit}}, - {?eh,tc_done,{ct_framework,init_tc,{framework_error,byebye}}}, - {?eh,test_stats,{0,10,{0,0}}}, + {?eh,tc_done,{lib_error_1_SUITE,init_tc_exit, + {auto_skipped,{failed,{ct_framework,init_tc,byebye}}}}}, + {?eh,test_stats,{0,8,{0,2}}}, {?eh,tc_start,{lib_error_1_SUITE,init_tc_throw}}, - {?eh,tc_done,{ct_framework,init_tc,{framework_error,catch_me_if_u_can}}}, - {?eh,test_stats,{0,11,{0,0}}}, + {?eh,tc_done,{lib_error_1_SUITE,init_tc_throw, + {auto_skipped,{failed,{ct_framework,init_tc, + catch_me_if_u_can}}}}}, + {?eh,test_stats,{0,8,{0,3}}}, {?eh,tc_start,{lib_error_1_SUITE,end_tc_error}}, - {?eh,tc_done,{ct_framework,end_tc, - {framework_error,{{badmatch,[1,2]},'_'}}}}, - {?eh,test_stats,{0,12,{0,0}}}, + {?eh,tc_done,{lib_error_1_SUITE,end_tc_error,ok}}, % warning in comment + {?eh,test_stats,{1,8,{0,3}}}, {?eh,tc_start,{lib_error_1_SUITE,end_tc_exit}}, - {?eh,tc_done,{ct_framework,end_tc,{framework_error,byebye}}}, - {?eh,test_stats,{0,13,{0,0}}}, + {?eh,tc_done,{lib_error_1_SUITE,end_tc_exit,ok}}, % warning in comment + {?eh,test_stats,{2,8,{0,3}}}, {?eh,tc_start,{lib_error_1_SUITE,end_tc_throw}}, - {?eh,tc_done,{ct_framework,end_tc,{framework_error,catch_me_if_u_can}}}, - {?eh,test_stats,{0,14,{0,0}}}, + {?eh,tc_done,{lib_error_1_SUITE,end_tc_throw,ok}}, % warning in comment + {?eh,test_stats,{3,8,{0,3}}}, {?eh,tc_start,{lib_error_1_SUITE,end_per_suite}}, {?eh,tc_done,{lib_error_1_SUITE,end_per_suite,ok}}, {?eh,test_done,{'DEF','STOP_TIME'}}, diff --git a/lib/common_test/test/ct_hooks_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE.erl index 8ba14e63bc..edcfec4fb5 100644 --- a/lib/common_test/test/ct_hooks_SUITE.erl +++ b/lib/common_test/test/ct_hooks_SUITE.erl @@ -73,18 +73,22 @@ all() -> all(suite) -> lists:reverse( [ + crash_groups, crash_all, bad_return_groups, bad_return_all, + illegal_values_groups, illegal_values_all, alter_groups, alter_all, + alter_all_to_skip, alter_all_from_skip, one_cth, two_cth, faulty_cth_no_init, faulty_cth_id_no_init, faulty_cth_exit_in_init, faulty_cth_exit_in_id, faulty_cth_exit_in_init_scope_suite, minimal_cth, minimal_and_maximal_cth, faulty_cth_undef, scope_per_suite_cth, scope_per_group_cth, scope_suite_cth, + scope_suite_group_only_cth, scope_per_suite_state_cth, scope_per_group_state_cth, scope_suite_state_cth, fail_pre_suite_cth, double_fail_pre_suite_cth, fail_post_suite_cth, skip_pre_suite_cth, skip_pre_end_cth, skip_pre_init_tc_cth, skip_post_suite_cth, recover_post_suite_cth, update_config_cth, - state_update_cth, options_cth, same_id_cth, + state_update_cth, update_result_cth, options_cth, same_id_cth, fail_n_skip_with_minimal_cth, prio_cth, no_config, no_init_suite_config, no_init_config, no_end_config, failed_sequence, repeat_force_stop, config_clash, @@ -152,6 +156,11 @@ scope_suite_cth(Config) when is_list(Config) -> do_test(scope_suite_cth, "ct_scope_suite_cth_SUITE.erl", [],Config). +scope_suite_group_only_cth(Config) when is_list(Config) -> + do_test(scope_suite_group_only_cth, + "ct_scope_suite_group_only_cth_SUITE.erl", + [],Config,ok,2,[{group,g1}]). + scope_per_group_cth(Config) when is_list(Config) -> do_test(scope_per_group_cth, "ct_scope_per_group_cth_SUITE.erl", [],Config). @@ -209,6 +218,10 @@ state_update_cth(Config) when is_list(Config) -> do_test(state_update_cth, "ct_cth_fail_one_skip_one_SUITE.erl", [state_update_cth,state_update_cth],Config). +update_result_cth(Config) -> + do_test(update_result_cth, "ct_cth_update_result_post_end_tc_SUITE.erl", + [update_result_post_end_tc_cth],Config). + options_cth(Config) when is_list(Config) -> do_test(options_cth, "ct_cth_empty_SUITE.erl", [{empty_cth,[test]}],Config). @@ -295,10 +308,74 @@ repeat_force_stop(Config) -> [{force_stop,skip_rest},{duration,"000009"}]). %% Test that expected callbacks, and only those, are called when a test -%% are fails due to clash in config alias names +%% fails due to clash in config alias names config_clash(Config) -> do_test(config_clash, "config_clash_SUITE.erl", [skip_cth], Config). +%% Test post_groups and post_all hook callbacks, introduced by OTP-14746 +alter_groups(Config) -> + CfgFile = gen_config(?FUNCTION_NAME, + [{post_groups_return,[{new_group,[tc1,tc2]}]}, + {post_all_return,[{group,new_group}]}],Config), + do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth], + Config, ok, 2, [{config,CfgFile}]). + +alter_all(Config) -> + CfgFile = gen_config(?FUNCTION_NAME,[{post_all_return,[tc2]}],Config), + do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth], + Config, ok, 2, [{config,CfgFile}]). + +alter_all_from_skip(Config) -> + CfgFile = gen_config(?FUNCTION_NAME,[{all_return,{skip,"skipped by all/0"}}, + {post_all_return,[tc2]}],Config), + do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth], + Config, ok, 2, [{config,CfgFile}]). + +alter_all_to_skip(Config) -> + CfgFile = gen_config(?FUNCTION_NAME, + [{post_all_return,{skip,"skipped by post_all/3"}}], + Config), + do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth], + Config, ok, 2, [{config,CfgFile}]). + +bad_return_groups(Config) -> + CfgFile = gen_config(?FUNCTION_NAME,[{post_groups_return,not_a_list}], + Config), + do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth], + Config, ok, 2, [{config,CfgFile}]). + +bad_return_all(Config) -> + CfgFile = gen_config(?FUNCTION_NAME,[{post_all_return,not_a_list}], + Config), + do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth], + Config, ok, 2, [{config,CfgFile}]). + +illegal_values_groups(Config) -> + CfgFile = gen_config(?FUNCTION_NAME, + [{post_groups_return,[{new_group,[this_test_does_not_exist]}, + this_is_not_a_group_def]}], + Config), + do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth], + Config, ok, 2, [{config,CfgFile}]). + +illegal_values_all(Config) -> + CfgFile = gen_config(?FUNCTION_NAME, + [{post_all_return,[{group,this_group_does_not_exist}, + {this_is_not_a_valid_term}]}], + Config), + do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth], + Config, ok, 2, [{config,CfgFile}]). + +crash_groups(Config) -> + CfgFile = gen_config(?FUNCTION_NAME,[{post_groups_return,crash}],Config), + do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth], + Config, ok, 2, [{config,CfgFile}]). + +crash_all(Config) -> + CfgFile = gen_config(?FUNCTION_NAME,[{post_all_return,crash}],Config), + do_test(?FUNCTION_NAME, "all_and_groups_SUITE.erl", [all_and_groups_cth], + Config, ok, 2, [{config,CfgFile}]). + %%%----------------------------------------------------------------- %%% HELP FUNCTIONS %%%----------------------------------------------------------------- @@ -318,6 +395,7 @@ do_test(Tag, {WhatTag,Wildcard}, CTHs, Config, Res, EC, ExtraOpts) -> filename:join([DataDir,"cth/tests",Wildcard])), {Opts,ERPid} = setup([{WhatTag,Files},{ct_hooks,CTHs},{label,Tag}|ExtraOpts], Config), + Res = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -343,6 +421,13 @@ reformat(Events, EH) -> %reformat(Events, _EH) -> % Events. +gen_config(Name,KeyVals,Config) -> + PrivDir = ?config(priv_dir,Config), + File = filename:join(PrivDir,atom_to_list(Name)++".cfg"), + ok = file:write_file(File,[io_lib:format("~p.~n",[{Key,Value}]) + || {Key,Value} <- KeyVals]), + File. + %%%----------------------------------------------------------------- %%% TEST EVENTS %%%----------------------------------------------------------------- @@ -361,13 +446,16 @@ test_events(one_empty_cth) -> {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, {?eh,cth,{empty_cth,id,[[]]}}, {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}}, + %% check that post_groups and post_all comes after init when hook + %% is installed with start flag/option. + {?eh,cth,{empty_cth,post_groups,[ct_cth_empty_SUITE,[]]}}, + {?eh,cth,{empty_cth,post_all,[ct_cth_empty_SUITE,[test_case],[]]}}, {?eh,tc_start,{ct_cth_empty_SUITE,init_per_suite}}, {?eh,cth,{empty_cth,pre_init_per_suite, [ct_cth_empty_SUITE,'$proplist',[]]}}, {?eh,cth,{empty_cth,post_init_per_suite, [ct_cth_empty_SUITE,'$proplist','$proplist',[]]}}, {?eh,tc_done,{ct_cth_empty_SUITE,init_per_suite,ok}}, - {?eh,tc_start,{ct_cth_empty_SUITE,test_case}}, {?eh,cth,{empty_cth,pre_init_per_testcase,[ct_cth_empty_SUITE,test_case,'$proplist',[]]}}, {?eh,cth,{empty_cth,post_init_per_testcase,[ct_cth_empty_SUITE,test_case,'$proplist','_',[]]}}, @@ -576,6 +664,10 @@ test_events(scope_suite_cth) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + %% check that post_groups and post_all comes before init when hook + %% is installed in suite/0 + {?eh,cth,{'_',post_groups,['_',[]]}}, + {?eh,cth,{'_',post_all,['_','_',[]]}}, {?eh,tc_start,{ct_scope_suite_cth_SUITE,init_per_suite}}, {?eh,cth,{'_',id,[[]]}}, {?eh,cth,{'_',init,['_',[]]}}, @@ -597,6 +689,34 @@ test_events(scope_suite_cth) -> {?eh,stop_logging,[]} ]; +test_events(scope_suite_group_only_cth) -> + Suite = ct_scope_suite_group_only_cth_SUITE, + CTH = empty_cth, + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,1}}, + %% check that post_groups and post_all comes before init when hook + %% is installed in suite/0 + {?eh,cth,{CTH,post_groups,['_',['_']]}}, + {negative, + {?eh,cth,{CTH,post_all,['_','_','_']}}, + {?eh,tc_start,{Suite,init_per_suite}}}, + {?eh,cth,{CTH,id,[[]]}}, + {?eh,cth,{CTH,init,['_',[]]}}, + {?eh,cth,{CTH,pre_init_per_suite,[Suite,'$proplist',mystate]}}, + {?eh,cth,{CTH,post_init_per_suite,[Suite,'$proplist','$proplist',mystate]}}, + {?eh,tc_done,{Suite,init_per_suite,ok}}, + + {?eh,tc_start,{Suite,end_per_suite}}, + {?eh,cth,{CTH,pre_end_per_suite,[Suite,'$proplist',mystate]}}, + {?eh,cth,{CTH,post_end_per_suite,[Suite,'$proplist','_',mystate]}}, + {?eh,cth,{CTH,terminate,[mystate]}}, + {?eh,tc_done,{Suite,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]; + test_events(scope_per_group_cth) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, @@ -656,6 +776,8 @@ test_events(scope_suite_state_cth) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{'_',post_groups,['_',[]]}}, + {?eh,cth,{'_',post_all,['_','_',[]]}}, {?eh,tc_start,{ct_scope_suite_state_cth_SUITE,init_per_suite}}, {?eh,cth,{'_',id,[[test]]}}, {?eh,cth,{'_',init,['_',[test]]}}, @@ -1094,6 +1216,106 @@ test_events(state_update_cth) -> {?eh,stop_logging,[]} ]; +test_events(update_result_cth) -> + Suite = ct_cth_update_result_post_end_tc_SUITE, + [ + {?eh,start_logging,'_'}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{'_',init,['_',[]]}}, + {?eh,tc_start,{Suite,init_per_suite}}, + {?eh,tc_done,{Suite,init_per_suite,ok}}, + + {?eh,tc_start,{Suite,tc_ok_to_fail}}, + {?eh,cth,{'_',post_end_per_testcase,[Suite,tc_ok_to_fail,'_',ok,[]]}}, + {?eh,tc_done,{Suite,tc_ok_to_fail,{failed,{error,"Test failure"}}}}, + {?eh,cth,{'_',on_tc_fail,'_'}}, + {?eh,test_stats,{0,1,{0,0}}}, + + {?eh,tc_start,{Suite,tc_ok_to_skip}}, + {?eh,cth,{'_',post_end_per_testcase,[Suite,tc_ok_to_skip,'_',ok,[]]}}, + {?eh,tc_done,{Suite,tc_ok_to_skip,{skipped,"Test skipped"}}}, + {?eh,cth,{'_',on_tc_skip,'_'}}, + {?eh,test_stats,{0,1,{1,0}}}, + + {?eh,tc_start,{Suite,tc_fail_to_ok}}, + {?eh,cth,{'_',post_end_per_testcase, + [Suite,tc_fail_to_ok,'_', + {error,{test_case_failed,"should be changed to ok"}},[]]}}, + {?eh,tc_done,{Suite,tc_fail_to_ok,ok}}, + {?eh,test_stats,{1,1,{1,0}}}, + + {?eh,tc_start,{Suite,tc_fail_to_skip}}, + {?eh,cth,{'_',post_end_per_testcase, + [Suite,tc_fail_to_skip,'_', + {error,{test_case_failed,"should be changed to skip"}},[]]}}, + {?eh,tc_done,{Suite,tc_fail_to_skip,{skipped,"Test skipped"}}}, + {?eh,cth,{'_',on_tc_skip,'_'}}, + {?eh,test_stats,{1,1,{2,0}}}, + + {?eh,tc_start,{Suite,tc_timetrap_to_ok}}, + {?eh,cth,{'_',post_end_per_testcase, + [Suite,tc_timetrap_to_ok,'_',{timetrap_timeout,3000},[]]}}, + {?eh,tc_done,{Suite,tc_timetrap_to_ok,ok}}, + {?eh,test_stats,{2,1,{2,0}}}, + + {?eh,tc_start,{Suite,tc_timetrap_to_skip}}, + {?eh,cth,{'_',post_end_per_testcase, + [Suite,tc_timetrap_to_skip,'_',{timetrap_timeout,3000},[]]}}, + {?eh,tc_done,{Suite,tc_timetrap_to_skip,{skipped,"Test skipped"}}}, + {?eh,cth,{'_',on_tc_skip,'_'}}, + {?eh,test_stats,{2,1,{3,0}}}, + + {?eh,tc_start,{Suite,tc_skip_to_fail}}, + {?eh,cth,{'_',post_end_per_testcase, + [Suite,tc_skip_to_fail,'_', + {skip,"should be changed to fail"},[]]}}, + {?eh,tc_done,{Suite,tc_skip_to_fail,{failed,{error,"Test failure"}}}}, + {?eh,cth,{'_',on_tc_fail,'_'}}, + {?eh,test_stats,{2,2,{3,0}}}, + + {?eh,tc_start,{Suite,end_fail_to_fail}}, + {?eh,cth,{'_',post_end_per_testcase, + [Suite,end_fail_to_fail,'_', + {failed, + {Suite,end_per_testcase, + {'EXIT',{test_case_failed,"change result when end fails"}}}},[]]}}, + {?eh,tc_done,{Suite,end_fail_to_fail,{failed,{error,"Test failure"}}}}, + {?eh,cth,{'_',on_tc_fail,'_'}}, + {?eh,test_stats,{2,3,{3,0}}}, + + {?eh,tc_start,{Suite,end_fail_to_skip}}, + {?eh,cth,{'_',post_end_per_testcase, + [Suite,end_fail_to_skip,'_', + {failed, + {Suite,end_per_testcase, + {'EXIT',{test_case_failed,"change result when end fails"}}}},[]]}}, + {?eh,tc_done,{Suite,end_fail_to_skip,{skipped,"Test skipped"}}}, + {?eh,cth,{'_',on_tc_skip,'_'}}, + {?eh,test_stats,{2,3,{4,0}}}, + + {?eh,tc_start,{Suite,end_timetrap_to_fail}}, + {?eh,cth,{'_',post_end_per_testcase, + [Suite,end_timetrap_to_fail,'_', + {failed,{Suite,end_per_testcase,{timetrap_timeout,3000}}},[]]}}, + {?eh,tc_done,{Suite,end_timetrap_to_fail,{failed,{error,"Test failure"}}}}, + {?eh,cth,{'_',on_tc_fail,'_'}}, + {?eh,test_stats,{2,4,{4,0}}}, + + {?eh,tc_start,{Suite,end_timetrap_to_skip}}, + {?eh,cth,{'_',post_end_per_testcase, + [Suite,end_timetrap_to_skip,'_', + {failed,{Suite,end_per_testcase,{timetrap_timeout,3000}}},[]]}}, + {?eh,tc_done,{Suite,end_timetrap_to_skip,{skipped,"Test skipped"}}}, + {?eh,cth,{'_',on_tc_skip,'_'}}, + {?eh,test_stats,{2,4,{5,0}}}, + + {?eh,tc_start,{Suite,end_per_suite}}, + {?eh,tc_done,{Suite,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,cth,{'_',terminate,[[]]}}, + {?eh,stop_logging,[]} + ]; + test_events(options_cth) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, @@ -2204,6 +2426,229 @@ test_events(config_clash) -> %% Make sure no 'cth_error' events are received! [{negative,{?eh,cth_error,'_'},E} || E <- Events]; +test_events(alter_groups) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{empty_cth,id,[[]]}}, + {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}}, + {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE, + [{new_group,[tc1,tc2]}]]}}, + {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,[{group,new_group}], + [{new_group,[tc1,tc2]}]]}}, + {?eh,start_info,{1,1,2}}, + {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE, + [{new_group,[tc1,tc2]}]]}}, + {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,[{group,new_group}], + [{new_group,[tc1,tc2]}]]}}, + {?eh,tc_start,{all_and_groups_SUITE,{init_per_group,new_group,[]}}}, + {?eh,tc_done,{all_and_groups_SUITE, + {init_per_group,new_group,'$proplist'},ok}}, + {?eh,tc_start,{all_and_groups_SUITE,tc1}}, + {?eh,tc_done,{all_and_groups_SUITE,tc1,ok}}, + {?eh,tc_start,{all_and_groups_SUITE,tc2}}, + {?eh,tc_done,{all_and_groups_SUITE,tc2,ok}}, + {?eh,tc_start,{all_and_groups_SUITE,{end_per_group,new_group,[]}}}, + {?eh,tc_done,{all_and_groups_SUITE, + {end_per_group,new_group,'$proplist'},ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,cth,{empty_cth,terminate,[[]]}}, + {?eh,stop_logging,[]} + ]; + +test_events(alter_all) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{empty_cth,id,[[]]}}, + {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}}, + {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE, + [{test_group,[tc1]}]]}}, + {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,[tc2], + [{test_group,[tc1]}]]}}, + {?eh,start_info,{1,1,1}}, + {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,'_']}}, + {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,[tc2],'_']}}, + {?eh,tc_start,{all_and_groups_SUITE,tc2}}, + {?eh,tc_done,{all_and_groups_SUITE,tc2,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,cth,{empty_cth,terminate,[[]]}}, + {?eh,stop_logging,[]} + ]; + +test_events(alter_all_from_skip) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{empty_cth,id,[[]]}}, + {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}}, + {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE, + [{test_group,[tc1]}]]}}, + {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,[tc2], + [{test_group,[tc1]}]]}}, + {?eh,start_info,{1,1,1}}, + {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,'_']}}, + {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,[tc2],'_']}}, + {?eh,tc_start,{all_and_groups_SUITE,tc2}}, + {?eh,tc_done,{all_and_groups_SUITE,tc2,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,cth,{empty_cth,terminate,[[]]}}, + {?eh,stop_logging,[]} + ]; + +test_events(alter_all_to_skip) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{empty_cth,id,[[]]}}, + {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}}, + {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE, + [{test_group,[tc1]}]]}}, + {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE, + {skip,"skipped by post_all/3"}, + [{test_group,[tc1]}]]}}, + {?eh,start_info,{1,1,0}}, + {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,'_']}}, + {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE, + {skip,"skipped by post_all/3"}, + '_']}}, + {?eh,tc_user_skip,{all_and_groups_SUITE,all,"skipped by post_all/3"}}, + {?eh,cth,{'_',on_tc_skip,[all_and_groups_SUITE,all, + {tc_user_skip,"skipped by post_all/3"}, + []]}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,cth,{empty_cth,terminate,[[]]}}, + {?eh,stop_logging,[]} + ]; + +test_events(illegal_values_groups) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{empty_cth,id,[[]]}}, + {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}}, + {?eh,cth,{empty_cth,post_groups, + [all_and_groups_SUITE, + [{new_group,[this_test_does_not_exist]}, + this_is_not_a_group_def]]}}, + {?eh,start_info,{1,0,0}}, + {?eh,cth,{empty_cth,post_groups, + [all_and_groups_SUITE, + [{new_group,[this_test_does_not_exist]}, + this_is_not_a_group_def]]}}, + {?eh,tc_start,{ct_framework,error_in_suite}}, + {?eh,tc_done,{ct_framework,error_in_suite,{failed,{error,'_'}}}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,cth,{empty_cth,terminate,[[]]}}, + {?eh,stop_logging,[]} + ]; + +test_events(illegal_values_all) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{empty_cth,id,[[]]}}, + {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}}, + {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,'_']}}, + {?eh,cth,{empty_cth,post_all, + [all_and_groups_SUITE, + [{group,this_group_does_not_exist}, + {this_is_not_a_valid_term}],'_']}}, + {?eh,start_info,{1,0,0}}, + {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,'_']}}, + {?eh,cth,{empty_cth,post_all, + [all_and_groups_SUITE, + [{group,this_group_does_not_exist}, + {this_is_not_a_valid_term}],'_']}}, + {?eh,tc_start,{ct_framework,error_in_suite}}, + {?eh,tc_done, + {ct_framework,error_in_suite, + {failed, + {error,'Invalid reference to group this_group_does_not_exist in all_and_groups_SUITE:all/0'}}}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,cth,{empty_cth,terminate,[[]]}}, + {?eh,stop_logging,[]} + ]; + +test_events(bad_return_groups) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{empty_cth,id,[[]]}}, + {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}}, + {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,not_a_list]}}, + {?eh,start_info,{1,0,0}}, + {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,not_a_list]}}, + {?eh,tc_start,{ct_framework,error_in_suite}}, + {?eh,tc_done, + {ct_framework,error_in_suite, + {failed, + {error, + {'Bad return value from post_groups/2 hook function',not_a_list}}}}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,cth,{empty_cth,terminate,[[]]}}, + {?eh,stop_logging,[]} + ]; + +test_events(bad_return_all) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{empty_cth,id,[[]]}}, + {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}}, + {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,'_']}}, + {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,not_a_list,'_']}}, + {?eh,start_info,{1,0,0}}, + {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,'_']}}, + {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,not_a_list,'_']}}, + {?eh,tc_start,{ct_framework,error_in_suite}}, + {?eh,tc_done, + {ct_framework,error_in_suite, + {failed, + {error,{'Bad return value from post_all/3 hook function',not_a_list}}}}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,cth,{empty_cth,terminate,[[]]}}, + {?eh,stop_logging,[]} + ]; + +test_events(crash_groups) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{empty_cth,id,[[]]}}, + {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}}, + {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,crash]}}, + {?eh,start_info,{1,0,0}}, + {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,crash]}}, + {?eh,tc_start,{ct_framework,error_in_suite}}, + {?eh,tc_done,{ct_framework,error_in_suite, + {failed, + {error,"all_and_groups_cth:post_groups/2 CTH call failed"}}}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,cth,{empty_cth,terminate,[[]]}}, + {?eh,stop_logging,[]} + ]; + +test_events(crash_all) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{empty_cth,id,[[]]}}, + {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}}, + {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,'_']}}, + {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,crash,'_']}}, + {?eh,start_info,{1,0,0}}, + {?eh,cth,{empty_cth,post_groups,[all_and_groups_SUITE,'_']}}, + {?eh,cth,{empty_cth,post_all,[all_and_groups_SUITE,crash,'_']}}, + {?eh,tc_start,{ct_framework,error_in_suite}}, + {?eh,tc_done,{ct_framework,error_in_suite, + {failed, + {error,"all_and_groups_cth:post_all/3 CTH call failed"}}}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,cth,{empty_cth,terminate,[[]]}}, + {?eh,stop_logging,[]} + ]; + test_events(ok) -> ok. diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/all_and_groups_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/all_and_groups_SUITE.erl new file mode 100644 index 0000000000..adc86005f9 --- /dev/null +++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/all_and_groups_SUITE.erl @@ -0,0 +1,47 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(all_and_groups_SUITE). + +-suite_defaults([{timetrap, {minutes, 10}}]). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include("ct.hrl"). + +init_per_group(_Group,Config) -> + Config. + +end_per_group(_Group,Config) -> + ok. + +all() -> + ct:get_config(all_return,[{group,test_group}]). + +groups() -> + [{test_group,[tc1]}]. + +%% Test cases starts here. +tc1(Config) -> + ok. + +tc2(Config) -> + ok. diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/all_and_groups_cth.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/all_and_groups_cth.erl new file mode 100644 index 0000000000..9ebc00e9de --- /dev/null +++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/all_and_groups_cth.erl @@ -0,0 +1,100 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + + +-module(all_and_groups_cth). + + +-include_lib("common_test/src/ct_util.hrl"). +-include_lib("common_test/include/ct_event.hrl"). + +%% Send a cth_error event if a callback is called with unexpected arguments +-define(fail(Info), + gen_event:notify( + ?CT_EVMGR_REF, + #event{ name = cth_error, + node = node(), + data = {illegal_hook_callback,{?MODULE,?FUNCTION_NAME,Info}}})). + +%% CT Hooks +-compile(export_all). + +id(Opts) -> + empty_cth:id(Opts). + +post_groups(Suite,Groups) -> + case empty_cth:post_groups(Suite,ct:get_config(post_groups_return,Groups)) of + crash -> error(crash_in_post_groups); + R -> R + end. + +post_all(Suite,Tests,Groups) -> + case empty_cth:post_all(Suite,ct:get_config(post_all_return,Tests),Groups) of + crash -> error(crash_in_post_all); + R -> R + end. + +init(Id, Opts) -> + empty_cth:init(Id, Opts). + +pre_init_per_suite(Suite, Config, State) -> + empty_cth:pre_init_per_suite(Suite,Config,State). + +post_init_per_suite(Suite,Config,Return,State) -> + empty_cth:post_init_per_suite(Suite,Config,Return,State). + +pre_end_per_suite(Suite,Config,State) -> + empty_cth:pre_end_per_suite(Suite,Config,State). + +post_end_per_suite(Suite,Config,Return,State) -> + empty_cth:post_end_per_suite(Suite,Config,Return,State). + +pre_init_per_group(Suite,Group,Config,State) -> + empty_cth:pre_init_per_group(Suite,Group,Config,State). + +post_init_per_group(Suite,Group,Config,Return,State) -> + empty_cth:post_init_per_group(Suite,Group,Config,Return,State). + +pre_end_per_group(Suite,Group,Config,State) -> + empty_cth:pre_end_per_group(Suite,Group,Config,State). + +post_end_per_group(Suite,Group,Config,Return,State) -> + empty_cth:post_end_per_group(Suite,Group,Config,Return,State). + +pre_init_per_testcase(Suite,TC,Config,State) -> + empty_cth:pre_init_per_testcase(Suite,TC,Config,State). + +post_init_per_testcase(Suite,TC,Config,Return,State) -> + empty_cth:post_init_per_testcase(Suite,TC,Config,Return,State). + +pre_end_per_testcase(Suite,TC,Config,State) -> + empty_cth:pre_end_per_testcase(Suite,TC,Config,State). + +post_end_per_testcase(Suite,TC,Config,Return,State) -> + empty_cth:post_end_per_testcase(Suite,TC,Config,Return,State). + +on_tc_fail(Suite,TC,Reason,State) -> + empty_cth:on_tc_fail(Suite,TC,Reason,State). + +on_tc_skip(Suite,TC,Reason,State) -> + empty_cth:on_tc_skip(Suite,TC,Reason,State). + +terminate(State) -> + empty_cth:terminate(State). diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_cth_update_result_post_end_tc_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_cth_update_result_post_end_tc_SUITE.erl new file mode 100644 index 0000000000..a16138ce6f --- /dev/null +++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_cth_update_result_post_end_tc_SUITE.erl @@ -0,0 +1,101 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(ct_cth_update_result_post_end_tc_SUITE). + +-compile(export_all). + +-include("ct.hrl"). + +suite() -> + [{timetrap,{seconds,3}}]. + +init_per_suite(Config) -> + Config. + +end_per_suite(Config) -> + ok. + +init_per_group(_,Config) -> + Config. + +end_per_group(_,_) -> + ok. + +init_per_testcase(_,Config) -> + Config. + +end_per_testcase(EndTimetrap,_) when EndTimetrap==end_timetrap_to_fail; + EndTimetrap==end_timetrap_to_skip-> + timer:sleep(10000); +end_per_testcase(EndFail,_) when EndFail==end_fail_to_fail; + EndFail==end_fail_to_skip-> + ct:fail("change result when end fails"); +end_per_testcase(_,_) -> + ok. + +all() -> + [tc_ok_to_fail, + tc_ok_to_skip, + tc_fail_to_ok, + tc_fail_to_skip, + tc_timetrap_to_ok, + tc_timetrap_to_skip, + tc_skip_to_fail, + end_fail_to_fail, + end_fail_to_skip, + end_timetrap_to_fail, + end_timetrap_to_skip]. + +%% Test cases starts here. +tc_ok_to_fail(_Config) -> + ok. + +tc_ok_to_skip(_Config) -> + ok. + +tc_fail_to_ok(_Config) -> + ct:fail("should be changed to ok"). + +tc_fail_to_skip(_Config) -> + ct:fail("should be changed to skip"). + +tc_timetrap_to_ok(_Config) -> + timer:sleep(10000), % will time out after 3 sek + ok. + +tc_timetrap_to_skip(_Config) -> + timer:sleep(10000), % will time out after 3 sek + ok. + +tc_skip_to_fail(_Config) -> + {skip,"should be changed to fail"}. + +end_fail_to_fail(_Config) -> + ok. + +end_fail_to_skip(_Config) -> + ok. + +end_timetrap_to_fail(_Config) -> + ok. + +end_timetrap_to_skip(_Config) -> + ok. diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_match_state_cth.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_match_state_cth.erl new file mode 100644 index 0000000000..38c9da903d --- /dev/null +++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_match_state_cth.erl @@ -0,0 +1,58 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + + +-module(ct_match_state_cth). + + +-include_lib("common_test/src/ct_util.hrl"). +-include_lib("common_test/include/ct_event.hrl"). + +-compile(export_all). + +id(Opts) -> + empty_cth:id(Opts). + +post_groups(Suite, Groups) -> + empty_cth:post_groups(Suite, Groups). + +post_all(Suite, Tests, Groups) -> + empty_cth:post_all(Suite, Tests, Groups). + +init(Id, Opts) -> + empty_cth:init(Id, Opts), + {ok,mystate}. + +%% In the following, always match against the state value, to ensure +%% that init has indeed been called before the rest of the hooks. +pre_init_per_suite(Suite,Config,mystate) -> + empty_cth:pre_init_per_suite(Suite,Config,mystate). + +post_init_per_suite(Suite,Config,Return,mystate) -> + empty_cth:post_init_per_suite(Suite,Config,Return,mystate). + +pre_end_per_suite(Suite,Config,mystate) -> + empty_cth:pre_end_per_suite(Suite,Config,mystate). + +post_end_per_suite(Suite,Config,Return,mystate) -> + empty_cth:post_end_per_suite(Suite,Config,Return,mystate). + +terminate(mystate) -> + empty_cth:terminate(mystate). diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_scope_suite_group_only_cth_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_scope_suite_group_only_cth_SUITE.erl new file mode 100644 index 0000000000..537c97d3f0 --- /dev/null +++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_scope_suite_group_only_cth_SUITE.erl @@ -0,0 +1,54 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(ct_scope_suite_group_only_cth_SUITE). + +-suite_defaults([{timetrap, {minutes, 10}}]). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include("ct.hrl"). + +%% Test server callback functions +suite() -> + [{ct_hooks,[ct_match_state_cth]}]. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, _Config) -> + ok. + +all() -> + [test_case]. + +groups() -> + [{g1,[test_case]}]. + +%% Test cases starts here. +test_case(Config) when is_list(Config) -> + ok. diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/empty_cth.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/empty_cth.erl index 961ea68d2d..324f1dc80a 100644 --- a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/empty_cth.erl +++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/empty_cth.erl @@ -39,6 +39,9 @@ -export([id/1]). -export([init/2]). +-export([post_all/3]). +-export([post_groups/2]). + -export([pre_init_per_suite/3]). -export([post_init_per_suite/4]). -export([pre_end_per_suite/3]). @@ -71,6 +74,31 @@ -record(state, { id = ?MODULE :: term()}). +%% @doc Called after groups/0. +%% you can change the return value in this function. +-spec post_groups(Suite :: atom(), Groups :: list()) -> list(). +post_groups(Suite,Groups) -> + gen_event:notify( + ?CT_EVMGR_REF, #event{ name = cth, node = node(), + data = {?MODULE, post_groups, + [Suite,Groups]}}), + ct:log("~w:post_groups(~w) called", [?MODULE,Suite]), + Groups. + +%% @doc Called after all/0. +%% you can change the return value in this function. +-spec post_all(Suite :: atom(), + Tests :: list(), + Groups :: term()) -> + list(). +post_all(Suite,Tests,Groups) -> + gen_event:notify( + ?CT_EVMGR_REF, #event{ name = cth, node = node(), + data = {?MODULE, post_all, + [Suite,Tests,Groups]}}), + ct:log("~w:post_all(~w) called", [?MODULE,Suite]), + Tests. + %% @doc Always called before any other callback function. Use this to initiate %% any common state. It should return an state for this CTH. -spec init(Id :: term(), Opts :: proplists:proplist()) -> diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/update_result_post_end_tc_cth.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/update_result_post_end_tc_cth.erl new file mode 100644 index 0000000000..7afb3d8781 --- /dev/null +++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/update_result_post_end_tc_cth.erl @@ -0,0 +1,98 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + + +-module(update_result_post_end_tc_cth). + + +-include_lib("common_test/src/ct_util.hrl"). +-include_lib("common_test/include/ct_event.hrl"). + + +%% CT Hooks +-compile(export_all). + +init(Id, Opts) -> + empty_cth:init(Id, Opts). + +pre_init_per_suite(Suite, Config, State) -> + empty_cth:pre_init_per_suite(Suite,Config,State). + +post_init_per_suite(Suite,Config,Return,State) -> + empty_cth:post_init_per_suite(Suite,Config,Return,State). + +pre_end_per_suite(Suite,Config,State) -> + empty_cth:pre_end_per_suite(Suite,Config,State). + +post_end_per_suite(Suite,Config,Return,State) -> + empty_cth:post_end_per_suite(Suite,Config,Return,State). + +pre_init_per_group(Suite,Group,Config,State) -> + empty_cth:pre_init_per_group(Suite,Group,Config,State). + +post_init_per_group(Suite,Group,Config,Return,State) -> + empty_cth:post_init_per_group(Suite,Group,Config,Return,State). + +pre_end_per_group(Suite,Group,Config,State) -> + empty_cth:pre_end_per_group(Suite,Group,Config,State). + +post_end_per_group(Suite,Group,Config,Return,State) -> + empty_cth:post_end_per_group(Suite,Group,Config,Return,State). + +pre_init_per_testcase(Suite,TC,Config,State) -> + empty_cth:pre_init_per_testcase(Suite,TC,Config,State). + +post_end_per_testcase(Suite,TC,Config,Return,State) -> + empty_cth:post_end_per_testcase(Suite,TC,Config,Return,State), + change_result(TC,Config,State). + +on_tc_fail(Suite,TC, Reason, State) -> + empty_cth:on_tc_fail(Suite,TC,Reason,State). + +on_tc_skip(Suite,TC, Reason, State) -> + empty_cth:on_tc_skip(Suite,TC,Reason,State). + +terminate(State) -> + empty_cth:terminate(State). + +%%%----------------------------------------------------------------- +%%% +change_result(tc_ok_to_fail,_Config,State) -> + {{fail, "Test failure"}, State}; +change_result(tc_ok_to_skip,_Config,State) -> + {{skip, "Test skipped"}, State}; +change_result(tc_fail_to_ok,Config,State) -> + {lists:keydelete(tc_status,1,Config),State}; +change_result(tc_fail_to_skip,Config,State) -> + {{skip,"Test skipped"},State}; +change_result(tc_timetrap_to_ok,Config,State) -> + {lists:keydelete(tc_status,1,Config),State}; +change_result(tc_timetrap_to_skip,Config,State) -> + {{skip,"Test skipped"},State}; +change_result(tc_skip_to_fail,_Config,State) -> + {{fail, "Test failure"}, State}; +change_result(end_fail_to_fail,_Config,State) -> + {{fail, "Test failure"}, State}; +change_result(end_fail_to_skip,_Config,State) -> + {{skip, "Test skipped"}, State}; +change_result(end_timetrap_to_fail,_Config,State) -> + {{fail, "Test failure"}, State}; +change_result(end_timetrap_to_skip,_Config,State) -> + {{skip, "Test skipped"}, State}. diff --git a/lib/common_test/test/ct_tc_repeat_SUITE.erl b/lib/common_test/test/ct_tc_repeat_SUITE.erl new file mode 100644 index 0000000000..433b5456fe --- /dev/null +++ b/lib/common_test/test/ct_tc_repeat_SUITE.erl @@ -0,0 +1,438 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +-module(ct_tc_repeat_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("common_test/include/ct_event.hrl"). + +-define(eh, ct_test_support_eh). + +%%-------------------------------------------------------------------- +%% TEST SERVER CALLBACK FUNCTIONS +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Description: Since Common Test starts another Test Server +%% instance, the tests need to be performed on a separate node (or +%% there will be clashes with logging processes etc). +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + DataDir = ?config(data_dir, Config), + ct_test_support:init_per_suite([{path_dirs,[DataDir]} | Config]). + +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() -> + [{timetrap,{minutes,1}}]. + +all() -> + all(suite). + +all(suite) -> + [ + repeat, + repeat_parallel_until_ok, + repeat_parallel_until_fail, + repeat_sequence_until_ok, + repeat_sequence_until_fail, + pick_one_test_from_group, + pick_one_test_from_subgroup + ]. + + +%%-------------------------------------------------------------------- +%% TEST CASES +%%-------------------------------------------------------------------- + +%%%----------------------------------------------------------------- +%%% +%% Test post_groups and post_all hook callbacks, introduced by OTP-14746 +repeat(Config) -> + ok = do_test(?FUNCTION_NAME, "tc_repeat_SUITE", [], [], Config). + +repeat_parallel_until_ok(Config) -> + {error,{{illegal_combination,{parallel,repeat_until_ok}},_}} = + do_test(?FUNCTION_NAME, "tc_repeat_SUITE", [{group,g_parallel_until_ok}], + [], Config, 1, []). + +repeat_parallel_until_fail(Config) -> + {error,{{illegal_combination,{parallel,repeat_until_fail}},_}} = + do_test(?FUNCTION_NAME, "tc_repeat_SUITE", [{group,g_parallel_until_fail}], + [], Config, 1, []). + +repeat_sequence_until_ok(Config) -> + {error,{{illegal_combination,{sequence,repeat_until_ok}},_}} = + do_test(?FUNCTION_NAME, "tc_repeat_SUITE", [{group,g_sequence_until_ok}], + [], Config, 1, []). + +repeat_sequence_until_fail(Config) -> + {error,{{illegal_combination,{sequence,repeat_until_fail}},_}} = + do_test(?FUNCTION_NAME, "tc_repeat_SUITE", [{group,g_sequence_until_fail}], + [], Config, 1, []). + +pick_one_test_from_group(Config) -> + do_test(?FUNCTION_NAME, "tc_repeat_SUITE", [{group,g_mixed},{testcase,tc2}], + [], Config, 1, []). + +pick_one_test_from_subgroup(Config) -> + do_test(?FUNCTION_NAME, "tc_repeat_SUITE", + [{group,[[g_mixed,subgroup]]},{testcase,tc2}], + [], Config, 1, []). + + +%%%----------------------------------------------------------------- +%%% HELP FUNCTIONS +%%%----------------------------------------------------------------- + +do_test(Tag, Suite, WTT, CTHs, Config) -> + do_test(Tag, Suite, WTT, CTHs, Config, 2, []). + +do_test(Tag, Suite0, WTT, CTHs, Config, EC, ExtraOpts) -> + DataDir = ?config(data_dir, Config), + Suite = filename:join([DataDir,Suite0]), + {Opts,ERPid} = + setup([{suite,Suite}|WTT]++[{ct_hooks,CTHs},{label,Tag}|ExtraOpts], + Config), + Res = ct_test_support:run(Opts, Config), + Events = ct_test_support:get_events(ERPid, Config), + %% io:format("~p~n",[Events]), + + ct_test_support:log_events(Tag, + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), + + TestEvents = events_to_check(Tag, EC), + ok = ct_test_support:verify_events(TestEvents, Events, Config), + Res. + +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}. + +reformat(Events, EH) -> + ct_test_support:reformat(Events, EH). + +gen_config(Name,KeyVals,Config) -> + PrivDir = ?config(priv_dir,Config), + File = filename:join(PrivDir,atom_to_list(Name)++".cfg"), + ok = file:write_file(File,[io_lib:format("~p.~n",[{Key,Value}]) + || {Key,Value} <- KeyVals]), + File. + +%%%----------------------------------------------------------------- +%%% TEST EVENTS +%%%----------------------------------------------------------------- +events_to_check(Test) -> + %% 2 tests (ct:run_test + script_start) is default + events_to_check(Test, 2). + +events_to_check(_, 0) -> + []; +events_to_check(Test, N) -> + test_events(Test) ++ events_to_check(Test, N-1). + +test_events(repeat) -> + S = tc_repeat_SUITE, + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,unknown}}, + + %% tc1, {repeat,2} + {?eh,tc_start,{S,tc1}}, + {?eh,tc_done,{S,tc1,ok}}, + {?eh,test_stats,{1,0,{0,0}}}, + {?eh,tc_start,{S,tc1}}, + {?eh,tc_done,{S,tc1,ok}}, + {?eh,test_stats,{2,0,{0,0}}}, + %% tc2, {repeat_until_ok,3} + {?eh,tc_start,{S,tc2}}, + {?eh,tc_done,{S,tc2,ok}}, + {?eh,test_stats,{3,0,{0,0}}}, + %% tc3, {repeat_until_ok,3} + {?eh,tc_start,{S,tc3}}, + {?eh,tc_done,{tc_repeat_SUITE,tc3, + {failed,{error,{test_case_failed,always_fail}}}}}, + {?eh,test_stats,{3,1,{0,0}}}, + {?eh,tc_start,{S,tc3}}, + {?eh,tc_done,{S,tc3,{failed,{error,{test_case_failed,always_fail}}}}}, + {?eh,test_stats,{3,2,{0,0}}}, + {?eh,tc_start,{S,tc3}}, + {?eh,tc_done,{S,tc3,{failed,{error,{test_case_failed,always_fail}}}}}, + {?eh,test_stats,{3,3,{0,0}}}, + %% tc4, {repeat_until_fail,3} + {?eh,tc_start,{S,tc4}}, + {?eh,tc_done,{S,tc4,ok}}, + {?eh,test_stats,{4,3,{0,0}}}, + {?eh,tc_start,{S,tc4}}, + {?eh,tc_done,{S,tc4,{failed,{error,{test_case_failed,second_time_fail}}}}}, + {?eh,test_stats,{4,4,{0,0}}}, + %% g, tc1, {repeat,2} + {?eh,tc_start,{S,{init_per_group,g,[]}}}, + {?eh,tc_done,{S,{init_per_group,g,[]},ok}}, + {?eh,tc_start,{S,tc1}}, + {?eh,tc_done,{S,tc1,ok}}, + {?eh,test_stats,{5,4,{0,0}}}, + {?eh,tc_start,{S,tc1}}, + {?eh,tc_done,{S,tc1,ok}}, + {?eh,test_stats,{6,4,{0,0}}}, + {?eh,tc_start,{S,{end_per_group,g,[]}}}, + {?eh,tc_done,{S,{end_per_group,g,[]},ok}}, + %% g_until_ok, tc2, {repeat_until_ok,3} + {?eh,tc_start,{S,{init_per_group,g_until_ok,[]}}}, + {?eh,tc_done,{S,{init_per_group,g_until_ok,[]},ok}}, + {?eh,tc_start,{S,tc2}}, + {?eh,tc_done,{S,tc2,ok}}, + {?eh,test_stats,{7,4,{0,0}}}, + {?eh,tc_start,{S,{end_per_group,g_until_ok,[]}}}, + {?eh,tc_done,{S,{end_per_group,g_until_ok,[]},ok}}, + %% g_until_fail, tc4, {repeat_until_fail,3} + {?eh,tc_start,{S,{init_per_group,g_until_fail,[]}}}, + {?eh,tc_done,{S,{init_per_group,g_until_fail,[]},ok}}, + {?eh,tc_start,{S,tc4}}, + {?eh,tc_done,{S,tc4,ok}}, + {?eh,test_stats,{8,4,{0,0}}}, + {?eh,tc_start,{S,tc4}}, + {?eh,tc_done,{S,tc4,{failed,{error,{test_case_failed,second_time_fail}}}}}, + {?eh,test_stats,{8,5,{0,0}}}, + {?eh,tc_start,{S,{end_per_group,g_until_fail,[]}}}, + {?eh,tc_done,{S,{end_per_group,g_until_fail,[]},ok}}, + %% g, parallel, tc1, {repeat,2} + {parallel, + [{?eh,tc_start,{S,{init_per_group,g,[parallel]}}}, + {?eh,tc_done,{S,{init_per_group,g,[parallel]},ok}}, + {?eh,tc_start,{S,tc1}}, + {?eh,tc_done,{S,tc1,ok}}, + {?eh,test_stats,{9,5,{0,0}}}, + {?eh,tc_start,{S,tc1}}, + {?eh,tc_done,{S,tc1,ok}}, + {?eh,test_stats,{10,5,{0,0}}}, + {?eh,tc_start,{S,{end_per_group,g,[parallel]}}}, + {?eh,tc_done,{S,{end_per_group,g,[parallel]},ok}}]}, + %% g, sequence, tc1, {repeat,2} + {?eh,tc_start,{S,{init_per_group,g,[sequence]}}}, + {?eh,tc_done,{S,{init_per_group,g,[sequence]},ok}}, + {?eh,tc_start,{S,tc1}}, + {?eh,tc_done,{S,tc1,ok}}, + {?eh,test_stats,{11,5,{0,0}}}, + {?eh,tc_start,{S,tc1}}, + {?eh,tc_done,{S,tc1,ok}}, + {?eh,test_stats,{12,5,{0,0}}}, + {?eh,tc_start,{S,{end_per_group,g,[sequence]}}}, + {?eh,tc_done,{S,{end_per_group,g,[sequence]},ok}}, + %% g_sequence_skip_rest, + {?eh,tc_start,{S,{init_per_group,g_mixed,[sequence]}}}, + {?eh,tc_done,{S,{init_per_group,g_mixed,[sequence]},ok}}, + {?eh,tc_start,{S,tc1}}, + {?eh,tc_done,{S,tc1,ok}}, + {?eh,test_stats,{13,5,{0,0}}}, + {?eh,tc_start,{S,tc1}}, + {?eh,tc_done,{S,tc1,ok}}, + {?eh,test_stats,{14,5,{0,0}}}, + {?eh,tc_start,{S,tc4}}, + {?eh,tc_done,{S,tc4,ok}}, + {?eh,test_stats,{15,5,{0,0}}}, + {?eh,tc_start,{S,tc4}}, + {?eh,tc_done,{S,tc4,{failed,{error,{test_case_failed,second_time_fail}}}}}, + {?eh,test_stats,{15,6,{0,0}}}, + %% ----> fail in sequence, so skip rest + {?eh,tc_auto_skip,{S,{tc4,g_mixed}, % last of current repeat tc4 + {failed,{tc_repeat_SUITE,tc4}}}}, + {?eh,test_stats,{15,6,{0,1}}}, + {?eh,tc_auto_skip,{S,{tc1,g_mixed}, % single tc1 + {failed,{tc_repeat_SUITE,tc4}}}}, + {?eh,test_stats,{15,6,{0,2}}}, + {?eh,tc_auto_skip,{S,{tc1,g}, % group g, tc1, {repeat,2} + {failed,{tc_repeat_SUITE,tc4}}}}, + {?eh,test_stats,{15,6,{0,3}}}, + {?eh,tc_auto_skip,{S,{tc1,subgroup}, % subgroup, single tc1 + {failed,{tc_repeat_SUITE,tc4}}}}, + {?eh,test_stats,{15,6,{0,4}}}, + {?eh,tc_auto_skip,{S,{tc2,subgroup}, % subgroup, tc2, {repeat,2} + {failed,{tc_repeat_SUITE,tc4}}}}, + {?eh,test_stats,{15,6,{0,5}}}, + {?eh,tc_auto_skip,{S,{tc2,g_mixed}, % tc2, {repeat,2} + {failed,{tc_repeat_SUITE,tc4}}}}, + {?eh,test_stats,{15,6,{0,6}}}, + {?eh,tc_auto_skip,{S,{tc2,g_mixed}, % single tc2 + {failed,{tc_repeat_SUITE,tc4}}}}, + {?eh,test_stats,{15,6,{0,7}}}, + {?eh,tc_auto_skip,{S,{tc1,g_mixed}, % tc1, {repeat,2} + {failed,{tc_repeat_SUITE,tc4}}}}, + {?eh,test_stats,{15,6,{0,8}}}, + {?eh,tc_auto_skip,{S,{tc1,g_mixed}, % single tc1 + {failed,{tc_repeat_SUITE,tc4}}}}, + {?eh,test_stats,{15,6,{0,9}}}, + {?eh,tc_start,{S,{end_per_group,g_mixed,'_'}}}, + {?eh,tc_done,{S,{end_per_group,g_mixed,'_'},ok}}, + %% done + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]; + +test_events(repeat_parallel_until_ok) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,tc_start,{'_',{init_per_group,g_parallel_until_ok,[parallel]}}}, + {?eh,tc_done,{'_',{init_per_group,g_parallel_until_ok,[parallel]},ok}}, + {?eh,severe_error,{{illegal_combination,{parallel,repeat_until_ok}},'_'}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]; + +test_events(repeat_parallel_until_fail) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,tc_start,{'_',{init_per_group,g_parallel_until_fail,[parallel]}}}, + {?eh,tc_done,{'_',{init_per_group,g_parallel_until_fail,[parallel]},ok}}, + {?eh,severe_error,{{illegal_combination,{parallel,repeat_until_fail}},'_'}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]; + +test_events(repeat_sequence_until_ok) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,tc_start,{'_',{init_per_group,g_sequence_until_ok,[sequence]}}}, + {?eh,tc_done,{'_',{init_per_group,g_sequence_until_ok,[sequence]},ok}}, + {?eh,severe_error,{{illegal_combination,{sequence,repeat_until_ok}},'_'}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]; + +test_events(repeat_sequence_until_fail) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,tc_start,{'_',{init_per_group,g_sequence_until_fail,[sequence]}}}, + {?eh,tc_done,{'_',{init_per_group,g_sequence_until_fail,[sequence]},ok}}, + {?eh,severe_error,{{illegal_combination,{sequence,repeat_until_fail}},'_'}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]; + +test_events(pick_one_test_from_group) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,tc_start,{'_',{init_per_group,g_mixed,[]}}}, + {?eh,tc_done,{'_',{init_per_group,g_mixed,[]},ok}}, + {negative, + {?eh,tc_start,{'_',tc1}}, + {?eh,tc_start,{'_',tc2}}}, % single tc2 + {?eh,tc_done,{'_',tc2,ok}}, + {?eh,tc_start,{'_',tc2}}, % tc2, {repeat,2} + {?eh,tc_done,{'_',tc2,ok}}, + {?eh,tc_start,{'_',tc2}}, + {?eh,tc_done,{'_',tc2,ok}}, + {negative, + {?eh,tc_start,{'_',tc1}}, + {?eh,tc_start,{'_',{end_per_group,g_mixed,[]}}}}, + {?eh,tc_done,{'_',{end_per_group,g_mixed,[]},ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]; + +test_events(pick_one_test_from_subgroup) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,tc_start,{'_',{init_per_group,g_mixed,[]}}}, + {?eh,tc_done,{'_',{init_per_group,g_mixed,[]},ok}}, + {negative, + {?eh,tc_start,{'_',tc2}}, + {?eh,tc_start,{'_',{init_per_group,subgroup,[]}}}}, + {?eh,tc_done,{'_',{init_per_group,subgroup,[]},ok}}, + {negative, + {?eh,tc_start,{'_',tc1}}, + {?eh,tc_start,{'_',tc2}}}, % tc2, {repeat,2} + {?eh,tc_done,{'_',tc2,ok}}, + {?eh,tc_start,{'_',tc2}}, + {?eh,tc_done,{'_',tc2,ok}}, + {negative, + {?eh,tc_start,{'_',tc1}}, + {?eh,tc_start,{'_',{end_per_group,subgroup,[]}}}}, + {?eh,tc_done,{'_',{end_per_group,subgroup,[]},ok}}, + {negative, + {?eh,tc_start,{'_',tc2}}, + {?eh,tc_start,{'_',{end_per_group,g_mixed,[]}}}}, + {?eh,tc_done,{'_',{end_per_group,g_mixed,[]},ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]; + +test_events(ok) -> + ok. + +%% test events help functions +contains(List) -> + fun(Proplist) when is_list(Proplist) -> + contains(List,Proplist) + end. + +contains([{not_in_order,List}|T],Rest) -> + contains_parallel(List,Rest), + contains(T,Rest); +contains([{Ele,Pos}|T] = L,[H|T2]) -> + case element(Pos,H) of + Ele -> + contains(T,T2); + _ -> + contains(L,T2) + end; +contains([Ele|T],[{Ele,_}|T2])-> + contains(T,T2); +contains([Ele|T],[Ele|T2])-> + contains(T,T2); +contains(List,[_|T]) -> + contains(List,T); +contains([],_) -> + match. + +contains_parallel([Key | T], Elems) -> + contains([Key],Elems), + contains_parallel(T,Elems); +contains_parallel([],_Elems) -> + match. + +not_contains(List) -> + fun(Proplist) when is_list(Proplist) -> + [] = [Ele || {Ele,_} <- Proplist, + Test <- List, + Test =:= Ele] + end. diff --git a/lib/common_test/test/ct_tc_repeat_SUITE_data/tc_repeat_SUITE.erl b/lib/common_test/test/ct_tc_repeat_SUITE_data/tc_repeat_SUITE.erl new file mode 100644 index 0000000000..f5d960d12f --- /dev/null +++ b/lib/common_test/test/ct_tc_repeat_SUITE_data/tc_repeat_SUITE.erl @@ -0,0 +1,85 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(tc_repeat_SUITE). + +-suite_defaults([{timetrap, {minutes, 10}}]). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include("ct.hrl"). + +init_per_group(_Group,Config) -> + Config. + +end_per_group(_Group,Config) -> + ok. + +all() -> + [{testcase,tc1,[{repeat,2}]}, + {testcase,tc2,[{repeat_until_ok,3}]}, + {testcase,tc3,[{repeat_until_ok,3}]}, + {testcase,tc4,[{repeat_until_fail,3}]}, + {group,g}, + {group,g_until_ok}, + {group,g_until_fail}, + {group,g,[parallel]}, + {group,g,[sequence]}, + {group,g_mixed,[sequence]}]. + +groups() -> + [{g,[{testcase,tc1,[{repeat,2}]}]}, + {g_until_ok,[{testcase,tc2,[{repeat_until_ok,3}]}]}, + {g_until_fail,[{testcase,tc4,[{repeat_until_fail,3}]}]}, + {g_parallel_until_ok,[parallel],[{testcase,tc2,[{repeat_until_ok,3}]}]}, + {g_parallel_until_fail,[parallel],[{testcase,tc1,[{repeat_until_fail,2}]}]}, + {g_sequence_until_ok,[sequence],[{testcase,tc2,[{repeat_until_ok,3}]}]}, + {g_sequence_until_fail,[sequence],[{testcase,tc1,[{repeat_until_fail,2}]}]}, + {g_mixed,[{testcase,tc1,[{repeat,2}]}, + {testcase,tc4,[{repeat,3}]}, + tc1, + {group,g}, + {subgroup,[tc1,{testcase,tc2,[{repeat,2}]}]}, + {testcase,tc2,[{repeat,2}]}, + tc2, + {testcase,tc1,[{repeat,2}]}, + tc1]}]. + +%% Test cases starts here. +tc1(_Config) -> + ok. + +tc2(_Config) -> + ok. + +tc3(_Config) -> + ct:fail(always_fail). + +tc4(Config) -> + case ?config(saved_config,Config) of + {tc4,_} -> + ct:fail(second_time_fail); + undefined -> + {save_config,Config} + end. + +tc5(_Config) -> + {skip,"just skip this"}. diff --git a/lib/common_test/test/ct_util_SUITE.erl b/lib/common_test/test/ct_util_SUITE.erl new file mode 100644 index 0000000000..1d773855da --- /dev/null +++ b/lib/common_test/test/ct_util_SUITE.erl @@ -0,0 +1,490 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +-module(ct_util_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("common_test/include/ct_event.hrl"). + +-define(eh, ct_test_support_eh). + +%%-------------------------------------------------------------------- +%% TEST SERVER CALLBACK FUNCTIONS +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Description: Since Common Test starts another Test Server +%% instance, the tests need to be performed on a separate node (or +%% there will be clashes with logging processes etc). +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + DataDir = ?config(data_dir, Config), + CTHs = filelib:wildcard(filename:join(DataDir,"*_cth.erl")), + io:format("CTHs: ~p",[CTHs]), + [io:format("Compiling ~p: ~p", + [FileName,compile:file(FileName,[{outdir,DataDir},debug_info])]) || + FileName <- CTHs], + ct_test_support:init_per_suite([{path_dirs,[DataDir]} | Config]). + +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() -> + [{timetrap,{minutes,1}}]. + +all() -> + all(suite). + +all(suite) -> + [ + pre_init_per_suite, + post_init_per_suite, + pre_end_per_suite, + post_end_per_suite, + pre_init_per_group, + post_init_per_group, + pre_end_per_group, + post_end_per_group, + pre_init_per_testcase, + post_init_per_testcase, + pre_end_per_testcase, + post_end_per_testcase + ]. + + +%%-------------------------------------------------------------------- +%% TEST CASES +%%-------------------------------------------------------------------- + +%%%----------------------------------------------------------------- +%%% +pre_init_per_suite(Config) -> + CfgFile = gen_config(?FUNCTION_NAME, + [{pre_init_per_suite, + {curr_tc_SUITE,kill}}], + Config), + ok = do_test(?FUNCTION_NAME, + "curr_tc_SUITE.erl", + [{ct_hooks,[ct_util_cth]},{config,CfgFile}], + Config). + +post_init_per_suite(Config) -> + CfgFile = gen_config(?FUNCTION_NAME, + [{post_init_per_suite, + {curr_tc_SUITE,kill}}], + Config), + ok = do_test(?FUNCTION_NAME, + "curr_tc_SUITE.erl", + [{ct_hooks,[ct_util_cth]},{config,CfgFile}], + Config). + +pre_end_per_suite(Config) -> + CfgFile = gen_config(?FUNCTION_NAME, + [{pre_end_per_suite, + {curr_tc_SUITE,kill}}], + Config), + ok = do_test(?FUNCTION_NAME, + "curr_tc_SUITE.erl", + [{ct_hooks,[ct_util_cth]},{config,CfgFile}], + Config). + +post_end_per_suite(Config) -> + CfgFile = gen_config(?FUNCTION_NAME, + [{post_end_per_suite, + {curr_tc_SUITE,kill}}], + Config), + ok = do_test(?FUNCTION_NAME, + "curr_tc_SUITE.erl", + [{ct_hooks,[ct_util_cth]},{config,CfgFile}], + Config). + + +pre_init_per_group(Config) -> + CfgFile = gen_config(?FUNCTION_NAME, + [{pre_init_per_group, + {curr_tc_SUITE,g,kill}}], + Config), + ok = do_test(?FUNCTION_NAME, + "curr_tc_SUITE.erl", + [{ct_hooks,[ct_util_cth]},{config,CfgFile}], + Config). + +post_init_per_group(Config) -> + CfgFile = gen_config(?FUNCTION_NAME, + [{post_init_per_group, + {curr_tc_SUITE,g,kill}}], + Config), + ok = do_test(?FUNCTION_NAME, + "curr_tc_SUITE.erl", + [{ct_hooks,[ct_util_cth]},{config,CfgFile}], + Config). + +pre_end_per_group(Config) -> + CfgFile = gen_config(?FUNCTION_NAME, + [{pre_end_per_group, + {curr_tc_SUITE,g,kill}}], + Config), + ok = do_test(?FUNCTION_NAME, + "curr_tc_SUITE.erl", + [{ct_hooks,[ct_util_cth]},{config,CfgFile}], + Config). + +post_end_per_group(Config) -> + CfgFile = gen_config(?FUNCTION_NAME, + [{post_end_per_group, + {curr_tc_SUITE,g,kill}}], + Config), + ok = do_test(?FUNCTION_NAME, + "curr_tc_SUITE.erl", + [{ct_hooks,[ct_util_cth]},{config,CfgFile}], + Config). + +pre_init_per_testcase(Config) -> + CfgFile = gen_config(?FUNCTION_NAME, + [{pre_init_per_testcase, + {curr_tc_SUITE,tc1,kill}}], + Config), + ok = do_test(?FUNCTION_NAME, + "curr_tc_SUITE.erl", + [{ct_hooks,[ct_util_cth]},{config,CfgFile}], + Config). + +post_init_per_testcase(Config) -> + CfgFile = gen_config(?FUNCTION_NAME, + [{post_init_per_testcase, + {curr_tc_SUITE,tc1,{timeout,5000}}}], + Config), + ok = do_test(?FUNCTION_NAME, + "curr_tc_SUITE.erl", + [{ct_hooks,[ct_util_cth]},{config,CfgFile}], + Config). + +pre_end_per_testcase(Config) -> + CfgFile = gen_config(?FUNCTION_NAME, + [{pre_end_per_testcase, + {curr_tc_SUITE,tc1,{timeout,5000}}}], + Config), + ok = do_test(?FUNCTION_NAME, + "curr_tc_SUITE.erl", + [{ct_hooks,[ct_util_cth]},{config,CfgFile}], + Config). + +post_end_per_testcase(Config) -> + CfgFile = gen_config(?FUNCTION_NAME, + [{post_end_per_testcase, + {curr_tc_SUITE,tc1,kill}}], + Config), + ok = do_test(?FUNCTION_NAME, + "curr_tc_SUITE.erl", + [{ct_hooks,[ct_util_cth]},{config,CfgFile}], + Config). + +%%%----------------------------------------------------------------- +%%% HELP FUNCTIONS +%%%----------------------------------------------------------------- + +do_test(Tag, Suite, RunTestArgs, Config) -> + do_test(Tag, Suite, RunTestArgs, Config, 2). + +do_test(Tag, Suite0, RunTestArgs, Config, EC) -> + DataDir = ?config(data_dir, Config), + Suite = filename:join([DataDir,Suite0]), + {Opts,ERPid} = setup([{suite,Suite}]++[{label,Tag}|RunTestArgs],Config), + Res = ct_test_support:run(Opts, Config), + Events = ct_test_support:get_events(ERPid, Config), + %% io:format("~p~n",[Events]), + + ct_test_support:log_events(Tag, + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), + + TestEvents = events_to_check(Tag, EC), + ok = ct_test_support:verify_events(TestEvents, Events, Config), + Res. + +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}. + +reformat(Events, EH) -> + ct_test_support:reformat(Events, EH). + +gen_config(Name,KeyVals,Config) -> + PrivDir = ?config(priv_dir,Config), + File = filename:join(PrivDir,atom_to_list(Name)++".cfg"), + ok = file:write_file(File,[io_lib:format("~p.~n",[{Key,Value}]) + || {Key,Value} <- KeyVals]), + File. + +%%%----------------------------------------------------------------- +%%% TEST EVENTS +%%%----------------------------------------------------------------- +events_to_check(Test) -> + %% 2 tests (ct:run_test + script_start) is default + events_to_check(Test, 2). + +events_to_check(_, 0) -> + []; +events_to_check(Test, N) -> + test_events(Test) ++ events_to_check(Test, N-1). + +test_events(IPS) when IPS=:=pre_init_per_suite; IPS=:=post_init_per_suite -> + S = curr_tc_SUITE, + FwFunc = + case IPS of + pre_init_per_suite -> init_tc; + post_init_per_suite -> end_tc + end, + E = {failed,{ct_framework,FwFunc,{test_case_failed,hahahahahah}}}, + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,4}}, + {?eh,tc_start,{S,init_per_suite}}, + {?eh,tc_done,{S,init_per_suite,E}}, + {?eh,tc_auto_skip,{S,tc1,{failed,{S,init_per_suite,E}}}}, + {?eh,tc_auto_skip,{S,tc2,{failed,{S,init_per_suite,E}}}}, + {?eh,tc_auto_skip,{S,{tc1,g},{failed,{S,init_per_suite,E}}}}, + {?eh,tc_auto_skip,{S,{tc2,g},{failed,{S,init_per_suite,E}}}}, + {?eh,test_stats,{0,0,{0,4}}}, + {?eh,tc_auto_skip,{S,end_per_suite,{failed,{S,init_per_suite,E}}}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]; + +test_events(EPS) when EPS=:=pre_end_per_suite; EPS=:=post_end_per_suite -> + S = curr_tc_SUITE, + FwFunc = + case EPS of + pre_end_per_suite -> init_tc; + post_end_per_suite -> end_tc + end, + E = {failed,{ct_framework,FwFunc,{test_case_failed,hahahahahah}}}, + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,4}}, + {?eh,tc_start,{S,init_per_suite}}, + {?eh,tc_done,{S,init_per_suite,ok}}, + {?eh,tc_start,{S,tc1}}, + {?eh,tc_done,{S,tc1,ok}}, + {?eh,test_stats,{1,0,{0,0}}}, + {?eh,tc_start,{S,tc2}}, + {?eh,tc_done,{S,tc2,ok}}, + {?eh,test_stats,{2,0,{0,0}}}, + [{?eh,tc_start,{S,{init_per_group,g,[]}}}, + {?eh,tc_done,{S,{init_per_group,g,[]},ok}}, + {?eh,tc_start,{S,tc1}}, + {?eh,tc_done,{S,tc1,ok}}, + {?eh,test_stats,{3,0,{0,0}}}, + {?eh,tc_start,{S,tc2}}, + {?eh,tc_done,{S,tc2,ok}}, + {?eh,test_stats,{4,0,{0,0}}}, + {?eh,tc_start,{S,{end_per_group,g,[]}}}, + {?eh,tc_done,{S,{end_per_group,g,[]},ok}}], + {?eh,tc_start,{S,end_per_suite}}, + {?eh,tc_done,{S,end_per_suite,E}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]; + +test_events(IPG) when IPG=:=pre_init_per_group; IPG=:=post_init_per_group -> + S = curr_tc_SUITE, + FwFunc = + case IPG of + pre_init_per_group -> init_tc; + post_init_per_group -> end_tc + end, + E = {failed,{ct_framework,FwFunc,{test_case_failed,hahahahahah}}}, + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,4}}, + {?eh,tc_start,{S,init_per_suite}}, + {?eh,tc_done,{S,init_per_suite,ok}}, + {?eh,tc_start,{S,tc1}}, + {?eh,tc_done,{S,tc1,ok}}, + {?eh,test_stats,{1,0,{0,0}}}, + {?eh,tc_start,{S,tc2}}, + {?eh,tc_done,{S,tc2,ok}}, + {?eh,test_stats,{2,0,{0,0}}}, + [{?eh,tc_start,{S,{init_per_group,g,[]}}}, + {?eh,tc_done,{S,{init_per_group,g,[]},E}}, + {?eh,tc_auto_skip,{S,{tc1,g},{failed,{S,init_per_group,E}}}}, + {?eh,tc_auto_skip,{S,{tc2,g},{failed,{S,init_per_group,E}}}}, + {?eh,test_stats,{2,0,{0,2}}}, + {?eh,tc_auto_skip,{S,{end_per_group,g},{failed,{S,init_per_group,E}}}}], + {?eh,tc_start,{S,end_per_suite}}, + {?eh,tc_done,{S,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]; + +test_events(EPG) when EPG=:=pre_end_per_group; EPG=:=post_end_per_group -> + S = curr_tc_SUITE, + FwFunc = + case EPG of + pre_end_per_group -> init_tc; + post_end_per_group -> end_tc + end, + E = {failed,{ct_framework,FwFunc,{test_case_failed,hahahahahah}}}, + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,4}}, + {?eh,tc_start,{S,init_per_suite}}, + {?eh,tc_done,{S,init_per_suite,ok}}, + {?eh,tc_start,{S,tc1}}, + {?eh,tc_done,{S,tc1,ok}}, + {?eh,test_stats,{1,0,{0,0}}}, + {?eh,tc_start,{S,tc2}}, + {?eh,tc_done,{S,tc2,ok}}, + {?eh,test_stats,{2,0,{0,0}}}, + [{?eh,tc_start,{S,{init_per_group,g,[]}}}, + {?eh,tc_done,{S,{init_per_group,g,[]},ok}}, + {?eh,tc_start,{S,tc1}}, + {?eh,tc_done,{S,tc1,ok}}, + {?eh,test_stats,{3,0,{0,0}}}, + {?eh,tc_start,{S,tc2}}, + {?eh,tc_done,{S,tc2,ok}}, + {?eh,test_stats,{4,0,{0,0}}}, + {?eh,tc_start,{S,{end_per_group,g,[]}}}, + {?eh,tc_done,{S,{end_per_group,g,[]},E}}], + {?eh,tc_start,{S,end_per_suite}}, + {?eh,tc_done,{S,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]; + +test_events(IPTC) when IPTC=:=pre_init_per_testcase; + IPTC=:=post_init_per_testcase -> + S = curr_tc_SUITE, + E = case IPTC of + pre_init_per_testcase -> + {failed,{ct_framework,init_tc,{test_case_failed,hahahahahah}}}; + post_init_per_testcase -> + {failed,{ct_framework,end_tc,{timetrap,3000}}} + end, + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,4}}, + {?eh,tc_start,{S,init_per_suite}}, + {?eh,tc_done,{S,init_per_suite,ok}}, + {?eh,tc_start,{S,tc1}}, + {?eh,tc_done,{S,tc1,{auto_skipped,E}}}, + {?eh,test_stats,{0,0,{0,1}}}, + {?eh,tc_start,{S,tc2}}, + {?eh,tc_done,{S,tc2,ok}}, + {?eh,test_stats,{1,0,{0,1}}}, + [{?eh,tc_start,{S,{init_per_group,g,[]}}}, + {?eh,tc_done,{S,{init_per_group,g,[]},ok}}, + {?eh,tc_start,{S,tc1}}, + {?eh,tc_done,{S,tc1,{auto_skipped,E}}}, + {?eh,test_stats,{1,0,{0,2}}}, + {?eh,tc_start,{S,tc2}}, + {?eh,tc_done,{S,tc2,ok}}, + {?eh,test_stats,{2,0,{0,2}}}, + {?eh,tc_start,{S,{end_per_group,g,[]}}}, + {?eh,tc_done,{S,{end_per_group,g,[]},ok}}], + {?eh,tc_start,{S,end_per_suite}}, + {?eh,tc_done,{S,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]; + +test_events(EPTC) when EPTC=:=pre_end_per_testcase; EPTC=:=post_end_per_testcase-> + S = curr_tc_SUITE, + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,4}}, + {?eh,tc_start,{S,tc1}}, + {?eh,tc_done,{S,tc1,ok}}, + {?eh,test_stats,{1,0,{0,0}}}, + {?eh,tc_start,{S,tc2}}, + {?eh,tc_done,{S,tc2,ok}}, + {?eh,test_stats,{2,0,{0,0}}}, + [{?eh,tc_start,{S,{init_per_group,g,[]}}}, + {?eh,tc_done,{S,{init_per_group,g,[]},ok}}, + {?eh,tc_start,{S,tc1}}, + {?eh,tc_done,{S,tc1,ok}}, + {?eh,test_stats,{3,0,{0,0}}}, + {?eh,tc_start,{S,tc2}}, + {?eh,tc_done,{S,tc2,ok}}, + {?eh,test_stats,{4,0,{0,0}}}, + {?eh,tc_start,{S,{end_per_group,g,[]}}}, + {?eh,tc_done,{S,{end_per_group,g,[]},ok}}], + {?eh,tc_start,{S,end_per_suite}}, + {?eh,tc_done,{S,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]. + +%% test events help functions +contains(List) -> + fun(Proplist) when is_list(Proplist) -> + contains(List,Proplist) + end. + +contains([{not_in_order,List}|T],Rest) -> + contains_parallel(List,Rest), + contains(T,Rest); +contains([{Ele,Pos}|T] = L,[H|T2]) -> + case element(Pos,H) of + Ele -> + contains(T,T2); + _ -> + contains(L,T2) + end; +contains([Ele|T],[{Ele,_}|T2])-> + contains(T,T2); +contains([Ele|T],[Ele|T2])-> + contains(T,T2); +contains(List,[_|T]) -> + contains(List,T); +contains([],_) -> + match. + +contains_parallel([Key | T], Elems) -> + contains([Key],Elems), + contains_parallel(T,Elems); +contains_parallel([],_Elems) -> + match. + +not_contains(List) -> + fun(Proplist) when is_list(Proplist) -> + [] = [Ele || {Ele,_} <- Proplist, + Test <- List, + Test =:= Ele] + end. diff --git a/lib/common_test/test/ct_util_SUITE_data/ct_util_cth.erl b/lib/common_test/test/ct_util_SUITE_data/ct_util_cth.erl new file mode 100644 index 0000000000..34c1568a87 --- /dev/null +++ b/lib/common_test/test/ct_util_SUITE_data/ct_util_cth.erl @@ -0,0 +1,105 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + + +-module(ct_util_cth). + + +-include_lib("common_test/src/ct_util.hrl"). +-include_lib("common_test/include/ct_event.hrl"). + +%% Send a cth_error event if a callback is called with unexpected arguments + +%% CT Hooks +-compile(export_all). + +id(Opts) -> + erlang:system_time(second). + +init(Id, Opts) -> + {ok,ok}. + +pre_init_per_suite(Suite,Config,State) -> + maybe_sleep(?FUNCTION_NAME,Suite), + {Config, State}. + +post_init_per_suite(Suite,Config,Return,State) -> + maybe_sleep(?FUNCTION_NAME,Suite), + {Return, State}. + +pre_end_per_suite(Suite,Config,State) -> + maybe_sleep(?FUNCTION_NAME,Suite), + {Config, State}. + +post_end_per_suite(Suite,Config,Return,State) -> + maybe_sleep(?FUNCTION_NAME,Suite), + {Return, State}. + +pre_init_per_group(Suite, Group, Config, State) -> + maybe_sleep(?FUNCTION_NAME,Suite,Group), + {Config,State}. + +post_init_per_group(Suite, Group, Config,Return,State) -> + maybe_sleep(?FUNCTION_NAME,Suite,Group), + {Return,State}. + +pre_end_per_group(Suite, Group, Config, State) -> + maybe_sleep(?FUNCTION_NAME,Suite,Group), + {Config,State}. + +post_end_per_group(Suite, Group, Config,Return,State) -> + maybe_sleep(?FUNCTION_NAME,Suite,Group), + {Return,State}. + +pre_init_per_testcase(Suite, TC, Config, State) -> + maybe_sleep(?FUNCTION_NAME,Suite,TC), + {Config,State}. + +post_init_per_testcase(Suite, TC, Config,Return,State) -> + maybe_sleep(?FUNCTION_NAME,Suite,TC), + {Return,State}. + +pre_end_per_testcase(Suite, TC, Config, State) -> + maybe_sleep(?FUNCTION_NAME,Suite,TC), + {Config,State}. + +post_end_per_testcase(Suite, TC, Config,Return,State) -> + maybe_sleep(?FUNCTION_NAME,Suite,TC), + {Return,State}. + +%%%----------------------------------------------------------------- +maybe_sleep(FuncName,Suite) -> + maybe_sleep(FuncName,Suite,undefined). +maybe_sleep(FuncName,Suite,GroupOrTC) -> + case ct:get_config(FuncName) of + {Suite,GroupOrTC,Fail} -> + fail(Fail); + {Suite,Fail} when GroupOrTC=:=undefined -> + fail(Fail); + _ -> + ok + end. + +fail({timeout,T}) -> + timer:sleep(T); +fail(kill) -> + spawn_link(fun() -> ct:fail(hahahahahah) end), + timer:sleep(10000). + diff --git a/lib/common_test/test/ct_util_SUITE_data/curr_tc_SUITE.erl b/lib/common_test/test/ct_util_SUITE_data/curr_tc_SUITE.erl new file mode 100644 index 0000000000..b48ba4d24e --- /dev/null +++ b/lib/common_test/test/ct_util_SUITE_data/curr_tc_SUITE.erl @@ -0,0 +1,59 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(curr_tc_SUITE). + +-suite_defaults([{timetrap, {seconds, 3}}]). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include("ct.hrl"). + +init_per_suite(Config) -> + [{?MODULE,?FUNCTION_NAME}] = ct_util:get_testdata(curr_tc), + Config. + +end_per_suite(Config) -> + [{?MODULE,?FUNCTION_NAME}] = ct_util:get_testdata(curr_tc), + ok. + +init_per_group(_Group,Config) -> + [{?MODULE,?FUNCTION_NAME}] = ct_util:get_testdata(curr_tc), + Config. + +end_per_group(_Group,Config) -> + [{?MODULE,?FUNCTION_NAME}] = ct_util:get_testdata(curr_tc), + ok. + +all() -> + [tc1,tc2,{group,g}]. + +groups() -> + [{g,[tc1,tc2]}]. + +%% Test cases starts here. +tc1(_Config) -> + [{?MODULE,?FUNCTION_NAME}] = ct_util:get_testdata(curr_tc), + ok. + +tc2(_Config) -> + [{?MODULE,?FUNCTION_NAME}] = ct_util:get_testdata(curr_tc), + ok. diff --git a/lib/common_test/vsn.mk b/lib/common_test/vsn.mk index ea3e9871cb..c8dfa6beb2 100644 --- a/lib/common_test/vsn.mk +++ b/lib/common_test/vsn.mk @@ -1 +1 @@ -COMMON_TEST_VSN = 1.15.4 +COMMON_TEST_VSN = 1.15.4.1 diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml index bc1f68337b..6aa662b79a 100644 --- a/lib/compiler/doc/src/notes.xml +++ b/lib/compiler/doc/src/notes.xml @@ -32,6 +32,39 @@ <p>This document describes the changes made to the Compiler application.</p> +<section><title>Compiler 7.1.5.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>Fix a regression in OTP-15204 that removed + <c>.beam</c> file metadata that some external build tools + relied on.</p> + <p> + Own Id: OTP-15292</p> + </item> + </list> + </section> + +</section> + +<section><title>Compiler 7.1.5.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>Fixed an issue where files compiled with the + <c>+deterministic</c> option differed if they were + compiled in a different directory but were otherwise + identical.</p> + <p> + Own Id: OTP-15204 Aux Id: ERL-679 </p> + </item> + </list> + </section> + +</section> + <section><title>Compiler 7.1.5</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index 50b0ba76f8..bdb52a02ad 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -1408,16 +1408,30 @@ encrypt_debug_info(DebugInfo, Key, Opts) -> end. cleanup_compile_options(Opts) -> - lists:filter(fun keep_compile_option/1, Opts). - + IsDeterministic = lists:member(deterministic, Opts), + lists:filter(fun(Opt) -> + keep_compile_option(Opt, IsDeterministic) + end, Opts). + +%% Include paths and current directory don't affect compilation, but they might +%% be helpful so we include them unless we're doing a deterministic build. +keep_compile_option({i, _}, Deterministic) -> + not Deterministic; +keep_compile_option({cwd, _}, Deterministic) -> + not Deterministic; %% We are storing abstract, not asm or core. -keep_compile_option(from_asm) -> false; -keep_compile_option(from_core) -> false; +keep_compile_option(from_asm, _Deterministic) -> + false; +keep_compile_option(from_core, _Deterministic) -> + false; %% Parse transform and macros have already been applied. -keep_compile_option({parse_transform, _}) -> false; -keep_compile_option({d, _, _}) -> false; +keep_compile_option({parse_transform, _}, _Deterministic) -> + false; +keep_compile_option({d, _, _}, _Deterministic) -> + false; %% Do not affect compilation result on future calls. -keep_compile_option(Option) -> effects_code_generation(Option). +keep_compile_option(Option, _Deterministic) -> + effects_code_generation(Option). start_crypto() -> try crypto:start() of diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl index 96897d612d..c455afcb48 100644 --- a/lib/compiler/test/compile_SUITE.erl +++ b/lib/compiler/test/compile_SUITE.erl @@ -36,7 +36,7 @@ core_roundtrip/1, asm/1, optimized_guards/1, sys_pre_attributes/1, dialyzer/1, warnings/1, pre_load_check/1, env_compiler_options/1, - bc_options/1 + bc_options/1, deterministic_include/1 ]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -53,7 +53,8 @@ all() -> strict_record, utf8_atoms, utf8_functions, extra_chunks, cover, env, core_pp, core_roundtrip, asm, optimized_guards, sys_pre_attributes, dialyzer, warnings, pre_load_check, - env_compiler_options, custom_debug_info, bc_options]. + env_compiler_options, custom_debug_info, bc_options, + deterministic_include]. groups() -> []. @@ -1445,6 +1446,23 @@ highest_opcode(DataDir, Mod, Opt) -> <<16:32,0:32,HighestOpcode:32,_/binary>> = Code, HighestOpcode. +deterministic_include(Config) when is_list(Config) -> + DataDir = proplists:get_value(data_dir, Config), + Simple = filename:join(DataDir, "simple"), + + %% Files without +deterministic should differ if their include paths do, + %% as their debug info will be different. + {ok,_,NonDetA} = compile:file(Simple, [binary, {i,"gurka"}]), + {ok,_,NonDetB} = compile:file(Simple, [binary, {i,"gaffel"}]), + true = NonDetA =/= NonDetB, + + %% ... but files with +deterministic shouldn't. + {ok,_,DetC} = compile:file(Simple, [binary, deterministic, {i,"gurka"}]), + {ok,_,DetD} = compile:file(Simple, [binary, deterministic, {i,"gaffel"}]), + true = DetC =:= DetD, + + ok. + %%% %%% Utilities. %%% diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk index ee75ee27fd..c9036d0178 100644 --- a/lib/compiler/vsn.mk +++ b/lib/compiler/vsn.mk @@ -1 +1 @@ -COMPILER_VSN = 7.1.5 +COMPILER_VSN = 7.1.5.2 diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c index 94a207cba6..6dd263adb2 100644 --- a/lib/crypto/c_src/crypto.c +++ b/lib/crypto/c_src/crypto.c @@ -1,7 +1,7 @@ /* * %CopyrightBegin% * - * Copyright Ericsson AB 2010-2017. All Rights Reserved. + * Copyright Ericsson AB 2010-2018. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -527,6 +527,7 @@ static ERL_NIF_TERM engine_remove_nif(ErlNifEnv* env, int argc, const ERL_NIF_TE static ERL_NIF_TERM engine_get_first_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM engine_get_next_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM engine_get_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM engine_get_name_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM engine_get_all_methods_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); /* helpers */ @@ -613,6 +614,7 @@ static ErlNifFunc nif_funcs[] = { {"engine_get_first_nif", 0, engine_get_first_nif}, {"engine_get_next_nif", 1, engine_get_next_nif}, {"engine_get_id_nif", 1, engine_get_id_nif}, + {"engine_get_name_nif", 1, engine_get_name_nif}, {"engine_get_all_methods_nif", 0, engine_get_all_methods_nif} }; @@ -1016,7 +1018,7 @@ static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info) */ return 0; } -#endif +#endif atom_true = enif_make_atom(env,"true"); atom_false = enif_make_atom(env,"false"); @@ -3757,7 +3759,7 @@ static ERL_NIF_TERM ecdh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF int i; EC_GROUP *group; const BIGNUM *priv_key; - EC_POINT *my_ecpoint; + EC_POINT *my_ecpoint = NULL; EC_KEY *other_ecdh = NULL; if (!get_ec_key(env, argv[1], argv[2], atom_undefined, &key)) @@ -4727,7 +4729,7 @@ static ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM } else { /* non-evp rsa private decrypt */ i = RSA_private_decrypt(in_bin.size, in_bin.data, - out_bin.data, rsa, crypt_opt.rsa_padding); + out_bin.data, rsa, crypt_opt.rsa_padding); if (i > 0) { ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i); enif_realloc_binary(&out_bin, i); @@ -4745,7 +4747,7 @@ static ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM } else { /* non-evp rsa public decrypt */ i = RSA_public_decrypt(in_bin.size, in_bin.data, - out_bin.data, rsa, crypt_opt.rsa_padding); + out_bin.data, rsa, crypt_opt.rsa_padding); if (i > 0) { ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i); enif_realloc_binary(&out_bin, i); @@ -4863,7 +4865,7 @@ static ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NI / * Example of result: { Curve = {Field, Prime, Point, Order, CoFactor} = - { + { Field = {prime_field,<<255,...,255>>}, Prime = {<<255,...,252>>, <<90,...,75>>, @@ -4876,9 +4878,9 @@ static ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NI Key = <<151,...,62>> } or - { + { Curve = - {characteristic_two_field, + {characteristic_two_field, M, Basis = {tpbasis, _} | {ppbasis, k1, k2, k3} @@ -4891,7 +4893,7 @@ static ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NI */ #endif } - + if (pkey) EVP_PKEY_free(pkey); return enif_make_badarg(env); } @@ -5072,7 +5074,7 @@ static ERL_NIF_TERM engine_ctrl_cmd_strings_nif(ErlNifEnv* env, int argc, const error: for(i = 0; cmds != NULL && cmds[i] != NULL; i++) - enif_free(cmds[i]); + enif_free(cmds[i]); enif_free(cmds); return ret; #else @@ -5390,7 +5392,7 @@ static ERL_NIF_TERM engine_get_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TE if (!engine_id) { enif_alloc_binary(0, &engine_id_bin); engine_id_bin.size = 0; - return enif_make_tuple2(env, atom_ok, enif_make_binary(env, &engine_id_bin)); + return enif_make_binary(env, &engine_id_bin); } size = strlen(engine_id); @@ -5398,7 +5400,39 @@ static ERL_NIF_TERM engine_get_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TE engine_id_bin.size = size; memcpy(engine_id_bin.data, engine_id, size); - return enif_make_tuple2(env, atom_ok, enif_make_binary(env, &engine_id_bin)); + return enif_make_binary(env, &engine_id_bin); +#else + return atom_notsup; +#endif +} + +static ERL_NIF_TERM engine_get_name_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Engine) */ +#ifdef HAS_ENGINE_SUPPORT + ErlNifBinary engine_name_bin; + const char *engine_name; + int size; + struct engine_ctx *ctx; + + // Get Engine + if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { + PRINTF_ERR0("engine_get_id_nif Leaved: Parameter not an engine resource object"); + return enif_make_badarg(env); + } + + engine_name = ENGINE_get_name(ctx->engine); + if (!engine_name) { + enif_alloc_binary(0, &engine_name_bin); + engine_name_bin.size = 0; + return enif_make_binary(env, &engine_name_bin); + } + + size = strlen(engine_name); + enif_alloc_binary(size, &engine_name_bin); + engine_name_bin.size = size; + memcpy(engine_name_bin.data, engine_name, size); + + return enif_make_binary(env, &engine_name_bin); #else return atom_notsup; #endif diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml index 464799b320..8eb414b9bf 100644 --- a/lib/crypto/doc/src/crypto.xml +++ b/lib/crypto/doc/src/crypto.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>1999</year><year>2017</year> + <year>1999</year><year>2018</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -142,7 +142,7 @@ password => password()}</code> <code>engine_ref() = term()</code> - <p>The result of a call to <seealso marker="#engine_load-3">engine_load/3</seealso>. + <p>The result of a call to for example <seealso marker="#engine_load-3">engine_load/3</seealso>. </p> <code>key_id() = string() | binary()</code> @@ -628,7 +628,7 @@ <desc> <p>Fetches the corresponding public key from a private key stored in an Engine. The key must be of the type indicated by the Type parameter. - </p> + </p> </desc> </func> @@ -953,8 +953,8 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre> <p> Returns a list of all possible engine methods. </p> - <p> - May throw exception notsup in case there is + <p> + May throw exception notsup in case there is no engine support in the underlying OpenSSL implementation. </p> <p> @@ -970,18 +970,18 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre> <type> <v>EngineId = unicode:chardata()</v> <v>PreCmds, PostCmds = [{unicode:chardata(), unicode:chardata()}]</v> - <v>Result = {ok, Engine::term()} | {error, Reason::term()}</v> + <v>Result = {ok, Engine::engine_ref()} | {error, Reason::term()}</v> </type> <desc> <p> Loads the OpenSSL engine given by <c>EngineId</c> if it is available and then returns ok and - an engine handle. This function is the same as calling <c>engine_load/4</c> with - <c>EngineMethods</c> set to a list of all the possible methods. An error tuple is + an engine handle. This function is the same as calling <c>engine_load/4</c> with + <c>EngineMethods</c> set to a list of all the possible methods. An error tuple is returned if the engine can't be loaded. </p> <p> The function throws a badarg if the parameters are in wrong format. - It may also throw the exception notsup in case there is + It may also throw the exception notsup in case there is no engine support in the underlying OpenSSL implementation. </p> <p> @@ -998,7 +998,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre> <v>EngineId = unicode:chardata()</v> <v>PreCmds, PostCmds = [{unicode:chardata(), unicode:chardata()}]</v> <v>EngineMethods = [engine_method_type()]</v> - <v>Result = {ok, Engine::term()} | {error, Reason::term()}</v> + <v>Result = {ok, Engine::engine_ref()} | {error, Reason::term()}</v> </type> <desc> <p> @@ -1007,7 +1007,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre> </p> <p> The function throws a badarg if the parameters are in wrong format. - It may also throw the exception notsup in case there is + It may also throw the exception notsup in case there is no engine support in the underlying OpenSSL implementation. </p> <p> @@ -1021,17 +1021,17 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre> <name>engine_unload(Engine) -> Result</name> <fsummary>Dynamical load an encryption engine</fsummary> <type> - <v>Engine = term()</v> + <v>Engine = engine_ref()</v> <v>Result = ok | {error, Reason::term()}</v> </type> <desc> <p> - Unloads the OpenSSL engine given by <c>EngineId</c>. + Unloads the OpenSSL engine given by <c>Engine</c>. An error tuple is returned if the engine can't be unloaded. </p> <p> The function throws a badarg if the parameter is in wrong format. - It may also throw the exception notsup in case there is + It may also throw the exception notsup in case there is no engine support in the underlying OpenSSL implementation. </p> <p> @@ -1042,19 +1042,24 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre> </func> <func> - <name>engine_list() -> Result</name> - <fsummary>List the known engine ids</fsummary> + <name>engine_by_id(EngineId) -> Result</name> + <fsummary>Get a reference to an already loaded engine</fsummary> <type> - <v>Result = [EngineId::unicode:chardata()]</v> + <v>EngineID = unicode:chardata()engine_ref()</v> + <v>Result = {ok, Engine::engine_ref()} | {error, Reason::term()}</v> </type> <desc> - <p>List the id's of all engines in OpenSSL's internal list.</p> <p> - It may also throw the exception notsup in case there is + Get a reference to an already loaded engine with <c>EngineId</c>. + An error tuple is returned if the engine can't be unloaded. + </p> + <p> + The function throws a badarg if the parameter is in wrong format. + It may also throw the exception notsup in case there is no engine support in the underlying OpenSSL implementation. </p> <p> - See also the chapter <seealso marker="crypto:engine_load#engine_load">Engine Load</seealso> + See also the chapter <seealso marker="crypto:engine_load#engine_load">Engine Load</seealso> in the User's Guide. </p> </desc> @@ -1064,7 +1069,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre> <name>engine_ctrl_cmd_string(Engine, CmdName, CmdArg) -> Result</name> <fsummary>Sends ctrl commands to an OpenSSL engine</fsummary> <type> - <v>Engine = term()</v> + <v>Engine = engine_ref()</v> <v>CmdName = unicode:chardata()</v> <v>CmdArg = unicode:chardata()</v> <v>Result = ok | {error, Reason::term()}</v> @@ -1072,12 +1077,12 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre> <desc> <p> Sends ctrl commands to the OpenSSL engine given by <c>Engine</c>. - This function is the same as calling <c>engine_ctrl_cmd_string/4</c> with + This function is the same as calling <c>engine_ctrl_cmd_string/4</c> with <c>Optional</c> set to <c>false</c>. </p> <p> The function throws a badarg if the parameters are in wrong format. - It may also throw the exception notsup in case there is + It may also throw the exception notsup in case there is no engine support in the underlying OpenSSL implementation. </p> </desc> @@ -1087,7 +1092,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre> <name>engine_ctrl_cmd_string(Engine, CmdName, CmdArg, Optional) -> Result</name> <fsummary>Sends ctrl commands to an OpenSSL engine</fsummary> <type> - <v>Engine = term()</v> + <v>Engine = engine_ref()</v> <v>CmdName = unicode:chardata()</v> <v>CmdArg = unicode:chardata()</v> <v>Optional = boolean()</v> @@ -1096,18 +1101,218 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre> <desc> <p> Sends ctrl commands to the OpenSSL engine given by <c>Engine</c>. - <c>Optional</c> is a boolean argument that can relax the semantics of the function. - If set to <c>true</c> it will only return failure if the ENGINE supported the given - command name but failed while executing it, if the ENGINE doesn't support the command - name it will simply return success without doing anything. In this case we assume + <c>Optional</c> is a boolean argument that can relax the semantics of the function. + If set to <c>true</c> it will only return failure if the ENGINE supported the given + command name but failed while executing it, if the ENGINE doesn't support the command + name it will simply return success without doing anything. In this case we assume the user is only supplying commands specific to the given ENGINE so we set this to <c>false</c>. </p> <p> The function throws a badarg if the parameters are in wrong format. - It may also throw the exception notsup in case there is + It may also throw the exception notsup in case there is + no engine support in the underlying OpenSSL implementation. + </p> + </desc> + </func> + + <func> + <name>engine_add(Engine) -> Result</name> + <fsummary>Add engine to OpenSSL internal list</fsummary> + <type> + <v>Engine = engine_ref()</v> + <v>Result = ok | {error, Reason::term()}</v> + </type> + <desc> + <p>Add the engine to OpenSSL's internal list.</p> + <p> + The function throws a badarg if the parameters are in wrong format. + It may also throw the exception notsup in case there is + no engine support in the underlying OpenSSL implementation. + </p> + </desc> + </func> + + <func> + <name>engine_remove(Engine) -> Result</name> + <fsummary>Remove engine to OpenSSL internal list</fsummary> + <type> + <v>Engine = engine_ref()</v> + <v>Result = ok | {error, Reason::term()}</v> + </type> + <desc> + <p>Remove the engine from OpenSSL's internal list.</p> + <p> + The function throws a badarg if the parameters are in wrong format. + It may also throw the exception notsup in case there is + no engine support in the underlying OpenSSL implementation. + </p> + </desc> + </func> + + <func> + <name>engine_get_id(Engine) -> EngineId</name> + <fsummary>Fetch engine ID</fsummary> + <type> + <v>Engine = engine_ref()</v> + <v>EngineId = unicode:chardata()</v> + </type> + <desc> + <p>Return the ID for the engine, or an empty binary if there is no id set.</p> + <p> + The function throws a badarg if the parameters are in wrong format. + It may also throw the exception notsup in case there is + no engine support in the underlying OpenSSL implementation. + </p> + </desc> + </func> + + <func> + <name>engine_get_name(Engine) -> EngineName</name> + <fsummary>Fetch engine name</fsummary> + <type> + <v>Engine = engine_ref()</v> + <v>EngineName = unicode:chardata()</v> + </type> + <desc> + <p>Return the name (eg a description) for the engine, or an empty binary if there is no name set.</p> + <p> + The function throws a badarg if the parameters are in wrong format. + It may also throw the exception notsup in case there is + no engine support in the underlying OpenSSL implementation. + </p> + </desc> + </func> + + <func> + <name>engine_list() -> Result</name> + <fsummary>List the known engine ids</fsummary> + <type> + <v>Result = [EngineId::unicode:chardata()]</v> + </type> + <desc> + <p>List the id's of all engines in OpenSSL's internal list.</p> + <p> + It may also throw the exception notsup in case there is + no engine support in the underlying OpenSSL implementation. + </p> + <p> + See also the chapter <seealso marker="crypto:engine_load#engine_load">Engine Load</seealso> + in the User's Guide. + </p> + <p> + May throw exception notsup in case engine functionality is not supported by the underlying + OpenSSL implementation. + </p> + </desc> + </func> + + <func> + <name>ensure_engine_loaded(EngineId, LibPath) -> Result</name> + <fsummary>Ensure encryption engine just loaded once</fsummary> + <type> + <v>EngineId = unicode:chardata()</v> + <v>LibPath = unicode:chardata()</v> + <v>Result = {ok, Engine::engine_ref()} | {error, Reason::term()}</v> + </type> + <desc> + <p> + Loads the OpenSSL engine given by <c>EngineId</c> and the path to the dynamic library + implementing the engine. This function is the same as calling <c>ensure_engine_loaded/3</c> with + <c>EngineMethods</c> set to a list of all the possible methods. An error tuple is + returned if the engine can't be loaded. + </p> + <p> + The function throws a badarg if the parameters are in wrong format. + It may also throw the exception notsup in case there is no engine support in the underlying OpenSSL implementation. </p> + <p> + See also the chapter <seealso marker="crypto:engine_load#engine_load">Engine Load</seealso> + in the User's Guide. + </p> + </desc> + </func> + + <func> + <name>ensure_engine_loaded(EngineId, LibPath, EngineMethods) -> Result</name> + <fsummary>Ensure encryption engine just loaded once</fsummary> + <type> + <v>EngineId = unicode:chardata()</v> + <v>LibPath = unicode:chardata()</v> + <v>EngineMethods = [engine_method_type()]</v> + <v>Result = {ok, Engine::engine_ref()} | {error, Reason::term()}</v> + </type> + <desc> + <p> + Loads the OpenSSL engine given by <c>EngineId</c> and the path to the dynamic library + implementing the engine. This function differs from the normal engine_load in that sense it + also add the engine id to the internal list in OpenSSL. Then in the following calls to the function + it just fetch the reference to the engine instead of loading it again. + An error tuple is returned if the engine can't be loaded. + </p> + <p> + The function throws a badarg if the parameters are in wrong format. + It may also throw the exception notsup in case there is + no engine support in the underlying OpenSSL implementation. + </p> + <p> + See also the chapter <seealso marker="crypto:engine_load#engine_load">Engine Load</seealso> + in the User's Guide. + </p> + </desc> + </func> + + <func> + <name>ensure_engine_unloaded(Engine) -> Result</name> + <fsummary>Unload an engine loaded with the ensure function</fsummary> + <type> + <v>Engine = engine_ref()</v> + <v>Result = ok | {error, Reason::term()}</v> + </type> + <desc> + <p> + Unloads an engine loaded with the <c>ensure_engine_loaded</c> function. + It both removes the label from the OpenSSL internal engine list and unloads the engine. + This function is the same as calling <c>ensure_engine_unloaded/2</c> with + <c>EngineMethods</c> set to a list of all the possible methods. An error tuple is + returned if the engine can't be unloaded. + </p> + <p> + The function throws a badarg if the parameters are in wrong format. + It may also throw the exception notsup in case there is + no engine support in the underlying OpenSSL implementation. + </p> + <p> + See also the chapter <seealso marker="crypto:engine_load#engine_load">Engine Load</seealso> + in the User's Guide. + </p> + </desc> + </func> + + <func> + <name>ensure_engine_unloaded(Engine, EngineMethods) -> Result</name> + <fsummary>Unload an engine loaded with the ensure function</fsummary> + <type> + <v>Engine = engine_ref()</v> + <v>EngineMethods = [engine_method_type()]</v> + <v>Result = ok | {error, Reason::term()}</v> + </type> + <desc> + <p> + Unloads an engine loaded with the <c>ensure_engine_loaded</c> function. + It both removes the label from the OpenSSL internal engine list and unloads the engine. + An error tuple is returned if the engine can't be unloaded. + </p> + <p> + The function throws a badarg if the parameters are in wrong format. + It may also throw the exception notsup in case there is + no engine support in the underlying OpenSSL implementation. + </p> + <p> + See also the chapter <seealso marker="crypto:engine_load#engine_load">Engine Load</seealso> + in the User's Guide. + </p> </desc> </func> diff --git a/lib/crypto/doc/src/engine_load.xml b/lib/crypto/doc/src/engine_load.xml index e5c3f5d561..3d0aa0c32a 100644 --- a/lib/crypto/doc/src/engine_load.xml +++ b/lib/crypto/doc/src/engine_load.xml @@ -42,6 +42,9 @@ operations. The hardware implementation usually offers improved performance over its software-based counterpart, which is known as cryptographic acceleration. </p> + <note> + <p>The file name requirement on the engine dynamic library can differ between SSL versions.</p> + </note> </section> <section> @@ -54,9 +57,6 @@ <code> 1> {ok, Engine} = crypto:engine_load(<<"otp_test_engine">>, [], []). {ok, #Ref}</code> - <note> - <p>The file name requirement on the engine dynamic library can differ between SSL versions.</p> - </note> </section> <section> @@ -72,9 +72,6 @@ <<"LOAD">>], []). {ok, #Ref}</code> - <note> - <p>The dynamic engine is not supported in LibreSSL from version 2.2.1</p> - </note> </section> <section> @@ -100,6 +97,28 @@ engine_method_pkey_meths, engine_method_pkey_asn1_meths]. </section> <section> + <title>Load with the ensure loaded function</title> + <p> + This function makes sure the engine is loaded just once and the ID is added to the internal + engine list of OpenSSL. The following calls to the function will check if the ID is loaded + and then just get a new reference to the engine. + </p> + <code> + 5> {ok, Engine} = crypto:ensure_engine_loaded(<<"MD5">>, + <<"/some/path/otp_test_engine.so">>). + {ok, #Ref}</code> + <p> + To unload it use crypto:ensure_engine_unloaded/1 which removes the ID from the internal list + before unloading the engine. + </p> + <code> + 6> crypto:ensure_engine_unloaded(<<"MD5">>). + ok</code> + </section> + + + + <section> <title>List all engines currently loaded</title> <code> 5> crypto:engine_list(). diff --git a/lib/crypto/doc/src/notes.xml b/lib/crypto/doc/src/notes.xml index 66619c9e11..21ce2891dc 100644 --- a/lib/crypto/doc/src/notes.xml +++ b/lib/crypto/doc/src/notes.xml @@ -31,6 +31,53 @@ </header> <p>This document describes the changes made to the Crypto application.</p> +<section><title>Crypto 4.2.2.2</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> Update the crypto engine functions to handle multiple + loads of an engine. </p> <p><c>engine_load/3/4</c> is + updated so it doesn't add the engine ID to OpenSSLs + internal list of engines which makes it possible to run + the engine_load more than once if it doesn't contain + global data.</p> <p>Added <c>ensure_engine_loaded/2/3</c> + which guarantees that the engine just is loaded once and + the following calls just returns a reference to it. This + is done by add the ID to the internal OpenSSL list and + check if it is already registered when the function is + called.</p> <p>Added <c>ensure_engine_unloaded/1/2</c> to + unload engines loaded with ensure_engine_loaded.</p> + <p>Then some more utility functions are added.</p> + <p><c>engine_add/1</c>, adds the engine to OpenSSL + internal list</p> <p><c>engine_remove/1</c>, remove the + engine from OpenSSL internal list</p> + <p><c>engine_get_id/1</c>, fetch the engines id</p> + <p><c>engine_get_name/1</c>, fetch the engine name</p> + <p> + Own Id: OTP-15233</p> + </item> + </list> + </section> + +</section> + +<section><title>Crypto 4.2.2.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>Fixed a node crash in <c>crypto:compute_key(ecdh, + ...)</c> when passing a wrongly typed Others + argument.</p> + <p> + Own Id: OTP-15194 Aux Id: ERL-673 </p> + </item> + </list> + </section> + +</section> + <section><title>Crypto 4.2.2</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl index 1a1b4f98b5..0d85b94b57 100644 --- a/lib/crypto/src/crypto.erl +++ b/lib/crypto/src/crypto.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2017. All Rights Reserved. +%% Copyright Ericsson AB 1999-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -51,9 +51,18 @@ engine_load/3, engine_load/4, engine_unload/1, + engine_by_id/1, engine_list/0, engine_ctrl_cmd_string/3, - engine_ctrl_cmd_string/4 + engine_ctrl_cmd_string/4, + engine_add/1, + engine_remove/1, + engine_get_id/1, + engine_get_name/1, + ensure_engine_loaded/2, + ensure_engine_loaded/3, + ensure_engine_unloaded/1, + ensure_engine_unloaded/2 ]). -export_type([engine_ref/0, @@ -444,7 +453,7 @@ sign(Algorithm, Type, Data, Key, Options) -> -type engine_key_ref() :: #{engine := engine_ref(), key_id := key_id(), password => password(), - term() => term() + term() => term() }. -type pk_algs() :: rsa | ecdsa | dss . @@ -604,7 +613,7 @@ compute_key(ecdh, Others, My, Curve) -> -type engine_method_type() :: engine_method_rsa | engine_method_dsa | engine_method_dh | engine_method_rand | engine_method_ecdh | engine_method_ecdsa | engine_method_ciphers | engine_method_digests | engine_method_store | - engine_method_pkey_meths | engine_method_pkey_asn1_meths | + engine_method_pkey_meths | engine_method_pkey_asn1_meths | engine_method_ec. -type engine_ref() :: term(). @@ -621,7 +630,8 @@ engine_get_all_methods() -> PreCmds::[{unicode:chardata(), unicode:chardata()}], PostCmds::[{unicode:chardata(), unicode:chardata()}]) -> {ok, Engine::engine_ref()} | {error, Reason::term()}. -engine_load(EngineId, PreCmds, PostCmds) when is_list(PreCmds), is_list(PostCmds) -> +engine_load(EngineId, PreCmds, PostCmds) when is_list(PreCmds), + is_list(PostCmds) -> engine_load(EngineId, PreCmds, PostCmds, engine_get_all_methods()). %%---------------------------------------------------------------------- @@ -638,28 +648,26 @@ engine_load(EngineId, PreCmds, PostCmds, EngineMethods) when is_list(PreCmds), ok = notsup_to_error(engine_load_dynamic_nif()), case notsup_to_error(engine_by_id_nif(ensure_bin_chardata(EngineId))) of {ok, Engine} -> - ok = engine_load_1(Engine, PreCmds, PostCmds, EngineMethods), - {ok, Engine}; + engine_load_1(Engine, PreCmds, PostCmds, EngineMethods); {error, Error1} -> {error, Error1} end catch - throw:Error2 -> - Error2 + throw:Error2 -> + Error2 end. engine_load_1(Engine, PreCmds, PostCmds, EngineMethods) -> try ok = engine_nif_wrapper(engine_ctrl_cmd_strings_nif(Engine, ensure_bin_cmds(PreCmds), 0)), - ok = engine_nif_wrapper(engine_add_nif(Engine)), ok = engine_nif_wrapper(engine_init_nif(Engine)), engine_load_2(Engine, PostCmds, EngineMethods), - ok + {ok, Engine} catch - throw:Error -> - %% The engine couldn't initialise, release the structural reference - ok = engine_free_nif(Engine), - throw(Error) + throw:Error -> + %% The engine couldn't initialise, release the structural reference + ok = engine_free_nif(Engine), + throw(Error) end. engine_load_2(Engine, PostCmds, EngineMethods) -> @@ -689,7 +697,6 @@ engine_unload(Engine, EngineMethods) -> try [ok = engine_nif_wrapper(engine_unregister_nif(Engine, engine_method_atom_to_int(Method))) || Method <- EngineMethods], - ok = engine_nif_wrapper(engine_remove_nif(Engine)), %% Release the functional reference from engine_init_nif ok = engine_nif_wrapper(engine_finish_nif(Engine)), %% Release the structural reference from engine_by_id_nif @@ -700,6 +707,41 @@ engine_unload(Engine, EngineMethods) -> end. %%---------------------------------------------------------------------- +%% Function: engine_by_id/1 +%%---------------------------------------------------------------------- +engine_by_id(EngineId) -> + try + notsup_to_error(engine_by_id_nif(ensure_bin_chardata(EngineId))) + catch + throw:Error -> + Error + end. + +%%---------------------------------------------------------------------- +%% Function: engine_add/1 +%%---------------------------------------------------------------------- +engine_add(Engine) -> + notsup_to_error(engine_add_nif(Engine)). + +%%---------------------------------------------------------------------- +%% Function: engine_remove/1 +%%---------------------------------------------------------------------- +engine_remove(Engine) -> + notsup_to_error(engine_remove_nif(Engine)). + +%%---------------------------------------------------------------------- +%% Function: engine_get_id/1 +%%---------------------------------------------------------------------- +engine_get_id(Engine) -> + notsup_to_error(engine_get_id_nif(Engine)). + +%%---------------------------------------------------------------------- +%% Function: engine_get_name/1 +%%---------------------------------------------------------------------- +engine_get_name(Engine) -> + notsup_to_error(engine_get_name_nif(Engine)). + +%%---------------------------------------------------------------------- %% Function: engine_list/0 %%---------------------------------------------------------------------- -spec engine_list() -> @@ -710,9 +752,9 @@ engine_list() -> []; {ok, Engine} -> case notsup_to_error(engine_get_id_nif(Engine)) of - {ok, <<>>} -> + <<>> -> engine_list(Engine, []); - {ok, EngineId} -> + EngineId -> engine_list(Engine, [EngineId]) end end. @@ -723,9 +765,9 @@ engine_list(Engine0, IdList) -> lists:reverse(IdList); {ok, Engine1} -> case notsup_to_error(engine_get_id_nif(Engine1)) of - {ok, <<>>} -> + <<>> -> engine_list(Engine1, IdList); - {ok, EngineId} -> + EngineId -> engine_list(Engine1, [EngineId |IdList]) end end. @@ -734,7 +776,7 @@ engine_list(Engine0, IdList) -> %% Function: engine_ctrl_cmd_string/3 %%---------------------------------------------------------------------- -spec engine_ctrl_cmd_string(Engine::term(), - CmdName::unicode:chardata(), + CmdName::unicode:chardata(), CmdArg::unicode:chardata()) -> ok | {error, Reason::term()}. engine_ctrl_cmd_string(Engine, CmdName, CmdArg) -> @@ -744,13 +786,13 @@ engine_ctrl_cmd_string(Engine, CmdName, CmdArg) -> %% Function: engine_ctrl_cmd_string/4 %%---------------------------------------------------------------------- -spec engine_ctrl_cmd_string(Engine::term(), - CmdName::unicode:chardata(), + CmdName::unicode:chardata(), CmdArg::unicode:chardata(), Optional::boolean()) -> ok | {error, Reason::term()}. engine_ctrl_cmd_string(Engine, CmdName, CmdArg, Optional) -> - case engine_ctrl_cmd_strings_nif(Engine, - ensure_bin_cmds([{CmdName, CmdArg}]), + case engine_ctrl_cmd_strings_nif(Engine, + ensure_bin_cmds([{CmdName, CmdArg}]), bool_to_int(Optional)) of ok -> ok; @@ -760,6 +802,82 @@ engine_ctrl_cmd_string(Engine, CmdName, CmdArg, Optional) -> {error, Error} end. +%%---------------------------------------------------------------------- +%% Function: ensure_engine_loaded/2 +%% Special version of load that only uses dynamic engine to load +%%---------------------------------------------------------------------- +ensure_engine_loaded(EngineId, LibPath) -> + ensure_engine_loaded(EngineId, LibPath, engine_get_all_methods()). + +%%---------------------------------------------------------------------- +%% Function: ensure_engine_loaded/3 +%% Special version of load that only uses dynamic engine to load +%%---------------------------------------------------------------------- +ensure_engine_loaded(EngineId, LibPath, EngineMethods) -> + try + List = crypto:engine_list(), + case lists:member(EngineId, List) of + true -> + notsup_to_error(engine_by_id_nif(ensure_bin_chardata(EngineId))); + false -> + ok = notsup_to_error(engine_load_dynamic_nif()), + case notsup_to_error(engine_by_id_nif(ensure_bin_chardata(<<"dynamic">>))) of + {ok, Engine} -> + PreCommands = [{<<"SO_PATH">>, ensure_bin_chardata(LibPath)}, + {<<"ID">>, ensure_bin_chardata(EngineId)}, + <<"LOAD">>], + ensure_engine_loaded_1(Engine, PreCommands, EngineMethods); + {error, Error1} -> + {error, Error1} + end + end + catch + throw:Error2 -> + Error2 + end. + +ensure_engine_loaded_1(Engine, PreCmds, Methods) -> + try + ok = engine_nif_wrapper(engine_ctrl_cmd_strings_nif(Engine, ensure_bin_cmds(PreCmds), 0)), + ok = engine_nif_wrapper(engine_add_nif(Engine)), + ok = engine_nif_wrapper(engine_init_nif(Engine)), + ensure_engine_loaded_2(Engine, Methods), + {ok, Engine} + catch + throw:Error -> + %% The engine couldn't initialise, release the structural reference + ok = engine_free_nif(Engine), + throw(Error) + end. + +ensure_engine_loaded_2(Engine, Methods) -> + try + [ok = engine_nif_wrapper(engine_register_nif(Engine, engine_method_atom_to_int(Method))) || + Method <- Methods], + ok + catch + throw:Error -> + %% The engine registration failed, release the functional reference + ok = engine_finish_nif(Engine), + throw(Error) + end. +%%---------------------------------------------------------------------- +%% Function: ensure_engine_unloaded/1 +%%---------------------------------------------------------------------- +ensure_engine_unloaded(Engine) -> + ensure_engine_unloaded(Engine, engine_get_all_methods()). + +%%---------------------------------------------------------------------- +%% Function: ensure_engine_unloaded/2 +%%---------------------------------------------------------------------- +ensure_engine_unloaded(Engine, EngineMethods) -> + case engine_remove(Engine) of + ok -> + engine_unload(Engine, EngineMethods); + {error, E} -> + {error, E} + end. + %%-------------------------------------------------------------------- %%% On load %%-------------------------------------------------------------------- @@ -827,7 +945,7 @@ path2bin(Path) when is_list(Path) -> max_bytes() -> ?MAX_BYTES_TO_NIF. -notsup_to_error(notsup) -> +notsup_to_error(notsup) -> erlang:error(notsup); notsup_to_error(Other) -> Other. @@ -1104,7 +1222,7 @@ privkey_to_pubkey(Alg, EngineMap) when Alg == rsa; Alg == dss; Alg == ecdsa -> error:notsup -> {error, notsup} end. - + privkey_to_pubkey_nif(_Alg, _EngineMap) -> ?nif_stub. @@ -1266,6 +1384,7 @@ engine_unregister_nif(_Engine, _EngineMethod) -> ?nif_stub. engine_get_first_nif() -> ?nif_stub. engine_get_next_nif(_Engine) -> ?nif_stub. engine_get_id_nif(_Engine) -> ?nif_stub. +engine_get_name_nif(_Engine) -> ?nif_stub. engine_get_all_methods_nif() -> ?nif_stub. %%-------------------------------------------------------------------- @@ -1323,7 +1442,7 @@ get_test_engine() -> Type = erlang:system_info(system_architecture), LibDir = filename:join([code:priv_dir(crypto), "lib"]), ArchDir = filename:join([LibDir, Type]), - case filelib:is_dir(ArchDir) of + case filelib:is_dir(ArchDir) of true -> check_otp_test_engine(ArchDir); false -> check_otp_test_engine(LibDir) end. @@ -1341,4 +1460,3 @@ check_otp_test_engine(LibDir) -> {error, notexist} end end. - diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl index 6dab459df6..c07e937737 100644 --- a/lib/crypto/test/crypto_SUITE.erl +++ b/lib/crypto/test/crypto_SUITE.erl @@ -31,6 +31,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [app, + {group, api_errors}, appup, {group, fips}, {group, non_fips}, @@ -169,7 +170,8 @@ groups() -> {no_aes_ige256, [], [no_support, no_block]}, {no_chacha20_poly1305, [], [no_support, no_aead]}, {no_rc2_cbc, [], [no_support, no_block]}, - {no_rc4, [], [no_support, no_stream]} + {no_rc4, [], [no_support, no_stream]}, + {api_errors, [], [api_errors_ecdh]} ]. %%------------------------------------------------------------------- @@ -237,6 +239,8 @@ init_per_group(non_fips, Config) -> _NotEnabled -> NonFIPSConfig end; +init_per_group(api_errors, Config) -> + Config; init_per_group(GroupName, Config) -> case atom_to_list(GroupName) of "no_" ++ TypeStr -> @@ -2491,3 +2495,14 @@ parse_rsp_cmac(Type, Key0, Msg0, Mlen0, Tlen, MAC0, Next, Acc) -> I -> parse_rsp(Type, Next, [{Type, Key, Msg, I, MAC}|Acc]) end. + +api_errors_ecdh(Config) when is_list(Config) -> + %% Check that we don't segfault when fed garbage. + Test = fun(Others, Curve) -> + {_Pub, Priv} = crypto:generate_key(ecdh, Curve), + crypto:compute_key(ecdh, Others, Priv, Curve) + end, + Others = [gurka, 0, <<0>>], + Curves = [gaffel, 0, sect571r1], + [_= (catch Test(O, C)) || O <- Others, C <- Curves], + ok. diff --git a/lib/crypto/test/engine_SUITE.erl b/lib/crypto/test/engine_SUITE.erl index f410542f72..891eaff23b 100644 --- a/lib/crypto/test/engine_SUITE.erl +++ b/lib/crypto/test/engine_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2017-2017. All Rights Reserved. +%% Copyright Ericsson AB 2017-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -39,6 +39,10 @@ all() -> get_all_possible_methods, engine_load_all_methods, engine_load_some_methods, + multiple_engine_load, + engine_list, + get_id_and_name, + engine_by_id, bad_arguments, unknown_engine, pre_command_fail_bad_value, @@ -46,6 +50,7 @@ all() -> failed_engine_init, ctrl_cmd_string, ctrl_cmd_string_optional, + ensure_load, {group, engine_stored_key} ]. @@ -95,7 +100,7 @@ init_per_group(engine_stored_key, Config) -> {error, notexist} -> {skip, "OTP Test engine not found"}; {error, notsup} -> - {skip, "Engine not supported on this OpenSSL version"}; + {skip, "Engine not supported on this SSL version"}; {error, bad_engine_id} -> {skip, "Dynamic Engine not supported"}; Other -> @@ -130,11 +135,12 @@ get_all_possible_methods() -> get_all_possible_methods(Config) when is_list(Config) -> try List = crypto:engine_get_all_methods(), + true = erlang:is_list(List), ct:log("crypto:engine_get_all_methods() -> ~p\n", [List]), ok catch error:notsup -> - {skip, "Engine not supported on this OpenSSL version"} + {skip, "Engine not supported on this SSL version"} end. engine_load_all_methods()-> @@ -147,13 +153,12 @@ engine_load_all_methods(Config) when is_list(Config) -> {error, notexist} -> {skip, "OTP Test engine not found"}; {ok, Engine} -> - try + try Md5Hash1 = <<106,30,3,246,166,222,229,158,244,217,241,179,50,232,107,109>>, Md5Hash1 = crypto:hash(md5, "Don't panic"), Md5Hash2 = <<0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>>, case crypto:engine_load(<<"dynamic">>, [{<<"SO_PATH">>, Engine}, - {<<"ID">>, <<"MD5">>}, <<"LOAD">>], []) of {ok, E} -> @@ -179,7 +184,7 @@ engine_load_all_methods(Config) when is_list(Config) -> end catch error:notsup -> - {skip, "Engine not supported on this OpenSSL version"} + {skip, "Engine not supported on this SSL version"} end end. @@ -193,21 +198,20 @@ engine_load_some_methods(Config) when is_list(Config) -> {error, notexist} -> {skip, "OTP Test engine not found"}; {ok, Engine} -> - try + try Md5Hash1 = <<106,30,3,246,166,222,229,158,244,217,241,179,50,232,107,109>>, Md5Hash1 = crypto:hash(md5, "Don't panic"), Md5Hash2 = <<0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>>, EngineMethods = crypto:engine_get_all_methods() -- - [engine_method_dh,engine_method_rand, + [engine_method_dh, engine_method_rand, engine_method_ciphers, engine_method_store, engine_method_pkey_meths, engine_method_pkey_asn1_meths], case crypto:engine_load(<<"dynamic">>, [{<<"SO_PATH">>, Engine}, - {<<"ID">>, <<"MD5">>}, <<"LOAD">>], [], EngineMethods) of - {ok, E} -> + {ok, E} -> case crypto:hash(md5, "Don't panic") of Md5Hash1 -> ct:fail(fail_to_load_engine_still_original); @@ -230,7 +234,168 @@ engine_load_some_methods(Config) when is_list(Config) -> end catch error:notsup -> - {skip, "Engine not supported on this OpenSSL version"} + {skip, "Engine not supported on this SSL version"} + end + end. + +multiple_engine_load()-> + [{doc, "Use a dummy md5 engine that does not implement md5" + "but rather returns a static binary to test that crypto:engine_load " + "functions works when called multiple times."}]. + +multiple_engine_load(Config) when is_list(Config) -> + case crypto:get_test_engine() of + {error, notexist} -> + {skip, "OTP Test engine not found"}; + {ok, Engine} -> + try + Md5Hash1 = <<106,30,3,246,166,222,229,158,244,217,241,179,50,232,107,109>>, + Md5Hash1 = crypto:hash(md5, "Don't panic"), + Md5Hash2 = <<0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>>, + case crypto:engine_load(<<"dynamic">>, + [{<<"SO_PATH">>, Engine}, + <<"LOAD">>], + []) of + {ok, E} -> + {ok, E1} = crypto:engine_load(<<"dynamic">>, + [{<<"SO_PATH">>, Engine}, + <<"LOAD">>], + []), + {ok, E2} = crypto:engine_load(<<"dynamic">>, + [{<<"SO_PATH">>, Engine}, + <<"LOAD">>], + []), + case crypto:hash(md5, "Don't panic") of + Md5Hash1 -> + ct:fail(fail_to_load_still_original_engine); + Md5Hash2 -> + ok; + _ -> + ct:fail(fail_to_load_engine) + end, + ok = crypto:engine_unload(E2), + case crypto:hash(md5, "Don't panic") of + Md5Hash1 -> + ct:fail(fail_to_load_still_original_engine); + Md5Hash2 -> + ok; + _ -> + ct:fail(fail_to_load_engine) + end, + ok = crypto:engine_unload(E), + case crypto:hash(md5, "Don't panic") of + Md5Hash1 -> + ct:fail(fail_to_load_still_original_engine); + Md5Hash2 -> + ok; + _ -> + ct:fail(fail_to_load_engine) + end, + ok = crypto:engine_unload(E1), + case crypto:hash(md5, "Don't panic") of + Md5Hash2 -> + ct:fail(fail_to_unload_still_test_engine); + Md5Hash1 -> + ok; + _ -> + ct:fail(fail_to_unload_engine) + end; + {error, bad_engine_id} -> + {skip, "Dynamic Engine not supported"} + end + catch + error:notsup -> + {skip, "Engine not supported on this SSL version"} + end + end. + +engine_list()-> + [{doc, "Test add and remove engine ID to the SSL internal engine list."}]. + +engine_list(Config) when is_list(Config) -> + case crypto:get_test_engine() of + {error, notexist} -> + {skip, "OTP Test engine not found"}; + {ok, Engine} -> + try + EngineList0 = crypto:engine_list(), + case crypto:engine_load(<<"dynamic">>, + [{<<"SO_PATH">>, Engine}, + <<"LOAD">>], + []) of + {ok, E} -> + EngineList0 = crypto:engine_list(), + ok = crypto:engine_add(E), + [<<"MD5">>] = lists:subtract(crypto:engine_list(), EngineList0), + ok = crypto:engine_remove(E), + EngineList0 = crypto:engine_list(), + ok = crypto:engine_unload(E); + {error, bad_engine_id} -> + {skip, "Dynamic Engine not supported"} + end + catch + error:notsup -> + {skip, "Engine not supported on this SSL version"} + end + end. + +get_id_and_name()-> + [{doc, "Test fetching id and name from an engine."}]. + +get_id_and_name(Config) when is_list(Config) -> + case crypto:get_test_engine() of + {error, notexist} -> + {skip, "OTP Test engine not found"}; + {ok, Engine} -> + try + case crypto:engine_load(<<"dynamic">>, + [{<<"SO_PATH">>, Engine}, + <<"LOAD">>], + []) of + {ok, E} -> + <<"MD5">> = crypto:engine_get_id(E), + <<"MD5 test engine">> = crypto:engine_get_name(E), + ok = crypto:engine_unload(E); + {error, bad_engine_id} -> + {skip, "Dynamic Engine not supported"} + end + catch + error:notsup -> + {skip, "Engine not supported on this SSL version"} + end + end. + +engine_by_id()-> + [{doc, "Test fetching a new reference the the engine when the" + "engine id is added to the SSL engine list."}]. + +engine_by_id(Config) when is_list(Config) -> + case crypto:get_test_engine() of + {error, notexist} -> + {skip, "OTP Test engine not found"}; + {ok, Engine} -> + try + case crypto:engine_load(<<"dynamic">>, + [{<<"SO_PATH">>, Engine}, + <<"LOAD">>], + []) of + {ok, E} -> + case crypto:engine_by_id(<<"MD5">>) of + {error,bad_engine_id} -> + ok; + {ok, _} -> + ct:fail(fail_engine_found) + end, + ok = crypto:engine_add(E), + {ok, _E1} = crypto:engine_by_id(<<"MD5">>), + ok = crypto:engine_remove(E), + ok = crypto:engine_unload(E); + {error, bad_engine_id} -> + {skip, "Dynamic Engine not supported"} + end + catch + error:notsup -> + {skip, "Engine not supported on this SSL version"} end end. @@ -244,7 +409,7 @@ bad_arguments(Config) when is_list(Config) -> {error, notexist} -> {skip, "OTP Test engine not found"}; {ok, Engine} -> - try + try try crypto:engine_load(fail_engine, [], []) catch @@ -274,7 +439,7 @@ bad_arguments(Config) when is_list(Config) -> end catch error:notsup -> - {skip, "Engine not supported on this OpenSSL version"} + {skip, "Engine not supported on this SSL version"} end end. @@ -287,7 +452,7 @@ unknown_engine(Config) when is_list(Config) -> ok catch error:notsup -> - {skip, "Engine not supported on this OpenSSL version"} + {skip, "Engine not supported on this SSL version"} end. pre_command_fail_bad_value() -> @@ -309,7 +474,7 @@ pre_command_fail_bad_value(Config) when is_list(Config) -> end catch error:notsup -> - {skip, "Engine not supported on this OpenSSL version"} + {skip, "Engine not supported on this SSL version"} end. pre_command_fail_bad_key() -> @@ -332,9 +497,9 @@ pre_command_fail_bad_key(Config) when is_list(Config) -> {skip, "Dynamic Engine not supported"} end end - catch + catch error:notsup -> - {skip, "Engine not supported on this OpenSSL version"} + {skip, "Engine not supported on this SSL version"} end. failed_engine_init()-> @@ -350,18 +515,20 @@ failed_engine_init(Config) when is_list(Config) -> [{<<"SO_PATH">>, Engine}, {<<"ID">>, <<"MD5">>}], []) of - {error, add_engine_failed} -> + {error, engine_init_failed} -> ok; {error, bad_engine_id} -> {skip, "Dynamic Engine not supported"} end end - catch + catch error:notsup -> - {skip, "Engine not supported on this OpenSSL version"} + {skip, "Engine not supported on this SSL version"} end. +%%------------------------------------------------------------------------- +%% Test the optional flag in ctrl comands ctrl_cmd_string()-> [{doc, "Test that a not known optional ctrl comand do not fail"}]. ctrl_cmd_string(Config) when is_list(Config) -> @@ -375,22 +542,22 @@ ctrl_cmd_string(Config) when is_list(Config) -> {<<"ID">>, <<"MD5">>}, <<"LOAD">>], []) of - {ok, E} -> + {ok, E} -> case crypto:engine_ctrl_cmd_string(E, <<"TEST">>, <<"17">>) of ok -> ct:fail(fail_ctrl_cmd_should_fail); - {error,ctrl_cmd_failed} -> + {error,ctrl_cmd_failed} -> ok end, - ok = crypto:engine_unload(E); + ok = crypto:engine_unload(E); {error, bad_engine_id} -> {skip, "Dynamic Engine not supported"} end end - catch + catch error:notsup -> - {skip, "Engine not supported on this OpenSSL version"} - end. + {skip, "Engine not supported on this SSL version"} + end. ctrl_cmd_string_optional()-> [{doc, "Test that a not known optional ctrl comand do not fail"}]. @@ -405,22 +572,63 @@ ctrl_cmd_string_optional(Config) when is_list(Config) -> {<<"ID">>, <<"MD5">>}, <<"LOAD">>], []) of - {ok, E} -> + {ok, E} -> case crypto:engine_ctrl_cmd_string(E, <<"TEST">>, <<"17">>, true) of ok -> ok; - _ -> + _ -> ct:fail(fail_ctrl_cmd_string) end, - ok = crypto:engine_unload(E); + ok = crypto:engine_unload(E); {error, bad_engine_id} -> {skip, "Dynamic Engine not supported"} end end - catch + catch error:notsup -> - {skip, "Engine not supported on this OpenSSL version"} - end. + {skip, "Engine not supported on this SSL version"} + end. + +ensure_load()-> + [{doc, "Test the special ensure load function."}]. + +ensure_load(Config) when is_list(Config) -> + case crypto:get_test_engine() of + {error, notexist} -> + {skip, "OTP Test engine not found"}; + {ok, Engine} -> + try + Md5Hash1 = <<106,30,3,246,166,222,229,158,244,217,241,179,50,232,107,109>>, + Md5Hash1 = crypto:hash(md5, "Don't panic"), + Md5Hash2 = <<0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>>, + case crypto:ensure_engine_loaded(<<"MD5">>, Engine) of + {ok, E} -> + {ok, _E1} = crypto:ensure_engine_loaded(<<"MD5">>, Engine), + case crypto:hash(md5, "Don't panic") of + Md5Hash1 -> + ct:fail(fail_to_load_still_original_engine); + Md5Hash2 -> + ok; + _ -> + ct:fail(fail_to_load_engine) + end, + ok = crypto:ensure_engine_unloaded(E), + case crypto:hash(md5, "Don't panic") of + Md5Hash2 -> + ct:fail(fail_to_unload_still_test_engine); + Md5Hash1 -> + ok; + _ -> + ct:fail(fail_to_unload_engine) + end; + {error, bad_engine_id} -> + {skip, "Dynamic Engine not supported"} + end + catch + error:notsup -> + {skip, "Engine not supported on this SSL version"} + end + end. %%%---------------------------------------------------------------- %%% Pub/priv key storage tests. Thoose are for testing the crypto.erl @@ -465,7 +673,7 @@ sign_verify_rsa_pwd_bad_pwd(Config) -> _ -> {fail, "PWD prot pubkey sign succeded with no pwd!"} catch error:badarg -> ok - end. + end. priv_encrypt_pub_decrypt_rsa(Config) -> Priv = #{engine => engine_ref(Config), @@ -538,7 +746,7 @@ get_pub_from_priv_key_rsa_pwd_no_pwd(Config) -> {skip, "RSA not supported"}; {error, Error} -> {fail, {wrong_error,Error}}; - Pub -> + Pub -> ct:log("rsa Pub = ~p",[Pub]), {fail, "PWD prot pubkey fetch succeded although no pwd!"} end. @@ -554,7 +762,7 @@ get_pub_from_priv_key_rsa_pwd_bad_pwd(Config) -> {skip, "RSA not supported"}; {error, Error} -> {fail, {wrong_error,Error}}; - Pub -> + Pub -> ct:log("rsa Pub = ~p",[Pub]), {fail, "PWD prot pubkey fetch succeded with bad pwd!"} end. @@ -588,7 +796,7 @@ get_pub_from_priv_key_ecdsa(Config) -> ct:log("ecdsa Pub = ~p",[Pub]), sign_verify(ecdsa, sha, Priv, Pub) end. - + %%%================================================================ %%% Help for engine_stored_pub_priv_keys* test cases %%% diff --git a/lib/crypto/vsn.mk b/lib/crypto/vsn.mk index 778aff9d13..fe9c19b971 100644 --- a/lib/crypto/vsn.mk +++ b/lib/crypto/vsn.mk @@ -1 +1 @@ -CRYPTO_VSN = 4.2.2 +CRYPTO_VSN = 4.2.2.2 diff --git a/lib/diameter/doc/src/notes.xml b/lib/diameter/doc/src/notes.xml index fa1be39b5b..18c3d76be4 100644 --- a/lib/diameter/doc/src/notes.xml +++ b/lib/diameter/doc/src/notes.xml @@ -43,6 +43,24 @@ first.</p> <!-- ===================================================================== --> +<section><title>diameter 2.1.4.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix failure of incoming answer message with faulty + Experimental-Result-Code. Failure to decode the AVP + resulted in an uncaught exception, with no no + handle_answer/error callback as a consequence.</p> + <p> + Own Id: OTP-15569 Aux Id: ERIERL-302 </p> + </item> + </list> + </section> + +</section> + <section><title>diameter 2.1.4</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/diameter/src/base/diameter_gen.erl b/lib/diameter/src/base/diameter_gen.erl index 93ebe57685..d30c3d0441 100644 --- a/lib/diameter/src/base/diameter_gen.erl +++ b/lib/diameter/src/base/diameter_gen.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2017. All Rights Reserved. +%% Copyright Ericsson AB 2010-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl index d2856ae530..2d3e4a2ac9 100644 --- a/lib/diameter/src/base/diameter_traffic.erl +++ b/lib/diameter/src/base/diameter_traffic.erl @@ -1925,6 +1925,8 @@ get_avp(Dict, Name, [#diameter_header{} | Avps]) -> A = find_avp(Code, Vid, Avps), avp_decode(Dict, Name, ungroup(A)) catch + {diameter_gen, _} -> %% faulty Grouped AVP + undefined; error: _ -> undefined end; diff --git a/lib/diameter/src/diameter.appup.src b/lib/diameter/src/diameter.appup.src index 05a8c9378e..dc09b0422a 100644 --- a/lib/diameter/src/diameter.appup.src +++ b/lib/diameter/src/diameter.appup.src @@ -2,7 +2,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2018. All Rights Reserved. +%% Copyright Ericsson AB 2010-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -57,7 +57,9 @@ {"2.1", [{restart_application, diameter}]}, %% 20.1 {"2.1.1", [{restart_application, diameter}]}, %% 20.1.2 {"2.1.2", [{restart_application, diameter}]}, %% 20.1.3 - {"2.1.3", [{restart_application, diameter}]} %% 20.2 + {"2.1.3", [{restart_application, diameter}]}, %% 20.2 + {"2.1.4", [{load_module, diameter_traffic, brutal_purge, soft_purge, []}]} + %% 20.3 ], [ {"0.9", [{restart_application, diameter}]}, @@ -96,6 +98,7 @@ {"2.1", [{restart_application, diameter}]}, {"2.1.1", [{restart_application, diameter}]}, {"2.1.2", [{restart_application, diameter}]}, - {"2.1.3", [{restart_application, diameter}]} + {"2.1.3", [{restart_application, diameter}]}, + {"2.1.4", [{load_module, diameter_traffic}]} ] }. diff --git a/lib/diameter/vsn.mk b/lib/diameter/vsn.mk index b0fb4ada28..542bf9bba3 100644 --- a/lib/diameter/vsn.mk +++ b/lib/diameter/vsn.mk @@ -17,5 +17,5 @@ # %CopyrightEnd% APPLICATION = diameter -DIAMETER_VSN = 2.1.4 +DIAMETER_VSN = 2.1.4.1 APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN) diff --git a/lib/eldap/doc/src/notes.xml b/lib/eldap/doc/src/notes.xml index 8b066671ee..915148b4a2 100644 --- a/lib/eldap/doc/src/notes.xml +++ b/lib/eldap/doc/src/notes.xml @@ -31,6 +31,22 @@ </header> <p>This document describes the changes made to the Eldap application.</p> +<section><title>Eldap 1.2.3.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + A race condition at close could cause the eldap client to + exit with a badarg message as cause.</p> + <p> + Own Id: OTP-15342 Aux Id: ERIERL-242 </p> + </item> + </list> + </section> + +</section> + <section><title>Eldap 1.2.3</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/eldap/src/eldap.erl b/lib/eldap/src/eldap.erl index 625309271b..5b4cbe5b98 100644 --- a/lib/eldap/src/eldap.erl +++ b/lib/eldap/src/eldap.erl @@ -957,10 +957,19 @@ do_modify_dn_0(Data, Entry, NewRDN, DelOldRDN, NewSup, Controls) -> do_unbind(Data) -> Req = "", log2(Data, "unbind request = ~p (has no reply)~n", [Req]), - send_request(Data#eldap.fd, Data, Data#eldap.id, {unbindRequest, Req}), case Data#eldap.using_tls of - true -> ssl:close(Data#eldap.fd); - false -> gen_tcp:close(Data#eldap.fd) + true -> + send_request(Data#eldap.fd, Data, Data#eldap.id, {unbindRequest, Req}), + ssl:close(Data#eldap.fd); + false -> + OldTrapExit = process_flag(trap_exit, true), + catch send_request(Data#eldap.fd, Data, Data#eldap.id, {unbindRequest, Req}), + catch gen_tcp:close(Data#eldap.fd), + receive + {'EXIT', _From, _Reason} -> ok + after 0 -> ok + end, + process_flag(trap_exit, OldTrapExit) end, {no_reply, Data#eldap{binddn = (#eldap{})#eldap.binddn, passwd = (#eldap{})#eldap.passwd, diff --git a/lib/eldap/vsn.mk b/lib/eldap/vsn.mk index 1636b6bb6d..a420db3e58 100644 --- a/lib/eldap/vsn.mk +++ b/lib/eldap/vsn.mk @@ -1 +1 @@ -ELDAP_VSN = 1.2.3 +ELDAP_VSN = 1.2.3.1 diff --git a/lib/erl_interface/doc/src/notes.xml b/lib/erl_interface/doc/src/notes.xml index f165dde259..4310a142b0 100644 --- a/lib/erl_interface/doc/src/notes.xml +++ b/lib/erl_interface/doc/src/notes.xml @@ -31,6 +31,24 @@ </header> <p>This document describes the changes made to the Erl_interface application.</p> +<section><title>Erl_Interface 3.10.2.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Make <c>ei_connect</c> and friends also accept state + <c>ok_simultaneous</c> during handshake, which means the + other node has initiated a connection setup that will be + cancelled in favor of this connection.</p> + <p> + Own Id: OTP-15161 Aux Id: ERIERL-191 </p> + </item> + </list> + </section> + +</section> + <section><title>Erl_Interface 3.10.2</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/erl_interface/src/Makefile.in b/lib/erl_interface/src/Makefile.in index 69b5b6003d..24a9b87aeb 100644 --- a/lib/erl_interface/src/Makefile.in +++ b/lib/erl_interface/src/Makefile.in @@ -780,29 +780,31 @@ $(MDD_OBJDIR)/ei_fake_prog_mdd_cxx$(EXE): prog/ei_fake_prog.c $(MDD_EILIB) # Create dependency file using gcc -MM ########################################################################### -depend: +depend: $(TARGET)/depend.mk + +$(TARGET)/depend.mk: $(TARGET)/config.h $(gen_verbose) - $(V_colon)@echo "Generating dependency file depend.mk..." - @echo "# Generated dependency rules" > depend.mk; \ - $(V_CC) $(CFLAGS) -MM $(SOURCES) | \ + $(V_colon)echo "Generating dependency file depend.mk..." + @echo "# Generated dependency rules" > $@ + $(V_CC) $(CFLAGS) -MM $(SOURCES) | \ sed 's&$(TARGET)&\$$\(TARGET\)&g' | \ - sed 's/^.*:/\$$\(ST_OBJDIR\)\/&/' >> depend.mk; \ - echo >> depend.mk; \ - $(CC) $(CFLAGS) -MM $(SOURCES) | \ + sed 's/^.*:/\$$\(ST_OBJDIR\)\/&/' >> $@ + @echo >> $@ + $(V_CC) $(CFLAGS) -MM $(SOURCES) | \ sed 's&$(TARGET)&\$$\(TARGET\)&g' | \ - sed 's/^.*:/\$$\(MT_OBJDIR\)\/&/' >> depend.mk; \ - echo >> depend.mk; \ - $(CC) $(CFLAGS) -MM $(SOURCES) | \ + sed 's/^.*:/\$$\(MT_OBJDIR\)\/&/' >> $@ + @echo >> $@ + $(V_CC) $(CFLAGS) -MM $(SOURCES) | \ sed 's&$(TARGET)&\$$\(TARGET\)&g' | \ - sed 's/^.*:/\$$\(MD_OBJDIR\)\/&/' >> depend.mk; \ - echo >> depend.mk; \ - $(CC) $(CFLAGS) -MM $(SOURCES) | \ + sed 's/^.*:/\$$\(MD_OBJDIR\)\/&/' >> $@ + @echo >> $@ + $(V_CC) $(CFLAGS) -MM $(SOURCES) | \ sed 's&$(TARGET)&\$$\(TARGET\)&g' | \ - sed 's/^.*:/\$$\(MDD_OBJDIR\)\/&/' >> depend.mk; \ - echo >> depend.mk + sed 's/^.*:/\$$\(MDD_OBJDIR\)\/&/' >> $@ + @echo >> $@ # For some reason this has to be after 'opt' target -include depend.mk +-include $(TARGET)/depend.mk # ---------------------------------------------------- # Release Target diff --git a/lib/erl_interface/src/connect/ei_connect.c b/lib/erl_interface/src/connect/ei_connect.c index 5c01223e3d..be228ab853 100644 --- a/lib/erl_interface/src/connect/ei_connect.c +++ b/lib/erl_interface/src/connect/ei_connect.c @@ -1357,11 +1357,14 @@ static int recv_status(int fd, unsigned ms) "<- RECV_STATUS socket read failed (%d)", rlen); goto error; } - if (rlen == 3 && buf[0] == 's' && buf[1] == 'o' && - buf[2] == 'k') { + + EI_TRACE_CONN2("recv_status", + "<- RECV_STATUS (%.*s)", (rlen>20 ? 20 : rlen), buf); + + if (rlen >= 3 && buf[0] == 's' && buf[1] == 'o' && buf[2] == 'k') { + /* Expecting "sok" or "sok_simultaneous" */ if (!is_static) free(buf); - EI_TRACE_CONN0("recv_status","<- RECV_STATUS (ok)"); return 0; } error: diff --git a/lib/erl_interface/src/depend.mk b/lib/erl_interface/src/depend.mk deleted file mode 100644 index af753046e5..0000000000 --- a/lib/erl_interface/src/depend.mk +++ /dev/null @@ -1,1133 +0,0 @@ -# Generated dependency rules -$(ST_OBJDIR)/ei_connect.o: connect/ei_connect.c $(TARGET)/config.h \ - misc/eidef.h ../include/ei.h misc/eiext.h misc/ei_portio.h \ - misc/ei_internal.h connect/ei_connect_int.h misc/ei_locking.h \ - connect/eisend.h connect/eirecv.h misc/eimd5.h misc/putget.h \ - connect/ei_resolve.h epmd/ei_epmd.h -$(ST_OBJDIR)/ei_resolve.o: connect/ei_resolve.c $(TARGET)/config.h \ - misc/eidef.h ../include/ei.h connect/ei_resolve.h misc/ei_locking.h -$(ST_OBJDIR)/eirecv.o: connect/eirecv.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h connect/eirecv.h misc/ei_portio.h \ - misc/ei_internal.h misc/putget.h misc/ei_trace.h misc/show_msg.h -$(ST_OBJDIR)/send.o: connect/send.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h connect/eisend.h misc/putget.h \ - connect/ei_connect_int.h misc/ei_internal.h misc/ei_trace.h \ - misc/show_msg.h -$(ST_OBJDIR)/send_exit.o: connect/send_exit.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/ei_connect_int.h misc/ei_trace.h \ - misc/ei_internal.h misc/putget.h misc/show_msg.h -$(ST_OBJDIR)/send_reg.o: connect/send_reg.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h connect/eisend.h misc/putget.h \ - connect/ei_connect_int.h misc/ei_internal.h misc/ei_trace.h \ - misc/show_msg.h -$(ST_OBJDIR)/decode_atom.o: decode/decode_atom.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/decode_big.o: decode/decode_big.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/decode_bignum.o: decode/decode_bignum.c $(TARGET)/config.h -$(ST_OBJDIR)/decode_binary.o: decode/decode_binary.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/decode_boolean.o: decode/decode_boolean.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/decode_char.o: decode/decode_char.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/decode_double.o: decode/decode_double.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/decode_fun.o: decode/decode_fun.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/ei_malloc.h decode/decode_skip.h misc/putget.h -$(ST_OBJDIR)/decode_intlist.o: decode/decode_intlist.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/decode_list_header.o: decode/decode_list_header.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/decode_long.o: decode/decode_long.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/decode_pid.o: decode/decode_pid.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/decode_port.o: decode/decode_port.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/decode_ref.o: decode/decode_ref.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/decode_skip.o: decode/decode_skip.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - decode/decode_skip.h -$(ST_OBJDIR)/decode_string.o: decode/decode_string.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/decode_trace.o: decode/decode_trace.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/putget.h -$(ST_OBJDIR)/decode_tuple_header.o: decode/decode_tuple_header.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/decode_ulong.o: decode/decode_ulong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/decode_version.o: decode/decode_version.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/decode_longlong.o: decode/decode_longlong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/decode_ulonglong.o: decode/decode_ulonglong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/encode_atom.o: encode/encode_atom.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/encode_bignum.o: encode/encode_bignum.c $(TARGET)/config.h -$(ST_OBJDIR)/encode_binary.o: encode/encode_binary.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/encode_boolean.o: encode/encode_boolean.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/encode_char.o: encode/encode_char.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/encode_double.o: encode/encode_double.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/encode_fun.o: encode/encode_fun.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/encode_list_header.o: encode/encode_list_header.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/encode_long.o: encode/encode_long.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/encode_pid.o: encode/encode_pid.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/encode_port.o: encode/encode_port.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/encode_ref.o: encode/encode_ref.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/encode_string.o: encode/encode_string.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/encode_trace.o: encode/encode_trace.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/putget.h -$(ST_OBJDIR)/encode_tuple_header.o: encode/encode_tuple_header.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/encode_ulong.o: encode/encode_ulong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/encode_version.o: encode/encode_version.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(ST_OBJDIR)/encode_longlong.o: encode/encode_longlong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h misc/ei_x_encode.h -$(ST_OBJDIR)/encode_ulonglong.o: encode/encode_ulonglong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h misc/ei_x_encode.h -$(ST_OBJDIR)/epmd_port.o: epmd/epmd_port.c misc/ei_internal.h epmd/ei_epmd.h \ - misc/putget.h -$(ST_OBJDIR)/epmd_publish.o: epmd/epmd_publish.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/ei_internal.h \ - misc/putget.h ../include/erl_interface.h epmd/ei_epmd.h -$(ST_OBJDIR)/epmd_unpublish.o: epmd/epmd_unpublish.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/ei_internal.h \ - misc/putget.h ../include/erl_interface.h epmd/ei_epmd.h -$(ST_OBJDIR)/ei_decode_term.o: misc/ei_decode_term.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/ei_decode_term.h misc/putget.h -$(ST_OBJDIR)/ei_format.o: misc/ei_format.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/ei_malloc.h misc/ei_format.h -$(ST_OBJDIR)/ei_locking.o: misc/ei_locking.c $(TARGET)/config.h \ - misc/ei_malloc.h misc/ei_locking.h -$(ST_OBJDIR)/ei_malloc.o: misc/ei_malloc.c misc/ei_malloc.h -$(ST_OBJDIR)/ei_portio.o: misc/ei_portio.c misc/ei_portio.h misc/ei_internal.h -$(ST_OBJDIR)/ei_printterm.o: misc/ei_printterm.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/ei_printterm.h misc/ei_malloc.h -$(ST_OBJDIR)/ei_pthreads.o: misc/ei_pthreads.c $(TARGET)/config.h \ - ../include/ei.h misc/ei_locking.h -$(ST_OBJDIR)/ei_trace.o: misc/ei_trace.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/ei_trace.h -$(ST_OBJDIR)/ei_x_encode.o: misc/ei_x_encode.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/ei_x_encode.h \ - misc/ei_malloc.h -$(ST_OBJDIR)/eimd5.o: misc/eimd5.c misc/eimd5.h -$(ST_OBJDIR)/get_type.o: misc/get_type.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h misc/putget.h -$(ST_OBJDIR)/show_msg.o: misc/show_msg.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h misc/putget.h misc/ei_printterm.h \ - misc/ei_internal.h misc/show_msg.h -$(ST_OBJDIR)/ei_compat.o: misc/ei_compat.c ../include/ei.h misc/ei_internal.h -$(ST_OBJDIR)/hash_dohash.o: registry/hash_dohash.c registry/hash.h ../include/ei.h -$(ST_OBJDIR)/hash_foreach.o: registry/hash_foreach.c registry/hash.h ../include/ei.h -$(ST_OBJDIR)/hash_freetab.o: registry/hash_freetab.c registry/hash.h ../include/ei.h -$(ST_OBJDIR)/hash_insert.o: registry/hash_insert.c registry/hash.h ../include/ei.h -$(ST_OBJDIR)/hash_isprime.o: registry/hash_isprime.c registry/hash.h ../include/ei.h -$(ST_OBJDIR)/hash_lookup.o: registry/hash_lookup.c registry/hash.h ../include/ei.h -$(ST_OBJDIR)/hash_newtab.o: registry/hash_newtab.c registry/hash.h ../include/ei.h -$(ST_OBJDIR)/hash_remove.o: registry/hash_remove.c registry/hash.h ../include/ei.h -$(ST_OBJDIR)/hash_resize.o: registry/hash_resize.c registry/hash.h ../include/ei.h -$(ST_OBJDIR)/hash_rlookup.o: registry/hash_rlookup.c registry/hash.h ../include/ei.h -$(ST_OBJDIR)/reg_close.o: registry/reg_close.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/reg_delete.o: registry/reg_delete.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/reg_dirty.o: registry/reg_dirty.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/reg_dump.o: registry/reg_dump.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - registry/reg.h registry/hash.h connect/eisend.h connect/eirecv.h \ - connect/ei_connect_int.h -$(ST_OBJDIR)/reg_free.o: registry/reg_free.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/reg_get.o: registry/reg_get.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/reg_getf.o: registry/reg_getf.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/reg_geti.o: registry/reg_geti.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/reg_getp.o: registry/reg_getp.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/reg_gets.o: registry/reg_gets.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/reg_make.o: registry/reg_make.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/reg_open.o: registry/reg_open.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/reg_purge.o: registry/reg_purge.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/reg_resize.o: registry/reg_resize.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/reg_restore.o: registry/reg_restore.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - registry/reg.h registry/hash.h connect/eisend.h connect/eirecv.h \ - connect/ei_connect_int.h -$(ST_OBJDIR)/reg_set.o: registry/reg_set.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/reg_setf.o: registry/reg_setf.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/reg_seti.o: registry/reg_seti.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/reg_setp.o: registry/reg_setp.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/reg_sets.o: registry/reg_sets.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/reg_stat.o: registry/reg_stat.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/reg_tabstat.o: registry/reg_tabstat.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(ST_OBJDIR)/decode_term.o: legacy/decode_term.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h ../include/erl_interface.h -$(ST_OBJDIR)/encode_term.o: legacy/encode_term.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h misc/ei_x_encode.h ../include/erl_interface.h \ - legacy/erl_marshal.h legacy/erl_eterm.h legacy/portability.h -$(ST_OBJDIR)/erl_connect.o: legacy/erl_connect.c $(TARGET)/config.h \ - ../include/erl_interface.h ../include/ei.h legacy/erl_config.h \ - legacy/erl_connect.h legacy/erl_eterm.h legacy/portability.h \ - legacy/erl_malloc.h misc/putget.h connect/ei_connect_int.h \ - misc/ei_locking.h epmd/ei_epmd.h misc/ei_internal.h -$(ST_OBJDIR)/erl_error.o: legacy/erl_error.c $(TARGET)/config.h \ - ../include/erl_interface.h ../include/ei.h legacy/erl_error.h -$(ST_OBJDIR)/erl_eterm.o: legacy/erl_eterm.c misc/ei_locking.h \ - $(TARGET)/config.h connect/ei_resolve.h \ - ../include/erl_interface.h ../include/ei.h legacy/erl_eterm.h \ - legacy/portability.h legacy/erl_malloc.h legacy/erl_marshal.h \ - legacy/erl_error.h legacy/erl_internal.h misc/ei_internal.h -$(ST_OBJDIR)/erl_fix_alloc.o: legacy/erl_fix_alloc.c $(TARGET)/config.h \ - misc/ei_locking.h ../include/erl_interface.h ../include/ei.h \ - legacy/erl_error.h legacy/erl_malloc.h legacy/erl_fix_alloc.h \ - legacy/erl_eterm.h legacy/portability.h -$(ST_OBJDIR)/erl_format.o: legacy/erl_format.c ../include/erl_interface.h \ - ../include/ei.h legacy/erl_eterm.h legacy/portability.h \ - legacy/erl_malloc.h legacy/erl_error.h legacy/erl_internal.h -$(ST_OBJDIR)/erl_malloc.o: legacy/erl_malloc.c ../include/erl_interface.h \ - ../include/ei.h legacy/erl_fix_alloc.h legacy/erl_malloc.h \ - legacy/erl_internal.h legacy/erl_eterm.h legacy/portability.h \ - misc/ei_malloc.h -$(ST_OBJDIR)/erl_marshal.o: legacy/erl_marshal.c $(TARGET)/config.h \ - ../include/erl_interface.h ../include/ei.h legacy/erl_marshal.h \ - legacy/erl_eterm.h legacy/portability.h legacy/erl_malloc.h \ - legacy/erl_error.h legacy/erl_internal.h misc/eiext.h misc/putget.h -$(ST_OBJDIR)/erl_timeout.o: legacy/erl_timeout.c $(TARGET)/config.h \ - legacy/erl_timeout.h -$(ST_OBJDIR)/global_names.o: legacy/global_names.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/eirecv.h connect/ei_connect_int.h \ - ../include/erl_interface.h legacy/erl_connect.h -$(ST_OBJDIR)/global_register.o: legacy/global_register.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/eirecv.h ../include/erl_interface.h -$(ST_OBJDIR)/global_unregister.o: legacy/global_unregister.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/eirecv.h connect/ei_connect_int.h \ - ../include/erl_interface.h legacy/erl_connect.h -$(ST_OBJDIR)/global_whereis.o: legacy/global_whereis.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/eirecv.h connect/ei_connect_int.h \ - ../include/erl_interface.h legacy/erl_connect.h - -$(MT_OBJDIR)/ei_connect.o: connect/ei_connect.c $(TARGET)/config.h \ - misc/eidef.h ../include/ei.h misc/eiext.h misc/ei_portio.h \ - misc/ei_internal.h connect/ei_connect_int.h misc/ei_locking.h \ - connect/eisend.h connect/eirecv.h misc/eimd5.h misc/putget.h \ - connect/ei_resolve.h epmd/ei_epmd.h -$(MT_OBJDIR)/ei_resolve.o: connect/ei_resolve.c $(TARGET)/config.h \ - misc/eidef.h ../include/ei.h connect/ei_resolve.h misc/ei_locking.h -$(MT_OBJDIR)/eirecv.o: connect/eirecv.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h connect/eirecv.h misc/ei_portio.h \ - misc/ei_internal.h misc/putget.h misc/ei_trace.h misc/show_msg.h -$(MT_OBJDIR)/send.o: connect/send.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h connect/eisend.h misc/putget.h \ - connect/ei_connect_int.h misc/ei_internal.h misc/ei_trace.h \ - misc/show_msg.h -$(MT_OBJDIR)/send_exit.o: connect/send_exit.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/ei_connect_int.h misc/ei_trace.h \ - misc/ei_internal.h misc/putget.h misc/show_msg.h -$(MT_OBJDIR)/send_reg.o: connect/send_reg.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h connect/eisend.h misc/putget.h \ - connect/ei_connect_int.h misc/ei_internal.h misc/ei_trace.h \ - misc/show_msg.h -$(MT_OBJDIR)/decode_atom.o: decode/decode_atom.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/decode_big.o: decode/decode_big.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/decode_bignum.o: decode/decode_bignum.c $(TARGET)/config.h -$(MT_OBJDIR)/decode_binary.o: decode/decode_binary.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/decode_boolean.o: decode/decode_boolean.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/decode_char.o: decode/decode_char.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/decode_double.o: decode/decode_double.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/decode_fun.o: decode/decode_fun.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/ei_malloc.h decode/decode_skip.h misc/putget.h -$(MT_OBJDIR)/decode_intlist.o: decode/decode_intlist.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/decode_list_header.o: decode/decode_list_header.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/decode_long.o: decode/decode_long.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/decode_pid.o: decode/decode_pid.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/decode_port.o: decode/decode_port.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/decode_ref.o: decode/decode_ref.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/decode_skip.o: decode/decode_skip.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - decode/decode_skip.h -$(MT_OBJDIR)/decode_string.o: decode/decode_string.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/decode_trace.o: decode/decode_trace.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/putget.h -$(MT_OBJDIR)/decode_tuple_header.o: decode/decode_tuple_header.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/decode_ulong.o: decode/decode_ulong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/decode_version.o: decode/decode_version.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/decode_longlong.o: decode/decode_longlong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/decode_ulonglong.o: decode/decode_ulonglong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/encode_atom.o: encode/encode_atom.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/encode_bignum.o: encode/encode_bignum.c $(TARGET)/config.h -$(MT_OBJDIR)/encode_binary.o: encode/encode_binary.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/encode_boolean.o: encode/encode_boolean.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/encode_char.o: encode/encode_char.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/encode_double.o: encode/encode_double.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/encode_fun.o: encode/encode_fun.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/encode_list_header.o: encode/encode_list_header.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/encode_long.o: encode/encode_long.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/encode_pid.o: encode/encode_pid.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/encode_port.o: encode/encode_port.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/encode_ref.o: encode/encode_ref.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/encode_string.o: encode/encode_string.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/encode_trace.o: encode/encode_trace.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/putget.h -$(MT_OBJDIR)/encode_tuple_header.o: encode/encode_tuple_header.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/encode_ulong.o: encode/encode_ulong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/encode_version.o: encode/encode_version.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MT_OBJDIR)/encode_longlong.o: encode/encode_longlong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h misc/ei_x_encode.h -$(MT_OBJDIR)/encode_ulonglong.o: encode/encode_ulonglong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h misc/ei_x_encode.h -$(MT_OBJDIR)/epmd_port.o: epmd/epmd_port.c misc/ei_internal.h epmd/ei_epmd.h \ - misc/putget.h -$(MT_OBJDIR)/epmd_publish.o: epmd/epmd_publish.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/ei_internal.h \ - misc/putget.h ../include/erl_interface.h epmd/ei_epmd.h -$(MT_OBJDIR)/epmd_unpublish.o: epmd/epmd_unpublish.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/ei_internal.h \ - misc/putget.h ../include/erl_interface.h epmd/ei_epmd.h -$(MT_OBJDIR)/ei_decode_term.o: misc/ei_decode_term.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/ei_decode_term.h misc/putget.h -$(MT_OBJDIR)/ei_format.o: misc/ei_format.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/ei_malloc.h misc/ei_format.h -$(MT_OBJDIR)/ei_locking.o: misc/ei_locking.c $(TARGET)/config.h \ - misc/ei_malloc.h misc/ei_locking.h -$(MT_OBJDIR)/ei_malloc.o: misc/ei_malloc.c misc/ei_malloc.h -$(MT_OBJDIR)/ei_portio.o: misc/ei_portio.c misc/ei_portio.h misc/ei_internal.h -$(MT_OBJDIR)/ei_printterm.o: misc/ei_printterm.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/ei_printterm.h misc/ei_malloc.h -$(MT_OBJDIR)/ei_pthreads.o: misc/ei_pthreads.c $(TARGET)/config.h \ - ../include/ei.h misc/ei_locking.h -$(MT_OBJDIR)/ei_trace.o: misc/ei_trace.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/ei_trace.h -$(MT_OBJDIR)/ei_x_encode.o: misc/ei_x_encode.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/ei_x_encode.h \ - misc/ei_malloc.h -$(MT_OBJDIR)/eimd5.o: misc/eimd5.c misc/eimd5.h -$(MT_OBJDIR)/get_type.o: misc/get_type.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h misc/putget.h -$(MT_OBJDIR)/show_msg.o: misc/show_msg.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h misc/putget.h misc/ei_printterm.h \ - misc/ei_internal.h misc/show_msg.h -$(MT_OBJDIR)/ei_compat.o: misc/ei_compat.c ../include/ei.h misc/ei_internal.h -$(MT_OBJDIR)/hash_dohash.o: registry/hash_dohash.c registry/hash.h ../include/ei.h -$(MT_OBJDIR)/hash_foreach.o: registry/hash_foreach.c registry/hash.h ../include/ei.h -$(MT_OBJDIR)/hash_freetab.o: registry/hash_freetab.c registry/hash.h ../include/ei.h -$(MT_OBJDIR)/hash_insert.o: registry/hash_insert.c registry/hash.h ../include/ei.h -$(MT_OBJDIR)/hash_isprime.o: registry/hash_isprime.c registry/hash.h ../include/ei.h -$(MT_OBJDIR)/hash_lookup.o: registry/hash_lookup.c registry/hash.h ../include/ei.h -$(MT_OBJDIR)/hash_newtab.o: registry/hash_newtab.c registry/hash.h ../include/ei.h -$(MT_OBJDIR)/hash_remove.o: registry/hash_remove.c registry/hash.h ../include/ei.h -$(MT_OBJDIR)/hash_resize.o: registry/hash_resize.c registry/hash.h ../include/ei.h -$(MT_OBJDIR)/hash_rlookup.o: registry/hash_rlookup.c registry/hash.h ../include/ei.h -$(MT_OBJDIR)/reg_close.o: registry/reg_close.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/reg_delete.o: registry/reg_delete.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/reg_dirty.o: registry/reg_dirty.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/reg_dump.o: registry/reg_dump.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - registry/reg.h registry/hash.h connect/eisend.h connect/eirecv.h \ - connect/ei_connect_int.h -$(MT_OBJDIR)/reg_free.o: registry/reg_free.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/reg_get.o: registry/reg_get.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/reg_getf.o: registry/reg_getf.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/reg_geti.o: registry/reg_geti.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/reg_getp.o: registry/reg_getp.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/reg_gets.o: registry/reg_gets.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/reg_make.o: registry/reg_make.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/reg_open.o: registry/reg_open.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/reg_purge.o: registry/reg_purge.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/reg_resize.o: registry/reg_resize.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/reg_restore.o: registry/reg_restore.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - registry/reg.h registry/hash.h connect/eisend.h connect/eirecv.h \ - connect/ei_connect_int.h -$(MT_OBJDIR)/reg_set.o: registry/reg_set.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/reg_setf.o: registry/reg_setf.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/reg_seti.o: registry/reg_seti.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/reg_setp.o: registry/reg_setp.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/reg_sets.o: registry/reg_sets.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/reg_stat.o: registry/reg_stat.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/reg_tabstat.o: registry/reg_tabstat.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MT_OBJDIR)/decode_term.o: legacy/decode_term.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h ../include/erl_interface.h -$(MT_OBJDIR)/encode_term.o: legacy/encode_term.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h misc/ei_x_encode.h ../include/erl_interface.h \ - legacy/erl_marshal.h legacy/erl_eterm.h legacy/portability.h -$(MT_OBJDIR)/erl_connect.o: legacy/erl_connect.c $(TARGET)/config.h \ - ../include/erl_interface.h ../include/ei.h legacy/erl_config.h \ - legacy/erl_connect.h legacy/erl_eterm.h legacy/portability.h \ - legacy/erl_malloc.h misc/putget.h connect/ei_connect_int.h \ - misc/ei_locking.h epmd/ei_epmd.h misc/ei_internal.h -$(MT_OBJDIR)/erl_error.o: legacy/erl_error.c $(TARGET)/config.h \ - ../include/erl_interface.h ../include/ei.h legacy/erl_error.h -$(MT_OBJDIR)/erl_eterm.o: legacy/erl_eterm.c misc/ei_locking.h \ - $(TARGET)/config.h connect/ei_resolve.h \ - ../include/erl_interface.h ../include/ei.h legacy/erl_eterm.h \ - legacy/portability.h legacy/erl_malloc.h legacy/erl_marshal.h \ - legacy/erl_error.h legacy/erl_internal.h misc/ei_internal.h -$(MT_OBJDIR)/erl_fix_alloc.o: legacy/erl_fix_alloc.c $(TARGET)/config.h \ - misc/ei_locking.h ../include/erl_interface.h ../include/ei.h \ - legacy/erl_error.h legacy/erl_malloc.h legacy/erl_fix_alloc.h \ - legacy/erl_eterm.h legacy/portability.h -$(MT_OBJDIR)/erl_format.o: legacy/erl_format.c ../include/erl_interface.h \ - ../include/ei.h legacy/erl_eterm.h legacy/portability.h \ - legacy/erl_malloc.h legacy/erl_error.h legacy/erl_internal.h -$(MT_OBJDIR)/erl_malloc.o: legacy/erl_malloc.c ../include/erl_interface.h \ - ../include/ei.h legacy/erl_fix_alloc.h legacy/erl_malloc.h \ - legacy/erl_internal.h legacy/erl_eterm.h legacy/portability.h \ - misc/ei_malloc.h -$(MT_OBJDIR)/erl_marshal.o: legacy/erl_marshal.c $(TARGET)/config.h \ - ../include/erl_interface.h ../include/ei.h legacy/erl_marshal.h \ - legacy/erl_eterm.h legacy/portability.h legacy/erl_malloc.h \ - legacy/erl_error.h legacy/erl_internal.h misc/eiext.h misc/putget.h -$(MT_OBJDIR)/erl_timeout.o: legacy/erl_timeout.c $(TARGET)/config.h \ - legacy/erl_timeout.h -$(MT_OBJDIR)/global_names.o: legacy/global_names.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/eirecv.h connect/ei_connect_int.h \ - ../include/erl_interface.h legacy/erl_connect.h -$(MT_OBJDIR)/global_register.o: legacy/global_register.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/eirecv.h ../include/erl_interface.h -$(MT_OBJDIR)/global_unregister.o: legacy/global_unregister.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/eirecv.h connect/ei_connect_int.h \ - ../include/erl_interface.h legacy/erl_connect.h -$(MT_OBJDIR)/global_whereis.o: legacy/global_whereis.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/eirecv.h connect/ei_connect_int.h \ - ../include/erl_interface.h legacy/erl_connect.h - -$(MD_OBJDIR)/ei_connect.o: connect/ei_connect.c $(TARGET)/config.h \ - misc/eidef.h ../include/ei.h misc/eiext.h misc/ei_portio.h \ - misc/ei_internal.h connect/ei_connect_int.h misc/ei_locking.h \ - connect/eisend.h connect/eirecv.h misc/eimd5.h misc/putget.h \ - connect/ei_resolve.h epmd/ei_epmd.h -$(MD_OBJDIR)/ei_resolve.o: connect/ei_resolve.c $(TARGET)/config.h \ - misc/eidef.h ../include/ei.h connect/ei_resolve.h misc/ei_locking.h -$(MD_OBJDIR)/eirecv.o: connect/eirecv.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h connect/eirecv.h misc/ei_portio.h \ - misc/ei_internal.h misc/putget.h misc/ei_trace.h misc/show_msg.h -$(MD_OBJDIR)/send.o: connect/send.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h connect/eisend.h misc/putget.h \ - connect/ei_connect_int.h misc/ei_internal.h misc/ei_trace.h \ - misc/show_msg.h -$(MD_OBJDIR)/send_exit.o: connect/send_exit.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/ei_connect_int.h misc/ei_trace.h \ - misc/ei_internal.h misc/putget.h misc/show_msg.h -$(MD_OBJDIR)/send_reg.o: connect/send_reg.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h connect/eisend.h misc/putget.h \ - connect/ei_connect_int.h misc/ei_internal.h misc/ei_trace.h \ - misc/show_msg.h -$(MD_OBJDIR)/decode_atom.o: decode/decode_atom.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/decode_big.o: decode/decode_big.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/decode_bignum.o: decode/decode_bignum.c $(TARGET)/config.h -$(MD_OBJDIR)/decode_binary.o: decode/decode_binary.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/decode_boolean.o: decode/decode_boolean.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/decode_char.o: decode/decode_char.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/decode_double.o: decode/decode_double.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/decode_fun.o: decode/decode_fun.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/ei_malloc.h decode/decode_skip.h misc/putget.h -$(MD_OBJDIR)/decode_intlist.o: decode/decode_intlist.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/decode_list_header.o: decode/decode_list_header.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/decode_long.o: decode/decode_long.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/decode_pid.o: decode/decode_pid.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/decode_port.o: decode/decode_port.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/decode_ref.o: decode/decode_ref.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/decode_skip.o: decode/decode_skip.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - decode/decode_skip.h -$(MD_OBJDIR)/decode_string.o: decode/decode_string.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/decode_trace.o: decode/decode_trace.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/putget.h -$(MD_OBJDIR)/decode_tuple_header.o: decode/decode_tuple_header.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/decode_ulong.o: decode/decode_ulong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/decode_version.o: decode/decode_version.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/decode_longlong.o: decode/decode_longlong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/decode_ulonglong.o: decode/decode_ulonglong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/encode_atom.o: encode/encode_atom.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/encode_bignum.o: encode/encode_bignum.c $(TARGET)/config.h -$(MD_OBJDIR)/encode_binary.o: encode/encode_binary.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/encode_boolean.o: encode/encode_boolean.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/encode_char.o: encode/encode_char.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/encode_double.o: encode/encode_double.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/encode_fun.o: encode/encode_fun.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/encode_list_header.o: encode/encode_list_header.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/encode_long.o: encode/encode_long.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/encode_pid.o: encode/encode_pid.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/encode_port.o: encode/encode_port.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/encode_ref.o: encode/encode_ref.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/encode_string.o: encode/encode_string.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/encode_trace.o: encode/encode_trace.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/putget.h -$(MD_OBJDIR)/encode_tuple_header.o: encode/encode_tuple_header.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/encode_ulong.o: encode/encode_ulong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/encode_version.o: encode/encode_version.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MD_OBJDIR)/encode_longlong.o: encode/encode_longlong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h misc/ei_x_encode.h -$(MD_OBJDIR)/encode_ulonglong.o: encode/encode_ulonglong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h misc/ei_x_encode.h -$(MD_OBJDIR)/epmd_port.o: epmd/epmd_port.c misc/ei_internal.h epmd/ei_epmd.h \ - misc/putget.h -$(MD_OBJDIR)/epmd_publish.o: epmd/epmd_publish.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/ei_internal.h \ - misc/putget.h ../include/erl_interface.h epmd/ei_epmd.h -$(MD_OBJDIR)/epmd_unpublish.o: epmd/epmd_unpublish.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/ei_internal.h \ - misc/putget.h ../include/erl_interface.h epmd/ei_epmd.h -$(MD_OBJDIR)/ei_decode_term.o: misc/ei_decode_term.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/ei_decode_term.h misc/putget.h -$(MD_OBJDIR)/ei_format.o: misc/ei_format.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/ei_malloc.h misc/ei_format.h -$(MD_OBJDIR)/ei_locking.o: misc/ei_locking.c $(TARGET)/config.h \ - misc/ei_malloc.h misc/ei_locking.h -$(MD_OBJDIR)/ei_malloc.o: misc/ei_malloc.c misc/ei_malloc.h -$(MD_OBJDIR)/ei_portio.o: misc/ei_portio.c misc/ei_portio.h misc/ei_internal.h -$(MD_OBJDIR)/ei_printterm.o: misc/ei_printterm.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/ei_printterm.h misc/ei_malloc.h -$(MD_OBJDIR)/ei_pthreads.o: misc/ei_pthreads.c $(TARGET)/config.h \ - ../include/ei.h misc/ei_locking.h -$(MD_OBJDIR)/ei_trace.o: misc/ei_trace.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/ei_trace.h -$(MD_OBJDIR)/ei_x_encode.o: misc/ei_x_encode.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/ei_x_encode.h \ - misc/ei_malloc.h -$(MD_OBJDIR)/eimd5.o: misc/eimd5.c misc/eimd5.h -$(MD_OBJDIR)/get_type.o: misc/get_type.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h misc/putget.h -$(MD_OBJDIR)/show_msg.o: misc/show_msg.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h misc/putget.h misc/ei_printterm.h \ - misc/ei_internal.h misc/show_msg.h -$(MD_OBJDIR)/ei_compat.o: misc/ei_compat.c ../include/ei.h misc/ei_internal.h -$(MD_OBJDIR)/hash_dohash.o: registry/hash_dohash.c registry/hash.h ../include/ei.h -$(MD_OBJDIR)/hash_foreach.o: registry/hash_foreach.c registry/hash.h ../include/ei.h -$(MD_OBJDIR)/hash_freetab.o: registry/hash_freetab.c registry/hash.h ../include/ei.h -$(MD_OBJDIR)/hash_insert.o: registry/hash_insert.c registry/hash.h ../include/ei.h -$(MD_OBJDIR)/hash_isprime.o: registry/hash_isprime.c registry/hash.h ../include/ei.h -$(MD_OBJDIR)/hash_lookup.o: registry/hash_lookup.c registry/hash.h ../include/ei.h -$(MD_OBJDIR)/hash_newtab.o: registry/hash_newtab.c registry/hash.h ../include/ei.h -$(MD_OBJDIR)/hash_remove.o: registry/hash_remove.c registry/hash.h ../include/ei.h -$(MD_OBJDIR)/hash_resize.o: registry/hash_resize.c registry/hash.h ../include/ei.h -$(MD_OBJDIR)/hash_rlookup.o: registry/hash_rlookup.c registry/hash.h ../include/ei.h -$(MD_OBJDIR)/reg_close.o: registry/reg_close.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/reg_delete.o: registry/reg_delete.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/reg_dirty.o: registry/reg_dirty.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/reg_dump.o: registry/reg_dump.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - registry/reg.h registry/hash.h connect/eisend.h connect/eirecv.h \ - connect/ei_connect_int.h -$(MD_OBJDIR)/reg_free.o: registry/reg_free.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/reg_get.o: registry/reg_get.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/reg_getf.o: registry/reg_getf.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/reg_geti.o: registry/reg_geti.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/reg_getp.o: registry/reg_getp.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/reg_gets.o: registry/reg_gets.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/reg_make.o: registry/reg_make.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/reg_open.o: registry/reg_open.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/reg_purge.o: registry/reg_purge.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/reg_resize.o: registry/reg_resize.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/reg_restore.o: registry/reg_restore.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - registry/reg.h registry/hash.h connect/eisend.h connect/eirecv.h \ - connect/ei_connect_int.h -$(MD_OBJDIR)/reg_set.o: registry/reg_set.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/reg_setf.o: registry/reg_setf.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/reg_seti.o: registry/reg_seti.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/reg_setp.o: registry/reg_setp.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/reg_sets.o: registry/reg_sets.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/reg_stat.o: registry/reg_stat.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/reg_tabstat.o: registry/reg_tabstat.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MD_OBJDIR)/decode_term.o: legacy/decode_term.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h ../include/erl_interface.h -$(MD_OBJDIR)/encode_term.o: legacy/encode_term.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h misc/ei_x_encode.h ../include/erl_interface.h \ - legacy/erl_marshal.h legacy/erl_eterm.h legacy/portability.h -$(MD_OBJDIR)/erl_connect.o: legacy/erl_connect.c $(TARGET)/config.h \ - ../include/erl_interface.h ../include/ei.h legacy/erl_config.h \ - legacy/erl_connect.h legacy/erl_eterm.h legacy/portability.h \ - legacy/erl_malloc.h misc/putget.h connect/ei_connect_int.h \ - misc/ei_locking.h epmd/ei_epmd.h misc/ei_internal.h -$(MD_OBJDIR)/erl_error.o: legacy/erl_error.c $(TARGET)/config.h \ - ../include/erl_interface.h ../include/ei.h legacy/erl_error.h -$(MD_OBJDIR)/erl_eterm.o: legacy/erl_eterm.c misc/ei_locking.h \ - $(TARGET)/config.h connect/ei_resolve.h \ - ../include/erl_interface.h ../include/ei.h legacy/erl_eterm.h \ - legacy/portability.h legacy/erl_malloc.h legacy/erl_marshal.h \ - legacy/erl_error.h legacy/erl_internal.h misc/ei_internal.h -$(MD_OBJDIR)/erl_fix_alloc.o: legacy/erl_fix_alloc.c $(TARGET)/config.h \ - misc/ei_locking.h ../include/erl_interface.h ../include/ei.h \ - legacy/erl_error.h legacy/erl_malloc.h legacy/erl_fix_alloc.h \ - legacy/erl_eterm.h legacy/portability.h -$(MD_OBJDIR)/erl_format.o: legacy/erl_format.c ../include/erl_interface.h \ - ../include/ei.h legacy/erl_eterm.h legacy/portability.h \ - legacy/erl_malloc.h legacy/erl_error.h legacy/erl_internal.h -$(MD_OBJDIR)/erl_malloc.o: legacy/erl_malloc.c ../include/erl_interface.h \ - ../include/ei.h legacy/erl_fix_alloc.h legacy/erl_malloc.h \ - legacy/erl_internal.h legacy/erl_eterm.h legacy/portability.h \ - misc/ei_malloc.h -$(MD_OBJDIR)/erl_marshal.o: legacy/erl_marshal.c $(TARGET)/config.h \ - ../include/erl_interface.h ../include/ei.h legacy/erl_marshal.h \ - legacy/erl_eterm.h legacy/portability.h legacy/erl_malloc.h \ - legacy/erl_error.h legacy/erl_internal.h misc/eiext.h misc/putget.h -$(MD_OBJDIR)/erl_timeout.o: legacy/erl_timeout.c $(TARGET)/config.h \ - legacy/erl_timeout.h -$(MD_OBJDIR)/global_names.o: legacy/global_names.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/eirecv.h connect/ei_connect_int.h \ - ../include/erl_interface.h legacy/erl_connect.h -$(MD_OBJDIR)/global_register.o: legacy/global_register.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/eirecv.h ../include/erl_interface.h -$(MD_OBJDIR)/global_unregister.o: legacy/global_unregister.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/eirecv.h connect/ei_connect_int.h \ - ../include/erl_interface.h legacy/erl_connect.h -$(MD_OBJDIR)/global_whereis.o: legacy/global_whereis.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/eirecv.h connect/ei_connect_int.h \ - ../include/erl_interface.h legacy/erl_connect.h - -$(MDD_OBJDIR)/ei_connect.o: connect/ei_connect.c $(TARGET)/config.h \ - misc/eidef.h ../include/ei.h misc/eiext.h misc/ei_portio.h \ - misc/ei_internal.h connect/ei_connect_int.h misc/ei_locking.h \ - connect/eisend.h connect/eirecv.h misc/eimd5.h misc/putget.h \ - connect/ei_resolve.h epmd/ei_epmd.h -$(MDD_OBJDIR)/ei_resolve.o: connect/ei_resolve.c $(TARGET)/config.h \ - misc/eidef.h ../include/ei.h connect/ei_resolve.h misc/ei_locking.h -$(MDD_OBJDIR)/eirecv.o: connect/eirecv.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h connect/eirecv.h misc/ei_portio.h \ - misc/ei_internal.h misc/putget.h misc/ei_trace.h misc/show_msg.h -$(MDD_OBJDIR)/send.o: connect/send.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h connect/eisend.h misc/putget.h \ - connect/ei_connect_int.h misc/ei_internal.h misc/ei_trace.h \ - misc/show_msg.h -$(MDD_OBJDIR)/send_exit.o: connect/send_exit.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/ei_connect_int.h misc/ei_trace.h \ - misc/ei_internal.h misc/putget.h misc/show_msg.h -$(MDD_OBJDIR)/send_reg.o: connect/send_reg.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h connect/eisend.h misc/putget.h \ - connect/ei_connect_int.h misc/ei_internal.h misc/ei_trace.h \ - misc/show_msg.h -$(MDD_OBJDIR)/decode_atom.o: decode/decode_atom.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/decode_big.o: decode/decode_big.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/decode_bignum.o: decode/decode_bignum.c $(TARGET)/config.h -$(MDD_OBJDIR)/decode_binary.o: decode/decode_binary.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/decode_boolean.o: decode/decode_boolean.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/decode_char.o: decode/decode_char.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/decode_double.o: decode/decode_double.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/decode_fun.o: decode/decode_fun.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/ei_malloc.h decode/decode_skip.h misc/putget.h -$(MDD_OBJDIR)/decode_intlist.o: decode/decode_intlist.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/decode_list_header.o: decode/decode_list_header.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/decode_long.o: decode/decode_long.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/decode_pid.o: decode/decode_pid.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/decode_port.o: decode/decode_port.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/decode_ref.o: decode/decode_ref.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/decode_skip.o: decode/decode_skip.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - decode/decode_skip.h -$(MDD_OBJDIR)/decode_string.o: decode/decode_string.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/decode_trace.o: decode/decode_trace.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/putget.h -$(MDD_OBJDIR)/decode_tuple_header.o: decode/decode_tuple_header.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/decode_ulong.o: decode/decode_ulong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/decode_version.o: decode/decode_version.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/decode_longlong.o: decode/decode_longlong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/decode_ulonglong.o: decode/decode_ulonglong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/encode_atom.o: encode/encode_atom.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/encode_bignum.o: encode/encode_bignum.c $(TARGET)/config.h -$(MDD_OBJDIR)/encode_binary.o: encode/encode_binary.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/encode_boolean.o: encode/encode_boolean.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/encode_char.o: encode/encode_char.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/encode_double.o: encode/encode_double.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/encode_fun.o: encode/encode_fun.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/encode_list_header.o: encode/encode_list_header.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/encode_long.o: encode/encode_long.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/encode_pid.o: encode/encode_pid.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/encode_port.o: encode/encode_port.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/encode_ref.o: encode/encode_ref.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/encode_string.o: encode/encode_string.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/encode_trace.o: encode/encode_trace.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/putget.h -$(MDD_OBJDIR)/encode_tuple_header.o: encode/encode_tuple_header.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/encode_ulong.o: encode/encode_ulong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/encode_version.o: encode/encode_version.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h -$(MDD_OBJDIR)/encode_longlong.o: encode/encode_longlong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h misc/ei_x_encode.h -$(MDD_OBJDIR)/encode_ulonglong.o: encode/encode_ulonglong.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h misc/ei_x_encode.h -$(MDD_OBJDIR)/epmd_port.o: epmd/epmd_port.c misc/ei_internal.h epmd/ei_epmd.h \ - misc/putget.h -$(MDD_OBJDIR)/epmd_publish.o: epmd/epmd_publish.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/ei_internal.h \ - misc/putget.h ../include/erl_interface.h epmd/ei_epmd.h -$(MDD_OBJDIR)/epmd_unpublish.o: epmd/epmd_unpublish.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/ei_internal.h \ - misc/putget.h ../include/erl_interface.h epmd/ei_epmd.h -$(MDD_OBJDIR)/ei_decode_term.o: misc/ei_decode_term.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/ei_decode_term.h misc/putget.h -$(MDD_OBJDIR)/ei_format.o: misc/ei_format.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/ei_malloc.h misc/ei_format.h -$(MDD_OBJDIR)/ei_locking.o: misc/ei_locking.c $(TARGET)/config.h \ - misc/ei_malloc.h misc/ei_locking.h -$(MDD_OBJDIR)/ei_malloc.o: misc/ei_malloc.c misc/ei_malloc.h -$(MDD_OBJDIR)/ei_portio.o: misc/ei_portio.c misc/ei_portio.h misc/ei_internal.h -$(MDD_OBJDIR)/ei_printterm.o: misc/ei_printterm.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/ei_printterm.h misc/ei_malloc.h -$(MDD_OBJDIR)/ei_pthreads.o: misc/ei_pthreads.c $(TARGET)/config.h \ - ../include/ei.h misc/ei_locking.h -$(MDD_OBJDIR)/ei_trace.o: misc/ei_trace.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/ei_trace.h -$(MDD_OBJDIR)/ei_x_encode.o: misc/ei_x_encode.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/ei_x_encode.h \ - misc/ei_malloc.h -$(MDD_OBJDIR)/eimd5.o: misc/eimd5.c misc/eimd5.h -$(MDD_OBJDIR)/get_type.o: misc/get_type.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h misc/putget.h -$(MDD_OBJDIR)/show_msg.o: misc/show_msg.c misc/eidef.h $(TARGET)/config.h \ - ../include/ei.h misc/eiext.h misc/putget.h misc/ei_printterm.h \ - misc/ei_internal.h misc/show_msg.h -$(MDD_OBJDIR)/ei_compat.o: misc/ei_compat.c ../include/ei.h misc/ei_internal.h -$(MDD_OBJDIR)/hash_dohash.o: registry/hash_dohash.c registry/hash.h ../include/ei.h -$(MDD_OBJDIR)/hash_foreach.o: registry/hash_foreach.c registry/hash.h ../include/ei.h -$(MDD_OBJDIR)/hash_freetab.o: registry/hash_freetab.c registry/hash.h ../include/ei.h -$(MDD_OBJDIR)/hash_insert.o: registry/hash_insert.c registry/hash.h ../include/ei.h -$(MDD_OBJDIR)/hash_isprime.o: registry/hash_isprime.c registry/hash.h ../include/ei.h -$(MDD_OBJDIR)/hash_lookup.o: registry/hash_lookup.c registry/hash.h ../include/ei.h -$(MDD_OBJDIR)/hash_newtab.o: registry/hash_newtab.c registry/hash.h ../include/ei.h -$(MDD_OBJDIR)/hash_remove.o: registry/hash_remove.c registry/hash.h ../include/ei.h -$(MDD_OBJDIR)/hash_resize.o: registry/hash_resize.c registry/hash.h ../include/ei.h -$(MDD_OBJDIR)/hash_rlookup.o: registry/hash_rlookup.c registry/hash.h ../include/ei.h -$(MDD_OBJDIR)/reg_close.o: registry/reg_close.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/reg_delete.o: registry/reg_delete.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/reg_dirty.o: registry/reg_dirty.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/reg_dump.o: registry/reg_dump.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - registry/reg.h registry/hash.h connect/eisend.h connect/eirecv.h \ - connect/ei_connect_int.h -$(MDD_OBJDIR)/reg_free.o: registry/reg_free.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/reg_get.o: registry/reg_get.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/reg_getf.o: registry/reg_getf.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/reg_geti.o: registry/reg_geti.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/reg_getp.o: registry/reg_getp.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/reg_gets.o: registry/reg_gets.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/reg_make.o: registry/reg_make.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/reg_open.o: registry/reg_open.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/reg_purge.o: registry/reg_purge.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/reg_resize.o: registry/reg_resize.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/reg_restore.o: registry/reg_restore.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - registry/reg.h registry/hash.h connect/eisend.h connect/eirecv.h \ - connect/ei_connect_int.h -$(MDD_OBJDIR)/reg_set.o: registry/reg_set.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/reg_setf.o: registry/reg_setf.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/reg_seti.o: registry/reg_seti.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/reg_setp.o: registry/reg_setp.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/reg_sets.o: registry/reg_sets.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/reg_stat.o: registry/reg_stat.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/reg_tabstat.o: registry/reg_tabstat.c registry/reg.h ../include/ei.h \ - registry/hash.h -$(MDD_OBJDIR)/decode_term.o: legacy/decode_term.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h ../include/erl_interface.h -$(MDD_OBJDIR)/encode_term.o: legacy/encode_term.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - misc/putget.h misc/ei_x_encode.h ../include/erl_interface.h \ - legacy/erl_marshal.h legacy/erl_eterm.h legacy/portability.h -$(MDD_OBJDIR)/erl_connect.o: legacy/erl_connect.c $(TARGET)/config.h \ - ../include/erl_interface.h ../include/ei.h legacy/erl_config.h \ - legacy/erl_connect.h legacy/erl_eterm.h legacy/portability.h \ - legacy/erl_malloc.h misc/putget.h connect/ei_connect_int.h \ - misc/ei_locking.h epmd/ei_epmd.h misc/ei_internal.h -$(MDD_OBJDIR)/erl_error.o: legacy/erl_error.c $(TARGET)/config.h \ - ../include/erl_interface.h ../include/ei.h legacy/erl_error.h -$(MDD_OBJDIR)/erl_eterm.o: legacy/erl_eterm.c misc/ei_locking.h \ - $(TARGET)/config.h connect/ei_resolve.h \ - ../include/erl_interface.h ../include/ei.h legacy/erl_eterm.h \ - legacy/portability.h legacy/erl_malloc.h legacy/erl_marshal.h \ - legacy/erl_error.h legacy/erl_internal.h misc/ei_internal.h -$(MDD_OBJDIR)/erl_fix_alloc.o: legacy/erl_fix_alloc.c $(TARGET)/config.h \ - misc/ei_locking.h ../include/erl_interface.h ../include/ei.h \ - legacy/erl_error.h legacy/erl_malloc.h legacy/erl_fix_alloc.h \ - legacy/erl_eterm.h legacy/portability.h -$(MDD_OBJDIR)/erl_format.o: legacy/erl_format.c ../include/erl_interface.h \ - ../include/ei.h legacy/erl_eterm.h legacy/portability.h \ - legacy/erl_malloc.h legacy/erl_error.h legacy/erl_internal.h -$(MDD_OBJDIR)/erl_malloc.o: legacy/erl_malloc.c ../include/erl_interface.h \ - ../include/ei.h legacy/erl_fix_alloc.h legacy/erl_malloc.h \ - legacy/erl_internal.h legacy/erl_eterm.h legacy/portability.h \ - misc/ei_malloc.h -$(MDD_OBJDIR)/erl_marshal.o: legacy/erl_marshal.c $(TARGET)/config.h \ - ../include/erl_interface.h ../include/ei.h legacy/erl_marshal.h \ - legacy/erl_eterm.h legacy/portability.h legacy/erl_malloc.h \ - legacy/erl_error.h legacy/erl_internal.h misc/eiext.h misc/putget.h -$(MDD_OBJDIR)/erl_timeout.o: legacy/erl_timeout.c $(TARGET)/config.h \ - legacy/erl_timeout.h -$(MDD_OBJDIR)/global_names.o: legacy/global_names.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/eirecv.h connect/ei_connect_int.h \ - ../include/erl_interface.h legacy/erl_connect.h -$(MDD_OBJDIR)/global_register.o: legacy/global_register.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/eirecv.h ../include/erl_interface.h -$(MDD_OBJDIR)/global_unregister.o: legacy/global_unregister.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/eirecv.h connect/ei_connect_int.h \ - ../include/erl_interface.h legacy/erl_connect.h -$(MDD_OBJDIR)/global_whereis.o: legacy/global_whereis.c misc/eidef.h \ - $(TARGET)/config.h ../include/ei.h misc/eiext.h \ - connect/eisend.h connect/eirecv.h connect/ei_connect_int.h \ - ../include/erl_interface.h legacy/erl_connect.h - diff --git a/lib/erl_interface/vsn.mk b/lib/erl_interface/vsn.mk index 8b6e91757d..4c9cc351c4 100644 --- a/lib/erl_interface/vsn.mk +++ b/lib/erl_interface/vsn.mk @@ -1,2 +1,2 @@ -EI_VSN = 3.10.2 +EI_VSN = 3.10.2.1 ERL_INTERFACE_VSN = $(EI_VSN) diff --git a/lib/ic/c_src/oe_ei_decode_wstring.c b/lib/ic/c_src/oe_ei_decode_wstring.c index 5b676fd579..d2a8270291 100644 --- a/lib/ic/c_src/oe_ei_decode_wstring.c +++ b/lib/ic/c_src/oe_ei_decode_wstring.c @@ -76,10 +76,12 @@ int oe_ei_decode_wstring(const char *buf, int *index, CORBA_wchar *p) { if (p) { /* Decoding part */ /* Allocate temporary string */ - tmp_space = (char*) malloc(length*(__OE_WCHARSZ__+1)); + tmp_space = (char*) malloc((length + 1)*sizeof(char)); - if ((error_code = ei_decode_string(buf, index, tmp_space)) < 0) - return error_code; + if ((error_code = ei_decode_string(buf, index, tmp_space)) < 0) { + free(tmp_space); + return error_code; + } /* Assign characters to wide characters */ for(tmp = 0; tmp < length; tmp++) @@ -88,7 +90,7 @@ int oe_ei_decode_wstring(const char *buf, int *index, CORBA_wchar *p) { p[tmp] = 0; /* Wide NULL */ /* Free temporary string */ - CORBA_free(tmp_space); + free(tmp_space); } else { /* Allocation counting part */ diff --git a/lib/ic/c_src/oe_ei_encode_atom.c b/lib/ic/c_src/oe_ei_encode_atom.c index 99a9fe26f0..9d2c1d5aa3 100644 --- a/lib/ic/c_src/oe_ei_encode_atom.c +++ b/lib/ic/c_src/oe_ei_encode_atom.c @@ -30,7 +30,7 @@ int oe_ei_encode_atom(CORBA_Environment *ev, const char *p) { int size = ev->_iout; size_t len = strlen(p); - if (DIRTY_ATOM_ENC_MAX(len) >= ev->_outbufsz) { + if (size + DIRTY_ATOM_ENC_MAX(len) >= ev->_outbufsz) { ei_encode_atom_len(0,&size,p,len); diff --git a/lib/ic/doc/src/notes.xml b/lib/ic/doc/src/notes.xml index 38cc77ca98..217f7ab740 100644 --- a/lib/ic/doc/src/notes.xml +++ b/lib/ic/doc/src/notes.xml @@ -31,7 +31,45 @@ <file>notes.xml</file> </header> - <section><title>IC 4.4.4</title> + <section><title>IC 4.4.4.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fixed potential buffer overflow bugs in + oe_ei_encode_long/ulong/longlong/ulonglong functions on + 64-bit architectures. These functions expect 32 bit + integers as the IDL type "long" is defined as 32 bits. + But there is nothing preventing user code from "breaking" + the interface and pass larger values on 64-bit + architectures where the C type "long" is 64 bits.</p> + <p> + Own Id: OTP-15179 Aux Id: ERIERL-208 </p> + </item> + </list> + </section> + +</section> + +<section><title>IC 4.4.4.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fixed bug in <c>ic</c> causing potential buffer overrun + in funtion <c>oe_ei_encode_atom</c>. Bug exists since + ic-4.4.4 (OTP-20.3.4).</p> + <p> + Own Id: OTP-15160 Aux Id: ERIERL-191 </p> + </item> + </list> + </section> + +</section> + +<section><title>IC 4.4.4</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/ic/include/ic.h b/lib/ic/include/ic.h index 3dc5dbd4b5..1eb9e1e9d9 100644 --- a/lib/ic/include/ic.h +++ b/lib/ic/include/ic.h @@ -251,13 +251,13 @@ extern "C" { #define __OE_MEMCHUNK__ 1024 #define __OE_VSNSZ__ 1 -#define __OE_LONGSZ__ 7 -#define __OE_LONGLONGSZ__ 7 -#define __OE_ULONGSZ__ 7 -#define __OE_ULONGLONGSZ__ 7 -#define __OE_DOUBLESZ__ 32 +#define __OE_LONGSZ__ (3+sizeof(long)) +#define __OE_LONGLONGSZ__ (3+sizeof(long)) +#define __OE_ULONGSZ__ (3+sizeof(long)) +#define __OE_ULONGLONGSZ__ (3+sizeof(long)) +#define __OE_DOUBLESZ__ 9 #define __OE_CHARSZ__ 2 -#define __OE_WCHARSZ__ 7 +#define __OE_WCHARSZ__ (3+sizeof(CORBA_wchar)) #define __OE_TUPLEHDRSZ__ 5 #define __OE_LISTHDRSZ__ 5 diff --git a/lib/ic/test/c_client_erl_server_SUITE_data/c_client.c b/lib/ic/test/c_client_erl_server_SUITE_data/c_client.c index 446b46ad82..098d9cb11b 100644 --- a/lib/ic/test/c_client_erl_server_SUITE_data/c_client.c +++ b/lib/ic/test/c_client_erl_server_SUITE_data/c_client.c @@ -389,6 +389,7 @@ int main(int argc, char **argv) strcpy(env->_regname, peer_process_name); env->_to_pid = NULL; env->_from_pid = &pid; + env->_memchunk = 32; strcpy(pid.node, this_node); pid.num = fd; diff --git a/lib/ic/test/c_client_erl_server_proto_SUITE_data/c_client.c b/lib/ic/test/c_client_erl_server_proto_SUITE_data/c_client.c index d6a78d2481..146cee66a1 100644 --- a/lib/ic/test/c_client_erl_server_proto_SUITE_data/c_client.c +++ b/lib/ic/test/c_client_erl_server_proto_SUITE_data/c_client.c @@ -392,6 +392,7 @@ int main(int argc, char **argv) strcpy(env->_regname, peer_process_name); env->_to_pid = NULL; env->_from_pid = &pid; + env->_memchunk = 32; strcpy(pid.node, this_node); pid.num = fd; diff --git a/lib/ic/test/c_client_erl_server_proto_tmo_SUITE_data/c_client.c b/lib/ic/test/c_client_erl_server_proto_tmo_SUITE_data/c_client.c index 17ef21f4f4..9658825fda 100644 --- a/lib/ic/test/c_client_erl_server_proto_tmo_SUITE_data/c_client.c +++ b/lib/ic/test/c_client_erl_server_proto_tmo_SUITE_data/c_client.c @@ -392,6 +392,7 @@ int main(int argc, char **argv) strcpy(env->_regname, peer_process_name); env->_to_pid = NULL; env->_from_pid = &pid; + env->_memchunk = 32; strcpy(pid.node, this_node); pid.num = fd; diff --git a/lib/ic/test/erl_client_c_server_SUITE_data/c_server.c b/lib/ic/test/erl_client_c_server_SUITE_data/c_server.c index f48480e8dc..32890af794 100644 --- a/lib/ic/test/erl_client_c_server_SUITE_data/c_server.c +++ b/lib/ic/test/erl_client_c_server_SUITE_data/c_server.c @@ -187,6 +187,7 @@ int main(int argc, char **argv) } env = CORBA_Environment_alloc(INBUFSZ, OUTBUFSZ); env->_fd = fd; + env->_memchunk = 32; status = 1; loop = 1; diff --git a/lib/ic/test/erl_client_c_server_proto_SUITE_data/c_server.c b/lib/ic/test/erl_client_c_server_proto_SUITE_data/c_server.c index e2ba5bd5b6..d283bf22da 100644 --- a/lib/ic/test/erl_client_c_server_proto_SUITE_data/c_server.c +++ b/lib/ic/test/erl_client_c_server_proto_SUITE_data/c_server.c @@ -187,6 +187,7 @@ int main(int argc, char **argv) } env = CORBA_Environment_alloc(INBUFSZ, OUTBUFSZ); env->_fd = fd; + env->_memchunk = 32; status = 1; loop = 1; diff --git a/lib/ic/vsn.mk b/lib/ic/vsn.mk index d35d1dce1e..8ef497ee92 100644 --- a/lib/ic/vsn.mk +++ b/lib/ic/vsn.mk @@ -1 +1 @@ -IC_VSN = 4.4.4 +IC_VSN = 4.4.4.2 diff --git a/lib/inets/doc/src/ftp.xml b/lib/inets/doc/src/ftp.xml index 42bece4d38..feea7b978a 100644 --- a/lib/inets/doc/src/ftp.xml +++ b/lib/inets/doc/src/ftp.xml @@ -548,11 +548,12 @@ <v>start_option() = {verbose, verbose()} | {debug, debug()}</v> <v>verbose() = boolean() (default is false)</v> <v>debug() = disable | debug | trace (default is disable)</v> - <v>open_option() = {ipfamily, ipfamily()} | {port, port()} | {mode, mode()} | {tls, tls_options()} | {timeout, timeout()} | {dtimeout, dtimeout()} | {progress, progress()}</v> + <v>open_option() = {ipfamily, ipfamily()} | {port, port()} | {mode, mode()} | {tls, tls_options()} | {timeout, timeout()} | {dtimeout, dtimeout()} | {progress, progress() | {sock_ctrl, sock_opts()} | {sock_data_act, sock_opts()} | {sock_data_pass, sock_opts()} }</v> <v>ipfamily() = inet | inet6 | inet6fb4 (default is inet)</v> <v>port() = integer() > 0 (default is 21)</v> <v>mode() = active | passive (default is passive)</v> <v>tls_options() = [<seealso marker="ssl:ssl#type-ssloption">ssl:ssloption()</seealso>]</v> + <v>sock_opts() = [<seealso marker="kernel:gen_tcp#type-option">gen_tcp:option()</seealso> except for ipv6_v6only, active, packet, mode, packet_size and header</v> <v>timeout() = integer() > 0 (default is 60000 milliseconds)</v> <v>dtimeout() = integer() > 0 | infinity (default is infinity)</v> <v>pogress() = ignore | {module(), function(), initial_data()} (default is ignore)</v> @@ -575,6 +576,11 @@ is used for securing both the control connection and the data sessions. </p> + <p>The options <c>sock_ctrl</c>, <c>sock_data_act</c> and <c>sock_data_pass</c> passes options down to + the underlying transport layer (tcp). The default value for <c>sock_ctrl</c> is <c>[]</c>. Both + <c>sock_data_act</c> and <c>sock_data_pass</c> uses the value of <c>sock_ctrl</c> as default value. + </p> + <p>A session opened in this way is closed using function <seealso marker="#close">close</seealso>.</p> diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index a6af1e834e..8374c0abef 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -33,7 +33,85 @@ <file>notes.xml</file> </header> - <section><title>Inets 6.5.2</title> + <section><title>Inets 6.5.2.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Do not use chunked-encoding with 1xx, 204 and 304 + responses when using mod_esi. Old behavior was not + compliant with HTTP/1.1 RFC and could cause clients to + hang when they received 1xx, 204 or 304 responses that + included an empty chunked-encoded body.</p> + <p> + Own Id: OTP-15241</p> + </item> + <item> + <p> + Add robust handling of chunked-encoded HTTP responses + with an empty body (1xx, 204, 304). Old behavior could + cause the client to hang when connecting to a faulty + server implementation.</p> + <p> + Own Id: OTP-15242</p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 6.5.2.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Change status code for no mod found to handle request to + 501</p> + <p> + Own Id: OTP-15215</p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 6.5.2.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Enhance error handling, that is mod_get will return 403 + if a path is a directory and not a file.</p> + <p> + Own Id: OTP-15192</p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 6.5.2.1</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Options added for setting low-level properties on the + underlying TCP connections. The options are: + <c>sock_ctrl</c>, <c>sock_data_act</c> and + <c>sock_data_pass</c>. See the manual for details.</p> + <p> + Own Id: OTP-15120 Aux Id: ERIERL-192 </p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 6.5.2</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/inets/src/ftp/ftp.erl b/lib/inets/src/ftp/ftp.erl index e0430654eb..ffcccdefea 100644 --- a/lib/inets/src/ftp/ftp.erl +++ b/lib/inets/src/ftp/ftp.erl @@ -94,6 +94,9 @@ %% data needed further on. caller = undefined, % term() ipfamily, % inet | inet6 | inet6fb4 + sockopts_ctrl = [], + sockopts_data_passive = [], + sockopts_data_active = [], progress = ignore, % ignore | pid() dtimeout = ?DATA_ACCEPT_TIMEOUT, % non_neg_integer() | infinity tls_upgrading_data_connection = false, @@ -138,9 +141,10 @@ open({option_list, Options}) when is_list(Options) -> try {ok, StartOptions} = start_options(Options), {ok, OpenOptions} = open_options(Options), + {ok, SockOpts} = socket_options(Options), case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of {ok, Pid} -> - call(Pid, {open, ip_comm, OpenOptions}, plain); + call(Pid, {open, ip_comm, OpenOptions, SockOpts}, plain); Error1 -> Error1 end @@ -168,9 +172,11 @@ open(Host, Opts) when is_list(Opts) -> ?fcrt("open", [{start_options, StartOptions}]), {ok, OpenOptions} = open_options([{host, Host}|Opts]), ?fcrt("open", [{open_options, OpenOptions}]), + {ok, SocketOptions} = socket_options(Opts), + ?fcrt("open", [{socket_options, SocketOptions}]), case start_link(StartOptions, []) of {ok, Pid} -> - do_open(Pid, OpenOptions, tls_options(Opts)); + do_open(Pid, OpenOptions, SocketOptions, tls_options(Opts)); Error1 -> ?fcrt("open - error", [{error1, Error1}]), Error1 @@ -181,8 +187,8 @@ open(Host, Opts) when is_list(Opts) -> Error2 end. -do_open(Pid, OpenOptions, TLSOpts) -> - case call(Pid, {open, ip_comm, OpenOptions}, plain) of +do_open(Pid, OpenOptions, SocketOptions, TLSOpts) -> + case call(Pid, {open, ip_comm, OpenOptions, SocketOptions}, plain) of {ok, Pid} -> maybe_tls_upgrade(Pid, TLSOpts); Error -> @@ -879,9 +885,10 @@ start_standalone(Options) -> try {ok, StartOptions} = start_options(Options), {ok, OpenOptions} = open_options(Options), + {ok, SocketOptions} = socket_options(Options), case start_link(StartOptions, []) of {ok, Pid} -> - call(Pid, {open, ip_comm, OpenOptions}, plain); + call(Pid, {open, ip_comm, OpenOptions, SocketOptions}, plain); Error1 -> Error1 end @@ -893,10 +900,11 @@ start_standalone(Options) -> start_service(Options) -> try {ok, StartOptions} = start_options(Options), - {ok, OpenOptions} = open_options(Options), + {ok, OpenOptions} = open_options(Options), + {ok, SocketOptions} = socket_options(Options), case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of {ok, Pid} -> - call(Pid, {open, ip_comm, OpenOptions}, plain); + call(Pid, {open, ip_comm, OpenOptions, SocketOptions}, plain); Error1 -> Error1 end @@ -1041,6 +1049,34 @@ open_options(Options) -> {ftp_extension, ValidateFtpExtension, false, ?FTP_EXT_DEFAULT}], validate_options(Options, ValidOptions, []). + + +socket_options(Options) -> + CtrlOpts = proplists:get_value(sock_ctrl, Options, []), + DataActOpts = proplists:get_value(sock_data_act, Options, CtrlOpts), + DataPassOpts = proplists:get_value(sock_data_pass, Options, CtrlOpts), + case [O || O <- lists:usort(CtrlOpts++DataPassOpts++DataActOpts), + not valid_socket_option(O)] of + [] -> + {ok, {CtrlOpts, DataPassOpts, DataActOpts}}; + Invalid -> + throw({error,{sock_opts,Invalid}}) + end. + + +valid_socket_option(inet ) -> false; +valid_socket_option(inet6 ) -> false; +valid_socket_option({ipv6_v6only, _}) -> false; +valid_socket_option({active,_} ) -> false; +valid_socket_option({packet,_} ) -> false; +valid_socket_option({mode,_} ) -> false; +valid_socket_option(binary ) -> false; +valid_socket_option(list ) -> false; +valid_socket_option({header,_} ) -> false; +valid_socket_option({packet_size,_} ) -> false; +valid_socket_option(_) -> true. + + tls_options(Options) -> %% Options will be validated by ssl application proplists:get_value(tls, Options, undefined). @@ -1182,7 +1218,7 @@ handle_call({_,latest_ctrl_response}, _, #state{latest_ctrl_response=Resp} = Sta handle_call({Pid, _}, _, #state{owner = Owner} = State) when Owner =/= Pid -> {reply, {error, not_connection_owner}, State}; -handle_call({_, {open, ip_comm, Opts}}, From, State) -> +handle_call({_, {open, ip_comm, Opts, {CtrlOpts, DataPassOpts, DataActOpts}}}, From, State) -> ?fcrd("handle_call(open)", [{opts, Opts}]), case key_search(host, Opts, undefined) of undefined -> @@ -1200,6 +1236,9 @@ handle_call({_, {open, ip_comm, Opts}}, From, State) -> mode = Mode, progress = progress(Progress), ipfamily = IpFamily, + sockopts_ctrl = CtrlOpts, + sockopts_data_passive = DataPassOpts, + sockopts_data_active = DataActOpts, dtimeout = DTimeout, ftp_extension = FtpExt}, @@ -1218,28 +1257,6 @@ handle_call({_, {open, ip_comm, Opts}}, From, State) -> end end; -handle_call({_, {open, ip_comm, Host, Opts}}, From, State) -> - Mode = key_search(mode, Opts, ?DEFAULT_MODE), - Port = key_search(port, Opts, ?FTP_PORT), - Timeout = key_search(timeout, Opts, ?CONNECTION_TIMEOUT), - DTimeout = key_search(dtimeout, Opts, ?DATA_ACCEPT_TIMEOUT), - Progress = key_search(progress, Opts, ignore), - FtpExt = key_search(ftp_extension, Opts, ?FTP_EXT_DEFAULT), - - State2 = State#state{client = From, - mode = Mode, - progress = progress(Progress), - dtimeout = DTimeout, - ftp_extension = FtpExt}, - - case setup_ctrl_connection(Host, Port, Timeout, State2) of - {ok, State3, WaitTimeout} -> - {noreply, State3, WaitTimeout}; - {error, _Reason} -> - gen_server:reply(From, {error, ehost}), - {stop, normal, State2#state{client = undefined}} - end; - handle_call({_, {open, tls_upgrade, TLSOptions}}, From, State) -> send_ctrl_message(State, mk_cmd("AUTH TLS", [])), activate_ctrl_connection(State), @@ -1822,11 +1839,12 @@ handle_ctrl_result({pos_compl, Lines}, client = From, caller = {setup_data_connection, Caller}, csock = CSock, + sockopts_data_passive = SockOpts, timeout = Timeout} = State) -> [_, PortStr | _] = lists:reverse(string:tokens(Lines, "|")), {ok, {IP, _}} = peername(CSock), - case connect(IP, list_to_integer(PortStr), Timeout, State) of + case connect(IP, list_to_integer(PortStr), SockOpts, Timeout, State) of {ok, _, Socket} -> handle_caller(State#state{caller = Caller, dsock = {tcp, Socket}}); {error, _Reason} = Error -> @@ -1839,7 +1857,8 @@ handle_ctrl_result({pos_compl, Lines}, ipfamily = inet, client = From, caller = {setup_data_connection, Caller}, - timeout = Timeout, + timeout = Timeout, + sockopts_data_passive = SockOpts, ftp_extension = false} = State) -> {_, [?LEFT_PAREN | Rest]} = @@ -1853,7 +1872,7 @@ handle_ctrl_result({pos_compl, Lines}, Port = (P1 * 256) + P2, ?DBG('<--data tcp connect to ~p:~p, Caller=~p~n',[IP,Port,Caller]), - case connect(IP, Port, Timeout, State) of + case connect(IP, Port, SockOpts, Timeout, State) of {ok, _, Socket} -> handle_caller(State#state{caller = Caller, dsock = {tcp,Socket}}); {error, _Reason} = Error -> @@ -1868,13 +1887,14 @@ handle_ctrl_result({pos_compl, Lines}, caller = {setup_data_connection, Caller}, csock = CSock, timeout = Timeout, + sockopts_data_passive = SockOpts, ftp_extension = true} = State) -> [_, PortStr | _] = lists:reverse(string:tokens(Lines, "|")), {ok, {IP, _}} = peername(CSock), ?DBG('<--data tcp connect to ~p:~p, Caller=~p~n',[IP,PortStr,Caller]), - case connect(IP, list_to_integer(PortStr), Timeout, State) of + case connect(IP, list_to_integer(PortStr), SockOpts, Timeout, State) of {ok, _, Socket} -> handle_caller(State#state{caller = Caller, dsock = {tcp, Socket}}); {error, _Reason} = Error -> @@ -2238,9 +2258,9 @@ handle_caller(#state{caller = {transfer_data, {Cmd, Bin, RemoteFile}}} = %% Connect to FTP server at Host (default is TCP port 21) %% in order to establish a control connection. -setup_ctrl_connection(Host, Port, Timeout, State) -> +setup_ctrl_connection(Host, Port, Timeout, #state{sockopts_ctrl = SockOpts} = State) -> MsTime = erlang:monotonic_time(), - case connect(Host, Port, Timeout, State) of + case connect(Host, Port, SockOpts, Timeout, State) of {ok, IpFam, CSock} -> NewState = State#state{csock = {tcp, CSock}, ipfamily = IpFam}, activate_ctrl_connection(NewState), @@ -2258,12 +2278,15 @@ setup_ctrl_connection(Host, Port, Timeout, State) -> setup_data_connection(#state{mode = active, caller = Caller, csock = CSock, + sockopts_data_active = SockOpts, ftp_extension = FtpExt} = State) -> case (catch sockname(CSock)) of - {ok, {{_, _, _, _, _, _, _, _} = IP, _}} -> + {ok, {{_, _, _, _, _, _, _, _} = IP0, _}} -> + IP = proplists:get_value(ip, SockOpts, IP0), {ok, LSock} = gen_tcp:listen(0, [{ip, IP}, {active, false}, - inet6, binary, {packet, 0}]), + inet6, binary, {packet, 0} | + lists:keydelete(ip,1,SockOpts)]), {ok, {_, Port}} = sockname({tcp,LSock}), IpAddress = inet_parse:ntoa(IP), Cmd = mk_cmd("EPRT |2|~s|~p|", [IpAddress, Port]), @@ -2271,9 +2294,11 @@ setup_data_connection(#state{mode = active, activate_ctrl_connection(State), {noreply, State#state{caller = {setup_data_connection, {LSock, Caller}}}}; - {ok, {{_,_,_,_} = IP, _}} -> + {ok, {{_,_,_,_} = IP0, _}} -> + IP = proplists:get_value(ip, SockOpts, IP0), {ok, LSock} = gen_tcp:listen(0, [{ip, IP}, {active, false}, - binary, {packet, 0}]), + binary, {packet, 0} | + lists:keydelete(ip,1,SockOpts)]), {ok, Port} = inet:port(LSock), case FtpExt of false -> @@ -2312,41 +2337,41 @@ setup_data_connection(#state{mode = passive, ipfamily = inet, activate_ctrl_connection(State), {noreply, State#state{caller = {setup_data_connection, Caller}}}. -connect(Host, Port, Timeout, #state{ipfamily = inet = IpFam}) -> - connect2(Host, Port, IpFam, Timeout); +connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet = IpFam}) -> + connect2(Host, Port, IpFam, SockOpts, Timeout); -connect(Host, Port, Timeout, #state{ipfamily = inet6 = IpFam}) -> - connect2(Host, Port, IpFam, Timeout); +connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet6 = IpFam}) -> + connect2(Host, Port, IpFam, SockOpts, Timeout); -connect(Host, Port, Timeout, #state{ipfamily = inet6fb4}) -> +connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet6fb4}) -> case inet:getaddr(Host, inet6) of {ok, {0, 0, 0, 0, 0, 16#ffff, _, _} = IPv6} -> case inet:getaddr(Host, inet) of {ok, IPv4} -> IpFam = inet, - connect2(IPv4, Port, IpFam, Timeout); + connect2(IPv4, Port, IpFam, SockOpts, Timeout); _ -> IpFam = inet6, - connect2(IPv6, Port, IpFam, Timeout) + connect2(IPv6, Port, IpFam, SockOpts, Timeout) end; {ok, IPv6} -> IpFam = inet6, - connect2(IPv6, Port, IpFam, Timeout); + connect2(IPv6, Port, IpFam, SockOpts, Timeout); _ -> case inet:getaddr(Host, inet) of {ok, IPv4} -> IpFam = inet, - connect2(IPv4, Port, IpFam, Timeout); + connect2(IPv4, Port, IpFam, SockOpts, Timeout); Error -> Error end end. -connect2(Host, Port, IpFam, Timeout) -> - Opts = [IpFam, binary, {packet, 0}, {active, false}], +connect2(Host, Port, IpFam, SockOpts, Timeout) -> + Opts = [IpFam, binary, {packet, 0}, {active, false} | SockOpts], case gen_tcp:connect(Host, Port, Opts, Timeout) of {ok, Sock} -> {ok, IpFam, Sock}; diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl index 26e4f4e699..1a2ce277bf 100644 --- a/lib/inets/src/http_client/httpc_handler.erl +++ b/lib/inets/src/http_client/httpc_handler.erl @@ -980,13 +980,23 @@ handle_http_body(_, #state{status = {ssl_tunnel, Request}, NewState = answer_request(Request, ClientErrMsg, State), {stop, normal, NewState}; -handle_http_body(<<>>, #state{status_line = {_,304, _}} = State) -> +%% All 1xx (informational), 204 (no content), and 304 (not modified) +%% responses MUST NOT include a message-body, and thus are always +%% terminated by the first empty line after the header fields. +%% This implies that chunked encoding MUST NOT be used for these +%% status codes. +handle_http_body(<<>>, #state{headers = Headers, + status_line = {_,StatusCode, _}} = State) + when Headers#http_response_h.'transfer-encoding' =/= "chunked" andalso + (StatusCode =:= 204 orelse %% No Content + StatusCode =:= 304 orelse %% Not Modified + 100 =< StatusCode andalso StatusCode =< 199) -> %% Informational handle_response(State#state{body = <<>>}); -handle_http_body(<<>>, #state{status_line = {_,204, _}} = State) -> - handle_response(State#state{body = <<>>}); -handle_http_body(<<>>, #state{request = #request{method = head}} = State) -> +handle_http_body(<<>>, #state{headers = Headers, + request = #request{method = head}} = State) + when Headers#http_response_h.'transfer-encoding' =/= "chunked" -> handle_response(State#state{body = <<>>}); handle_http_body(Body, #state{headers = Headers, diff --git a/lib/inets/src/http_server/httpd_example.erl b/lib/inets/src/http_server/httpd_example.erl index 47a8c48d01..dbf7c44ad3 100644 --- a/lib/inets/src/http_server/httpd_example.erl +++ b/lib/inets/src/http_server/httpd_example.erl @@ -22,7 +22,7 @@ -export([print/1]). -export([get/2, put/2, post/2, yahoo/2, test1/2, get_bin/2, peer/2,new_status_and_location/2]). --export([newformat/3, post_chunked/3]). +-export([newformat/3, post_chunked/3, post_204/3]). %% These are used by the inets test-suite -export([delay/1, chunk_timeout/3]). @@ -151,6 +151,12 @@ post_chunked(SessionID, _Env, {last, _Body, undefined} = _Bodychunk) -> post_chunked(_, _, _Body) -> exit(body_not_chunked). +post_204(SessionID, _Env, _Input) -> + mod_esi:deliver(SessionID, + ["Status: 204 No Content" ++ "\r\n\r\n"]), + mod_esi:deliver(SessionID, []). + + newformat(SessionID,_,_) -> mod_esi:deliver(SessionID, "Content-Type:text/html\r\n\r\n"), mod_esi:deliver(SessionID, top("new esi format test")), diff --git a/lib/inets/src/http_server/httpd_file.erl b/lib/inets/src/http_server/httpd_file.erl index 4d419172d0..fb71834e95 100644 --- a/lib/inets/src/http_server/httpd_file.erl +++ b/lib/inets/src/http_server/httpd_file.erl @@ -33,6 +33,9 @@ handle_error(enoent, Op, ModData, Path) -> handle_error(enotdir, Op, ModData, Path) -> handle_error(404, Op, ModData, Path, ": A component of the file name is not a directory"); +handle_error(eisdir, Op, ModData, Path) -> + handle_error(403, Op, ModData, Path, + ":Ilegal operation expected a file not a directory"); handle_error(emfile, Op, _ModData, Path) -> handle_error(500, Op, none, Path, ": Too many open files"); handle_error({enfile,_}, Op, _ModData, Path) -> diff --git a/lib/inets/src/http_server/httpd_response.erl b/lib/inets/src/http_server/httpd_response.erl index 6b9053fda6..0bcdee1e16 100644 --- a/lib/inets/src/http_server/httpd_response.erl +++ b/lib/inets/src/http_server/httpd_response.erl @@ -61,8 +61,12 @@ generate_and_send_response(#mod{config_db = ConfigDB} = ModData) -> {StatusCode, Response} -> %% Old way send_response_old(ModData, StatusCode, Response), ok; - undefined -> - send_status(ModData, 500, none), + undefined -> + %% Happens when no mod_* + %% handles the request + send_status(ModData, 501, {ModData#mod.method, + ModData#mod.request_uri, + ModData#mod.http_version}), ok end end diff --git a/lib/inets/src/http_server/mod_esi.erl b/lib/inets/src/http_server/mod_esi.erl index 3206d957d9..9fbedce6aa 100644 --- a/lib/inets/src/http_server/mod_esi.erl +++ b/lib/inets/src/http_server/mod_esi.erl @@ -394,7 +394,16 @@ deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid, Timeout) -> Continue; {Headers, Body} -> {ok, NewHeaders, StatusCode} = httpd_esi:handle_headers(Headers), - IsDisableChunkedSend = httpd_response:is_disable_chunked_send(Db), + %% All 1xx (informational), 204 (no content), and 304 (not modified) + %% responses MUST NOT include a message-body, and thus are always + %% terminated by the first empty line after the header fields. + %% This implies that chunked encoding MUST NOT be used for these + %% status codes. + IsDisableChunkedSend = + httpd_response:is_disable_chunked_send(Db) orelse + StatusCode =:= 204 orelse %% No Content + StatusCode =:= 304 orelse %% Not Modified + (100 =< StatusCode andalso StatusCode =< 199), %% Informational case (ModData#mod.http_version =/= "HTTP/1.1") or (IsDisableChunkedSend) of true -> @@ -405,8 +414,8 @@ deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid, Timeout) -> send_headers(ModData, StatusCode, [{"transfer-encoding", "chunked"} | NewHeaders]) - end, - handle_body(Pid, ModData, Body, Timeout, length(Body), + end, + handle_body(Pid, ModData, Body, Timeout, length(Body), IsDisableChunkedSend); timeout -> send_headers(ModData, 504, [{"connection", "close"}]), diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl index d723fd0460..f3898e1b74 100644 --- a/lib/inets/test/httpc_SUITE.erl +++ b/lib/inets/test/httpc_SUITE.erl @@ -157,7 +157,8 @@ misc() -> [ server_does_not_exist, timeout_memory_leak, - wait_for_whole_response + wait_for_whole_response, + post_204_chunked ]. %%-------------------------------------------------------------------- @@ -1243,6 +1244,59 @@ wait_for_whole_response(Config) when is_list(Config) -> ReqSeqNumServer ! shutdown. %%-------------------------------------------------------------------- +post_204_chunked() -> + [{doc,"Test that chunked encoded 204 responses do not freeze the http client"}]. +post_204_chunked(_Config) -> + Msg = "HTTP/1.1 204 No Content\r\n" ++ + "Date: Thu, 23 Aug 2018 13:36:29 GMT\r\n" ++ + "Content-Type: text/html\r\n" ++ + "Server: inets/6.5.2.3\r\n" ++ + "Cache-Control: no-cache\r\n" ++ + "Pragma: no-cache\r\n" ++ + "Expires: Fri, 24 Aug 2018 07:49:35 GMT\r\n" ++ + "Transfer-Encoding: chunked\r\n" ++ + "\r\n", + Chunk = "0\r\n\r\n", + + {ok, ListenSocket} = gen_tcp:listen(0, [{active,once}, binary]), + {ok,{_,Port}} = inet:sockname(ListenSocket), + spawn(fun () -> custom_server(Msg, Chunk, ListenSocket) end), + + {ok,Host} = inet:gethostname(), + End = "/cgi-bin/erl/httpd_example:post_204", + URL = ?URL_START ++ Host ++ ":" ++ integer_to_list(Port) ++ End, + {ok, _} = httpc:request(post, {URL, [], "text/html", []}, [], []), + timer:sleep(500), + %% Second request times out in the faulty case. + {ok, _} = httpc:request(post, {URL, [], "text/html", []}, [], []). + +custom_server(Msg, Chunk, ListenSocket) -> + {ok, Accept} = gen_tcp:accept(ListenSocket), + receive_packet(), + send_response(Msg, Chunk, Accept), + custom_server_loop(Msg, Chunk, Accept). + +custom_server_loop(Msg, Chunk, Accept) -> + receive_packet(), + send_response(Msg, Chunk, Accept), + custom_server_loop(Msg, Chunk, Accept). + +send_response(Msg, Chunk, Socket) -> + inet:setopts(Socket, [{active, once}]), + gen_tcp:send(Socket, Msg), + timer:sleep(250), + gen_tcp:send(Socket, Chunk). + +receive_packet() -> + receive + {tcp, _, Msg} -> + ct:log("Message received: ~p", [Msg]) + after + 1000 -> + ct:fail("Timeout: did not recive packet") + end. + +%%-------------------------------------------------------------------- stream_fun_server_close() -> [{doc, "Test that an error msg is received when using a receiver fun as stream target"}]. stream_fun_server_close(Config) when is_list(Config) -> diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl index 9a85c51d24..90192e633c 100644 --- a/lib/inets/test/httpd_SUITE.erl +++ b/lib/inets/test/httpd_SUITE.erl @@ -75,6 +75,8 @@ all() -> {group, http_mime_types}, {group, http_logging}, {group, http_post}, + {group, http_not_sup}, + {group, https_not_sup}, mime_types_format ]. @@ -102,6 +104,8 @@ groups() -> {http_reload, [], [{group, reload}]}, {https_reload, [], [{group, reload}]}, {http_post, [], [{group, post}]}, + {http_not_sup, [], [{group, not_sup}]}, + {https_not_sup, [], [{group, not_sup}]}, {http_mime_types, [], [alias_1_1, alias_1_0, alias_0_9]}, {limit, [], [max_clients_1_1, max_clients_1_0, max_clients_0_9]}, {custom, [], [customize, add_default]}, @@ -114,7 +118,7 @@ groups() -> disturbing_1_0, disturbing_0_9 ]}, - {post, [], [chunked_post, chunked_chunked_encoded_post]}, + {post, [], [chunked_post, chunked_chunked_encoded_post, post_204]}, {basic_auth, [], [basic_auth_1_1, basic_auth_1_0, basic_auth_0_9]}, {auth_api, [], [auth_api_1_1, auth_api_1_0, auth_api_0_9 ]}, @@ -131,7 +135,8 @@ groups() -> trace, range, if_modified_since, mod_esi_chunk_timeout, esi_put, esi_post] ++ http_head() ++ http_get() ++ load()}, {http_1_0, [], [host, cgi, trace] ++ http_head() ++ http_get() ++ load()}, - {http_0_9, [], http_head() ++ http_get() ++ load()} + {http_0_9, [], http_head() ++ http_get() ++ load()}, + {not_sup, [], [put_not_sup]} ]. basic_groups ()-> @@ -203,7 +208,8 @@ init_per_group(Group, Config0) when Group == https_basic; Group == https_auth_api_dets; Group == https_auth_api_mnesia; Group == https_security; - Group == https_reload + Group == https_reload; + Group == https_not_sup -> catch crypto:stop(), try crypto:start() of @@ -222,6 +228,7 @@ init_per_group(Group, Config0) when Group == http_basic; Group == http_auth_api_mnesia; Group == http_security; Group == http_reload; + Group == http_not_sup; Group == http_post; Group == http_mime_types -> @@ -268,6 +275,8 @@ init_per_group(http_logging, Config) -> ServerRoot = proplists:get_value(server_root, Config1), Path = ServerRoot ++ "/httpd_log_transfer", [{transfer_log, Path} | Config1]; +init_per_group(not_sup, Config) -> + [{http_version, "HTTP/1.1"} | Config]; init_per_group(_, Config) -> Config. @@ -441,8 +450,19 @@ get(Config) when is_list(Config) -> {header, "Content-Type", "text/html"}, {header, "Date"}, {header, "Server"}, + {version, Version}]), + + ok = httpd_test_lib:verify_request(proplists:get_value(type, Config), Host, + proplists:get_value(port, Config), + transport_opts(Type, Config), + proplists:get_value(node, Config), + http_request("GET /open/ ", Version, Host), + [{statuscode, 403}, + {header, "Content-Type", "text/html"}, + {header, "Date"}, + {header, "Server"}, {version, Version}]). - + basic_auth_1_1(Config) when is_list(Config) -> basic_auth([{http_version, "HTTP/1.1"} | Config]). @@ -726,6 +746,42 @@ chunked_chunked_encoded_post(Config) when is_list(Config) -> [{http_version, "HTTP/1.1"} | Config], [{statuscode, 200}]). +%%------------------------------------------------------------------------- +post_204() -> + [{doc,"Test that 204 responses are not chunk encoded"}]. +post_204(Config) -> + Host = proplists:get_value(host, Config), + Port = proplists:get_value(port, Config), + SockType = proplists:get_value(type, Config), + TranspOpts = transport_opts(SockType, Config), + Request = "POST /cgi-bin/erl/httpd_example:post_204 ", + + try inets_test_lib:connect_bin(SockType, Host, Port, TranspOpts) of + {ok, Socket} -> + RequestStr = http_request(Request, "HTTP/1.1", Host), + ok = inets_test_lib:send(SockType, Socket, RequestStr), + receive + {tcp, Socket, Data} -> + case binary:match(Data, <<"chunked">>,[]) of + nomatch -> + ok; + {_, _} -> + ct:fail("Chunked encoding detected.") + end + after 2000 -> + ct:fail(connection_timed_out) + end; + ConnectError -> + ct:fail({connect_error, ConnectError, + [SockType, Host, Port, TranspOpts]}) + catch + T:E -> + ct:fail({connect_failure, + [{type, T}, + {error, E}, + {stacktrace, erlang:get_stacktrace()}, + {args, [SockType, Host, Port, TranspOpts]}]}) + end. %%------------------------------------------------------------------------- htaccess_1_1(Config) when is_list(Config) -> @@ -891,6 +947,33 @@ max_clients_0_9() -> max_clients_0_9(Config) when is_list(Config) -> do_max_clients([{http_version, "HTTP/0.9"} | Config]). + + +%%------------------------------------------------------------------------- +put_not_sup() -> + [{doc, "Test unhandled request"}]. + +put_not_sup(Config) when is_list(Config) -> + ok = http_status("PUT /index.html ", + {"Content-Length:100 \r\n", + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"}, + Config, [{statuscode, 501}]). %%------------------------------------------------------------------------- esi() -> [{doc, "Test mod_esi"}]. @@ -1738,7 +1821,8 @@ start_apps(Group) when Group == https_basic; Group == https_auth_api_mnesia; Group == https_htaccess; Group == https_security; - Group == https_reload + Group == https_reload; + Group == https_not_sup -> inets_test_lib:start_apps([inets, asn1, crypto, public_key, ssl]); start_apps(Group) when Group == http_basic; @@ -1753,6 +1837,8 @@ start_apps(Group) when Group == http_basic; Group == http_logging; Group == http_reload; Group == http_post; + Group == http_mime_types; + Group == http_not_sup; Group == http_mime_types-> inets_test_lib:start_apps([inets]). @@ -1797,6 +1883,10 @@ server_config(http_basic, Config) -> basic_conf() ++ server_config(http, Config); server_config(https_basic, Config) -> basic_conf() ++ server_config(https, Config); +server_config(http_not_sup, Config) -> + not_sup_conf() ++ server_config(http, Config); +server_config(https_not_sup, Config) -> + not_sup_conf() ++ server_config(https, Config); server_config(http_reload, Config) -> [{keep_alive_timeout, 2}] ++ server_config(http, Config); server_config(http_post, Config) -> @@ -1878,7 +1968,6 @@ server_config(http, Config) -> {erl_script_alias, {"/cgi-bin/erl", [httpd_example, io]}}, {eval_script_alias, {"/eval", [httpd_example, io]}} ]; - server_config(https, Config) -> PrivDir = proplists:get_value(priv_dir, Config), [{socket_type, {essl, @@ -1930,7 +2019,9 @@ head_status(_) -> basic_conf() -> [{modules, [mod_alias, mod_range, mod_responsecontrol, - mod_trace, mod_esi, mod_cgi, mod_dir, mod_get, mod_head]}]. + mod_trace, mod_esi, mod_cgi, mod_get, mod_head]}]. +not_sup_conf() -> + [{modules, [mod_get]}]. auth_access_conf() -> [{modules, [mod_alias, mod_htaccess, mod_dir, mod_get, mod_head]}, diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index 9bbcd06914..3259a8a195 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -19,6 +19,6 @@ # %CopyrightEnd% APPLICATION = inets -INETS_VSN = 6.5.2 +INETS_VSN = 6.5.2.4 PRE_VSN = APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml index 09844f1502..1b7f11d4fa 100644 --- a/lib/kernel/doc/src/notes.xml +++ b/lib/kernel/doc/src/notes.xml @@ -31,6 +31,42 @@ </header> <p>This document describes the changes made to the Kernel application.</p> +<section><title>Kernel 5.4.3.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Non semantic change in dist_util.erl to silence dialyzer + warning.</p> + <p> + Own Id: OTP-15170</p> + </item> + </list> + </section> + +</section> + +<section><title>Kernel 5.4.3.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix some potential buggy behavior in how ticks are sent + on inter node distribution connections. Tick is now sent + to c-node even if there are unsent buffered data, as + c-nodes need ticks in order to send reply ticks. The + amount of sent data was calculated wrongly when ticks + where suppressed due to unsent buffered data.</p> + <p> + Own Id: OTP-15162 Aux Id: ERIERL-191 </p> + </item> + </list> + </section> + +</section> + <section><title>Kernel 5.4.3</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/kernel/src/dist_util.erl b/lib/kernel/src/dist_util.erl index b3507e5d13..cddb24c34a 100644 --- a/lib/kernel/src/dist_util.erl +++ b/lib/kernel/src/dist_util.erl @@ -476,12 +476,12 @@ con_loop({Kernel, Node, Socket, Type, MFTick, MFGetstat, MFSetOpts, MFGetOpts}=C {Kernel, disconnect} -> ?shutdown2(Node, disconnected); {Kernel, aux_tick} -> - case MFGetstat(Socket) of - {ok, _, _, PendWrite} -> - send_tick(Socket, PendWrite, MFTick); - _ -> - ignore_it - end, + _ = case MFGetstat(Socket) of + {ok, _, _, PendWrite} -> + send_tick(Socket, PendWrite, MFTick); + _ -> + ignore_it + end, con_loop(ConData, Tick); {Kernel, tick} -> case send_tick(Socket, Tick, Type, @@ -731,7 +731,7 @@ send_status(#hs_data{socket = Socket, other_node = Node, %% The detection time interval is thus, by default, 45s < DT < 75s -%% A HIDDEN node is always (if not a pending write) ticked if +%% A HIDDEN node is always ticked if %% we haven't read anything as a hidden node only ticks when it receives %% a TICK !! @@ -745,17 +745,17 @@ send_tick(Socket, Tick, Type, MFTick, MFGetstat) -> case MFGetstat(Socket) of {ok, Read, _, _} when Ticked =:= T -> {error, not_responding}; - {ok, Read, W, Pend} when Type =:= hidden -> - send_tick(Socket, Pend, MFTick), + {ok, Read, W, _} when Type =:= hidden -> + MFTick(Socket), {ok, Tick#tick{write = W + 1, tick = T1}}; {ok, Read, Write, Pend} -> - send_tick(Socket, Pend, MFTick), - {ok, Tick#tick{write = Write + 1, + Sent = send_tick(Socket, Pend, MFTick), + {ok, Tick#tick{write = Write + Sent, tick = T1}}; {ok, R, Write, Pend} -> - send_tick(Socket, Pend, MFTick), - {ok, Tick#tick{write = Write + 1, + Sent = send_tick(Socket, Pend, MFTick), + {ok, Tick#tick{write = Write + Sent, read = R, tick = T1, ticked = T}}; @@ -772,10 +772,11 @@ send_tick(Socket, Tick, Type, MFTick, MFGetstat) -> end. send_tick(Socket, 0, MFTick) -> - MFTick(Socket); + MFTick(Socket), + 1; send_tick(_, _Pend, _) -> %% Dont send tick if pending write. - ok. + 0. %% ------------------------------------------------------------ %% Connection setup timeout timer. diff --git a/lib/kernel/vsn.mk b/lib/kernel/vsn.mk index 60a1b0bff8..d00032f89c 100644 --- a/lib/kernel/vsn.mk +++ b/lib/kernel/vsn.mk @@ -1 +1 @@ -KERNEL_VSN = 5.4.3 +KERNEL_VSN = 5.4.3.2 diff --git a/lib/mnesia/doc/src/notes.xml b/lib/mnesia/doc/src/notes.xml index ba94e913f5..2b715f8f47 100644 --- a/lib/mnesia/doc/src/notes.xml +++ b/lib/mnesia/doc/src/notes.xml @@ -39,7 +39,49 @@ thus constitutes one section in this document. The title of each section is the version number of Mnesia.</p> - <section><title>Mnesia 4.15.3</title> + <section><title>Mnesia 4.15.3.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fixed a bug where the bag table index data was not + deleted when objects were deleted.</p> + <p> + Own Id: OTP-15243</p> + </item> + </list> + </section> + +</section> + +<section><title>Mnesia 4.15.3.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + When master node is set do not force a load from + ram_copies replica when there are no available + disc_copies, since that would load an empty table. Wait + until a disk replica is available or until user + explicitly force_loads the table.</p> + <p> + Own Id: OTP-15221 Aux Id: ERIERL-217 </p> + </item> + <item> + <p> + Allow to add replicas even if all other replicas are down + when the other replicase are not stored on disk.</p> + <p> + Own Id: OTP-15226 Aux Id: ERIERL-221 </p> + </item> + </list> + </section> + +</section> + +<section><title>Mnesia 4.15.3</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/mnesia/src/mnesia_controller.erl b/lib/mnesia/src/mnesia_controller.erl index 77013489b3..11185a1762 100644 --- a/lib/mnesia/src/mnesia_controller.erl +++ b/lib/mnesia/src/mnesia_controller.erl @@ -771,22 +771,6 @@ handle_call({unannounce_add_table_copy, [Tab, Node], From}, ReplyTo, State) -> noreply(State#state{early_msgs = [{call, Msg, undefined} | Msgs]}) end; -handle_call({net_load, Tab, Cs}, From, State) -> - State2 = - case State#state.schema_is_merged of - true -> - Worker = #net_load{table = Tab, - opt_reply_to = From, - reason = {dumper,{add_table_copy, unknown}}, - cstruct = Cs - }, - add_worker(Worker, State); - false -> - reply(From, {not_loaded, schema_not_merged}), - State - end, - noreply(State2); - handle_call(Msg, From, State) when State#state.schema_is_merged /= true -> %% Buffer early messages Msgs = State#state.early_msgs, @@ -1456,7 +1440,8 @@ orphan_tables([Tab | Tabs], Node, Ns, Local, Remote) -> L = [Tab | Local], orphan_tables(Tabs, Node, Ns, L, Remote); Masters -> - R = [{Tab, Masters} | Remote], + %% Do not disc_load table from RamCopyHolders + R = [{Tab, Masters -- RamCopyHolders} | Remote], orphan_tables(Tabs, Node, Ns, Local, R) end; _ -> @@ -2161,6 +2146,15 @@ load_table_fun(#net_load{cstruct=Cs, table=Tab, reason=Reason, opt_reply_to=Repl {dumper,{add_table_copy,_}} -> true; _ -> false end, + + OnlyRamCopies = case Cs of + #cstruct{disc_copies = DC, + disc_only_copies = DOC, + external_copies = Ext} -> + [] =:= (DC ++ (DOC ++ Ext)) -- [node()]; + _ -> + false + end, if ReadNode == node() -> %% Already loaded locally @@ -2172,6 +2166,8 @@ load_table_fun(#net_load{cstruct=Cs, table=Tab, reason=Reason, opt_reply_to=Repl end; AccessMode == read_only, not AddTableCopy -> fun() -> disc_load_table(Tab, Reason, ReplyTo) end; + Active =:= [], AddTableCopy, OnlyRamCopies -> + fun() -> disc_load_table(Tab, Reason, ReplyTo) end; true -> fun() -> %% Either we cannot read the table yet diff --git a/lib/mnesia/src/mnesia_index.erl b/lib/mnesia/src/mnesia_index.erl index d121bd01e9..f8b3b9cd02 100644 --- a/lib/mnesia/src/mnesia_index.erl +++ b/lib/mnesia/src/mnesia_index.erl @@ -155,7 +155,7 @@ del_object_bag_([IxK|IxKs], Found, Type, Tab, Key, Obj, Ixt) -> bag -> db_match_erase(Ixt, {IxK, Key}); ordered -> - db_erase(Ixt, {{IxK, Key}}) + db_erase(Ixt, {IxK, Key}) end; _ -> ok diff --git a/lib/mnesia/src/mnesia_loader.erl b/lib/mnesia/src/mnesia_loader.erl index 4c6336cb73..39704c65f9 100644 --- a/lib/mnesia/src/mnesia_loader.erl +++ b/lib/mnesia/src/mnesia_loader.erl @@ -66,7 +66,7 @@ do_get_disc_copy2(Tab, Reason, Storage, Type) when Storage == disc_copies -> EtsOpts = proplists:get_value(ets, StorageProps, []), Args = [{keypos, 2}, public, named_table, Type | EtsOpts], case Reason of - {dumper, _} -> %% Resources already allocated + {dumper, DR} when is_atom(DR) -> %% Resources already allocated ignore; _ -> mnesia_monitor:mktab(Tab, Args), @@ -90,8 +90,8 @@ do_get_disc_copy2(Tab, Reason, Storage, Type) when Storage == ram_copies -> EtsOpts = proplists:get_value(ets, StorageProps, []), Args = [{keypos, 2}, public, named_table, Type | EtsOpts], case Reason of - {dumper, _} -> %% Resources allready allocated - ignore; + {dumper, DR} when is_atom(DR) -> + ignore; %% Resources already allocated _ -> mnesia_monitor:mktab(Tab, Args), Fname = mnesia_lib:tab2dcd(Tab), @@ -130,7 +130,7 @@ do_get_disc_copy2(Tab, Reason, Storage, Type) when Storage == disc_only_copies - {repair, mnesia_monitor:get_env(auto_repair)} | DetsOpts], case Reason of - {dumper, _} -> + {dumper, DR} when is_atom(DR) -> mnesia_index:init_index(Tab, Storage), snmpify(Tab, Storage), set({Tab, load_node}, node()), diff --git a/lib/mnesia/test/mnesia_durability_test.erl b/lib/mnesia/test/mnesia_durability_test.erl index 2fac5cac82..c186f7330b 100644 --- a/lib/mnesia/test/mnesia_durability_test.erl +++ b/lib/mnesia/test/mnesia_durability_test.erl @@ -46,6 +46,7 @@ master_nodes/1, starting_master_nodes/1, master_on_non_local_tables/1, remote_force_load_with_local_master_node/1, + master_node_with_ram_copy_2/1, master_node_with_ram_copy_3/1, dump_ram_copies/1, dump_disc_copies/1, dump_disc_only/1]). -include("mnesia_test_lib.hrl"). @@ -91,7 +92,8 @@ groups() -> {load_tables_with_master_tables, [], [master_nodes, starting_master_nodes, master_on_non_local_tables, - remote_force_load_with_local_master_node]}, + remote_force_load_with_local_master_node, + master_node_with_ram_copy_2, master_node_with_ram_copy_3]}, {durability_of_dump_tables, [], [dump_ram_copies, dump_disc_copies, dump_disc_only]}]. @@ -1165,6 +1167,107 @@ remote_force_load_with_local_master_node(Config) when is_list(Config) -> ?verify_mnesia(Nodes, []). +master_node_with_ram_copy_2(Config) when is_list(Config) -> + [A, B] = Nodes = ?acquire_nodes(2, Config), + Tab = ?FUNCTION_NAME, + ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, [A]}, {ram_copies, [B]}])), + ?match({atomic,ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + %% Test that we don't load from ram_copies + ?match(stopped, rpc:call(A, mnesia, stop, [])), + ?match(stopped, rpc:call(B, mnesia, stop, [])), + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match({timeout, [Tab]}, rpc:call(B, mnesia, wait_for_tables, [[Tab], 1000])), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])), + ?match([{Tab, 1, init}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, init}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + + %% Test that master_nodes set to ram_copy node require force_load + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [[B]])), + ?match(stopped, rpc:call(A, mnesia, stop, [])), + ?match(stopped, rpc:call(B, mnesia, stop, [])), + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match({timeout, [Tab]}, rpc:call(B, mnesia, wait_for_tables, [[Tab], 1000])), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match({timeout, [Tab]}, rpc:call(B, mnesia, wait_for_tables, [[Tab], 1000])), + + ?match(yes, rpc:call(A, mnesia, force_load_table, [Tab])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 1000])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 1000])), + ?match([{Tab, 1, init}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, init}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + + ?verify_mnesia(Nodes, []). + + +master_node_with_ram_copy_3(Config) when is_list(Config) -> + [A, B, C] = Nodes = ?acquire_nodes(3, Config), + Tab = ?FUNCTION_NAME, + ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, [A,C]}, {ram_copies, [B]}])), + ?match({atomic,ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + + %% Test that we don't load from ram_copies + ?match(stopped, rpc:call(A, mnesia, stop, [])), + ?match(stopped, rpc:call(C, mnesia, stop, [])), + ?match(stopped, rpc:call(B, mnesia, stop, [])), + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match({timeout, [Tab]}, rpc:call(B, mnesia, wait_for_tables, [[Tab], 1000])), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match(ok, rpc:call(C, mnesia, start, [])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + ?match([{Tab, 1, init}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, init}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, init}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + %% Test that master_nodes set to ram_copy node will wait until loaded + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [[B]])), + ?match(stopped, rpc:call(A, mnesia, stop, [])), + ?match({atomic,ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, update})])), + ?match(stopped, rpc:call(C, mnesia, stop, [])), + ?match({atomic,ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, ram_copies})])), + ?match(stopped, rpc:call(B, mnesia, stop, [])), + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match({timeout, [Tab]}, rpc:call(B, mnesia, wait_for_tables, [[Tab], 500])), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 500])), + ?match(ok, rpc:call(C, mnesia, start, [])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])), + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + ?match([{Tab, 1, update}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, update}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, update}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + %% Test that master_nodes set to ram_copy node requires force load + ?match({atomic,ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))), + ?match(ok, rpc:call(A, mnesia, set_master_nodes, [[B]])), + ?match(ok, rpc:call(C, mnesia, set_master_nodes, [[B]])), + + ?match(stopped, rpc:call(A, mnesia, stop, [])), + ?match({atomic,ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, update})])), + ?match(stopped, rpc:call(C, mnesia, stop, [])), + ?match({atomic,ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, ram_copies})])), + ?match(stopped, rpc:call(B, mnesia, stop, [])), + ?match(ok, rpc:call(B, mnesia, start, [])), + ?match({timeout, [Tab]}, rpc:call(B, mnesia, wait_for_tables, [[Tab], 500])), + ?match(ok, rpc:call(A, mnesia, start, [])), + ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 500])), + ?match(ok, rpc:call(C, mnesia, start, [])), + ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 500])), + ?match({timeout, [Tab]}, rpc:call(B, mnesia, wait_for_tables, [[Tab], 500])), + ?match({timeout, [Tab]}, rpc:call(B, mnesia, wait_for_tables, [[Tab], 500])), + ?match(yes, rpc:call(C, mnesia, force_load_table, [Tab])), + + ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])), + ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])), + ?match(ok, rpc:call(C, mnesia, wait_for_tables, [[Tab], 3000])), + ?match([{Tab, 1, update}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, update}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])), + ?match([{Tab, 1, update}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])), + + ?verify_mnesia(Nodes, []). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1415,7 +1518,7 @@ do_disc_durability(Config,CopyType) -> [{Tab_bag, 22, a_2222}], [{Tab_bag, 33, a_3333}], [{Tab_set, counter, 10}]]), - timer:sleep(1000), %% Debugging strange msgs.. + timer:sleep(500), %% Debugging strange msgs.. ?log("Flushed ~p ~n", [mnesia_test_lib:flush()]), ?verify_mnesia(Nodes, []). diff --git a/lib/mnesia/test/mnesia_evil_coverage_test.erl b/lib/mnesia/test/mnesia_evil_coverage_test.erl index 074967469b..1a561154a7 100644 --- a/lib/mnesia/test/mnesia_evil_coverage_test.erl +++ b/lib/mnesia/test/mnesia_evil_coverage_test.erl @@ -31,10 +31,11 @@ db_node_lifecycle/1, evil_delete_db_node/1, start_and_stop/1, checkpoint/1, table_lifecycle/1, storage_options/1, add_copy_conflict/1, add_copy_when_going_down/1, + add_copy_with_down/1, replica_management/1, clear_table_during_load/1, schema_availability/1, local_content/1, replica_location/1, user_properties/1, unsupp_user_props/1, - sorted_ets/1, + sorted_ets/1, index_cleanup/1, change_table_access_mode/1, change_table_load_order/1, set_master_nodes/1, offline_set_master_nodes/1, dump_tables/1, dump_log/1, wait_for_tables/1, force_load_table/1, @@ -48,7 +49,7 @@ record_name_dirty_access_disc_only/1, record_name_dirty_access_xets/1]). --export([info_check/8]). +-export([info_check/8, index_size/1]). -define(cleanup(N, Config), mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}], @@ -65,13 +66,14 @@ all() -> db_node_lifecycle, evil_delete_db_node, start_and_stop, checkpoint, table_lifecycle, storage_options, add_copy_conflict, - add_copy_when_going_down, replica_management, clear_table_during_load, + add_copy_when_going_down, add_copy_with_down, replica_management, + clear_table_during_load, schema_availability, local_content, {group, table_access_modifications}, replica_location, {group, table_sync}, user_properties, unsupp_user_props, {group, record_name}, {group, snmp_access}, {group, subscriptions}, {group, iteration}, - {group, debug_support}, sorted_ets, + {group, debug_support}, sorted_ets, index_cleanup, {mnesia_dirty_access_test, all}, {mnesia_trans_access_test, all}, {mnesia_evil_backup, all}]. @@ -732,6 +734,49 @@ add_copy_when_going_down(Config) -> ?match_receive({test,{aborted,_}}), ?verify_mnesia([Node2], []). +add_copy_with_down(suite) -> []; +add_copy_with_down(Config) -> + %% Allow add_table_copy() with ram_copies even all other replicas are down + Nodes = [Node1, Node2, Node3] = ?acquire_nodes(3, Config), + ?match({atomic, ok}, mnesia:create_table(a, [{ram_copies, [Node3]}, {disc_copies, [Node2]}])), + stopped = rpc:call(Node2, mnesia, stop, []), + stopped = rpc:call(Node3, mnesia, stop, []), + ?match({aborted, _}, mnesia:add_table_copy(a, Node1, ram_copies)), + ?match({aborted, _}, mnesia:del_table_copy(a, Node2)), + ok = rpc:call(Node3, mnesia, start, []), + ?match({aborted, _}, mnesia:add_table_copy(a, Node1, ram_copies)), + ?match([], mnesia_test_lib:start_mnesia([Node2], [a])), + ?match({atomic, ok}, mnesia:change_table_copy_type(a, Node2, ram_copies)), + stopped = rpc:call(Node2, mnesia, stop, []), + stopped = rpc:call(Node3, mnesia, stop, []), + ?match({atomic, ok}, mnesia:add_table_copy(a, Node1, ram_copies)), + ?match(ok, mnesia:dirty_write({a,1,1})), + ?match([], mnesia_test_lib:start_mnesia([Node2,Node3], [a])), + ?match([{a,1,1}], rpc:call(Node1, mnesia, dirty_read, [{a,1}])), + ?match([{a,1,1}], rpc:call(Node2, mnesia, dirty_read, [{a,1}])), + ?match([{a,1,1}], rpc:call(Node3, mnesia, dirty_read, [{a,1}])), + + ?match({atomic, ok}, mnesia:del_table_copy(a, Node1)), + stopped = rpc:call(Node2, mnesia, stop, []), + stopped = rpc:call(Node3, mnesia, stop, []), + ?match({atomic, ok}, mnesia:add_table_copy(a, Node1, disc_copies)), + ?match(ok, mnesia:dirty_write({a,1,1})), + ?match([], mnesia_test_lib:start_mnesia([Node2,Node3], [a])), + ?match([{a,1,1}], rpc:call(Node1, mnesia, dirty_read, [{a,1}])), + ?match([{a,1,1}], rpc:call(Node2, mnesia, dirty_read, [{a,1}])), + ?match([{a,1,1}], rpc:call(Node3, mnesia, dirty_read, [{a,1}])), + + ?match({atomic, ok}, mnesia:del_table_copy(a, Node1)), + stopped = rpc:call(Node2, mnesia, stop, []), + stopped = rpc:call(Node3, mnesia, stop, []), + ?match({atomic, ok}, mnesia:add_table_copy(a, Node1, disc_only_copies)), + ?match(ok, mnesia:dirty_write({a,1,1})), + ?match([], mnesia_test_lib:start_mnesia([Node2,Node3], [a])), + ?match([{a,1,1}], rpc:call(Node1, mnesia, dirty_read, [{a,1}])), + ?match([{a,1,1}], rpc:call(Node2, mnesia, dirty_read, [{a,1}])), + ?match([{a,1,1}], rpc:call(Node3, mnesia, dirty_read, [{a,1}])), + + ?verify_mnesia(Nodes, []). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Add, drop and move replicas, change storage types @@ -2514,3 +2559,55 @@ sorted_ets(Config) when is_list(Config) -> ?match({atomic, [{rec,1,1}, {rec,2,1}]}, mnesia:transaction(TestIt)). +index_cleanup(Config) when is_list(Config) -> + [N1, N2] = All = ?acquire_nodes(2, Config), + ?match({atomic, ok}, mnesia:create_table(i_set, [{type, set}, {ram_copies, [N1]}, {index, [val]}, + {disc_only_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(i_bag, [{type, bag}, {ram_copies, [N1]}, {index, [val]}, + {disc_only_copies, [N2]}])), + ?match({atomic, ok}, mnesia:create_table(i_oset, [{type, ordered_set}, {ram_copies, [N1, N2]}, + {index, [val]}])), + + Tabs = [i_set, i_bag, i_oset], + + Add = fun(Tab) -> + Write = fun(Tab) -> + Recs = [{Tab, N, N rem 5} || N <- lists:seq(1,10)], + [ok = mnesia:write(Rec) || Rec <- Recs], + Recs + end, + {atomic, Recs} = mnesia:sync_transaction(Write, [Tab]), + lists:sort(Recs) + end, + + IRead = fun(Tab) -> + Read = fun(Tab) -> + [mnesia:index_read(Tab, N, val) || N <- lists:seq(0,4)] + end, + {atomic, Recs} = mnesia:transaction(Read, [Tab]), + lists:sort(lists:flatten(Recs)) + end, + + Delete = fun(Rec) -> + Del = fun() -> mnesia:delete_object(Rec) end, + {atomic, ok} = mnesia:sync_transaction(Del), + ok + end, + + + Recs = [Add(Tab) || Tab <- Tabs], + ?match(Recs, [IRead(Tab) || Tab <- Tabs]), + [Delete(Rec) || Rec <- lists:flatten(Recs)], + + [?match({Tab,0}, {Tab,mnesia:table_info(Tab, size)}) || Tab <- Tabs], + + [?match({Tab,Node,0, _}, rpc:call(Node, ?MODULE, index_size, [Tab])) + || Node <- All, Tab <- Tabs], + ?verify_mnesia(All, []). + +index_size(Tab) -> + %% White box testing + case mnesia:table_info(Tab, index_info) of + {index, _, [{_, {ram, Ref}}=Dbg]} -> {Tab, node(), ets:info(Ref, size), Dbg}; + {index, _, [{_, {dets, Ref}}=Dbg]} -> {Tab, node(), dets:info(Ref, size), Dbg} + end. diff --git a/lib/mnesia/vsn.mk b/lib/mnesia/vsn.mk index 45f811846d..9079a6f02a 100644 --- a/lib/mnesia/vsn.mk +++ b/lib/mnesia/vsn.mk @@ -1 +1 @@ -MNESIA_VSN = 4.15.3 +MNESIA_VSN = 4.15.3.2 diff --git a/lib/snmp/include/snmp_types.hrl b/lib/snmp/include/snmp_types.hrl index ffe30996dc..eff17a13a3 100644 --- a/lib/snmp/include/snmp_types.hrl +++ b/lib/snmp/include/snmp_types.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2016. All Rights Reserved. +%% Copyright Ericsson AB 1996-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -349,6 +349,9 @@ -define(view_included, 1). -define(view_excluded, 2). +-define(view_wildcard, 0). +-define(view_exact, 1). + %%----------------------------------------------------------------- %% From SNMPv2-SMI diff --git a/lib/snmp/src/agent/depend.mk b/lib/snmp/src/agent/depend.mk index 8eba50fa3b..49c7669e41 100644 --- a/lib/snmp/src/agent/depend.mk +++ b/lib/snmp/src/agent/depend.mk @@ -2,7 +2,7 @@ # %CopyrightBegin% # -# Copyright Ericsson AB 2004-2016. All Rights Reserved. +# Copyright Ericsson AB 2004-2019. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -24,6 +24,9 @@ $(EBIN)/snmpa_authentication_service.$(EMULATOR): \ $(EBIN)/snmpa_error_report.$(EMULATOR): \ snmpa_error_report.erl +$(EBIN)/snmpa_get_mechanism.$(EMULATOR): \ + snmpa_get_mechanism.erl + $(EBIN)/snmpa_network_interface.$(EMULATOR): \ snmpa_network_interface.erl @@ -78,6 +81,20 @@ $(EBIN)/snmpa_error_logger.$(EMULATOR): \ snmpa_error_report.erl \ snmpa_error_logger.erl +$(EBIN)/snmpa_set.$(EMULATOR): \ + snmpa_set_mechanism.erl \ + snmpa_set.erl \ + ../misc/snmp_verbosity.hrl + +$(EBIN)/snmpa_get.$(EMULATOR): \ + snmpa_get_mechanism.erl \ + snmpa_get.erl \ + ../misc/snmp_verbosity.hrl + +$(EBIN)/snmpa_get_lib.$(EMULATOR): \ + snmpa_get_lib.erl \ + ../misc/snmp_verbosity.hrl + $(EBIN)/snmpa_local_db.$(EMULATOR): \ snmpa_local_db.erl \ ../misc/snmp_debug.hrl \ diff --git a/lib/snmp/src/agent/modules.mk b/lib/snmp/src/agent/modules.mk index 0f8615588a..49cc158c2e 100644 --- a/lib/snmp/src/agent/modules.mk +++ b/lib/snmp/src/agent/modules.mk @@ -2,7 +2,7 @@ # %CopyrightBegin% # -# Copyright Ericsson AB 2004-2016. All Rights Reserved. +# Copyright Ericsson AB 2004-2019. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ BEHAVIOUR_MODULES = \ snmpa_authentication_service \ snmpa_discovery_handler \ snmpa_error_report \ + snmpa_get_mechanism \ snmpa_mib_storage \ snmpa_mib_data \ snmpa_network_interface \ @@ -30,12 +31,24 @@ BEHAVIOUR_MODULES = \ snmpa_notification_filter \ snmpa_set_mechanism +MIB_MODULES = \ + snmp_community_mib \ + snmp_framework_mib \ + snmp_notification_mib \ + snmp_standard_mib \ + snmp_target_mib \ + snmp_user_based_sm_mib \ + snmp_view_based_acm_mib + # snmpa is "plain" interface module but also defines some agent specific types # and therefor must be compiled before the modules that use them, including # the behaviour modules... +# Some of the MIB modules also define types used elsewhere and therefor +# has to be built before the other mods. # snmpa_mib_data_ttln MODULES = \ snmpa \ + $(MIB_MODULES) \ $(BEHAVIOUR_MODULES) \ snmpa_acm \ snmpa_agent \ @@ -46,6 +59,8 @@ MODULES = \ snmpa_error \ snmpa_error_io \ snmpa_error_logger \ + snmpa_get \ + snmpa_get_lib \ snmpa_local_db \ snmpa_mib_storage_ets \ snmpa_mib_storage_dets \ @@ -66,17 +81,10 @@ MODULES = \ snmpa_trap \ snmpa_usm \ snmpa_vacm \ - snmp_community_mib \ - snmp_framework_mib \ snmp_generic \ snmp_generic_mnesia \ snmp_index \ - snmp_notification_mib \ - snmp_shadow_table \ - snmp_standard_mib \ - snmp_target_mib \ - snmp_user_based_sm_mib \ - snmp_view_based_acm_mib + snmp_shadow_table INTERNAL_HRL_FILES = \ diff --git a/lib/snmp/src/agent/snmp_view_based_acm_mib.erl b/lib/snmp/src/agent/snmp_view_based_acm_mib.erl index 02415e8036..c6eeb7cea2 100644 --- a/lib/snmp/src/agent/snmp_view_based_acm_mib.erl +++ b/lib/snmp/src/agent/snmp_view_based_acm_mib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2016. All Rights Reserved. +%% Copyright Ericsson AB 1999-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -37,6 +37,12 @@ %% -export([emask2imask/1]). +-export_type([ + mibview/0, + internal_view_mask/0, + internal_view_mask_element/0, + internal_view_type/0 + ]). -include("snmp_types.hrl"). -include("SNMPv2-TC.hrl"). @@ -53,7 +59,13 @@ -type internal_view_mask() :: null | [internal_view_mask_element()]. --type internal_view_mask_element() :: 0 | 1. +-type internal_view_mask_element() :: ?view_wildcard | + ?view_exact. +-type internal_view_type() :: ?view_included | ?view_excluded. + +-type mibview() :: [{SubTree :: snmp:oid(), + Mask :: internal_view_mask(), + Type :: internal_view_type()}]. -type external_view_mask() :: octet_string(). % At most length of 16 octet -type octet_string() :: [octet()]. diff --git a/lib/snmp/src/agent/snmpa_agent.erl b/lib/snmp/src/agent/snmpa_agent.erl index 458b88359b..a521b3773b 100644 --- a/lib/snmp/src/agent/snmpa_agent.erl +++ b/lib/snmp/src/agent/snmpa_agent.erl @@ -28,7 +28,8 @@ %% External exports -export([start_link/4, start_link/5, stop/1]). --export([subagent_set/2, +-export([subagent_get/3, subagent_get_next/3, + subagent_set/2, load_mibs/3, unload_mibs/3, which_mibs/1, whereis_mib/2, info/1, register_subagent/3, unregister_subagent/2, @@ -362,12 +363,19 @@ do_init(Prio, Parent, Ref, Options) -> "~n Options: ~p",[Prio, Parent, Ref, Options]), Mibs = get_mibs(Options), + SetModule = get_set_mechanism(Options), put(set_module, SetModule), + ?vtrace("set-module: ~w", [SetModule]), + + GetModule = get_get_mechanism(Options), + put(get_module, GetModule), + ?vtrace("get-module: ~w", [GetModule]), %% OTP-3324. For AXD301. AuthModule = get_authentication_service(Options), put(auth_module, AuthModule), + ?vtrace("auth-module: ~w", [AuthModule]), MultiT = get_multi_threaded(Options), Vsns = get_versions(Options), @@ -1133,7 +1141,7 @@ handle_call({subagent_get_next, MibView, Varbinds, PduData}, _From, S) -> "~n PduData: ~p", [MibView,Varbinds,PduData]), put_pdu_data(PduData), - {reply, do_get_next(MibView, Varbinds, infinity), S}; + {reply, do_get_next(MibView, Varbinds), S}; handle_call({subagent_set, Arguments, PduData}, _From, S) -> ?vlog("[handle_call] subagent set:" "~n Arguments: ~p" @@ -1174,7 +1182,7 @@ handle_call({get_next, Vars, Context}, _From, S) -> ?vdebug("Varbinds: ~p",[Varbinds]), MibView = snmpa_acm:get_root_mib_view(), Reply = - case do_get_next(MibView, Varbinds, infinity) of + case do_get_next(MibView, Varbinds) of {noError, 0, NewVarbinds} -> Vbs = lists:keysort(#varbind.org_index, NewVarbinds), [{Oid,Val} || #varbind{oid = Oid, value = Val} <- Vbs]; @@ -2559,7 +2567,7 @@ process_pdu(#pdu{type = 'get-next-request', request_id = ReqId, varbinds = Vbs}, "~n ReqId: ~p" "~n Vbs: ~p" "~n MibView: ~p",[ReqId, Vbs, MibView]), - Res = get_err(do_get_next(MibView, Vbs, infinity)), + Res = get_err(do_get_next(MibView, Vbs)), ?vtrace("get-next result: " "~n ~p",[Res]), {ErrStatus, ErrIndex, ResVarbinds} = @@ -2650,8 +2658,7 @@ validate_next_v1_2([Vb | _Vbs], _MibView, _Res) {noSuchName, Vb#varbind.org_index}; validate_next_v1_2([Vb | Vbs], MibView, Res) when Vb#varbind.variabletype =:= 'Counter64' -> - case validate_next_v1( - do_get_next(MibView, [mk_next_oid(Vb)], infinity), MibView) of + case validate_next_v1( do_get_next(MibView, [mk_next_oid(Vb)]), MibView) of {noError, 0, [NVb]} -> validate_next_v1_2(Vbs, MibView, [NVb | Res]); {Error, Index, _OrgVb} -> @@ -2693,6 +2700,20 @@ mk_next_oid(Vb) -> %%%----------------------------------------------------------------- %%----------------------------------------------------------------- +%% Func: do_get/2 +%% Purpose: Handles all VBs in a request that is inside the +%% mibview (local). +%% Returns: {noError, 0, ListOfNewVarbinds} | +%% {ErrorStatus, ErrorIndex, []} +%%----------------------------------------------------------------- + +do_get(UnsortedVarbinds, IsNotification) -> + Extra = get(net_if_data), + GetModule = get(get_module), + GetModule:do_get(UnsortedVarbinds, IsNotification, Extra). + + +%%----------------------------------------------------------------- %% Func: do_get/3 %% Purpose: do_get handles "getRequests". %% Pre: incoming varbinds have type == 'NULL', value == unSpecified @@ -2700,390 +2721,24 @@ mk_next_oid(Vb) -> %% {ErrorStatus, ErrorIndex, []} %%----------------------------------------------------------------- -%% If this function is called from a worker-process, we *may* -%% need to tunnel into the master-agent and let it do the -%% work +%% If this function is called from a worker-process (or other process), +%% we *may* need to tunnel into the master-agent and let it do the work. do_get(MibView, UnsortedVarbinds, IsNotification) -> - do_get(MibView, UnsortedVarbinds, IsNotification, false). + Extra = get(net_if_data), + GetModule = get(get_module), + GetModule:do_get(MibView, UnsortedVarbinds, IsNotification, Extra). do_get(MibView, UnsortedVarbinds, IsNotification, ForceMaster) -> - ?vtrace("do_get -> entry with" - "~n MibView: ~p" - "~n UnsortedVarbinds: ~p" - "~n IsNotification: ~p", - [MibView, UnsortedVarbinds, IsNotification]), case (whereis(snmp_master_agent) =:= self()) of false when (ForceMaster =:= true) -> - %% I am a lowly worker process, handoff to the master agent PduData = get_pdu_data(), call(snmp_master_agent, {do_get, MibView, UnsortedVarbinds, IsNotification, PduData}); - - _ -> - %% This is me, the master, so go ahead - {OutSideView, InSideView} = - split_vbs_view(UnsortedVarbinds, MibView), - {Error, Index, NewVbs} = - do_get(InSideView, IsNotification), - {Error, Index, NewVbs ++ OutSideView} - - end. - - -split_vbs_view(Vbs, MibView) -> - ?vtrace("split the varbinds view", []), - split_vbs_view(Vbs, MibView, [], []). - -split_vbs_view([Vb | Vbs], MibView, Out, In) -> - case snmpa_acm:validate_mib_view(Vb#varbind.oid, MibView) of - true -> split_vbs_view(Vbs, MibView, Out, [Vb | In]); - false -> split_vbs_view(Vbs, MibView, - [Vb#varbind{value = noSuchObject} | Out], In) - end; -split_vbs_view([], _MibView, Out, In) -> - {Out, In}. - -do_get(UnsortedVarbinds, IsNotification) -> - {MyVarbinds, SubagentVarbinds} = sort_varbindlist(UnsortedVarbinds), - case do_get_local(MyVarbinds, [], IsNotification) of - {noError, 0, NewMyVarbinds} -> - case do_get_subagents(SubagentVarbinds, IsNotification) of - {noError, 0, NewSubagentVarbinds} -> - {noError, 0, NewMyVarbinds ++ NewSubagentVarbinds}; - {ErrorStatus, ErrorIndex, _} -> - {ErrorStatus, ErrorIndex, []} - end; - {ErrorStatus, ErrorIndex, _} -> - {ErrorStatus, ErrorIndex, []} + _ -> + do_get(MibView, UnsortedVarbinds, IsNotification) end. -%%----------------------------------------------------------------- -%% Func: do_get_local/3 -%% Purpose: Loop the variablebindings list. We know that each varbind -%% in that list belongs to us. -%% Returns: {noError, 0, ListOfNewVarbinds} | -%% {ErrorStatus, ErrorIndex, []} -%%----------------------------------------------------------------- -do_get_local([Vb | Vbs], Res, IsNotification) -> - case try_get(Vb, IsNotification) of - NewVb when is_record(NewVb, varbind) -> - do_get_local(Vbs, [NewVb | Res], IsNotification); - ListOfNewVb when is_list(ListOfNewVb) -> - do_get_local(Vbs, lists:append(ListOfNewVb, Res), IsNotification); - {error, Error, OrgIndex} -> - {Error, OrgIndex, []} - end; -do_get_local([], Res, _IsNotification) -> - {noError, 0, Res}. - -%%----------------------------------------------------------------- -%% Func: do_get_subagents/2 -%% Purpose: Loop the list of varbinds for different subagents. -%% For each of them, call sub_agent_get to retreive -%% the values for them. -%% Returns: {noError, 0, ListOfNewVarbinds} | -%% {ErrorStatus, ErrorIndex, []} -%%----------------------------------------------------------------- -do_get_subagents(SubagentVarbinds, IsNotification) -> - do_get_subagents(SubagentVarbinds, [], IsNotification). -do_get_subagents([{SubAgentPid, SAVbs} | Tail], Res, IsNotification) -> - {_SAOids, Vbs} = sa_split(SAVbs), - case catch subagent_get(SubAgentPid, Vbs, IsNotification) of - {noError, 0, NewVbs} -> - do_get_subagents(Tail, lists:append(NewVbs, Res), IsNotification); - {ErrorStatus, ErrorIndex, _} -> - {ErrorStatus, ErrorIndex, []}; - {'EXIT', Reason} -> - user_err("Lost contact with subagent (get) ~w. Using genErr", - [Reason]), - {genErr, 0, []} - end; -do_get_subagents([], Res, _IsNotification) -> - {noError, 0, Res}. - - -%%----------------------------------------------------------------- -%% Func: try_get/2 -%% Returns: {error, ErrorStatus, OrgIndex} | -%% #varbind | -%% List of #varbind -%%----------------------------------------------------------------- -try_get(IVb, IsNotification) when is_record(IVb, ivarbind) -> - ?vtrace("try_get(ivarbind) -> entry with" - "~n IVb: ~p", [IVb]), - get_var_value_from_ivb(IVb, IsNotification); -try_get({TableOid, TableVbs}, IsNotification) -> - ?vtrace("try_get(table) -> entry with" - "~n TableOid: ~p" - "~n TableVbs: ~p", [TableOid, TableVbs]), - [#ivarbind{mibentry = MibEntry}|_] = TableVbs, - {NoAccessVbs, AccessVbs} = - check_all_table_vbs(TableVbs, IsNotification, [], []), - case get_tab_value_from_mib(MibEntry, TableOid, AccessVbs) of - {error, ErrorStatus, OrgIndex} -> - {error, ErrorStatus, OrgIndex}; - NVbs -> - NVbs ++ NoAccessVbs - end. - -%%----------------------------------------------------------------- -%% Make sure all requested columns are accessible. -%%----------------------------------------------------------------- -check_all_table_vbs([IVb| IVbs], IsNotification, NoA, A) -> - #ivarbind{mibentry = Me, varbind = Vb} = IVb, - case Me#me.access of - 'not-accessible' -> - NNoA = [Vb#varbind{value = noSuchInstance} | NoA], - check_all_table_vbs(IVbs, IsNotification, NNoA, A); - 'accessible-for-notify' when IsNotification =:= false -> - NNoA = [Vb#varbind{value = noSuchInstance} | NoA], - check_all_table_vbs(IVbs, IsNotification, NNoA, A); - 'write-only' -> - NNoA = [Vb#varbind{value = noSuchInstance} | NoA], - check_all_table_vbs(IVbs, IsNotification, NNoA, A); - _ -> - check_all_table_vbs(IVbs, IsNotification, NoA, [IVb | A]) - end; -check_all_table_vbs([], _IsNotification, NoA, A) -> {NoA, A}. - -%%----------------------------------------------------------------- -%% Returns: {error, ErrorStatus, OrgIndex} | -%% #varbind -%%----------------------------------------------------------------- -get_var_value_from_ivb(IVb, IsNotification) - when IVb#ivarbind.status =:= noError -> - ?vtrace("get_var_value_from_ivb(noError) -> entry", []), - #ivarbind{mibentry = Me, varbind = Vb} = IVb, - #varbind{org_index = OrgIndex, oid = Oid} = Vb, - case Me#me.access of - 'not-accessible' -> - Vb#varbind{value = noSuchInstance}; - 'accessible-for-notify' when IsNotification =:= false -> - Vb#varbind{value = noSuchInstance}; - 'write-only' -> - Vb#varbind{value = noSuchInstance}; - _ -> - case get_var_value_from_mib(Me, Oid) of - {value, Type, Value} -> - Vb#varbind{variabletype = Type, value = Value}; - {error, ErrorStatus} -> - {error, ErrorStatus, OrgIndex} - end - end; -get_var_value_from_ivb(#ivarbind{status = Status, varbind = Vb}, _) -> - ?vtrace("get_var_value_from_ivb(~p) -> entry", [Status]), - Vb#varbind{value = Status}. - -%%----------------------------------------------------------------- -%% Func: get_var_value_from_mib/1 -%% Purpose: -%% Returns: {error, ErrorStatus} | -%% {value, Type, Value} -%%----------------------------------------------------------------- -%% Pre: Oid is a correct instance Oid (lookup checked that). -%% Returns: A correct return value (see make_value_a_correct_value) -get_var_value_from_mib(#me{entrytype = variable, - asn1_type = ASN1Type, - mfa = {Mod, Func, Args}}, - _Oid) -> - ?vtrace("get_var_value_from_mib(variable) -> entry when" - "~n Mod: ~p" - "~n Func: ~p" - "~n Args: ~p", [Mod, Func, Args]), - Result = (catch dbg_apply(Mod, Func, [get | Args])), - % mib shall return {value, <a-nice-value-within-range>} | - % {noValue, noSuchName} (v1) | - % {noValue, noSuchObject | noSuchInstance} (v2, v1) - % everything else (including 'genErr') will generate 'genErr'. - make_value_a_correct_value(Result, ASN1Type, {Mod, Func, Args}); - -get_var_value_from_mib(#me{entrytype = table_column, - oid = MeOid, - asn1_type = ASN1Type, - mfa = {Mod, Func, Args}}, - Oid) -> - ?vtrace("get_var_value_from_mib(table_column) -> entry when" - "~n MeOid: ~p" - "~n Mod: ~p" - "~n Func: ~p" - "~n Args: ~p" - "~n Oid: ~p", [MeOid, Mod, Func, Args, Oid]), - Col = lists:last(MeOid), - Indexes = snmp_misc:diff(Oid, MeOid), - [Result] = (catch dbg_apply(Mod, Func, [get, Indexes, [Col] | Args])), - make_value_a_correct_value(Result, ASN1Type, - {Mod, Func, Args, Indexes, Col}). - - -%% For table operations we need to pass RestOid down to the table-function. -%% Its up to the table-function to check for noSuchInstance (ex: a -%% non-existing row). -%% Returns: {error, ErrorStatus, OrgIndex} | -%% {value, Type, Value} -get_tab_value_from_mib(#me{mfa = {Mod, Func, Args}}, TableOid, TableVbs) -> - ?vtrace("get_tab_value_from_mib -> entry when" - "~n Mod: ~p" - "~n Func: ~p" - "~n Args: ~p", [Mod, Func, Args]), - TableOpsWithShortOids = deletePrefixes(TableOid, TableVbs), - SortedVBsRows = snmpa_svbl:sort_varbinds_rows(TableOpsWithShortOids), - case get_value_all_rows(SortedVBsRows, Mod, Func, Args, []) of - {Error, Index} -> - #ivarbind{varbind = Vb} = lists:nth(Index, TableVbs), - {error, Error, Vb#varbind.org_index}; - ListOfValues -> - merge_varbinds_and_value(TableVbs, ListOfValues) - end. - -%%----------------------------------------------------------------- -%% Values is a scrambled list of {CorrectValue, Index}, where Index -%% is index into the #ivarbind list. So for each Value, we must -%% find the corresponding #ivarbind, and merge them into a new -%% #varbind. -%% The Values list comes from validate_tab_res. -%%----------------------------------------------------------------- -merge_varbinds_and_value(IVbs, [{{value, Type, Value}, Index} | Values]) -> - #ivarbind{varbind = Vb} = lists:nth(Index, IVbs), - [Vb#varbind{variabletype = Type, value = Value} | - merge_varbinds_and_value(IVbs, Values)]; -merge_varbinds_and_value(_, []) -> []. - -get_value_all_rows([{[], OrgCols} | Rows], Mod, Func, Args, Res) -> - ?vtrace("get_value_all_rows -> entry when" - "~n OrgCols: ~p", [OrgCols]), - Cols = [{{value, noValue, noSuchInstance}, Index} || - {_Col, _ASN1Type, Index} <- OrgCols], - NewRes = lists:append(Cols, Res), - get_value_all_rows(Rows, Mod, Func, Args, NewRes); -get_value_all_rows([{RowIndex, OrgCols} | Rows], Mod, Func, Args, Res) -> - ?vtrace("get_value_all_rows -> entry when" - "~n RowIndex: ~p" - "~n OrgCols: ~p", [RowIndex, OrgCols]), - {DOrgCols, Dup} = remove_duplicates(OrgCols), - Cols = delete_index(DOrgCols), - Result = (catch dbg_apply(Mod, Func, [get, RowIndex, Cols | Args])), - case validate_tab_res(Result, DOrgCols, {Mod, Func, Args}) of - Values when is_list(Values) -> - NVals = restore_duplicates(Dup, Values), - NewRes = lists:append(NVals, Res), - get_value_all_rows(Rows, Mod, Func, Args, NewRes); - {error, ErrorStatus, Index} -> - validate_err(row_set, {ErrorStatus, Index}, {Mod, Func, Args}) - end; -get_value_all_rows([], _Mod, _Func, _Args, Res) -> - ?vtrace("get_value_all_rows -> entry when done" - "~n Res: ~p", [Res]), - Res. - -%%----------------------------------------------------------------- -%% Returns: list of {ShortOid, ASN1TYpe} -%%----------------------------------------------------------------- -deletePrefixes(Prefix, [#ivarbind{varbind = Varbind, mibentry = ME} | Vbs]) -> - #varbind{oid = Oid} = Varbind, - [{snmp_misc:diff(Oid, Prefix), ME#me.asn1_type} | - deletePrefixes(Prefix, Vbs)]; -deletePrefixes(_Prefix, []) -> []. - -%%----------------------------------------------------------------- -%% Args: {RowIndex, list of {ShortOid, ASN1Type}} -%% Returns: list of Col -%%----------------------------------------------------------------- -delete_index([{Col, _Val, _OrgIndex} | T]) -> - [Col | delete_index(T)]; -delete_index([]) -> []. - -%%----------------------------------------------------------------- -%% This function is called before 'get' on a table, and removes -%% any duplicate columns. It returns {Cols, DupInfo}. The Cols -%% are the unique columns. The instrumentation function is -%% called to get the values. These values, together with the -%% DupInfo, is later passed to restore_duplicates, which uses -%% the retrieved values to reconstruct the original column list, -%% but with the retrieved value for each column. -%%----------------------------------------------------------------- -remove_duplicates(Cols) -> - remove_duplicates(Cols, [], []). - - -remove_duplicates([{Col, V1, OrgIdx1}, {Col, V2, OrgIdx2} | T], NCols, Dup) -> - remove_duplicates([{Col, V1, OrgIdx1} | T], NCols, - [{Col, V2, OrgIdx2} | Dup]); -remove_duplicates([Col | T], NCols, Dup) -> - remove_duplicates(T, [Col | NCols], Dup); -remove_duplicates([], NCols, Dup) -> - {lists:reverse(NCols), lists:reverse(Dup)}. - -restore_duplicates([], Cols) -> - [{Val, OrgIndex} || {_Col, Val, OrgIndex} <- Cols]; -restore_duplicates([{Col, _Val2, OrgIndex2} | Dup], - [{Col, NVal, OrgIndex1} | Cols]) -> - [{NVal, OrgIndex2} | - restore_duplicates(Dup, [{Col, NVal, OrgIndex1} | Cols])]; -restore_duplicates(Dup, [{_Col, Val, OrgIndex} | T]) -> - [{Val, OrgIndex} | restore_duplicates(Dup, T)]. - -%% Maps the column number to Index. -% col_to_index(0, _) -> 0; -% col_to_index(Col, [{Col, _, Index}|_]) -> -% Index; -% col_to_index(Col, [_|Cols]) -> -% col_to_index(Col, Cols). - -%%----------------------------------------------------------------- -%% Three cases: -%% 1) All values ok -%% 2) table_func returned {Error, ...} -%% 3) Some value in Values list is erroneous. -%% Args: Value is a list of values from table_func(get..) -%% OrgCols is a list with {Col, ASN1Type, OrgIndex} -%% each element in Values and OrgCols correspond to each -%% other. -%%----------------------------------------------------------------- -validate_tab_res(Values, OrgCols, Mfa) when is_list(Values) -> - {_Col, _ASN1Type, OneIdx} = hd(OrgCols), - validate_tab_res(Values, OrgCols, Mfa, [], OneIdx); -validate_tab_res({noValue, Error}, OrgCols, Mfa) -> - Values = lists:duplicate(length(OrgCols), {noValue, Error}), - validate_tab_res(Values, OrgCols, Mfa); -validate_tab_res({genErr, Col}, OrgCols, Mfa) -> - case lists:keysearch(Col, 1, OrgCols) of - {value, {_Col, _ASN1Type, Index}} -> - {error, genErr, Index}; - _ -> - user_err("Invalid column in {genErr, ~w} from ~w (get)", - [Col, Mfa]), - [{_Col, _ASN1Type, Index} | _] = OrgCols, - {error, genErr, Index} - end; -validate_tab_res(genErr, [{_Col, __ASN1Type, Index} | _OrgCols], _Mfa) -> - {error, genErr, Index}; -validate_tab_res(Error, [{_Col, _ASN1Type, Index} | _OrgCols], Mfa) -> - user_err("Invalid return value ~w from ~w (get)",[Error, Mfa]), - {error, genErr, Index}. - -validate_tab_res([Value | Values], - [{Col, ASN1Type, Index} | OrgCols], - Mfa, Res, I) -> - %% This one makes it possible to return a list of genErr, which - %% is not allowed according to the manual. But that's ok, as - %% everything else will generate a genErr! (the only problem is - %% that it won't generate a user_error). - case make_value_a_correct_value(Value, ASN1Type, Mfa) of - {error, ErrorStatus} -> - {error, ErrorStatus, Index}; - CorrectValue -> - NewRes = [{Col, CorrectValue, Index} | Res], - validate_tab_res(Values, OrgCols, Mfa, NewRes, I) - end; -validate_tab_res([], [], _Mfa, Res, _I) -> - lists:reverse(Res); -validate_tab_res([], [{_Col, _ASN1Type, Index}|_], Mfa, _Res, _I) -> - user_err("Too few values returned from ~w (get)", [Mfa]), - {error, genErr, Index}; -validate_tab_res(_TooMany, [], Mfa, _Res, I) -> - user_err("Too many values returned from ~w (get)", [Mfa]), - {error, genErr, I}. %%%----------------------------------------------------------------- @@ -3125,491 +2780,12 @@ validate_tab_res(_TooMany, [], Mfa, _Res, I) -> %% subagent must be considered to be very rare. %%----------------------------------------------------------------- -%% It may be a bit agressive to check this already, -%% but since it is a security measure, it makes sense. -do_get_next(_MibView, UnsortedVarbinds, GbMaxVBs) - when (is_integer(GbMaxVBs) andalso (length(UnsortedVarbinds) > GbMaxVBs)) -> - {tooBig, 0, []}; % What is the correct index in this case? -do_get_next(MibView, UnsortedVBs, GbMaxVBs) -> - ?vt("do_get_next -> entry when" - "~n MibView: ~p" - "~n UnsortedVBs: ~p", [MibView, UnsortedVBs]), - SortedVBs = oid_sort_vbs(UnsortedVBs), - ?vt("do_get_next -> " - "~n SortedVBs: ~p", [SortedVBs]), - next_loop_varbinds([], SortedVBs, MibView, [], [], GbMaxVBs). - -oid_sort_vbs(Vbs) -> - lists:keysort(#varbind.oid, Vbs). - -next_loop_varbinds(_, Vbs, _MibView, Res, _LAVb, GbMaxVBs) - when (is_integer(GbMaxVBs) andalso - ((length(Vbs) + length(Res)) > GbMaxVBs)) -> - {tooBig, 0, []}; % What is the correct index in this case? - -%% LAVb is Last Accessible Vb -next_loop_varbinds([], [Vb | Vbs], MibView, Res, LAVb, GbMaxVBs) -> - ?vt("next_loop_varbinds -> entry when" - "~n Vb: ~p" - "~n MibView: ~p", [Vb, MibView]), - case varbind_next(Vb, MibView) of - endOfMibView -> - ?vt("next_loop_varbind -> endOfMibView", []), - RVb = if LAVb =:= [] -> Vb; - true -> LAVb - end, - NewVb = RVb#varbind{variabletype = 'NULL', value = endOfMibView}, - next_loop_varbinds([], Vbs, MibView, [NewVb | Res], [], GbMaxVBs); - - {variable, ME, VarOid} when ((ME#me.access =/= 'not-accessible') andalso - (ME#me.access =/= 'write-only') andalso - (ME#me.access =/= 'accessible-for-notify')) -> - ?vt("next_loop_varbind -> variable: " - "~n ME: ~p" - "~n VarOid: ~p", [ME, VarOid]), - case try_get_instance(Vb, ME) of - {value, noValue, _NoSuchSomething} -> - ?vt("next_loop_varbind -> noValue", []), - %% Try next one - NewVb = Vb#varbind{oid = VarOid, - value = 'NULL'}, - next_loop_varbinds([], [NewVb | Vbs], MibView, Res, [], - GbMaxVBs); - {value, Type, Value} -> - ?vt("next_loop_varbind -> value" - "~n Type: ~p" - "~n Value: ~p", [Type, Value]), - NewVb = Vb#varbind{oid = VarOid, - variabletype = Type, - value = Value}, - next_loop_varbinds([], Vbs, MibView, [NewVb | Res], [], - GbMaxVBs); - {error, ErrorStatus} -> - ?vdebug("next loop varbinds:" - "~n ErrorStatus: ~p",[ErrorStatus]), - {ErrorStatus, Vb#varbind.org_index, []} - end; - {variable, _ME, VarOid} -> - ?vt("next_loop_varbind -> variable: " - "~n VarOid: ~p", [VarOid]), - RVb = if LAVb =:= [] -> Vb; - true -> LAVb - end, - NewVb = Vb#varbind{oid = VarOid, value = 'NULL'}, - next_loop_varbinds([], [NewVb | Vbs], MibView, Res, RVb, GbMaxVBs); - {table, TableOid, TableRestOid, ME} -> - ?vt("next_loop_varbind -> table: " - "~n TableOid: ~p" - "~n TableRestOid: ~p" - "~n ME: ~p", [TableOid, TableRestOid, ME]), - next_loop_varbinds({table, TableOid, ME, - [{tab_oid(TableRestOid), Vb}]}, - Vbs, MibView, Res, [], GbMaxVBs); - {subagent, SubAgentPid, SAOid} -> - ?vt("next_loop_varbind -> subagent: " - "~n SubAgentPid: ~p" - "~n SAOid: ~p", [SubAgentPid, SAOid]), - NewVb = Vb#varbind{variabletype = 'NULL', value = 'NULL'}, - next_loop_varbinds({subagent, SubAgentPid, SAOid, [NewVb]}, - Vbs, MibView, Res, [], GbMaxVBs) - end; -next_loop_varbinds({table, TableOid, ME, TabOids}, - [Vb | Vbs], MibView, Res, _LAVb, GbMaxVBs) -> - ?vt("next_loop_varbinds(table) -> entry with" - "~n TableOid: ~p" - "~n Vb: ~p", [TableOid, Vb]), - case varbind_next(Vb, MibView) of - {table, TableOid, TableRestOid, _ME} -> - next_loop_varbinds({table, TableOid, ME, - [{tab_oid(TableRestOid), Vb} | TabOids]}, - Vbs, MibView, Res, [], GbMaxVBs); - _ -> - case get_next_table(ME, TableOid, TabOids, MibView) of - {ok, TabRes, TabEndOfTabVbs} -> - NewVbs = lists:append(TabEndOfTabVbs, [Vb | Vbs]), - NewRes = lists:append(TabRes, Res), - next_loop_varbinds([], NewVbs, MibView, NewRes, [], - GbMaxVBs); - {ErrorStatus, OrgIndex} -> - ?vdebug("next loop varbinds: next varbind" - "~n ErrorStatus: ~p" - "~n OrgIndex: ~p", - [ErrorStatus,OrgIndex]), - {ErrorStatus, OrgIndex, []} - end - end; -next_loop_varbinds({table, TableOid, ME, TabOids}, - [], MibView, Res, _LAVb, GbMaxVBs) -> - ?vt("next_loop_varbinds(table) -> entry with" - "~n TableOid: ~p", [TableOid]), - case get_next_table(ME, TableOid, TabOids, MibView) of - {ok, TabRes, TabEndOfTabVbs} -> - ?vt("next_loop_varbinds(table) -> get_next_table result:" - "~n TabRes: ~p" - "~n TabEndOfTabVbs: ~p", [TabRes, TabEndOfTabVbs]), - NewRes = lists:append(TabRes, Res), - next_loop_varbinds([], TabEndOfTabVbs, MibView, NewRes, [], - GbMaxVBs); - {ErrorStatus, OrgIndex} -> - ?vdebug("next loop varbinds: next table" - "~n ErrorStatus: ~p" - "~n OrgIndex: ~p", - [ErrorStatus,OrgIndex]), - {ErrorStatus, OrgIndex, []} - end; -next_loop_varbinds({subagent, SAPid, SAOid, SAVbs}, - [Vb | Vbs], MibView, Res, _LAVb, GbMaxVBs) -> - ?vt("next_loop_varbinds(subagent) -> entry with" - "~n SAPid: ~p" - "~n SAOid: ~p" - "~n Vb: ~p", [SAPid, SAOid, Vb]), - case varbind_next(Vb, MibView) of - {subagent, _SubAgentPid, SAOid} -> - next_loop_varbinds({subagent, SAPid, SAOid, - [Vb | SAVbs]}, - Vbs, MibView, Res, [], GbMaxVBs); - _ -> - case get_next_sa(SAPid, SAOid, SAVbs, MibView) of - {ok, SARes, SAEndOfMibViewVbs} -> - NewVbs = lists:append(SAEndOfMibViewVbs, [Vb | Vbs]), - NewRes = lists:append(SARes, Res), - next_loop_varbinds([], NewVbs, MibView, NewRes, [], - GbMaxVBs); - {noSuchName, OrgIndex} -> - %% v1 reply, treat this Vb as endOfMibView, and try again - %% for the others. - case lists:keysearch(OrgIndex, #varbind.org_index, SAVbs) of - {value, EVb} -> - NextOid = next_oid(SAOid), - EndOfVb = - EVb#varbind{oid = NextOid, - value = {endOfMibView, NextOid}}, - case lists:delete(EVb, SAVbs) of - [] -> - next_loop_varbinds([], [EndOfVb, Vb | Vbs], - MibView, Res, [], - GbMaxVBs); - TryAgainVbs -> - next_loop_varbinds({subagent, SAPid, SAOid, - TryAgainVbs}, - [EndOfVb, Vb | Vbs], - MibView, Res, [], - GbMaxVBs) - end; - false -> - %% bad index from subagent - {genErr, (hd(SAVbs))#varbind.org_index, []} - end; - {ErrorStatus, OrgIndex} -> - ?vdebug("next loop varbinds: next subagent" - "~n Vb: ~p" - "~n ErrorStatus: ~p" - "~n OrgIndex: ~p", - [Vb,ErrorStatus,OrgIndex]), - {ErrorStatus, OrgIndex, []} - end - end; -next_loop_varbinds({subagent, SAPid, SAOid, SAVbs}, - [], MibView, Res, _LAVb, GbMaxVBs) -> - ?vt("next_loop_varbinds(subagent) -> entry with" - "~n SAPid: ~p" - "~n SAOid: ~p", [SAPid, SAOid]), - case get_next_sa(SAPid, SAOid, SAVbs, MibView) of - {ok, SARes, SAEndOfMibViewVbs} -> - NewRes = lists:append(SARes, Res), - next_loop_varbinds([], SAEndOfMibViewVbs, MibView, NewRes, [], - GbMaxVBs); - {noSuchName, OrgIndex} -> - %% v1 reply, treat this Vb as endOfMibView, and try again for - %% the others. - case lists:keysearch(OrgIndex, #varbind.org_index, SAVbs) of - {value, EVb} -> - NextOid = next_oid(SAOid), - EndOfVb = EVb#varbind{oid = NextOid, - value = {endOfMibView, NextOid}}, - case lists:delete(EVb, SAVbs) of - [] -> - next_loop_varbinds([], [EndOfVb], MibView, Res, [], - GbMaxVBs); - TryAgainVbs -> - next_loop_varbinds({subagent, SAPid, SAOid, - TryAgainVbs}, - [EndOfVb], MibView, Res, [], - GbMaxVBs) - end; - false -> - %% bad index from subagent - {genErr, (hd(SAVbs))#varbind.org_index, []} - end; - {ErrorStatus, OrgIndex} -> - ?vdebug("next loop varbinds: next subagent" - "~n ErrorStatus: ~p" - "~n OrgIndex: ~p", - [ErrorStatus,OrgIndex]), - {ErrorStatus, OrgIndex, []} - end; -next_loop_varbinds([], [], _MibView, Res, _LAVb, _GbMaxVBs) -> - ?vt("next_loop_varbinds -> entry when done", []), - {noError, 0, Res}. - -try_get_instance(_Vb, #me{mfa = {M, F, A}, asn1_type = ASN1Type}) -> - ?vtrace("try_get_instance -> entry with" - "~n M: ~p" - "~n F: ~p" - "~n A: ~p", [M,F,A]), - Result = (catch dbg_apply(M, F, [get | A])), - % mib shall return {value, <a-nice-value-within-range>} | - % {noValue, noSuchName} (v1) | - % {noValue, noSuchObject | noSuchInstance} (v2, v1) - % everything else (including 'genErr') will generate 'genErr'. - make_value_a_correct_value(Result, ASN1Type, {M, F, A}). - -tab_oid([]) -> [0]; -tab_oid(X) -> X. - - -%%----------------------------------------------------------------- -%% Perform a next, using the varbinds Oid if value is simple -%% value. If value is {endOf<something>, NextOid}, use NextOid. -%% This case happens when a table has returned endOfTable, or -%% a subagent has returned endOfMibView. -%%----------------------------------------------------------------- -varbind_next(#varbind{value = Value, oid = Oid}, MibView) -> - ?vt("varbind_next -> entry with" - "~n Value: ~p" - "~n Oid: ~p" - "~n MibView: ~p", [Value, Oid, MibView]), - case Value of - {endOfTable, NextOid} -> - snmpa_mib:next(get(mibserver), NextOid, MibView); - {endOfMibView, NextOid} -> - snmpa_mib:next(get(mibserver), NextOid, MibView); - _ -> - snmpa_mib:next(get(mibserver), Oid, MibView) - end. - -get_next_table(#me{mfa = {M, F, A}}, TableOid, TableOids, MibView) -> - % We know that all TableOids have at least a column number as oid - ?vt("get_next_table -> entry with" - "~n M: ~p" - "~n F: ~p" - "~n A: ~p" - "~n TableOid: ~p" - "~n TableOids: ~p" - "~n MibView: ~p", [M, F, A, TableOid, TableOids, MibView]), - Sorted = snmpa_svbl:sort_varbinds_rows(TableOids), - case get_next_values_all_rows(Sorted, M,F,A, [], TableOid) of - NewVbs when is_list(NewVbs) -> - ?vt("get_next_table -> " - "~n NewVbs: ~p", [NewVbs]), - % We must now check each Vb for endOfTable and that it is - % in the MibView. If not, it becomes a endOfTable. We - % collect all of these together. - transform_tab_next_result(NewVbs, {[], []}, MibView); - {ErrorStatus, OrgIndex} -> - {ErrorStatus, OrgIndex} - end. - -get_next_values_all_rows([Row | Rows], M, F, A, Res, TabOid) -> - {RowIndex, TableOids} = Row, - Cols = delete_index(TableOids), - ?vt("get_next_values_all_rows -> " - "~n Cols: ~p", [Cols]), - Result = (catch dbg_apply(M, F, [get_next, RowIndex, Cols | A])), - ?vt("get_next_values_all_rows -> " - "~n Result: ~p", [Result]), - case validate_tab_next_res(Result, TableOids, {M, F, A}, TabOid) of - Values when is_list(Values) -> - ?vt("get_next_values_all_rows -> " - "~n Values: ~p", [Values]), - NewRes = lists:append(Values, Res), - get_next_values_all_rows(Rows, M, F, A, NewRes, TabOid); - {ErrorStatus, OrgIndex} -> - {ErrorStatus, OrgIndex} - end; -get_next_values_all_rows([], _M, _F, _A, Res, _TabOid) -> - Res. - -transform_tab_next_result([Vb | Vbs], {Res, EndOfs}, MibView) -> - case Vb#varbind.value of - {endOfTable, _} -> -%% ?vtrace("transform_tab_next_result -> endOfTable: " -%% "split varbinds",[]), -%% R = split_varbinds(Vbs, Res, [Vb | EndOfs]), -%% ?vtrace("transform_tab_next_result -> " -%% "~n R: ~p", [R]), -%% R; - split_varbinds(Vbs, Res, [Vb | EndOfs]); - _ -> - case snmpa_acm:validate_mib_view(Vb#varbind.oid, MibView) of - true -> - transform_tab_next_result(Vbs, {[Vb|Res], EndOfs},MibView); - _ -> - Oid = Vb#varbind.oid, - NewEndOf = Vb#varbind{value = {endOfTable, Oid}}, - transform_tab_next_result(Vbs, {Res, [NewEndOf | EndOfs]}, - MibView) - end - end; -transform_tab_next_result([], {Res, EndOfs}, _MibView) -> - ?vt("transform_tab_next_result -> entry with: " - "~n Res: ~p" - "~n EndIfs: ~p",[Res, EndOfs]), - {ok, Res, EndOfs}. - -%%----------------------------------------------------------------- -%% Three cases: -%% 1) All values ok -%% 2) table_func returned {Error, ...} -%% 3) Some value in Values list is erroneous. -%% Args: Value is a list of values from table_func(get_next, ...) -%% TableOids is a list of {TabRestOid, OrgVb} -%% each element in Values and TableOids correspond to each -%% other. -%% Returns: List of NewVarbinds | -%% {ErrorStatus, OrgIndex} -%% (In the NewVarbinds list, the value may be endOfTable) -%%----------------------------------------------------------------- -validate_tab_next_res(Values, TableOids, Mfa, TabOid) -> - ?vt("validate_tab_next_res -> entry with: " - "~n Values: ~p" - "~n TableOids: ~p" - "~n Mfa: ~p" - "~n TabOid: ~p", [Values, TableOids, Mfa, TabOid]), - {_Col, _ASN1Type, OneIdx} = hd(TableOids), - validate_tab_next_res(Values, TableOids, Mfa, [], TabOid, - next_oid(TabOid), OneIdx). -validate_tab_next_res([{NextOid, Value} | Values], - [{_ColNo, OrgVb, _Index} | TableOids], - Mfa, Res, TabOid, TabNextOid, I) -> - ?vt("validate_tab_next_res -> entry with: " - "~n NextOid: ~p" - "~n Value: ~p" - "~n Values: ~p" - "~n TableOids: ~p" - "~n Mfa: ~p" - "~n TabOid: ~p", - [NextOid, Value, Values, TableOids, Mfa, TabOid]), - #varbind{org_index = OrgIndex} = OrgVb, - ?vt("validate_tab_next_res -> OrgIndex: ~p", [OrgIndex]), - NextCompleteOid = lists:append(TabOid, NextOid), - case snmpa_mib:lookup(get(mibserver), NextCompleteOid) of - {table_column, #me{asn1_type = ASN1Type}, _TableEntryOid} -> - ?vt("validate_tab_next_res -> ASN1Type: ~p", [ASN1Type]), - case make_value_a_correct_value({value, Value}, ASN1Type, Mfa) of - {error, ErrorStatus} -> - ?vt("validate_tab_next_res -> " - "~n ErrorStatus: ~p", [ErrorStatus]), - {ErrorStatus, OrgIndex}; - {value, Type, NValue} -> - ?vt("validate_tab_next_res -> " - "~n Type: ~p" - "~n NValue: ~p", [Type, NValue]), - NewVb = OrgVb#varbind{oid = NextCompleteOid, - variabletype = Type, value = NValue}, - validate_tab_next_res(Values, TableOids, Mfa, - [NewVb | Res], TabOid, TabNextOid, I) - end; - Error -> - user_err("Invalid oid ~w from ~w (get_next). Using genErr => ~p", - [NextOid, Mfa, Error]), - {genErr, OrgIndex} - end; -validate_tab_next_res([endOfTable | Values], - [{_ColNo, OrgVb, _Index} | TableOids], - Mfa, Res, TabOid, TabNextOid, I) -> - ?vt("validate_tab_next_res(endOfTable) -> entry with: " - "~n Values: ~p" - "~n OrgVb: ~p" - "~n TableOids: ~p" - "~n Mfa: ~p" - "~n Res: ~p" - "~n TabOid: ~p" - "~n TabNextOid: ~p" - "~n I: ~p", - [Values, OrgVb, TableOids, Mfa, Res, TabOid, TabNextOid, I]), - NewVb = OrgVb#varbind{value = {endOfTable, TabNextOid}}, - validate_tab_next_res(Values, TableOids, Mfa, [NewVb | Res], - TabOid, TabNextOid, I); -validate_tab_next_res([], [], _Mfa, Res, _TabOid, _TabNextOid, _I) -> - Res; -validate_tab_next_res([], [{_Col, _OrgVb, Index}|_], Mfa, _Res, _, _, _I) -> - user_err("Too few values returned from ~w (get_next)", [Mfa]), - {genErr, Index}; -validate_tab_next_res({genErr, ColNumber}, OrgCols, - Mfa, _Res, _TabOid, _TabNextOid, _I) -> - OrgIndex = snmpa_svbl:col_to_orgindex(ColNumber, OrgCols), - validate_err(table_next, {genErr, OrgIndex}, Mfa); -validate_tab_next_res({error, Reason}, [{_ColNo, OrgVb, _Index} | _TableOids], - Mfa, _Res, _TabOid, _TabNextOid, _I) -> - #varbind{org_index = OrgIndex} = OrgVb, - user_err("Erroneous return value ~w from ~w (get_next)", - [Reason, Mfa]), - {genErr, OrgIndex}; -validate_tab_next_res(Error, [{_ColNo, OrgVb, _Index} | _TableOids], - Mfa, _Res, _TabOid, _TabNextOid, _I) -> - #varbind{org_index = OrgIndex} = OrgVb, - user_err("Invalid return value ~w from ~w (get_next)", - [Error, Mfa]), - {genErr, OrgIndex}; -validate_tab_next_res(TooMany, [], Mfa, _Res, _, _, I) -> - user_err("Too many values ~w returned from ~w (get_next)", - [TooMany, Mfa]), - {genErr, I}. - -%%----------------------------------------------------------------- -%% Func: get_next_sa/4 -%% Purpose: Loop the list of varbinds for the subagent. -%% Call subagent_get_next to retreive -%% the next varbinds. -%% Returns: {ok, ListOfNewVbs, ListOfEndOfMibViewsVbs} | -%% {ErrorStatus, ErrorIndex} -%%----------------------------------------------------------------- -get_next_sa(SAPid, SAOid, SAVbs, MibView) -> - case catch subagent_get_next(SAPid, MibView, SAVbs) of - {noError, 0, NewVbs} -> - NewerVbs = transform_sa_next_result(NewVbs,SAOid,next_oid(SAOid)), - split_varbinds(NewerVbs, [], []); - {ErrorStatus, ErrorIndex, _} -> - {ErrorStatus, ErrorIndex}; - {'EXIT', Reason} -> - user_err("Lost contact with subagent (next) ~w. Using genErr", - [Reason]), - {genErr, 0} - end. -%%----------------------------------------------------------------- -%% Check for wrong prefix returned or endOfMibView, and convert -%% into {endOfMibView, SANextOid}. -%%----------------------------------------------------------------- -transform_sa_next_result([Vb | Vbs], SAOid, SANextOid) - when Vb#varbind.value =:= endOfMibView -> - [Vb#varbind{value = {endOfMibView, SANextOid}} | - transform_sa_next_result(Vbs, SAOid, SANextOid)]; -transform_sa_next_result([Vb | Vbs], SAOid, SANextOid) -> - case lists:prefix(SAOid, Vb#varbind.oid) of - true -> - [Vb | transform_sa_next_result(Vbs, SAOid, SANextOid)]; - _ -> - [Vb#varbind{oid = SANextOid, value = {endOfMibView, SANextOid}} | - transform_sa_next_result(Vbs, SAOid, SANextOid)] - end; -transform_sa_next_result([], _SAOid, _SANextOid) -> - []. - -split_varbinds([Vb | Vbs], Res, EndOfs) -> - case Vb#varbind.value of - {endOfMibView, _} -> split_varbinds(Vbs, Res, [Vb | EndOfs]); - {endOfTable, _} -> split_varbinds(Vbs, Res, [Vb | EndOfs]); - _ -> split_varbinds(Vbs, [Vb | Res], EndOfs) - end; -split_varbinds([], Res, EndOfs) -> {ok, Res, EndOfs}. +do_get_next(MibView, UnsortedVarbinds) -> + Extra = get(net_if_data), + GetModule = get(get_module), + GetModule:do_get_next(MibView, UnsortedVarbinds, Extra). -next_oid(Oid) -> - case lists:reverse(Oid) of - [H | T] -> lists:reverse([H+1 | T]); - [] -> [] - end. %%%----------------------------------------------------------------- @@ -3623,200 +2799,12 @@ next_oid(Oid) -> %%%----------------------------------------------------------------- do_get_bulk(MibView, NonRepeaters, MaxRepetitions, PduMS, Varbinds, GbMaxVBs) -> - ?vtrace("do_get_bulk -> entry with" - "~n MibView: ~p" - "~n NonRepeaters: ~p" - "~n MaxRepetitions: ~p" - "~n PduMS: ~p" - "~n Varbinds: ~p" - "~n GbMaxVBs: ~p", - [MibView, NonRepeaters, MaxRepetitions, PduMS, Varbinds, GbMaxVBs]), - {NonRepVbs, RestVbs} = split_vbs(NonRepeaters, Varbinds, []), - ?vt("do_get_bulk -> split: " - "~n NonRepVbs: ~p" - "~n RestVbs: ~p", [NonRepVbs, RestVbs]), - case do_get_next(MibView, NonRepVbs, GbMaxVBs) of - {noError, 0, UResNonRepVbs} -> - ?vt("do_get_bulk -> next noError: " - "~n UResNonRepVbs: ~p", [UResNonRepVbs]), - ResNonRepVbs = lists:keysort(#varbind.org_index, UResNonRepVbs), - %% Decode the first varbinds, produce a reversed list of - %% listOfBytes. - case (catch enc_vbs(PduMS - ?empty_pdu_size, ResNonRepVbs)) of - {error, Idx, Reason} -> - user_err("failed encoding varbind ~w:~n~p", [Idx, Reason]), - {genErr, Idx, []}; - {SizeLeft, Res} when is_integer(SizeLeft) and is_list(Res) -> - ?vtrace("do_get_bulk -> encoded: " - "~n SizeLeft: ~p" - "~n Res: ~w", [SizeLeft, Res]), - case (catch do_get_rep(SizeLeft, MibView, MaxRepetitions, - RestVbs, Res, - length(UResNonRepVbs), GbMaxVBs)) of - {error, Idx, Reason} -> - user_err("failed encoding varbind ~w:~n~p", - [Idx, Reason]), - {genErr, Idx, []}; - Res when is_list(Res) -> - ?vtrace("do get bulk -> Res: " - "~n ~w", [Res]), - {noError, 0, conv_res(Res)}; - {noError, 0, Data} = OK -> - ?vtrace("do get bulk -> OK: " - "~n length(Data): ~w", [length(Data)]), - OK; - Else -> - ?vtrace("do get bulk -> Else: " - "~n ~w", [Else]), - Else - end; - Res when is_list(Res) -> - {noError, 0, conv_res(Res)} - end; + Extra = get(net_if_data), + GetModule = get(get_module), + GetModule:do_get_bulk(MibView, NonRepeaters, MaxRepetitions, + PduMS, Varbinds, GbMaxVBs, + Extra). - {ErrorStatus, Index, _} -> - ?vdebug("do get bulk: " - "~n ErrorStatus: ~p" - "~n Index: ~p",[ErrorStatus, Index]), - {ErrorStatus, Index, []} - end. - -% sz(L) when list(L) -> length(L); -% sz(B) when binary(B) -> size(B); -% sz(_) -> unknown. - -split_vbs(N, Varbinds, Res) when N =< 0 -> {Res, Varbinds}; -split_vbs(N, [H | T], Res) -> split_vbs(N-1, T, [H | Res]); -split_vbs(_N, [], Res) -> {Res, []}. - -enc_vbs(SizeLeft, Vbs) -> - ?vt("enc_vbs -> entry with" - "~n SizeLeft: ~w", [SizeLeft]), - Fun = fun(Vb, {Sz, Res}) when Sz > 0 -> - ?vt("enc_vbs -> (fun) entry with" - "~n Vb: ~p" - "~n Sz: ~p" - "~n Res: ~w", [Vb, Sz, Res]), - case (catch snmp_pdus:enc_varbind(Vb)) of - {'EXIT', Reason} -> - ?vtrace("enc_vbs -> encode failed: " - "~n Reason: ~p", [Reason]), - throw({error, Vb#varbind.org_index, Reason}); - X -> - ?vt("enc_vbs -> X: ~w", [X]), - Lx = length(X), - ?vt("enc_vbs -> Lx: ~w", [Lx]), - if - Lx < Sz -> - {Sz - length(X), [X | Res]}; - true -> - throw(Res) - end - end; - (_Vb, {_Sz, [_H | T]}) -> - ?vt("enc_vbs -> (fun) entry with" - "~n T: ~p", [T]), - throw(T); - (_Vb, {_Sz, []}) -> - ?vt("enc_vbs -> (fun) entry", []), - throw([]) - end, - lists:foldl(Fun, {SizeLeft, []}, Vbs). - -do_get_rep(Sz, MibView, MaxRepetitions, Varbinds, Res, GbNumVBs, GbMaxVBs) - when MaxRepetitions >= 0 -> - do_get_rep(Sz, MibView, 0, MaxRepetitions, Varbinds, Res, - GbNumVBs, GbMaxVBs); -do_get_rep(Sz, MibView, _MaxRepetitions, Varbinds, Res, GbNumVBs, GbMaxVBs) -> - do_get_rep(Sz, MibView, 0, 0, Varbinds, Res, GbNumVBs, GbMaxVBs). - -conv_res(ResVarbinds) -> - conv_res(ResVarbinds, []). -conv_res([VbListOfBytes | T], Bytes) -> - conv_res(T, VbListOfBytes ++ Bytes); -conv_res([], Bytes) -> - Bytes. - -%% The only other value, then a positive integer, is infinity. -do_get_rep(_Sz, _MibView, Count, Max, _, _Res, GbNumVBs, GbMaxVBs) - when (is_integer(GbMaxVBs) andalso (GbNumVBs > GbMaxVBs)) -> - ?vinfo("Max Get-BULK VBs limit (~w) exceeded (~w) when:" - "~n Count: ~p" - "~n Max: ~p", [GbMaxVBs, GbNumVBs, Count, Max]), - {tooBig, 0, []}; -do_get_rep(_Sz, _MibView, Max, Max, _, Res, _GbNumVBs, _GbMaxVBs) -> - ?vt("do_get_rep -> done when: " - "~n Res: ~p", [Res]), - {noError, 0, conv_res(Res)}; -do_get_rep(Sz, MibView, Count, Max, Varbinds, Res, GbNumVBs, GbMaxVBs) -> - ?vt("do_get_rep -> entry when: " - "~n Sz: ~p" - "~n Count: ~p" - "~n Res: ~w", [Sz, Count, Res]), - case try_get_bulk(Sz, MibView, Varbinds, GbMaxVBs) of - {noError, NextVarbinds, SizeLeft, Res2} -> - ?vt("do_get_rep -> noError: " - "~n SizeLeft: ~p" - "~n Res2: ~p", [SizeLeft, Res2]), - do_get_rep(SizeLeft, MibView, Count+1, Max, NextVarbinds, - Res2 ++ Res, - GbNumVBs + length(Varbinds), GbMaxVBs); - {endOfMibView, _NextVarbinds, _SizeLeft, Res2} -> - ?vt("do_get_rep -> endOfMibView: " - "~n Res2: ~p", [Res2]), - {noError, 0, conv_res(Res2 ++ Res)}; - {ErrorStatus, Index} -> - ?vtrace("do_get_rep -> done when error: " - "~n ErrorStatus: ~p" - "~n Index: ~p", [ErrorStatus, Index]), - {ErrorStatus, Index, []} - end. - -org_index_sort_vbs(Vbs) -> - lists:keysort(#varbind.org_index, Vbs). - -try_get_bulk(Sz, MibView, Varbinds, GbMaxVBs) -> - ?vt("try_get_bulk -> entry with" - "~n Sz: ~w" - "~n MibView: ~w" - "~n Varbinds: ~w", [Sz, MibView, Varbinds]), - case do_get_next(MibView, Varbinds, GbMaxVBs) of - {noError, 0, UNextVarbinds} -> - ?vt("try_get_bulk -> noError: " - "~n UNextVarbinds: ~p", [UNextVarbinds]), - NextVarbinds = org_index_sort_vbs(UNextVarbinds), - case (catch enc_vbs(Sz, NextVarbinds)) of - {error, Idx, Reason} -> - user_err("failed encoding varbind ~w:~n~p", [Idx, Reason]), - ?vtrace("try_get_bulk -> encode error: " - "~n Idx: ~p" - "~n Reason: ~p", [Idx, Reason]), - {genErr, Idx}; - {SizeLeft, Res} when is_integer(SizeLeft) andalso - is_list(Res) -> - ?vt("try get bulk -> encode ok: " - "~n SizeLeft: ~w" - "~n Res: ~w", [SizeLeft, Res]), - {check_end_of_mibview(NextVarbinds), - NextVarbinds, SizeLeft, Res}; - Res when is_list(Res) -> - ?vt("try get bulk -> Res: " - "~n ~w", [Res]), - {endOfMibView, [], 0, Res} - end; - {ErrorStatus, Index, _} -> - ?vt("try_get_bulk -> error: " - "~n ErrorStatus: ~p" - "~n Index: ~p", [ErrorStatus, Index]), - {ErrorStatus, Index} - end. - -%% If all variables in this pass are endOfMibView, -%% there is no reason to continue. -check_end_of_mibview([#varbind{value = endOfMibView} | T]) -> - check_end_of_mibview(T); -check_end_of_mibview([]) -> endOfMibView; -check_end_of_mibview(_) -> noError. %%%-------------------------------------------------- @@ -3834,14 +2822,11 @@ do_subagent_set(Arguments) -> SetModule = get(set_module), apply(SetModule, do_subagent_set, [Arguments]). + + %%%----------------------------------------------------------------- %%% 7. Misc functions %%%----------------------------------------------------------------- -sort_varbindlist(Varbinds) -> - snmpa_svbl:sort_varbindlist(get(mibserver), Varbinds). - -sa_split(SubagentVarbinds) -> - snmpa_svbl:sa_split(SubagentVarbinds). make_response_pdu(ReqId, ErrStatus, ErrIndex, OrgVarbinds, _ResponseVarbinds) when ErrIndex =/= 0 -> @@ -4139,6 +3124,7 @@ report_err(Val, Mfa, Err) -> user_err("Got ~p from ~w. Using ~w", [Val, Mfa, Err]), {error, Err}. + is_valid_pdu_type('get-request') -> true; is_valid_pdu_type('get-next-request') -> true; is_valid_pdu_type('get-bulk-request') -> true; @@ -4176,33 +3162,8 @@ mapfoldl(F, Eas, Accu0, [Hd|Tail]) -> mapfoldl(_F, _Eas, Accu, []) -> {Accu,[]}. -%%----------------------------------------------------------------- -%% Runtime debugging of the agent. -%%----------------------------------------------------------------- - -dbg_apply(M,F,A) -> - case get(verbosity) of - silence -> - apply(M,F,A); - _ -> - ?vlog("~n apply: ~w,~w,~p~n", [M,F,A]), - Res = (catch apply(M,F,A)), - case Res of - {'EXIT', Reason} -> - ?vinfo("Call to: " - "~n Module: ~p" - "~n Function: ~p" - "~n Args: ~p" - "~n" - "~nresulted in an exit" - "~n" - "~n ~p", [M, F, A, Reason]); - _ -> - ?vlog("~n returned: ~p", [Res]) - end, - Res - end. +%% --------------------------------------------------------------------- short_name(none) -> ma; short_name(_Pid) -> sa. @@ -4450,6 +3411,9 @@ get_mib_storage(Opts) -> get_set_mechanism(Opts) -> get_option(set_mechanism, Opts, snmpa_set). +get_get_mechanism(Opts) -> + get_option(get_mechanism, Opts, snmpa_get). + get_authentication_service(Opts) -> get_option(authentication_service, Opts, snmpa_acm). diff --git a/lib/snmp/src/agent/snmpa_app.erl b/lib/snmp/src/agent/snmpa_app.erl index 86ff145e93..c00929c334 100644 --- a/lib/snmp/src/agent/snmpa_app.erl +++ b/lib/snmp/src/agent/snmpa_app.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2016. All Rights Reserved. +%% Copyright Ericsson AB 1996-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -67,6 +67,7 @@ convert_config(Opts) -> SaVerb = get_sub_agent_verbosity(Opts), [{agent_type, AgentType}, {agent_verbosity, SaVerb}, + {get_mechanism, snmpa_get}, {set_mechanism, SetModule}, {authentication_service, AuthModule}, {priority, Prio}, @@ -97,6 +98,7 @@ convert_config(Opts) -> {verbosity, ConfVerb}], [{agent_type, AgentType}, {agent_verbosity, MaVerb}, + {get_mechanism, snmpa_get}, {set_mechanism, SetModule}, {authentication_service, AuthModule}, {db_dir, DbDir}, diff --git a/lib/snmp/src/agent/snmpa_get.erl b/lib/snmp/src/agent/snmpa_get.erl new file mode 100644 index 0000000000..e67975a67d --- /dev/null +++ b/lib/snmp/src/agent/snmpa_get.erl @@ -0,0 +1,1150 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(snmpa_get). + +-behaviour(snmpa_get_mechanism). + + +%%%----------------------------------------------------------------- +%%% snmpa_get_mechanism exports +%%%----------------------------------------------------------------- + +-export([ + do_get/3, do_get/4, + do_get_next/3, + do_get_bulk/7 + ]). + +-define(VMODULE,"GET"). +-include("snmpa_internal.hrl"). +-include("snmp_types.hrl"). +-include("snmp_debug.hrl"). +-include("snmp_verbosity.hrl"). + +-ifndef(default_verbosity). +-define(default_verbosity,silence). +-endif. + +-define(empty_pdu_size, 21). + +-ifdef(snmp_extended_verbosity). +-define(vt(F,A), ?vtrace(F, A)). +-else. +-define(vt(_F, _A), ok). +-endif. + + +-define(AGENT, snmpa_agent). +-define(LIB, snmpa_get_lib). + + + +%%%----------------------------------------------------------------- +%%% 3. GET REQUEST +%%% -------------- +%%% According to RFC1157, section 4.1.2 and RFC1905, section 4.2.1. +%%% In rfc1157:4.1.2 it isn't specified if noSuchName should be +%%% returned even if some other varbind generates a genErr. +%%% In rfc1905:4.2.1 this is not a problem since exceptions are +%%% used, and thus a genErr will be returned anyway. +%%%----------------------------------------------------------------- + +%%----------------------------------------------------------------- +%% Func: do_get/2 +%% Purpose: Handles all VBs in a request that is inside the +%% mibview (local). +%% Returns: {noError, 0, ListOfNewVarbinds} | +%% {ErrorStatus, ErrorIndex, []} +%%----------------------------------------------------------------- + +do_get(UnsortedVarbinds, IsNotification, _Extra) -> + {MyVarbinds, SubagentVarbinds} = ?LIB:agent_sort_vbs(UnsortedVarbinds), + case do_get_local(MyVarbinds, IsNotification) of + {noError, 0, NewMyVarbinds} -> + case do_get_subagents(SubagentVarbinds, IsNotification) of + {noError, 0, NewSubagentVarbinds} -> + {noError, 0, NewMyVarbinds ++ NewSubagentVarbinds}; + {ErrorStatus, ErrorIndex, _} -> + {ErrorStatus, ErrorIndex, []} + end; + {ErrorStatus, ErrorIndex, _} -> + {ErrorStatus, ErrorIndex, []} + end. + + +%%----------------------------------------------------------------- +%% Func: do_get/3 +%% Purpose: do_get handles "getRequests". +%% Pre: incoming varbinds have type == 'NULL', value == unSpecified +%% Returns: {noError, 0, ListOfNewVarbinds} | +%% {ErrorStatus, ErrorIndex, []} +%%----------------------------------------------------------------- + +do_get(MibView, UnsortedVarbinds, IsNotification, Extra) -> + ?vtrace("do_get -> entry with" + "~n MibView: ~p" + "~n UnsortedVarbinds: ~p" + "~n IsNotification: ~p", + [MibView, UnsortedVarbinds, IsNotification]), + %% This is me, the master, so go ahead + {OutSideView, InSideView} = ?LIB:split_vbs_view(UnsortedVarbinds, MibView), + {Error, Index, NewVbs} = do_get(InSideView, IsNotification, Extra), + {Error, Index, NewVbs ++ OutSideView}. + + + +%%----------------------------------------------------------------- +%% Func: do_get_local/2,3 +%% Purpose: Loop the variablebindings list. We know that each varbind +%% in that list belongs to us. +%% Returns: {noError, 0, ListOfNewVarbinds} | +%% {ErrorStatus, ErrorIndex, []} +%%----------------------------------------------------------------- + +do_get_local(VBs, IsNotification) -> + do_get_local(VBs, [], IsNotification). + +do_get_local([Vb | Vbs], Res, IsNotification) -> + case try_get(Vb, IsNotification) of + NewVb when is_record(NewVb, varbind) -> + do_get_local(Vbs, [NewVb | Res], IsNotification); + ListOfNewVb when is_list(ListOfNewVb) -> + do_get_local(Vbs, lists:append(ListOfNewVb, Res), IsNotification); + {error, Error, OrgIndex} -> + {Error, OrgIndex, []} + end; +do_get_local([], Res, _IsNotification) -> + {noError, 0, Res}. + + + +%%----------------------------------------------------------------- +%% Func: do_get_subagents/2 +%% Purpose: Loop the list of varbinds for different subagents. +%% For each of them, call sub_agent_get to retreive +%% the values for them. +%% Returns: {noError, 0, ListOfNewVarbinds} | +%% {ErrorStatus, ErrorIndex, []} +%%----------------------------------------------------------------- +do_get_subagents(SubagentVarbinds, IsNotification) -> + do_get_subagents(SubagentVarbinds, [], IsNotification). +do_get_subagents([{SubAgentPid, SAVbs} | Tail], Res, IsNotification) -> + {_SAOids, Vbs} = ?LIB:sa_split(SAVbs), + case catch ?AGENT:subagent_get(SubAgentPid, Vbs, IsNotification) of + {noError, 0, NewVbs} -> + do_get_subagents(Tail, lists:append(NewVbs, Res), IsNotification); + {ErrorStatus, ErrorIndex, _} -> + {ErrorStatus, ErrorIndex, []}; + {'EXIT', Reason} -> + ?LIB:user_err("Lost contact with subagent (get) ~w. Using genErr", + [Reason]), + {genErr, 0, []} + end; +do_get_subagents([], Res, _IsNotification) -> + {noError, 0, Res}. + + +%%----------------------------------------------------------------- +%% Func: try_get/2 +%% Returns: {error, ErrorStatus, OrgIndex} | +%% #varbind | +%% List of #varbind +%%----------------------------------------------------------------- +try_get(IVb, IsNotification) when is_record(IVb, ivarbind) -> + ?vtrace("try_get(ivarbind) -> entry with" + "~n IVb: ~p", [IVb]), + get_var_value_from_ivb(IVb, IsNotification); +try_get({TableOid, TableVbs}, IsNotification) -> + ?vtrace("try_get(table) -> entry with" + "~n TableOid: ~p" + "~n TableVbs: ~p", [TableOid, TableVbs]), + [#ivarbind{mibentry = MibEntry}|_] = TableVbs, + {NoAccessVbs, AccessVbs} = + check_all_table_vbs(TableVbs, IsNotification, [], []), + case get_tab_value_from_mib(MibEntry, TableOid, AccessVbs) of + {error, ErrorStatus, OrgIndex} -> + {error, ErrorStatus, OrgIndex}; + NVbs -> + NVbs ++ NoAccessVbs + end. + +%%----------------------------------------------------------------- +%% Make sure all requested columns are accessible. +%%----------------------------------------------------------------- +check_all_table_vbs([IVb| IVbs], IsNotification, NoA, A) -> + #ivarbind{mibentry = Me, varbind = Vb} = IVb, + case Me#me.access of + 'not-accessible' -> + NNoA = [Vb#varbind{value = noSuchInstance} | NoA], + check_all_table_vbs(IVbs, IsNotification, NNoA, A); + 'accessible-for-notify' when IsNotification =:= false -> + NNoA = [Vb#varbind{value = noSuchInstance} | NoA], + check_all_table_vbs(IVbs, IsNotification, NNoA, A); + 'write-only' -> + NNoA = [Vb#varbind{value = noSuchInstance} | NoA], + check_all_table_vbs(IVbs, IsNotification, NNoA, A); + _ -> + check_all_table_vbs(IVbs, IsNotification, NoA, [IVb | A]) + end; +check_all_table_vbs([], _IsNotification, NoA, A) -> {NoA, A}. + +%%----------------------------------------------------------------- +%% Returns: {error, ErrorStatus, OrgIndex} | +%% #varbind +%%----------------------------------------------------------------- +get_var_value_from_ivb(IVb, IsNotification) + when IVb#ivarbind.status =:= noError -> + ?vtrace("get_var_value_from_ivb(noError) -> entry", []), + #ivarbind{mibentry = Me, varbind = Vb} = IVb, + #varbind{org_index = OrgIndex, oid = Oid} = Vb, + case Me#me.access of + 'not-accessible' -> + Vb#varbind{value = noSuchInstance}; + 'accessible-for-notify' when IsNotification =:= false -> + Vb#varbind{value = noSuchInstance}; + 'write-only' -> + Vb#varbind{value = noSuchInstance}; + _ -> + case get_var_value_from_mib(Me, Oid) of + {value, Type, Value} -> + Vb#varbind{variabletype = Type, value = Value}; + {error, ErrorStatus} -> + {error, ErrorStatus, OrgIndex} + end + end; +get_var_value_from_ivb(#ivarbind{status = Status, varbind = Vb}, _) -> + ?vtrace("get_var_value_from_ivb(~p) -> entry", [Status]), + Vb#varbind{value = Status}. + +%%----------------------------------------------------------------- +%% Func: get_var_value_from_mib/1 +%% Purpose: +%% Returns: {error, ErrorStatus} | +%% {value, Type, Value} +%%----------------------------------------------------------------- +%% Pre: Oid is a correct instance Oid (lookup checked that). +%% Returns: A correct return value (see ?AGENT:make_value_a_correct_value) +get_var_value_from_mib(#me{entrytype = variable, + asn1_type = ASN1Type, + mfa = {Mod, Func, Args}}, + _Oid) -> + ?vtrace("get_var_value_from_mib(variable) -> entry when" + "~n Mod: ~p" + "~n Func: ~p" + "~n Args: ~p", [Mod, Func, Args]), + Result = (catch ?LIB:dbg_apply(Mod, Func, [get | Args])), + %% mib shall return {value, <a-nice-value-within-range>} | + %% {noValue, noSuchName} (v1) | + %% {noValue, noSuchObject | noSuchInstance} (v2, v1) + %% everything else (including 'genErr') will generate 'genErr'. + ?AGENT:make_value_a_correct_value(Result, ASN1Type, {Mod, Func, Args}); + +get_var_value_from_mib(#me{entrytype = table_column, + oid = MeOid, + asn1_type = ASN1Type, + mfa = {Mod, Func, Args}}, + Oid) -> + ?vtrace("get_var_value_from_mib(table_column) -> entry when" + "~n MeOid: ~p" + "~n Mod: ~p" + "~n Func: ~p" + "~n Args: ~p" + "~n Oid: ~p", [MeOid, Mod, Func, Args, Oid]), + Col = lists:last(MeOid), + Indexes = snmp_misc:diff(Oid, MeOid), + [Result] = (catch ?LIB:dbg_apply(Mod, Func, [get, Indexes, [Col] | Args])), + ?AGENT:make_value_a_correct_value(Result, ASN1Type, + {Mod, Func, Args, Indexes, Col}). + + +%% For table operations we need to pass RestOid down to the table-function. +%% Its up to the table-function to check for noSuchInstance (ex: a +%% non-existing row). +%% Returns: {error, ErrorStatus, OrgIndex} | +%% {value, Type, Value} +get_tab_value_from_mib(#me{mfa = {Mod, Func, Args}}, TableOid, TableVbs) -> + ?vtrace("get_tab_value_from_mib -> entry when" + "~n Mod: ~p" + "~n Func: ~p" + "~n Args: ~p", [Mod, Func, Args]), + TableOpsWithShortOids = ?LIB:delete_prefixes(TableOid, TableVbs), + SortedVBsRows = snmpa_svbl:sort_varbinds_rows(TableOpsWithShortOids), + case get_value_all_rows(SortedVBsRows, Mod, Func, Args, []) of + {Error, Index} -> + #ivarbind{varbind = Vb} = lists:nth(Index, TableVbs), + {error, Error, Vb#varbind.org_index}; + ListOfValues -> + merge_varbinds_and_value(TableVbs, ListOfValues) + end. + +%%----------------------------------------------------------------- +%% Values is a scrambled list of {CorrectValue, Index}, where Index +%% is index into the #ivarbind list. So for each Value, we must +%% find the corresponding #ivarbind, and merge them into a new +%% #varbind. +%% The Values list comes from validate_tab_res. +%%----------------------------------------------------------------- +merge_varbinds_and_value(IVbs, [{{value, Type, Value}, Index} | Values]) -> + #ivarbind{varbind = Vb} = lists:nth(Index, IVbs), + [Vb#varbind{variabletype = Type, value = Value} | + merge_varbinds_and_value(IVbs, Values)]; +merge_varbinds_and_value(_, []) -> []. + +get_value_all_rows([{[], OrgCols} | Rows], Mod, Func, Args, Res) -> + ?vtrace("get_value_all_rows -> entry when" + "~n OrgCols: ~p", [OrgCols]), + Cols = [{{value, noValue, noSuchInstance}, Index} || + {_Col, _ASN1Type, Index} <- OrgCols], + NewRes = lists:append(Cols, Res), + get_value_all_rows(Rows, Mod, Func, Args, NewRes); +get_value_all_rows([{RowIndex, OrgCols} | Rows], Mod, Func, Args, Res) -> + ?vtrace("get_value_all_rows -> entry when" + "~n RowIndex: ~p" + "~n OrgCols: ~p", [RowIndex, OrgCols]), + {DOrgCols, Dup} = remove_duplicates(OrgCols), + Cols = delete_index(DOrgCols), + Result = (catch ?LIB:dbg_apply(Mod, Func, [get, RowIndex, Cols | Args])), + case validate_tab_res(Result, DOrgCols, {Mod, Func, Args}) of + Values when is_list(Values) -> + NVals = restore_duplicates(Dup, Values), + NewRes = lists:append(NVals, Res), + get_value_all_rows(Rows, Mod, Func, Args, NewRes); + {error, ErrorStatus, Index} -> + ?AGENT:validate_err(row_set, {ErrorStatus, Index}, {Mod, Func, Args}) + end; +get_value_all_rows([], _Mod, _Func, _Args, Res) -> + ?vtrace("get_value_all_rows -> entry when done" + "~n Res: ~p", [Res]), + Res. + +%%----------------------------------------------------------------- +%% Args: {RowIndex, list of {ShortOid, ASN1Type}} +%% Returns: list of Col +%%----------------------------------------------------------------- +delete_index([{Col, _Val, _OrgIndex} | T]) -> + [Col | delete_index(T)]; +delete_index([]) -> []. + +%%----------------------------------------------------------------- +%% This function is called before 'get' on a table, and removes +%% any duplicate columns. It returns {Cols, DupInfo}. The Cols +%% are the unique columns. The instrumentation function is +%% called to get the values. These values, together with the +%% DupInfo, is later passed to restore_duplicates, which uses +%% the retrieved values to reconstruct the original column list, +%% but with the retrieved value for each column. +%%----------------------------------------------------------------- +remove_duplicates(Cols) -> + remove_duplicates(Cols, [], []). + + +remove_duplicates([{Col, V1, OrgIdx1}, {Col, V2, OrgIdx2} | T], NCols, Dup) -> + remove_duplicates([{Col, V1, OrgIdx1} | T], NCols, + [{Col, V2, OrgIdx2} | Dup]); +remove_duplicates([Col | T], NCols, Dup) -> + remove_duplicates(T, [Col | NCols], Dup); +remove_duplicates([], NCols, Dup) -> + {lists:reverse(NCols), lists:reverse(Dup)}. + +restore_duplicates([], Cols) -> + [{Val, OrgIndex} || {_Col, Val, OrgIndex} <- Cols]; +restore_duplicates([{Col, _Val2, OrgIndex2} | Dup], + [{Col, NVal, OrgIndex1} | Cols]) -> + [{NVal, OrgIndex2} | + restore_duplicates(Dup, [{Col, NVal, OrgIndex1} | Cols])]; +restore_duplicates(Dup, [{_Col, Val, OrgIndex} | T]) -> + [{Val, OrgIndex} | restore_duplicates(Dup, T)]. + + + +%%----------------------------------------------------------------- +%% Three cases: +%% 1) All values ok +%% 2) table_func returned {Error, ...} +%% 3) Some value in Values list is erroneous. +%% Args: Value is a list of values from table_func(get..) +%% OrgCols is a list with {Col, ASN1Type, OrgIndex} +%% each element in Values and OrgCols correspond to each +%% other. +%%----------------------------------------------------------------- +validate_tab_res(Values, OrgCols, Mfa) when is_list(Values) -> + {_Col, _ASN1Type, OneIdx} = hd(OrgCols), + validate_tab_res(Values, OrgCols, Mfa, [], OneIdx); +validate_tab_res({noValue, Error}, OrgCols, Mfa) -> + Values = lists:duplicate(length(OrgCols), {noValue, Error}), + validate_tab_res(Values, OrgCols, Mfa); +validate_tab_res({genErr, Col}, OrgCols, Mfa) -> + case lists:keysearch(Col, 1, OrgCols) of + {value, {_Col, _ASN1Type, Index}} -> + {error, genErr, Index}; + _ -> + ?LIB:user_err("Invalid column in {genErr, ~w} from ~w (get)", + [Col, Mfa]), + [{_Col, _ASN1Type, Index} | _] = OrgCols, + {error, genErr, Index} + end; +validate_tab_res(genErr, [{_Col, __ASN1Type, Index} | _OrgCols], _Mfa) -> + {error, genErr, Index}; +validate_tab_res(Error, [{_Col, _ASN1Type, Index} | _OrgCols], Mfa) -> + ?LIB:user_err("Invalid return value ~w from ~w (get)",[Error, Mfa]), + {error, genErr, Index}. + +validate_tab_res([Value | Values], + [{Col, ASN1Type, Index} | OrgCols], + Mfa, Res, I) -> + %% This one makes it possible to return a list of genErr, which + %% is not allowed according to the manual. But that's ok, as + %% everything else will generate a genErr! (the only problem is + %% that it won't generate a user_error). + case ?AGENT:make_value_a_correct_value(Value, ASN1Type, Mfa) of + {error, ErrorStatus} -> + {error, ErrorStatus, Index}; + CorrectValue -> + NewRes = [{Col, CorrectValue, Index} | Res], + validate_tab_res(Values, OrgCols, Mfa, NewRes, I) + end; +validate_tab_res([], [], _Mfa, Res, _I) -> + lists:reverse(Res); +validate_tab_res([], [{_Col, _ASN1Type, Index}|_], Mfa, _Res, _I) -> + ?LIB:user_err("Too few values returned from ~w (get)", [Mfa]), + {error, genErr, Index}; +validate_tab_res(_TooMany, [], Mfa, _Res, I) -> + ?LIB:user_err("Too many values returned from ~w (get)", [Mfa]), + {error, genErr, I}. + + + +%%%----------------------------------------------------------------- +%%% 4. GET-NEXT REQUEST +%%% -------------- +%%% According to RFC1157, section 4.1.3 and RFC1905, section 4.2.2. +%%%----------------------------------------------------------------- +%%----------------------------------------------------------------- +%% Func: do_get_next/3 +%% Purpose: do_get_next handles "getNextRequests". +%% Note: Even if it is SNMPv1, a varbind's value can be +%% endOfMibView. This is converted to noSuchName in process_pdu. +%% Returns: {noError, 0, ListOfNewVarbinds} | +%% {ErrorStatus, ErrorIndex, []} +%% Note2: ListOfNewVarbinds is not sorted in any order!!! +%% Alg: First, the variables are sorted in OID order. +%% +%% Second, next in the MIB is performed for each OID, and +%% the result is collected as: if next oid is a variable, +%% perform a get to retrieve its value; if next oid is in a +%% table, save this value and continue until we get an oid +%% outside this table. Then perform get_next on the table, +%% and continue with all endOfTables and the oid outside the +%% table; if next oid is an subagent, save this value and +%% continue as in the table case. +%% +%% Third, each response is checked for endOfMibView, or (for +%% subagents) that the Oid returned has the correct prefix. +%% (This is necessary since an SA can be registered under many +%% separated subtrees, and if the last variable in the first +%% subtree is requested in a next, the SA will return the first +%% variable in the second subtree. This might be working, since +%% there may be a variable in between these subtrees.) For each +%% of these, a new get-next is performed, one at a time. +%% This alg. might be optimised in several ways. The most +%% striking one is that the same SA might be called several +%% times, when one time should be enough. But it isn't clear +%% that this really matters, since many nexts across the same +%% subagent must be considered to be very rare. +%%----------------------------------------------------------------- + +do_get_next(MibView, UnsortedVBs, _Extra) -> + do_get_next2(MibView, UnsortedVBs, infinity). + +%% The third argument is only used if we are called as result +%% of a get-bulk request. +do_get_next2(_MibView, UnsortedVarbinds, GbMaxVBs) + when (is_integer(GbMaxVBs) andalso (length(UnsortedVarbinds) > GbMaxVBs)) -> + {tooBig, 0, []}; % What is the correct index in this case? +do_get_next2(MibView, UnsortedVBs, GbMaxVBs) -> + ?vt("do_get_next2 -> entry when" + "~n MibView: ~p" + "~n UnsortedVBs: ~p", [MibView, UnsortedVBs]), + SortedVBs = ?LIB:oid_sort_vbs(UnsortedVBs), + ?vt("do_get_next -> " + "~n SortedVBs: ~p", [SortedVBs]), + next_loop_varbinds([], SortedVBs, MibView, [], [], GbMaxVBs). + +next_loop_varbinds(_, Vbs, _MibView, Res, _LAVb, GbMaxVBs) + when (is_integer(GbMaxVBs) andalso + ((length(Vbs) + length(Res)) > GbMaxVBs)) -> + {tooBig, 0, []}; % What is the correct index in this case? + +%% LAVb is Last Accessible Vb +next_loop_varbinds([], [Vb | Vbs], MibView, Res, LAVb, GbMaxVBs) -> + ?vt("next_loop_varbinds -> entry when" + "~n Vb: ~p" + "~n MibView: ~p", [Vb, MibView]), + case varbind_next(Vb, MibView) of + endOfMibView -> + ?vt("next_loop_varbind -> endOfMibView", []), + RVb = if LAVb =:= [] -> Vb; + true -> LAVb + end, + NewVb = RVb#varbind{variabletype = 'NULL', value = endOfMibView}, + next_loop_varbinds([], Vbs, MibView, [NewVb | Res], [], GbMaxVBs); + + {variable, ME, VarOid} when ((ME#me.access =/= 'not-accessible') andalso + (ME#me.access =/= 'write-only') andalso + (ME#me.access =/= 'accessible-for-notify')) -> + ?vt("next_loop_varbind -> variable: " + "~n ME: ~p" + "~n VarOid: ~p", [ME, VarOid]), + case try_get_instance(Vb, ME) of + {value, noValue, _NoSuchSomething} -> + ?vt("next_loop_varbind -> noValue", []), + %% Try next one + NewVb = Vb#varbind{oid = VarOid, + value = 'NULL'}, + next_loop_varbinds([], [NewVb | Vbs], MibView, Res, [], + GbMaxVBs); + {value, Type, Value} -> + ?vt("next_loop_varbind -> value" + "~n Type: ~p" + "~n Value: ~p", [Type, Value]), + NewVb = Vb#varbind{oid = VarOid, + variabletype = Type, + value = Value}, + next_loop_varbinds([], Vbs, MibView, [NewVb | Res], [], + GbMaxVBs); + {error, ErrorStatus} -> + ?vdebug("next loop varbinds:" + "~n ErrorStatus: ~p",[ErrorStatus]), + {ErrorStatus, Vb#varbind.org_index, []} + end; + {variable, _ME, VarOid} -> + ?vt("next_loop_varbind -> variable: " + "~n VarOid: ~p", [VarOid]), + RVb = if LAVb =:= [] -> Vb; + true -> LAVb + end, + NewVb = Vb#varbind{oid = VarOid, value = 'NULL'}, + next_loop_varbinds([], [NewVb | Vbs], MibView, Res, RVb, GbMaxVBs); + {table, TableOid, TableRestOid, ME} -> + ?vt("next_loop_varbind -> table: " + "~n TableOid: ~p" + "~n TableRestOid: ~p" + "~n ME: ~p", [TableOid, TableRestOid, ME]), + next_loop_varbinds({table, TableOid, ME, + [{tab_oid(TableRestOid), Vb}]}, + Vbs, MibView, Res, [], GbMaxVBs); + {subagent, SubAgentPid, SAOid} -> + ?vt("next_loop_varbind -> subagent: " + "~n SubAgentPid: ~p" + "~n SAOid: ~p", [SubAgentPid, SAOid]), + NewVb = Vb#varbind{variabletype = 'NULL', value = 'NULL'}, + next_loop_varbinds({subagent, SubAgentPid, SAOid, [NewVb]}, + Vbs, MibView, Res, [], GbMaxVBs) + end; +next_loop_varbinds({table, TableOid, ME, TabOids}, + [Vb | Vbs], MibView, Res, _LAVb, GbMaxVBs) -> + ?vt("next_loop_varbinds(table) -> entry with" + "~n TableOid: ~p" + "~n Vb: ~p", [TableOid, Vb]), + case varbind_next(Vb, MibView) of + {table, TableOid, TableRestOid, _ME} -> + next_loop_varbinds({table, TableOid, ME, + [{tab_oid(TableRestOid), Vb} | TabOids]}, + Vbs, MibView, Res, [], GbMaxVBs); + _ -> + case get_next_table(ME, TableOid, TabOids, MibView) of + {ok, TabRes, TabEndOfTabVbs} -> + NewVbs = lists:append(TabEndOfTabVbs, [Vb | Vbs]), + NewRes = lists:append(TabRes, Res), + next_loop_varbinds([], NewVbs, MibView, NewRes, [], + GbMaxVBs); + {ErrorStatus, OrgIndex} -> + ?vdebug("next loop varbinds: next varbind" + "~n ErrorStatus: ~p" + "~n OrgIndex: ~p", + [ErrorStatus,OrgIndex]), + {ErrorStatus, OrgIndex, []} + end + end; +next_loop_varbinds({table, TableOid, ME, TabOids}, + [], MibView, Res, _LAVb, GbMaxVBs) -> + ?vt("next_loop_varbinds(table) -> entry with" + "~n TableOid: ~p", [TableOid]), + case get_next_table(ME, TableOid, TabOids, MibView) of + {ok, TabRes, TabEndOfTabVbs} -> + ?vt("next_loop_varbinds(table) -> get_next_table result:" + "~n TabRes: ~p" + "~n TabEndOfTabVbs: ~p", [TabRes, TabEndOfTabVbs]), + NewRes = lists:append(TabRes, Res), + next_loop_varbinds([], TabEndOfTabVbs, MibView, NewRes, [], + GbMaxVBs); + {ErrorStatus, OrgIndex} -> + ?vdebug("next loop varbinds: next table" + "~n ErrorStatus: ~p" + "~n OrgIndex: ~p", + [ErrorStatus,OrgIndex]), + {ErrorStatus, OrgIndex, []} + end; +next_loop_varbinds({subagent, SAPid, SAOid, SAVbs}, + [Vb | Vbs], MibView, Res, _LAVb, GbMaxVBs) -> + ?vt("next_loop_varbinds(subagent) -> entry with" + "~n SAPid: ~p" + "~n SAOid: ~p" + "~n Vb: ~p", [SAPid, SAOid, Vb]), + case varbind_next(Vb, MibView) of + {subagent, _SubAgentPid, SAOid} -> + next_loop_varbinds({subagent, SAPid, SAOid, + [Vb | SAVbs]}, + Vbs, MibView, Res, [], GbMaxVBs); + _ -> + case get_next_sa(SAPid, SAOid, SAVbs, MibView) of + {ok, SARes, SAEndOfMibViewVbs} -> + NewVbs = lists:append(SAEndOfMibViewVbs, [Vb | Vbs]), + NewRes = lists:append(SARes, Res), + next_loop_varbinds([], NewVbs, MibView, NewRes, [], + GbMaxVBs); + {noSuchName, OrgIndex} -> + %% v1 reply, treat this Vb as endOfMibView, and try again + %% for the others. + case lists:keysearch(OrgIndex, #varbind.org_index, SAVbs) of + {value, EVb} -> + NextOid = next_oid(SAOid), + EndOfVb = + EVb#varbind{oid = NextOid, + value = {endOfMibView, NextOid}}, + case lists:delete(EVb, SAVbs) of + [] -> + next_loop_varbinds([], [EndOfVb, Vb | Vbs], + MibView, Res, [], + GbMaxVBs); + TryAgainVbs -> + next_loop_varbinds({subagent, SAPid, SAOid, + TryAgainVbs}, + [EndOfVb, Vb | Vbs], + MibView, Res, [], + GbMaxVBs) + end; + false -> + %% bad index from subagent + {genErr, (hd(SAVbs))#varbind.org_index, []} + end; + {ErrorStatus, OrgIndex} -> + ?vdebug("next loop varbinds: next subagent" + "~n Vb: ~p" + "~n ErrorStatus: ~p" + "~n OrgIndex: ~p", + [Vb,ErrorStatus,OrgIndex]), + {ErrorStatus, OrgIndex, []} + end + end; +next_loop_varbinds({subagent, SAPid, SAOid, SAVbs}, + [], MibView, Res, _LAVb, GbMaxVBs) -> + ?vt("next_loop_varbinds(subagent) -> entry with" + "~n SAPid: ~p" + "~n SAOid: ~p", [SAPid, SAOid]), + case get_next_sa(SAPid, SAOid, SAVbs, MibView) of + {ok, SARes, SAEndOfMibViewVbs} -> + NewRes = lists:append(SARes, Res), + next_loop_varbinds([], SAEndOfMibViewVbs, MibView, NewRes, [], + GbMaxVBs); + {noSuchName, OrgIndex} -> + %% v1 reply, treat this Vb as endOfMibView, and try again for + %% the others. + case lists:keysearch(OrgIndex, #varbind.org_index, SAVbs) of + {value, EVb} -> + NextOid = next_oid(SAOid), + EndOfVb = EVb#varbind{oid = NextOid, + value = {endOfMibView, NextOid}}, + case lists:delete(EVb, SAVbs) of + [] -> + next_loop_varbinds([], [EndOfVb], MibView, Res, [], + GbMaxVBs); + TryAgainVbs -> + next_loop_varbinds({subagent, SAPid, SAOid, + TryAgainVbs}, + [EndOfVb], MibView, Res, [], + GbMaxVBs) + end; + false -> + %% bad index from subagent + {genErr, (hd(SAVbs))#varbind.org_index, []} + end; + {ErrorStatus, OrgIndex} -> + ?vdebug("next loop varbinds: next subagent" + "~n ErrorStatus: ~p" + "~n OrgIndex: ~p", + [ErrorStatus,OrgIndex]), + {ErrorStatus, OrgIndex, []} + end; +next_loop_varbinds([], [], _MibView, Res, _LAVb, _GbMaxVBs) -> + ?vt("next_loop_varbinds -> entry when done", []), + {noError, 0, Res}. + +try_get_instance(_Vb, #me{mfa = {M, F, A}, asn1_type = ASN1Type}) -> + ?vtrace("try_get_instance -> entry with" + "~n M: ~p" + "~n F: ~p" + "~n A: ~p", [M,F,A]), + Result = (catch ?LIB:dbg_apply(M, F, [get | A])), + % mib shall return {value, <a-nice-value-within-range>} | + % {noValue, noSuchName} (v1) | + % {noValue, noSuchObject | noSuchInstance} (v2, v1) + % everything else (including 'genErr') will generate 'genErr'. + ?AGENT:make_value_a_correct_value(Result, ASN1Type, {M, F, A}). + +tab_oid([]) -> [0]; +tab_oid(X) -> X. + + +%%----------------------------------------------------------------- +%% Perform a next, using the varbinds Oid if value is simple +%% value. If value is {endOf<something>, NextOid}, use NextOid. +%% This case happens when a table has returned endOfTable, or +%% a subagent has returned endOfMibView. +%%----------------------------------------------------------------- +varbind_next(#varbind{value = Value, oid = Oid}, MibView) -> + ?vt("varbind_next -> entry with" + "~n Value: ~p" + "~n Oid: ~p" + "~n MibView: ~p", [Value, Oid, MibView]), + case Value of + {endOfTable, NextOid} -> + snmpa_mib:next(get(mibserver), NextOid, MibView); + {endOfMibView, NextOid} -> + snmpa_mib:next(get(mibserver), NextOid, MibView); + _ -> + snmpa_mib:next(get(mibserver), Oid, MibView) + end. + +get_next_table(#me{mfa = {M, F, A}}, TableOid, TableOids, MibView) -> + % We know that all TableOids have at least a column number as oid + ?vt("get_next_table -> entry with" + "~n M: ~p" + "~n F: ~p" + "~n A: ~p" + "~n TableOid: ~p" + "~n TableOids: ~p" + "~n MibView: ~p", [M, F, A, TableOid, TableOids, MibView]), + Sorted = snmpa_svbl:sort_varbinds_rows(TableOids), + case get_next_values_all_rows(Sorted, M,F,A, [], TableOid) of + NewVbs when is_list(NewVbs) -> + ?vt("get_next_table -> " + "~n NewVbs: ~p", [NewVbs]), + % We must now check each Vb for endOfTable and that it is + % in the MibView. If not, it becomes a endOfTable. We + % collect all of these together. + transform_tab_next_result(NewVbs, {[], []}, MibView); + {ErrorStatus, OrgIndex} -> + {ErrorStatus, OrgIndex} + end. + +get_next_values_all_rows([Row | Rows], M, F, A, Res, TabOid) -> + {RowIndex, TableOids} = Row, + Cols = delete_index(TableOids), + ?vt("get_next_values_all_rows -> " + "~n Cols: ~p", [Cols]), + Result = (catch ?LIB:dbg_apply(M, F, [get_next, RowIndex, Cols | A])), + ?vt("get_next_values_all_rows -> " + "~n Result: ~p", [Result]), + case validate_tab_next_res(Result, TableOids, {M, F, A}, TabOid) of + Values when is_list(Values) -> + ?vt("get_next_values_all_rows -> " + "~n Values: ~p", [Values]), + NewRes = lists:append(Values, Res), + get_next_values_all_rows(Rows, M, F, A, NewRes, TabOid); + {ErrorStatus, OrgIndex} -> + {ErrorStatus, OrgIndex} + end; +get_next_values_all_rows([], _M, _F, _A, Res, _TabOid) -> + Res. + +transform_tab_next_result([Vb | Vbs], {Res, EndOfs}, MibView) -> + case Vb#varbind.value of + {endOfTable, _} -> + {ResVBs, EndOfVBs} = ?LIB:split_vbs(Vbs, Res, [Vb | EndOfs]), + {ok, ResVBs, EndOfVBs}; + _ -> + case snmpa_acm:validate_mib_view(Vb#varbind.oid, MibView) of + true -> + transform_tab_next_result(Vbs, {[Vb|Res], EndOfs},MibView); + _ -> + Oid = Vb#varbind.oid, + NewEndOf = Vb#varbind{value = {endOfTable, Oid}}, + transform_tab_next_result(Vbs, {Res, [NewEndOf | EndOfs]}, + MibView) + end + end; +transform_tab_next_result([], {Res, EndOfs}, _MibView) -> + ?vt("transform_tab_next_result -> entry with: " + "~n Res: ~p" + "~n EndIfs: ~p",[Res, EndOfs]), + {ok, Res, EndOfs}. + + + +%%----------------------------------------------------------------- +%% Three cases: +%% 1) All values ok +%% 2) table_func returned {Error, ...} +%% 3) Some value in Values list is erroneous. +%% Args: Value is a list of values from table_func(get_next, ...) +%% TableOids is a list of {TabRestOid, OrgVb} +%% each element in Values and TableOids correspond to each +%% other. +%% Returns: List of NewVarbinds | +%% {ErrorStatus, OrgIndex} +%% (In the NewVarbinds list, the value may be endOfTable) +%%----------------------------------------------------------------- +validate_tab_next_res(Values, TableOids, Mfa, TabOid) -> + ?vt("validate_tab_next_res -> entry with: " + "~n Values: ~p" + "~n TableOids: ~p" + "~n Mfa: ~p" + "~n TabOid: ~p", [Values, TableOids, Mfa, TabOid]), + {_Col, _ASN1Type, OneIdx} = hd(TableOids), + validate_tab_next_res(Values, TableOids, Mfa, [], TabOid, + next_oid(TabOid), OneIdx). +validate_tab_next_res([{NextOid, Value} | Values], + [{_ColNo, OrgVb, _Index} | TableOids], + Mfa, Res, TabOid, TabNextOid, I) -> + ?vt("validate_tab_next_res -> entry with: " + "~n NextOid: ~p" + "~n Value: ~p" + "~n Values: ~p" + "~n TableOids: ~p" + "~n Mfa: ~p" + "~n TabOid: ~p", + [NextOid, Value, Values, TableOids, Mfa, TabOid]), + #varbind{org_index = OrgIndex} = OrgVb, + ?vt("validate_tab_next_res -> OrgIndex: ~p", [OrgIndex]), + NextCompleteOid = lists:append(TabOid, NextOid), + case snmpa_mib:lookup(get(mibserver), NextCompleteOid) of + {table_column, #me{asn1_type = ASN1Type}, _TableEntryOid} -> + ?vt("validate_tab_next_res -> ASN1Type: ~p", [ASN1Type]), + case ?AGENT:make_value_a_correct_value({value, Value}, ASN1Type, Mfa) of + {error, ErrorStatus} -> + ?vt("validate_tab_next_res -> " + "~n ErrorStatus: ~p", [ErrorStatus]), + {ErrorStatus, OrgIndex}; + {value, Type, NValue} -> + ?vt("validate_tab_next_res -> " + "~n Type: ~p" + "~n NValue: ~p", [Type, NValue]), + NewVb = OrgVb#varbind{oid = NextCompleteOid, + variabletype = Type, value = NValue}, + validate_tab_next_res(Values, TableOids, Mfa, + [NewVb | Res], TabOid, TabNextOid, I) + end; + Error -> + ?LIB:user_err("Invalid oid ~w from ~w (get_next). Using genErr => ~p", + [NextOid, Mfa, Error]), + {genErr, OrgIndex} + end; +validate_tab_next_res([endOfTable | Values], + [{_ColNo, OrgVb, _Index} | TableOids], + Mfa, Res, TabOid, TabNextOid, I) -> + ?vt("validate_tab_next_res(endOfTable) -> entry with: " + "~n Values: ~p" + "~n OrgVb: ~p" + "~n TableOids: ~p" + "~n Mfa: ~p" + "~n Res: ~p" + "~n TabOid: ~p" + "~n TabNextOid: ~p" + "~n I: ~p", + [Values, OrgVb, TableOids, Mfa, Res, TabOid, TabNextOid, I]), + NewVb = OrgVb#varbind{value = {endOfTable, TabNextOid}}, + validate_tab_next_res(Values, TableOids, Mfa, [NewVb | Res], + TabOid, TabNextOid, I); +validate_tab_next_res([], [], _Mfa, Res, _TabOid, _TabNextOid, _I) -> + Res; +validate_tab_next_res([], [{_Col, _OrgVb, Index}|_], Mfa, _Res, _, _, _I) -> + ?LIB:user_err("Too few values returned from ~w (get_next)", [Mfa]), + {genErr, Index}; +validate_tab_next_res({genErr, ColNumber}, OrgCols, + Mfa, _Res, _TabOid, _TabNextOid, _I) -> + OrgIndex = snmpa_svbl:col_to_orgindex(ColNumber, OrgCols), + ?AGENT:validate_err(table_next, {genErr, OrgIndex}, Mfa); +validate_tab_next_res({error, Reason}, [{_ColNo, OrgVb, _Index} | _TableOids], + Mfa, _Res, _TabOid, _TabNextOid, _I) -> + #varbind{org_index = OrgIndex} = OrgVb, + ?LIB:user_err("Erroneous return value ~w from ~w (get_next)", + [Reason, Mfa]), + {genErr, OrgIndex}; +validate_tab_next_res(Error, [{_ColNo, OrgVb, _Index} | _TableOids], + Mfa, _Res, _TabOid, _TabNextOid, _I) -> + #varbind{org_index = OrgIndex} = OrgVb, + ?LIB:user_err("Invalid return value ~w from ~w (get_next)", + [Error, Mfa]), + {genErr, OrgIndex}; +validate_tab_next_res(TooMany, [], Mfa, _Res, _, _, I) -> + ?LIB:user_err("Too many values ~w returned from ~w (get_next)", + [TooMany, Mfa]), + {genErr, I}. + +%%----------------------------------------------------------------- +%% Func: get_next_sa/4 +%% Purpose: Loop the list of varbinds for the subagent. +%% Call subagent_get_next to retreive +%% the next varbinds. +%% Returns: {ok, ListOfNewVbs, ListOfEndOfMibViewsVbs} | +%% {ErrorStatus, ErrorIndex} +%%----------------------------------------------------------------- +get_next_sa(SAPid, SAOid, SAVbs, MibView) -> + case catch ?AGENT:subagent_get_next(SAPid, MibView, SAVbs) of + {noError, 0, NewVbs} -> + NewerVbs = transform_sa_next_result(NewVbs,SAOid,next_oid(SAOid)), + {ResVBs, EndOfVBs} = ?LIB:split_vbs(NewerVbs), + {ok, ResVBs, EndOfVBs}; + {ErrorStatus, ErrorIndex, _} -> + {ErrorStatus, ErrorIndex}; + {'EXIT', Reason} -> + ?LIB:user_err("Lost contact with subagent (next) ~w. Using genErr", + [Reason]), + {genErr, 0} + end. + +%%----------------------------------------------------------------- +%% Check for wrong prefix returned or endOfMibView, and convert +%% into {endOfMibView, SANextOid}. +%%----------------------------------------------------------------- +transform_sa_next_result([Vb | Vbs], SAOid, SANextOid) + when Vb#varbind.value =:= endOfMibView -> + [Vb#varbind{value = {endOfMibView, SANextOid}} | + transform_sa_next_result(Vbs, SAOid, SANextOid)]; +transform_sa_next_result([Vb | Vbs], SAOid, SANextOid) -> + case lists:prefix(SAOid, Vb#varbind.oid) of + true -> + [Vb | transform_sa_next_result(Vbs, SAOid, SANextOid)]; + _ -> + [Vb#varbind{oid = SANextOid, value = {endOfMibView, SANextOid}} | + transform_sa_next_result(Vbs, SAOid, SANextOid)] + end; +transform_sa_next_result([], _SAOid, _SANextOid) -> + []. + + +next_oid(Oid) -> + case lists:reverse(Oid) of + [H | T] -> lists:reverse([H+1 | T]); + [] -> [] + end. + + + +%%%----------------------------------------------------------------- +%%% 5. GET-BULK REQUEST +%%% +%%% In order to prevent excesses in reply sizes there are two +%%% preventive methods in place. One is to check that the encode +%%% size does not exceed Max PDU size (this is mentioned in the +%%% standard). The other is a simple VBs limit. That is, the +%%% resulting response cannot contain more then this number of VBs. +%%%----------------------------------------------------------------- + +do_get_bulk(MibView, NonRepeaters, MaxRepetitions, + PduMS, Varbinds, GbMaxVBs, _Extra) -> + ?vtrace("do_get_bulk -> entry with" + "~n MibView: ~p" + "~n NonRepeaters: ~p" + "~n MaxRepetitions: ~p" + "~n PduMS: ~p" + "~n Varbinds: ~p" + "~n GbMaxVBs: ~p", + [MibView, NonRepeaters, MaxRepetitions, PduMS, Varbinds, GbMaxVBs]), + {NonRepVbs, RestVbs} = ?LIB:split_vbs_gb(NonRepeaters, Varbinds), + ?vt("do_get_bulk -> split: " + "~n NonRepVbs: ~p" + "~n RestVbs: ~p", [NonRepVbs, RestVbs]), + case do_get_next2(MibView, NonRepVbs, GbMaxVBs) of + {noError, 0, UResNonRepVbs} -> + ?vt("do_get_bulk -> next noError: " + "~n UResNonRepVbs: ~p", [UResNonRepVbs]), + ResNonRepVbs = lists:keysort(#varbind.org_index, UResNonRepVbs), + %% Decode the first varbinds, produce a reversed list of + %% listOfBytes. + case (catch enc_vbs(PduMS - ?empty_pdu_size, ResNonRepVbs)) of + {error, Idx, Reason} -> + ?LIB:user_err("failed encoding varbind ~w:~n~p", [Idx, Reason]), + {genErr, Idx, []}; + {SizeLeft, Res} when is_integer(SizeLeft) and is_list(Res) -> + ?vtrace("do_get_bulk -> encoded: " + "~n SizeLeft: ~p" + "~n Res: ~w", [SizeLeft, Res]), + case (catch do_get_rep(SizeLeft, MibView, MaxRepetitions, + RestVbs, Res, + length(UResNonRepVbs), GbMaxVBs)) of + {error, Idx, Reason} -> + ?LIB:user_err("failed encoding varbind ~w:~n~p", + [Idx, Reason]), + {genErr, Idx, []}; + Res when is_list(Res) -> + ?vtrace("do get bulk -> Res: " + "~n ~w", [Res]), + {noError, 0, conv_res(Res)}; + {noError, 0, Data} = OK -> + ?vtrace("do get bulk -> OK: " + "~n length(Data): ~w", [length(Data)]), + OK; + Else -> + ?vtrace("do get bulk -> Else: " + "~n ~w", [Else]), + Else + end; + Res when is_list(Res) -> + {noError, 0, conv_res(Res)} + end; + + {ErrorStatus, Index, _} -> + ?vdebug("do get bulk: " + "~n ErrorStatus: ~p" + "~n Index: ~p",[ErrorStatus, Index]), + {ErrorStatus, Index, []} + end. + +enc_vbs(SizeLeft, Vbs) -> + ?vt("enc_vbs -> entry with" + "~n SizeLeft: ~w", [SizeLeft]), + Fun = fun(Vb, {Sz, Res}) when Sz > 0 -> + ?vt("enc_vbs -> (fun) entry with" + "~n Vb: ~p" + "~n Sz: ~p" + "~n Res: ~w", [Vb, Sz, Res]), + case (catch snmp_pdus:enc_varbind(Vb)) of + {'EXIT', Reason} -> + ?vtrace("enc_vbs -> encode failed: " + "~n Reason: ~p", [Reason]), + throw({error, Vb#varbind.org_index, Reason}); + X -> + ?vt("enc_vbs -> X: ~w", [X]), + Lx = length(X), + ?vt("enc_vbs -> Lx: ~w", [Lx]), + if + Lx < Sz -> + {Sz - length(X), [X | Res]}; + true -> + throw(Res) + end + end; + (_Vb, {_Sz, [_H | T]}) -> + ?vt("enc_vbs -> (fun) entry with" + "~n T: ~p", [T]), + throw(T); + (_Vb, {_Sz, []}) -> + ?vt("enc_vbs -> (fun) entry", []), + throw([]) + end, + lists:foldl(Fun, {SizeLeft, []}, Vbs). + +do_get_rep(Sz, MibView, MaxRepetitions, Varbinds, Res, GbNumVBs, GbMaxVBs) + when MaxRepetitions >= 0 -> + do_get_rep(Sz, MibView, 0, MaxRepetitions, Varbinds, Res, + GbNumVBs, GbMaxVBs); +do_get_rep(Sz, MibView, _MaxRepetitions, Varbinds, Res, GbNumVBs, GbMaxVBs) -> + do_get_rep(Sz, MibView, 0, 0, Varbinds, Res, GbNumVBs, GbMaxVBs). + +conv_res(ResVarbinds) -> + conv_res(ResVarbinds, []). +conv_res([VbListOfBytes | T], Bytes) -> + conv_res(T, VbListOfBytes ++ Bytes); +conv_res([], Bytes) -> + Bytes. + +%% The only other value, then a positive integer, is infinity. +do_get_rep(_Sz, _MibView, Count, Max, _, _Res, GbNumVBs, GbMaxVBs) + when (is_integer(GbMaxVBs) andalso (GbNumVBs > GbMaxVBs)) -> + ?vinfo("Max Get-BULK VBs limit (~w) exceeded (~w) when:" + "~n Count: ~p" + "~n Max: ~p", [GbMaxVBs, GbNumVBs, Count, Max]), + {tooBig, 0, []}; +do_get_rep(_Sz, _MibView, Max, Max, _, Res, _GbNumVBs, _GbMaxVBs) -> + ?vt("do_get_rep -> done when: " + "~n Res: ~p", [Res]), + {noError, 0, conv_res(Res)}; +do_get_rep(Sz, MibView, Count, Max, Varbinds, Res, GbNumVBs, GbMaxVBs) -> + ?vt("do_get_rep -> entry when: " + "~n Sz: ~p" + "~n Count: ~p" + "~n Res: ~w", [Sz, Count, Res]), + case try_get_bulk(Sz, MibView, Varbinds, GbMaxVBs) of + {noError, NextVarbinds, SizeLeft, Res2} -> + ?vt("do_get_rep -> noError: " + "~n SizeLeft: ~p" + "~n Res2: ~p", [SizeLeft, Res2]), + do_get_rep(SizeLeft, MibView, Count+1, Max, NextVarbinds, + Res2 ++ Res, + GbNumVBs + length(Varbinds), GbMaxVBs); + {endOfMibView, _NextVarbinds, _SizeLeft, Res2} -> + ?vt("do_get_rep -> endOfMibView: " + "~n Res2: ~p", [Res2]), + {noError, 0, conv_res(Res2 ++ Res)}; + {ErrorStatus, Index} -> + ?vtrace("do_get_rep -> done when error: " + "~n ErrorStatus: ~p" + "~n Index: ~p", [ErrorStatus, Index]), + {ErrorStatus, Index, []} + end. + +try_get_bulk(Sz, MibView, Varbinds, GbMaxVBs) -> + ?vt("try_get_bulk -> entry with" + "~n Sz: ~w" + "~n MibView: ~w" + "~n Varbinds: ~w", [Sz, MibView, Varbinds]), + case do_get_next2(MibView, Varbinds, GbMaxVBs) of + {noError, 0, UNextVarbinds} -> + ?vt("try_get_bulk -> noError: " + "~n UNextVarbinds: ~p", [UNextVarbinds]), + NextVarbinds = ?LIB:org_index_sort_vbs(UNextVarbinds), + case (catch enc_vbs(Sz, NextVarbinds)) of + {error, Idx, Reason} -> + ?LIB:user_err("failed encoding varbind ~w:~n~p", [Idx, Reason]), + ?vtrace("try_get_bulk -> encode error: " + "~n Idx: ~p" + "~n Reason: ~p", [Idx, Reason]), + {genErr, Idx}; + {SizeLeft, Res} when is_integer(SizeLeft) andalso + is_list(Res) -> + ?vt("try get bulk -> encode ok: " + "~n SizeLeft: ~w" + "~n Res: ~w", [SizeLeft, Res]), + {check_end_of_mibview(NextVarbinds), + NextVarbinds, SizeLeft, Res}; + Res when is_list(Res) -> + ?vt("try get bulk -> Res: " + "~n ~w", [Res]), + {endOfMibView, [], 0, Res} + end; + {ErrorStatus, Index, _} -> + ?vt("try_get_bulk -> error: " + "~n ErrorStatus: ~p" + "~n Index: ~p", [ErrorStatus, Index]), + {ErrorStatus, Index} + end. + +%% If all variables in this pass are endOfMibView, +%% there is no reason to continue. +check_end_of_mibview([#varbind{value = endOfMibView} | T]) -> + check_end_of_mibview(T); +check_end_of_mibview([]) -> endOfMibView; +check_end_of_mibview(_) -> noError. + + + diff --git a/lib/snmp/src/agent/snmpa_get_lib.erl b/lib/snmp/src/agent/snmpa_get_lib.erl new file mode 100644 index 0000000000..eaf7fe2641 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_get_lib.erl @@ -0,0 +1,254 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +%% Note that most of these functions *assume* that they are executed +%% by the agent. If they are not they may note work as they require +%% some properties to be set in the process dictionary! +%% + +-module(snmpa_get_lib). + +-export([ + split_vbs/1, split_vbs/3, + split_vbs_view/2, + split_vbs_gb/2, + + agent_sort_vbs/1, + oid_sort_vbs/1, org_index_sort_vbs/1, + + sa_split/1, + + delete_prefixes/2, + + dbg_apply/3, + + user_err/2 + ]). + +-define(VMODULE,"GET-LIB"). +-include("snmpa_internal.hrl"). +-include("snmp_types.hrl"). +-include("snmp_debug.hrl"). +-include("snmp_verbosity.hrl"). + + + + +%%----------------------------------------------------------------- +%% split_vbs/1,3 +%% +%% Splits the list of varbinds (basically) into two lists. One +%% of 'end of'-varbinds (mib view and tables) and then the rest +%% of the varbinds. +%%----------------------------------------------------------------- + +-spec split_vbs(VBs :: [snmp:varbind()]) -> + {ResVBs :: [snmp:varbind()], + EndOfVBs :: [snmp:varbind()]}. + +split_vbs(VBs) -> + split_vbs(VBs, [], []). + +-spec split_vbs(VBs :: [snmp:varbind()], + Res :: [snmp:varbind()], + EndOfs :: [snmp:varbind()]) -> + {ResVBs :: [snmp:varbind()], + EndOfVBs :: [snmp:varbind()]}. + +split_vbs([], ResVBs, EndOfVBs) -> + {ResVBs, EndOfVBs}; +split_vbs([VB | VBs], Res, EndOfs) -> + case VB#varbind.value of + {endOfMibView, _} -> split_vbs(VBs, Res, [VB | EndOfs]); + {endOfTable, _} -> split_vbs(VBs, Res, [VB | EndOfs]); + _ -> split_vbs(VBs, [VB | Res], EndOfs) + end. + + + +%%----------------------------------------------------------------- +%% split_vbs_view/2 +%% +%% Splits a list of varbinds into two lists based on the provided +%% MibView. One list of varbinds inside the MibView and one of +%% varbinds outside the MibView. +%%----------------------------------------------------------------- + +-spec split_vbs_view(VBs :: [snmp:varbind()], + MibView :: snmp_view_based_acm_mib:mibview()) -> + {OutSideView :: [snmp:varbind()], + InSideView :: [snmp:varbind()]}. + +split_vbs_view(VBs, MibView) -> + ?vtrace("split the varbinds view", []), + split_vbs_view(VBs, MibView, [], []). + +split_vbs_view([], _MibView, Out, In) -> + {Out, In}; +split_vbs_view([VB | VBs], MibView, Out, In) -> + case snmpa_acm:validate_mib_view(VB#varbind.oid, MibView) of + true -> + split_vbs_view(VBs, MibView, Out, [VB | In]); + false -> + VB2 = VB#varbind{value = noSuchObject}, + split_vbs_view(VBs, MibView, [VB2 | Out], In) + end. + + + +%%----------------------------------------------------------------- +%% split_vbs_gb/2 +%% +%% Performs a get-bulk split of the varbinds +%%----------------------------------------------------------------- + +-spec split_vbs_gb(NonRepeaters :: integer(), + VBs :: [snmp:varbind()]) -> + {NonRepVBs :: [snmp:varbind()], + RestVBs :: [snmp:varbind()]}. + +split_vbs_gb(N, VBs) -> + split_vbs_gb(N, VBs, []). + +split_vbs_gb(N, Varbinds, Res) when N =< 0 -> + {Res, Varbinds}; +split_vbs_gb(N, [H | T], Res) -> + split_vbs_gb(N-1, T, [H | Res]); +split_vbs_gb(_N, [], Res) -> + {Res, []}. + + + +%%----------------------------------------------------------------- +%% agent_sort_vbs/1 +%% +%% Sorts the varbinds into two categories. The first is varbinds +%% belonging to "our" agent and the other is varbinds for +%% subagents. +%%----------------------------------------------------------------- + +-spec agent_sort_vbs(VBs :: [snmp:varbind()]) -> + {AgentVBs :: [snmp:varbind()], + SubAgentVBs :: [snmp:varbind()]}. + +agent_sort_vbs(VBs) -> + snmpa_svbl:sort_varbindlist(get(mibserver), VBs). + + +%%----------------------------------------------------------------- +%% oid_sort_vbs/1 +%% +%% Sorts the varbinds based on their oid. +%%----------------------------------------------------------------- + +-spec oid_sort_vbs(VBs :: [snmp:varbind()]) -> SortedVBs :: [snmp:varbind()]. + +oid_sort_vbs(VBs) -> + lists:keysort(#varbind.oid, VBs). + + +%%----------------------------------------------------------------- +%% org_index_sort_vbs/1 +%% +%% Sorts the varbinds based on their org_index. +%%----------------------------------------------------------------- + +-spec org_index_sort_vbs(VBs :: [snmp:varbind()]) -> SortedVBs :: [snmp:varbind()]. + +org_index_sort_vbs(Vbs) -> + lists:keysort(#varbind.org_index, Vbs). + + + +%%----------------------------------------------------------------- +%% sa_split/1 +%% +%% Splits a list of {oid(), varbind()} into two lists of oid() +%% and varbind. The resulting lists are reversed! +%%----------------------------------------------------------------- + +-spec sa_split(SAVBs :: [{SAOid :: snmp:oid(), snmp:varbind()}]) -> + {Oids :: [snmp:oid()], VBs :: [snmp:varbind()]}. + +sa_split(SAVBs) -> + snmpa_svbl:sa_split(SAVBs). + + + +%%----------------------------------------------------------------- +%% delete_prefixes/2 +%% +%% Takes an Oid prefix and a list of ivarbinds and produces a list +%% of {ShortOid, ASN1Type}. The ShortOid is basically the oid with +%% the OidPrefix removed. +%%----------------------------------------------------------------- + +-spec delete_prefixes(OidPrefix :: snmp:oid(), + VBs :: [snmp:ivarbind()]) -> + [{ShortOid :: snmp:oid(), + ASN1Type :: snmp:asn1_type()}]. + +delete_prefixes(OidPrefix, IVBs) -> + [{snmp_misc:diff(Oid, OidPrefix), ME#me.asn1_type} || + #ivarbind{varbind = #varbind{oid = Oid}, mibentry = ME} <- IVBs]. + + + +%%----------------------------------------------------------------- +%% dbg_apply/3 +%% +%% Call instrumentation functions, but allow for debug printing +%% of useful debug info. +%%----------------------------------------------------------------- + +-spec dbg_apply(M :: atom(), F :: atom(), A :: list()) -> + any(). + +dbg_apply(M, F, A) -> + case get(verbosity) of + silence -> + apply(M,F,A); + _ -> + ?vlog("~n apply: ~w, ~w, ~p~n", [M,F,A]), + Res = (catch apply(M,F,A)), + case Res of + {'EXIT', Reason} -> + ?vinfo("Call to: " + "~n Module: ~p" + "~n Function: ~p" + "~n Args: ~p" + "~n" + "~nresulted in an exit" + "~n" + "~n ~p~n", [M, F, A, Reason]); + _ -> + ?vlog("~n returned: ~p~n", [Res]) + end, + Res + end. + + +%% --------------------------------------------------------------------- + +user_err(F, A) -> + snmpa_error:user_err(F, A). + + diff --git a/lib/snmp/src/agent/snmpa_get_mechanism.erl b/lib/snmp/src/agent/snmpa_get_mechanism.erl new file mode 100644 index 0000000000..744a6529e1 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_get_mechanism.erl @@ -0,0 +1,79 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(snmpa_get_mechanism). + +%% +%% This module defines the behaviour for the undocumented (hidden) +%% get-mechanism feature. This allows for implementing your own +%% handling of get, get-next and get-bulk requests. +%% Probably only useful for special cases (e.g. optimization). +%% + + + +%% ----------- do_get/2,3 ----------------------------------------------------- + +%% Purpose: Handles all VBs in a request that is inside the +%% mibview (local). + +-callback do_get(UnsortedVBs :: [snmp:varbind()], + IsNotification :: boolean(), + Extra :: term()) -> + {noError, 0, ResVBs :: [snmp:varbind()]} | + {ErrStatus :: snmp:error_status(), ErrIndex :: snmp:error_index(), []}. + + +%% Purpose: Handles "get-requests". + +-callback do_get(MibView :: snmp_view_based_acm_mib:mibview(), + UnsortedVBs :: [snmp:varbind()], + IsNotification :: boolean(), + Extra :: term()) -> + {noError, 0, ResVBs :: [snmp:varbind()]} | + {ErrStatus :: snmp:error_status(), ErrIndex :: snmp:error_index(), []}. + + + + +%% ----------- do_get_next/2 ------------------------------------------------ + +%% Purpose: Handles "get-next-requests". + +-callback do_get_next(MibView :: snmp_view_based_acm_mib:mibview(), + UnsortedVBs :: [snmp:varbind()], + Extra :: term()) -> + {noError, 0, ResVBs :: [snmp:varbind()]} | + {ErrStatus :: snmp:error_status(), ErrIndex :: snmp:error_index(), []}. + + + + +%% ----------- do_get_bulk/6 ------------------------------------------------ + +-callback do_get_bulk(MibView :: snmp_view_based_acm_mib:mibview(), + NonRepeaters :: non_neg_integer(), + MaxRepetitions :: non_neg_integer(), + PduMS :: pos_integer(), + VBs :: [snmp:varbind()], + MaxVBs :: pos_integer(), + Extra :: term()) -> + {noError, 0, ResVBs :: [snmp:varbind()]} | + {ErrStatus :: snmp:error_status(), ErrIndex :: snmp:error_index(), []}. diff --git a/lib/snmp/src/agent/snmpa_set_lib.erl b/lib/snmp/src/agent/snmpa_set_lib.erl index 57507a36e8..3dcf49cbe6 100644 --- a/lib/snmp/src/agent/snmpa_set_lib.erl +++ b/lib/snmp/src/agent/snmpa_set_lib.erl @@ -390,7 +390,7 @@ dbg_apply(M,F,A) -> {'EXIT', {function_clause, [{M, F, A} | _]}} -> {'EXIT', {hook_function_clause, {M, F, A}}}; - % XYZ: Older format for compatibility + %% XYZ: Older format for compatibility {'EXIT', {undef, {M, F, A}}} -> {'EXIT', {hook_undef, {M, F, A}}}; {'EXIT', {function_clause, {M, F, A}}} -> diff --git a/lib/snmp/src/agent/snmpa_supervisor.erl b/lib/snmp/src/agent/snmpa_supervisor.erl index cdb5ca840d..2cb0556001 100644 --- a/lib/snmp/src/agent/snmpa_supervisor.erl +++ b/lib/snmp/src/agent/snmpa_supervisor.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2016. All Rights Reserved. +%% Copyright Ericsson AB 1996-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -193,36 +193,36 @@ init([AgentType, Opts]) -> ?vdebug("agent restart type: ~w", [Restart]), %% -- Agent type -- - ets:insert(snmp_agent_table, {agent_type, AgentType}), + store(agent_type, AgentType), %% -- Prio -- Prio = get_opt(priority, Opts, normal), ?vdebug("[agent table] store priority: ~p",[Prio]), - ets:insert(snmp_agent_table, {priority, Prio}), + store(priority, Prio), %% -- Versions -- Vsns = get_opt(versions, Opts, [v1,v2,v3]), ?vdebug("[agent table] store versions: ~p",[Vsns]), - ets:insert(snmp_agent_table, {versions, Vsns}), + store(versions, Vsns), %% -- Max number of VBs in a Get-BULK response -- GbMaxVBs = get_gb_max_vbs(Opts), ?vdebug("[agent table] Get-BULK max VBs: ~p", [GbMaxVBs]), - ets:insert(snmp_agent_table, {gb_max_vbs, GbMaxVBs}), + store(gb_max_vbs, GbMaxVBs), %% -- DB-directory -- DbDir = get_opt(db_dir, Opts), ?vdebug("[agent table] store db_dir: ~n ~p",[DbDir]), - ets:insert(snmp_agent_table, {db_dir, filename:join([DbDir])}), + store(db_dir, filename:join([DbDir])), DbInitError = get_opt(db_init_error, Opts, terminate), ?vdebug("[agent table] store db_init_error: ~n ~p",[DbInitError]), - ets:insert(snmp_agent_table, {db_init_error, DbInitError}), + store(db_init_error, DbInitError), %% -- Error report module -- ErrorReportMod = get_opt(error_report_mod, Opts, snmpa_error_logger), ?vdebug("[agent table] store error report module: ~w",[ErrorReportMod]), - ets:insert(snmp_agent_table, {error_report_mod, ErrorReportMod}), + store(error_report_mod, ErrorReportMod), %% -- mib storage -- %% MibStorage has only one mandatory part: module @@ -320,31 +320,31 @@ init([AgentType, Opts]) -> end, ?vdebug("[agent table] store mib storage: ~w", [MibStorage]), - ets:insert(snmp_agent_table, {mib_storage, MibStorage}), + store(mib_storage, MibStorage), %% -- Agent mib storage -- AgentMibStorage = get_opt(agent_mib_storage, Opts, persistent), %% ?vdebug("[agent table] store agent mib storage: ~w",[AgentMibStorage]), - ets:insert(snmp_agent_table, {agent_mib_storage, AgentMibStorage}), + store(agent_mib_storage, AgentMibStorage), %% -- System start time -- ?vdebug("[agent table] store system start time",[]), - ets:insert(snmp_agent_table, {system_start_time, snmp_misc:now(cs)}), + store(system_start_time, snmp_misc:now(cs)), %% -- Symbolic store options -- SsOpts = get_opt(symbolic_store, Opts, []), ?vdebug("[agent table] store symbolic store options: ~w",[SsOpts]), - ets:insert(snmp_agent_table, {symbolic_store, SsOpts}), + store(symbolic_store, SsOpts), %% -- Local DB options -- LdbOpts = get_opt(local_db, Opts, []), ?vdebug("[agent table] store local db options: ~w",[LdbOpts]), - ets:insert(snmp_agent_table, {local_db, LdbOpts}), + store(local_db, LdbOpts), %% -- Target cache options -- TargetCacheOpts = get_opt(target_cache, Opts, []), ?vdebug("[agent table] store target cache options: ~w",[TargetCacheOpts]), - ets:insert(snmp_agent_table, {target_cache, TargetCacheOpts}), + store(target_cache, TargetCacheOpts), %% -- Specs -- SupFlags = {one_for_all, 0, 3600}, @@ -377,7 +377,7 @@ init([AgentType, Opts]) -> %% -- Config -- ConfOpts = get_opt(config, Opts, []), ?vdebug("[agent table] store config options: ~p", [ConfOpts]), - ets:insert(snmp_agent_table, {config, ConfOpts}), + store(config, ConfOpts), ConfigArgs = [Vsns, ConfOpts], ConfigSpec = @@ -390,43 +390,46 @@ init([AgentType, Opts]) -> %% -- Discovery processing -- DiscoOpts = get_opt(discovery, Opts, []), ?vdebug("[agent table] store discovery options: ~p", [DiscoOpts]), - ets:insert(snmp_agent_table, {discovery, DiscoOpts}), + store(discovery, DiscoOpts), %% -- Mibs -- Mibs = get_mibs(get_opt(mibs, Opts, []), Vsns), ?vdebug("[agent table] store mibs: ~n ~p",[Mibs]), - ets:insert(snmp_agent_table, {mibs, Mibs}), + store(mibs, Mibs), Ref = make_ref(), + %% -- Get module -- + GetModule = get_opt(get_mechanism, Opts, snmpa_get), + ?vdebug("[agent table] store get-module: ~p", [GetModule]), + store(get_mechanism, GetModule), + %% -- Set module -- SetModule = get_opt(set_mechanism, Opts, snmpa_set), ?vdebug("[agent table] store set-module: ~p",[SetModule]), - ets:insert(snmp_agent_table, {set_mechanism, ConfOpts}), + store(set_mechanism, SetModule), %% -- Authentication service -- AuthModule = get_opt(authentication_service, Opts, snmpa_acm), ?vdebug("[agent table] store authentication service: ~w", [AuthModule]), - ets:insert(snmp_agent_table, - {authentication_service, AuthModule}), + store(authentication_service, AuthModule), %% -- Multi-threaded -- MultiT = get_opt(multi_threaded, Opts, false), - ?vdebug("[agent table] store multi-threaded: ~p",[MultiT]), - ets:insert(snmp_agent_table, {multi_threaded, MultiT}), + ?vdebug("[agent table] store multi-threaded: ~p", [MultiT]), + store(multi_threaded, MultiT), %% -- Audit trail log -- case get_opt(audit_trail_log, Opts, not_found) of not_found -> - ?vdebug("[agent table] no audit trail log",[]), + ?vdebug("[agent table] no audit trail log", []), ok; AtlOpts -> ?vdebug("[agent table] " "store audit trail log options: ~p", [AtlOpts]), - ets:insert(snmp_agent_table, - {audit_trail_log, AtlOpts}), + store(audit_trail_log, AtlOpts), ok end, @@ -434,24 +437,25 @@ init([AgentType, Opts]) -> MibsOpts = get_opt(mib_server, Opts, []), ?vdebug("[agent table] store mib-server options: " "~n ~p", [MibsOpts]), - ets:insert(snmp_agent_table, {mib_server, MibsOpts}), + store(mib_server, MibsOpts), %% -- Network interface -- NiOpts = get_opt(net_if, Opts, []), ?vdebug("[agent table] store net-if options: " "~n ~p", [NiOpts]), - ets:insert(snmp_agent_table, {net_if, NiOpts}), + store(net_if, NiOpts), %% -- Note store -- NsOpts = get_opt(note_store, Opts, []), ?vdebug("[agent table] store note-store options: " "~n ~p",[NsOpts]), - ets:insert(snmp_agent_table, {note_store, NsOpts}), + store(note_store, NsOpts), AgentOpts = [{verbosity, AgentVerb}, {mibs, Mibs}, {mib_storage, MibStorage}, + {get_mechanism, GetModule}, {set_mechanism, SetModule}, {authentication_service, AuthModule}, {multi_threaded, MultiT}, @@ -480,6 +484,10 @@ init([AgentType, Opts]) -> {ok, {SupFlags, [MiscSupSpec, SymStoreSpec, LocalDbSpec, TargetCacheSpec | Rest]}}. + +store(Key, Value) -> + ets:insert(snmp_agent_table, {Key, Value}). + get_mibs(Mibs, Vsns) -> MibDir = filename:join(code:priv_dir(snmp), "mibs"), StdMib = diff --git a/lib/snmp/src/agent/snmpa_trap.erl b/lib/snmp/src/agent/snmpa_trap.erl index e75016f7ec..31805c3bee 100644 --- a/lib/snmp/src/agent/snmpa_trap.erl +++ b/lib/snmp/src/agent/snmpa_trap.erl @@ -917,7 +917,7 @@ do_send_v2_trap(Recvs, Vbs, ExtraInfo, NetIf) -> TrapPdu = make_v2_notif_pdu(Vbs, 'snmpv2-trap'), AddrCommunities = mk_addr_communities(Recvs), lists:foreach(fun({Community, Addrs}) -> - ?vtrace("~n send v2 trap to ~p",[Addrs]), + ?vtrace("send v2 trap to ~p",[Addrs]), NetIf ! {send_pdu, 'version-2', TrapPdu, {community, Community}, Addrs, ExtraInfo} end, AddrCommunities), diff --git a/lib/snmp/src/app/snmp.app.src b/lib/snmp/src/app/snmp.app.src index d4bf0de61a..178309b488 100644 --- a/lib/snmp/src/app/snmp.app.src +++ b/lib/snmp/src/app/snmp.app.src @@ -49,6 +49,9 @@ snmpa_error_io, snmpa_error_logger, snmpa_error_report, + snmpa_get, + snmpa_get_lib, + snmpa_get_mechanism, snmpa_local_db, snmpa_mib, snmpa_mib_data, diff --git a/lib/snmp/src/app/snmp.config b/lib/snmp/src/app/snmp.config index b66ef5d7df..f35a636157 100644 --- a/lib/snmp/src/app/snmp.config +++ b/lib/snmp/src/app/snmp.config @@ -8,6 +8,7 @@ %% {agent_verbosity, verbosity()} | %% {versions, versions()} | %% {priority, atom()} | +%% {get_mechanism, module()} | %% {set_mechanism, module()} | %% {authentication_service, module()} | %% {multi_threaded, bool()} | diff --git a/lib/snmp/src/app/snmp.erl b/lib/snmp/src/app/snmp.erl index 8a736f688b..216452afdd 100644 --- a/lib/snmp/src/app/snmp.erl +++ b/lib/snmp/src/app/snmp.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2017. All Rights Reserved. +%% Copyright Ericsson AB 1996-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -116,7 +116,10 @@ pdu/0, trappdu/0, mib/0, - mib_name/0, + mib_name/0, + + error_status/0, + error_index/0, void/0 ]). @@ -208,6 +211,23 @@ -type pdu() :: #pdu{}. -type trappdu() :: #trappdu{}. +%% We should really specify all of these, but they are so numerous... +%% See the validate_err/1 function in the snmpa_agent. +%% Here are a number of them: +%% badValue | +%% commitFailed | +%% genErr | +%% inconsistentName | inconsistentValue | +%% noAccess | noCreation | +%% noSuchInstance | noSuchName | noSuchObject | +%% notWritable | +%% resourceUnavailable | +%% undoFailed | +%% wrongValue + +-type error_status() :: atom(). +-type error_index() :: pos_integer(). + -type void() :: term(). diff --git a/lib/snmp/test/modules.mk b/lib/snmp/test/modules.mk index 0f54e67c65..8b6547f9a9 100644 --- a/lib/snmp/test/modules.mk +++ b/lib/snmp/test/modules.mk @@ -31,6 +31,7 @@ SUITE_MODULES = \ snmp_agent_mibs_test \ snmp_agent_nfilter_test \ snmp_agent_test \ + snmp_agent_test_get \ snmp_agent_conf_test \ snmp_agent_test_lib \ snmp_manager_config_test \ diff --git a/lib/snmp/test/snmp_agent_test_get.erl b/lib/snmp/test/snmp_agent_test_get.erl new file mode 100644 index 0000000000..517c71507a --- /dev/null +++ b/lib/snmp/test/snmp_agent_test_get.erl @@ -0,0 +1,58 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(snmp_agent_test_get). + +-behaviour(snmpa_get_mechanism). + + +%%%----------------------------------------------------------------- +%%% snmpa_get_mechanism exports +%%%----------------------------------------------------------------- + +-export([ + do_get/3, do_get/4, + do_get_next/3, + do_get_bulk/7 + ]). + + + +do_get(UnsortedVarbinds, IsNotification, Extra) -> + snmpa_get:do_get(UnsortedVarbinds, IsNotification, Extra). + + + +do_get(MibView, UnsortedVarbinds, IsNotification, Extra) -> + snmpa_get:do_get(MibView, UnsortedVarbinds, IsNotification, Extra). + + + +do_get_next(MibView, UnsortedVBs, Extra) -> + snmpa_get:do_get_next(MibView, UnsortedVBs, Extra). + + + + +do_get_bulk(MibView, NonRepeaters, MaxRepetitions, + PduMS, Varbinds, GbMaxVBs, Extra) -> + snmpa_get:do_get_bulk(MibView, NonRepeaters, MaxRepetitions, + PduMS, Varbinds, GbMaxVBs, + Extra). diff --git a/lib/snmp/test/snmp_agent_test_lib.erl b/lib/snmp/test/snmp_agent_test_lib.erl index 66211d7105..c19c88528f 100644 --- a/lib/snmp/test/snmp_agent_test_lib.erl +++ b/lib/snmp/test/snmp_agent_test_lib.erl @@ -445,6 +445,7 @@ start_agent(Config, Vsns, Opts) -> [{versions, Vsns}, {agent_type, master}, {agent_verbosity, trace}, + {get_mechanism, snmp_agent_test_get}, {db_dir, AgentDbDir}, {audit_trail_log, [{type, read_write}, {dir, AgentLogDir}, diff --git a/lib/snmp/test/snmp_manager_test.erl b/lib/snmp/test/snmp_manager_test.erl index 6ced55f0cc..bb9b05b89f 100644 --- a/lib/snmp/test/snmp_manager_test.erl +++ b/lib/snmp/test/snmp_manager_test.erl @@ -6179,7 +6179,12 @@ start_agent(Node, Vsns, Conf0, _Opts) -> {mib_server, [{verbosity, MSV}]}, {note_store, [{verbosity, NSV}]}, {stymbolic_store, [{verbosity, SSV}]}, - {net_if, [{verbosity, NIV}]}, + {net_if, [{verbosity, NIV}, + %% On some linux "they" add a 127.0.1.1 or somthing + %% similar, so if we don't specify bind_to + %% we don't know which address will be selected + %% (which will cause problems for some test cases). + {options, [{bind_to, true}]}]}, {multi_threaded, true}], ?line ok = set_agent_env(Node, Env), diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml index ce01a04112..7d47674d39 100644 --- a/lib/ssh/doc/src/notes.xml +++ b/lib/ssh/doc/src/notes.xml @@ -30,6 +30,66 @@ <file>notes.xml</file> </header> +<section><title>Ssh 4.6.9.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fixed port leakage if a ssh:daemon call failed.</p> + <p> + Own Id: OTP-15397 Aux Id: ERL-801 </p> + </item> + </list> + </section> + +</section> + +<section><title>Ssh 4.6.9.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Incompatibility with newer OpenSSH fixed. Previously + versions 7.8 and later could cause Erlang SSH to exit.</p> + <p> + Own Id: OTP-15413</p> + </item> + </list> + </section> + +</section> + +<section><title>Ssh 4.6.9.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + SFTP clients reported the error reason <c>""</c> if a + non-OTP sftp server was killed during a long file + transmission.</p> + <p> + Now the signal name (for example <c>"KILL"</c>) will be + the error reason if the server's reason is empty.</p> + <p> + The documentation also lacked type information about this + class of errors.</p> + <p> + Own Id: OTP-15148 Aux Id: ERIERL-194 </p> + </item> + <item> + <p> + Fix ssh_sftp decode error for sftp protocol version 4</p> + <p> + Own Id: OTP-15149 Aux Id: ERIERL-199 </p> + </item> + </list> + </section> + +</section> + <section><title>Ssh 4.6.9</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/ssh/doc/src/ssh_sftp.xml b/lib/ssh/doc/src/ssh_sftp.xml index 129426a6d5..f367560b5f 100644 --- a/lib/ssh/doc/src/ssh_sftp.xml +++ b/lib/ssh/doc/src/ssh_sftp.xml @@ -46,9 +46,9 @@ <taglist> <tag><c>reason()</c></tag> <item> - <p>= <c>atom()</c> A description of the reason why an operation failed.</p> + <p>= <c>atom() | string() | tuple() </c>A description of the reason why an operation failed.</p> <p> - The value is formed from the sftp error codes in the protocol-level responses as defined in + The <c>atom()</c> value is formed from the sftp error codes in the protocol-level responses as defined in <url href="https://tools.ietf.org/id/draft-ietf-secsh-filexfer-13.txt">draft-ietf-secsh-filexfer-13.txt</url> section 9.1. </p> @@ -57,6 +57,10 @@ E.g. the error code <c>SSH_FX_NO_SUCH_FILE</c> will cause the <c>reason()</c> to be <c>no_such_file</c>. </p> + <p>The <c>string()</c> reason is the error information from the server in case of an exit-signal. If that information is empty, the reason is the exit signal name. + </p> + <p>The <c>tuple()</c> reason are other errors like the <c>{exit_status,integer()}</c> if the exit status is not 0. + </p> </item> <tag><c>ssh_connection_ref() =</c></tag> diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 25d537c624..a67a9a15e8 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -109,7 +109,7 @@ connect(Socket, UserOptions, Timeout) when is_port(Socket), Options -> case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of ok -> - {ok, {Host,_Port}} = inet:sockname(Socket), + {ok, {Host,_Port}} = inet:peername(Socket), Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options), ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); {error,SockError} -> @@ -231,25 +231,38 @@ daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535, try {Host1, UserOptions} = handle_daemon_args(Host0, UserOptions0), #{} = Options0 = ssh_options:handle_options(server, UserOptions), - - {{Host,Port}, ListenSocket} = - open_listen_socket(Host1, Port0, Options0), - - %% Now Host,Port is what to use for the supervisor to register its name, - %% and ListenSocket is for listening on connections. But it is still owned - %% by self()... - - finalize_start(Host, Port, ?GET_OPT(profile, Options0), - ?PUT_INTERNAL_OPT({lsocket,{ListenSocket,self()}}, Options0), - fun(Opts, Result) -> - {_, Callback, _} = ?GET_OPT(transport, Opts), - receive - {request_control, ListenSocket, ReqPid} -> - ok = Callback:controlling_process(ListenSocket, ReqPid), - ReqPid ! {its_yours,ListenSocket}, - Result - end - end) + {open_listen_socket(Host1, Port0, Options0), Options0} + of + {{{Host,Port}, ListenSocket}, Options1} -> + try + %% Now Host,Port is what to use for the supervisor to register its name, + %% and ListenSocket is for listening on connections. But it is still owned + %% by self()... + finalize_start(Host, Port, ?GET_OPT(profile, Options1), + ?PUT_INTERNAL_OPT({lsocket,{ListenSocket,self()}}, Options1), + fun(Opts, Result) -> + {_, Callback, _} = ?GET_OPT(transport, Opts), + receive + {request_control, ListenSocket, ReqPid} -> + ok = Callback:controlling_process(ListenSocket, ReqPid), + ReqPid ! {its_yours,ListenSocket}, + Result + end + end) + of + {error,Err} -> + close_listen_socket(ListenSocket, Options1), + {error,Err}; + OK -> + OK + catch + error:Error -> + close_listen_socket(ListenSocket, Options1), + error(Error); + exit:Exit -> + close_listen_socket(ListenSocket, Options1), + exit(Exit) + end catch throw:bad_fd -> {error,bad_fd}; @@ -457,6 +470,15 @@ open_listen_socket(_Host0, Port0, Options0) -> {{LHost,LPort}, LSock}. %%%---------------------------------------------------------------- +close_listen_socket(ListenSocket, Options) -> + try + {_, Callback, _} = ?GET_OPT(transport, Options), + Callback:close(ListenSocket) + catch + _C:_E -> ok + end. + +%%%---------------------------------------------------------------- finalize_start(Host, Port, Profile, Options0, F) -> try %% throws error:Error if no usable hostkey is found diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 36152bacf7..84719ebc97 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -952,6 +952,10 @@ handle_event(_, #ssh_msg_userauth_info_request{}, {userauth_keyboard_interactive %%% ######## {connected, client|server} #### +%% Skip ext_info messages in connected state (for example from OpenSSH >= 7.7) +handle_event(_, #ssh_msg_ext_info{}, {connected,_Role}, D) -> + {keep_state, D}; + handle_event(_, {#ssh_msg_kexinit{},_}, {connected,Role}, D0) -> {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(D0#data.ssh_params), D = D0#data{ssh_params = Ssh, diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 1e10f72956..3417466c4d 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -445,12 +445,6 @@ default(client) -> class => user_options }, - {pref_public_key_algs, def} => - #{default => ssh_transport:default_algorithms(public_key), - chk => fun check_pref_public_key_algs/1, - class => user_options - }, - {dh_gex_limits, def} => #{default => {1024, 6144, 8192}, % FIXME: Is this true nowadays? chk => fun({Min,I,Max}) -> @@ -516,6 +510,12 @@ default(common) -> class => user_options }, + {pref_public_key_algs, def} => + #{default => ssh_transport:default_algorithms(public_key), + chk => fun check_pref_public_key_algs/1, + class => user_options + }, + {preferred_algorithms, def} => #{default => ssh:default_algorithms(), chk => fun check_preferred_algorithms/1, diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 9e1229dc85..79586141b2 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -801,13 +801,22 @@ handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) -> %% Ignore signals according to RFC 4254 section 6.9. {ok, State}; -handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}}, +handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, Signal, Error0, _}}, State0) -> + Error = + case Error0 of + "" -> Signal; + _ -> Error0 + end, State = reply_all(State0, {error, Error}), {stop, ChannelId, State}; handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, Status}}, State0) -> - State = reply_all(State0, {error, {exit_status, Status}}), + State = + case State0 of + 0 -> State0; + _ -> reply_all(State0, {error, {exit_status, Status}}) + end, {stop, ChannelId, State}. %%-------------------------------------------------------------------- diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index a9136e5614..9e172fdb9c 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -137,9 +137,9 @@ handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) -> %% Ignore signals according to RFC 4254 section 6.9. {ok, State}; -handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}}, State) -> - Report = io_lib:format("Connection closed by peer ~n Error ~p~n", - [Error]), +handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, Signal, Error, _}}, State) -> + Report = io_lib:format("Connection closed by peer signal ~p~n Error ~p~n", + [Signal,Error]), error_logger:error_report(Report), {stop, ChannelId, State}; diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index edc927e807..6820f534cb 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -772,8 +772,7 @@ ext_info_message(#ssh{role=server, send_ext_info=true, opts = Opts} = Ssh0) -> AlgsList = lists:map(fun erlang:atom_to_list/1, - proplists:get_value(public_key, - ?GET_OPT(preferred_algorithms, Opts))), + ?GET_OPT(pref_public_key_algs, Opts)), Msg = #ssh_msg_ext_info{nr_extensions = 1, data = [{"server-sig-algs", string:join(AlgsList,",")}] }, diff --git a/lib/ssh/src/ssh_xfer.erl b/lib/ssh/src/ssh_xfer.erl index e1680c120e..7bb9c2d101 100644 --- a/lib/ssh/src/ssh_xfer.erl +++ b/lib/ssh/src/ssh_xfer.erl @@ -734,7 +734,7 @@ decode_ATTR(Vsn, <<?UINT32(Flags), Tail/binary>>) -> {Type,Tail2} = if Vsn =< 3 -> {?SSH_FILEXFER_TYPE_UNKNOWN, Tail}; - Vsn >= 5 -> + true -> <<?BYTE(T), TL/binary>> = Tail, {T, TL} end, diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index 365f25fabb..0131654dd0 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -37,6 +37,7 @@ cli/1, close/1, daemon_already_started/1, + daemon_error_closes_port/1, daemon_opt_fd/1, multi_daemon_opt_fd/1, double_close/1, @@ -108,6 +109,7 @@ all() -> {group, internal_error}, {group, rsa_host_key_is_actualy_ecdsa}, daemon_already_started, + daemon_error_closes_port, double_close, daemon_opt_fd, multi_daemon_opt_fd, @@ -797,6 +799,24 @@ daemon_already_started(Config) when is_list(Config) -> ssh:stop_daemon(Pid). %%-------------------------------------------------------------------- +%%% Test that a failed daemon start does not leave the port open +daemon_error_closes_port(Config) -> + GoodSystemDir = proplists:get_value(data_dir, Config), + Port = ssh_test_lib:inet_port(), + {error,_} = ssh_test_lib:daemon(Port, []), % No system dir + case ssh_test_lib:daemon(Port, [{system_dir, GoodSystemDir}]) of + {error,eaddrinuse} -> + {fail, "Port leakage"}; + {error,Error} -> + ct:log("Strange error: ~p",[Error]), + {fail, "Strange error"}; + {Pid, _Host, Port} -> + %% Ok + ssh:stop_daemon(Pid) + end. + + +%%-------------------------------------------------------------------- %%% check that known_hosts is updated correctly known_hosts(Config) when is_list(Config) -> SystemDir = proplists:get_value(data_dir, Config), diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk index 538490ef96..2674126672 100644 --- a/lib/ssh/vsn.mk +++ b/lib/ssh/vsn.mk @@ -1,4 +1,4 @@ #-*-makefile-*- ; force emacs to enter makefile-mode -SSH_VSN = 4.6.9 +SSH_VSN = 4.6.9.3 APP_VSN = "ssh-$(SSH_VSN)" diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index 34fe352d08..caa1110307 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -27,6 +27,90 @@ </header> <p>This document describes the changes made to the SSL application.</p> +<section><title>SSL 8.2.6.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Add engine support for RSA key exchange</p> + <p> + Own Id: OTP-15420</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 8.2.6.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Extend check for undelivered data at closing, could under + some circumstances fail to deliverd all data that was + acctualy recivied.</p> + <p> + Own Id: OTP-15412</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 8.2.6.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Correct handling of empty server SNI extension</p> + <p> + Own Id: OTP-15168</p> + </item> + <item> + <p> + Correct cipher suite handling for ECDHE_*, the incorrect + handling could cause an incorrrect suite to be selected + and most likly fail the handshake.</p> + <p> + Own Id: OTP-15203</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 8.2.6.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Improve cipher suite handling correcting ECC and TLS-1.2 + requierments. Backport of solution for ERL-641</p> + <p> + Own Id: OTP-15178</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Option keyfile defaults to certfile and should be trumped + with key. This failed for engine keys.</p> + <p> + Own Id: OTP-15193</p> + </item> + </list> + </section> + +</section> + <section><title>SSL 8.2.6</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index 6071eece13..1a415a5f76 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -174,7 +174,9 @@ handle_client_hello(Version, signature_algs = ClientHashSigns} = HelloExt}, #ssl_options{versions = Versions, - signature_algs = SupportedHashSigns} = SslOpts, + signature_algs = SupportedHashSigns, + eccs = SupportedECCs, + honor_ecc_order = ECCOrder} = SslOpts, {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _}, Renegotiation) -> case dtls_record:is_acceptable_version(Version, Versions) of @@ -182,7 +184,7 @@ handle_client_hello(Version, TLSVersion = dtls_v1:corresponding_tls_version(Version), AvailableHashSigns = ssl_handshake:available_signature_algs( ClientHashSigns, SupportedHashSigns, Cert,TLSVersion), - ECCCurve = ssl_handshake:select_curve(Curves, ssl_handshake:supported_ecc(TLSVersion)), + ECCCurve = ssl_handshake:select_curve(Curves, SupportedECCs, ECCOrder), {Type, #session{cipher_suite = CipherSuite} = Session1} = ssl_handshake:select_session(SugesstedId, CipherSuites, AvailableHashSigns, Compressions, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index fb4448e180..f5d7c3dc00 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -476,8 +476,9 @@ eccs() -> eccs_filter_supported(Curves). %%-------------------------------------------------------------------- --spec eccs(tls_record:tls_version() | tls_record:tls_atom_version()) -> - tls_v1:curves(). +-spec eccs(tls_record:tls_version() | tls_record:tls_atom_version() | + dtls_record:dtls_version() | dtls_record:dtls_atom_version()) -> + tls_v1:curves(). %% Description: returns the curves supported for a given version of %% ssl/tls. %%-------------------------------------------------------------------- @@ -486,8 +487,17 @@ eccs({3,0}) -> eccs({3,_}) -> Curves = tls_v1:ecc_curves(all), eccs_filter_supported(Curves); -eccs(AtomVersion) when is_atom(AtomVersion) -> - eccs(tls_record:protocol_version(AtomVersion)). + +eccs({254,_} = Version) -> + eccs(dtls_v1:corresponding_tls_version(Version)); +eccs(Version) when Version == 'tlsv1.2'; + Version == 'tlsv1.1'; + Version == tlsv1; + Version == sslv3 -> + eccs(tls_record:protocol_version(Version)); +eccs(Version) when Version == 'dtlsv1.2'; + Version == 'dtlsv1'-> + eccs(dtls_v1:corresponding_tls_version(dtls_record:protocol_version(Version))). eccs_filter_supported(Curves) -> CryptoCurves = crypto:ec_curves(), diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index 59cf05fd42..aa453fe3f1 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -36,10 +36,11 @@ -export([security_parameters/2, security_parameters/3, suite_definition/1, erl_suite_definition/1, cipher_init/3, decipher/6, cipher/5, decipher_aead/6, cipher_aead/6, - suite/1, suites/1, all_suites/1, crypto_support_filters/0, - ec_keyed_suites/0, anonymous_suites/1, psk_suites/1, psk_suites_anon/1, srp_suites/0, - srp_suites_anon/0, rc4_suites/1, des_suites/1, openssl_suite/1, openssl_suite_name/1, - filter/2, filter_suites/1, filter_suites/2, + suite/1, suites/1, all_suites/1, crypto_support_filters/0, + anonymous_suites/1, psk_suites/1, psk_suites_anon/1, + srp_suites/0, srp_suites_anon/0, + rc4_suites/1, des_suites/1, openssl_suite/1, openssl_suite_name/1, + filter/3, filter_suites/1, filter_suites/2, hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1, random_bytes/1, calc_mac_hash/4, is_stream_ciphersuite/1]). @@ -2016,39 +2017,25 @@ openssl_suite_name(Cipher) -> suite_definition(Cipher). %%-------------------------------------------------------------------- --spec filter(undefined | binary(), [cipher_suite()]) -> [cipher_suite()]. +-spec filter(undefined | binary(), [cipher_suite()], ssl_record:ssl_version()) -> [cipher_suite()]. %% %% Description: Select the cipher suites that can be used together with the %% supplied certificate. (Server side functionality) %%------------------------------------------------------------------- -filter(undefined, Ciphers) -> +filter(undefined, Ciphers, _) -> Ciphers; -filter(DerCert, Ciphers) -> +filter(DerCert, Ciphers0, Version) -> OtpCert = public_key:pkix_decode_cert(DerCert, otp), SigAlg = OtpCert#'OTPCertificate'.signatureAlgorithm, PubKeyInfo = OtpCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.subjectPublicKeyInfo, PubKeyAlg = PubKeyInfo#'OTPSubjectPublicKeyInfo'.algorithm, - Ciphers1 = - case ssl_certificate:public_key_type(PubKeyAlg#'PublicKeyAlgorithm'.algorithm) of - rsa -> - filter_keyuse(OtpCert, ((Ciphers -- dsa_signed_suites()) -- ec_keyed_suites()) -- ecdh_suites(), - rsa_suites(), dhe_rsa_suites() ++ ecdhe_rsa_suites()); - dsa -> - (Ciphers -- rsa_keyed_suites()) -- ec_keyed_suites(); - ec -> - filter_keyuse(OtpCert, (Ciphers -- rsa_keyed_suites()) -- dsa_signed_suites(), - [], ecdhe_ecdsa_suites()) - end, - - case public_key:pkix_sign_types(SigAlg#'SignatureAlgorithm'.algorithm) of - {_, rsa} -> - Ciphers1 -- ecdsa_signed_suites(); - {_, dsa} -> - Ciphers1; - {_, ecdsa} -> - Ciphers1 -- rsa_signed_suites() - end. + Ciphers = filter_suites_pubkey( + ssl_certificate:public_key_type(PubKeyAlg#'PublicKeyAlgorithm'.algorithm), + Ciphers0, Version, OtpCert), + {_, Sign} = public_key:pkix_sign_types(SigAlg#'SignatureAlgorithm'.algorithm), + filter_suites_signature(Sign, Ciphers, Version). + %%-------------------------------------------------------------------- -spec filter_suites([erl_cipher_suite()] | [cipher_suite()], map()) -> [erl_cipher_suite()] | [cipher_suite()]. @@ -2479,143 +2466,214 @@ next_iv(Bin, IV) -> <<_:FirstPart/binary, NextIV:IVSz/binary>> = Bin, NextIV. -rsa_signed_suites() -> - dhe_rsa_suites() ++ rsa_suites() ++ - psk_rsa_suites() ++ srp_rsa_suites() ++ - ecdh_rsa_suites() ++ ecdhe_rsa_suites(). - -rsa_keyed_suites() -> - dhe_rsa_suites() ++ rsa_suites() ++ - psk_rsa_suites() ++ srp_rsa_suites() ++ - ecdhe_rsa_suites(). - -dhe_rsa_suites() -> - [?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, - ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, - ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - ?TLS_DHE_RSA_WITH_DES_CBC_SHA, - ?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, - ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, - ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - ]. - -psk_rsa_suites() -> - [?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384, - ?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256, - ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384, - ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256, - ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA, - ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA, - ?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA, - ?TLS_RSA_PSK_WITH_RC4_128_SHA]. - -srp_rsa_suites() -> - [?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, - ?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA]. - -rsa_suites() -> - [?TLS_RSA_WITH_AES_256_CBC_SHA256, - ?TLS_RSA_WITH_AES_256_CBC_SHA, - ?TLS_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_RSA_WITH_AES_128_CBC_SHA256, - ?TLS_RSA_WITH_AES_128_CBC_SHA, - ?TLS_RSA_WITH_RC4_128_SHA, - ?TLS_RSA_WITH_RC4_128_MD5, - ?TLS_RSA_WITH_DES_CBC_SHA, - ?TLS_RSA_WITH_AES_128_GCM_SHA256, - ?TLS_RSA_WITH_AES_256_GCM_SHA384]. - -ecdh_rsa_suites() -> - [?TLS_ECDH_RSA_WITH_NULL_SHA, - ?TLS_ECDH_RSA_WITH_RC4_128_SHA, - ?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, - ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, - ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, - ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, - ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, - ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384]. - -ecdhe_rsa_suites() -> - [?TLS_ECDHE_RSA_WITH_NULL_SHA, - ?TLS_ECDHE_RSA_WITH_RC4_128_SHA, - ?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, - ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, - ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - ?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256]. - -dsa_signed_suites() -> - dhe_dss_suites() ++ srp_dss_suites(). - -dhe_dss_suites() -> - [?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, - ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, - ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, - ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, - ?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, - ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384]. - -srp_dss_suites() -> - [?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA, - ?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA, - ?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA]. +filter_suites_pubkey(rsa, CiphersSuites0, _Version, OtpCert) -> + KeyUses = key_uses(OtpCert), + NotECDSAKeyed = (CiphersSuites0 -- ec_keyed_suites(CiphersSuites0)) + -- dss_keyed_suites(CiphersSuites0), + CiphersSuites = filter_keyuse_suites(keyEncipherment, KeyUses, + NotECDSAKeyed, + rsa_suites_encipher(CiphersSuites0)), + filter_keyuse_suites(digitalSignature, KeyUses, CiphersSuites, + rsa_ecdhe_dhe_suites(CiphersSuites)); +filter_suites_pubkey(dsa, Ciphers, _, OtpCert) -> + KeyUses = key_uses(OtpCert), + NotECRSAKeyed = (Ciphers -- rsa_keyed_suites(Ciphers)) -- ec_keyed_suites(Ciphers), + filter_keyuse_suites(digitalSignature, KeyUses, NotECRSAKeyed, + dss_dhe_suites(Ciphers)); +filter_suites_pubkey(ec, Ciphers, _, OtpCert) -> + Uses = key_uses(OtpCert), + NotRSADSAKeyed = (Ciphers -- rsa_keyed_suites(Ciphers)) -- dss_keyed_suites(Ciphers), + CiphersSuites = filter_keyuse_suites(digitalSignature, Uses, NotRSADSAKeyed, + ec_ecdhe_suites(Ciphers)), + filter_keyuse_suites(keyAgreement, Uses, CiphersSuites, ec_ecdh_suites(Ciphers)). + +filter_suites_signature(rsa, Ciphers, {3, N}) when N >= 3 -> + Ciphers; +filter_suites_signature(rsa, Ciphers, Version) -> + (Ciphers -- ecdsa_signed_suites(Ciphers, Version)) -- dsa_signed_suites(Ciphers, Version); +filter_suites_signature(dsa, Ciphers, Version) -> + (Ciphers -- ecdsa_signed_suites(Ciphers, Version)) -- rsa_signed_suites(Ciphers, Version); +filter_suites_signature(ecdsa, Ciphers, Version) -> + (Ciphers -- rsa_signed_suites(Ciphers, Version)) -- dsa_signed_suites(Ciphers, Version). + + +%% From RFC 5246 - Section 7.4.2. Server Certificate +%% If the client provided a "signature_algorithms" extension, then all +%% certificates provided by the server MUST be signed by a +%% hash/signature algorithm pair that appears in that extension. Note +%% that this implies that a certificate containing a key for one +%% signature algorithm MAY be signed using a different signature +%% algorithm (for instance, an RSA key signed with a DSA key). This is +%% a departure from TLS 1.1, which required that the algorithms be the +%% same. +%% Note that this also implies that the DH_DSS, DH_RSA, +%% ECDH_ECDSA, and ECDH_RSA key exchange algorithms do not restrict the +%% algorithm used to sign the certificate. Fixed DH certificates MAY be +%% signed with any hash/signature algorithm pair appearing in the +%% extension. The names DH_DSS, DH_RSA, ECDH_ECDSA, and ECDH_RSA are +%% historical. +%% Note: DH_DSS and DH_RSA is not supported +rsa_signed({3,N}) when N >= 3 -> + fun(rsa) -> true; + (dhe_rsa) -> true; + (ecdhe_rsa) -> true; + (rsa_psk) -> true; + (srp_rsa) -> true; + (_) -> false + end; +rsa_signed(_) -> + fun(rsa) -> true; + (dhe_rsa) -> true; + (ecdhe_rsa) -> true; + (ecdh_rsa) -> true; + (rsa_psk) -> true; + (srp_rsa) -> true; + (_) -> false + end. +%% Cert should be signed by RSA +rsa_signed_suites(Ciphers, Version) -> + filter_suites(Ciphers, #{key_exchange_filters => [rsa_signed(Version)], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). +ecdsa_signed({3,N}) when N >= 3 -> + fun(ecdhe_ecdsa) -> true; + (_) -> false + end; +ecdsa_signed(_) -> + fun(ecdhe_ecdsa) -> true; + (ecdh_ecdsa) -> true; + (_) -> false + end. + +%% Cert should be signed by ECDSA +ecdsa_signed_suites(Ciphers, Version) -> + filter_suites(Ciphers, #{key_exchange_filters => [ecdsa_signed(Version)], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). + +rsa_keyed(dhe_rsa) -> + true; +rsa_keyed(ecdhe_rsa) -> + true; +rsa_keyed(rsa) -> + true; +rsa_keyed(rsa_psk) -> + true; +rsa_keyed(srp_rsa) -> + true; +rsa_keyed(_) -> + false. -ec_keyed_suites() -> - ecdh_ecdsa_suites() ++ ecdhe_ecdsa_suites() - ++ ecdh_rsa_suites(). +%% Certs key is an RSA key +rsa_keyed_suites(Ciphers) -> + filter_suites(Ciphers, #{key_exchange_filters => [fun(Kex) -> rsa_keyed(Kex) end], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). + +%% RSA Certs key can be used for encipherment +rsa_suites_encipher(Ciphers) -> + filter_suites(Ciphers, #{key_exchange_filters => [fun(rsa) -> true; + (rsa_psk) -> true; + (_) -> false + end], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). + +dss_keyed(dhe_dss) -> + true; +dss_keyed(spr_dss) -> + true; +dss_keyed(_) -> + false. + +%% Cert should be have DSS key (DSA) +dss_keyed_suites(Ciphers) -> + filter_suites(Ciphers, #{key_exchange_filters => [fun(Kex) -> dss_keyed(Kex) end], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). + +%% Cert should be signed by DSS (DSA) +dsa_signed_suites(Ciphers, Version) -> + filter_suites(Ciphers, #{key_exchange_filters => [dsa_signed(Version)], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). +dsa_signed(_) -> + fun(dhe_dss) -> true; + (_) -> false + end. -ecdsa_signed_suites() -> - ecdh_ecdsa_suites() ++ ecdhe_ecdsa_suites(). +dss_dhe_suites(Ciphers) -> + filter_suites(Ciphers, #{key_exchange_filters => [fun(dhe_dss) -> true; + (_) -> false + end], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). -ecdh_suites() -> - ecdh_rsa_suites() ++ ecdh_ecdsa_suites(). +ec_keyed(ecdh_ecdsa) -> + true; +ec_keyed(ecdh_rsa) -> + true; +ec_keyed(ecdhe_ecdsa) -> + true; +ec_keyed(_) -> + false. -ecdh_ecdsa_suites() -> - [?TLS_ECDH_ECDSA_WITH_NULL_SHA, - ?TLS_ECDH_ECDSA_WITH_RC4_128_SHA, - ?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, - ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, - ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, - ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, - ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, - ?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384]. - -ecdhe_ecdsa_suites() -> - [?TLS_ECDHE_ECDSA_WITH_NULL_SHA, - ?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, - ?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, - ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, - ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - ?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256]. - -filter_keyuse(OtpCert, Ciphers, Suites, SignSuites) -> +%% Certs key is an ECC key +ec_keyed_suites(Ciphers) -> + filter_suites(Ciphers, #{key_exchange_filters => [fun(Kex) -> ec_keyed(Kex) end], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). + +%% EC Certs key usage keyAgreement +ec_ecdh_suites(Ciphers)-> + filter_suites(Ciphers, #{key_exchange_filters => [fun(ecdh_ecdsa) -> true; + (_) -> false + end], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). + +%% EC Certs key usage digitalSignature +ec_ecdhe_suites(Ciphers) -> + filter_suites(Ciphers, #{key_exchange_filters => [fun(ecdhe_ecdsa) -> true; + (ecdhe_rsa) -> true; + (_) -> false + end], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). +%% RSA Certs key usage digitalSignature +rsa_ecdhe_dhe_suites(Ciphers) -> + filter_suites(Ciphers, #{key_exchange_filters => [fun(dhe_rsa) -> true; + (ecdhe_rsa) -> true; + (_) -> false + end], + cipher_filters => [], + mac_filters => [], + prf_filters => []}). + +key_uses(OtpCert) -> TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, TBSExtensions = TBSCert#'OTPTBSCertificate'.extensions, Extensions = ssl_certificate:extensions_list(TBSExtensions), case ssl_certificate:select_extension(?'id-ce-keyUsage', Extensions) of undefined -> - Ciphers; - #'Extension'{extnValue = KeyUse} -> - Result = filter_keyuse_suites(keyEncipherment, - KeyUse, Ciphers, Suites), - filter_keyuse_suites(digitalSignature, - KeyUse, Result, SignSuites) + []; + #'Extension'{extnValue = KeyUses} -> + KeyUses end. +%% If no key-usage extension is defined all key-usages are allowed +filter_keyuse_suites(_, [], CiphersSuites, _) -> + CiphersSuites; filter_keyuse_suites(Use, KeyUse, CipherSuits, Suites) -> case ssl_certificate:is_valid_key_usage(KeyUse, Use) of true -> diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl index 022fb7eac0..028721341c 100644 --- a/lib/ssl/src/ssl_config.erl +++ b/lib/ssl/src/ssl_config.erl @@ -91,9 +91,9 @@ init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, server end; init_certificates(Cert, Config, _, _) -> {ok, Config#{own_certificate => Cert}}. -init_private_key(_, #{algorithm := Alg} = Key, <<>>, _Password, _Client) when Alg == ecdsa; - Alg == rsa; - Alg == dss -> +init_private_key(_, #{algorithm := Alg} = Key, _, _Password, _Client) when Alg == ecdsa; + Alg == rsa; + Alg == dss -> case maps:is_key(engine, Key) andalso maps:is_key(key_id, Key) of true -> Key; @@ -132,7 +132,13 @@ private_key(#'PrivateKeyInfo'{privateKeyAlgorithm = #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-dsa'}, privateKey = Key}) -> public_key:der_decode('DSAPrivateKey', iolist_to_binary(Key)); - +private_key(#'PrivateKeyInfo'{privateKeyAlgorithm = + #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-ecPublicKey', + parameters = {asn1_OPENTYPE, Parameters}}, + privateKey = Key}) -> + ECKey = public_key:der_decode('ECPrivateKey', iolist_to_binary(Key)), + ECParameters = public_key:der_decode('EcpkParameters', Parameters), + ECKey#'ECPrivateKey'{parameters = ECParameters}; private_key(Key) -> Key. diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 64ecc29b97..94e756d5f6 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -681,6 +681,7 @@ certify(internal, #server_key_exchange{exchange_keys = Keys}, #state{role = client, negotiated_version = Version, key_algorithm = Alg, public_key_info = PubKeyInfo, + session = Session, connection_states = ConnectionStates} = State, Connection) when Alg == dhe_dss; Alg == dhe_rsa; Alg == ecdhe_rsa; Alg == ecdhe_ecdsa; @@ -702,7 +703,8 @@ certify(internal, #server_key_exchange{exchange_keys = Keys}, ConnectionStates, ssl:tls_version(Version), PubKeyInfo) of true -> calculate_secret(Params#server_key_params.params, - State#state{hashsign_algorithm = HashSign}, + State#state{hashsign_algorithm = HashSign, + session = session_handle_params(Params#server_key_params.params, Session)}, Connection); false -> handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR), @@ -1244,7 +1246,7 @@ connection_info(#state{sni_hostname = SNIHostname, RecordCB = record_cb(Connection), CipherSuiteDef = #{key_exchange := KexAlg} = ssl_cipher:suite_definition(CipherSuite), IsNamedCurveSuite = lists:member(KexAlg, - [ecdh_ecdsa, ecdhe_ecdsa, ecdh_anon]), + [ecdh_ecdsa, ecdhe_ecdsa, ecdh_rsa, ecdhe_rsa, ecdh_anon]), CurveInfo = case ECCCurve of {namedCurve, Curve} when IsNamedCurveSuite -> [{ecc, {named_curve, pubkey_cert_records:namedCurves(Curve)}}]; @@ -1344,11 +1346,12 @@ handle_peer_cert(Role, PeerCert, PublicKeyInfo, handle_peer_cert_key(client, _, {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey, PublicKeyParams}, - KeyAlg, State) when KeyAlg == ecdh_rsa; - KeyAlg == ecdh_ecdsa -> + KeyAlg, #state{session = Session} = State) when KeyAlg == ecdh_rsa; + KeyAlg == ecdh_ecdsa -> ECDHKey = public_key:generate_key(PublicKeyParams), PremasterSecret = ssl_handshake:premaster_secret(PublicKey, ECDHKey), - master_secret(PremasterSecret, State#state{diffie_hellman_keys = ECDHKey}); + master_secret(PremasterSecret, State#state{diffie_hellman_keys = ECDHKey, + session = Session#session{ecc = PublicKeyParams}}); %% We do currently not support cipher suites that use fixed DH. %% If we want to implement that the following clause can be used %% to extract DH parameters form cert. @@ -1516,9 +1519,11 @@ key_exchange(#state{role = server, key_algorithm = Algo, PrivateKey}), State = Connection:queue_handshake(Msg, State0), State#state{diffie_hellman_keys = DHKeys}; -key_exchange(#state{role = server, private_key = Key, key_algorithm = Algo} = State, _) +key_exchange(#state{role = server, private_key = #'ECPrivateKey'{parameters = ECCurve} = Key, key_algorithm = Algo, + session = Session} = State, _) when Algo == ecdh_ecdsa; Algo == ecdh_rsa -> - State#state{diffie_hellman_keys = Key}; + State#state{diffie_hellman_keys = Key, + session = Session#session{ecc = ECCurve}}; key_exchange(#state{role = server, key_algorithm = Algo, hashsign_algorithm = HashSignAlgo, private_key = PrivateKey, @@ -1653,12 +1658,13 @@ key_exchange(#state{role = client, key_exchange(#state{role = client, key_algorithm = Algorithm, negotiated_version = Version, - diffie_hellman_keys = Keys} = State0, Connection) + session = Session, + diffie_hellman_keys = #'ECPrivateKey'{parameters = ECCurve} = Key} = State0, Connection) when Algorithm == ecdhe_ecdsa; Algorithm == ecdhe_rsa; Algorithm == ecdh_ecdsa; Algorithm == ecdh_rsa; Algorithm == ecdh_anon -> - Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdh, Keys}), - Connection:queue_handshake(Msg, State0); + Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdh, Key}), + Connection:queue_handshake(Msg, State0#state{session = Session#session{ecc = ECCurve}}); key_exchange(#state{role = client, ssl_options = SslOpts, key_algorithm = psk, @@ -2134,6 +2140,11 @@ cancel_timer(Timer) -> erlang:cancel_timer(Timer), ok. +session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) -> + Session#session{ecc = ECCurve}; +session_handle_params(_, Session) -> + Session. + register_session(client, Host, Port, #session{is_resumable = new} = Session0) -> Session = Session0#session{is_resumable = true}, ssl_manager:register_session(Host, Port, Session), diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 8b1ea52ac9..c5a87e28bc 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -759,11 +759,12 @@ available_suites(UserSuites, Version) -> lists:filtermap(fun(Suite) -> lists:member(Suite, VersionSuites) end, UserSuites). available_suites(ServerCert, UserSuites, Version, undefined, Curve) -> - ssl_cipher:filter(ServerCert, available_suites(UserSuites, Version)) - -- unavailable_ecc_suites(Curve); + Suites = ssl_cipher:filter(ServerCert, available_suites(UserSuites, Version), Version), + filter_unavailable_ecc_suites(Curve, Suites); available_suites(ServerCert, UserSuites, Version, HashSigns, Curve) -> Suites = available_suites(ServerCert, UserSuites, Version, undefined, Curve), - filter_hashsigns(Suites, [ssl_cipher:suite_definition(Suite) || Suite <- Suites], HashSigns, []). + filter_hashsigns(Suites, [ssl_cipher:suite_definition(Suite) || Suite <- Suites], HashSigns, + Version, []). available_signature_algs(undefined, _) -> undefined; @@ -801,7 +802,7 @@ prf({3,0}, _, _, _, _, _) -> prf({3,_N}, PRFAlgo, Secret, Label, Seed, WantedLength) -> {ok, tls_v1:prf(PRFAlgo, Secret, Label, Seed, WantedLength)}. -select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, Port, #session{ecc = ECCCurve} = +select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, Port, #session{ecc = ECCCurve0} = Session, Version, #ssl_options{ciphers = UserSuites, honor_cipher_order = HonorCipherOrder} = SslOpts, Cache, CacheCb, Cert) -> @@ -810,10 +811,12 @@ select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, Port, Cache, CacheCb), case Resumed of undefined -> - Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve), - CipherSuite = select_cipher_suite(CipherSuites, Suites, HonorCipherOrder), + Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve0), + CipherSuite0 = select_cipher_suite(CipherSuites, Suites, HonorCipherOrder), + {ECCCurve, CipherSuite} = cert_curve(Cert, ECCCurve0, CipherSuite0), Compression = select_compression(Compressions), {new, Session#session{session_id = SessionId, + ecc = ECCCurve, cipher_suite = CipherSuite, compression_method = Compression}}; _ -> @@ -895,6 +898,13 @@ premaster_secret(EncSecret, #'RSAPrivateKey'{} = RSAPrivateKey) -> catch _:_ -> throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR)) + end; +premaster_secret(EncSecret, #{algorithm := rsa} = Engine) -> + try crypto:private_decrypt(rsa, EncSecret, maps:remove(algorithm, Engine), + [{rsa_pad, rsa_pkcs1_padding}]) + catch + _:_ -> + throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR)) end. %%==================================================================== %% Extensions handling @@ -1026,7 +1036,8 @@ select_curve(undefined, _, _) -> %%-------------------------------------------------------------------- select_hashsign(_, _, KeyExAlgo, _, _Version) when KeyExAlgo == dh_anon; KeyExAlgo == ecdh_anon; - KeyExAlgo == srp_anon -> + KeyExAlgo == srp_anon; + KeyExAlgo == psk -> {null, anon}; %% The signature_algorithms extension was introduced with TLS 1.2. Ignore it if we have %% negotiated a lower version. @@ -1035,17 +1046,14 @@ select_hashsign(HashSigns, Cert, KeyExAlgo, select_hashsign(HashSigns, Cert, KeyExAlgo, tls_v1:default_signature_algs(Version), Version); select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert, KeyExAlgo, SupportedHashSigns, {Major, Minor}) when Major >= 3 andalso Minor >= 3 -> - #'OTPCertificate'{tbsCertificate = TBSCert, - signatureAlgorithm = {_,SignAlgo, _}} = public_key:pkix_decode_cert(Cert, otp), + #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp), #'OTPSubjectPublicKeyInfo'{algorithm = {_, SubjAlgo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, - Sign = sign_algo(SignAlgo), - SubSing = sign_algo(SubjAlgo), - - case lists:filter(fun({_, S} = Algos) when S == Sign -> - is_acceptable_hash_sign(Algos, Sign, - SubSing, KeyExAlgo, SupportedHashSigns); + SubSign = sign_algo(SubjAlgo), + + case lists:filter(fun({_, S} = Algos) when S == SubSign -> + is_acceptable_hash_sign(Algos, KeyExAlgo, SupportedHashSigns); (_) -> false end, HashSigns) of @@ -1849,7 +1857,7 @@ dec_hello_extensions(<<?UINT16(?EC_POINT_FORMATS_EXT), ?UINT16(Len), ECPointFormats}}); dec_hello_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len), Rest/binary>>, Acc) when Len == 0 -> - dec_hello_extensions(Rest, Acc#hello_extensions{sni = ""}); %% Server may send an empy SNI + dec_hello_extensions(Rest, Acc#hello_extensions{sni = #sni{hostname = ""}}); %% Server may send an empy SNI dec_hello_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len), ExtData:Len/binary, Rest/binary>>, Acc) -> @@ -1993,25 +2001,26 @@ handle_psk_identity(_PSKIdentity, LookupFun) handle_psk_identity(PSKIdentity, {Fun, UserState}) -> Fun(psk, PSKIdentity, UserState). -filter_hashsigns([], [], _, Acc) -> - lists:reverse(Acc); -filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, - Acc) when KeyExchange == dhe_ecdsa; - KeyExchange == ecdhe_ecdsa -> - do_filter_hashsigns(ecdsa, Suite, Suites, Algos, HashSigns, Acc); -filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, +filter_hashsigns([], [], _, _, Acc) -> + lists:reverse(Acc); +filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version, + Acc) when KeyExchange == dhe_ecdsa; + KeyExchange == ecdhe_ecdsa -> + do_filter_hashsigns(ecdsa, Suite, Suites, Algos, HashSigns, Version, Acc); +filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version, Acc) when KeyExchange == rsa; KeyExchange == dhe_rsa; KeyExchange == ecdhe_rsa; KeyExchange == srp_rsa; KeyExchange == rsa_psk -> - do_filter_hashsigns(rsa, Suite, Suites, Algos, HashSigns, Acc); -filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Acc) when + do_filter_hashsigns(rsa, Suite, Suites, Algos, HashSigns, Version, Acc); +filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version, Acc) when KeyExchange == dhe_dss; KeyExchange == srp_dss -> - do_filter_hashsigns(dsa, Suite, Suites, Algos, HashSigns, Acc); -filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Acc) when + do_filter_hashsigns(dsa, Suite, Suites, Algos, HashSigns, Version, Acc); +filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Verion, + Acc) when KeyExchange == dh_dss; KeyExchange == dh_rsa; KeyExchange == dh_ecdsa; @@ -2020,28 +2029,37 @@ filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], Has %% Fixed DH certificates MAY be signed with any hash/signature %% algorithm pair appearing in the hash_sign extension. The names %% DH_DSS, DH_RSA, ECDH_ECDSA, and ECDH_RSA are historical. - filter_hashsigns(Suites, Algos, HashSigns, [Suite| Acc]); -filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Acc) when + filter_hashsigns(Suites, Algos, HashSigns, Verion, [Suite| Acc]); +filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Version, + Acc) when KeyExchange == dh_anon; KeyExchange == ecdh_anon; KeyExchange == srp_anon; KeyExchange == psk; KeyExchange == dhe_psk -> %% In this case hashsigns is not used as the kexchange is anonaymous - filter_hashsigns(Suites, Algos, HashSigns, [Suite| Acc]). + filter_hashsigns(Suites, Algos, HashSigns, Version, [Suite| Acc]). -do_filter_hashsigns(SignAlgo, Suite, Suites, Algos, HashSigns, Acc) -> +do_filter_hashsigns(SignAlgo, Suite, Suites, Algos, HashSigns, Version, Acc) -> case lists:keymember(SignAlgo, 2, HashSigns) of true -> - filter_hashsigns(Suites, Algos, HashSigns, [Suite| Acc]); + filter_hashsigns(Suites, Algos, HashSigns, Version, [Suite| Acc]); false -> - filter_hashsigns(Suites, Algos, HashSigns, Acc) + filter_hashsigns(Suites, Algos, HashSigns, Version, Acc) end. -unavailable_ecc_suites(no_curve) -> - ssl_cipher:ec_keyed_suites(); -unavailable_ecc_suites(_) -> - []. +filter_unavailable_ecc_suites(no_curve, Suites) -> + ECCSuites = ssl_cipher:filter_suites(Suites, #{key_exchange_filters => [fun(ecdh_ecdsa) -> true; + (ecdhe_ecdsa) -> true; + (ecdh_rsa) -> true; + (_) -> false + end], + cipher_filters => [], + mac_filters => [], + prf_filters => []}), + Suites -- ECCSuites; +filter_unavailable_ecc_suites(_, Suites) -> + Suites. %%-------------Extension handling -------------------------------- handle_renegotiation_extension(Role, RecordCB, Version, Info, Random, NegotiatedCipherSuite, @@ -2134,35 +2152,7 @@ sign_algo(Alg) -> {_, Sign} =public_key:pkix_sign_types(Alg), Sign. -is_acceptable_hash_sign(Algos, _, _, KeyExAlgo, SupportedHashSigns) when - KeyExAlgo == dh_dss; - KeyExAlgo == dh_rsa; - KeyExAlgo == dh_ecdsa -> - %% dh_* could be called only dh in TLS-1.2 - is_acceptable_hash_sign(Algos, SupportedHashSigns); -is_acceptable_hash_sign(Algos, rsa, ecdsa, ecdh_rsa, SupportedHashSigns) -> - is_acceptable_hash_sign(Algos, SupportedHashSigns); -is_acceptable_hash_sign({_, rsa} = Algos, rsa, _, dhe_rsa, SupportedHashSigns) -> - is_acceptable_hash_sign(Algos, SupportedHashSigns); -is_acceptable_hash_sign({_, rsa} = Algos, rsa, rsa, ecdhe_rsa, SupportedHashSigns) -> - is_acceptable_hash_sign(Algos, SupportedHashSigns); -is_acceptable_hash_sign({_, rsa} = Algos, rsa, rsa, rsa, SupportedHashSigns) -> - is_acceptable_hash_sign(Algos, SupportedHashSigns); -is_acceptable_hash_sign({_, rsa} = Algos, rsa, _, srp_rsa, SupportedHashSigns) -> - is_acceptable_hash_sign(Algos, SupportedHashSigns); -is_acceptable_hash_sign({_, rsa} = Algos, rsa, _, rsa_psk, SupportedHashSigns) -> - is_acceptable_hash_sign(Algos, SupportedHashSigns); -is_acceptable_hash_sign({_, dsa} = Algos, dsa, _, dhe_dss, SupportedHashSigns) -> - is_acceptable_hash_sign(Algos, SupportedHashSigns); -is_acceptable_hash_sign({_, dsa} = Algos, dsa, _, srp_dss, SupportedHashSigns) -> - is_acceptable_hash_sign(Algos, SupportedHashSigns); -is_acceptable_hash_sign({_, ecdsa} = Algos, ecdsa, _, dhe_ecdsa, SupportedHashSigns) -> - is_acceptable_hash_sign(Algos, SupportedHashSigns); -is_acceptable_hash_sign({_, ecdsa} = Algos, ecdsa, ecdsa, ecdh_ecdsa, SupportedHashSigns) -> - is_acceptable_hash_sign(Algos, SupportedHashSigns); -is_acceptable_hash_sign({_, ecdsa} = Algos, ecdsa, ecdsa, ecdhe_ecdsa, SupportedHashSigns) -> - is_acceptable_hash_sign(Algos, SupportedHashSigns); -is_acceptable_hash_sign(_, _, _, KeyExAlgo, _) when +is_acceptable_hash_sign( _, KeyExAlgo, _) when KeyExAlgo == psk; KeyExAlgo == dhe_psk; KeyExAlgo == srp_anon; @@ -2170,8 +2160,9 @@ is_acceptable_hash_sign(_, _, _, KeyExAlgo, _) when KeyExAlgo == ecdhe_anon -> true; -is_acceptable_hash_sign(_,_, _,_,_) -> - false. +is_acceptable_hash_sign(Algos,_, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns). + is_acceptable_hash_sign(Algos, SupportedHashSigns) -> lists:member(Algos, SupportedHashSigns). @@ -2350,3 +2341,21 @@ handle_renegotiation_info(_RecordCB, ConnectionStates, SecureRenegotation) -> {false, false} -> {ok, ConnectionStates} end. + +cert_curve(_, _, no_suite) -> + {no_curve, no_suite}; +cert_curve(Cert, ECCCurve0, CipherSuite) -> + case ssl_cipher:suite_definition(CipherSuite) of + #{key_exchange := Kex} when Kex == ecdh_ecdsa; + Kex == ecdh_rsa -> + OtpCert = public_key:pkix_decode_cert(Cert, otp), + TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, + #'OTPSubjectPublicKeyInfo'{algorithm = AlgInfo} + = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, + {namedCurve, Oid} = AlgInfo#'PublicKeyAlgorithm'.parameters, + {{namedCurve, Oid}, CipherSuite}; + _ -> + {ECCCurve0, CipherSuite} + end. + + diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 914ee9f22f..d3b3902fea 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -676,6 +676,7 @@ handle_info({CloseTag, Socket}, StateName, #state{socket = Socket, close_tag = CloseTag, socket_options = #socket_options{active = Active}, protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}, + user_data_buffer = Buffer, negotiated_version = Version} = State) -> %% Note that as of TLS 1.1, @@ -683,7 +684,7 @@ handle_info({CloseTag, Socket}, StateName, %% session not be resumed. This is a change from TLS 1.0 to conform %% with widespread implementation practice. - case (Active == false) andalso (CTs =/= []) of + case (Active == false) andalso ((CTs =/= []) or (Buffer =/= <<>>)) of false -> case Version of {1, N} when N >= 1 -> diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile index aa01552c39..256c0bb017 100644 --- a/lib/ssl/test/Makefile +++ b/lib/ssl/test/Makefile @@ -53,7 +53,9 @@ MODULES = \ ssl_pem_cache_SUITE \ ssl_session_cache_SUITE \ ssl_to_openssl_SUITE \ - ssl_ECC_SUITE \ + ssl_ECC\ + ssl_ECC_SUITE \ + ssl_ECC_openssl_SUITE\ ssl_upgrade_SUITE\ ssl_sni_SUITE \ make_certs\ diff --git a/lib/ssl/test/ssl_ECC.erl b/lib/ssl/test/ssl_ECC.erl new file mode 100644 index 0000000000..6422cd75e9 --- /dev/null +++ b/lib/ssl/test/ssl_ECC.erl @@ -0,0 +1,171 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(ssl_ECC). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%% Test diffrent certificate chain types, note that it is the servers +%% chain that affect what cipher suit that will be choosen + +%% ECDH_RSA +client_ecdh_rsa_server_ecdh_rsa(Config) when is_list(Config) -> + Ext = x509_test:extensions([{key_usage, [keyAgreement]}]), + Suites = all_rsa_suites(Config), + Default = ssl_test_lib:default_cert_chain_conf(), + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, + [[], [], [{extensions, Ext}]]}, + {client_chain, Default}], + ecdh_rsa, ecdh_rsa, Config), + ssl_test_lib:basic_test(ssl_test_lib:ssl_options(COpts, Config), + ssl_test_lib:ssl_options(SOpts, Config), + [{check_keyex, ecdh_rsa}, {ciphers, Suites} | proplists:delete(check_keyex, Config)]). +client_ecdhe_rsa_server_ecdh_rsa(Config) when is_list(Config) -> + Ext = x509_test:extensions([{key_usage, [keyAgreement]}]), + Suites = all_rsa_suites(Config), + Default = ssl_test_lib:default_cert_chain_conf(), + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, + [[], [], [{extensions, Ext}]]}, + {client_chain, Default}], + ecdhe_rsa, ecdh_rsa, Config), + ssl_test_lib:basic_test(ssl_test_lib:ssl_options(COpts, Config), + ssl_test_lib:ssl_options(SOpts, Config), + [{check_keyex, ecdh_rsa}, {ciphers, Suites} | proplists:delete(check_keyex, Config)]). +client_ecdhe_ecdsa_server_ecdh_rsa(Config) when is_list(Config) -> + Ext = x509_test:extensions([{key_usage, [keyAgreement]}]), + Suites = all_rsa_suites(Config), + Default = ssl_test_lib:default_cert_chain_conf(), + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, + [[], [], [{extensions, Ext}]]}, + {client_chain, Default}], + ecdhe_ecdsa, ecdh_rsa, Config), + ssl_test_lib:basic_test(ssl_test_lib:ssl_options(COpts, Config), + ssl_test_lib:ssl_options(SOpts, Config), + [{check_keyex, ecdh_rsa}, {ciphers, Suites} | proplists:delete(check_keyex, Config)]). + +%% ECDHE_RSA +client_ecdh_rsa_server_ecdhe_rsa(Config) when is_list(Config) -> + Ext = x509_test:extensions([{key_usage, [digitalSignature]}]), + Default = ssl_test_lib:default_cert_chain_conf(), + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, + [[], [], [{extensions, Ext}]]}, + {client_chain, Default}], + ecdh_rsa, ecdhe_rsa, Config), + ssl_test_lib:basic_test(ssl_test_lib:ssl_options(COpts, Config), + ssl_test_lib:ssl_options(SOpts, Config), + [{check_keyex, ecdhe_rsa} | proplists:delete(check_keyex, Config)]). +client_ecdhe_rsa_server_ecdhe_rsa(Config) when is_list(Config) -> + Ext = x509_test:extensions([{key_usage, [digitalSignature]}]), + Default = ssl_test_lib:default_cert_chain_conf(), + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, + [[], [], [{extensions, Ext}]]}, + {client_chain, Default}], + ecdhe_rsa, ecdhe_rsa, Config), + ssl_test_lib:basic_test(ssl_test_lib:ssl_options(COpts, Config), + ssl_test_lib:ssl_options(SOpts, Config), + [{check_keyex, ecdhe_rsa} | proplists:delete(check_keyex, Config)]). +client_ecdhe_ecdsa_server_ecdhe_rsa(Config) when is_list(Config) -> + Ext = x509_test:extensions([{key_usage, [digitalSignature]}]), + Default = ssl_test_lib:default_cert_chain_conf(), + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, + [[], [], [{extensions, Ext}]]}, + {client_chain, Default}], + ecdh_ecdsa, ecdhe_rsa, Config), + ssl_test_lib:basic_test(ssl_test_lib:ssl_options(COpts, Config), + ssl_test_lib:ssl_options(SOpts, Config), + [{check_keyex, ecdhe_rsa} | proplists:delete(check_keyex, Config)]). + +%% ECDH_ECDSA +client_ecdh_ecdsa_server_ecdh_ecdsa(Config) when is_list(Config) -> + Ext = x509_test:extensions([{key_usage, [keyAgreement]}]), + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, + [[], [], [{extensions, Ext}]]}, + {client_chain, + ssl_test_lib:default_cert_chain_conf()}], + ecdh_ecdsa, ecdh_ecdsa, Config), + ssl_test_lib:basic_test(ssl_test_lib:ssl_options(COpts, Config), + ssl_test_lib:ssl_options(SOpts, Config), + [{check_keyex, ecdh_ecdsa} | proplists:delete(check_keyex, Config)]). +client_ecdhe_rsa_server_ecdh_ecdsa(Config) when is_list(Config) -> + Ext = x509_test:extensions([{key_usage, [keyAgreement]}]), + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, + [[], [], [{extensions, Ext}]]}, + {client_chain, + ssl_test_lib:default_cert_chain_conf()}], + ecdhe_rsa, ecdh_ecdsa, Config), + ssl_test_lib:basic_test(ssl_test_lib:ssl_options(COpts, Config), + ssl_test_lib:ssl_options(SOpts, Config), + [{check_keyex, ecdh_ecdsa} | proplists:delete(check_keyex, Config)]). + +client_ecdhe_ecdsa_server_ecdh_ecdsa(Config) when is_list(Config) -> + Ext = x509_test:extensions([{key_usage, [keyAgreement]}]), + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, + [[], [], [{extensions, Ext}]]}, + {client_chain, + ssl_test_lib:default_cert_chain_conf()}], + ecdhe_ecdsa, ecdh_ecdsa, Config), + ssl_test_lib:basic_test(ssl_test_lib:ssl_options(COpts, Config), + ssl_test_lib:ssl_options(SOpts, Config), + [{check_keyex, ecdh_ecdsa} | proplists:delete(check_keyex, Config)]). + +%% ECDHE_ECDSA +client_ecdh_rsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> + Ext = x509_test:extensions([{key_usage, [digitalSignature]}]), + Default = ssl_test_lib:default_cert_chain_conf(), + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, + [[], [], [{extensions, Ext}]]}, + {client_chain, Default}], + ecdh_rsa, ecdhe_ecdsa, Config), + ssl_test_lib:basic_test(ssl_test_lib:ssl_options(COpts, Config), + ssl_test_lib:ssl_options(SOpts, Config), + [{check_keyex, ecdhe_ecdsa} | proplists:delete(check_keyex, Config)]). +client_ecdh_ecdsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> + Ext = x509_test:extensions([{key_usage, [digitalSignature]}]), + Default = ssl_test_lib:default_cert_chain_conf(), + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, + [[], [], [{extensions, Ext}]]}, + {client_chain, Default}], + ecdh_ecdsa, ecdhe_ecdsa, Config), + ssl_test_lib:basic_test(ssl_test_lib:ssl_options(COpts, Config), + ssl_test_lib:ssl_options(SOpts, Config), + [{check_keyex, ecdhe_ecdsa} | proplists:delete(check_keyex, Config)]). +client_ecdhe_ecdsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> + Ext = x509_test:extensions([{key_usage, [digitalSignature]}]), + Default = ssl_test_lib:default_cert_chain_conf(), + {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, + [[], [], [{extensions, Ext}]]}, + {client_chain, Default}], + ecdhe_ecdsa, ecdhe_ecdsa, Config), + ssl_test_lib:basic_test(ssl_test_lib:ssl_options(COpts, Config), + ssl_test_lib:ssl_options(SOpts, Config), + [{check_keyex, ecdhe_ecdsa} | proplists:delete(check_keyex, Config)]). + +all_rsa_suites(Config) -> + Version = proplists:get_value(tls_version, Config), + All = ssl:cipher_suites(all, Version), + Default = ssl:cipher_suites(default, Version), + RSASuites = ssl:filter_cipher_suites(All,[{key_exchange, fun(rsa) -> true;(_) -> false end}]), + ssl:append_cipher_suites(RSASuites, Default). diff --git a/lib/ssl/test/ssl_ECC_SUITE.erl b/lib/ssl/test/ssl_ECC_SUITE.erl index f38c0a7416..c93f066825 100644 --- a/lib/ssl/test/ssl_ECC_SUITE.erl +++ b/lib/ssl/test/ssl_ECC_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2017. All Rights Reserved. +%% Copyright Ericsson AB 2007-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -43,52 +43,17 @@ all() -> groups() -> [ - {'tlsv1.2', [], all_versions_groups()}, - {'tlsv1.1', [], all_versions_groups()}, - {'tlsv1', [], all_versions_groups()}, - {'dtlsv1.2', [], all_versions_groups()}, - {'dtlsv1', [], all_versions_groups()}, - {'erlang_server', [], openssl_key_cert_combinations()}, - %%{'erlang_client', [], openssl_key_cert_combinations()}, - {'erlang', [], key_cert_combinations() ++ misc() - ++ ecc_negotiation()} + {'tlsv1.2', [], [mix_sign | test_cases()]}, + {'tlsv1.1', [], test_cases()}, + {'tlsv1', [], test_cases()}, + {'dtlsv1.2', [], [mix_sign | test_cases()]}, + {'dtlsv1', [], test_cases()} ]. -all_versions_groups ()-> - [{group, 'erlang_server'}, - %%{group, 'erlang_client'}, - {group, 'erlang'} - ]. - - -openssl_key_cert_combinations() -> - ECDH_RSA = case ssl_test_lib:openssl_filter("ECDH-RSA") of - [] -> - []; - _ -> - server_ecdh_rsa() - end, - - ECDHE_RSA = case ssl_test_lib:openssl_filter("ECDHE-RSA") of - [] -> - []; - _ -> - server_ecdhe_rsa() - end, - ECDH_ECDSA = case ssl_test_lib:openssl_filter("ECDH-ECDSA") of - [] -> - []; - _ -> - server_ecdhe_ecdsa() - end, - - ECDHE_ECDSA = case ssl_test_lib:openssl_filter("ECDHE-ECDSA") of - [] -> - []; - _ -> - server_ecdhe_ecdsa() - end, - ECDH_RSA ++ ECDHE_RSA ++ ECDH_ECDSA ++ ECDHE_ECDSA. +test_cases()-> + key_cert_combinations() + ++ misc() + ++ ecc_negotiation(). key_cert_combinations() -> server_ecdh_rsa() ++ @@ -116,7 +81,6 @@ server_ecdhe_ecdsa() -> client_ecdh_ecdsa_server_ecdhe_ecdsa, client_ecdhe_ecdsa_server_ecdhe_ecdsa]. - misc()-> [client_ecdsa_server_ecdsa_with_raw_key]. @@ -142,9 +106,14 @@ init_per_suite(Config0) -> end_per_suite(Config0), try crypto:start() of ok -> - Config0 + case ssl_test_lib:sufficient_crypto_support(cipher_ec) of + true -> + Config0; + false -> + {skip, "Crypto does not support ECC"} + end catch _:_ -> - {skip, "Crypto did not start"} + {skip, "Crypto did not start"} end. end_per_suite(_Config) -> @@ -152,52 +121,14 @@ end_per_suite(_Config) -> application:stop(crypto). %%-------------------------------------------------------------------- -init_per_group(erlang_client = Group, Config) -> - case ssl_test_lib:is_sane_ecc(openssl) of - true -> - common_init_per_group(Group, [{server_type, openssl}, - {client_type, erlang} | Config]); - false -> - {skip, "Known ECC bug in openssl"} - end; - -init_per_group(erlang_server = Group, Config) -> - case ssl_test_lib:is_sane_ecc(openssl) of - true -> - common_init_per_group(Group, [{server_type, erlang}, - {client_type, openssl} | Config]); - false -> - {skip, "Known ECC bug in openssl"} - end; - -init_per_group(erlang = Group, Config) -> - case ssl_test_lib:sufficient_crypto_support(Group) of - true -> - common_init_per_group(Group, [{server_type, erlang}, - {client_type, erlang} | Config]); - false -> - {skip, "Crypto does not support ECC"} - end; - -init_per_group(openssl = Group, Config) -> - case ssl_test_lib:sufficient_crypto_support(Group) of - true -> - common_init_per_group(Group, [{server_type, openssl}, - {client_type, openssl} | Config]); - false -> - {skip, "Crypto does not support ECC"} - end; - -init_per_group(Group, Config) -> - common_init_per_group(Group, Config). - -common_init_per_group(GroupName, Config) -> +init_per_group(GroupName, Config) -> case ssl_test_lib:is_tls_version(GroupName) of true -> - Config0 = ssl_test_lib:init_tls_version(GroupName, Config), - [{tls_version, GroupName} | Config0]; - _ -> - openssl_check(GroupName, Config) + [{tls_version, GroupName}, + {server_type, erlang}, + {client_type, erlang} | ssl_test_lib:init_tls_version(GroupName, Config)]; + _ -> + Config end. end_per_group(GroupName, Config0) -> @@ -215,7 +146,7 @@ init_per_testcase(TestCase, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]), end_per_testcase(TestCase, Config), - ssl_test_lib:clean_start(), + ssl:start(), ct:timetrap({seconds, 15}), Config. @@ -226,104 +157,45 @@ end_per_testcase(_TestCase, Config) -> %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- - %% Test diffrent certificate chain types, note that it is the servers %% chain that affect what cipher suit that will be choosen %% ECDH_RSA client_ecdh_rsa_server_ecdh_rsa(Config) when is_list(Config) -> - Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, - {client_chain, Default}], - ecdh_rsa, ecdh_rsa, Config), - basic_test(COpts, SOpts, [{check_keyex, ecdh_rsa} | proplists:delete(check_keyex, Config)]). -client_ecdhe_rsa_server_ecdh_rsa(Config) when is_list(Config) -> - Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, - {client_chain, Default}], - ecdhe_rsa, ecdh_rsa, Config), - basic_test(COpts, SOpts, [{check_keyex, ecdh_rsa} | proplists:delete(check_keyex, Config)]). -client_ecdhe_ecdsa_server_ecdh_rsa(Config) when is_list(Config) -> - Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, - {client_chain, Default}], - ecdhe_ecdsa, ecdh_rsa, Config), - basic_test(COpts, SOpts, [{check_keyex, ecdh_rsa} | proplists:delete(check_keyex, Config)]). - + ssl_ECC:client_ecdh_rsa_server_ecdh_rsa(Config). +client_ecdhe_rsa_server_ecdh_rsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdhe_rsa_server_ecdh_rsa(Config). +client_ecdhe_ecdsa_server_ecdh_rsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdhe_ecdsa_server_ecdh_rsa(Config). %% ECDHE_RSA client_ecdh_rsa_server_ecdhe_rsa(Config) when is_list(Config) -> - Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, - {client_chain, Default}], - ecdh_rsa, ecdhe_rsa, Config), - basic_test(COpts, SOpts, [{check_keyex, ecdhe_rsa} | proplists:delete(check_keyex, Config)]). -client_ecdhe_rsa_server_ecdhe_rsa(Config) when is_list(Config) -> - Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, - {client_chain, Default}], - ecdhe_rsa, ecdhe_rsa, Config), - basic_test(COpts, SOpts, [{check_keyex, ecdhe_rsa} | proplists:delete(check_keyex, Config)]). -client_ecdhe_ecdsa_server_ecdhe_rsa(Config) when is_list(Config) -> - Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, - {client_chain, Default}], - ecdh_ecdsa, ecdhe_rsa, Config), - basic_test(COpts, SOpts, [{check_keyex, ecdhe_rsa} | proplists:delete(check_keyex, Config)]). - + ssl_ECC:client_ecdh_rsa_server_ecdhe_rsa(Config). +client_ecdhe_rsa_server_ecdhe_rsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdhe_rsa_server_ecdhe_rsa(Config). +client_ecdhe_ecdsa_server_ecdhe_rsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdhe_ecdsa_server_ecdhe_rsa(Config). %% ECDH_ECDSA -client_ecdh_ecdsa_server_ecdh_ecdsa(Config) when is_list(Config) -> - Ext = x509_test:extensions([{key_usage, [keyEncipherment]}]), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, - [[], [], [{extensions, Ext}]]}, - {client_chain, - ssl_test_lib:default_cert_chain_conf()}], - ecdh_ecdsa, ecdh_ecdsa, Config), - basic_test(COpts, SOpts, - [{check_keyex, ecdh_ecdsa} | proplists:delete(check_keyex, Config)]). -client_ecdhe_rsa_server_ecdh_ecdsa(Config) when is_list(Config) -> - Ext = x509_test:extensions([{key_usage, [keyEncipherment]}]), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, - [[], [], [{extensions, Ext}]]}, - {client_chain, - ssl_test_lib:default_cert_chain_conf()}], - ecdhe_rsa, ecdh_ecdsa, Config), - basic_test(COpts, SOpts, [{check_keyex, ecdh_ecdsa} | proplists:delete(check_keyex, Config)]). - -client_ecdhe_ecdsa_server_ecdh_ecdsa(Config) when is_list(Config) -> - Ext = x509_test:extensions([{key_usage, [keyEncipherment]}]), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, - [[], [], [{extensions, Ext}]]}, - {client_chain, - ssl_test_lib:default_cert_chain_conf()}], - ecdhe_ecdsa, ecdh_ecdsa, Config), - basic_test(COpts, SOpts, - [{check_keyex, ecdh_ecdsa} | proplists:delete(check_keyex, Config)]). - +client_ecdh_ecdsa_server_ecdh_ecdsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdh_ecdsa_server_ecdh_ecdsa(Config). +client_ecdhe_rsa_server_ecdh_ecdsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdhe_rsa_server_ecdh_ecdsa(Config). +client_ecdhe_ecdsa_server_ecdh_ecdsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdhe_ecdsa_server_ecdh_ecdsa(Config). %% ECDHE_ECDSA -client_ecdh_rsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> - Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, - {client_chain, Default}], - ecdh_rsa, ecdhe_ecdsa, Config), - basic_test(COpts, SOpts, [{check_keyex, ecdhe_ecdsa} | proplists:delete(check_keyex, Config)]). -client_ecdh_ecdsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> - Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, - {client_chain, Default}], - ecdh_ecdsa, ecdhe_ecdsa, Config), - basic_test(COpts, SOpts, [{check_keyex, ecdhe_ecdsa} | proplists:delete(check_keyex, Config)]). -client_ecdhe_ecdsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> - Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, - {client_chain, Default}], - ecdhe_ecdsa, ecdhe_ecdsa, Config), - basic_test(COpts, SOpts, [{check_keyex, ecdhe_ecdsa} | proplists:delete(check_keyex, Config)]). +client_ecdh_rsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdh_rsa_server_ecdhe_ecdsa(Config). +client_ecdh_ecdsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdh_ecdsa_server_ecdhe_ecdsa(Config). +client_ecdhe_ecdsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdhe_ecdsa_server_ecdhe_ecdsa(Config). client_ecdsa_server_ecdsa_with_raw_key(Config) when is_list(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, + {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, {client_chain, Default}] , ecdhe_ecdsa, ecdhe_ecdsa, Config), + COpts = ssl_test_lib:ssl_options(COpts0, Config), + SOpts = ssl_test_lib:ssl_options(SOpts0, Config), ServerKeyFile = proplists:get_value(keyfile, SOpts), {ok, PemBin} = file:read_file(ServerKeyFile), PemEntries = public_key:pem_decode(PemBin), @@ -331,331 +203,202 @@ client_ecdsa_server_ecdsa_with_raw_key(Config) when is_list(Config) -> ServerKey = {'ECPrivateKey', Key}, SType = proplists:get_value(server_type, Config), CType = proplists:get_value(client_type, Config), - {Server, Port} = start_server_with_raw_key(SType, - [{key, ServerKey} | proplists:delete(keyfile, SOpts)], - Config), - Client = start_client(CType, Port, COpts, Config), - check_result(Server, SType, Client, CType), - close(Server, Client). + {Server, Port} = ssl_test_lib:start_server_with_raw_key(SType, + [{key, ServerKey} | proplists:delete(keyfile, SOpts)], + Config), + Client = ssl_test_lib:start_client(CType, Port, COpts, Config), + ssl_test_lib:gen_check_result(Server, SType, Client, CType), + ssl_test_lib:stop(Server, Client). ecc_default_order(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, - {client_chain, Default}], - ecdhe_ecdsa, ecdhe_ecdsa, Config), + {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, + {client_chain, Default}], + ecdhe_ecdsa, ecdhe_ecdsa, Config), + COpts = ssl_test_lib:ssl_options(COpts0, Config), + SOpts = ssl_test_lib:ssl_options(SOpts0, Config), ECCOpts = [], - case supported_eccs([{eccs, [sect571r1]}]) of - true -> ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); + case ssl_test_lib:supported_eccs([{eccs, [sect571r1]}]) of + true -> ssl_test_lib:ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. ecc_default_order_custom_curves(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, - {client_chain, Default}], - ecdhe_ecdsa, ecdhe_ecdsa, Config), + {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, + {client_chain, Default}], + ecdhe_ecdsa, ecdhe_ecdsa, Config), + COpts = ssl_test_lib:ssl_options(COpts0, Config), + SOpts = ssl_test_lib:ssl_options(SOpts0, Config), ECCOpts = [{eccs, [secp256r1, sect571r1]}], - case supported_eccs(ECCOpts) of - true -> ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); + case ssl_test_lib:supported_eccs(ECCOpts) of + true -> ssl_test_lib:ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. ecc_client_order(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, - {client_chain, Default}], - ecdhe_ecdsa, ecdhe_ecdsa, Config), + {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, + {client_chain, Default}], + ecdhe_ecdsa, ecdhe_ecdsa, Config), + COpts = ssl_test_lib:ssl_options(COpts0, Config), + SOpts = ssl_test_lib:ssl_options(SOpts0, Config), ECCOpts = [{honor_ecc_order, false}], - case supported_eccs([{eccs, [sect571r1]}]) of - true -> ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); + case ssl_test_lib:supported_eccs([{eccs, [sect571r1]}]) of + true -> ssl_test_lib:ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. ecc_client_order_custom_curves(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, + {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, {client_chain, Default}], ecdhe_ecdsa, ecdhe_ecdsa, Config), + COpts = ssl_test_lib:ssl_options(COpts0, Config), + SOpts = ssl_test_lib:ssl_options(SOpts0, Config), ECCOpts = [{honor_ecc_order, false}, {eccs, [secp256r1, sect571r1]}], - case supported_eccs(ECCOpts) of - true -> ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); + case ssl_test_lib:supported_eccs(ECCOpts) of + true -> ssl_test_lib:ecc_test(sect571r1, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. ecc_unknown_curve(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, + {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, {client_chain, Default}], ecdhe_ecdsa, ecdhe_ecdsa, Config), + COpts = ssl_test_lib:ssl_options(COpts0, Config), + SOpts = ssl_test_lib:ssl_options(SOpts0, Config), ECCOpts = [{eccs, ['123_fake_curve']}], - ecc_test_error(COpts, SOpts, [], ECCOpts, Config). + ssl_test_lib:ecc_test_error(COpts, SOpts, [], ECCOpts, Config). client_ecdh_rsa_server_ecdhe_ecdsa_server_custom(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, + {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, {client_chain, Default}], - ecdh_rsa, ecdhe_ecdsa, Config), + ecdh_rsa, ecdhe_ecdsa, Config), + COpts = ssl_test_lib:ssl_options(COpts0, Config), + SOpts = ssl_test_lib:ssl_options(SOpts0, Config), ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], - case supported_eccs(ECCOpts) of - true -> ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); + case ssl_test_lib:supported_eccs(ECCOpts) of + true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. client_ecdh_rsa_server_ecdhe_rsa_server_custom(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, - {client_chain, Default}], - ecdh_rsa, ecdhe_rsa, Config), + {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, + {client_chain, Default}], + ecdh_rsa, ecdhe_rsa, Config), + COpts = ssl_test_lib:ssl_options(COpts0, Config), + SOpts = ssl_test_lib:ssl_options(SOpts0, Config), ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], - case supported_eccs(ECCOpts) of - true -> ecc_test(undefined, COpts, SOpts, [], ECCOpts, Config); + + case ssl_test_lib:supported_eccs(ECCOpts) of + true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. client_ecdhe_rsa_server_ecdhe_ecdsa_server_custom(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, - {client_chain, Default}], - ecdhe_rsa, ecdhe_ecdsa, Config), + {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, + {client_chain, Default}], + ecdhe_rsa, ecdhe_ecdsa, Config), + COpts = ssl_test_lib:ssl_options(COpts0, Config), + SOpts = ssl_test_lib:ssl_options(SOpts0, Config), ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], - case supported_eccs(ECCOpts) of - true -> ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); + case ssl_test_lib:supported_eccs(ECCOpts) of + true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. client_ecdhe_rsa_server_ecdhe_rsa_server_custom(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, - {client_chain, Default}], - ecdhe_rsa, ecdhe_rsa, Config), + {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, + {client_chain, Default}], + ecdhe_rsa, ecdhe_rsa, Config), + + COpts = ssl_test_lib:ssl_options(COpts0, Config), + SOpts = ssl_test_lib:ssl_options(SOpts0, Config), ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], - case supported_eccs(ECCOpts) of - true -> ecc_test(undefined, COpts, SOpts, [], ECCOpts, Config); + case ssl_test_lib:supported_eccs(ECCOpts) of + true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. client_ecdhe_rsa_server_ecdh_rsa_server_custom(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), Ext = x509_test:extensions([{key_usage, [keyEncipherment]}]), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, [[], [], [{extensions, Ext}]]}, - {client_chain, Default}], - ecdhe_rsa, ecdh_rsa, Config), + {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, [[], [], [{extensions, Ext}]]}, + {client_chain, Default}], + ecdhe_rsa, ecdh_rsa, Config), + + COpts = ssl_test_lib:ssl_options(COpts0, Config), + SOpts = ssl_test_lib:ssl_options(SOpts0, Config), ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], - case supported_eccs(ECCOpts) of - true -> ecc_test(undefined, COpts, SOpts, [], ECCOpts, Config); + Expected = pubkey_cert_records:namedCurves(hd(tls_v1:ecc_curves(0))), %% The certificate curve + + case ssl_test_lib:supported_eccs(ECCOpts) of + true -> ssl_test_lib:ecc_test(Expected, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. client_ecdhe_ecdsa_server_ecdhe_ecdsa_server_custom(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, + {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, {client_chain, Default}], - ecdhe_ecdsa, ecdhe_ecdsa, Config), + ecdhe_ecdsa, ecdhe_ecdsa, Config), + COpts = ssl_test_lib:ssl_options(COpts0, Config), + SOpts = ssl_test_lib:ssl_options(SOpts0, Config), ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], - case supported_eccs(ECCOpts) of - true -> ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); + case ssl_test_lib:supported_eccs(ECCOpts) of + true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. client_ecdhe_ecdsa_server_ecdhe_rsa_server_custom(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, - {client_chain, Default}], - ecdhe_ecdsa, ecdhe_rsa, Config), + {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, + {client_chain, Default}], + ecdhe_ecdsa, ecdhe_rsa, Config), + COpts = ssl_test_lib:ssl_options(COpts0, Config), + SOpts = ssl_test_lib:ssl_options(SOpts0, Config), ECCOpts = [{honor_ecc_order, true}, {eccs, [secp256r1, sect571r1]}], - case supported_eccs(ECCOpts) of - true -> ecc_test(undefined, COpts, SOpts, [], ECCOpts, Config); + case ssl_test_lib:supported_eccs(ECCOpts) of + true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, [], ECCOpts, Config); false -> {skip, "unsupported named curves"} end. client_ecdhe_ecdsa_server_ecdhe_ecdsa_client_custom(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, + {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, {client_chain, Default}], - ecdhe_ecdsa, ecdhe_ecdsa, Config), + ecdhe_ecdsa, ecdhe_ecdsa, Config), + COpts = ssl_test_lib:ssl_options(COpts0, Config), + SOpts = ssl_test_lib:ssl_options(SOpts0, Config), ECCOpts = [{eccs, [secp256r1, sect571r1]}], - case supported_eccs(ECCOpts) of - true -> ecc_test(secp256r1, COpts, SOpts, ECCOpts, [], Config); + case ssl_test_lib:supported_eccs(ECCOpts) of + true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, ECCOpts, [], Config); false -> {skip, "unsupported named curves"} end. client_ecdhe_rsa_server_ecdhe_ecdsa_client_custom(Config) -> Default = ssl_test_lib:default_cert_chain_conf(), - {COpts, SOpts} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, - {client_chain, Default}], - ecdhe_rsa, ecdhe_ecdsa, Config), + {COpts0, SOpts0} = ssl_test_lib:make_ec_cert_chains([{server_chain, Default}, + {client_chain, Default}], + ecdhe_rsa, ecdhe_ecdsa, Config), + COpts = ssl_test_lib:ssl_options(COpts0, Config), + SOpts = ssl_test_lib:ssl_options(SOpts0, Config), ECCOpts = [{eccs, [secp256r1, sect571r1]}], - case supported_eccs(ECCOpts) of - true -> ecc_test(secp256r1, COpts, SOpts, ECCOpts, [], Config); + case ssl_test_lib:supported_eccs(ECCOpts) of + true -> ssl_test_lib:ecc_test(secp256r1, COpts, SOpts, ECCOpts, [], Config); false -> {skip, "unsupported named curves"} end. -%%-------------------------------------------------------------------- -%% Internal functions ------------------------------------------------ -%%-------------------------------------------------------------------- -basic_test(COpts, SOpts, Config) -> - SType = proplists:get_value(server_type, Config), - CType = proplists:get_value(client_type, Config), - {Server, Port} = start_server(SType, SOpts, Config), - Client = start_client(CType, Port, COpts, Config), - check_result(Server, SType, Client, CType), - close(Server, Client). - - -ecc_test(Expect, COpts, SOpts, CECCOpts, SECCOpts, Config) -> - {Server, Port} = start_server_ecc(erlang, SOpts, Expect, SECCOpts, Config), - Client = start_client_ecc(erlang, Port, COpts, Expect, CECCOpts, Config), - ssl_test_lib:check_result(Server, ok, Client, ok), - close(Server, Client). - -ecc_test_error(COpts, SOpts, CECCOpts, SECCOpts, Config) -> - {Server, Port} = start_server_ecc_error(erlang, SOpts, SECCOpts, Config), - Client = start_client_ecc_error(erlang, Port, COpts, CECCOpts, Config), - Error = {error, {tls_alert, "insufficient security"}}, - ssl_test_lib:check_result(Server, Error, Client, Error). - - -start_client(openssl, Port, ClientOpts, _Config) -> - Cert = proplists:get_value(certfile, ClientOpts), - Key = proplists:get_value(keyfile, ClientOpts), - CA = proplists:get_value(cacertfile, ClientOpts), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), - Exe = "openssl", - Args = ["s_client", "-verify", "2", "-port", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", Cert, "-CAfile", CA, - "-key", Key, "-host","localhost", "-msg", "-debug"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - true = port_command(OpenSslPort, "Hello world"), - OpenSslPort; - -start_client(erlang, Port, ClientOpts, Config) -> - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - KeyEx = proplists:get_value(check_keyex, Config, false), - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, check_key_exchange_send_active, [KeyEx]}}, - {options, [{verify, verify_peer} | ClientOpts]}]). - - -start_client_ecc(erlang, Port, ClientOpts, Expect, ECCOpts, Config) -> - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, check_ecc, [client, Expect]}}, - {options, - ECCOpts ++ - [{verify, verify_peer} | ClientOpts]}]). - -start_client_ecc_error(erlang, Port, ClientOpts, ECCOpts, Config) -> - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, - ECCOpts ++ - [{verify, verify_peer} | ClientOpts]}]). - - -start_server(openssl, ServerOpts, _Config) -> - Cert = proplists:get_value(certfile, ServerOpts), - Key = proplists:get_value(keyfile, ServerOpts), - CA = proplists:get_value(cacertfile, ServerOpts), - Port = ssl_test_lib:inet_port(node()), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-verify", "2", "-cert", Cert, "-CAfile", CA, - "-key", Key, "-msg", "-debug"], - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - true = port_command(OpenSslPort, "Hello world"), - {OpenSslPort, Port}; -start_server(erlang, ServerOpts, Config) -> - {_, ServerNode, _} = ssl_test_lib:run_where(Config), - KeyEx = proplists:get_value(check_keyex, Config, false), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - check_key_exchange_send_active, - [KeyEx]}}, - {options, [{verify, verify_peer} | ServerOpts]}]), - {Server, ssl_test_lib:inet_port(Server)}. - -start_server_with_raw_key(erlang, ServerOpts, Config) -> - {_, ServerNode, _} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active, - []}}, - {options, - [{verify, verify_peer} | ServerOpts]}]), - {Server, ssl_test_lib:inet_port(Server)}. - -start_server_ecc(erlang, ServerOpts, Expect, ECCOpts, Config) -> - {_, ServerNode, _} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, check_ecc, [server, Expect]}}, - {options, - ECCOpts ++ - [{verify, verify_peer} | ServerOpts]}]), - {Server, ssl_test_lib:inet_port(Server)}. - -start_server_ecc_error(erlang, ServerOpts, ECCOpts, Config) -> - {_, ServerNode, _} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, - ECCOpts ++ - [{verify, verify_peer} | ServerOpts]}]), - {Server, ssl_test_lib:inet_port(Server)}. - -check_result(Server, erlang, Client, erlang) -> - ssl_test_lib:check_result(Server, ok, Client, ok); -check_result(Server, erlang, _, _) -> - ssl_test_lib:check_result(Server, ok); -check_result(_, _, Client, erlang) -> - ssl_test_lib:check_result(Client, ok); -check_result(_,openssl, _, openssl) -> - ok. - -openssl_check(erlang, Config) -> - Config; -openssl_check(_, Config) -> - TLSVersion = proplists:get_value(tls_version, Config), - case ssl_test_lib:check_sane_openssl_version(TLSVersion) of - true -> - Config; - false -> - {skip, "TLS version not supported by openssl"} - end. - -close(Port1, Port2) when is_port(Port1), is_port(Port2) -> - ssl_test_lib:close_port(Port1), - ssl_test_lib:close_port(Port2); -close(Port, Pid) when is_port(Port) -> - ssl_test_lib:close_port(Port), - ssl_test_lib:close(Pid); -close(Pid, Port) when is_port(Port) -> - ssl_test_lib:close_port(Port), - ssl_test_lib:close(Pid); -close(Client, Server) -> - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -supported_eccs(Opts) -> - ToCheck = proplists:get_value(eccs, Opts, []), - Supported = ssl:eccs(), - lists:all(fun(Curve) -> lists:member(Curve, Supported) end, ToCheck). - -check_ecc(SSL, Role, Expect) -> - {ok, Data} = ssl:connection_information(SSL), - case lists:keyfind(ecc, 1, Data) of - {ecc, {named_curve, Expect}} -> ok; - false when Expect =:= undefined -> ok; - Other -> {error, Role, Expect, Other} - end. - +mix_sign(Config) -> + {COpts0, SOpts0} = ssl_test_lib:make_mix_cert(Config), + COpts = ssl_test_lib:ssl_options(COpts0, Config), + SOpts = ssl_test_lib:ssl_options(SOpts0, Config), + ECDHE_ECDSA = + ssl:filter_cipher_suites(ssl:cipher_suites(default, 'tlsv1.2'), + [{key_exchange, fun(ecdhe_ecdsa) -> true; (_) -> false end}]), + ssl_test_lib:basic_test(COpts, [{ciphers, ECDHE_ECDSA} | SOpts], Config). diff --git a/lib/ssl/test/ssl_ECC_openssl_SUITE.erl b/lib/ssl/test/ssl_ECC_openssl_SUITE.erl new file mode 100644 index 0000000000..81a7dfd2da --- /dev/null +++ b/lib/ssl/test/ssl_ECC_openssl_SUITE.erl @@ -0,0 +1,218 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(ssl_ECC_openssl_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + case test_cases() of + [_|_] -> + all_groups(); + [] -> + [skip] + end. + +all_groups() -> + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'}]; + false -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}] + end. + +groups() -> + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{'tlsv1.2', [], [mix_sign | test_cases()]}, + {'tlsv1.1', [], test_cases()}, + {'tlsv1', [], test_cases()}, + {'dtlsv1.2', [], [mix_sign | test_cases()]}, + {'dtlsv1', [], test_cases()}]; + false -> + [{'tlsv1.2', [], [mix_sign | test_cases()]}, + {'tlsv1.1', [], test_cases()}, + {'tlsv1', [], test_cases()}] + end. + +test_cases()-> + cert_combinations(). + +cert_combinations() -> + lists:append(lists:map(fun({Name, Suites}) -> + case ssl_test_lib:openssl_filter(Name) of + [] -> + []; + [_|_] -> + Suites + end + end, [{"ECDH-ECDSA", server_ecdh_ecdsa()}, + {"ECDH-RSA", server_ecdh_rsa()}, + {"ECDHE-RSA", server_ecdhe_rsa()}, + {"ECDHE-ECDSA", server_ecdhe_ecdsa()} + ])). +server_ecdh_rsa() -> + [client_ecdh_rsa_server_ecdh_rsa, + client_ecdhe_rsa_server_ecdh_rsa, + client_ecdhe_ecdsa_server_ecdh_rsa]. + +server_ecdhe_rsa() -> + [client_ecdh_rsa_server_ecdhe_rsa, + client_ecdhe_rsa_server_ecdhe_rsa, + client_ecdhe_ecdsa_server_ecdhe_rsa]. + +server_ecdh_ecdsa() -> + [client_ecdh_ecdsa_server_ecdh_ecdsa, + client_ecdhe_rsa_server_ecdh_ecdsa, + client_ecdhe_ecdsa_server_ecdh_ecdsa]. + +server_ecdhe_ecdsa() -> + [client_ecdh_rsa_server_ecdhe_ecdsa, + client_ecdh_ecdsa_server_ecdhe_ecdsa, + client_ecdhe_ecdsa_server_ecdhe_ecdsa]. + +%%-------------------------------------------------------------------- +init_per_suite(Config0) -> + end_per_suite(Config0), + try crypto:start() of + ok -> + case ssl_test_lib:sufficient_crypto_support(cipher_ec) of + true -> + Config0; + false -> + {skip, "Openssl does not support ECC"} + end + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + application:stop(ssl), + application:stop(crypto). + +%%-------------------------------------------------------------------- +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + [{tls_version, GroupName}, + {server_type, erlang}, + {client_type, openssl} | ssl_test_lib:init_tls_version(GroupName, Config)]; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> + Config + end. + +end_per_group(GroupName, Config0) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + Config = ssl_test_lib:clean_tls_version(Config0), + proplists:delete(tls_version, Config); + false -> + Config0 + end. + +%%-------------------------------------------------------------------- +init_per_testcase(skip, Config) -> + Config; +init_per_testcase(TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + Version = proplists:get_value(tls_version, Config), + ct:log("Ciphers: ~p~n ", [ssl:cipher_suites(default, Version)]), + end_per_testcase(TestCase, Config), + ssl:start(), + ct:timetrap({seconds, 30}), + Config. + +end_per_testcase(_TestCase, Config) -> + application:stop(ssl), + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +skip(Config) when is_list(Config) -> + {skip, openssl_does_not_support_ECC}. + +%% Test diffrent certificate chain types, note that it is the servers +%% chain that affect what cipher suit that will be choosen + +%% ECDH_RSA +client_ecdh_rsa_server_ecdh_rsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdh_rsa_server_ecdh_rsa(Config). +client_ecdhe_rsa_server_ecdh_rsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdhe_rsa_server_ecdh_rsa(Config). +client_ecdhe_ecdsa_server_ecdh_rsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdhe_ecdsa_server_ecdh_rsa(Config). +%% ECDHE_RSA +client_ecdh_rsa_server_ecdhe_rsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdh_rsa_server_ecdhe_rsa(Config). +client_ecdhe_rsa_server_ecdhe_rsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdhe_rsa_server_ecdhe_rsa(Config). +client_ecdhe_ecdsa_server_ecdhe_rsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdhe_ecdsa_server_ecdhe_rsa(Config). +%% ECDH_ECDSA +client_ecdh_ecdsa_server_ecdh_ecdsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdh_ecdsa_server_ecdh_ecdsa(Config). +client_ecdhe_rsa_server_ecdh_ecdsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdhe_rsa_server_ecdh_ecdsa(Config). +client_ecdhe_ecdsa_server_ecdh_ecdsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdhe_ecdsa_server_ecdh_ecdsa(Config). +%% ECDHE_ECDSA +client_ecdh_rsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdh_rsa_server_ecdhe_ecdsa(Config). +client_ecdh_ecdsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdh_ecdsa_server_ecdhe_ecdsa(Config). +client_ecdhe_ecdsa_server_ecdhe_ecdsa(Config) when is_list(Config) -> + ssl_ECC:client_ecdhe_ecdsa_server_ecdhe_ecdsa(Config). + +mix_sign(Config) -> + {COpts0, SOpts0} = ssl_test_lib:make_mix_cert(Config), + COpts = ssl_test_lib:ssl_options(COpts0, Config), + SOpts = ssl_test_lib:ssl_options(SOpts0, Config), + ECDHE_ECDSA = + ssl:filter_cipher_suites(ssl:cipher_suites(default, 'tlsv1.2'), + [{key_exchange, fun(ecdhe_ecdsa) -> true; (_) -> false end}]), + ssl_test_lib:basic_test(COpts, [{ciphers, ECDHE_ECDSA} | SOpts], [{client_type, erlang}, + {server_type, openssl} | Config]). + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index ce62017a7e..0023c8656a 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -2427,7 +2427,7 @@ anonymous_cipher_suites()-> [{doc,"Test the anonymous ciphersuites"}]. anonymous_cipher_suites(Config) when is_list(Config) -> NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:anonymous_suites(NVersion), + Ciphers = ssl_test_lib:ecdh_dh_anonymous_suites(NVersion), run_suites(Ciphers, Config, anonymous). %%------------------------------------------------------------------- psk_cipher_suites() -> @@ -2522,7 +2522,7 @@ default_reject_anonymous(Config) when is_list(Config) -> Version = ssl_test_lib:protocol_version(Config), TLSVersion = ssl_test_lib:tls_version(Version), - [CipherSuite | _] = ssl_test_lib:anonymous_suites(TLSVersion), + [CipherSuite | _] = ssl_test_lib:ecdh_dh_anonymous_suites(TLSVersion), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, @@ -4036,17 +4036,16 @@ unordered_protocol_versions_server(Config) when is_list(Config) -> {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, connection_info_result, []}}, + {mfa, {?MODULE, protocol_info_result, []}}, {options, [{versions, ['tlsv1.1', 'tlsv1.2']} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, - {mfa, {?MODULE, connection_info_result, []}}, + {mfa, {?MODULE, protocol_info_result, []}}, {options, ClientOpts}]), - CipherSuite = first_rsa_suite(ssl:cipher_suites()), - ServerMsg = ClientMsg = {ok, {'tlsv1.2', CipherSuite}}, + ServerMsg = ClientMsg = {ok,'tlsv1.2'}, ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). %%-------------------------------------------------------------------- @@ -4061,18 +4060,16 @@ unordered_protocol_versions_client(Config) when is_list(Config) -> {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, connection_info_result, []}}, + {mfa, {?MODULE, protocol_info_result, []}}, {options, ServerOpts }]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, - {mfa, {?MODULE, connection_info_result, []}}, + {mfa, {?MODULE, protocol_info_result, []}}, {options, [{versions, ['tlsv1.1', 'tlsv1.2']} | ClientOpts]}]), - - CipherSuite = first_rsa_suite(ssl:cipher_suites()), - ServerMsg = ClientMsg = {ok, {'tlsv1.2', CipherSuite}}, + ServerMsg = ClientMsg = {ok, 'tlsv1.2'}, ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). %%-------------------------------------------------------------------- @@ -4709,38 +4706,39 @@ client_server_opts(#{key_exchange := KeyAlgo}, Config) when KeyAlgo == ecdh_rsa ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)}. run_suites(Ciphers, Config, Type) -> - NVersion = ssl_test_lib:protocol_version(Config, tuple), Version = ssl_test_lib:protocol_version(Config), ct:log("Running cipher suites ~p~n", [Ciphers]), {ClientOpts, ServerOpts} = case Type of rsa -> {ssl_test_lib:ssl_options(client_verification_opts, Config), - ssl_test_lib:ssl_options(server_verification_opts, Config)}; + [{ciphers, Ciphers} | + ssl_test_lib:ssl_options(server_verification_opts, Config)]}; dsa -> {ssl_test_lib:ssl_options(client_verification_opts, Config), - ssl_test_lib:ssl_options(server_dsa_opts, Config)}; + [{ciphers, Ciphers} | + ssl_test_lib:ssl_options(server_dsa_opts, Config)]}; anonymous -> %% No certs in opts! {ssl_test_lib:ssl_options(client_verification_opts, Config), - [{reuseaddr, true}, {ciphers, ssl_test_lib:anonymous_suites(NVersion)} | + [{ciphers, Ciphers} | ssl_test_lib:ssl_options([], Config)]}; psk -> {ssl_test_lib:ssl_options(client_psk, Config), - [{ciphers, ssl_test_lib:psk_suites(NVersion)} | + [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_psk, Config)]}; psk_with_hint -> {ssl_test_lib:ssl_options(client_psk, Config), - [{ciphers, ssl_test_lib:psk_suites(NVersion)} | + [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_psk_hint, Config) ]}; psk_anon -> {ssl_test_lib:ssl_options(client_psk, Config), - [{ciphers, ssl_test_lib:psk_anon_suites(NVersion)} | + [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_psk_anon, Config)]}; psk_anon_with_hint -> {ssl_test_lib:ssl_options(client_psk, Config), - [{ciphers, ssl_test_lib:psk_anon_suites(NVersion)} | + [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_psk_anon_hint, Config)]}; srp -> {ssl_test_lib:ssl_options(client_srp, Config), @@ -4753,7 +4751,8 @@ run_suites(Ciphers, Config, Type) -> ssl_test_lib:ssl_options(server_srp_dsa, Config)}; ecdsa -> {ssl_test_lib:ssl_options(client_verification_opts, Config), - ssl_test_lib:ssl_options(server_ecdsa_opts, Config)}; + [{ciphers, Ciphers} | + ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]}; ecdh_rsa -> {ssl_test_lib:ssl_options(client_verification_opts, Config), ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)}; @@ -4776,9 +4775,17 @@ run_suites(Ciphers, Config, Type) -> des_rsa -> {ssl_test_lib:ssl_options(client_verification_opts, Config), [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_verification_opts, Config)]} + ssl_test_lib:ssl_options(server_verification_opts, Config)]}; + chacha_rsa -> + {ssl_test_lib:ssl_options(client_verification_opts, Config), + [{ciphers, Ciphers} | + ssl_test_lib:ssl_options(server_verification_opts, Config)]}; + chacha_ecdsa -> + {ssl_test_lib:ssl_options(client_verification_opts, Config), + [{ciphers, Ciphers} | + ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]} end, - + ct:pal("ssl_test_lib:filter_suites(~p ~p) -> ~p ", [Ciphers, Version, ssl_test_lib:filter_suites(Ciphers, Version)]), Result = lists:map(fun(Cipher) -> cipher(Cipher, Version, Config, ClientOpts, ServerOpts) end, ssl_test_lib:filter_suites(Ciphers, Version)), @@ -4789,7 +4796,6 @@ run_suites(Ciphers, Config, Type) -> ct:log("Cipher suite errors: ~p~n", [Error]), ct:fail(cipher_suite_failed_see_test_case_log) end. - erlang_cipher_suite(Suite) when is_list(Suite)-> ssl_cipher:erl_suite_definition(ssl_cipher:openssl_suite(Suite)); erlang_cipher_suite(Suite) -> @@ -4845,6 +4851,11 @@ connection_information_result(Socket) -> connection_info_result(Socket) -> {ok, Info} = ssl:connection_information(Socket, [protocol, cipher_suite]), {ok, {proplists:get_value(protocol, Info), proplists:get_value(cipher_suite, Info)}}. + +protocol_info_result(Socket) -> + {ok, [{protocol, PVersion}]} = ssl:connection_information(Socket, [protocol]), + {ok, PVersion}. + version_info_result(Socket) -> {ok, [{version, Version}]} = ssl:connection_information(Socket, [version]), {ok, Version}. @@ -4967,21 +4978,6 @@ try_recv_active_once(Socket) -> {error, einval} = ssl:recv(Socket, 11), ok. -first_rsa_suite([{ecdhe_rsa, _, _} = Suite | _]) -> - Suite; -first_rsa_suite([{dhe_rsa, _, _} = Suite| _]) -> - Suite; -first_rsa_suite([{rsa, _, _} = Suite| _]) -> - Suite; -first_rsa_suite([{ecdhe_rsa, _, _, _} = Suite | _]) -> - Suite; -first_rsa_suite([{dhe_rsa, _, _, _} = Suite| _]) -> - Suite; -first_rsa_suite([{rsa, _, _, _} = Suite| _]) -> - Suite; -first_rsa_suite([_ | Rest]) -> - first_rsa_suite(Rest). - wait_for_send(Socket) -> %% Make sure TLS process processed send message event _ = ssl:connection_information(Socket). diff --git a/lib/ssl/test/ssl_engine_SUITE.erl b/lib/ssl/test/ssl_engine_SUITE.erl index 71891356e8..c348fa0a9c 100644 --- a/lib/ssl/test/ssl_engine_SUITE.erl +++ b/lib/ssl/test/ssl_engine_SUITE.erl @@ -90,12 +90,14 @@ end_per_testcase(_TestCase, Config) -> private_key(Config) when is_list(Config) -> ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "client_engine"]), ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "server_engine"]), + Ext = x509_test:extensions([{key_usage, [digitalSignature, keyEncipherment]}]), #{server_config := ServerConf, client_config := ClientConf} = GenCertData = public_key:pkix_test_data(#{server_chain => #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}], intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]], - peer => [{key, ssl_test_lib:hardcode_rsa_key(3)} + peer => [{extensions, Ext}, + {key, ssl_test_lib:hardcode_rsa_key(3)} ]}, client_chain => #{root => [{key, ssl_test_lib:hardcode_rsa_key(4)}], @@ -117,8 +119,29 @@ private_key(Config) when is_list(Config) -> EngineServerConf = [{key, #{algorithm => rsa, engine => Engine, key_id => ServerKey}} | proplists:delete(key, ServerConf)], + + EngineFileClientConf = [{key, #{algorithm => rsa, + engine => Engine, + key_id => ClientKey}} | + proplists:delete(keyfile, FileClientConf)], + + EngineFileServerConf = [{key, #{algorithm => rsa, + engine => Engine, + key_id => ServerKey}} | + proplists:delete(keyfile, FileServerConf)], + %% Test with engine test_tls_connection(EngineServerConf, EngineClientConf, Config), + + %% Test with engine and rsa keyexchange + RSASuites = all_kex_rsa_suites([{tls_version, 'tlsv1.2'} | Config]), + + test_tls_connection([{ciphers, RSASuites}, {versions, ['tlsv1.2']} | EngineServerConf], + [{ciphers, RSASuites}, {versions, ['tlsv1.2']} | EngineClientConf], Config), + + %% Test with engine and present file arugments + test_tls_connection(EngineFileServerConf, EngineFileClientConf, Config), + %% Test that sofware fallback is available test_tls_connection(ServerConf, [{reuse_sessions, false} |ClientConf], Config). @@ -145,3 +168,8 @@ test_tls_connection(ServerConf, ClientConf, Config) -> ssl_test_lib:check_result(Server, ok, Client, ok), ssl_test_lib:close(Server), ssl_test_lib:close(Client). + +all_kex_rsa_suites(Config) -> + Version = proplists:get_value(tls_version, Config), + All = ssl:cipher_suites(all, Version), + ssl:filter_cipher_suites(All,[{key_exchange, fun(rsa) -> true;(_) -> false end}]). diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl index 9658cb5f56..c492c54066 100644 --- a/lib/ssl/test/ssl_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_handshake_SUITE.erl @@ -157,7 +157,7 @@ decode_single_hello_sni_extension_correctly(_Config) -> Exts = Decoded. decode_empty_server_sni_correctly(_Config) -> - Exts = #hello_extensions{sni = ""}, + Exts = #hello_extensions{sni = #sni{hostname = ""}}, SNI = <<?UINT16(?SNI_EXT),?UINT16(0)>>, Decoded = ssl_handshake:decode_hello_extensions(SNI), Exts = Decoded. diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index f9cc976815..3e6c5e8b8a 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -502,6 +502,17 @@ default_cert_chain_conf() -> %% Use only default options [[],[],[]]. +gen_conf(mix, mix, UserClient, UserServer) -> + ClientTag = conf_tag("client"), + ServerTag = conf_tag("server"), + + DefaultClient = default_cert_chain_conf(), + DefaultServer = default_cert_chain_conf(), + + ClientConf = merge_chain_spec(UserClient, DefaultClient, []), + ServerConf = merge_chain_spec(UserServer, DefaultServer, []), + + new_format([{ClientTag, ClientConf}, {ServerTag, ServerConf}]); gen_conf(ClientChainType, ServerChainType, UserClient, UserServer) -> ClientTag = conf_tag("client"), ServerTag = conf_tag("server"), @@ -595,6 +606,32 @@ merge_spec(User, Default, [Conf | Rest], Acc) -> merge_spec(User, Default, Rest, [{Conf, Value} | Acc]) end. +make_mix_cert(Config) -> + Ext = x509_test:extensions([{key_usage, [digitalSignature]}]), + Digest = {digest, appropriate_sha(crypto:supports())}, + CurveOid = hd(tls_v1:ecc_curves(0)), + ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "mix"]), + ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "mix"]), + ClientChain = [[Digest, {key, {namedCurve, CurveOid}}], + [Digest, {key, hardcode_rsa_key(1)}], + [Digest, {key, {namedCurve, CurveOid}}, {extensions, Ext}] + ], + ServerChain = [[Digest, {key, {namedCurve, CurveOid}}], + [Digest, {key, hardcode_rsa_key(2)}], + [Digest, {key, {namedCurve, CurveOid}},{extensions, Ext}] + ], + ClientChainType =ServerChainType = mix, + CertChainConf = gen_conf(ClientChainType, ServerChainType, ClientChain, ServerChain), + ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(ClientChainType)]), + ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(ServerChainType)]), + GenCertData = public_key:pkix_test_data(CertChainConf), + [{server_config, ServerConf}, + {client_config, ClientConf}] = + x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase), + {[{verify, verify_peer} | ClientConf], + [{reuseaddr, true}, {verify, verify_peer} | ServerConf] + }. + make_ecdsa_cert(Config) -> CryptoSupport = crypto:supports(), case proplists:get_bool(ecdsa, proplists:get_value(public_keys, CryptoSupport)) of @@ -861,6 +898,159 @@ accepters(Acc, N) -> {accepter, _, Server} -> accepters([Server| Acc], N-1) end. +basic_test(COpts, SOpts, Config) -> + SType = proplists:get_value(server_type, Config), + CType = proplists:get_value(client_type, Config), + {Server, Port} = start_server(SType, SOpts, Config), + Client = start_client(CType, Port, COpts, Config), + gen_check_result(Server, SType, Client, CType), + stop(Server, Client). + +ecc_test(Expect, COpts, SOpts, CECCOpts, SECCOpts, Config) -> + {Server, Port} = start_server_ecc(erlang, SOpts, Expect, SECCOpts, Config), + Client = start_client_ecc(erlang, Port, COpts, Expect, CECCOpts, Config), + check_result(Server, ok, Client, ok), + stop(Server, Client). + +ecc_test_error(COpts, SOpts, CECCOpts, SECCOpts, Config) -> + {Server, Port} = start_server_ecc_error(erlang, SOpts, SECCOpts, Config), + Client = start_client_ecc_error(erlang, Port, COpts, CECCOpts, Config), + Error = {error, {tls_alert, "insufficient security"}}, + check_result(Server, Error, Client, Error). + + +start_client(openssl, Port, ClientOpts, Config) -> + Cert = proplists:get_value(certfile, ClientOpts), + Key = proplists:get_value(keyfile, ClientOpts), + CA = proplists:get_value(cacertfile, ClientOpts), + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_client", "-verify", "2", "-port", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-cert", Cert, "-CAfile", CA, + "-key", Key, "-host","localhost", "-msg", "-debug"], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + true = port_command(OpenSslPort, "Hello world"), + OpenSslPort; + +start_client(erlang, Port, ClientOpts, Config) -> + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + KeyEx = proplists:get_value(check_keyex, Config, false), + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, check_key_exchange_send_active, [KeyEx]}}, + {options, [{verify, verify_peer} | ClientOpts]}]). + + +start_client_ecc(erlang, Port, ClientOpts, Expect, ECCOpts, Config) -> + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, check_ecc, [client, Expect]}}, + {options, + ECCOpts ++ + [{verify, verify_peer} | ClientOpts]}]). + +start_client_ecc_error(erlang, Port, ClientOpts, ECCOpts, Config) -> + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, + ECCOpts ++ + [{verify, verify_peer} | ClientOpts]}]). + + +start_server(openssl, ServerOpts, Config) -> + Cert = proplists:get_value(certfile, ServerOpts), + Key = proplists:get_value(keyfile, ServerOpts), + CA = proplists:get_value(cacertfile, ServerOpts), + Port = inet_port(node()), + Version = protocol_version(Config), + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-verify", "2", "-cert", Cert, "-CAfile", CA, + "-key", Key, "-msg", "-debug"], + OpenSslPort = portable_open_port(Exe, Args), + true = port_command(OpenSslPort, "Hello world"), + {OpenSslPort, Port}; +start_server(erlang, ServerOpts, Config) -> + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + KeyEx = proplists:get_value(check_keyex, Config, false), + Server = start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + check_key_exchange_send_active, + [KeyEx]}}, + {options, [{verify, verify_peer} | ServerOpts]}]), + {Server, inet_port(Server)}. + +start_server_with_raw_key(erlang, ServerOpts, Config) -> + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + Server = start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + send_recv_result_active, + []}}, + {options, + [{verify, verify_peer} | ServerOpts]}]), + {Server, inet_port(Server)}. + +start_server_ecc(erlang, ServerOpts, Expect, ECCOpts, Config) -> + {_, ServerNode, _} = run_where(Config), + Server = start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, check_ecc, [server, Expect]}}, + {options, + ECCOpts ++ + [{verify, verify_peer} | ServerOpts]}]), + {Server, inet_port(Server)}. + +start_server_ecc_error(erlang, ServerOpts, ECCOpts, Config) -> + {_, ServerNode, _} = run_where(Config), + Server = start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, + ECCOpts ++ + [{verify, verify_peer} | ServerOpts]}]), + {Server, inet_port(Server)}. + +gen_check_result(Server, erlang, Client, erlang) -> + check_result(Server, ok, Client, ok); +gen_check_result(Server, erlang, _, _) -> + check_result(Server, ok); +gen_check_result(_, _, Client, erlang) -> + check_result(Client, ok); +gen_check_result(_,openssl, _, openssl) -> + ok. + +stop(Port1, Port2) when is_port(Port1), is_port(Port2) -> + close_port(Port1), + close_port(Port2); +stop(Port, Pid) when is_port(Port) -> + close_port(Port), + close(Pid); +stop(Pid, Port) when is_port(Port) -> + close_port(Port), + close(Pid); +stop(Client, Server) -> + close(Server), + close(Client). + +supported_eccs(Opts) -> + ToCheck = proplists:get_value(eccs, Opts, []), + Supported = ssl:eccs(), + lists:all(fun(Curve) -> lists:member(Curve, Supported) end, ToCheck). + +check_ecc(SSL, Role, Expect) -> + {ok, Data} = ssl:connection_information(SSL), + case lists:keyfind(ecc, 1, Data) of + {ecc, {named_curve, Expect}} -> ok; + Other -> {error, Role, Expect, Other} + end. inet_port(Pid) when is_pid(Pid)-> receive @@ -1024,8 +1214,16 @@ string_regex_filter(Str, Search) when is_list(Str) -> string_regex_filter(_Str, _Search) -> false. -anonymous_suites(Version) -> - ssl:filter_cipher_suites([ssl_cipher:suite_definition(S) || S <- ssl_cipher:anonymous_suites(Version)],[]). +ecdh_dh_anonymous_suites(Version) -> + ssl:filter_cipher_suites([ssl_cipher:suite_definition(S) || S <- ssl_cipher:anonymous_suites(Version)], + [{key_exchange, + fun(dh_anon) -> + true; + (ecdh_anon) -> + true; + (_) -> + false + end}]). psk_suites(Version) -> ssl:filter_cipher_suites([ssl_cipher:suite_definition(S) || S <- ssl_cipher:psk_suites(Version)], []). @@ -1181,10 +1379,7 @@ sufficient_crypto_support(Version) when Version == 'tlsv1.2'; Version == 'dtlsv1.2' -> CryptoSupport = crypto:supports(), proplists:get_bool(sha256, proplists:get_value(hashs, CryptoSupport)); -sufficient_crypto_support(Group) when Group == ciphers_ec; %% From ssl_basic_SUITE - Group == erlang_server; %% From ssl_ECC_SUITE - Group == erlang_client; %% From ssl_ECC_SUITE - Group == erlang -> %% From ssl_ECC_SUITE +sufficient_crypto_support(cipher_ec) -> CryptoSupport = crypto:supports(), proplists:get_bool(ecdh, proplists:get_value(public_keys, CryptoSupport)); sufficient_crypto_support(_) -> @@ -1193,16 +1388,36 @@ sufficient_crypto_support(_) -> check_key_exchange_send_active(Socket, false) -> send_recv_result_active(Socket); check_key_exchange_send_active(Socket, KeyEx) -> - {ok, [{cipher_suite, Suite}]} = ssl:connection_information(Socket, [cipher_suite]), - true = check_key_exchange(Suite, KeyEx), + {ok, Info} = + ssl:connection_information(Socket, [cipher_suite, protocol]), + Suite = proplists:get_value(cipher_suite, Info), + Version = proplists:get_value(protocol, Info), + true = check_key_exchange(Suite, KeyEx, Version), send_recv_result_active(Socket). -check_key_exchange({KeyEx,_, _}, KeyEx) -> +check_key_exchange({KeyEx,_, _}, KeyEx, _) -> + ct:pal("Kex: ~p", [KeyEx]), + true; +check_key_exchange({KeyEx,_,_,_}, KeyEx, _) -> + ct:pal("Kex: ~p", [KeyEx]), + true; +check_key_exchange(KeyEx1, KeyEx2, Version) -> + ct:pal("Kex: ~p ~p", [KeyEx1, KeyEx2]), + case Version of + 'tlsv1.2' -> + v_1_2_check(element(1, KeyEx1), KeyEx2); + 'dtlsv1.2' -> + v_1_2_check(element(1, KeyEx1), KeyEx2); + _ -> + ct:pal("Negotiated ~p Expected ~p", [KeyEx1, KeyEx2]), + false + end. + +v_1_2_check(ecdh_ecdsa, ecdh_rsa) -> true; -check_key_exchange({KeyEx,_,_,_}, KeyEx) -> +v_1_2_check(ecdh_rsa, ecdh_ecdsa) -> true; -check_key_exchange(KeyEx1, KeyEx2) -> - ct:pal("Negotiated ~p Expected ~p", [KeyEx1, KeyEx2]), +v_1_2_check(_, _) -> false. send_recv_result_active(Socket) -> @@ -1326,12 +1541,60 @@ openssl_dsa_support() -> true end. +%% Acctual support is tested elsewhere, this is to exclude some LibreSSL and OpenSSL versions +openssl_sane_dtls() -> + case os:cmd("openssl version") of + "OpenSSL 0." ++ _ -> + false; + "OpenSSL 1.0.1s-freebsd" ++ _ -> + false; + "OpenSSL 1.0.2k-freebsd" ++ _ -> + false; + "OpenSSL 1.0.2d" ++ _ -> + false; + "OpenSSL 1.0.2n" ++ _ -> + false; + "OpenSSL 1.0.0" ++ _ -> + false; + "OpenSSL" ++ _ -> + true; + "LibreSSL 2.7" ++ _ -> + true; + _ -> + false + end. +openssl_sane_client_cert() -> + case os:cmd("openssl version") of + "LibreSSL 2.5.2" ++ _ -> + true; + "LibreSSL 2.4" ++ _ -> + false; + "LibreSSL 2.3" ++ _ -> + false; + "LibreSSL 2.1" ++ _ -> + false; + "LibreSSL 2.0" ++ _ -> + false; + "LibreSSL 2.0" ++ _ -> + false; + "OpenSSL 1.0.1s-freebsd" -> + false; + "OpenSSL 1.0.0" ++ _ -> + false; + _ -> + true + end. + check_sane_openssl_version(Version) -> case supports_ssl_tls_version(Version) of true -> case {Version, os:cmd("openssl version")} of {'sslv3', "OpenSSL 1.0.2" ++ _} -> false; + {'dtlsv1', _} -> + not is_fips(openssl); + {'dtlsv1.2', _} -> + not is_fips(openssl); {_, "OpenSSL 1.0.2" ++ _} -> true; {_, "OpenSSL 1.0.1" ++ _} -> @@ -1340,7 +1603,7 @@ check_sane_openssl_version(Version) -> false; {'tlsv1.1', "OpenSSL 1.0.0" ++ _} -> false; - {'dtlsv1.2', "OpenSSL 1.0.0" ++ _} -> + {'dtlsv1.2', "OpenSSL 1.0.2" ++ _} -> false; {'dtlsv1', "OpenSSL 1.0.0" ++ _} -> false; @@ -1675,4 +1938,3 @@ hardcode_dsa_key(3) -> g = 20302424198893709525243209250470907105157816851043773596964076323184805650258390738340248469444700378962907756890306095615785481696522324901068493502141775433048117442554163252381401915027666416630898618301033737438756165023568220631119672502120011809327566543827706483229480417066316015458225612363927682579, y = 48598545580251057979126570873881530215432219542526130654707948736559463436274835406081281466091739849794036308281564299754438126857606949027748889019480936572605967021944405048011118039171039273602705998112739400664375208228641666852589396502386172780433510070337359132965412405544709871654840859752776060358, x = 1457508827177594730669011716588605181448418352823}. - diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl index dcdea6beb5..9b0ae94ef2 100644 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl @@ -37,25 +37,42 @@ %%-------------------------------------------------------------------- all() -> - [ - {group, basic}, - {group, 'tlsv1.2'}, - {group, 'tlsv1.1'}, - {group, 'tlsv1'}, - {group, 'sslv3'}, - {group, 'dtlsv1.2'}, - {group, 'dtlsv1'} - ]. + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{group, basic}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'}]; + false -> + [{group, basic}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}] + end. groups() -> - [{basic, [], basic_tests()}, - {'tlsv1.2', [], all_versions_tests() ++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, - {'tlsv1.1', [], all_versions_tests() ++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, - {'tlsv1', [], all_versions_tests()++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, - {'sslv3', [], all_versions_tests()}, - {'dtlsv1.2', [], dtls_all_versions_tests()}, - {'dtlsv1', [], dtls_all_versions_tests()} - ]. + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{basic, [], basic_tests()}, + {'tlsv1.2', [], all_versions_tests() ++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, + {'tlsv1.1', [], all_versions_tests() ++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, + {'tlsv1', [], all_versions_tests()++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, + {'sslv3', [], all_versions_tests()}, + {'dtlsv1.2', [], dtls_all_versions_tests()}, + {'dtlsv1', [], dtls_all_versions_tests()} + ]; + false -> + [{basic, [], basic_tests()}, + {'tlsv1.2', [], all_versions_tests() ++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, + {'tlsv1.1', [], all_versions_tests() ++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, + {'tlsv1', [], all_versions_tests()++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, + {'sslv3', [], all_versions_tests()} + ] + end. basic_tests() -> [basic_erlang_client_openssl_server, @@ -86,9 +103,20 @@ all_versions_tests() -> expired_session, ssl2_erlang_server_openssl_client ]. + dtls_all_versions_tests() -> - [ - erlang_client_openssl_server, + case ssl_test_lib:openssl_sane_client_cert() of + true -> + [erlang_server_openssl_client_client_cert, + erlang_client_openssl_server_no_server_ca_cert, + erlang_client_openssl_server_client_cert + | dtls_all_versions_tests_2()]; + false -> + dtls_all_versions_tests_2() + end. + +dtls_all_versions_tests_2() -> + [erlang_client_openssl_server, erlang_server_openssl_client, erlang_client_openssl_server_dsa_cert, erlang_server_openssl_client_dsa_cert, @@ -99,12 +127,8 @@ dtls_all_versions_tests() -> erlang_client_openssl_server_renegotiate, erlang_client_openssl_server_nowrap_seqnum, erlang_server_openssl_client_nowrap_seqnum, - erlang_client_openssl_server_no_server_ca_cert, - erlang_client_openssl_server_client_cert, - erlang_server_openssl_client_client_cert, ciphers_rsa_signed_certs, ciphers_dsa_signed_certs - %%erlang_client_bad_openssl_server, %%expired_session ]. @@ -566,7 +590,7 @@ erlang_client_openssl_server_anon(Config) when is_list(Config) -> ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), ClientOpts = ssl_test_lib:ssl_options(client_anon_opts, Config), VersionTuple = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:anonymous_suites(VersionTuple), + Ciphers = ssl_test_lib:ecdh_dh_anonymous_suites(VersionTuple), {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -609,7 +633,7 @@ erlang_server_openssl_client_anon(Config) when is_list(Config) -> process_flag(trap_exit, true), ServerOpts = ssl_test_lib:ssl_options(server_anon_opts, Config), VersionTuple = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:anonymous_suites(VersionTuple), + Ciphers = ssl_test_lib:ecdh_dh_anonymous_suites(VersionTuple), {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -643,7 +667,7 @@ erlang_server_openssl_client_anon(Config) when is_list(Config) -> process_flag(trap_exit, true), ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), VersionTuple = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:anonymous_suites(VersionTuple), + Ciphers = ssl_test_lib:ecdh_dh_anonymous_suites(VersionTuple), {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index eb85a55717..1280efa77c 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -1 +1 @@ -SSL_VSN = 8.2.6 +SSL_VSN = 8.2.6.4 diff --git a/lib/stdlib/doc/src/lists.xml b/lib/stdlib/doc/src/lists.xml index 7efafedc82..55227aaee5 100644 --- a/lib/stdlib/doc/src/lists.xml +++ b/lib/stdlib/doc/src/lists.xml @@ -838,14 +838,6 @@ splitwith(Pred, List) -> > <input>lists:subtract("123212", "212").</input> "312".</pre> <p><c>lists:subtract(A, B)</c> is equivalent to <c>A -- B</c>.</p> - <warning> - <p>The complexity of <c>lists:subtract(A, B)</c> is proportional to - <c>length(A)*length(B)</c>, meaning that it is very slow if both - <c>A</c> and <c>B</c> are long lists. (If both lists are long, it - is a much better choice to use ordered lists and - <seealso marker="ordsets#subtract/2"> - <c>ordsets:subtract/2</c></seealso>.</p> - </warning> </desc> </func> diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml index e26c4aba74..3c7c8bf400 100644 --- a/lib/stdlib/doc/src/notes.xml +++ b/lib/stdlib/doc/src/notes.xml @@ -31,6 +31,21 @@ </header> <p>This document describes the changes made to the STDLIB application.</p> +<section><title>STDLIB 3.4.5.1</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p>List subtraction (The <c>--</c> operator) will now + yield properly on large inputs.</p> + <p> + Own Id: OTP-15371</p> + </item> + </list> + </section> + +</section> + <section><title>STDLIB 3.4.5</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl index 1a8260b041..5b7a4eca55 100644 --- a/lib/stdlib/test/ets_SUITE.erl +++ b/lib/stdlib/test/ets_SUITE.erl @@ -41,7 +41,7 @@ -export([t_delete_object/1, t_init_table/1, t_whitebox/1, select_bound_chunk/1, t_delete_all_objects/1, t_insert_list/1, t_test_ms/1, - t_select_delete/1,t_select_replace/1,t_ets_dets/1]). + t_select_delete/1,t_select_replace/1,t_select_replace_next_bug/1,t_ets_dets/1]). -export([ordered/1, ordered_match/1, interface_equality/1, fixtable_next/1, fixtable_insert/1, rename/1, rename_unnamed/1, evil_rename/1, @@ -58,6 +58,7 @@ -export([otp_5340/1]). -export([otp_6338/1]). -export([otp_6842_select_1000/1]). +-export([select_mbuf_trapping/1]). -export([otp_7665/1]). -export([meta_wb/1]). -export([grow_shrink/1, grow_pseudo_deleted/1, shrink_pseudo_deleted/1]). @@ -122,9 +123,11 @@ all() -> select_bound_chunk, t_init_table, t_whitebox, t_delete_all_objects, t_insert_list, t_test_ms, t_select_delete, t_select_replace, + t_select_replace_next_bug, t_ets_dets, memory, t_select_reverse, t_bucket_disappears, select_fail, t_insert_new, t_repair_continuation, otp_5340, otp_6338, otp_6842_select_1000, otp_7665, + select_mbuf_trapping, otp_8732, meta_wb, grow_shrink, grow_pseudo_deleted, shrink_pseudo_deleted, {group, meta_smp}, smp_insert, smp_fixed_delete, smp_unfix_fix, smp_select_replace, @@ -1373,6 +1376,25 @@ t_select_replace(Config) when is_list(Config) -> verify_etsmem(EtsMem). +%% OTP-15346: Bug caused select_replace of bound key to corrupt static stack +%% used by ets:next and ets:prev. +t_select_replace_next_bug(Config) when is_list(Config) -> + T = ets:new(k, [ordered_set]), + [ets:insert(T, {I, value}) || I <- lists:seq(1,10)], + 1 = ets:first(T), + + %% Make sure select_replace does not leave pointer + %% to deallocated {2,value} in static stack. + MS = [{{2,value}, [], [{{2,"new_value"}}]}], + 1 = ets:select_replace(T, MS), + + %% This would crash or give wrong result at least on DEBUG emulator + %% where deallocated memory is overwritten. + 2 = ets:next(T, 1), + + ets:delete(T). + + %% Test that partly bound keys gives faster matches. partly_bound(Config) when is_list(Config) -> case os:type() of @@ -5022,6 +5044,61 @@ otp_6338(Config) when is_list(Config) -> [[4839,recv]] = ets:match(T,{[{sbm,ppb2_bs12@blade_0_8},'$1'],'$2'}), ets:delete(T). +%% OTP-15660: Verify select not doing excessive trapping +%% when process have mbuf heap fragments. +select_mbuf_trapping(Config) when is_list(Config) -> + select_mbuf_trapping_do(set), + select_mbuf_trapping_do(ordered_set). + +select_mbuf_trapping_do(Type) -> + T = ets:new(xxx, [Type]), + NKeys = 50, + [ets:insert(T, {K, value}) || K <- lists:seq(1,NKeys)], + + {priority, Prio} = process_info(self(), priority), + Tracee = self(), + [SchedTracer] + = start_loopers(1, Prio, + fun (SC) -> + receive + {trace, Tracee, out, _} -> + SC+1; + done -> + Tracee ! {schedule_count, SC}, + exit(normal) + end + end, + 0), + + erlang:garbage_collect(), + 1 = erlang:trace(self(), true, [running,{tracer,SchedTracer}]), + + %% Artificially create an mbuf heap fragment + MbufTerm = "Frag me up", + MbufTerm = erts_debug:set_internal_state(mbuf, MbufTerm), + + Keys = ets:select(T, [{{'$1', value}, [], ['$1']}]), + NKeys = length(Keys), + + 1 = erlang:trace(self(), false, [running]), + Ref = erlang:trace_delivered(Tracee), + receive + {trace_delivered, Tracee, Ref} -> + SchedTracer ! done + end, + receive + {schedule_count, N} -> + io:format("~p context switches: ~p", [Type,N]), + if + N < 3 -> ok; + true -> ct:fail(failed) + end + end, + true = ets:delete(T), + ok. + + + %% Elements could come in the wrong order in a bag if a rehash occurred. otp_5340(Config) when is_list(Config) -> repeat_for_opts(fun otp_5340_do/1). diff --git a/lib/stdlib/test/lists_SUITE.erl b/lib/stdlib/test/lists_SUITE.erl index 7c99244b36..c380b3bba1 100644 --- a/lib/stdlib/test/lists_SUITE.erl +++ b/lib/stdlib/test/lists_SUITE.erl @@ -2597,6 +2597,13 @@ subtract(Config) when is_list(Config) -> {'EXIT',_} = (catch sub([a|b], [])), {'EXIT',_} = (catch sub([a|b], [a])), + %% Trapping, both crashing and otherwise. + [sub_trapping(N) || N <- lists:seq(0, 18)], + + %% The current implementation chooses which algorithm to use based on + %% certain thresholds, and we need proper coverage for all corner cases. + [sub_thresholds(N) || N <- lists:seq(0, 32)], + ok. sub_non_matching(A, B) -> @@ -2606,6 +2613,41 @@ sub(A, B) -> Res = A -- B, Res = lists:subtract(A, B). +sub_trapping(N) -> + List = lists:duplicate(N + (1 bsl N), gurka), + ImproperList = List ++ crash, + + {'EXIT',_} = (catch sub_trapping_1(ImproperList, [])), + {'EXIT',_} = (catch sub_trapping_1(List, ImproperList)), + + List = List -- lists:duplicate(N + (1 bsl N), gaffel), + ok = sub_trapping_1(List, []). + +sub_trapping_1([], _) -> ok; +sub_trapping_1(L, R) -> sub_trapping_1(L -- R, [gurka | R]). + +sub_thresholds(N) -> + %% This needs to be long enough to cause trapping. + OtherLen = 1 bsl 18, + Other = lists:seq(0, OtherLen - 1), + + Disjoint = lists:seq(-N, -1), + Subset = lists:seq(1, N), + + %% LHS is disjoint from RHS, so all elements must be retained. + Disjoint = Disjoint -- Other, + + %% LHS is covered by RHS, so all elements must be removed. + [] = Subset -- Other, + + %% RHS is disjoint from LHS, so all elements must be retained. + Other = Other -- Disjoint, + + %% RHS is covered by LHS, so N elements must be removed. + N = OtherLen - length(Other -- Subset), + + ok. + %% Test lists:droplast/1 droplast(Config) when is_list(Config) -> [] = lists:droplast([x]), diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk index 09a4d6fb50..34ba8c083d 100644 --- a/lib/stdlib/vsn.mk +++ b/lib/stdlib/vsn.mk @@ -1 +1 @@ -STDLIB_VSN = 3.4.5 +STDLIB_VSN = 3.4.5.1 diff --git a/lib/syntax_tools/doc/src/notes.xml b/lib/syntax_tools/doc/src/notes.xml index bd2bcde2c2..eb3d4591d7 100644 --- a/lib/syntax_tools/doc/src/notes.xml +++ b/lib/syntax_tools/doc/src/notes.xml @@ -32,6 +32,20 @@ <p>This document describes the changes made to the Syntax_Tools application.</p> +<section><title>Syntax_Tools 2.1.4.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> Fix a bug regarding reverting map types. </p> + <p> + Own Id: OTP-15098 Aux Id: ERIERL-177 </p> + </item> + </list> + </section> + +</section> + <section><title>Syntax_Tools 2.1.4</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index 9b2b503762..5bd67b3806 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -5324,7 +5324,7 @@ revert_map_type_assoc(Node) -> Pos = get_pos(Node), Name = map_type_assoc_name(Node), Value = map_type_assoc_value(Node), - {type, Pos, map_type_assoc, [Name, Value]}. + {type, Pos, map_field_assoc, [Name, Value]}. %% ===================================================================== @@ -5382,7 +5382,7 @@ revert_map_type_exact(Node) -> Pos = get_pos(Node), Name = map_type_exact_name(Node), Value = map_type_exact_value(Node), - {type, Pos, map_type_exact, [Name, Value]}. + {type, Pos, map_field_exact, [Name, Value]}. %% ===================================================================== diff --git a/lib/syntax_tools/test/syntax_tools_SUITE.erl b/lib/syntax_tools/test/syntax_tools_SUITE.erl index ae2c67c03e..c8e6448d37 100644 --- a/lib/syntax_tools/test/syntax_tools_SUITE.erl +++ b/lib/syntax_tools/test/syntax_tools_SUITE.erl @@ -24,13 +24,14 @@ %% Test cases -export([app_test/1,appup_test/1,smoke_test/1,revert/1,revert_map/1, + revert_map_type/1, t_abstract_type/1,t_erl_parse_type/1,t_epp_dodger/1, t_comment_scan/1,t_igor/1,t_erl_tidy/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> - [app_test,appup_test,smoke_test,revert,revert_map, + [app_test,appup_test,smoke_test,revert,revert_map,revert_map_type, t_abstract_type,t_erl_parse_type,t_epp_dodger, t_comment_scan,t_igor,t_erl_tidy]. @@ -121,7 +122,26 @@ revert_map(Config) when is_list(Config) -> {map_field_assoc,{atom,17,name},{var,18,'Value'}}}]), ?t:timetrap_cancel(Dog). - +%% Testing bug fix for reverting map_field_assoc in types +revert_map_type(Config) when is_list(Config) -> + Dog = ?t:timetrap(?t:minutes(1)), + Form1 = {attribute,4,record, + {state, + [{typed_record_field, + {record_field,5,{atom,5,x}}, + {type,5,map, + [{type,5,map_field_exact,[{atom,5,y},{atom,5,z}]}]}}]}}, + Mapped1 = erl_syntax_lib:map(fun(X) -> X end, Form1), + Form1 = erl_syntax:revert(Mapped1), + Form2 = {attribute,4,record, + {state, + [{typed_record_field, + {record_field,5,{atom,5,x}}, + {type,5,map, + [{type,5,map_field_assoc,[{atom,5,y},{atom,5,z}]}]}}]}}, + Mapped2 = erl_syntax_lib:map(fun(X) -> X end, Form2), + Form2 = erl_syntax:revert(Mapped2), + ?t:timetrap_cancel(Dog). %% api tests diff --git a/lib/syntax_tools/vsn.mk b/lib/syntax_tools/vsn.mk index 8d37c40742..42f32305e2 100644 --- a/lib/syntax_tools/vsn.mk +++ b/lib/syntax_tools/vsn.mk @@ -1 +1 @@ -SYNTAX_TOOLS_VSN = 2.1.4 +SYNTAX_TOOLS_VSN = 2.1.4.1 diff --git a/lib/xmerl/doc/src/notes.xml b/lib/xmerl/doc/src/notes.xml index f62a8dc53d..aca98cc8c2 100644 --- a/lib/xmerl/doc/src/notes.xml +++ b/lib/xmerl/doc/src/notes.xml @@ -32,6 +32,21 @@ <p>This document describes the changes made to the Xmerl application.</p> +<section><title>Xmerl 1.3.16.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>The charset detection parsing crash in some cases when + the XML directive is not syntactic correct.</p> + <p> + Own Id: OTP-15492 Aux Id: ERIERL-283 </p> + </item> + </list> + </section> + +</section> + <section><title>Xmerl 1.3.16</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/xmerl/src/xmerl_sax_parser.erl b/lib/xmerl/src/xmerl_sax_parser.erl index e383c4c349..fe836fd8cd 100644 --- a/lib/xmerl/src/xmerl_sax_parser.erl +++ b/lib/xmerl/src/xmerl_sax_parser.erl @@ -1,8 +1,8 @@ %%-------------------------------------------------------------------- %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2008-2017. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. +%% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at @@ -14,13 +14,13 @@ %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -%% +%% %% %CopyrightEnd% %%---------------------------------------------------------------------- %% File : xmerl_sax_parser.erl %% Description : XML SAX parse API module. %% -%% Created : 4 Jun 2008 +%% Created : 4 Jun 2008 %%---------------------------------------------------------------------- -module(xmerl_sax_parser). @@ -72,9 +72,9 @@ file(Name,Options) -> CL = filename:absname(Dir), File = filename:basename(Name), ContinuationFun = fun default_continuation_cb/1, - Res = stream(<<>>, + Res = stream(<<>>, [{continuation_fun, ContinuationFun}, - {continuation_state, FD}, + {continuation_state, FD}, {current_location, CL}, {entity, File} |Options], @@ -101,39 +101,39 @@ stream(Xml, Options, InputType) when is_list(Xml), is_list(Options) -> State = parse_options(Options, initial_state()), case State#xmerl_sax_parser_state.file_type of dtd -> - xmerl_sax_parser_list:parse_dtd(Xml, + xmerl_sax_parser_list:parse_dtd(Xml, State#xmerl_sax_parser_state{encoding = list, input_type = InputType}); normal -> - xmerl_sax_parser_list:parse(Xml, + xmerl_sax_parser_list:parse(Xml, State#xmerl_sax_parser_state{encoding = list, input_type = InputType}) end; stream(Xml, Options, InputType) when is_binary(Xml), is_list(Options) -> - case parse_options(Options, initial_state()) of + case parse_options(Options, initial_state()) of {error, Reason} -> {error, Reason}; State -> - ParseFunction = + ParseFunction = case State#xmerl_sax_parser_state.file_type of dtd -> parse_dtd; normal -> parse end, - try + try {Xml1, State1} = detect_charset(Xml, State), parse_binary(Xml1, State1#xmerl_sax_parser_state{input_type = InputType}, ParseFunction) catch throw:{fatal_error, {State2, Reason}} -> - {fatal_error, + {fatal_error, { State2#xmerl_sax_parser_state.current_location, - State2#xmerl_sax_parser_state.entity, + State2#xmerl_sax_parser_state.entity, 1 }, - Reason, [], + Reason, [], State2#xmerl_sax_parser_state.event_state} end end. @@ -157,7 +157,7 @@ parse_binary(Xml, #xmerl_sax_parser_state{encoding={utf16,big}}=State, F) -> xmerl_sax_parser_utf16be:F(Xml, State); parse_binary(Xml, #xmerl_sax_parser_state{encoding=latin1}=State, F) -> xmerl_sax_parser_latin1:F(Xml, State); -parse_binary(_, #xmerl_sax_parser_state{encoding=Enc}, State) -> +parse_binary(_, #xmerl_sax_parser_state{encoding=Enc}, State) -> ?fatal_error(State, lists:flatten(io_lib:format("Charcter set ~p not supported", [Enc]))). %%---------------------------------------------------------------------- @@ -177,9 +177,9 @@ initial_state() -> %%---------------------------------------------------------------------- %% Function: parse_options(Options, State) %% Input: Options = [Option] -%% Option = {event_state, term()} | {event_fun, fun()} | +%% Option = {event_state, term()} | {event_fun, fun()} | %% {continuation_state, term()} | {continuation_fun, fun()} | -%% {encoding, Encoding} | {file_type, FT} +%% {encoding, Encoding} | {file_type, FT} %% FT = normal | dtd %% Encoding = utf8 | utf16le | utf16be | list | iso8859 %% State = #xmerl_sax_parser_state{} @@ -200,7 +200,7 @@ parse_options([{file_type, FT} |Options], State) when FT==normal; FT==dtd -> parse_options(Options, State#xmerl_sax_parser_state{file_type = FT}); parse_options([{encoding, E} |Options], State) -> case check_encoding_option(E) of - {error, Reason} -> + {error, Reason} -> {error, Reason}; Enc -> parse_options(Options, State#xmerl_sax_parser_state{encoding = Enc}) @@ -231,7 +231,7 @@ check_encoding_option(E) -> %% Description: Detects which character set is used in a binary stream. %%---------------------------------------------------------------------- detect_charset(<<>>, #xmerl_sax_parser_state{continuation_fun = undefined} = State) -> - ?fatal_error(State, "Can't detect character encoding due to lack of indata"); + ?fatal_error(State, "Can't detect character encoding due to lack of indata"); detect_charset(<<>>, State) -> cf(<<>>, State, fun detect_charset/2); detect_charset(Bytes, State) -> @@ -269,22 +269,14 @@ detect_charset_1(<<16#3C, 16#3F, 16#78, 16#6D>> = Xml, State) -> cf(Xml, State, fun detect_charset_1/2); detect_charset_1(<<16#3C, 16#3F, 16#78, 16#6D, 16#6C, Xml2/binary>>, State) -> {Xml3, State1} = read_until_end_of_xml_directive(Xml2, State), - case parse_xml_directive(Xml3) of - {error, Reason} -> - ?fatal_error(State, Reason); - AttrList -> - case lists:keysearch("encoding", 1, AttrList) of - {value, {_, E}} -> - case convert_encoding(E) of - {error, Reason} -> - ?fatal_error(State, Reason); - Enc -> - {<<16#3C, 16#3F, 16#78, 16#6D, 16#6C, Xml3/binary>>, - State1#xmerl_sax_parser_state{encoding=Enc}} - end; - _ -> - {<<16#3C, 16#3F, 16#78, 16#6D, 16#6C, Xml3/binary>>, State1} - end + AttrList = parse_xml_directive(Xml3, State), + case lists:keysearch("encoding", 1, AttrList) of + {value, {_, E}} -> + Enc = convert_encoding(E, State), + {<<16#3C, 16#3F, 16#78, 16#6D, 16#6C, Xml3/binary>>, + State1#xmerl_sax_parser_state{encoding=Enc}}; + _ -> + {<<16#3C, 16#3F, 16#78, 16#6D, 16#6C, Xml3/binary>>, State1} end; detect_charset_1(Xml, State) -> {Xml, State}. @@ -295,7 +287,7 @@ detect_charset_1(Xml, State) -> %% Output: utf8 | iso8859 %% Description: Converting 7,8 bit and utf8 encoding strings to internal format. %%---------------------------------------------------------------------- -convert_encoding(Enc) -> %% Just for 7,8 bit + utf8 +convert_encoding(Enc, State) -> %% Just for 7,8 bit + utf8 case string:to_lower(Enc) of "utf-8" -> utf8; "us-ascii" -> utf8; @@ -309,19 +301,19 @@ convert_encoding(Enc) -> %% Just for 7,8 bit + utf8 "iso-8859-7" -> latin1; "iso-8859-8" -> latin1; "iso-8859-9" -> latin1; - _ -> {error, "Unknown encoding: " ++ Enc} + _ -> ?fatal_error(State, "Unknown encoding: " ++ Enc) end. %%---------------------------------------------------------------------- %% Function: parse_xml_directive(Xml) %% Input: Xml = binary() %% Acc = list() -%% Output: +%% Output: %% Description: Parsing the xml declaration from the input stream. %%---------------------------------------------------------------------- -parse_xml_directive(<<C, Rest/binary>>) when ?is_whitespace(C) -> - parse_xml_directive_1(Rest, []). - +parse_xml_directive(<<C, Rest/binary>>, State) when ?is_whitespace(C) -> + parse_xml_directive_1(Rest, [], State). + %%---------------------------------------------------------------------- %% Function: parse_xml_directive_1(Xml, Acc) -> [{Name, Value}] %% Input: Xml = binary() @@ -331,20 +323,20 @@ parse_xml_directive(<<C, Rest/binary>>) when ?is_whitespace(C) -> %% Output: see above %% Description: Parsing the xml declaration from the input stream. %%---------------------------------------------------------------------- -parse_xml_directive_1(<<C, Rest/binary>>, Acc) when ?is_whitespace(C) -> - parse_xml_directive_1(Rest, Acc); -parse_xml_directive_1(<<"?>", _/binary>>, Acc) -> +parse_xml_directive_1(<<C, Rest/binary>>, Acc, State) when ?is_whitespace(C) -> + parse_xml_directive_1(Rest, Acc, State); +parse_xml_directive_1(<<"?>", _/binary>>, Acc, _State) -> Acc; -parse_xml_directive_1(<<C, Rest/binary>>, Acc) when 97 =< C, C =< 122 -> +parse_xml_directive_1(<<C, Rest/binary>>, Acc, State) when 97 =< C, C =< 122 -> {Name, Rest1} = parse_name(Rest, [C]), - Rest2 = parse_eq(Rest1), - {Value, Rest3} = parse_value(Rest2), - parse_xml_directive_1(Rest3, [{Name, Value} |Acc]); -parse_xml_directive_1(_, _) -> - {error, "Unknown attribute in xml directive"}. + Rest2 = parse_eq(Rest1, State), + {Value, Rest3} = parse_value(Rest2, State), + parse_xml_directive_1(Rest3, [{Name, Value} |Acc], State); +parse_xml_directive_1(_, _, State) -> + ?fatal_error(State, "Unknown attribute in xml directive"). %%---------------------------------------------------------------------- -%% Function: parse_xml_directive_1(Xml, Acc) -> Name +%% Function: parse_name(Xml, Acc) -> Name %% Input: Xml = binary() %% Acc = string() %% Output: Name = string() @@ -361,10 +353,12 @@ parse_name(Rest, Acc) -> %% Output: Rest = binary() %% Description: Reads an '=' from the stream. %%---------------------------------------------------------------------- -parse_eq(<<C, Rest/binary>>) when ?is_whitespace(C) -> - parse_eq(Rest); -parse_eq(<<"=", Rest/binary>>) -> - Rest. +parse_eq(<<C, Rest/binary>>, State) when ?is_whitespace(C) -> + parse_eq(Rest, State); +parse_eq(<<"=", Rest/binary>>, _State) -> + Rest; +parse_eq(_, State) -> + ?fatal_error(State, "expecting = or whitespace"). %%---------------------------------------------------------------------- %% Function: parse_value(Xml) -> {Value, Rest} @@ -373,10 +367,12 @@ parse_eq(<<"=", Rest/binary>>) -> %% Rest = binary() %% Description: Parsing an attribute value from the stream. %%---------------------------------------------------------------------- -parse_value(<<C, Rest/binary>>) when ?is_whitespace(C) -> - parse_value(Rest); -parse_value(<<C, Rest/binary>>) when C == $'; C == $" -> - parse_value_1(Rest, C, []). +parse_value(<<C, Rest/binary>>, State) when ?is_whitespace(C) -> + parse_value(Rest, State); +parse_value(<<C, Rest/binary>>, _State) when C == $'; C == $" -> + parse_value_1(Rest, C, []); +parse_value(_, State) -> + ?fatal_error(State, "\', \" or whitespace expected"). %%---------------------------------------------------------------------- %% Function: parse_value_1(Xml, Stop, Acc) -> {Value, Rest} @@ -431,7 +427,7 @@ read_until_end_of_xml_directive(Rest, State) -> nomatch -> case cf(Rest, State) of {<<>>, _} -> - ?fatal_error(State, "Can't detect character encoding due to lack of indata"); + ?fatal_error(State, "Can't detect character encoding due to lack of indata"); {NewBytes, NewState} -> read_until_end_of_xml_directive(NewBytes, NewState) end; @@ -450,9 +446,9 @@ read_until_end_of_xml_directive(Rest, State) -> %% input stream and calls the fun in NextCall. %%---------------------------------------------------------------------- cf(_Rest, #xmerl_sax_parser_state{continuation_fun = undefined} = State) -> - ?fatal_error(State, "Continuation function undefined"); + ?fatal_error(State, "Continuation function undefined"); cf(Rest, #xmerl_sax_parser_state{continuation_fun = CFun, continuation_state = CState} = State) -> - Result = + Result = try CFun(CState) catch @@ -463,9 +459,9 @@ cf(Rest, #xmerl_sax_parser_state{continuation_fun = CFun, continuation_state = C end, case Result of {<<>>, _} -> - ?fatal_error(State, "Can't detect character encoding due to lack of indata"); + ?fatal_error(State, "Can't detect character encoding due to lack of indata"); {NewBytes, NewContState} -> - {<<Rest/binary, NewBytes/binary>>, + {<<Rest/binary, NewBytes/binary>>, State#xmerl_sax_parser_state{continuation_state = NewContState}} end. @@ -479,10 +475,10 @@ cf(Rest, #xmerl_sax_parser_state{continuation_fun = CFun, continuation_state = C %% input stream and calls the fun in NextCall. %%---------------------------------------------------------------------- cf(_Rest, #xmerl_sax_parser_state{continuation_fun = undefined} = State, _) -> - ?fatal_error(State, "Continuation function undefined"); -cf(Rest, #xmerl_sax_parser_state{continuation_fun = CFun, continuation_state = CState} = State, + ?fatal_error(State, "Continuation function undefined"); +cf(Rest, #xmerl_sax_parser_state{continuation_fun = CFun, continuation_state = CState} = State, NextCall) -> - Result = + Result = try CFun(CState) catch @@ -493,8 +489,8 @@ cf(Rest, #xmerl_sax_parser_state{continuation_fun = CFun, continuation_state = C end, case Result of {<<>>, _} -> - ?fatal_error(State, "Can't detect character encoding due to lack of indata"); + ?fatal_error(State, "Can't detect character encoding due to lack of indata"); {NewBytes, NewContState} -> - NextCall(<<Rest/binary, NewBytes/binary>>, + NextCall(<<Rest/binary, NewBytes/binary>>, State#xmerl_sax_parser_state{continuation_state = NewContState}) end. diff --git a/lib/xmerl/vsn.mk b/lib/xmerl/vsn.mk index ddff0c8894..09c5b181a1 100644 --- a/lib/xmerl/vsn.mk +++ b/lib/xmerl/vsn.mk @@ -1 +1 @@ -XMERL_VSN = 1.3.16 +XMERL_VSN = 1.3.16.1 |