diff options
Diffstat (limited to 'lib')
397 files changed, 31334 insertions, 21419 deletions
diff --git a/lib/common_test/doc/src/notes.xml b/lib/common_test/doc/src/notes.xml index a64818da7b..c454608bbe 100644 --- a/lib/common_test/doc/src/notes.xml +++ b/lib/common_test/doc/src/notes.xml @@ -62,6 +62,37 @@ </section> +<section><title>Common_Test 1.17.2.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + If a ct hook is installed in the <c>suite/0</c> function + in a test suite, then the hook's <c>terminate/1</c> + function would be called several times without it's + <c>init/2</c> function being called first. This is now + corrected.</p> + <p> + Own Id: OTP-15863 Aux Id: ERIERL-370 </p> + </item> + <item> + <p> + If <c>init_per_testcase</c> fails, the test itself is + skipped. According to the documentation, it should be + possible to change the result to failed in a hook + function. The only available hook function in this case + is <c>post_init_per_testcase</c>, but changing the return + value there did not affect the test case result. This is + now corrected.</p> + <p> + Own Id: OTP-15869 Aux Id: ERIERL-350 </p> + </item> + </list> + </section> + +</section> + <section><title>Common_Test 1.17.2</title> <section><title>Fixed Bugs and Malfunctions</title> @@ -239,6 +270,37 @@ </section> +<section><title>Common_Test 1.15.4.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + If a ct hook is installed in the <c>suite/0</c> function + in a test suite, then the hook's <c>terminate/1</c> + function would be called several times without it's + <c>init/2</c> function being called first. This is now + corrected.</p> + <p> + Own Id: OTP-15863 Aux Id: ERIERL-370 </p> + </item> + <item> + <p> + If <c>init_per_testcase</c> fails, the test itself is + skipped. According to the documentation, it should be + possible to change the result to failed in a hook + function. The only available hook function in this case + is <c>post_init_per_testcase</c>, but changing the return + value there did not affect the test case result. This is + now corrected.</p> + <p> + Own Id: OTP-15869 Aux Id: ERIERL-350 </p> + </item> + </list> + </section> + +</section> + <section><title>Common_Test 1.15.4.2</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/common_test/src/ct_hooks.erl b/lib/common_test/src/ct_hooks.erl index 97c349578f..94551d6815 100644 --- a/lib/common_test/src/ct_hooks.erl +++ b/lib/common_test/src/ct_hooks.erl @@ -363,7 +363,16 @@ terminate_if_scope_ends(HookId, Function0, Hooks) -> Function = strip_config(Function0), case lists:keyfind(HookId, #ct_hook_config.id, Hooks) of #ct_hook_config{ id = HookId, scope = Function} = Hook -> - terminate([Hook]), + case Function of + [AllOrGroup,_] when AllOrGroup=:=post_all; + AllOrGroup=:=post_groups -> + %% The scope only contains one function (post_all + %% or post_groups), and init has not been called, + %% so skip terminate as well. + ok; + _ -> + terminate([Hook]) + end, lists:keydelete(HookId, #ct_hook_config.id, Hooks); _ -> Hooks diff --git a/lib/common_test/src/ct_release_test.erl b/lib/common_test/src/ct_release_test.erl index ac3dcab7c9..839fb300c7 100644 --- a/lib/common_test/src/ct_release_test.erl +++ b/lib/common_test/src/ct_release_test.erl @@ -475,7 +475,7 @@ fetch_all_apps(Node) -> A = list_to_atom(filename:basename(filename:rootname(F))), _ = rpc:call(Node,application,load,[A]), case rpc:call(Node,application,get_key,[A,vsn]) of - {ok,V} -> [{A,V}]; + {ok,V} -> [{A,V,rpc:call(Node,code,lib_dir,[A])}]; _ -> [] end end, @@ -517,7 +517,7 @@ upgrade(Apps,Level,Callback,CreateDir,InstallDir,Config) -> target_system(Apps,CreateDir,InstallDir,{FromVsn,_,AllAppsVsns,Path}) -> RelName0 = "otp-"++FromVsn, - AppsVsns = [{A,V} || {A,V} <- AllAppsVsns, lists:member(A,Apps)], + AppsVsns = [{A,V,D} || {A,V,D} <- AllAppsVsns, lists:member(A,Apps)], {RelName,ErtsVsn} = create_relfile(AppsVsns,CreateDir,RelName0,FromVsn), %% Create .script and .boot @@ -636,8 +636,7 @@ do_upgrade({Cb,InitState},FromVsn,FromAppsVsns,ToRel,ToAppsVsns,InstallDir) -> {ok,Node} = start_node(Start,FromVsn,FromAppsVsns), ct:log("Node started: ~p",[Node]), - CtData = #ct_data{from = [{A,V,code:lib_dir(A)} || {A,V} <- FromAppsVsns], - to=[{A,V,code:lib_dir(A)} || {A,V} <- ToAppsVsns]}, + CtData = #ct_data{from = FromAppsVsns,to=ToAppsVsns}, State1 = do_callback(Node,Cb,upgrade_init,[CtData,InitState]), [{"OTP upgrade test",FromVsn,_,permanent}] = @@ -724,14 +723,14 @@ previous_major(Rel) -> integer_to_list(list_to_integer(Rel)-1). create_relfile(AppsVsns,CreateDir,RelName0,RelVsn) -> - UpgradeAppsVsns = [{A,V,restart_type(A)} || {A,V} <- AppsVsns], + UpgradeAppsVsns = [{A,V,restart_type(A)} || {A,V,_D} <- AppsVsns], CoreAppVsns0 = get_vsns([kernel,stdlib,sasl]), CoreAppVsns = - [{A,V,restart_type(A)} || {A,V} <- CoreAppVsns0, + [{A,V,restart_type(A)} || {A,V,_D} <- CoreAppVsns0, false == lists:keymember(A,1,AppsVsns)], - Apps = [App || {App,_} <- AppsVsns], + Apps = [App || {App,_,_} <- AppsVsns], StartDepsVsns = get_start_deps(Apps,CoreAppVsns), StartApps = [StartApp || {StartApp,_,_} <- StartDepsVsns] ++ Apps, @@ -744,7 +743,7 @@ create_relfile(AppsVsns,CreateDir,RelName0,RelVsn) -> %% processes of these applications will not be running. TestToolAppsVsns0 = get_vsns([common_test]), TestToolAppsVsns = - [{A,V,none} || {A,V} <- TestToolAppsVsns0, + [{A,V,none} || {A,V,_D} <- TestToolAppsVsns0, false == lists:keymember(A,1,AllAppsVsns0)], AllAppsVsns1 = AllAppsVsns0 ++ TestToolAppsVsns, @@ -766,7 +765,7 @@ get_vsns(Apps) -> [begin _ = application:load(A), {ok,V} = application:get_key(A,vsn), - {A,V} + {A,V,code:lib_dir(A)} end || A <- Apps]. get_start_deps([App|Apps],Acc) -> @@ -880,8 +879,9 @@ start_node(Start,ExpVsn,ExpAppsVsns) -> erlang:port_close(Port), wait_node_up(permanent,ExpVsn,ExpAppsVsns). -wait_node_up(ExpStatus,ExpVsn,ExpAppsVsns) -> +wait_node_up(ExpStatus,ExpVsn,ExpAppsVsns0) -> Node = node_name(?testnode), + ExpAppsVsns = [{A,V} || {A,V,_D} <- ExpAppsVsns0], wait_node_up(Node,ExpStatus,ExpVsn,lists:keysort(1,ExpAppsVsns),60). wait_node_up(Node,ExpStatus,ExpVsn,ExpAppsVsns,0) -> @@ -893,7 +893,7 @@ wait_node_up(Node,ExpStatus,ExpVsn,ExpAppsVsns,N) -> rpc:call(Node, application, which_applications, [])} of {[{_,ExpVsn,_,_}],Apps} when is_list(Apps) -> case [{A,V} || {A,_,V} <- lists:keysort(1,Apps), - lists:keymember(A,1,ExpAppsVsns)] of + lists:keymember(A,1,ExpAppsVsns)] of ExpAppsVsns -> {ok,Node}; _ -> diff --git a/lib/common_test/src/test_server.erl b/lib/common_test/src/test_server.erl index 756cd4d692..588396f101 100644 --- a/lib/common_test/src/test_server.erl +++ b/lib/common_test/src/test_server.erl @@ -1364,23 +1364,29 @@ do_end_tc_call(Mod, IPTC={init_per_testcase,Func}, Res, Return) -> {NOk,_} when NOk == auto_skip; NOk == fail; NOk == skip ; NOk == skipped -> {_,Args} = Res, - IPTCEndRes = + {NewConfig,IPTCEndRes} = case do_end_tc_call1(Mod, IPTC, Res, Return) of IPTCEndConfig when is_list(IPTCEndConfig) -> - IPTCEndConfig; + {IPTCEndConfig,IPTCEndConfig}; + {failed,RetReason} when Return=:={fail,RetReason} -> + %% Fail reason not changed by framework or hook + {Args,Return}; + {SF,_} = IPTCEndResult when SF=:=skip; SF=:=skipped; + SF=:=fail; SF=:=failed -> + {Args,IPTCEndResult}; _ -> - Args + {Args,Return} end, EPTCInitRes = case do_init_tc_call(Mod,{end_per_testcase_not_run,Func}, - IPTCEndRes,Return) of + NewConfig,IPTCEndRes) of {ok,EPTCInitConfig} when is_list(EPTCInitConfig) -> - {Return,EPTCInitConfig}; + {IPTCEndRes,EPTCInitConfig}; _ -> - {Return,IPTCEndRes} + {IPTCEndRes,NewConfig} end, do_end_tc_call1(Mod, {end_per_testcase_not_run,Func}, - EPTCInitRes, Return); + EPTCInitRes, IPTCEndRes); _Ok -> do_end_tc_call1(Mod, IPTC, Res, Return) end; diff --git a/lib/common_test/test/ct_hooks_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE.erl index 340b8f3d52..b87464f5e4 100644 --- a/lib/common_test/test/ct_hooks_SUITE.erl +++ b/lib/common_test/test/ct_hooks_SUITE.erl @@ -86,7 +86,7 @@ all(suite) -> 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_pre_init_tc_cth, fail_post_init_tc_cth, skip_post_suite_cth, recover_post_suite_cth, update_config_cth, state_update_cth, update_result_cth, options_cth, same_id_cth, fail_n_skip_with_minimal_cth, prio_cth, no_config, @@ -206,6 +206,10 @@ skip_pre_init_tc_cth(Config) -> do_test(skip_pre_init_tc_cth, "ct_cth_empty_SUITE.erl", [skip_pre_init_tc_cth],Config). +fail_post_init_tc_cth(Config) -> + do_test(fail_post_init_tc_cth, "ct_fail_init_tc_SUITE.erl", + [fail_post_init_tc_cth],Config). + recover_post_suite_cth(Config) when is_list(Config) -> do_test(recover_post_suite_cth, "ct_cth_fail_per_suite_SUITE.erl", [recover_post_suite_cth],Config). @@ -671,9 +675,15 @@ test_events(scope_suite_cth) -> {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, %% check that post_groups and post_all comes before init when hook %% is installed in suite/0 + %% And there should be no terminate after these, since init is + %% not yet called. {?eh,cth,{'_',post_groups,['_',[]]}}, - {?eh,cth,{'_',post_all,['_','_',[]]}}, - {?eh,tc_start,{ct_scope_suite_cth_SUITE,init_per_suite}}, + {negative, + {?eh,cth,{'_',terminate,['_']}}, + {?eh,cth,{'_',post_all,['_','_',[]]}}}, + {negative, + {?eh,cth,{'_',terminate,['_']}}, + {?eh,tc_start,{ct_scope_suite_cth_SUITE,init_per_suite}}}, {?eh,cth,{'_',id,[[]]}}, {?eh,cth,{'_',init,['_',[]]}}, {?eh,cth,{'_',pre_init_per_suite,[ct_scope_suite_cth_SUITE,'$proplist',[]]}}, @@ -1036,6 +1046,44 @@ test_events(skip_pre_init_tc_cth) -> {?eh,stop_logging,[]} ]; +test_events(fail_post_init_tc_cth) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{empty_cth,init,['_',[]]}}, + {?eh,start_info,{1,1,1}}, + {?eh,tc_start,{ct_fail_init_tc_SUITE,init_per_suite}}, + {?eh,cth,{empty_cth,pre_init_per_suite,[ct_fail_init_tc_SUITE,'$proplist',[]]}}, + {?eh,cth,{empty_cth,post_init_per_suite, + [ct_fail_init_tc_SUITE,'$proplist','$proplist',[]]}}, + {?eh,tc_done,{ct_fail_init_tc_SUITE,init_per_suite,ok}}, + {?eh,tc_start,{ct_fail_init_tc_SUITE,test_case}}, + {?eh,cth,{empty_cth,pre_init_per_testcase, + [ct_fail_init_tc_SUITE,test_case,'$proplist',[]]}}, + {?eh,cth,{empty_cth,post_init_per_testcase, + [ct_fail_init_tc_SUITE,test_case,'$proplist', + {skip, + {failed, + {ct_fail_init_tc_SUITE,init_per_testcase, + {{test_case_failed,"Failed in init_per_testcase"},'_'}}}}, + []]}}, + {?eh,tc_done,{ct_fail_init_tc_SUITE,test_case, + {failed,"Changed skip to fail in post_init_per_testcase"}}}, + {?eh,cth,{empty_cth,on_tc_fail, + [ct_fail_init_tc_SUITE,test_case, + "Changed skip to fail in post_init_per_testcase", + []]}}, + {?eh,test_stats,{0,1,{0,0}}}, + {?eh,tc_start,{ct_fail_init_tc_SUITE,end_per_suite}}, + {?eh,cth,{empty_cth,pre_end_per_suite,[ct_fail_init_tc_SUITE,'$proplist',[]]}}, + {?eh,cth,{empty_cth,post_end_per_suite, + [ct_fail_init_tc_SUITE,'$proplist',ok,[]]}}, + {?eh,tc_done,{ct_fail_init_tc_SUITE,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,cth,{empty_cth,terminate,[[]]}}, + {?eh,stop_logging,[]} + ]; + test_events(recover_post_suite_cth) -> Suite = ct_cth_fail_per_suite_SUITE, [ diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_fail_init_tc_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_fail_init_tc_SUITE.erl new file mode 100644 index 0000000000..96ddfc5782 --- /dev/null +++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_fail_init_tc_SUITE.erl @@ -0,0 +1,49 @@ +%% +%% %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_fail_init_tc_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 +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_testcase(TestCase, _Config) -> + ct:fail("Failed in init_per_testcase"). + +end_per_testcase(_TestCase, _Config) -> + ok. + +all() -> + [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/fail_post_init_tc_cth.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/fail_post_init_tc_cth.erl new file mode 100644 index 0000000000..ca9f05c40f --- /dev/null +++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/fail_post_init_tc_cth.erl @@ -0,0 +1,81 @@ +%% +%% %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(fail_post_init_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_init_per_testcase(Suite,TC,Config,{skip,_}=Return,State) -> + empty_cth:post_init_per_testcase(Suite,TC,Config,Return,State), + {{fail,"Changed skip to fail in post_init_per_testcase"},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_release_test_SUITE_data/release_test_SUITE.erl b/lib/common_test/test/ct_release_test_SUITE_data/release_test_SUITE.erl index 7f0ba65791..fe69ad0748 100644 --- a/lib/common_test/test/ct_release_test_SUITE_data/release_test_SUITE.erl +++ b/lib/common_test/test/ct_release_test_SUITE_data/release_test_SUITE.erl @@ -47,7 +47,9 @@ end_per_suite(_Config) -> init_per_testcase(major_fail_no_init, Config) -> Config; init_per_testcase(_Case, Config) -> - ct_release_test:init(Config). + Config1 = ct_release_test:init(Config), + ct:log("ct_release_test:init/1 returned:~n~p",[Config1]), + Config1. end_per_testcase(_Case, Config) -> ct_release_test:cleanup(Config). diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml index 275c6268fa..f11444137d 100644 --- a/lib/compiler/doc/src/notes.xml +++ b/lib/compiler/doc/src/notes.xml @@ -32,6 +32,127 @@ <p>This document describes the changes made to the Compiler application.</p> +<section><title>Compiler 7.4.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>Fixed a compiler crash introduced in <c>22.0.6</c> + (OTP-15952).</p> + <p> + Own Id: OTP-15953 Aux Id: ERL-999 </p> + </item> + </list> + </section> + +</section> + +<section><title>Compiler 7.4.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>Fixed an unsafe optimization when matching + <c>tuple_size/1</c> outside of guards, which could crash + the emulator if the argument was not a tuple.</p> + <p> + Own Id: OTP-15945</p> + </item> + <item> + <p>Fixed a rare bug that could cause the wrong kind of + exception to be thrown when a BIF failed in a function + that matched bitstrings.</p> + <p> + Own Id: OTP-15946</p> + </item> + <item> + <p>Fixed a bug where receive statements inside try/catch + blocks could return incorrect results.</p> + <p> + Own Id: OTP-15952</p> + </item> + </list> + </section> + +</section> + +<section><title>Compiler 7.4.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>Fixed an incorrect type determination for constructed + binaries, which could cause <c>is_binary</c> checks to + succeed when they shouldn't have.</p> + <p> + Own Id: OTP-15872</p> + </item> + </list> + </section> + +</section> + +<section><title>Compiler 7.4.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>The type optimization pass of the compiler could hang + or loop for a long time when analyzing a + <c>setelement/3</c> call with a varible position.</p> + <p> + Own Id: OTP-15828 Aux Id: ERL-948 </p> + </item> + <item> + <p>Certain complex receive statements would result in an + internal compiler failure.</p> + <p> + Own Id: OTP-15832 Aux Id: ERL-950 </p> + </item> + <item> + <p>Fixed an unsafe type optimization.</p> + <p> + Own Id: OTP-15838</p> + </item> + <item> + <p>Fixed a crash when optimizing compiler-generated + exceptions (like badmatch) whose offending term was a + constructed binary.</p> + <p> + Own Id: OTP-15839 Aux Id: ERL-954 </p> + </item> + <item> + <p>Fixed a bad optimization related to the <c>++/2</c> + operator, where the compiler assumed that it always + produced a list (<c>[] ++ RHS</c> returns <c>RHS</c> + verbatim, even if it's not a list).</p> + <p> + Own Id: OTP-15841</p> + </item> + <item> + <p>An <c>is_binary/1</c> test followed by + <c>is_bitstring/1</c> (or vice versa) could fail because + of an usafe optimization.</p> + <p> + Own Id: OTP-15845</p> + </item> + <item> + <p>A Core Erlang module where the last clause in a + <c>case</c> matched a map would fail to load.</p> + <p> + Own Id: OTP-15846 Aux Id: ERL-955 </p> + </item> + <item> + <p>Fixed a bug that could cause the compiler to crash + when compiling complex nested case expressions.</p> + <p> + Own Id: OTP-15848 Aux Id: ERL-956 </p> + </item> + </list> + </section> + +</section> + <section><title>Compiler 7.4</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/compiler/src/beam_except.erl b/lib/compiler/src/beam_except.erl index 28c89782c9..2b9c1b0cf5 100644 --- a/lib/compiler/src/beam_except.erl +++ b/lib/compiler/src/beam_except.erl @@ -140,8 +140,11 @@ fix_block_1([{set,[],[],{alloc,Live,{F1,F2,Needed0,F3}}}|Is], Words) -> [{set,[],[],{alloc,Live,{F1,F2,Needed,F3}}}|Is] end; fix_block_1([I|Is], Words) -> - [I|fix_block_1(Is, Words)]. - + [I|fix_block_1(Is, Words)]; +fix_block_1([], _Words) -> + %% Rare. The heap allocation was probably done by a binary + %% construction instruction. + []. dig_out_fc(Arity, Is0) -> Regs0 = maps:from_list([{{x,X},{arg,X}} || X <- seq(0, Arity-1)]), @@ -149,7 +152,7 @@ dig_out_fc(Arity, Is0) -> ({test,_,_,_}) -> false; (_) -> true end, Is0), - {Regs,Acc} = dig_out_fc_1(reverse(Is), Regs0, Acc0), + {Regs,Acc} = dig_out_fc_1(reverse(Is), Arity, Regs0, Acc0), case Regs of #{{x,0}:={atom,function_clause},{x,1}:=Args} -> case moves_from_stack(Args, 0, []) of @@ -162,19 +165,27 @@ dig_out_fc(Arity, Is0) -> no end. -dig_out_fc_1([{block,Bl}|Is], Regs0, Acc) -> +dig_out_fc_1([{block,Bl}|Is], Arity, Regs0, Acc) -> Regs = dig_out_fc_block(Bl, Regs0), - dig_out_fc_1(Is, Regs, Acc); -dig_out_fc_1([{bs_set_position,_,_}=I|Is], Regs, Acc) -> - dig_out_fc_1(Is, Regs, [I|Acc]); -dig_out_fc_1([{bs_get_tail,Src,Dst,Live0}|Is], Regs0, Acc) -> - Regs = prune_xregs(Live0, Regs0), - Live = dig_out_stack_live(Regs, Live0), - I = {bs_get_tail,Src,Dst,Live}, - dig_out_fc_1(Is, Regs, [I|Acc]); -dig_out_fc_1([_|_], _Regs, _Acc) -> + dig_out_fc_1(Is, Arity, Regs, Acc); +dig_out_fc_1([{bs_set_position,_,_}=I|Is], Arity, Regs, Acc) -> + dig_out_fc_1(Is, Arity, Regs, [I|Acc]); +dig_out_fc_1([{bs_get_tail,Src,Dst,Live0}|Is], Arity, Regs0, Acc) -> + case Src of + {x,X} when X < Arity -> + %% The heuristic for determining the number of live + %% registers is likely to give an incorrect result. + %% Give up. + {#{},[]}; + _ -> + Regs = prune_xregs(Live0, Regs0), + Live = dig_out_stack_live(Regs, Live0), + I = {bs_get_tail,Src,Dst,Live}, + dig_out_fc_1(Is, Arity, Regs, [I|Acc]) + end; +dig_out_fc_1([_|_], _Arity, _Regs, _Acc) -> {#{},[]}; -dig_out_fc_1([], Regs, Acc) -> +dig_out_fc_1([], _Arity, Regs, Acc) -> {Regs,Acc}. dig_out_fc_block([{set,[],[],{alloc,Live,_}}|Is], Regs0) -> diff --git a/lib/compiler/src/beam_ssa.erl b/lib/compiler/src/beam_ssa.erl index a9977b0b1d..6492d1e1bf 100644 --- a/lib/compiler/src/beam_ssa.erl +++ b/lib/compiler/src/beam_ssa.erl @@ -96,7 +96,8 @@ %% To avoid the collapsing, change the value of SET_LIMIT to 50 in the %% file erl_types.erl in the hipe application. --type prim_op() :: 'bs_add' | 'bs_extract' | 'bs_init' | 'bs_init_writable' | +-type prim_op() :: 'bs_add' | 'bs_extract' | 'bs_get_tail' | + 'bs_init' | 'bs_init_writable' | 'bs_match' | 'bs_put' | 'bs_start_match' | 'bs_test_tail' | 'bs_utf16_size' | 'bs_utf8_size' | 'build_stacktrace' | 'call' | 'catch_end' | @@ -117,11 +118,12 @@ '+' | '-' | '*' | '/'. %% Primops only used internally during code generation. --type cg_prim_op() :: 'bs_get' | 'bs_match_string' | 'bs_restore' | 'bs_skip' | +-type cg_prim_op() :: 'bs_get' | 'bs_get_position' | 'bs_match_string' | + 'bs_restore' | 'bs_save' | 'bs_set_position' | 'bs_skip' | 'copy' | 'put_tuple_arity' | 'put_tuple_element' | - 'set_tuple_element'. + 'put_tuple_elements' | 'set_tuple_element'. --import(lists, [foldl/3,keyfind/3,mapfoldl/3,member/2,reverse/1]). +-import(lists, [foldl/3,keyfind/3,mapfoldl/3,member/2,reverse/1,umerge/1]). -spec add_anno(Key, Value, Construct) -> Construct when Key :: atom(), @@ -649,12 +651,18 @@ is_commutative('=/=') -> true; is_commutative('/=') -> true; is_commutative(_) -> false. -def_used_1([#b_blk{is=Is,last=Last}|Bs], Preds, Def0, Used0) -> - {Def,Used1} = def_used_is(Is, Preds, Def0, Used0), - Used = ordsets:union(used(Last), Used1), - def_used_1(Bs, Preds, Def, Used); -def_used_1([], _Preds, Def, Used) -> - {ordsets:from_list(Def),Used}. +def_used_1([#b_blk{is=Is,last=Last}|Bs], Preds, Def0, UsedAcc) -> + {Def,Used} = def_used_is(Is, Preds, Def0, used(Last)), + case Used of + [] -> + def_used_1(Bs, Preds, Def, UsedAcc); + [_|_] -> + def_used_1(Bs, Preds, Def, [Used|UsedAcc]) + end; +def_used_1([], _Preds, Def0, UsedAcc) -> + Def = ordsets:from_list(Def0), + Used = umerge(UsedAcc), + {Def,Used}. def_used_is([#b_set{op=phi,dst=Dst,args=Args}|Is], Preds, Def0, Used0) -> diff --git a/lib/compiler/src/beam_ssa_bsm.erl b/lib/compiler/src/beam_ssa_bsm.erl index 382e6f635e..1ac9e6a3bb 100644 --- a/lib/compiler/src/beam_ssa_bsm.erl +++ b/lib/compiler/src/beam_ssa_bsm.erl @@ -683,8 +683,12 @@ aca_copy_successors(Lbl0, Blocks0, Counter0) -> Lbl = maps:get(Lbl0, BRs), {Lbl, Blocks, Counter}. +aca_cs_build_brs([?BADARG_BLOCK=Lbl | Path], Counter, Acc) -> + %% ?BADARG_BLOCK is a marker and not an actual block, so renaming it will + %% break exception handling. + aca_cs_build_brs(Path, Counter, Acc#{ Lbl => Lbl }); aca_cs_build_brs([Lbl | Path], Counter0, Acc) -> - aca_cs_build_brs(Path, Counter0 + 1, maps:put(Lbl, Counter0, Acc)); + aca_cs_build_brs(Path, Counter0 + 1, Acc#{ Lbl => Counter0 }); aca_cs_build_brs([], Counter, Acc) -> {Acc, Counter}. diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl index c2d5035b19..08641e2abc 100644 --- a/lib/compiler/src/beam_ssa_codegen.erl +++ b/lib/compiler/src/beam_ssa_codegen.erl @@ -764,9 +764,8 @@ defined(Linear, #cg{regs=Regs}) -> def([{L,#cg_blk{is=Is0,last=Last}=Blk0}|Bs], DefMap0, Regs) -> Def0 = def_get(L, DefMap0), - {Is,Def} = def_is(Is0, Regs, Def0, []), - Successors = successors(Last), - DefMap = def_successors(Successors, Def, DefMap0), + {Is,Def,MaybeDef} = def_is(Is0, Regs, Def0, []), + DefMap = def_successors(Last, Def, MaybeDef, DefMap0), Blk = Blk0#cg_blk{is=Is}, [{L,Blk}|def(Bs, DefMap, Regs)]; def([], _, _) -> []. @@ -780,6 +779,11 @@ def_get(L, DefMap) -> def_is([#cg_alloc{anno=Anno0}=I0|Is], Regs, Def, Acc) -> I = I0#cg_alloc{anno=Anno0#{def_yregs=>Def}}, def_is(Is, Regs, Def, [I|Acc]); +def_is([#cg_set{op=succeeded,args=[Var]}=I], Regs, Def, Acc) -> + %% Var will only be defined on the success branch of the `br` + %% for this block. + MaybeDef = def_add_yreg(Var, [], Regs), + {reverse(Acc, [I]),Def,MaybeDef}; def_is([#cg_set{op=kill_try_tag,args=[#b_var{}=Tag]}=I|Is], Regs, Def0, Acc) -> Def = ordsets:del_element(Tag, Def0), def_is(Is, Regs, Def, [I|Acc]); @@ -822,7 +826,7 @@ def_is([#cg_set{anno=Anno0,dst=Dst}=I0|Is], Regs, Def0, Acc) -> Def = def_add_yreg(Dst, Def0, Regs), def_is(Is, Regs, Def, [I|Acc]); def_is([], _, Def, Acc) -> - {reverse(Acc),Def}. + {reverse(Acc),Def,[]}. def_add_yreg(Dst, Def, Regs) -> case is_yreg(Dst, Regs) of @@ -830,6 +834,12 @@ def_add_yreg(Dst, Def, Regs) -> false -> Def end. +def_successors(#cg_br{bool=#b_var{},succ=Succ,fail=Fail}, Def, MaybeDef, DefMap0) -> + DefMap = def_successors([Fail], ordsets:subtract(Def, MaybeDef), DefMap0), + def_successors([Succ], Def, DefMap); +def_successors(Last, Def, [], DefMap) -> + def_successors(successors(Last), Def, DefMap). + def_successors([S|Ss], Def0, DefMap) -> case DefMap of #{S:=Def1} -> @@ -1016,6 +1026,14 @@ bif_fail({catch_tag,_}) -> {f,0}. next_block([]) -> none; next_block([{Next,_}|_]) -> Next. +%% Certain instructions (such as get_map_element or is_nonempty_list) +%% are only used in guards and **must** have a non-zero label; +%% otherwise, the loader will refuse to load the +%% module. ensure_label/2 replaces a zero label with the "ultimate +%% failure" label to make the module loadable. The instruction that +%% have had the zero label replaced is **not** supposed to ever fail +%% and actually jump to the label. + ensure_label(Fail0, #cg{ultimate_fail=Lbl}) -> case bif_fail(Fail0) of {f,0} -> {f,Lbl}; @@ -1160,6 +1178,11 @@ cg_block([#cg_set{op=call}=I, #cg_set{op=succeeded,dst=Bool}], {Bool,_Fail}, St) -> %% A call in try/catch block. cg_block([I], none, St); +cg_block([#cg_set{op=get_map_element,dst=Dst0,args=Args0}, + #cg_set{op=succeeded,dst=Bool}], {Bool,Fail0}, St) -> + [Dst,Map,Key] = beam_args([Dst0|Args0], St), + Fail = ensure_label(Fail0, St), + {[{get_map_elements,Fail,Map,{list,[Key,Dst]}}],St}; cg_block([#cg_set{op=Op,dst=Dst0,args=Args0}=I, #cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) -> [Dst|Args] = beam_args([Dst0|Args0], St), @@ -1606,8 +1629,6 @@ cg_test({float,Op0}, Fail, Args, Dst, #cg_set{anno=Anno}) -> '/' -> fdiv end, [line(Anno),{bif,Op,Fail,Args,Dst}]; -cg_test(get_map_element, Fail, [Map,Key], Dst, _I) -> - [{get_map_elements,Fail,Map,{list,[Key,Dst]}}]; cg_test(peek_message, Fail, [], Dst, _I) -> [{loop_rec,Fail,{x,0}}|copy({x,0}, Dst)]; cg_test(put_map, Fail, [{atom,exact},SrcMap|Ss], Dst, Set) -> diff --git a/lib/compiler/src/beam_ssa_dead.erl b/lib/compiler/src/beam_ssa_dead.erl index bb43a550ae..e78e4647a8 100644 --- a/lib/compiler/src/beam_ssa_dead.erl +++ b/lib/compiler/src/beam_ssa_dead.erl @@ -30,7 +30,7 @@ -import(lists, [append/1,keymember/3,last/1,member/2, takewhile/2,reverse/1]). --type used_vars() :: #{beam_ssa:label():=ordsets:ordset(beam_ssa:var_name())}. +-type used_vars() :: #{beam_ssa:label():=cerl_sets:set(beam_ssa:var_name())}. -type basic_type_test() :: atom() | {'is_tagged_tuple',pos_integer(),atom()}. -type type_test() :: basic_type_test() | {'not',basic_type_test()}. @@ -90,13 +90,11 @@ shortcut_opt(#st{bs=Blocks}=St) -> %% the diff.) %% %% Unfortunately, processing the blocks in reverse post order - %% potentially makes the time complexity quadratic or even cubic if - %% the ordset of unset variables grows large, instead of - %% linear for post order processing. We try to still get reasonable - %% compilation times by optimizations that will keep the constant - %% factor as low as possible, and we try to avoid the cubic time - %% complexity by trying to keep the set of unset variables as small - %% as possible. + %% potentially makes the time complexity quadratic, instead of + %% linear for post order processing. We avoid drastic slowdowns by + %% limiting how far we search forward to a common block that + %% both the success and failure label will reach (see the comment + %% in the first clause of shortcut_2/5). Ls = beam_ssa:rpo(Blocks), shortcut_opt(Ls, #{}, St). @@ -124,10 +122,15 @@ shortcut_terminator(#b_br{bool=#b_var{}=Bool,succ=Succ0,fail=Fail0}=Br, Is, From, Bs, St0) -> St = St0#st{target=one_way}, RelOp = get_rel_op(Bool, Is), - SuccBs = bind_var(Bool, #b_literal{val=true}, Bs), + + %% The boolean in a `br` is seldom used by the successors. By + %% not binding its value unless it is actually used we might be able + %% to skip some work in shortcut/4 and sub/2. + SuccBs = bind_var_if_used(Succ0, Bool, #b_literal{val=true}, Bs, St), BrSucc = shortcut(Succ0, From, SuccBs, St#st{rel_op=RelOp}), - FailBs = bind_var(Bool, #b_literal{val=false}, Bs), + FailBs = bind_var_if_used(Fail0, Bool, #b_literal{val=false}, Bs, St), BrFail = shortcut(Fail0, From, FailBs, St#st{rel_op=invert_op(RelOp)}), + case {BrSucc,BrFail} of {#b_br{bool=#b_literal{val=true},succ=Succ}, #b_br{bool=#b_literal{val=true},succ=Fail}} @@ -152,8 +155,14 @@ shortcut_switch([{Lit,L0}|T], Bool, From, Bs, St0) -> [{Lit,L}|shortcut_switch(T, Bool, From, Bs, St0)]; shortcut_switch([], _, _, _, _) -> []. +shortcut(L, _From, Bs, #st{rel_op=none,target=one_way}) when map_size(Bs) =:= 0 -> + %% There is no way that we can find a suitable branch, because there is no + %% relational operator stored, there are no bindings, and the block L can't + %% have any phi nodes from which we could pick bindings because when the target + %% is `one_way`, it implies the From block has a two-way `br` terminator. + #b_br{bool=#b_literal{val=true},succ=L,fail=L}; shortcut(L, From, Bs, St) -> - shortcut_1(L, From, Bs, ordsets:new(), St). + shortcut_1(L, From, Bs, cerl_sets:new(), St). shortcut_1(L, From, Bs0, UnsetVars0, St) -> case shortcut_2(L, From, Bs0, UnsetVars0, St) of @@ -170,7 +179,19 @@ shortcut_1(L, From, Bs0, UnsetVars0, St) -> end. %% Try to shortcut this block, branching to a successor. -shortcut_2(L, From, Bs0, UnsetVars0, St) -> +shortcut_2(L, From, Bs, UnsetVars, St) -> + case cerl_sets:size(UnsetVars) of + SetSize when SetSize > 128 -> + %% This is an heuristic to limit the search for a forced label + %% before it drastically slows down the compiler. Experiments + %% with scripts/diffable showed that limits larger than 31 did not + %% find any more opportunities for optimization. + none; + _SetSize -> + shortcut_3(L, From, Bs, UnsetVars, St) + end. + +shortcut_3(L, From, Bs0, UnsetVars0, St) -> #b_blk{is=Is,last=Last} = get_block(L, St), case eval_is(Is, From, Bs0, St) of none -> @@ -347,7 +368,7 @@ update_unset_vars(L, Is, Br, UnsetVars, #st{skippable=Skippable}) -> %% Some variables defined in this block are used by %% successors. We must update the set of unset variables. SetInThisBlock = [V || #b_set{dst=V} <- Is], - ordsets:union(UnsetVars, ordsets:from_list(SetInThisBlock)) + cerl_sets:union(UnsetVars, cerl_sets:from_list(SetInThisBlock)) end. shortcut_two_way(#b_br{succ=Succ,fail=Fail}, From, Bs0, UnsetVars0, St0) -> @@ -376,14 +397,14 @@ is_br_safe(UnsetVars, Br, #st{us=Us}=St) -> %% A two-way branch never branches to a phi node, so there %% is no need to check for phi nodes here. - not member(V, UnsetVars) andalso - ordsets:is_disjoint(Used0, UnsetVars) andalso - ordsets:is_disjoint(Used1, UnsetVars); + not cerl_sets:is_element(V, UnsetVars) andalso + cerl_sets:is_disjoint(Used0, UnsetVars) andalso + cerl_sets:is_disjoint(Used1, UnsetVars); #b_br{succ=Same,fail=Same} -> %% An unconditional branch must not jump to %% a phi node. not is_forbidden(Same, St) andalso - ordsets:is_disjoint(map_get(Same, Us), UnsetVars) + cerl_sets:is_disjoint(map_get(Same, Us), UnsetVars) end. is_forbidden(L, St) -> @@ -436,8 +457,22 @@ get_phi_arg([{Val,From}|_], From) -> Val; get_phi_arg([_|As], From) -> get_phi_arg(As, From). eval_terminator(#b_br{bool=#b_var{}=Bool}=Br, Bs, _St) -> - Val = get_value(Bool, Bs), - beam_ssa:normalize(Br#b_br{bool=Val}); + case get_value(Bool, Bs) of + #b_literal{val=Val}=Lit -> + case is_boolean(Val) of + true -> + beam_ssa:normalize(Br#b_br{bool=Lit}); + false -> + %% Non-boolean literal. This means that this `br` + %% terminator will never actually be reached with + %% these bindings. (There must be a previous two-way + %% branch that branches the other way when Bool + %% is bound to a non-boolean literal.) + none + end; + #b_var{}=Var -> + beam_ssa:normalize(Br#b_br{bool=Var}) + end; eval_terminator(#b_br{bool=#b_literal{}}=Br, _Bs, _St) -> beam_ssa:normalize(Br); eval_terminator(#b_switch{arg=Arg,fail=Fail,list=List}=Sw, Bs, St) -> @@ -486,6 +521,15 @@ eval_switch_1([], _Arg, _PrevOp, Fail) -> %% Fail is now either the failure label or 'none'. Fail. +bind_var_if_used(L, Var, Val0, Bs, #st{us=Us}) -> + case cerl_sets:is_element(Var, map_get(L, Us)) of + true -> + Val = get_value(Val0, Bs), + Bs#{Var=>Val}; + false -> + Bs + end. + bind_var(Var, Val0, Bs) -> Val = get_value(Val0, Bs), Bs#{Var=>Val}. @@ -680,11 +724,8 @@ will_succeed_test(is_list, is_nonempty_list) -> maybe; will_succeed_test(is_nonempty_list, is_list) -> yes; -will_succeed_test(T1, T2) -> - case is_numeric_test(T1) andalso is_numeric_test(T2) of - true -> maybe; - false -> no - end. +will_succeed_test(_T1, _T2) -> + maybe. will_succeed_1('=:=', A, '<', B) -> if @@ -769,11 +810,6 @@ will_succeed_vars('==', Val1, '/=', Val2) when Val1 == Val2 -> no; will_succeed_vars(_, _, _, _) -> maybe. -is_numeric_test(is_float) -> true; -is_numeric_test(is_integer) -> true; -is_numeric_test(is_number) -> true; -is_numeric_test(_) -> false. - eval_type_test(Test, Arg) -> case eval_type_test_1(Test, Arg) of true -> yes; @@ -983,7 +1019,7 @@ used_vars([{L,#b_blk{is=Is}=Blk}|Bs], UsedVars0, Skip0) -> %% shortcut_opt/1. Successors = beam_ssa:successors(Blk), - Used0 = used_vars_succ(Successors, L, UsedVars0, []), + Used0 = used_vars_succ(Successors, L, UsedVars0, cerl_sets:new()), Used = used_vars_blk(Blk, Used0), UsedVars = used_vars_phis(Is, L, Used, UsedVars0), @@ -994,8 +1030,8 @@ used_vars([{L,#b_blk{is=Is}=Blk}|Bs], UsedVars0, Skip0) -> %% shortcut_opt/1. Defined0 = [Def || #b_set{dst=Def} <- Is], - Defined = ordsets:from_list(Defined0), - MaySkip = ordsets:is_disjoint(Defined, Used0), + Defined = cerl_sets:from_list(Defined0), + MaySkip = cerl_sets:is_disjoint(Defined, Used0), case MaySkip of true -> Skip = Skip0#{L=>true}, @@ -1012,11 +1048,11 @@ used_vars_succ([S|Ss], L, LiveMap, Live0) -> #{Key:=Live} -> %% The successor has a phi node, and the value for %% this block in the phi node is a variable. - used_vars_succ(Ss, L, LiveMap, ordsets:union(Live, Live0)); + used_vars_succ(Ss, L, LiveMap, cerl_sets:union(Live, Live0)); #{S:=Live} -> %% No phi node in the successor, or the value for %% this block in the phi node is a literal. - used_vars_succ(Ss, L, LiveMap, ordsets:union(Live, Live0)); + used_vars_succ(Ss, L, LiveMap, cerl_sets:union(Live, Live0)); #{} -> %% A peek_message block which has not been processed yet. used_vars_succ(Ss, L, LiveMap, Live0) @@ -1034,7 +1070,7 @@ used_vars_phis(Is, L, Live0, UsedVars0) -> case [{P,V} || {#b_var{}=V,P} <- PhiArgs] of [_|_]=PhiVars -> PhiLive0 = rel2fam(PhiVars), - PhiLive = [{{L,P},ordsets:union(ordsets:from_list(Vs), Live0)} || + PhiLive = [{{L,P},cerl_sets:union(cerl_sets:from_list(Vs), Live0)} || {P,Vs} <- PhiLive0], maps:merge(UsedVars, maps:from_list(PhiLive)); [] -> @@ -1044,14 +1080,14 @@ used_vars_phis(Is, L, Live0, UsedVars0) -> end. used_vars_blk(#b_blk{is=Is,last=Last}, Used0) -> - Used = ordsets:union(Used0, beam_ssa:used(Last)), + Used = cerl_sets:union(Used0, cerl_sets:from_list(beam_ssa:used(Last))), used_vars_is(reverse(Is), Used). used_vars_is([#b_set{op=phi}|Is], Used) -> used_vars_is(Is, Used); used_vars_is([#b_set{dst=Dst}=I|Is], Used0) -> - Used1 = ordsets:union(Used0, beam_ssa:used(I)), - Used = ordsets:del_element(Dst, Used1), + Used1 = cerl_sets:union(Used0, cerl_sets:from_list(beam_ssa:used(I))), + Used = cerl_sets:del_element(Dst, Used1), used_vars_is(Is, Used); used_vars_is([], Used) -> Used. @@ -1060,8 +1096,9 @@ used_vars_is([], Used) -> %%% Common utilities. %%% -sub(#b_set{args=Args}=I, Sub) -> - I#b_set{args=[sub_arg(A, Sub) || A <- Args]}. +sub(#b_set{args=Args}=I, Sub) when map_size(Sub) =/= 0 -> + I#b_set{args=[sub_arg(A, Sub) || A <- Args]}; +sub(I, _Sub) -> I. sub_arg(#b_var{}=Old, Sub) -> case Sub of diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl index 90c0d3cf16..d87c66c272 100644 --- a/lib/compiler/src/beam_ssa_opt.erl +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -904,8 +904,7 @@ cse_suitable(#b_set{}) -> false. }). ssa_opt_float({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) -> - NonGuards0 = float_non_guards(Linear0), - NonGuards = gb_sets:from_list(NonGuards0), + NonGuards = non_guards(Linear0), Blocks = maps:from_list(Linear0), Fs = #fs{non_guards=NonGuards,bs=Blocks}, {Linear,Count} = float_opt(Linear0, Count0, Fs), @@ -916,15 +915,6 @@ float_blk_is_in_guard(#b_blk{last=#b_br{fail=F}}, #fs{non_guards=NonGuards}) -> float_blk_is_in_guard(#b_blk{}, #fs{}) -> false. -float_non_guards([{L,#b_blk{is=Is}}|Bs]) -> - case Is of - [#b_set{op=landingpad}|_] -> - [L|float_non_guards(Bs)]; - _ -> - float_non_guards(Bs) - end; -float_non_guards([]) -> [?BADARG_BLOCK]. - float_opt([{L,Blk}|Bs0], Count0, Fs) -> case float_blk_is_in_guard(Blk, Fs) of true -> @@ -1774,35 +1764,44 @@ opt_bs_put_split_int_1(Int, L, R) -> %%% ssa_opt_tuple_size({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) -> - {Linear,Count} = opt_tup_size(Linear0, Count0, []), + %% This optimization is only safe in guards, as prefixing tuple_size with + %% an is_tuple check prevents it from throwing an exception. + NonGuards = non_guards(Linear0), + {Linear,Count} = opt_tup_size(Linear0, NonGuards, Count0, []), {St#st{ssa=Linear,cnt=Count}, FuncDb}. -opt_tup_size([{L,#b_blk{is=Is,last=Last}=Blk}|Bs], Count0, Acc0) -> +opt_tup_size([{L,#b_blk{is=Is,last=Last}=Blk}|Bs], NonGuards, Count0, Acc0) -> case {Is,Last} of {[#b_set{op={bif,'=:='},dst=Bool,args=[#b_var{}=Tup,#b_literal{val=Arity}]}], #b_br{bool=Bool}} when is_integer(Arity), Arity >= 0 -> - {Acc,Count} = opt_tup_size_1(Tup, L, Count0, Acc0), - opt_tup_size(Bs, Count, [{L,Blk}|Acc]); + {Acc,Count} = opt_tup_size_1(Tup, L, NonGuards, Count0, Acc0), + opt_tup_size(Bs, NonGuards, Count, [{L,Blk}|Acc]); {_,_} -> - opt_tup_size(Bs, Count0, [{L,Blk}|Acc0]) + opt_tup_size(Bs, NonGuards, Count0, [{L,Blk}|Acc0]) end; -opt_tup_size([], Count, Acc) -> +opt_tup_size([], _NonGuards, Count, Acc) -> {reverse(Acc),Count}. -opt_tup_size_1(Size, EqL, Count0, [{L,Blk0}|Acc]) -> - case Blk0 of - #b_blk{is=Is0,last=#b_br{bool=Bool,succ=EqL,fail=Fail}} -> - case opt_tup_size_is(Is0, Bool, Size, []) of - none -> +opt_tup_size_1(Size, EqL, NonGuards, Count0, [{L,Blk0}|Acc]) -> + #b_blk{is=Is0,last=Last} = Blk0, + case Last of + #b_br{bool=Bool,succ=EqL,fail=Fail} -> + case gb_sets:is_member(Fail, NonGuards) of + true -> {[{L,Blk0}|Acc],Count0}; - {PreIs,TupleSizeIs,Tuple} -> - opt_tup_size_2(PreIs, TupleSizeIs, L, EqL, - Tuple, Fail, Count0, Acc) + false -> + case opt_tup_size_is(Is0, Bool, Size, []) of + none -> + {[{L,Blk0}|Acc],Count0}; + {PreIs,TupleSizeIs,Tuple} -> + opt_tup_size_2(PreIs, TupleSizeIs, L, EqL, + Tuple, Fail, Count0, Acc) + end end; - #b_blk{} -> + _ -> {[{L,Blk0}|Acc],Count0} end; -opt_tup_size_1(_, _, Count, Acc) -> +opt_tup_size_1(_, _, _, Count, Acc) -> {Acc,Count}. opt_tup_size_2(PreIs, TupleSizeIs, PreL, EqL, Tuple, Fail, Count0, Acc) -> @@ -1940,12 +1939,24 @@ verify_merge_is(_) -> is_merge_allowed(_, #b_blk{}, #b_blk{is=[#b_set{op=peek_message}|_]}) -> false; -is_merge_allowed(L, #b_blk{last=#b_br{}}=Blk, #b_blk{}) -> +is_merge_allowed(L, #b_blk{last=#b_br{}}=Blk, #b_blk{is=Is}) -> %% The predecessor block must have exactly one successor (L) for %% the merge to be safe. case beam_ssa:successors(Blk) of - [L] -> true; - [_|_] -> false + [L] -> + case Is of + [#b_set{op=phi,args=[_]}|_] -> + %% The type optimizer pass must have been + %% turned off, since it would have removed this + %% redundant phi node. Refuse to merge the blocks + %% to ensure that this phi node remains at the + %% beginning of a block. + false; + _ -> + true + end; + [_|_] -> + false end; is_merge_allowed(_, #b_blk{last=#b_switch{}}, #b_blk{}) -> false. @@ -2241,6 +2252,19 @@ gcd(A, B) -> X -> gcd(B, X) end. +non_guards(Linear) -> + gb_sets:from_list(non_guards_1(Linear)). + +non_guards_1([{L,#b_blk{is=Is}}|Bs]) -> + case Is of + [#b_set{op=landingpad}|_] -> + [L | non_guards_1(Bs)]; + _ -> + non_guards_1(Bs) + end; +non_guards_1([]) -> + [?BADARG_BLOCK]. + rel2fam(S0) -> S1 = sofs:relation(S0), S = sofs:rel2fam(S1), diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl index bf99e8fc26..7f816b9802 100644 --- a/lib/compiler/src/beam_ssa_pre_codegen.erl +++ b/lib/compiler/src/beam_ssa_pre_codegen.erl @@ -156,7 +156,9 @@ passes(Opts) -> %% Allocate registers. ?PASS(linear_scan), ?PASS(frame_size), - ?PASS(turn_yregs)], + ?PASS(turn_yregs), + + ?PASS(assert_no_critical_edges)], [P || P <- Ps, P =/= ignore]. function(#b_function{anno=Anno,args=Args,bs=Blocks0,cnt=Count0}=F0, @@ -1321,10 +1323,11 @@ fix_receives_1([{L,Blk}|Ls], Blocks0, Count0) -> LoopExit = find_loop_exit(Rm, Blocks0), Defs0 = beam_ssa:def([L], Blocks0), CommonUsed = recv_common(Defs0, LoopExit, Blocks0), - {Blocks1,Count1} = recv_fix_common(CommonUsed, LoopExit, Rm, - Blocks0, Count0), + {Blocks1,Count1} = recv_crit_edges(Rm, LoopExit, Blocks0, Count0), + {Blocks2,Count2} = recv_fix_common(CommonUsed, LoopExit, Rm, + Blocks1, Count1), Defs = ordsets:subtract(Defs0, CommonUsed), - {Blocks,Count} = fix_receive(Rm, Defs, Blocks1, Count1), + {Blocks,Count} = fix_receive(Rm, Defs, Blocks2, Count2), fix_receives_1(Ls, Blocks, Count); #b_blk{} -> fix_receives_1(Ls, Blocks0, Count0) @@ -1341,6 +1344,57 @@ recv_common(Defs, Exit, Blocks) -> Def = ordsets:subtract(Defs, ExitDefs), ordsets:intersection(Def, ExitUsed). +%% recv_crit_edges([RemoveMessageLabel], LoopExit, +%% Blocks0, Count0) -> {Blocks,Count}. +%% +%% Adds dummy blocks on all conditional jumps to the exit block so that +%% recv_fix_common/5 can insert phi nodes without having to worry about +%% critical edges. + +recv_crit_edges(_Rms, none, Blocks0, Count0) -> + {Blocks0, Count0}; +recv_crit_edges(Rms, Exit, Blocks0, Count0) -> + Ls = beam_ssa:rpo(Rms, Blocks0), + rce_insert_edges(Ls, Exit, Count0, Blocks0). + +rce_insert_edges([L | Ls], Exit, Count0, Blocks0) -> + Successors = beam_ssa:successors(map_get(L, Blocks0)), + case member(Exit, Successors) of + true when Successors =/= [Exit] -> + {Blocks, Count} = rce_insert_edge(L, Exit, Count0, Blocks0), + rce_insert_edges(Ls, Exit, Count, Blocks); + _ -> + rce_insert_edges(Ls, Exit, Count0, Blocks0) + end; +rce_insert_edges([], _Exit, Count, Blocks) -> + {Blocks, Count}. + +rce_insert_edge(L, Exit, Count, Blocks0) -> + #b_blk{last=Last0} = FromBlk0 = map_get(L, Blocks0), + + ToExit = #b_br{bool=#b_literal{val=true},succ=Exit,fail=Exit}, + + FromBlk = FromBlk0#b_blk{last=rce_reroute_terminator(Last0, Exit, Count)}, + EdgeBlk = #b_blk{anno=#{},is=[],last=ToExit}, + + Blocks = Blocks0#{ Count => EdgeBlk, L => FromBlk }, + {Blocks, Count + 1}. + +rce_reroute_terminator(#b_br{succ=Exit}=Last, Exit, New) -> + rce_reroute_terminator(Last#b_br{succ=New}, Exit, New); +rce_reroute_terminator(#b_br{fail=Exit}=Last, Exit, New) -> + rce_reroute_terminator(Last#b_br{fail=New}, Exit, New); +rce_reroute_terminator(#b_br{}=Last, _Exit, _New) -> + Last; +rce_reroute_terminator(#b_switch{fail=Exit}=Last, Exit, New) -> + rce_reroute_terminator(Last#b_switch{fail=New}, Exit, New); +rce_reroute_terminator(#b_switch{list=List0}=Last, Exit, New) -> + List = [if + Lbl =:= Exit -> {Arg, New}; + Lbl =/= Exit -> {Arg, Lbl} + end || {Arg, Lbl} <- List0], + Last#b_switch{list=List}. + %% recv_fix_common([CommonVar], LoopExit, [RemoveMessageLabel], %% Blocks0, Count0) -> {Blocks,Count}. %% Handle variables alwys defined in a receive and used @@ -1409,18 +1463,51 @@ fix_receive([], _Defs, Blocks, Count) -> {Blocks,Count}. %% find_loop_exit([Label], Blocks) -> Label | none. -%% Find the block to which control is transferred when the -%% the receive loop is exited. - -find_loop_exit([L1,L2|_Ls], Blocks) -> - Path1 = beam_ssa:rpo([L1], Blocks), - Path2 = beam_ssa:rpo([L2], Blocks), - find_loop_exit_1(reverse(Path1), reverse(Path2), none); -find_loop_exit(_, _) -> none. - -find_loop_exit_1([H|T1], [H|T2], _) -> - find_loop_exit_1(T1, T2, H); -find_loop_exit_1(_, _, Exit) -> Exit. +%% Given the list of all blocks with the remove_message instructions +%% for this receive, find the block to which control is transferred +%% when the receive loop is exited (if any). + +find_loop_exit([_,_|_]=RmBlocks, Blocks) -> + %% We used to only analyze the path from two of the remove_message + %% blocks. That would fail to find a common block if one or both + %% of the blocks happened to raise an exception. To be sure that + %% we always find a common block if there is one (shared by at + %% least two clauses), we must analyze the path from all + %% remove_message blocks. + {Dominators,_} = beam_ssa:dominators(Blocks), + RmSet = cerl_sets:from_list(RmBlocks), + Rpo = beam_ssa:rpo(RmBlocks, Blocks), + find_loop_exit_1(Rpo, RmSet, Dominators); +find_loop_exit(_, _) -> + %% There is (at most) a single clause. There is no common + %% loop exit block. + none. + +find_loop_exit_1([?BADARG_BLOCK|Ls], RmSet, Dominators) -> + %% ?BADARG_BLOCK is a marker and not an actual block, so it is not + %% the block we are looking for. + find_loop_exit_1(Ls, RmSet, Dominators); +find_loop_exit_1([L|Ls], RmSet, Dominators) -> + DomBy = map_get(L, Dominators), + case any(fun(E) -> cerl_sets:is_element(E, RmSet) end, DomBy) of + true -> + %% This block is dominated by one of the remove_message blocks, + %% which means that the block is part of only one clause. + %% It is not the block we are looking for. + find_loop_exit_1(Ls, RmSet, Dominators); + false -> + %% This block is the first block that is not dominated by + %% any of the blocks with remove_message instructions, + %% which means that at least two of the receive clauses + %% will ultimately transfer control to it. It is the block + %% we are looking for. + L + end; +find_loop_exit_1([], _, _) -> + %% None of clauses transfers control to a common block after the receive + %% statement. That means that the receive statement is a the end of a + %% function (or that all clauses raise exceptions). + none. %% find_rm_blocks(StartLabel, Blocks) -> [Label]. %% Find all blocks that start with remove_message within the receive diff --git a/lib/compiler/src/beam_ssa_share.erl b/lib/compiler/src/beam_ssa_share.erl index 426efa2cc9..73983bd34a 100644 --- a/lib/compiler/src/beam_ssa_share.erl +++ b/lib/compiler/src/beam_ssa_share.erl @@ -303,8 +303,12 @@ canonical_is([#b_ret{arg=Arg}], VarMap, Acc0) -> Acc0 end, {{ret,canonical_arg(Arg, VarMap),Acc1},VarMap}; -canonical_is([#b_br{bool=#b_var{},fail=Fail}], VarMap, Acc) -> - {{br,succ,Fail,Acc},VarMap}; +canonical_is([#b_br{bool=#b_var{}=Arg,fail=Fail}], VarMap, Acc) -> + %% A previous buggy version of this code omitted the canonicalized + %% argument in the return value. Unfortunately, that worked most + %% of the time, except when `br` terminator referenced a variable + %% defined in a previous block instead of in the same block. + {{br,canonical_arg(Arg, VarMap),succ,Fail,Acc},VarMap}; canonical_is([#b_br{succ=Succ}], VarMap, Acc) -> {{br,Succ,Acc},VarMap}; canonical_is([], VarMap, Acc) -> diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl index 06b42f1928..3c06c83e2e 100644 --- a/lib/compiler/src/beam_ssa_type.erl +++ b/lib/compiler/src/beam_ssa_type.erl @@ -23,8 +23,8 @@ -include("beam_ssa_opt.hrl"). -import(lists, [all/2,any/2,droplast/1,foldl/3,last/1,member/2, - keyfind/3,partition/2,reverse/1,reverse/2, - seq/2,sort/1,split/2]). + keyfind/3,reverse/1,reverse/2, + sort/1,split/2]). -define(UNICODE_INT, #t_integer{elements={0,16#10FFFF}}). @@ -160,6 +160,10 @@ opt_finish_1([Arg | Args], [TypeMap | TypeMaps], ParamInfo0) -> case join(maps:values(TypeMap)) of any -> opt_finish_1(Args, TypeMaps, ParamInfo0); + none -> + %% This function will never be called. Pretend that we don't + %% know the type for this argument. + opt_finish_1(Args, TypeMaps, ParamInfo0); JoinedType -> JoinedType = verified_type(JoinedType), ParamInfo = ParamInfo0#{ Arg => validator_anno(JoinedType) }, @@ -840,15 +844,8 @@ type({bif,Bif}, Args, Ts, _Ds) -> Type -> Type end; -type(bs_init, [#b_literal{val=Type}|Args], _Ts, _Ds) -> - case {Type,Args} of - {new,[_,#b_literal{val=Unit}]} -> - {binary,Unit}; - {append,[_,_,#b_literal{val=Unit}]} -> - {binary,Unit}; - {private_append,[_,_,#b_literal{val=Unit}]} -> - {binary,Unit} - end; +type(bs_init, _Args, _Ts, _Ds) -> + {binary, 1}; type(bs_extract, [Ctx], Ts, _Ds) -> #t_bs_match{type=Type} = get_type(Ctx, Ts), Type; @@ -874,11 +871,11 @@ type(call, [#b_remote{mod=#b_literal{val=Mod}, true -> none end; - {#t_integer{elements={Min,Max}}, + {#t_integer{elements={Min,_}}=IntType, #t_tuple{elements=Es0,size=Size}=T} -> - %% We know this will land between Min and Max, so kill the - %% types for those indexes. - Es = maps:without(seq(Min, Max), Es0), + %% Remove type information for all indices that + %% falls into the range of the integer. + Es = remove_element_info(IntType, Es0), case T#t_tuple.exact of false -> T#t_tuple{elements=Es,size=max(Min, Size)}; @@ -896,11 +893,15 @@ type(call, [#b_remote{mod=#b_literal{val=Mod}, {_,_} -> #t_tuple{} end; - {erlang,'++',[List1,List2]} -> - case get_type(List1, Ts) =:= cons orelse - get_type(List2, Ts) =:= cons of - true -> cons; - false -> list + {erlang,'++',[LHS,RHS]} -> + LType = get_type(LHS, Ts), + RType = get_type(RHS, Ts), + case LType =:= cons orelse RType =:= cons of + true -> + cons; + false -> + %% `[] ++ RHS` yields RHS, even if RHS is not a list. + join(list, RType) end; {erlang,'--',[_,_]} -> list; @@ -1388,24 +1389,11 @@ get_type(#b_literal{val=Val}, _Ts) -> %% type for L. For example, if L was known to be 'list', subtracting %% 'cons' would give 'nil' as the only possible type. The result of the %% subtraction for L will be added to FailTypes. -%% -%% Here is another example, asking about the variable Bool: -%% -%% Head = bif:hd L -%% Bool = succeeded Head -%% -%% 'succeeded Head' will evaluate to 'true' if the instrution that -%% defined Head succeeded. In this case, it is the 'bif:hd L' -%% instruction, which will succeed if L is 'cons'. Thus, the meet of -%% the previous type for L and 'cons' will be added to SuccTypes. -%% -%% If 'succeeded Head' evaluates to 'false', it means that 'bif:hd L' -%% failed and that L is not 'cons'. 'cons' can be subtracted from the -%% previously known type for L and the result put in FailTypes. infer_types_br(#b_var{}=V, Ts, #d{ds=Ds}) -> #{V:=#b_set{op=Op,args=Args}} = Ds, - Types0 = infer_type(Op, Args, Ds), + PosTypes0 = infer_type(Op, Args, Ds), + NegTypes0 = infer_type_negative(Op, Args, Ds), %% We must be careful with types inferred from '=:='. %% @@ -1416,13 +1404,17 @@ infer_types_br(#b_var{}=V, Ts, #d{ds=Ds}) -> %% %% However, it is safe to subtract a type inferred from '=:=' if %% it is single-valued, e.g. if it is [] or the atom 'true'. - EqTypes0 = infer_eq_type(Op, Args, Ts, Ds), - {Types1,EqTypes} = partition(fun({_,T}) -> - is_singleton_type(T) - end, EqTypes0), - Types = Types1 ++ Types0, - {meet_types(EqTypes++Types, Ts),subtract_types(Types, Ts)}. + EqTypes = infer_eq_type(Op, Args, Ts, Ds), + NegTypes1 = [P || {_,T}=P <- EqTypes, is_singleton_type(T)], + + PosTypes = EqTypes ++ PosTypes0, + SuccTs = meet_types(PosTypes, Ts), + + NegTypes = NegTypes0 ++ NegTypes1, + FailTs = subtract_types(NegTypes, Ts), + + {SuccTs,FailTs}. infer_types_switch(V, Lit, Ts, #d{ds=Ds}) -> Types = infer_eq_type({bif,'=:='}, [V, Lit], Ts, Ds), @@ -1457,6 +1449,19 @@ infer_eq_lit(#b_set{op=get_tuple_element, [{Tuple,#t_tuple{size=Index,elements=Es}}]; infer_eq_lit(_, _) -> []. +infer_type_negative(Op, Args, Ds) -> + case is_negative_inference_safe(Op, Args) of + true -> + infer_type(Op, Args, Ds); + false -> + [] + end. + +%% Conservative list of instructions for which negative +%% inference is safe. +is_negative_inference_safe(is_nonempty_list, _Args) -> true; +is_negative_inference_safe(_, _) -> false. + infer_type({bif,element}, [#b_literal{val=Pos},#b_var{}=Tuple], _Ds) -> if is_integer(Pos), 1 =< Pos -> @@ -1649,6 +1654,12 @@ get_literal_from_type(nil) -> #b_literal{val=[]}; get_literal_from_type(_) -> none. +remove_element_info(#t_integer{elements={Min,Max}}, Es) -> + foldl(fun(El, Acc) when Min =< El, El =< Max -> + maps:remove(El, Acc); + (_El, Acc) -> Acc + end, Es, maps:keys(Es)). + t_atom() -> #t_atom{elements=any}. diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index 09a5a6c104..349d74eb58 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -1068,8 +1068,11 @@ verify_get_map(Fail, Src, List, Vst0) -> %% {get_map_elements,{f,7},{x,1},{list,[{atom,a},{x,1},{atom,b},{x,2}]}}. %% %% If 'a' exists but not 'b', {x,1} is overwritten when we jump to {f,7}. +%% +%% We must be careful to preserve the uninitialized status for Y registers +%% that have been allocated but not yet defined. clobber_map_vals([Key,Dst|T], Map, Vst0) -> - case is_reg_defined(Dst, Vst0) of + case is_reg_initialized(Dst, Vst0) of true -> Vst = extract_term(term, {bif,map_get}, [Key, Map], Dst, Vst0), clobber_map_vals(T, Map, Vst); @@ -1079,6 +1082,17 @@ clobber_map_vals([Key,Dst|T], Map, Vst0) -> clobber_map_vals([], _Map, Vst) -> Vst. +is_reg_initialized({x,_}=Reg, #vst{current=#st{xs=Xs}}) -> + is_map_key(Reg, Xs); +is_reg_initialized({y,_}=Reg, #vst{current=#st{ys=Ys}}) -> + case Ys of + #{Reg:=Val} -> + Val =/= uninitialized; + #{} -> + false + end; +is_reg_initialized(V, #vst{}) -> error({not_a_register, V}). + extract_map_keys([Key,_Val|T]) -> [Key|extract_map_keys(T)]; extract_map_keys([]) -> []. @@ -1604,13 +1618,8 @@ infer_types_1(#value{op={bif,'=:='},args=[LHS,RHS]}) -> end; infer_types_1(#value{op={bif,element},args=[{integer,Index}=Key,Tuple]}) -> fun(Val, S) -> - case is_value_alive(Tuple, S) of - true -> - Type = {tuple,[Index], #{ Key => get_term_type(Val, S) }}, - update_type(fun meet/2, Type, Tuple, S); - false -> - S - end + Type = {tuple,[Index], #{ Key => get_term_type(Val, S) }}, + update_type(fun meet/2, Type, Tuple, S) end; infer_types_1(#value{op={bif,is_atom},args=[Src]}) -> infer_type_test_bif({atom,[]}, Src); @@ -1634,10 +1643,7 @@ infer_types_1(#value{op={bif,is_tuple},args=[Src]}) -> infer_type_test_bif({tuple,[0],#{}}, Src); infer_types_1(#value{op={bif,tuple_size}, args=[Tuple]}) -> fun({integer,Arity}, S) -> - case is_value_alive(Tuple, S) of - true -> update_type(fun meet/2, {tuple,Arity,#{}}, Tuple, S); - false -> S - end; + update_type(fun meet/2, {tuple,Arity,#{}}, Tuple, S); (_, S) -> S end; infer_types_1(_) -> @@ -1645,10 +1651,7 @@ infer_types_1(_) -> infer_type_test_bif(Type, Src) -> fun({atom,true}, S) -> - case is_value_alive(Src, S) of - true -> update_type(fun meet/2, Type, Src, S); - false -> S - end; + update_type(fun meet/2, Type, Src, S); (_, S) -> S end. @@ -1885,10 +1888,6 @@ check_try_catch_tags(Type, {y,N}=Reg, Vst) -> ok end. -is_reg_defined({x,_}=Reg, #vst{current=#st{xs=Xs}}) -> is_map_key(Reg, Xs); -is_reg_defined({y,_}=Reg, #vst{current=#st{ys=Ys}}) -> is_map_key(Reg, Ys); -is_reg_defined(V, #vst{}) -> error({not_a_register, V}). - assert_term(Src, Vst) -> _ = get_term_type(Src, Vst), ok. @@ -2285,9 +2284,6 @@ get_raw_type(#value_ref{}=Ref, #vst{current=#st{vs=Vs}}) -> get_raw_type(Src, #vst{}) -> get_literal_type(Src). -is_value_alive(#value_ref{}=Ref, #vst{current=#st{vs=Vs}}) -> - is_map_key(Ref, Vs). - get_literal_type(nil=T) -> T; get_literal_type({atom,A}=T) when is_atom(A) -> T; get_literal_type({float,F}=T) when is_float(F) -> T; @@ -2469,25 +2465,44 @@ merge_vrefs(RefA, RefB, Merge, Counter) -> merge_values(Merge, VsA, VsB) -> maps:fold(fun(Spec, New, Acc) -> - merge_values_1(Spec, New, VsA, VsB, Acc) + mv_1(Spec, New, VsA, VsB, Acc) end, #{}, Merge). -merge_values_1(Same, Same, VsA, VsB, Acc) -> +mv_1(Same, Same, VsA, VsB, Acc0) -> %% We're merging different versions of the same value, so it's safe to %% reuse old entries if the type's unchanged. - #value{type=TypeA}=EntryA = map_get(Same, VsA), - #value{type=TypeB}=EntryB = map_get(Same, VsB), + #value{type=TypeA,args=Args}=EntryA = map_get(Same, VsA), + #value{type=TypeB,args=Args}=EntryB = map_get(Same, VsB), + Entry = case join(TypeA, TypeB) of TypeA -> EntryA; TypeB -> EntryB; JoinedType -> EntryA#value{type=JoinedType} end, - Acc#{ Same => Entry }; -merge_values_1({RefA, RefB}, New, VsA, VsB, Acc) -> + + Acc = Acc0#{ Same => Entry }, + + %% Type inference may depend on values that are no longer reachable from a + %% register, so all arguments must be merged into the new state. + mv_args(Args, VsA, VsB, Acc); +mv_1({RefA, RefB}, New, VsA, VsB, Acc) -> #value{type=TypeA} = map_get(RefA, VsA), #value{type=TypeB} = map_get(RefB, VsB), Acc#{ New => #value{op=join,args=[],type=join(TypeA, TypeB)} }. +mv_args([#value_ref{}=Arg | Args], VsA, VsB, Acc0) -> + case Acc0 of + #{ Arg := _ } -> + mv_args(Args, VsA, VsB, Acc0); + #{} -> + Acc = mv_1(Arg, Arg, VsA, VsB, Acc0), + mv_args(Args, VsA, VsB, Acc) + end; +mv_args([_ | Args], VsA, VsB, Acc) -> + mv_args(Args, VsA, VsB, Acc); +mv_args([], _VsA, _VsB, Acc) -> + Acc. + merge_fragility(FragileA, FragileB) -> cerl_sets:union(FragileA, FragileB). @@ -2844,10 +2859,14 @@ call_return_type_1(erlang, setelement, 3, Vst) -> setelement(3, TupleType, #{}) end; call_return_type_1(erlang, '++', 2, Vst) -> - case get_term_type({x,0}, Vst) =:= cons orelse - get_term_type({x,1}, Vst) =:= cons of - true -> cons; - false -> list + LType = get_term_type({x,0}, Vst), + RType = get_term_type({x,1}, Vst), + case LType =:= cons orelse RType =:= cons of + true -> + cons; + false -> + %% `[] ++ RHS` yields RHS, even if RHS is not a list + join(list, RType) end; call_return_type_1(erlang, '--', 2, _Vst) -> list; diff --git a/lib/compiler/src/cerl.erl b/lib/compiler/src/cerl.erl index 62cd5b5120..bc28f58712 100644 --- a/lib/compiler/src/cerl.erl +++ b/lib/compiler/src/cerl.erl @@ -263,7 +263,7 @@ %% @see subtrees/1 %% @see meta/1 --type ctype() :: 'alias' | 'apply' | 'binary' | 'bitrst' | 'call' | 'case' +-type ctype() :: 'alias' | 'apply' | 'binary' | 'bitstr' | 'call' | 'case' | 'catch' | 'clause' | 'cons' | 'fun' | 'let' | 'letrec' | 'literal' | 'map' | 'map_pair' | 'module' | 'primop' | 'receive' | 'seq' | 'try' | 'tuple' | 'values' | 'var'. diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index 28db8986ff..0325c714d0 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -268,8 +268,11 @@ expand_opt(r21, Os) -> [no_put_tuple2 | expand_opt(no_bsm3, Os)]; expand_opt({debug_info_key,_}=O, Os) -> [encrypt_debug_info,O|Os]; -expand_opt(no_type_opt, Os) -> - [no_ssa_opt_type_start, +expand_opt(no_type_opt=O, Os) -> + %% Be sure to keep the no_type_opt option so that it will + %% be recorded in the BEAM file, allowing the test suites + %% to recompile the file with this option. + [O,no_ssa_opt_type_start, no_ssa_opt_type_continue, no_ssa_opt_type_finish | Os]; expand_opt(O, Os) -> [O|Os]. diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl index 3699c9d22e..007a0247f4 100644 --- a/lib/compiler/src/v3_core.erl +++ b/lib/compiler/src/v3_core.erl @@ -1811,7 +1811,8 @@ force_safe(Ce, St0) -> is_safe(#c_cons{}) -> true; is_safe(#c_tuple{}) -> true; -is_safe(#c_var{}) -> true; +is_safe(#c_var{name={_,_}}) -> false; %Fun. Not safe. +is_safe(#c_var{name=_}) -> true; %Ordinary variable. is_safe(#c_literal{}) -> true; is_safe(_) -> false. diff --git a/lib/compiler/test/Makefile b/lib/compiler/test/Makefile index db8eb7e2e1..7be23fbb93 100644 --- a/lib/compiler/test/Makefile +++ b/lib/compiler/test/Makefile @@ -109,6 +109,8 @@ NO_MOD_OPT = $(NO_OPT) NO_SSA_OPT = $(NO_OPT) +NO_TYPE_OPT = $(NO_OPT) + NO_OPT_MODULES= $(NO_OPT:%=%_no_opt_SUITE) NO_OPT_ERL_FILES= $(NO_OPT_MODULES:%=%.erl) POST_OPT_MODULES= $(NO_OPT:%=%_post_opt_SUITE) @@ -121,6 +123,8 @@ NO_MOD_OPT_MODULES= $(NO_MOD_OPT:%=%_no_module_opt_SUITE) NO_MOD_OPT_ERL_FILES= $(NO_MOD_OPT_MODULES:%=%.erl) NO_SSA_OPT_MODULES= $(NO_SSA_OPT:%=%_no_ssa_opt_SUITE) NO_SSA_OPT_ERL_FILES= $(NO_SSA_OPT_MODULES:%=%.erl) +NO_TYPE_OPT_MODULES= $(NO_TYPE_OPT:%=%_no_type_opt_SUITE) +NO_TYPE_OPT_ERL_FILES= $(NO_TYPE_OPT_MODULES:%=%.erl) ERL_FILES= $(MODULES:%=%.erl) CORE_FILES= $(CORE_MODULES:%=%.core) @@ -150,7 +154,7 @@ EBIN = . # ---------------------------------------------------- make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) $(NO_SSA_OPT_ERL_FILES) \ - $(INLINE_ERL_FILES) $(R21_ERL_FILES) $(NO_MOD_OPT_ERL_FILES) + $(INLINE_ERL_FILES) $(R21_ERL_FILES) $(NO_MOD_OPT_ERL_FILES) $(NO_TYPE_OPT_ERL_FILES) $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) \ > $(EMAKEFILE) $(ERL_TOP)/make/make_emakefile +no_copt +no_postopt \ @@ -169,6 +173,8 @@ make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) $(NO_SSA_OPT_ERL_FILES -o$(EBIN) $(NO_MOD_OPT_MODULES) >> $(EMAKEFILE) $(ERL_TOP)/make/make_emakefile +from_core $(ERL_COMPILE_FLAGS) \ -o$(EBIN) $(CORE_MODULES) >> $(EMAKEFILE) + $(ERL_TOP)/make/make_emakefile +no_type_opt $(ERL_COMPILE_FLAGS) \ + -o$(EBIN) $(NO_TYPE_OPT_MODULES) >> $(EMAKEFILE) tests debug opt: make_emakefile erl $(ERL_MAKE_FLAGS) -make @@ -202,6 +208,10 @@ docs: %_no_module_opt_SUITE.erl: %_SUITE.erl sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@ +%_no_type_opt_SUITE.erl: %_SUITE.erl + sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@ + + # ---------------------------------------------------- # Release Target # ---------------------------------------------------- @@ -216,7 +226,8 @@ release_tests_spec: make_emakefile $(INSTALL_DATA) $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) \ $(INLINE_ERL_FILES) $(R21_ERL_FILES) \ $(NO_MOD_OPT_ERL_FILES) \ - $(NO_SSA_OPT_ERL_FILES) "$(RELSYSDIR)" + $(NO_SSA_OPT_ERL_FILES) \ + $(NO_TYPE_OPT_ERL_FILES) "$(RELSYSDIR)" $(INSTALL_DATA) $(CORE_FILES) "$(RELSYSDIR)" for file in $(ERL_DUMMY_FILES); do \ module=`basename $$file .erl`; \ diff --git a/lib/compiler/test/beam_except_SUITE.erl b/lib/compiler/test/beam_except_SUITE.erl index 8e3b373d29..f52239f2a8 100644 --- a/lib/compiler/test/beam_except_SUITE.erl +++ b/lib/compiler/test/beam_except_SUITE.erl @@ -21,7 +21,8 @@ -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, - multiple_allocs/1,bs_get_tail/1,coverage/1]). + multiple_allocs/1,bs_get_tail/1,coverage/1, + binary_construction_allocation/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -32,7 +33,8 @@ groups() -> [{p,[parallel], [multiple_allocs, bs_get_tail, - coverage]}]. + coverage, + binary_construction_allocation]}]. init_per_suite(Config) -> test_lib:recompile(?MODULE), @@ -70,11 +72,25 @@ bs_get_tail(Config) -> {function_clause, [{?MODULE,bs_get_tail_1,[<<>>,0,0,Config],_}|_]}} = (catch bs_get_tail_1(id(<<>>), 0, 0, Config)), + + ok = bs_get_tail_2(<<"W">>, <<"X">>, <<"Z">>), + ok = bs_get_tail_2(<<"M">>, <<"X">>, <<"Z">>), + {'EXIT', + {function_clause, + [{?MODULE,do_get_bs_tail_2,[<<"A">>,<<"B">>,[],<<"C">>],_}|_]}} = + (catch bs_get_tail_2(<<"A">>, <<"B">>, <<"C">>)), + ok. bs_get_tail_1(<<_:32, Rest/binary>>, Z1, Z2, F1) -> {Rest,Z1,Z2,F1}. +bs_get_tail_2(A, B, C) -> + do_get_bs_tail_2(A, B, [], C). + +do_get_bs_tail_2(<<"W">>, <<"X">>, _, <<"Z">>) -> ok; +do_get_bs_tail_2(<<"M">>, <<"X">>, _, <<"Z">>) -> ok. + coverage(_) -> File = {file,"fake.erl"}, ok = fc(a), @@ -118,6 +134,20 @@ coverage(_) -> fake_function_clause(A) -> error(function_clause, [A,42.0]). + +binary_construction_allocation(_Config) -> + ok = do_binary_construction_allocation("PUT"), + ok. + +do_binary_construction_allocation(Req) -> + %% Allocation for building the error term was done by the + %% bs_init2 instruction. beam_except crashed because it expected + %% an explicit allocation instruction. + ok = case Req of + "POST" -> {error, <<"BAD METHOD ", Req/binary>>, Req}; + _ -> ok + end. + id(I) -> I. -file("fake.erl", 1). diff --git a/lib/compiler/test/beam_ssa_SUITE.erl b/lib/compiler/test/beam_ssa_SUITE.erl index 15cf9bcbf3..3b510f3528 100644 --- a/lib/compiler/test/beam_ssa_SUITE.erl +++ b/lib/compiler/test/beam_ssa_SUITE.erl @@ -22,7 +22,8 @@ -export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1, init_per_group/2,end_per_group/2, calls/1,tuple_matching/1,recv/1,maps/1, - cover_ssa_dead/1,combine_sw/1,share_opt/1]). + cover_ssa_dead/1,combine_sw/1,share_opt/1, + beam_ssa_dead_crash/1,stack_init/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -37,7 +38,9 @@ groups() -> maps, cover_ssa_dead, combine_sw, - share_opt + share_opt, + beam_ssa_dead_crash, + stack_init ]}]. init_per_suite(Config) -> @@ -188,6 +191,15 @@ recv(_Config) -> self() ! {[self(),r1],{2,99,<<"data">>}}, {Parent,r1,<<1:32,2:8,99:8,"data">>} = tricky_recv_4(), + %% Test tricky_recv_5/0. + self() ! 1, + a = tricky_recv_5(), + self() ! 2, + b = tricky_recv_5(), + + %% tricky_recv_6/0 is a compile-time error. + tricky_recv_6(), + ok. sync_wait_mon({Pid, Ref}, Timeout) -> @@ -293,6 +305,38 @@ tricky_recv_4() -> end, id({Pid,R,Request}). +%% beam_ssa_pre_codegen would accidentally create phi nodes on critical edges +%% when fixing up receives; the call to id/2 can either succeed or land in the +%% catch block, and we added a phi node to its immediate successor. +tricky_recv_5() -> + try + receive + X=1 -> + id(42), + a; + X=2 -> + b + end, + case X of + 1 -> a; + 2 -> b + end + catch + _:_ -> c + end. + +%% When fixing tricky_recv_5, we introduced a compiler crash when the common +%% exit block was ?BADARG_BLOCK and floats were in the picture. +tricky_recv_6() -> + RefA = make_ref(), + RefB = make_ref(), + receive + {RefA, Number} -> Number + 1.0; + {RefB, Number} -> Number + 2.0 + after 0 -> + ok + end. + maps(_Config) -> {'EXIT',{{badmatch,#{}},_}} = (catch maps_1(any)), ok. @@ -481,9 +525,11 @@ do_comb_sw_2(X) -> erase(?MODULE). share_opt(_Config) -> - ok = do_share_opt(0). + ok = do_share_opt_1(0), + ok = do_share_opt_2(), + ok. -do_share_opt(A) -> +do_share_opt_1(A) -> %% The compiler would be stuck in an infinite loop in beam_ssa_share. case A of 0 -> a; @@ -492,6 +538,104 @@ do_share_opt(A) -> end, receive after 1 -> ok end. +do_share_opt_2() -> + ok = sopt_2({[pointtopoint], [{dstaddr,any}]}, ok), + ok = sopt_2({[broadcast], [{broadaddr,any}]}, ok), + ok = sopt_2({[], []}, ok), + ok. + +sopt_2({Flags, Opts}, ok) -> + Broadcast = lists:member(broadcast, Flags), + P2P = lists:member(pointtopoint, Flags), + case Opts of + %% The following two clauses would be combined to one, silently + %% discarding the guard test of the P2P variable. + [{broadaddr,_}|Os] when Broadcast -> + sopt_2({Flags, Os}, ok); + [{dstaddr,_}|Os] when P2P -> + sopt_2({Flags, Os}, ok); + [] -> + ok + end. + +beam_ssa_dead_crash(_Config) -> + not_A_B = do_beam_ssa_dead_crash(id(false), id(true)), + not_A_not_B = do_beam_ssa_dead_crash(false, false), + neither = do_beam_ssa_dead_crash(true, false), + neither = do_beam_ssa_dead_crash(true, true), + ok. + +do_beam_ssa_dead_crash(A, B) -> + %% beam_ssa_dead attempts to shortcut branches that branch other + %% branches. When a two-way branch is encountered, beam_ssa_dead + %% will simulate execution along both paths, in the hope that both + %% paths happens to end up in the same place. + %% + %% During the simulated execution of this function, the boolean + %% varible for a `br` instruction would be replaced with the + %% literal atom `nil`, which is not allowed, and would crash the + %% compiler. In practice, during the actual execution, control + %% would never be transferred to that `br` instruction when the + %% variable in question had the value `nil`. + %% + %% beam_ssa_dead has been updated to immediately abort the search + %% along the current path if there is an attempt to substitute a + %% non-boolean value into a `br` instruction. + + case + case not A of + false -> + false; + true -> + B + end + of + V + when + V /= nil + andalso + V /= false -> + not_A_B; + _ -> + case + case not A of + false -> + false; + true -> + not B + end + of + true -> + not_A_not_B; + false -> + neither + end + end. + +stack_init(_Config) -> + 6 = stack_init(a, #{a => [1,2,3]}), + 0 = stack_init(missing, #{}), + ok. + +stack_init(Key, Map) -> + %% beam_ssa_codegen would wrongly assume that y(0) would always be + %% initialized by the `get_map_elements` instruction that follows, and + %% would set up the stack frame using an `allocate` instruction and + %% would not generate an `init` instruction to initialize y(0). + Res = case Map of + #{Key := Elements} -> + %% Elements will be assigned to y(0) if the key Key exists. + lists:foldl(fun(El, Acc) -> + Acc + El + end, 0, Elements); + #{} -> + %% y(0) will be left uninitialized when the key is not + %% present in the map. + 0 + end, + %% y(0) would be uninitialized here if the key was not present in the map + %% (if the second clause was executed). + id(Res). %% The identity function. id(I) -> I. diff --git a/lib/compiler/test/beam_type_SUITE.erl b/lib/compiler/test/beam_type_SUITE.erl index 882e281a44..a99dee48aa 100644 --- a/lib/compiler/test/beam_type_SUITE.erl +++ b/lib/compiler/test/beam_type_SUITE.erl @@ -24,7 +24,8 @@ integers/1,numbers/1,coverage/1,booleans/1,setelement/1, cons/1,tuple/1,record_float/1,binary_float/1,float_compare/1, arity_checks/1,elixir_binaries/1,find_best/1, - test_size/1,cover_lists_functions/1]). + test_size/1,cover_lists_functions/1,list_append/1,bad_binary_unit/1, + none_argument/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -47,7 +48,10 @@ groups() -> elixir_binaries, find_best, test_size, - cover_lists_functions + cover_lists_functions, + list_append, + bad_binary_unit, + none_argument ]}]. init_per_suite(Config) -> @@ -271,8 +275,22 @@ setelement(_Config) -> T0 = id({a,42}), {a,_} = T0, {b,_} = setelement(1, T0, b), + {z,b} = do_setelement_1(<<(id(1)):32>>, {a,b}, z), + {new,two} = do_setelement_2(<<(id(1)):1>>, {one,two}, new), ok. +do_setelement_1(<<N:32>>, Tuple, NewValue) -> + _ = element(N, Tuple), + %% While updating the type for Tuple, beam_ssa_type would do: + %% maps:without(lists:seq(0, 4294967295), Elements) + setelement(N, Tuple, NewValue). + +do_setelement_2(<<N:1>>, Tuple, NewValue) -> + %% Cover the second clause in remove_element_info/2. The + %% type for the second element will be kept. + two = element(2, Tuple), + setelement(N, Tuple, NewValue). + cons(_Config) -> [did] = cons(assigned, did), @@ -487,5 +505,39 @@ cover_lists_functions(Config) -> true = is_list(Zipped), ok. +list_append(_Config) -> + %% '++'/2 has a quirk where it returns the right-hand argument as-is when + %% the left-hand is []. + hello = id([]) ++ id(hello), + ok. + +%% OTP-15872: The compiler would treat the "Unit" of bs_init instructions as +%% the unit of the result instead of the required unit of the input, causing +%% is_binary checks to be wrongly optimized away. +bad_binary_unit(_Config) -> + Bin = id(<<1,2,3>>), + Bitstring = <<Bin/binary,1:1>>, + false = is_binary(Bitstring), + ok. + +%% ERL-1013: The compiler would crash during the type optimization pass. +none_argument(_Config) -> + Binary = id(<<3:16, 42>>), + error = id(case Binary of + <<Len:16, Body/binary>> when length(Body) == Len - 2 -> + %% The type for Body will be none. It means + %% that this clause will never match and that + %% uncompress/1 will never be called. + uncompress(Body); + _ -> + error + end), + ok. + +uncompress(CompressedBinary) -> + %% The type for CompressedBinary is none, which beam_ssa_type + %% did not handle properly. + zlib:uncompress(CompressedBinary). + id(I) -> I. diff --git a/lib/compiler/test/beam_validator_SUITE.erl b/lib/compiler/test/beam_validator_SUITE.erl index 6b1438abdd..20f6cb2691 100644 --- a/lib/compiler/test/beam_validator_SUITE.erl +++ b/lib/compiler/test/beam_validator_SUITE.erl @@ -681,11 +681,16 @@ infer_on_eq_4(T) -> %% ERIERL-348; types were inferred for dead values, causing validation to fail. +-record(idv, {key}). + infer_dead_value(Config) when is_list(Config) -> a = idv_1({a, b, c, d, e, f, g}, {0, 0, 0, 0, 0, 0, 0}), b = idv_1({a, b, c, d, 0, 0, 0}, {a, b, c, d, 0, 0, 0}), c = idv_1({0, 0, 0, 0, 0, f, g}, {0, 0, 0, 0, 0, f, g}), error = idv_1(gurka, gaffel), + + ok = idv_2(id(#idv{})), + ok. idv_1({_A, _B, _C, _D, _E, _F, _G}, @@ -700,6 +705,23 @@ idv_1({_A, _B, _C, _D, _E, F, G}, idv_1(_A, _B) -> error. +%% ERL-995: The first solution to ERIERL-348 was incomplete and caused +%% validation to fail when living values depended on delayed type inference on +%% "dead" values. + +idv_2(State) -> + Flag = (State#idv.key == undefined), + case id(gurka) of + {_} -> id([Flag]); + _ -> ok + end, + if + Flag -> idv_called_once(State); + true -> ok + end. + +idv_called_once(_State) -> ok. + %%%------------------------------------------------------------------------- transform_remove(Remove, Module) -> diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl index d97f49c56e..145a50f4ad 100644 --- a/lib/compiler/test/bs_match_SUITE.erl +++ b/lib/compiler/test/bs_match_SUITE.erl @@ -44,7 +44,8 @@ beam_bsm/1,guard/1,is_ascii/1,non_opt_eq/1, expression_before_match/1,erl_689/1,restore_on_call/1, restore_after_catch/1,matches_on_parameter/1,big_positions/1, - matching_meets_apply/1,bs_start_match2_defs/1]). + matching_meets_apply/1,bs_start_match2_defs/1, + exceptions_after_match_failure/1]). -export([coverage_id/1,coverage_external_ignore/2]). @@ -80,7 +81,8 @@ groups() -> beam_bsm,guard,is_ascii,non_opt_eq, expression_before_match,erl_689,restore_on_call, matches_on_parameter,big_positions, - matching_meets_apply,bs_start_match2_defs]}]. + matching_meets_apply,bs_start_match2_defs, + exceptions_after_match_failure]}]. init_per_suite(Config) -> @@ -2005,4 +2007,17 @@ do_matching_meets_apply(_Bin, {Handler, State}) -> %% Another case of the above. Handler:abs(State). +%% Exception handling was broken on the failure path of bs_start_match as +%% beam_ssa_bsm accidentally cloned and renamed the ?BADARG_BLOCK. +exceptions_after_match_failure(_Config) -> + {'EXIT', {badarith, _}} = (catch do_exceptions_after_match_failure(atom)), + ok = do_exceptions_after_match_failure(<<0, 1, "gurka">>), + ok = do_exceptions_after_match_failure(2.0). + +do_exceptions_after_match_failure(<<_A, _B, "gurka">>) -> + ok; +do_exceptions_after_match_failure(Other) -> + Other / 2.0, + ok. + id(I) -> I. diff --git a/lib/compiler/test/core_SUITE.erl b/lib/compiler/test/core_SUITE.erl index e5611e99d1..72016c6d76 100644 --- a/lib/compiler/test/core_SUITE.erl +++ b/lib/compiler/test/core_SUITE.erl @@ -29,7 +29,8 @@ bs_shadowed_size_var/1, cover_v3_kernel_1/1,cover_v3_kernel_2/1,cover_v3_kernel_3/1, cover_v3_kernel_4/1,cover_v3_kernel_5/1, - non_variable_apply/1,name_capture/1,fun_letrec_effect/1]). + non_variable_apply/1,name_capture/1,fun_letrec_effect/1, + get_map_element/1]). -include_lib("common_test/include/ct.hrl"). @@ -57,7 +58,8 @@ groups() -> bs_shadowed_size_var, cover_v3_kernel_1,cover_v3_kernel_2,cover_v3_kernel_3, cover_v3_kernel_4,cover_v3_kernel_5, - non_variable_apply,name_capture,fun_letrec_effect + non_variable_apply,name_capture,fun_letrec_effect, + get_map_element ]}]. @@ -95,6 +97,7 @@ end_per_group(_GroupName, Config) -> ?comp(non_variable_apply). ?comp(name_capture). ?comp(fun_letrec_effect). +?comp(get_map_element). try_it(Mod, Conf) -> Src = filename:join(proplists:get_value(data_dir, Conf), diff --git a/lib/compiler/test/core_SUITE_data/get_map_element.core b/lib/compiler/test/core_SUITE_data/get_map_element.core new file mode 100644 index 0000000000..092b5e71eb --- /dev/null +++ b/lib/compiler/test/core_SUITE_data/get_map_element.core @@ -0,0 +1,18 @@ +module 'get_map_element' ['get_map_element'/0] +attributes [] + +'get_map_element'/0 = + fun () -> + apply 'match_map'/1(~{'foo'=>'bar'}~) + +'match_map'/1 = + fun (_0) -> + case _0 of + <~{'foo':='bar'}~> when 'true' -> + 'ok' + %% It will be undefined behaviour at runtime if no + %% clause of the case can be selected. That can't + %% happen for this module, because match_map/1 is + %% always called with a matching map argument. + end +end diff --git a/lib/compiler/test/fun_SUITE.erl b/lib/compiler/test/fun_SUITE.erl index 1df0a05275..7fc6195e31 100644 --- a/lib/compiler/test/fun_SUITE.erl +++ b/lib/compiler/test/fun_SUITE.erl @@ -22,7 +22,8 @@ -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, test1/1,overwritten_fun/1,otp_7202/1,bif_fun/1, - external/1,eep37/1,eep37_dup/1,badarity/1,badfun/1]). + external/1,eep37/1,eep37_dup/1,badarity/1,badfun/1, + duplicated_fun/1]). %% Internal exports. -export([call_me/1,dup1/0,dup2/0]). @@ -37,7 +38,7 @@ all() -> groups() -> [{p,[parallel], [test1,overwritten_fun,otp_7202,bif_fun,external,eep37, - eep37_dup,badarity,badfun]}]. + eep37_dup,badarity,badfun,duplicated_fun]}]. init_per_suite(Config) -> test_lib:recompile(?MODULE), @@ -261,5 +262,20 @@ badfun(_Config) -> expect_badfun(Term, Exit) -> {'EXIT',{{badfun,Term},_}} = Exit. +duplicated_fun(_Config) -> + try + %% The following code used to crash the compiler before + %% v3_core:is_safe/1 was corrected to consider fun variables + %% unsafe. + id([print_result_paths_fun = fun duplicated_fun_helper/1]), + ct:error(should_fail) + catch + error:{badmatch,F} when is_function(F, 1) -> + ok + end. + +duplicated_fun_helper(_) -> + ok. + id(I) -> I. diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl index ed0a56f064..cea7a374cd 100644 --- a/lib/compiler/test/guard_SUITE.erl +++ b/lib/compiler/test/guard_SUITE.erl @@ -35,7 +35,8 @@ basic_andalso_orelse/1,traverse_dcd/1, check_qlc_hrl/1,andalso_semi/1,t_tuple_size/1,binary_part/1, bad_constants/1,bad_guards/1, - guard_in_catch/1,beam_bool_SUITE/1]). + guard_in_catch/1,beam_bool_SUITE/1, + repeated_type_tests/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -53,7 +54,8 @@ groups() -> rel_ops,rel_op_combinations, literal_type_tests,basic_andalso_orelse,traverse_dcd, check_qlc_hrl,andalso_semi,t_tuple_size,binary_part, - bad_constants,bad_guards,guard_in_catch,beam_bool_SUITE]}]. + bad_constants,bad_guards,guard_in_catch,beam_bool_SUITE, + repeated_type_tests]}]. init_per_suite(Config) -> test_lib:recompile(?MODULE), @@ -2261,6 +2263,25 @@ maps() -> evidence(#{0 := Charge}) when 0; #{[] => Charge} == #{[] => 42} -> ok. +repeated_type_tests(_Config) -> + binary = repeated_type_test(<<42>>), + bitstring = repeated_type_test(<<1:1>>), + other = repeated_type_test(atom), + ok. + +repeated_type_test(T) -> + %% Test for a bug in beam_ssa_dead. + if is_bitstring(T) -> + if is_binary(T) -> %This test would be optimized away. + binary; + true -> + bitstring + end; + true -> + other + end. + + %% Call this function to turn off constant propagation. id(I) -> I. diff --git a/lib/compiler/test/match_SUITE.erl b/lib/compiler/test/match_SUITE.erl index 94bfbb0efe..bc74ec4984 100644 --- a/lib/compiler/test/match_SUITE.erl +++ b/lib/compiler/test/match_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2018. 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. @@ -25,7 +25,8 @@ match_in_call/1,untuplify/1,shortcut_boolean/1,letify_guard/1, selectify/1,deselectify/1,underscore/1,match_map/1,map_vars_used/1, coverage/1,grab_bag/1,literal_binary/1, - unary_op/1,eq_types/1,match_after_return/1]). + unary_op/1,eq_types/1,match_after_return/1,match_right_tuple/1, + tuple_size_in_try/1]). -include_lib("common_test/include/ct.hrl"). @@ -41,7 +42,8 @@ groups() -> shortcut_boolean,letify_guard,selectify,deselectify, underscore,match_map,map_vars_used,coverage, grab_bag,literal_binary,unary_op,eq_types, - match_after_return]}]. + match_after_return,match_right_tuple, + tuple_size_in_try]}]. init_per_suite(Config) -> @@ -902,4 +904,39 @@ match_after_return(Config) when is_list(Config) -> mar_test_tuple(I) -> {gurka, I}. +match_right_tuple(Config) when is_list(Config) -> + %% The loader wrongly coalesced certain get_tuple_element sequences, fusing + %% the code below into a single i_get_tuple_element2 operating on {x,0} + %% even though the first one overwrites it. + %% + %% {get_tuple_element,{x,0},0,{x,0}}. + %% {get_tuple_element,{x,0},1,{x,1}}. + + Inner = {id(wrong_element), id(ok)}, + Outer = {Inner, id(wrong_tuple)}, + ok = match_right_tuple_1(Outer). + +match_right_tuple_1(T) -> + {A, _} = T, + {_, B} = A, + %% The call ensures that A is in {x,0} and B is in {x,1} + id(force_succ_regs(A, B)). + +force_succ_regs(_A, B) -> B. + +tuple_size_in_try(Config) when is_list(Config) -> + %% The tuple_size optimization was applied outside of guards, causing + %% either the emulator or compiler to crash. + ok = tsit(gurka), + ok = tsit(gaffel). + +tsit(A) -> + try + id(ignored), + 1 = tuple_size(A), + error + catch + _:_ -> ok + end. + id(I) -> I. diff --git a/lib/compiler/test/receive_SUITE.erl b/lib/compiler/test/receive_SUITE.erl index 0038eb1a4b..8cd864c59e 100644 --- a/lib/compiler/test/receive_SUITE.erl +++ b/lib/compiler/test/receive_SUITE.erl @@ -26,7 +26,7 @@ init_per_testcase/2,end_per_testcase/2, export/1,recv/1,coverage/1,otp_7980/1,ref_opt/1, wait/1,recv_in_try/1,double_recv/1,receive_var_zero/1, - match_built_terms/1]). + match_built_terms/1,elusive_common_exit/1]). -include_lib("common_test/include/ct.hrl"). @@ -47,7 +47,7 @@ groups() -> [{p,test_lib:parallel(), [recv,coverage,otp_7980,ref_opt,export,wait, recv_in_try,double_recv,receive_var_zero, - match_built_terms]}]. + match_built_terms,elusive_common_exit]}]. init_per_suite(Config) -> @@ -427,4 +427,61 @@ match_built_terms(Config) when is_list(Config) -> ?MATCH_BUILT_TERM(Ref, <<A, B>>), ?MATCH_BUILT_TERM(Ref, #{ 1 => A, 2 => B}). +elusive_common_exit(_Config) -> + self() ! {1, a}, + self() ! {2, b}, + {[z], [{2,b},{1,a}]} = elusive_loop([x,y,z], 2, []), + + CodeServer = whereis(code_server), + Self = self(), + Self ! {Self, abc}, + Self ! {CodeServer, []}, + Self ! {Self, other}, + try elusive2([]) of + Unexpected -> + ct:fail("Expected an exception; got ~p\n", [Unexpected]) + catch + throw:[other, CodeServer, Self] -> + ok + end, + + ok. + +elusive_loop(List, 0, Results) -> + {List, Results}; +elusive_loop(List, ToReceive, Results) -> + {Result, RemList} = + receive + {_Pos, _R} = Res when List =/= [] -> + [_H|T] = List, + {Res, T}; + {_Pos, _R} = Res when List =:= [] -> + {Res, []} + end, + %% beam_ssa_pre_codegen:fix_receives() would fail to find + %% the common exit block for this receive. That would mean + %% that it would not insert all necessary copy instructions. + elusive_loop(RemList, ToReceive-1, [Result | Results]). + + +elusive2(Acc) -> + receive + {Pid, abc} -> + ok; + {Pid, []} -> + ok; + {Pid, Res} -> + %% beam_ssa_pre_codegen:find_loop_exit/2 attempts to find + %% the first block of the common code after the receive + %% statement. It used to only look at the two last clauses + %% of the receive. In this function, the last two clauses + %% don't have any common block, so it would be assumed + %% that there was no common block for any of the + %% clauses. That would mean that copy instructions would + %% not be inserted as needed. + throw([Res | Acc]) + end, + %% Common code. + elusive2([Pid | Acc]). + id(I) -> I. diff --git a/lib/compiler/test/test_lib.erl b/lib/compiler/test/test_lib.erl index 3348c6e9ea..34410e4b2a 100644 --- a/lib/compiler/test/test_lib.erl +++ b/lib/compiler/test/test_lib.erl @@ -97,7 +97,8 @@ get_data_dir(Config) -> Data2 = re:replace(Data1, "_post_opt_SUITE", "_SUITE", Opts), Data3 = re:replace(Data2, "_inline_SUITE", "_SUITE", Opts), Data4 = re:replace(Data3, "_r21_SUITE", "_SUITE", Opts), - Data = re:replace(Data4, "_no_module_opt_SUITE", "_SUITE", Opts), + Data5 = re:replace(Data4, "_no_module_opt_SUITE", "_SUITE", Opts), + Data = re:replace(Data5, "_no_type_opt_SUITE", "_SUITE", Opts), re:replace(Data, "_no_ssa_opt_SUITE", "_SUITE", Opts). is_cloned_mod(Mod) -> diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk index 494de072ff..7192ddca15 100644 --- a/lib/compiler/vsn.mk +++ b/lib/compiler/vsn.mk @@ -1 +1 @@ -COMPILER_VSN = 7.4 +COMPILER_VSN = 7.4.4 diff --git a/lib/crypto/c_src/Makefile.in b/lib/crypto/c_src/Makefile.in index b6a65d7488..f922c3fb9b 100644 --- a/lib/crypto/c_src/Makefile.in +++ b/lib/crypto/c_src/Makefile.in @@ -92,16 +92,15 @@ CRYPTO_OBJS = $(OBJDIR)/crypto$(TYPEMARKER).o \ $(OBJDIR)/hash$(TYPEMARKER).o \ $(OBJDIR)/hmac$(TYPEMARKER).o \ $(OBJDIR)/info$(TYPEMARKER).o \ + $(OBJDIR)/mac$(TYPEMARKER).o \ $(OBJDIR)/math$(TYPEMARKER).o \ $(OBJDIR)/pkey$(TYPEMARKER).o \ - $(OBJDIR)/poly1305$(TYPEMARKER).o \ $(OBJDIR)/rand$(TYPEMARKER).o \ $(OBJDIR)/rsa$(TYPEMARKER).o \ $(OBJDIR)/srp$(TYPEMARKER).o CALLBACK_OBJS = $(OBJDIR)/crypto_callback$(TYPEMARKER).o NIF_MAKEFILE = $(PRIVDIR)/Makefile -CRYPTO_STATIC_OBJS = $(OBJDIR)/crypto_static$(TYPEMARKER).o\ - $(OBJDIR)/crypto_callback_static$(TYPEMARKER).o +CRYPTO_STATIC_OBJS = $(patsubst $(OBJDIR)/%$(TYPEMARKER).o,$(OBJDIR)/%_static$(TYPEMARKER).o,$(CRYPTO_OBJS) $(CALLBACK_OBJS)) NIF_ARCHIVE = $(LIBDIR)/crypto$(TYPEMARKER).a diff --git a/lib/crypto/c_src/algorithms.c b/lib/crypto/c_src/algorithms.c index 75cddeb1e9..53b8b7eaa9 100644 --- a/lib/crypto/c_src/algorithms.c +++ b/lib/crypto/c_src/algorithms.c @@ -20,13 +20,12 @@ #include "algorithms.h" #include "cipher.h" +#include "mac.h" static unsigned int algo_hash_cnt, algo_hash_fips_cnt; static ERL_NIF_TERM algo_hash[14]; /* increase when extending the list */ static unsigned int algo_pubkey_cnt, algo_pubkey_fips_cnt; static ERL_NIF_TERM algo_pubkey[12]; /* increase when extending the list */ -static unsigned int algo_mac_cnt, algo_mac_fips_cnt; -static ERL_NIF_TERM algo_mac[3]; /* increase when extending the list */ static unsigned int algo_curve_cnt, algo_curve_fips_cnt; static ERL_NIF_TERM algo_curve[89]; /* increase when extending the list */ static unsigned int algo_rsa_opts_cnt, algo_rsa_opts_fips_cnt; @@ -101,19 +100,6 @@ void init_algorithms_types(ErlNifEnv* env) #endif algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "srp"); - - // Validated algorithms first - algo_mac_cnt = 0; - algo_mac[algo_mac_cnt++] = enif_make_atom(env,"hmac"); -#ifdef HAVE_CMAC - algo_mac[algo_mac_cnt++] = enif_make_atom(env,"cmac"); -#endif -#ifdef HAVE_POLY1305 - algo_mac[algo_mac_cnt++] = enif_make_atom(env,"poly1305"); -#endif - // Non-validated algorithms follow - algo_mac_fips_cnt = algo_mac_cnt; - // Validated algorithms first algo_curve_cnt = 0; #if defined(HAVE_EC) @@ -250,7 +236,6 @@ void init_algorithms_types(ErlNifEnv* env) // Check that the max number of algos is updated ASSERT(algo_hash_cnt <= sizeof(algo_hash)/sizeof(ERL_NIF_TERM)); ASSERT(algo_pubkey_cnt <= sizeof(algo_pubkey)/sizeof(ERL_NIF_TERM)); - ASSERT(algo_mac_cnt <= sizeof(algo_mac)/sizeof(ERL_NIF_TERM)); ASSERT(algo_curve_cnt <= sizeof(algo_curve)/sizeof(ERL_NIF_TERM)); ASSERT(algo_rsa_opts_cnt <= sizeof(algo_rsa_opts)/sizeof(ERL_NIF_TERM)); } @@ -284,18 +269,12 @@ ERL_NIF_TERM cipher_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv return cipher_types_as_list(env); /* Exclude old api ciphers */ } + ERL_NIF_TERM mac_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - unsigned int cnt = -#ifdef FIPS_SUPPORT - FIPS_mode() ? algo_mac_fips_cnt : -#endif - algo_mac_cnt; - - return enif_make_list_from_array(env, algo_mac, cnt); + return mac_types_as_list(env); } - ERL_NIF_TERM curve_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { unsigned int cnt = diff --git a/lib/crypto/c_src/api_ng.c b/lib/crypto/c_src/api_ng.c index 3408ba1b88..941e03cc98 100644 --- a/lib/crypto/c_src/api_ng.c +++ b/lib/crypto/c_src/api_ng.c @@ -100,7 +100,7 @@ static int get_init_args(ErlNifEnv* env, } - if (FORBIDDEN_IN_FIPS(*cipherp)) + if (CIPHER_FORBIDDEN_IN_FIPS(*cipherp)) { *return_term = EXCP_NOTSUP(env, "Forbidden in FIPS"); goto err; @@ -334,12 +334,11 @@ ERL_NIF_TERM ng_crypto_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM arg if ((ctx_res = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx))) == NULL) return EXCP_ERROR(env, "Can't allocate resource"); - if (!get_init_args(env, ctx_res, argv[0], argv[1], argv[2], argv[argc-1], + if (get_init_args(env, ctx_res, argv[0], argv[1], argv[2], argv[argc-1], &cipherp, &ret)) - /* Error msg in &ret */ - goto ret; + ret = enif_make_resource(env, ctx_res); + /* else error msg in ret */ - ret = enif_make_resource(env, ctx_res); if(ctx_res) enif_release_resource(ctx_res); } else if (enif_get_resource(env, argv[0], (ErlNifResourceType*)evp_cipher_ctx_rtype, (void**)&ctx_res)) { diff --git a/lib/crypto/c_src/atoms.c b/lib/crypto/c_src/atoms.c index 059c14690f..bbeb329fa2 100644 --- a/lib/crypto/c_src/atoms.c +++ b/lib/crypto/c_src/atoms.c @@ -30,6 +30,10 @@ ERL_NIF_TERM atom_rsa_no_padding; ERL_NIF_TERM atom_signature_md; ERL_NIF_TERM atom_undefined; +ERL_NIF_TERM atom_hmac; +ERL_NIF_TERM atom_cmac; +ERL_NIF_TERM atom_poly1305; + ERL_NIF_TERM atom_ok; ERL_NIF_TERM atom_none; ERL_NIF_TERM atom_notsup; @@ -155,6 +159,11 @@ int init_atoms(ErlNifEnv *env, const ERL_NIF_TERM fips_mode, const ERL_NIF_TERM atom_rsa_no_padding = enif_make_atom(env,"rsa_no_padding"); atom_signature_md = enif_make_atom(env,"signature_md"); atom_undefined = enif_make_atom(env,"undefined"); + + atom_hmac = enif_make_atom(env,"hmac"); + atom_cmac = enif_make_atom(env,"cmac"); + atom_poly1305 = enif_make_atom(env,"poly1305"); + atom_ok = enif_make_atom(env,"ok"); atom_none = enif_make_atom(env,"none"); atom_notsup = enif_make_atom(env,"notsup"); diff --git a/lib/crypto/c_src/atoms.h b/lib/crypto/c_src/atoms.h index f5913de96f..0e2f1a0022 100644 --- a/lib/crypto/c_src/atoms.h +++ b/lib/crypto/c_src/atoms.h @@ -34,6 +34,10 @@ extern ERL_NIF_TERM atom_rsa_no_padding; extern ERL_NIF_TERM atom_signature_md; extern ERL_NIF_TERM atom_undefined; +extern ERL_NIF_TERM atom_hmac; +extern ERL_NIF_TERM atom_cmac; +extern ERL_NIF_TERM atom_poly1305; + extern ERL_NIF_TERM atom_ok; extern ERL_NIF_TERM atom_none; extern ERL_NIF_TERM atom_notsup; diff --git a/lib/crypto/c_src/bn.c b/lib/crypto/c_src/bn.c index 34ed4f7ebc..6021d56db6 100644 --- a/lib/crypto/c_src/bn.c +++ b/lib/crypto/c_src/bn.c @@ -32,8 +32,6 @@ int get_bn_from_mpint(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp) if (bin.size > INT_MAX - 4) goto err; - ERL_VALGRIND_ASSERT_MEM_DEFINED(bin.data, bin.size); - if (bin.size < 4) goto err; sz = (int)bin.size - 4; @@ -60,8 +58,6 @@ int get_bn_from_bin(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp) if (bin.size > INT_MAX) goto err; - ERL_VALGRIND_ASSERT_MEM_DEFINED(bin.data, bin.size); - if ((ret = BN_bin2bn(bin.data, (int)bin.size, NULL)) == NULL) goto err; @@ -103,8 +99,6 @@ ERL_NIF_TERM mod_exp_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) unsigned extra_byte; ERL_NIF_TERM ret; - ASSERT(argc == 4); - if (!get_bn_from_bin(env, argv[0], &bn_base)) goto bad_arg; if (!get_bn_from_bin(env, argv[1], &bn_exponent)) @@ -177,7 +171,6 @@ ERL_NIF_TERM bn2term(ErlNifEnv* env, const BIGNUM *bn) BN_bn2bin(bn, ptr); - ERL_VALGRIND_MAKE_MEM_DEFINED(ptr, dlen); return ret; err: diff --git a/lib/crypto/c_src/cipher.c b/lib/crypto/c_src/cipher.c index 00072af632..e144a891a6 100644 --- a/lib/crypto/c_src/cipher.c +++ b/lib/crypto/c_src/cipher.c @@ -214,7 +214,7 @@ ERL_NIF_TERM cipher_info_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[] if ((cipherp = get_cipher_type_no_key(argv[0])) == NULL) return enif_make_badarg(env); - if (FORBIDDEN_IN_FIPS(cipherp)) + if (CIPHER_FORBIDDEN_IN_FIPS(cipherp)) return enif_raise_exception(env, atom_notsup); if ((cipher = cipherp->cipher.p) == NULL) return enif_raise_exception(env, atom_notsup); @@ -330,10 +330,11 @@ ERL_NIF_TERM cipher_types_as_list(ErlNifEnv* env) for (p = cipher_types; (p->type.atom & (p->type.atom != atom_false)); p++) { if ((prev == p->type.atom) || - FORBIDDEN_IN_FIPS(p) ) + CIPHER_FORBIDDEN_IN_FIPS(p) ) continue; if ((p->cipher.p != NULL) || + (p->flags & AES_CTR_COMPAT) || (p->type.atom == atom_aes_ige256)) /* Special handling. Bad indeed... */ { hd = enif_make_list_cell(env, p->type.atom, hd); diff --git a/lib/crypto/c_src/cipher.h b/lib/crypto/c_src/cipher.h index 0e51c410eb..c23e128824 100644 --- a/lib/crypto/c_src/cipher.h +++ b/lib/crypto/c_src/cipher.h @@ -52,10 +52,10 @@ struct cipher_type_t { #ifdef FIPS_SUPPORT /* May have FIPS support, must check dynamically if it is enabled */ -# define FORBIDDEN_IN_FIPS(P) (((P)->flags & NO_FIPS_CIPHER) && FIPS_mode()) +# define CIPHER_FORBIDDEN_IN_FIPS(P) (((P)->flags & NO_FIPS_CIPHER) && FIPS_mode()) #else /* No FIPS support since the symbol FIPS_SUPPORT is undefined */ -# define FORBIDDEN_IN_FIPS(P) 0 +# define CIPHER_FORBIDDEN_IN_FIPS(P) 0 #endif extern ErlNifResourceType* evp_cipher_ctx_rtype; diff --git a/lib/crypto/c_src/cmac.c b/lib/crypto/c_src/cmac.c index 49e67ccf29..a1564f6661 100644 --- a/lib/crypto/c_src/cmac.c +++ b/lib/crypto/c_src/cmac.c @@ -18,71 +18,56 @@ * %CopyrightEnd% */ -#include "cmac.h" -#include "cipher.h" +#include "common.h" -ERL_NIF_TERM cmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Type, Key, Data) */ -#if defined(HAVE_CMAC) - const struct cipher_type_t *cipherp; - const EVP_CIPHER *cipher; - CMAC_CTX *ctx = NULL; - ErlNifBinary key; - ErlNifBinary data; - ERL_NIF_TERM ret; - size_t ret_size; - unsigned char *outp; - int cipher_len; +/***************************************************************** + * + * This file has functions for compatibility with cryptolibs + * lacking the EVP_Digest API. + * + * See mac.c for the implementation using the EVP interface. + * + ****************************************************************/ - ASSERT(argc == 3); +#if defined(HAVE_CMAC) && !defined(HAVE_EVP_PKEY_new_CMAC_key) - if (!enif_inspect_iolist_as_binary(env, argv[1], &key)) - goto bad_arg; - if ((cipherp = get_cipher_type(argv[0], key.size)) == NULL) - goto bad_arg; - if (cipherp->flags & (NON_EVP_CIPHER | AEAD_CIPHER)) - goto bad_arg; - if (!enif_inspect_iolist_as_binary(env, argv[2], &data)) - goto bad_arg; +#include "cmac.h" - if (FORBIDDEN_IN_FIPS(cipherp)) - return enif_raise_exception(env, atom_notsup); - if ((cipher = cipherp->cipher.p) == NULL) - return enif_raise_exception(env, atom_notsup); +int cmac_low_level(ErlNifEnv* env, + ErlNifBinary key_bin, const EVP_CIPHER* cipher, ErlNifBinary text, + ErlNifBinary *ret_bin, int *ret_bin_alloc, ERL_NIF_TERM *return_term) +{ + CMAC_CTX *ctx = NULL; + size_t size; if ((ctx = CMAC_CTX_new()) == NULL) - goto err; - if (!CMAC_Init(ctx, key.data, key.size, cipher, NULL)) - goto err; - if (!CMAC_Update(ctx, data.data, data.size)) - goto err; - if ((cipher_len = EVP_CIPHER_block_size(cipher)) < 0) - goto err; - if ((outp = enif_make_new_binary(env, (size_t)cipher_len, &ret)) == NULL) - goto err; - if (!CMAC_Final(ctx, outp, &ret_size)) - goto err; + goto local_err; - ASSERT(ret_size == (unsigned)EVP_CIPHER_block_size(cipher)); - CONSUME_REDS(env, data); - goto done; + if (!CMAC_Init(ctx, key_bin.data, key_bin.size, cipher, NULL)) + goto local_err; - bad_arg: - return enif_make_badarg(env); + if (!CMAC_Update(ctx, text.data, text.size)) + goto local_err; - err: - ret = atom_notsup; + if ((size = (size_t)EVP_CIPHER_block_size(cipher)) < 0) + goto local_err; - done: + if (!enif_alloc_binary(size, ret_bin)) + goto local_err; + *ret_bin_alloc = 1; + + if (!CMAC_Final(ctx, ret_bin->data, &ret_bin->size)) + goto local_err; + + CMAC_CTX_free(ctx); + return 1; + + local_err: if (ctx) CMAC_CTX_free(ctx); - return ret; -#else - /* The CMAC functionality was introduced in OpenSSL 1.0.1 - * Although OTP requires at least version 0.9.8, the versions 0.9.8 and 1.0.0 are - * no longer maintained. */ - return atom_notsup; -#endif + *return_term = EXCP_ERROR(env,"Compat cmac"); + return 0; } +#endif diff --git a/lib/crypto/c_src/cmac.h b/lib/crypto/c_src/cmac.h index 14488def58..04c742b2dc 100644 --- a/lib/crypto/c_src/cmac.h +++ b/lib/crypto/c_src/cmac.h @@ -23,6 +23,12 @@ #include "common.h" -ERL_NIF_TERM cmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +#if defined(HAVE_CMAC) && !defined(HAVE_EVP_PKEY_new_CMAC_key) + +int cmac_low_level(ErlNifEnv* env, + ErlNifBinary key_bin, const EVP_CIPHER* cipher, ErlNifBinary text, + ErlNifBinary *ret_bin, int *ret_bin_alloc, ERL_NIF_TERM *return_term); + +#endif #endif /* E_CMAC_H__ */ diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c index d533cba140..802818541b 100644 --- a/lib/crypto/c_src/crypto.c +++ b/lib/crypto/c_src/crypto.c @@ -31,7 +31,7 @@ #include "api_ng.h" #include "bn.h" #include "cipher.h" -#include "cmac.h" +#include "mac.h" #include "dh.h" #include "digest.h" #include "dss.h" @@ -46,7 +46,6 @@ #include "info.h" #include "math.h" #include "pkey.h" -#include "poly1305.h" #include "rand.h" #include "rsa.h" #include "srp.h" @@ -74,13 +73,10 @@ static ErlNifFunc nif_funcs[] = { {"hash_init_nif", 1, hash_init_nif, 0}, {"hash_update_nif", 2, hash_update_nif, 0}, {"hash_final_nif", 1, hash_final_nif, 0}, - {"hmac_nif", 3, hmac_nif, 0}, - {"hmac_nif", 4, hmac_nif, 0}, - {"hmac_init_nif", 2, hmac_init_nif, 0}, - {"hmac_update_nif", 2, hmac_update_nif, 0}, - {"hmac_final_nif", 1, hmac_final_nif, 0}, - {"hmac_final_nif", 2, hmac_final_nif, 0}, - {"cmac_nif", 3, cmac_nif, 0}, + {"mac_nif", 4, mac_nif, 0}, + {"mac_init_nif", 3, mac_init_nif, 0}, + {"mac_update_nif", 2, mac_update_nif, 0}, + {"mac_final_nif", 1, mac_final_nif, 0}, {"cipher_info_nif", 1, cipher_info_nif, 0}, {"aes_ige_crypt_nif", 4, aes_ige_crypt_nif, 0}, {"ng_crypto_init_nif", 4, ng_crypto_init_nif, 0}, @@ -112,8 +108,6 @@ static ErlNifFunc nif_funcs[] = { {"aead_cipher", 7, aead_cipher, 0}, - {"poly1305_nif", 2, poly1305_nif, 0}, - {"engine_by_id_nif", 1, engine_by_id_nif, 0}, {"engine_init_nif", 1, engine_init_nif, 0}, {"engine_finish_nif", 1, engine_finish_nif, 0}, @@ -181,9 +175,15 @@ static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info) if (!enif_inspect_binary(env, tpl_array[1], &lib_bin)) return __LINE__; +#ifdef HAS_EVP_PKEY_CTX + if (!init_mac_ctx(env)) { + return __LINE__; + } +#else if (!init_hmac_ctx(env)) { return __LINE__; } +#endif if (!init_hash_ctx(env)) { return __LINE__; } @@ -248,6 +248,7 @@ static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info) #endif /* OPENSSL_THREADS */ init_digest_types(env); + init_mac_types(env); init_cipher_types(env); init_algorithms_types(env); diff --git a/lib/crypto/c_src/digest.c b/lib/crypto/c_src/digest.c index c987a664d5..0f887ab765 100644 --- a/lib/crypto/c_src/digest.c +++ b/lib/crypto/c_src/digest.c @@ -22,7 +22,7 @@ static struct digest_type_t digest_types[] = { - {{"md4"}, + {{"md4"}, NO_FIPS_DIGEST, #ifdef HAVE_MD4 {&EVP_md4} #else @@ -30,7 +30,7 @@ static struct digest_type_t digest_types[] = #endif }, - {{"md5"}, + {{"md5"}, NO_FIPS_DIGEST, #ifdef HAVE_MD5 {&EVP_md5} #else @@ -38,7 +38,7 @@ static struct digest_type_t digest_types[] = #endif }, - {{"ripemd160"}, + {{"ripemd160"}, NO_FIPS_DIGEST, #ifdef HAVE_RIPEMD160 {&EVP_ripemd160} #else @@ -46,9 +46,9 @@ static struct digest_type_t digest_types[] = #endif }, - {{"sha"}, {&EVP_sha1}}, + {{"sha"}, 0, {&EVP_sha1}}, - {{"sha224"}, + {{"sha224"}, 0, #ifdef HAVE_SHA224 {&EVP_sha224} #else @@ -56,7 +56,7 @@ static struct digest_type_t digest_types[] = #endif }, - {{"sha256"}, + {{"sha256"}, 0, #ifdef HAVE_SHA256 {&EVP_sha256} #else @@ -64,7 +64,7 @@ static struct digest_type_t digest_types[] = #endif }, - {{"sha384"}, + {{"sha384"}, 0, #ifdef HAVE_SHA384 {&EVP_sha384} #else @@ -72,7 +72,7 @@ static struct digest_type_t digest_types[] = #endif }, - {{"sha512"}, + {{"sha512"}, 0, #ifdef HAVE_SHA512 {&EVP_sha512} #else @@ -80,7 +80,7 @@ static struct digest_type_t digest_types[] = #endif }, - {{"sha3_224"}, + {{"sha3_224"}, 0, #ifdef HAVE_SHA3_224 {&EVP_sha3_224} #else @@ -88,7 +88,7 @@ static struct digest_type_t digest_types[] = #endif }, - {{"sha3_256"}, + {{"sha3_256"}, 0, #ifdef HAVE_SHA3_256 {&EVP_sha3_256} #else @@ -96,7 +96,7 @@ static struct digest_type_t digest_types[] = #endif }, - {{"sha3_384"}, + {{"sha3_384"}, 0, #ifdef HAVE_SHA3_384 {&EVP_sha3_384} #else @@ -104,7 +104,7 @@ static struct digest_type_t digest_types[] = #endif }, - {{"sha3_512"}, + {{"sha3_512"}, 0, #ifdef HAVE_SHA3_512 {&EVP_sha3_512} #else @@ -112,7 +112,7 @@ static struct digest_type_t digest_types[] = #endif }, - {{"blake2b"}, + {{"blake2b"}, 0, #ifdef HAVE_BLAKE2 {&EVP_blake2b512} #else @@ -120,7 +120,7 @@ static struct digest_type_t digest_types[] = #endif }, - {{"blake2s"}, + {{"blake2s"}, 0, #ifdef HAVE_BLAKE2 {&EVP_blake2s256} #else @@ -128,7 +128,8 @@ static struct digest_type_t digest_types[] = #endif }, - {{NULL}, {NULL}} + /*==== End of list ==== */ + {{NULL}, 0, {NULL}} }; void init_digest_types(ErlNifEnv* env) diff --git a/lib/crypto/c_src/digest.h b/lib/crypto/c_src/digest.h index 06852416cf..b1f8128a1f 100644 --- a/lib/crypto/c_src/digest.h +++ b/lib/crypto/c_src/digest.h @@ -28,12 +28,25 @@ struct digest_type_t { const char* str; /* before init, NULL for end-of-table */ ERL_NIF_TERM atom; /* after init, 'false' for end-of-table */ }type; + unsigned flags; union { const EVP_MD* (*funcp)(void); /* before init, NULL if notsup */ const EVP_MD* p; /* after init, NULL if notsup */ }md; }; +/* masks in the flags field if digest_type_t */ +#define NO_FIPS_DIGEST 1 + +#ifdef FIPS_SUPPORT +/* May have FIPS support, must check dynamically if it is enabled */ +# define DIGEST_FORBIDDEN_IN_FIPS(P) (((P)->flags & NO_FIPS_DIGEST) && FIPS_mode()) +#else +/* No FIPS support since the symbol FIPS_SUPPORT is undefined */ +# define DIGEST_FORBIDDEN_IN_FIPS(P) 0 +#endif + + void init_digest_types(ErlNifEnv* env); struct digest_type_t* get_digest_type(ERL_NIF_TERM type); diff --git a/lib/crypto/c_src/hmac.c b/lib/crypto/c_src/hmac.c index ff7005d75e..5e2c68bfee 100644 --- a/lib/crypto/c_src/hmac.c +++ b/lib/crypto/c_src/hmac.c @@ -18,6 +18,18 @@ * %CopyrightEnd% */ + +/***************************************************************** + * + * This file has functions for compatibility with cryptolibs + * lacking the EVP_Digest API. + * + * See mac.c for the implementation using the EVP interface. + * + ****************************************************************/ + +#ifndef HAS_EVP_PKEY_CTX + #include "hmac.h" #include "digest.h" @@ -47,61 +59,6 @@ int init_hmac_ctx(ErlNifEnv *env) { return 0; } -ERL_NIF_TERM hmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Type, Key, Data) or (Type, Key, Data, MacSize) */ - struct digest_type_t *digp = NULL; - ErlNifBinary key, data; - unsigned char buff[EVP_MAX_MD_SIZE]; - unsigned size = 0, req_size = 0; - ERL_NIF_TERM ret; - unsigned char *outp; - - ASSERT(argc == 3 || argc == 4); - - if ((digp = get_digest_type(argv[0])) == NULL) - goto bad_arg; - if (!enif_inspect_iolist_as_binary(env, argv[1], &key)) - goto bad_arg; - if (key.size > INT_MAX) - goto bad_arg; - if (!enif_inspect_iolist_as_binary(env, argv[2], &data)) - goto bad_arg; - if (argc == 4) { - if (!enif_get_uint(env, argv[3], &req_size)) - goto bad_arg; - } - - if (digp->md.p == NULL) - goto err; - if (HMAC(digp->md.p, - key.data, (int)key.size, - data.data, data.size, - buff, &size) == NULL) - goto err; - - ASSERT(0 < size && size <= EVP_MAX_MD_SIZE); - CONSUME_REDS(env, data); - - if (argc == 4) { - if (req_size > size) - goto bad_arg; - - size = req_size; - } - - if ((outp = enif_make_new_binary(env, size, &ret)) == NULL) - goto err; - - memcpy(outp, buff, size); - return ret; - - bad_arg: - return enif_make_badarg(env); - - err: - return atom_notsup; -} - static void hmac_context_dtor(ErlNifEnv* env, struct hmac_context *obj) { if (obj == NULL) @@ -118,17 +75,17 @@ static void hmac_context_dtor(ErlNifEnv* env, struct hmac_context *obj) } ERL_NIF_TERM hmac_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Type, Key) */ +{/* (hmac, Type, Key) */ struct digest_type_t *digp = NULL; ErlNifBinary key; ERL_NIF_TERM ret; struct hmac_context *obj = NULL; - ASSERT(argc == 2); + ASSERT(argc == 3); - if ((digp = get_digest_type(argv[0])) == NULL) + if ((digp = get_digest_type(argv[1])) == NULL) goto bad_arg; - if (!enif_inspect_iolist_as_binary(env, argv[1], &key)) + if (!enif_inspect_iolist_as_binary(env, argv[2], &key)) goto bad_arg; if (key.size > INT_MAX) goto bad_arg; @@ -268,3 +225,44 @@ ERL_NIF_TERM hmac_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) return ret; } + + +int hmac_low_level(ErlNifEnv* env, const EVP_MD *md, + ErlNifBinary key_bin, ErlNifBinary text, + ErlNifBinary *ret_bin, int *ret_bin_alloc, ERL_NIF_TERM *return_term) +{ + unsigned int size_int; + size_t size; + + /* Find the needed space */ + if (HMAC(md, + key_bin.data, (int)key_bin.size, + text.data, text.size, + NULL, &size_int) == NULL) + { + *return_term = EXCP_ERROR(env, "Get HMAC size failed"); + return 0; + } + + size = (size_t)size_int; /* Otherwise "size" is unused in 0.9.8.... */ + if (!enif_alloc_binary(size, ret_bin)) + { + *return_term = EXCP_ERROR(env, "Alloc binary"); + return 0; + } + *ret_bin_alloc = 1; + + /* And do the real HMAC calc */ + if (HMAC(md, + key_bin.data, (int)key_bin.size, + text.data, text.size, + ret_bin->data, &size_int) == NULL) + { + *return_term = EXCP_ERROR(env, "HMAC sign failed"); + return 0; + } + + return 1; +} + +#endif diff --git a/lib/crypto/c_src/hmac.h b/lib/crypto/c_src/hmac.h index 1f0e0ca632..f5805e13e5 100644 --- a/lib/crypto/c_src/hmac.h +++ b/lib/crypto/c_src/hmac.h @@ -21,13 +21,19 @@ #ifndef E_HMAC_H__ #define E_HMAC_H__ 1 +#ifndef HAS_EVP_PKEY_CTX + #include "common.h" int init_hmac_ctx(ErlNifEnv *env); -ERL_NIF_TERM hmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM hmac_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM hmac_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM hmac_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +int hmac_low_level(ErlNifEnv* env, const EVP_MD *md, + ErlNifBinary key_bin, ErlNifBinary text, + ErlNifBinary *ret_bin, int *ret_bin_alloc, ERL_NIF_TERM *return_term); +#endif + #endif /* E_HMAC_H__ */ diff --git a/lib/crypto/c_src/mac.c b/lib/crypto/c_src/mac.c new file mode 100644 index 0000000000..149975ba9d --- /dev/null +++ b/lib/crypto/c_src/mac.c @@ -0,0 +1,751 @@ +/* + * %CopyrightBegin% + * + * 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. + * 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% + */ + +#include "common.h" +#include "cipher.h" +#include "digest.h" +#include "cmac.h" +#include "hmac.h" +#include "mac.h" + +/*************************** + MAC type declaration +***************************/ + +struct mac_type_t { + union { + const char* str; /* before init, NULL for end-of-table */ + ERL_NIF_TERM atom; /* after init, 'false' for end-of-table */ + }name; + unsigned flags; + union { + const int pkey_type; + }alg; + int type; + size_t key_len; /* != 0 to also match on key_len */ +}; + +/* masks in the flags field if mac_type_t */ +#define NO_FIPS_MAC 1 + +#define NO_mac 0 +#define HMAC_mac 1 +#define CMAC_mac 2 +#define POLY1305_mac 3 + +static struct mac_type_t mac_types[] = +{ + {{"poly1305"}, NO_FIPS_MAC, +#ifdef HAVE_POLY1305 + /* If we have POLY then we have EVP_PKEY */ + {EVP_PKEY_POLY1305}, POLY1305_mac, 32 +#else + {EVP_PKEY_NONE}, NO_mac, 0 +#endif + }, + + {{"hmac"}, 0, +#ifdef HAS_EVP_PKEY_CTX + {EVP_PKEY_HMAC}, HMAC_mac, 0 +#else + /* HMAC is always supported, but possibly with low-level routines */ + {EVP_PKEY_NONE}, HMAC_mac, 0 +#endif + }, + + {{"cmac"}, 0, +#ifdef HAVE_CMAC + /* If we have CMAC then we have EVP_PKEY */ + {EVP_PKEY_CMAC}, CMAC_mac, 0 +#else + {EVP_PKEY_NONE}, NO_mac, 0 +#endif + }, + + /*==== End of list ==== */ + {{NULL}, 0, + {0}, NO_mac, 0 + } +}; + + +#ifdef FIPS_SUPPORT +/* May have FIPS support, must check dynamically if it is enabled */ +# define MAC_FORBIDDEN_IN_FIPS(P) (((P)->flags & NO_FIPS_MAC) && FIPS_mode()) +#else +/* No FIPS support since the symbol FIPS_SUPPORT is undefined */ +# define MAC_FORBIDDEN_IN_FIPS(P) 0 +#endif + + +/*************************** + Mandatory prototypes +***************************/ + +struct mac_type_t* get_mac_type(ERL_NIF_TERM type, size_t key_len); +struct mac_type_t* get_mac_type_no_key(ERL_NIF_TERM type); + +ERL_NIF_TERM mac_one_time(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +ERL_NIF_TERM mac_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + + +/******************************** + Support functions for type array +*********************************/ + +void init_mac_types(ErlNifEnv* env) +{ + struct mac_type_t* p = mac_types; + + for (p = mac_types; p->name.str; p++) { + p->name.atom = enif_make_atom(env, p->name.str); + } + p->name.atom = atom_false; /* end marker */ +} + + +ERL_NIF_TERM mac_types_as_list(ErlNifEnv* env) +{ + struct mac_type_t* p; + ERL_NIF_TERM prev, hd; + + hd = enif_make_list(env, 0); + prev = atom_undefined; + + for (p = mac_types; (p->name.atom & (p->name.atom != atom_false)); p++) { + if (prev == p->name.atom) + continue; + + if (p->type != NO_mac) + { + hd = enif_make_list_cell(env, p->name.atom, hd); + } + } + + return hd; +} + +struct mac_type_t* get_mac_type(ERL_NIF_TERM type, size_t key_len) +{ + struct mac_type_t* p = NULL; + for (p = mac_types; p->name.atom != atom_false; p++) { + if (type == p->name.atom) { + if ((p->key_len == 0) || (p->key_len == key_len)) + return p; + } + } + return NULL; +} + +struct mac_type_t* get_mac_type_no_key(ERL_NIF_TERM type) +{ + struct mac_type_t* p = NULL; + for (p = mac_types; p->name.atom != atom_false; p++) { + if (type == p->name.atom) { + return p; + } + } + return NULL; +} + +/******************************************************************* + * + * Mac nif + * + ******************************************************************/ +ERL_NIF_TERM mac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (MacType, SubType, Key, Text) */ + ErlNifBinary text; + + if (!enif_inspect_iolist_as_binary(env, argv[3], &text)) + return EXCP_BADARG(env, "Bad text"); + + if (text.size > INT_MAX) + return EXCP_BADARG(env, "Too long text"); + + /* Run long jobs on a dirty scheduler to not block the current emulator thread */ + if (text.size > MAX_BYTES_TO_NIF) { + return enif_schedule_nif(env, "mac_one_time", + ERL_NIF_DIRTY_JOB_CPU_BOUND, + mac_one_time, argc, argv); + } + + return mac_one_time(env, argc, argv); +} + + + +ERL_NIF_TERM mac_one_time(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (MacType, SubType, Key, Text) */ + + struct mac_type_t *macp; + ErlNifBinary key_bin, text; + int ret_bin_alloc = 0; + ERL_NIF_TERM return_term; + const EVP_MD *md = NULL; + ErlNifBinary ret_bin; +#ifdef HAS_EVP_PKEY_CTX + size_t size; + EVP_PKEY *pkey = NULL; + EVP_MD_CTX *mctx = NULL; +#endif + + /*--------------------------------- + Get common indata and validate it + */ + if (!enif_inspect_iolist_as_binary(env, argv[2], &key_bin)) + { + return_term = EXCP_BADARG(env, "Bad key"); + goto err; + } + + if (!enif_inspect_iolist_as_binary(env, argv[3], &text)) + { + return_term = EXCP_BADARG(env, "Bad text"); + goto err; + } + + if (!(macp = get_mac_type(argv[0], key_bin.size))) + { + if (!get_mac_type_no_key(argv[0])) + return_term = EXCP_BADARG(env, "Unknown mac algorithm"); + else + return_term = EXCP_BADARG(env, "Bad key length"); + goto err; + } + + if (MAC_FORBIDDEN_IN_FIPS(macp)) + { + return_term = EXCP_NOTSUP(env, "MAC algorithm forbidden in FIPS"); + goto err; + } + + /*-------------------------------------------------- + Algorithm dependent indata checking and computation. + If EVP_PKEY is available, only set the pkey variable + and do the computation after the switch statement. + If not available, do the low-level calls in the + corresponding case part + */ + switch (macp->type) { + + /******** + * HMAC * + ********/ + case HMAC_mac: + { + struct digest_type_t *digp; + + if ((digp = get_digest_type(argv[1])) == NULL) + { + return_term = EXCP_BADARG(env, "Bad digest algorithm for HMAC"); + goto err; + } + if (digp->md.p == NULL) + { + return_term = EXCP_NOTSUP(env, "Unsupported digest algorithm"); + goto err; + } + if (DIGEST_FORBIDDEN_IN_FIPS(digp)) + { + return_term = EXCP_NOTSUP(env, "Digest algorithm for HMAC forbidden in FIPS"); + goto err; + } + md = digp->md.p; + +#ifdef HAS_EVP_PKEY_CTX +# ifdef HAVE_PKEY_new_raw_private_key + /* Prefered for new applications according to EVP_PKEY_new_mac_key(3) */ + pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, /*engine*/ NULL, key_bin.data, key_bin.size); +# else + /* Available in older versions */ + pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, /*engine*/ NULL, key_bin.data, key_bin.size); +# endif + +#else + if (!hmac_low_level(env, md, key_bin, text, &ret_bin, &ret_bin_alloc, &return_term)) + goto err; + else + goto success; +#endif + } + break; + + + /******** + * CMAC * + ********/ +#ifdef HAVE_CMAC + case CMAC_mac: + { + const struct cipher_type_t *cipherp; + if (!(cipherp = get_cipher_type(argv[1], key_bin.size))) + { /* Something went wrong. Find out what by retrying in another way. */ + if (!get_cipher_type_no_key(argv[1])) + return_term = EXCP_BADARG(env, "Unknown cipher"); + else + /* Cipher exists, so it must be the key size that is wrong */ + return_term = EXCP_BADARG(env, "Bad key size"); + goto err; + } + + if (CIPHER_FORBIDDEN_IN_FIPS(cipherp)) + { + return_term = EXCP_NOTSUP(env, "Cipher algorithm not supported in FIPS"); + goto err; + } + + if (cipherp->cipher.p == NULL) + { + return_term = EXCP_NOTSUP(env, "Unsupported cipher algorithm"); + goto err; + } + +# ifdef HAVE_EVP_PKEY_new_CMAC_key + pkey = EVP_PKEY_new_CMAC_key(/*engine*/ NULL, key_bin.data, key_bin.size, cipherp->cipher.p); +# else + if (!cmac_low_level(env, key_bin, cipherp->cipher.p, text, &ret_bin, &ret_bin_alloc, &return_term)) + goto err; + else + goto success; +# endif + } + break; +#endif /* HAVE_CMAC */ + + + /************ + * POLY1305 * + ************/ +#ifdef HAVE_POLY1305 + case POLY1305_mac: + /* poly1305 implies that EVP_PKEY_new_raw_private_key exists */ + pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_POLY1305, /*engine*/ NULL, key_bin.data, key_bin.size); + break; +#endif + + + /*************** + * Unknown MAC * + ***************/ + case NO_mac: + default: + /* We know that this mac is supported with some version(s) of cryptolib */ + return_term = EXCP_NOTSUP(env, "Unsupported mac algorithm"); + goto err; + } + + /*----------------------------------------- + Common computations when we have EVP_PKEY + */ +#ifdef HAS_EVP_PKEY_CTX + if (!pkey) + { + return_term = EXCP_ERROR(env, "EVP_PKEY_key creation"); + goto err; + } + + if ((mctx = EVP_MD_CTX_new()) == NULL) + { + return_term = EXCP_ERROR(env, "EVP_MD_CTX_new"); + goto err; + } + + if (EVP_DigestSignInit(mctx, /*&pctx*/ NULL, md, /*engine*/ NULL, pkey) != 1) + { + return_term = EXCP_ERROR(env, "EVP_DigestSign"); + goto err; + } + +# ifdef HAVE_DigestSign_as_single_op + if (EVP_DigestSign(mctx, NULL, &size, text.data, text.size) != 1) + { + return_term = EXCP_ERROR(env, "Can't get sign size"); + goto err; + } +# else + if (EVP_DigestSignUpdate(mctx, text.data, text.size) != 1) + { + return_term = EXCP_ERROR(env, "EVP_DigestSignUpdate"); + goto err; + } + + if (EVP_DigestSignFinal(mctx, NULL, &size) != 1) + { + return_term = EXCP_ERROR(env, "Can't get sign size"); + goto err; + } +# endif + + if (!enif_alloc_binary(size, &ret_bin)) + { + return_term = EXCP_ERROR(env, "Alloc binary"); + goto err; + } + ret_bin_alloc = 1; + +# ifdef HAVE_DigestSign_as_single_op + if (EVP_DigestSign(mctx, ret_bin.data, &size, text.data, text.size) != 1) +# else + if (EVP_DigestSignFinal(mctx, ret_bin.data, &size) != 1) +# endif + { + return_term = EXCP_ERROR(env, "Signing"); + goto err; + } + + goto success; /* The label "success:" could be left without any "goto success" + in some combination of flags. This prevents a compiler warning + */ +#endif /* ifdef HAS_EVP_PKEY_CTX */ + + + /**************************** + Exit when we got a signature + *****************************/ + success: + CONSUME_REDS(env, text); + + return_term = enif_make_binary(env, &ret_bin); + ret_bin_alloc = 0; + + err: + +#ifdef HAS_EVP_PKEY_CTX + if (pkey) + EVP_PKEY_free(pkey); + if (mctx) + EVP_MD_CTX_free(mctx); +#endif + + if (ret_bin_alloc) + enif_release_binary(&ret_bin); + + return return_term; +} + + +/******************************************************************* + * + * Mac ctx + * + ******************************************************************/ + +int init_mac_ctx(ErlNifEnv *env); + +struct mac_context +{ + EVP_MD_CTX *ctx; +}; + +static ErlNifResourceType* mac_context_rtype; + +static void mac_context_dtor(ErlNifEnv* env, struct mac_context*); + +int init_mac_ctx(ErlNifEnv *env) { + mac_context_rtype = enif_open_resource_type(env, NULL, "mac_context", + (ErlNifResourceDtor*) mac_context_dtor, + ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER, + NULL); + if (mac_context_rtype == NULL) + goto err; + + return 1; + + err: + PRINTF_ERR0("CRYPTO: Could not open resource type 'mac_context'"); + return 0; +} + + +static void mac_context_dtor(ErlNifEnv* env, struct mac_context *obj) +{ + if (obj == NULL) + return; + + if (obj->ctx) + EVP_MD_CTX_free(obj->ctx); +} + +/******************************************************************* + * + * mac_init, mac_update, mac_final nifs + * + ******************************************************************/ + +ERL_NIF_TERM mac_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (MacType, SubType, Key) */ +#ifdef HAS_EVP_PKEY_CTX + struct mac_context *obj = NULL; + struct mac_type_t *macp; + ErlNifBinary key_bin; + ERL_NIF_TERM return_term; + const EVP_MD *md = NULL; + EVP_PKEY *pkey = NULL; + + /*--------------------------------- + Get common indata and validate it + */ + if (!enif_inspect_iolist_as_binary(env, argv[2], &key_bin)) + { + return_term = EXCP_BADARG(env, "Bad key"); + goto err; + } + + if (!(macp = get_mac_type(argv[0], key_bin.size))) + { + if (!get_mac_type_no_key(argv[0])) + return_term = EXCP_BADARG(env, "Unknown mac algorithm"); + else + return_term = EXCP_BADARG(env, "Bad key length"); + goto err; + } + + if (MAC_FORBIDDEN_IN_FIPS(macp)) + { + return_term = EXCP_NOTSUP(env, "MAC algorithm forbidden in FIPS"); + goto err; + } + + /*-------------------------------------------------- + Algorithm dependent indata checking and computation. + If EVP_PKEY is available, only set the pkey variable + and do the computation after the switch statement. + If not available, do the low-level calls in the + corresponding case part + */ + switch (macp->type) { + + /******** + * HMAC * + ********/ + case HMAC_mac: + { + struct digest_type_t *digp; + + if ((digp = get_digest_type(argv[1])) == NULL) + { + return_term = EXCP_BADARG(env, "Bad digest algorithm for HMAC"); + goto err; + } + if (digp->md.p == NULL) + { + return_term = EXCP_NOTSUP(env, "Unsupported digest algorithm"); + goto err; + } + if (DIGEST_FORBIDDEN_IN_FIPS(digp)) + { + return_term = EXCP_NOTSUP(env, "Digest algorithm for HMAC forbidden in FIPS"); + goto err; + } + md = digp->md.p; + +# ifdef HAVE_PKEY_new_raw_private_key + /* Prefered for new applications according to EVP_PKEY_new_mac_key(3) */ + pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, /*engine*/ NULL, key_bin.data, key_bin.size); +# else + /* Available in older versions */ + pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, /*engine*/ NULL, key_bin.data, key_bin.size); +# endif + } + break; + + + /******** + * CMAC * + ********/ +#if defined(HAVE_CMAC) && defined(HAVE_EVP_PKEY_new_CMAC_key) + case CMAC_mac: + { + const struct cipher_type_t *cipherp; + if (!(cipherp = get_cipher_type(argv[1], key_bin.size))) + { /* Something went wrong. Find out what by retrying in another way. */ + if (!get_cipher_type_no_key(argv[1])) + return_term = EXCP_BADARG(env, "Unknown cipher"); + else + /* Cipher exists, so it must be the key size that is wrong */ + return_term = EXCP_BADARG(env, "Bad key size"); + goto err; + } + + if (CIPHER_FORBIDDEN_IN_FIPS(cipherp)) + { + return_term = EXCP_NOTSUP(env, "Cipher algorithm not supported in FIPS"); + goto err; + } + + if (cipherp->cipher.p == NULL) + { + return_term = EXCP_NOTSUP(env, "Unsupported cipher algorithm"); + goto err; + } + + pkey = EVP_PKEY_new_CMAC_key(/*engine*/ NULL, key_bin.data, key_bin.size, cipherp->cipher.p); + } + break; +#endif /* HAVE_CMAC && HAVE_EVP_PKEY_new_CMAC_key */ + + + /************ + * POLY1305 * + ************/ +#ifdef HAVE_POLY1305 + case POLY1305_mac: + /* poly1305 implies that EVP_PKEY_new_raw_private_key exists */ + pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_POLY1305, /*engine*/ NULL, key_bin.data, key_bin.size); + break; +#endif + + + /*************** + * Unknown MAC * + ***************/ + case NO_mac: + default: + /* We know that this mac is supported with some version(s) of cryptolib */ + return_term = EXCP_NOTSUP(env, "Unsupported mac algorithm"); + goto err; + } + + /*----------------------------------------- + Common computations + */ + if (!pkey) + { + return_term = EXCP_ERROR(env, "EVP_PKEY_key creation"); + goto err; + } + + if ((obj = enif_alloc_resource(mac_context_rtype, sizeof(struct mac_context))) == NULL) + { + return_term = EXCP_ERROR(env, "Can't allocate mac_context_rtype"); + goto err; + } + + if ((obj->ctx = EVP_MD_CTX_new()) == NULL) + { + return_term = EXCP_ERROR(env, "EVP_MD_CTX_new"); + goto err; + } + + if (EVP_DigestSignInit(obj->ctx, /*&pctx*/ NULL, md, /*engine*/ NULL, pkey) != 1) + { + return_term = EXCP_ERROR(env, "EVP_DigestSign"); + goto err; + } + + return_term = enif_make_resource(env, obj); + + err: + + if (obj) + enif_release_resource(obj); + + if (pkey) + EVP_PKEY_free(pkey); + + return return_term; + +#else + if (argv[0] != atom_hmac) + return EXCP_NOTSUP(env, "Unsupported mac algorithm"); + + return hmac_init_nif(env, argc, argv); +#endif +} + + + +ERL_NIF_TERM mac_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Ref, Text) */ + ErlNifBinary text; + + if (!enif_inspect_iolist_as_binary(env, argv[1], &text)) + return EXCP_BADARG(env, "Bad text"); + + if (text.size > INT_MAX) + return EXCP_BADARG(env, "Too long text"); + + /* Run long jobs on a dirty scheduler to not block the current emulator thread */ + if (text.size > MAX_BYTES_TO_NIF) { + return enif_schedule_nif(env, "mac_update", + ERL_NIF_DIRTY_JOB_CPU_BOUND, + mac_update, argc, argv); + } + + return mac_update(env, argc, argv); +} + + +ERL_NIF_TERM mac_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Ref, Text) */ +#ifdef HAS_EVP_PKEY_CTX + struct mac_context *obj = NULL; + ErlNifBinary text; + + if (!enif_get_resource(env, argv[0], (ErlNifResourceType*)mac_context_rtype, (void**)&obj)) + return EXCP_BADARG(env, "Bad ref"); + + if (!enif_inspect_iolist_as_binary(env, argv[1], &text)) + return EXCP_BADARG(env, "Bad text"); + + if (EVP_DigestSignUpdate(obj->ctx, text.data, text.size) != 1) + return EXCP_ERROR(env, "EVP_DigestSignUpdate"); + + CONSUME_REDS(env, text); + return argv[0]; + +#else + return hmac_update_nif(env, argc, argv); +#endif +} + + + +ERL_NIF_TERM mac_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Ref) */ +#ifdef HAS_EVP_PKEY_CTX + struct mac_context *obj; + size_t size; + ErlNifBinary ret_bin; + + if (!enif_get_resource(env, argv[0], (ErlNifResourceType*)mac_context_rtype, (void**)&obj)) + return EXCP_BADARG(env, "Bad ref"); + + if (EVP_DigestSignFinal(obj->ctx, NULL, &size) != 1) + return EXCP_ERROR(env, "Can't get sign size"); + + if (!enif_alloc_binary(size, &ret_bin)) + return EXCP_ERROR(env, "Alloc binary"); + + if (EVP_DigestSignFinal(obj->ctx, ret_bin.data, &size) != 1) + { + enif_release_binary(&ret_bin); + return EXCP_ERROR(env, "Signing"); + } + + return enif_make_binary(env, &ret_bin); + +#else + return hmac_final_nif(env, argc, argv); +#endif +} + diff --git a/lib/crypto/c_src/poly1305.h b/lib/crypto/c_src/mac.h index 4bf45e6218..053a331324 100644 --- a/lib/crypto/c_src/poly1305.h +++ b/lib/crypto/c_src/mac.h @@ -18,11 +18,21 @@ * %CopyrightEnd% */ -#ifndef E_POLY1305_H__ -#define E_POLY1305_H__ 1 +#ifndef E_MAC_H__ +#define E_MAC_H__ 1 #include "common.h" -ERL_NIF_TERM poly1305_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +int init_mac_ctx(ErlNifEnv *env); -#endif /* E_POLY1305_H__ */ +void init_mac_types(ErlNifEnv* env); + +ERL_NIF_TERM mac_types_as_list(ErlNifEnv* env); + +ERL_NIF_TERM mac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +ERL_NIF_TERM mac_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM mac_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM mac_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_MAC_H__ */ diff --git a/lib/crypto/c_src/openssl_config.h b/lib/crypto/c_src/openssl_config.h index 339eb5b8f4..32a0830717 100644 --- a/lib/crypto/c_src/openssl_config.h +++ b/lib/crypto/c_src/openssl_config.h @@ -110,6 +110,12 @@ # define HAS_EVP_PKEY_CTX # define HAVE_EVP_CIPHER_CTX_COPY # endif + +# if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,1) +# define HAVE_PKEY_new_raw_private_key +# define HAVE_EVP_PKEY_new_CMAC_key +# define HAVE_DigestSign_as_single_op +# endif #endif diff --git a/lib/crypto/c_src/pkey.c b/lib/crypto/c_src/pkey.c index a1e2677b34..d53d91c25b 100644 --- a/lib/crypto/c_src/pkey.c +++ b/lib/crypto/c_src/pkey.c @@ -59,8 +59,9 @@ static int get_pkey_public_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_T EVP_PKEY **pkey); static int get_pkey_crypt_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options, PKeyCryptOptions *opt); +#ifdef HAVE_RSA_SSLV23_PADDING static size_t size_of_RSA(EVP_PKEY *pkey); - +#endif static int get_pkey_digest_type(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM type, const EVP_MD **md) @@ -1031,6 +1032,7 @@ static int get_pkey_crypt_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NI return PKEY_BADARG; } +#ifdef HAVE_RSA_SSLV23_PADDING static size_t size_of_RSA(EVP_PKEY *pkey) { int ret = 0; RSA *rsa = NULL; @@ -1045,6 +1047,7 @@ static size_t size_of_RSA(EVP_PKEY *pkey) { return (ret < 0) ? 0 : (size_t)ret; } +#endif ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {/* (Algorithm, Data, PublKey=[E,N]|[E,N,D]|[E,N,D,P1,P2,E1,E2,C], Options, IsPrivate, IsEncrypt) */ diff --git a/lib/crypto/c_src/poly1305.c b/lib/crypto/c_src/poly1305.c deleted file mode 100644 index 76579c0a29..0000000000 --- a/lib/crypto/c_src/poly1305.c +++ /dev/null @@ -1,90 +0,0 @@ -/* - * %CopyrightBegin% - * - * 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. - * 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% - */ - -#include "poly1305.h" - -/* For OpenSSL >= 1.1.1 the hmac_nif and cmac_nif could be integrated into poly1305 (with 'type' as parameter) */ -ERL_NIF_TERM poly1305_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Key, Text) */ -#ifdef HAVE_POLY1305 - ErlNifBinary key_bin, text, ret_bin; - ERL_NIF_TERM ret; - EVP_PKEY *key = NULL; - EVP_MD_CTX *mctx = NULL; - EVP_PKEY_CTX *pctx = NULL; - const EVP_MD *md = NULL; - size_t size; - int ret_bin_alloc = 0; - - ASSERT(argc == 2); - - if (!enif_inspect_binary(env, argv[0], &key_bin)) - goto bad_arg; - if (key_bin.size != 32) - goto bad_arg; - if (!enif_inspect_binary(env, argv[1], &text)) - goto bad_arg; - - if ((key = EVP_PKEY_new_raw_private_key(EVP_PKEY_POLY1305, /*engine*/ NULL, key_bin.data, key_bin.size)) == NULL) - goto err; - - if ((mctx = EVP_MD_CTX_new()) == NULL) - goto err; - if (EVP_DigestSignInit(mctx, &pctx, md, /*engine*/ NULL, key) != 1) - goto err; - if (EVP_DigestSignUpdate(mctx, text.data, text.size) != 1) - goto err; - - if (EVP_DigestSignFinal(mctx, NULL, &size) != 1) - goto err; - if (!enif_alloc_binary(size, &ret_bin)) - goto err; - ret_bin_alloc = 1; - if (EVP_DigestSignFinal(mctx, ret_bin.data, &size) != 1) - goto err; - - if (size != ret_bin.size) { - if (!enif_realloc_binary(&ret_bin, size)) - goto err; - } - - ret = enif_make_binary(env, &ret_bin); - ret_bin_alloc = 0; - goto done; - - bad_arg: - return enif_make_badarg(env); - - err: - if (ret_bin_alloc) - enif_release_binary(&ret_bin); - ret = atom_error; - - done: - if (mctx) - EVP_MD_CTX_free(mctx); - if (key) - EVP_PKEY_free(key); - return ret; - -#else - return enif_raise_exception(env, atom_notsup); -#endif -} diff --git a/lib/crypto/doc/src/algorithm_details.xml b/lib/crypto/doc/src/algorithm_details.xml index 854bfbb4b1..71014764c8 100644 --- a/lib/crypto/doc/src/algorithm_details.xml +++ b/lib/crypto/doc/src/algorithm_details.xml @@ -37,122 +37,163 @@ <section> <title>Ciphers</title> + <p>A <seealso marker="crypto#type-cipher">cipher</seealso> in the + <seealso marker="crypto:new_api#the-new-api">new api</seealso> + is categorized as either + <seealso marker="crypto#type-cipher_no_iv">cipher_no_iv()</seealso>, + <seealso marker="crypto#type-cipher_iv">cipher_iv()</seealso> or + <seealso marker="crypto#type-cipher_aead">cipher_aead()</seealso>. + The letters IV are short for <i>Initialization Vector</i> and + AEAD is an abreviation of <i>Authenticated Encryption with Associated Data</i>. + </p> + <p>Due to irregular naming conventions, some cipher names in the old api are + substitued by new names in the new api. For a list of retired names, see + <seealso marker="crypto:new_api#retired-cipher-names">Retired cipher names</seealso>. + </p> + <p>To dynamically check availability, check that the name in the <i>Cipher and Mode</i> column is present in the + list returned by <seealso marker="crypto#supports-1">crypto:supports(ciphers)</seealso>. + </p> + <section> - <title>Block Ciphers</title> - <p>To be used in - <seealso marker="crypto#block_encrypt-3">block_encrypt/3</seealso>, - <seealso marker="crypto#block_encrypt-4">block_encrypt/4</seealso>, - <seealso marker="crypto#block_decrypt-3">block_decrypt/3</seealso> and - <seealso marker="crypto#block_decrypt-4">block_decrypt/4</seealso>. - </p> - <p>Available in all OpenSSL compatible with Erlang CRYPTO if not disabled by configuration. + <title>Ciphers without an IV - cipher_no_iv()</title> + <p>To be used with: </p> - <p>To dynamically check availability, check that the name in the <i>Cipher and Mode</i> column is present in the - list with the <c>cipher</c> tag in the return value of - <seealso marker="crypto#supports-0">crypto:supports()</seealso>. + <list> + <item><seealso marker="crypto#crypto_one_time/4">crypto_one_time/4</seealso></item> + <item><seealso marker="crypto#crypto_init/3">crypto_init/3</seealso></item> + </list> + <p>The ciphers are: </p> <table> - <row><cell><strong>Cipher and Mode</strong></cell><cell><strong>Key length</strong><br/><strong>[bytes]</strong></cell><cell><strong>IV length</strong><br/><strong>[bytes]</strong></cell><cell><strong>Block size</strong><br/><strong>[bytes]</strong></cell></row> - <row><cell><c>aes_cbc</c></cell> <cell>16, 24, 32</cell><cell>16</cell><cell>16</cell></row> - <row><cell><c>aes_cbc128</c></cell><cell>16</cell><cell>16</cell><cell>16</cell></row> - <row><cell><c>aes_cbc256</c></cell><cell>32</cell><cell>16</cell><cell>16</cell></row> - - <row><cell><c>aes_cfb8</c></cell> <cell>16, 24, 32</cell><cell>16</cell><cell>any</cell></row> - - <row><cell><c>aes_ecb</c></cell><cell>16, 24, 32</cell><cell> </cell><cell>16</cell></row> - - <row><cell><c>aes_ige256</c></cell><cell>16</cell><cell>32</cell><cell>16</cell></row> - <row><cell><c>blowfish_cbc</c></cell> <cell>4-56</cell> <cell>8</cell> <cell>8</cell></row> - <row><cell><c>blowfish_cfb64</c></cell> <cell>≥1</cell> <cell>8</cell> <cell>any</cell></row> - <row><cell><c>blowfish_ecb</c></cell><cell>≥1</cell><cell> </cell><cell>8</cell></row> - <row><cell><c>blowfish_ofb64</c></cell><cell>≥1</cell><cell>8</cell><cell>any</cell></row> - - <row><cell><c>des3_cbc</c><br/><i>(=DES EDE3 CBC)</i></cell><cell>[8,8,8]</cell><cell>8</cell><cell>8</cell></row> - <row><cell><c>des3_cfb</c><br/><i>(=DES EDE3 CFB)</i></cell><cell>[8,8,8]</cell><cell>8</cell><cell>any</cell></row> - - <row><cell><c>des_cbc</c></cell><cell>8</cell><cell>8</cell> <cell>8</cell></row> - <row><cell><c>des_cfb</c></cell><cell>8</cell><cell>8</cell><cell>any</cell></row> - <row><cell><c>des_ecb</c></cell><cell>8</cell><cell> </cell><cell>8</cell></row> - <row><cell><c>des_ede3</c><br/><i>(=DES EDE3 CBC)</i></cell><cell>[8,8,8]</cell><cell>8</cell><cell>8</cell></row> - <row><cell><c>rc2_cbc</c></cell><cell>≥1</cell><cell>8</cell><cell>8</cell></row> - <tcaption>Block cipher key lengths</tcaption> + <row> + <cell><strong>Cipher and Mode</strong></cell> + <cell><strong>Key length</strong><br/><strong>[bytes]</strong></cell> + <cell><strong>Block size</strong><br/><strong>[bytes]</strong></cell> + </row> + <row><cell><c>aes_128_ecb</c></cell> <cell>16</cell> <cell>16</cell></row> + <row><cell><c>aes_192_ecb</c></cell> <cell>24</cell> <cell>16</cell></row> + <row><cell><c>aes_256_ecb</c></cell> <cell>32</cell> <cell>16</cell></row> + <row><cell><c>blowfish_ecb</c></cell> <cell>16</cell> <cell> 8</cell></row> + <row><cell><c>des_ecb</c></cell> <cell> 8</cell> <cell> 8</cell></row> + <row><cell><c>rc4</c></cell> <cell>16</cell> <cell> 1</cell></row> + <tcaption>Ciphers without IV</tcaption> </table> </section> <section> - <title>AEAD Ciphers</title> - <p>To be used in <seealso marker="crypto#block_encrypt-4">block_encrypt/4</seealso> and - <seealso marker="crypto#block_decrypt-4">block_decrypt/4</seealso>. + <title>Ciphers with an IV - cipher_iv()</title> + <p>To be used with: </p> - <p>To dynamically check availability, check that the name in the <i>Cipher and Mode</i> column is present in the - list with the <c>cipher</c> tag in the return value of - <seealso marker="crypto#supports-0">crypto:supports()</seealso>. + <list> + <item><seealso marker="crypto#crypto_one_time/5">crypto_one_time/5</seealso></item> + <item><seealso marker="crypto#crypto_init/4">crypto_init/4</seealso></item> + <item><seealso marker="crypto#crypto_dyn_iv_init/3">crypto_dyn_iv_init/3</seealso></item> + </list> + <p>The ciphers are: </p> <table> - <row><cell><strong>Cipher and Mode</strong></cell><cell><strong>Key length</strong><br/><strong>[bytes]</strong></cell><cell><strong>IV length</strong><br/><strong>[bytes]</strong></cell><cell><strong>AAD length</strong><br/><strong>[bytes]</strong></cell><cell><strong>Tag length</strong><br/><strong>[bytes]</strong></cell><cell><strong>Block size</strong><br/><strong>[bytes]</strong></cell><cell><strong>Supported with</strong><br/><strong>OpenSSL versions</strong></cell></row> - <row><cell><c>aes_ccm</c></cell> <cell>16,24,32</cell> <cell>7-13</cell> <cell>any</cell> <cell>even 4-16<br/>default: 12</cell> <cell>any</cell><cell>≥1.1.0</cell></row> - <row><cell><c>aes_gcm</c></cell> <cell>16,24,32</cell> <cell>≥1</cell> <cell>any</cell> <cell>1-16<br/>default: 16</cell> <cell>any</cell><cell>≥1.1.0</cell></row> - <row><cell><c>chacha20_poly1305</c></cell><cell>32</cell> <cell>1-16</cell> <cell>any</cell> <cell>16</cell> <cell>any</cell><cell>≥1.1.0</cell></row> - <tcaption>AEAD cipher key lengths</tcaption> + <row> + <cell><strong>Cipher and Mode</strong></cell> + <cell><strong>Key length</strong><br/><strong>[bytes]</strong></cell> + <cell><strong>IV length</strong><br/><strong>[bytes]</strong></cell> + <cell><strong>Block size</strong><br/><strong>[bytes]</strong></cell> + <cell><strong>Limited to</strong><br/><strong>OpenSSL versions</strong></cell> + </row> + <row><cell><c>aes_128_cbc</c></cell> <cell>16</cell> <cell>16</cell> <cell>16</cell> <cell></cell></row> + <row><cell><c>aes_192_cbc</c></cell> <cell>24</cell> <cell>16</cell> <cell>16</cell> <cell></cell></row> + <row><cell><c>aes_256_cbc</c></cell> <cell>32</cell> <cell>16</cell> <cell>16</cell> <cell></cell></row> + <row><cell><c>aes_128_cfb8</c></cell> <cell>16</cell> <cell>16</cell> <cell> 1</cell> <cell></cell></row> + <row><cell><c>aes_192_cfb8</c></cell> <cell>24</cell> <cell>16</cell> <cell> 1</cell> <cell></cell></row> + <row><cell><c>aes_256_cfb8</c></cell> <cell>32</cell> <cell>16</cell> <cell> 1</cell> <cell></cell></row> + <row><cell><c>aes_128_cfb128</c></cell><cell>16</cell> <cell>16</cell> <cell> 1</cell> <cell></cell></row> + <row><cell><c>aes_192_cfb128</c></cell><cell>24</cell> <cell>16</cell> <cell> 1</cell> <cell></cell></row> + <row><cell><c>aes_256_cfb128</c></cell><cell>32</cell> <cell>16</cell> <cell> 1</cell> <cell></cell></row> + <row><cell><c>aes_128_ctr</c></cell> <cell>16</cell> <cell>16</cell> <cell> 1</cell> <cell></cell></row> + <row><cell><c>aes_192_ctr</c></cell> <cell>24</cell> <cell>16</cell> <cell> 1</cell> <cell></cell></row> + <row><cell><c>aes_256_ctr</c></cell> <cell>32</cell> <cell>16</cell> <cell> 1</cell> <cell></cell></row> + <row><cell><c>aes_ige256</c></cell> <cell>16</cell> <cell>32</cell> <cell>16</cell> <cell></cell></row> + <row><cell><c>blowfish_cbc</c></cell> <cell>16</cell> <cell> 8</cell> <cell> 8</cell> <cell></cell></row> + <row><cell><c>blowfish_cfb64</c></cell><cell>16</cell> <cell> 8</cell> <cell> 1</cell> <cell></cell></row> + <row><cell><c>blowfish_ofb64</c></cell><cell>16</cell> <cell> 8</cell> <cell> 1</cell> <cell></cell></row> + <row><cell><c>chacha20</c></cell> <cell>32</cell> <cell>16</cell> <cell> 1</cell> <cell>≥1.1.0d</cell></row> + <row><cell><c>des_cbc</c></cell> <cell> 8</cell> <cell> 8</cell> <cell> 8</cell> <cell></cell></row> + <row><cell><c>des_ede3_cbc</c></cell> <cell>24</cell> <cell> 8</cell> <cell> 8</cell> <cell></cell></row> + <row><cell><c>des_cfb</c></cell> <cell> 8</cell> <cell> 8</cell> <cell> 1</cell> <cell></cell></row> + <row><cell><c>des_ede3_cfb</c></cell> <cell>24</cell> <cell> 8</cell> <cell> 1</cell> <cell></cell></row> + <row><cell><c>rc2_cbc</c></cell> <cell>16</cell> <cell> 8</cell> <cell> 8</cell> <cell></cell></row> + <tcaption>Ciphers with IV</tcaption> </table> </section> <section> - <title>Stream Ciphers</title> - <p>To be used in <seealso marker="crypto#stream_init-2">stream_init/2</seealso> and - <seealso marker="crypto#stream_init/3">stream_init/3</seealso>. + <title>Ciphers with AEAD - cipher_aead()</title> + <p>To be used with: </p> - <p>To dynamically check availability, check that the name in the <i>Cipher and Mode</i> column is present in the - list with the <c>cipher</c> tag in the return value of - <seealso marker="crypto#supports-0">crypto:supports()</seealso>. + <list> + <item><seealso marker="crypto#crypto_one_time_aead/6">crypto_one_time_aead/6</seealso></item> + <item><seealso marker="crypto#crypto_one_time_aead/7">crypto_one_time_aead/7</seealso></item> + </list> + <p>The ciphers are: </p> <table> - <row><cell><strong>Cipher and Mode</strong></cell><cell><strong>Key length</strong><br/><strong>[bytes]</strong></cell><cell><strong>IV length</strong><br/><strong>[bytes]</strong></cell><cell><strong>Supported with</strong><br/><strong>OpenSSL versions</strong></cell></row> - <row><cell><c>aes_ctr</c></cell><cell>16, 24, 32</cell><cell>16</cell><cell>≥1.0.1</cell></row> - <row><cell><c>rc4</c></cell><cell>≥1</cell><cell> </cell> <cell>all</cell></row> - <tcaption>Stream cipher key lengths</tcaption> + <row> + <cell><strong>Cipher and Mode</strong></cell> + <cell><strong>Key length</strong><br/><strong>[bytes]</strong></cell> + <cell><strong>IV length</strong><br/><strong>[bytes]</strong></cell> + <cell><strong>AAD length</strong><br/><strong>[bytes]</strong></cell> + <cell><strong>Tag length</strong><br/><strong>[bytes]</strong></cell> + <cell><strong>Block size</strong><br/><strong>[bytes]</strong></cell> + <cell><strong>Limited to</strong><br/><strong>OpenSSL versions</strong></cell> + </row> + <row><cell><c>aes_128_ccm</c></cell> <cell>16</cell> <cell>7-13</cell> <cell>any</cell> <cell>even 4-16<br/>default: 12</cell> <cell>any</cell><cell>≥1.0.1</cell></row> + <row><cell><c>aes_192_ccm</c></cell> <cell>24</cell> <cell>7-13</cell> <cell>any</cell> <cell>even 4-16<br/>default: 12</cell> <cell>any</cell><cell>≥1.0.1</cell></row> + <row><cell><c>aes_256_ccm</c></cell> <cell>32</cell> <cell>7-13</cell> <cell>any</cell> <cell>even 4-16<br/>default: 12</cell> <cell>any</cell><cell>≥1.0.1</cell></row> + + <row><cell><c>aes_128_gcm</c></cell> <cell>16</cell> <cell>≥1</cell> <cell>any</cell> <cell>1-16<br/>default: 16</cell> <cell>any</cell><cell>≥1.0.1</cell></row> + <row><cell><c>aes_192_gcm</c></cell> <cell>24</cell> <cell>≥1</cell> <cell>any</cell> <cell>1-16<br/>default: 16</cell> <cell>any</cell><cell>≥1.0.1</cell></row> + <row><cell><c>aes_256_gcm</c></cell> <cell>32</cell> <cell>≥1</cell> <cell>any</cell> <cell>1-16<br/>default: 16</cell> <cell>any</cell><cell>≥1.0.1</cell></row> + + <row><cell><c>chacha20_poly1305</c></cell><cell>32</cell> <cell>1-16</cell> <cell>any</cell> <cell>16</cell> <cell>any</cell><cell>≥1.1.0</cell></row> + <tcaption>AEAD ciphers</tcaption> </table> </section> </section> + <section> <title>Message Authentication Codes (MACs)</title> + <p>To be used in <seealso marker="crypto#mac-4">mac/4</seealso> and + <seealso marker="crypto:new_api#macs--message-authentication-codes-">related functions</seealso>. + </p> <section> <title>CMAC</title> - <p>To be used in <seealso marker="crypto#cmac-3">cmac/3</seealso> and - <seealso marker="crypto#cmac-3">cmac/4</seealso>. - </p> <p>CMAC with the following ciphers are available with OpenSSL 1.0.1 or later if not disabled by configuration. </p> <p>To dynamically check availability, check that the name <c>cmac</c> is present in the - list with the <c>macs</c> tag in the return value of - <seealso marker="crypto#supports-0">crypto:supports()</seealso>. + list returned by <seealso marker="crypto#supports-1">crypto:supports(macs)</seealso>. Also check that the name in the <i>Cipher and Mode</i> column is present in the - list with the <c>cipher</c> tag in the return value. + list returned by <seealso marker="crypto#supports-1">crypto:supports(ciphers)</seealso>. </p> <table> - <row><cell><strong>Cipher and Mode</strong></cell><cell><strong>Key length</strong><br/><strong>[bytes]</strong></cell><cell><strong>Max Mac Length</strong><br/><strong>[bytes]</strong></cell></row> - <row><cell><c>aes_cbc</c></cell> <cell>16, 24, 32</cell><cell>16</cell></row> - <row><cell><c>aes_cbc128</c></cell><cell>16</cell><cell>16</cell></row> - <row><cell><c>aes_cbc256</c></cell><cell>32</cell><cell>16</cell></row> - - <row><cell><c>aes_cfb8</c></cell> <cell>16</cell><cell>1</cell></row> - - <row><cell><c>blowfish_cbc</c></cell> <cell>4-56</cell> <cell>8</cell></row> - <row><cell><c>blowfish_cfb64</c></cell> <cell>≥1</cell> <cell>1</cell></row> - <row><cell><c>blowfish_ecb</c></cell><cell>≥1</cell> <cell>8</cell></row> - <row><cell><c>blowfish_ofb64</c></cell><cell>≥1</cell> <cell>1</cell></row> - - <row><cell><c>des3_cbc</c><br/><i>(=DES EDE3 CBC)</i></cell><cell>[8,8,8]</cell><cell>8</cell></row> - <row><cell><c>des3_cfb</c><br/><i>(=DES EDE3 CFB)</i></cell><cell>[8,8,8]</cell><cell>1</cell></row> - - <row><cell><c>des_cbc</c></cell><cell>8</cell><cell>8</cell></row> - - <row><cell><c>des_cfb</c></cell><cell>8</cell><cell>1</cell></row> - <row><cell><c>des_ecb</c></cell><cell>8</cell><cell>1</cell></row> - <row><cell><c>rc2_cbc</c></cell><cell>≥1</cell><cell>8</cell></row> + <row> + <cell><strong>Cipher and Mode</strong></cell> + <cell><strong>Key length</strong><br/><strong>[bytes]</strong></cell> + <cell><strong>Max Mac Length</strong><br/><strong>(= default length)</strong><br/><strong>[bytes]</strong></cell> + </row> + <row><cell><c>aes_128_cbc</c></cell> <cell>16</cell> <cell>16</cell></row> + <row><cell><c>aes_192_cbc</c></cell> <cell>24</cell> <cell>16</cell></row> + <row><cell><c>aes_256_cbc</c></cell> <cell>32</cell> <cell>16</cell></row> + <row><cell><c>aes_128_ecb</c></cell> <cell>16</cell> <cell>16</cell></row> + <row><cell><c>aes_192_ecb</c></cell> <cell>24</cell> <cell>16</cell></row> + <row><cell><c>aes_256_ecb</c></cell> <cell>32</cell> <cell>16</cell></row> + <row><cell><c>blowfish_cbc</c></cell> <cell>16</cell> <cell> 8</cell></row> + <row><cell><c>blowfish_ecb</c></cell> <cell>16</cell> <cell> 8</cell></row> + <row><cell><c>des_cbc</c></cell> <cell> 8</cell> <cell> 8</cell></row> + <row><cell><c>des_ecb</c></cell> <cell> 8</cell> <cell> 8</cell></row> + <row><cell><c>des_ede3_cbc</c></cell> <cell>24</cell> <cell> 8</cell></row> + <row><cell><c>rc2_cbc</c></cell> <cell>16</cell> <cell> 8</cell></row> <tcaption>CMAC cipher key lengths</tcaption> </table> </section> @@ -162,9 +203,34 @@ <p>Available in all OpenSSL compatible with Erlang CRYPTO if not disabled by configuration. </p> <p>To dynamically check availability, check that the name <c>hmac</c> is present in the - list with the <c>macs</c> tag in the return value of - <seealso marker="crypto#supports-0">crypto:supports()</seealso>. + list returned by <seealso marker="crypto#supports-1">crypto:supports(macs)</seealso> and + that the hash name is present in the + list returned by <seealso marker="crypto#supports-1">crypto:supports(hashs)</seealso>. </p> + + <table> + <row> + <cell><strong>Hash</strong></cell> + <cell><strong>Max Mac Length</strong><br/><strong>(= default length)</strong><br/><strong>[bytes]</strong></cell> + </row> + <row><cell><c>sha</c></cell> <cell>20</cell></row> + <row><cell><c>sha224</c></cell> <cell>28</cell></row> + <row><cell><c>sha256</c></cell> <cell>32</cell></row> + <row><cell><c>sha384</c></cell> <cell>48</cell></row> + <row><cell><c>sha512</c></cell> <cell>64</cell></row> + <row><cell><c>sha3_224</c></cell> <cell>28</cell></row> + <row><cell><c>sha3_256</c></cell> <cell>32</cell></row> + <row><cell><c>sha3_384</c></cell> <cell>48</cell></row> + <row><cell><c>sha3_512</c></cell> <cell>64</cell></row> + <row><cell><c>blake2b</c></cell> <cell>64</cell></row> + <row><cell><c>blake2s</c></cell> <cell>32</cell></row> + <row><cell><c>md4</c></cell> <cell>16</cell></row> + <row><cell><c>md5</c></cell> <cell>16</cell></row> + <row><cell><c>ripemd160</c></cell> <cell>20</cell></row> + <tcaption>HMAC output sizes</tcaption> + </table> + + </section> <section> @@ -172,8 +238,9 @@ <p>POLY1305 is available with OpenSSL 1.1.1 or later if not disabled by configuration. </p> <p>To dynamically check availability, check that the name <c>poly1305</c> is present in the - list with the <c>macs</c> tag in the return value of - <seealso marker="crypto#supports-0">crypto:supports()</seealso>. + list returned by <seealso marker="crypto#supports-1">crypto:supports(macs)</seealso>. + </p> + <p>The poly1305 mac wants an 32 bytes key and produces a 16 byte MAC by default. </p> </section> @@ -183,22 +250,20 @@ <title>Hash</title> <p>To dynamically check availability, check that the wanted name in the <i>Names</i> column is present in the - list with the <c>hashs</c> tag in the return value of - <seealso marker="crypto#supports-0">crypto:supports()</seealso>. + list returned by <seealso marker="crypto#supports-1">crypto:supports(hashs)</seealso>. </p> - <table> <row><cell><strong>Type</strong></cell> <cell><strong>Names</strong></cell> - <cell><strong>Supported with</strong><br/><strong>OpenSSL versions</strong></cell> + <cell><strong>Limitated to</strong><br/><strong>OpenSSL versions</strong></cell> </row> - <row><cell>SHA1</cell><cell>sha</cell><cell>all</cell></row> - <row><cell>SHA2</cell><cell>sha224, sha256, sha384, sha512</cell><cell>all</cell></row> + <row><cell>SHA1</cell><cell>sha</cell><cell></cell></row> + <row><cell>SHA2</cell><cell>sha224, sha256, sha384, sha512</cell><cell></cell></row> <row><cell>SHA3</cell><cell>sha3_224, sha3_256, sha3_384, sha3_512</cell><cell>≥1.1.1</cell></row> - <row><cell>MD4</cell><cell>md4</cell><cell>all</cell></row> - <row><cell>MD5</cell><cell>md5</cell><cell>all</cell></row> - <row><cell>RIPEMD</cell><cell>ripemd160</cell><cell>all</cell></row> + <row><cell>MD4</cell><cell>md4</cell><cell></cell></row> + <row><cell>MD5</cell><cell>md5</cell><cell></cell></row> + <row><cell>RIPEMD</cell><cell>ripemd160</cell><cell></cell></row> <tcaption></tcaption> </table> </section> @@ -210,8 +275,7 @@ <title>RSA</title> <p>RSA is available with all OpenSSL versions compatible with Erlang CRYPTO if not disabled by configuration. To dynamically check availability, check that the atom <c>rsa</c> is present in the - list with the <c>public_keys</c> tag in the return value of - <seealso marker="crypto#supports-0">crypto:supports()</seealso>. + list returned by <seealso marker="crypto#supports-1">crypto:supports(public_keys)</seealso>. </p> <warning> <!-- In RefMan rsa_opt(), rsa_sign_verify_opt() and User's man RSA --> @@ -283,8 +347,7 @@ <title>DSS</title> <p>DSS is available with OpenSSL versions compatible with Erlang CRYPTO if not disabled by configuration. To dynamically check availability, check that the atom <c>dss</c> is present in the - list with the <c>public_keys</c> tag in the return value of - <seealso marker="crypto#supports-0">crypto:supports()</seealso>. + list returned by <seealso marker="crypto#supports-1">crypto:supports(public_keys)</seealso>. </p> </section> @@ -292,13 +355,11 @@ <title>ECDSA</title> <p>ECDSA is available with OpenSSL 0.9.8o or later if not disabled by configuration. To dynamically check availability, check that the atom <c>ecdsa</c> is present in the - list with the <c>public_keys</c> tag in the return value of - <seealso marker="crypto#supports-0">crypto:supports()</seealso>. - If the atom <c>ec_gf2m</c> characteristic two field curves are available. + list returned by <seealso marker="crypto#supports-1">crypto:supports(public_keys)</seealso>. + If the atom <c>ec_gf2m</c> also is present, the characteristic two field curves are available. </p> - <p>The actual supported named curves could be checked by examining the list with the - <c>curves</c> tag in the return value of - <seealso marker="crypto#supports-0">crypto:supports()</seealso>. + <p>The actual supported named curves could be checked by examining the + list returned by <seealso marker="crypto#supports-1">crypto:supports(curves)</seealso>. </p> </section> @@ -306,13 +367,11 @@ <title>EdDSA</title> <p>EdDSA is available with OpenSSL 1.1.1 or later if not disabled by configuration. To dynamically check availability, check that the atom <c>eddsa</c> is present in the - list with the <c>public_keys</c> tag in the return value of - <seealso marker="crypto#supports-0">crypto:supports()</seealso>. + list returned by <seealso marker="crypto#supports-1">crypto:supports(public_keys)</seealso>. </p> <p>Support for the curves ed25519 and ed448 is implemented. The actual supported named curves could be checked by examining the list with the - <c>curves</c> tag in the return value of - <seealso marker="crypto#supports-0">crypto:supports()</seealso>. + list returned by <seealso marker="crypto#supports-1">crypto:supports(curves)</seealso>. </p> </section> @@ -321,8 +380,7 @@ <p>Diffie-Hellman computations are available with OpenSSL versions compatible with Erlang CRYPTO if not disabled by configuration. To dynamically check availability, check that the atom <c>dh</c> is present in the - list with the <c>public_keys</c> tag in the return value of - <seealso marker="crypto#supports-0">crypto:supports()</seealso>. + list returned by <seealso marker="crypto#supports-1">crypto:supports(public_keys)</seealso>. </p> </section> @@ -330,17 +388,15 @@ <title>Elliptic Curve Diffie-Hellman</title> <p>Elliptic Curve Diffie-Hellman is available with OpenSSL 0.9.8o or later if not disabled by configuration. To dynamically check availability, check that the atom <c>ecdh</c> is present in the - list with the <c>public_keys</c> tag in the return value of - <seealso marker="crypto#supports-0">crypto:supports()</seealso>. + list returned by <seealso marker="crypto#supports-1">crypto:supports(public_keys)</seealso>. </p> <p>The Edward curves <c>x25519</c> and <c>x448</c> are supported with OpenSSL 1.1.1 or later if not disabled by configuration. </p> - <p>The actual supported named curves could be checked by examining the list with the - <c>curves</c> tag in the return value of - <seealso marker="crypto#supports-0">crypto:supports()</seealso>. + <p>The actual supported named curves could be checked by examining the + list returned by <seealso marker="crypto#supports-1">crypto:supports(curves)</seealso>. </p> </section> diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml index d1d1252f29..3973cf3f9f 100644 --- a/lib/crypto/doc/src/crypto.xml +++ b/lib/crypto/doc/src/crypto.xml @@ -302,6 +302,12 @@ </datatype> <datatype> + <name name="cmac_cipher_algorithm"/> + <desc> + </desc> + </datatype> + + <datatype> <name name="rsa_digest_type"/> <desc> </desc> @@ -324,6 +330,11 @@ <name name="sha2"/> <name name="sha3"/> <name name="blake2"/> + <desc> + </desc> + </datatype> + + <datatype> <name name="compatibility_only_hash"/> <desc> <p>The <c>compatibility_only_hash()</c> algorithms are recommended only for compatibility with existing applications.</p> @@ -575,10 +586,11 @@ <datatype_title>Internal data types</datatype_title> <datatype> - <name name="stream_state"/> - <name name="hmac_state"/> - <name name="hash_state"/> <name name="crypto_state"/> + <name name="hash_state"/> + <name name="hmac_state"/> + <name name="mac_state"/> + <name name="stream_state"/> <desc> <p>Contexts with an internal state that should not be manipulated but passed between function calls. </p> @@ -783,6 +795,187 @@ </desc> </func> + <func> + <name name="mac" arity="3" since="OTP @OTP-13872@"/> + <fsummary></fsummary> + <desc> + <p>Short for <seealso marker="#mac-4">mac(Type, undefined, Key, Data)</seealso>. + </p> + </desc> + </func> + + <func> + <name name="mac" arity="4" since="OTP @OTP-13872@"/> + <fsummary></fsummary> + <desc> + <p>Computes a MAC (Message Authentication Code) of type <c>Type</c> from <c>Data</c>. + </p> + + <p><c>SubType</c> depends on the MAC <c>Type</c>: + </p> + <list> + <item>For <c>hmac</c> it is a hash algorithm, see + <seealso marker="algorithm_details#hmac">Algorithm Details</seealso> in the User's Guide. + </item> + <item>For <c>cmac</c> it is a cipher suitable for cmac, see + <seealso marker="algorithm_details#cmac">Algorithm Details</seealso> in the User's Guide. + </item> + <item>For <c>poly1305</c> it should be set to <c>undefined</c> or the + <seealso marker="#mac_init-2">mac/2</seealso> function could be used instead, see + <seealso marker="algorithm_details#poly1305">Algorithm Details</seealso> in the User's Guide. + </item> + </list> + + <p><c>Key</c> is the authentication key with a length according to the + <c>Type</c> and <c>SubType</c>. + The key length could be found with the + <seealso marker="#hash_info-1">hash_info/1</seealso> (<c>hmac</c>) for and + <seealso marker="#cipher_info-1">cipher_info/1</seealso> (<c>cmac</c>) + functions. For <c>poly1305</c> the key length is 32 bytes. Note that + the cryptographic quality of the key is not checked. + </p> + + <p>The <c>Mac</c> result will have a default length depending on the <c>Type</c> and <c>SubType</c>. + To set a shorter length, use <seealso marker="#macN-4">macN/4</seealso> or + <seealso marker="#macN-5">macN/5</seealso> instead. + The default length is documented in + <seealso marker="algorithm_details#message-authentication-codes--macs-">Algorithm Details</seealso> + in the User's Guide. + </p> + </desc> + </func> + + <func> + <name name="macN" arity="4" since="OTP @OTP-13872@"/> + <fsummary></fsummary> + <desc> + <p>Short for <seealso marker="#macN-5">macN(Type, undefined, Key, Data, MacLength)</seealso>. + </p> + </desc> + </func> + + <func> + <name name="macN" arity="5" since="OTP @OTP-13872@"/> + <fsummary></fsummary> + <desc> + <p>Computes a MAC (Message Authentication Code) + as <seealso marker="#mac-3">mac/3</seealso> and <seealso marker="#mac-4">mac/4</seealso> but + <c>MacLength</c> will limit the size of the resultant <c>Mac</c> to + at most <c>MacLength</c> bytes. + Note that if <c>MacLength</c> is greater than the actual number of + bytes returned from the underlying hash, the returned hash will have + that shorter length instead. + </p> + <p>The max <c>MacLength</c> is documented in + <seealso marker="algorithm_details#message-authentication-codes--macs-">Algorithm Details</seealso> + in the User's Guide. + </p> + </desc> + </func> + + <func> + <name name="mac_init" arity="2" since="OTP @OTP-13872@"/> + <fsummary></fsummary> + <desc> + <p>Short for <seealso marker="#mac_init-3">mac_init(Type, undefined, Key)</seealso>. + </p> + </desc> + </func> + + <func> + <name name="mac_init" arity="3" since="OTP @OTP-13872@"/> + <fsummary></fsummary> + <desc> + <p>Initializes the context for streaming MAC operations. + </p> + <p><c>Type</c> determines which mac algorithm to use in the MAC operation. + </p> + + <p><c>SubType</c> depends on the MAC <c>Type</c>: + </p> + <list> + <item>For <c>hmac</c> it is a hash algorithm, see + <seealso marker="algorithm_details#hmac">Algorithm Details</seealso> in the User's Guide. + </item> + <item>For <c>cmac</c> it is a cipher suitable for cmac, see + <seealso marker="algorithm_details#cmac">Algorithm Details</seealso> in the User's Guide. + </item> + <item>For <c>poly1305</c> it should be set to <c>undefined</c> or the + <seealso marker="#mac_init-2">mac/2</seealso> function could be used instead, see + <seealso marker="algorithm_details#poly1305">Algorithm Details</seealso> in the User's Guide. + </item> + </list> + + <p><c>Key</c> is the authentication key with a length according to the + <c>Type</c> and <c>SubType</c>. + The key length could be found with the + <seealso marker="#hash_info-1">hash_info/1</seealso> (<c>hmac</c>) for and + <seealso marker="#cipher_info-1">cipher_info/1</seealso> (<c>cmac</c>) + functions. For <c>poly1305</c> the key length is 32 bytes. Note that + the cryptographic quality of the key is not checked. + </p> + + <p>The returned <c>State</c> should be used in one or more subsequent calls to + <seealso marker="#mac_update-2">mac_update/2</seealso>. + The MAC value is finally returned by calling + <seealso marker="#mac_final-1">mac_final/1</seealso> or + <seealso marker="#mac_finalN-2">mac_finalN/2</seealso>. + </p> + + <p>See <seealso marker="crypto:new_api#example-of-mac_init-mac_update-and-mac_final"> + examples in the User's Guide.</seealso> + </p> + </desc> + </func> + + <func> + <name name="mac_update" arity="2" since="OTP @OTP-13872@"/> + <fsummary></fsummary> + <desc> + <p>Updates the MAC represented by <c>State0</c> using the given <c>Data</c> which + could be of any length. + </p> + <p>The <c>State0</c> is the State value originally from a MAC init function, that is + <seealso marker="#mac_init-2">mac_init/2</seealso>, + <seealso marker="#mac_init-3">mac_init/3</seealso> or + a previous call of <c>mac_update/2</c>. + The value <c>State0</c> is returned unchanged by the function as <c>State</c>. + </p> + </desc> + </func> + + <func> + <name name="mac_final" arity="1" since="OTP @OTP-13872@"/> + <fsummary></fsummary> + <desc> + <p>Finalizes the MAC operation referenced by <c>State</c>. The <c>Mac</c> result will have + a default length depending on the <c>Type</c> and <c>SubType</c> in the + <seealso marker="#mac_init-3">mac_init/2,3</seealso> call. + To set a shorter length, use <seealso marker="#mac_finalN-2">mac_finalN/2</seealso> instead. + The default length is documented in + <seealso marker="algorithm_details#message-authentication-codes--macs-">Algorithm Details</seealso> + in the User's Guide. + </p> + </desc> + </func> + + <func> + <name name="mac_finalN" arity="2" since="OTP @OTP-13872@"/> + <fsummary></fsummary> + <desc> + <p>Finalizes the MAC operation referenced by <c>State</c>. + </p> + <p><c>Mac</c> will be a binary with at most <c>MacLength</c> bytes. + Note that if <c>MacLength</c> is greater than the actual number of + bytes returned from the underlying hash, the returned hash will have + that shorter length instead. + </p> + <p>The max <c>MacLength</c> is documented in + <seealso marker="algorithm_details#message-authentication-codes--macs-">Algorithm Details</seealso> + in the User's Guide. + </p> + </desc> + </func> </funcs> <section> @@ -886,75 +1079,6 @@ </func> <func> - <name name="hmac" arity="3" since="OTP R16B"/> - <name name="hmac" arity="4" since="OTP R16B"/> - <fsummary></fsummary> - <desc> - <p>Computes a HMAC of type <c>Type</c> from <c>Data</c> using - <c>Key</c> as the authentication key.</p> <p><c>MacLength</c> - will limit the size of the resultant <c>Mac</c>.</p> - </desc> - </func> - - <func> - <name name="hmac_init" arity="2" since="OTP R14B03"/> - <fsummary></fsummary> - <desc> - <p>Initializes the context for streaming HMAC operations. <c>Type</c> determines - which hash function to use in the HMAC operation. <c>Key</c> is the authentication - key. The key can be any length.</p> - </desc> - </func> - - <func> - <name name="hmac_update" arity="2" since="OTP R14B03"/> - <fsummary></fsummary> - <desc> - <p>Updates the HMAC represented by <c>Context</c> using the given <c>Data</c>. <c>Context</c> - must have been generated using an HMAC init function (such as - <seealso marker="#hmac_init-2">hmac_init</seealso>). <c>Data</c> can be any length. <c>NewContext</c> - must be passed into the next call to <c>hmac_update</c> - or to one of the functions <seealso marker="#hmac_final-1">hmac_final</seealso> and - <seealso marker="#hmac_final_n-2">hmac_final_n</seealso> - </p> - <warning><p>Do not use a <c>Context</c> as argument in more than one - call to hmac_update or hmac_final. The semantics of reusing old contexts - in any way is undefined and could even crash the VM in earlier releases. - The reason for this limitation is a lack of support in the underlying - libcrypto API.</p></warning> - </desc> - </func> - - <func> - <name name="hmac_final" arity="1" since="OTP R14B03"/> - <fsummary></fsummary> - <desc> - <p>Finalizes the HMAC operation referenced by <c>Context</c>. The size of the resultant MAC is - determined by the type of hash function used to generate it.</p> - </desc> - </func> - - <func> - <name name="hmac_final_n" arity="2" since="OTP R14B03"/> - <fsummary></fsummary> - <desc> - <p>Finalizes the HMAC operation referenced by <c>Context</c>. <c>HashLen</c> must be greater than - zero. <c>Mac</c> will be a binary with at most <c>HashLen</c> bytes. Note that if HashLen is greater than the actual number of bytes returned from the underlying hash, the returned hash will have fewer than <c>HashLen</c> bytes.</p> - </desc> - </func> - - <func> - <name name="cmac" arity="3" since="OTP 20.0"/> - <name name="cmac" arity="4" since="OTP 20.0"/> - <fsummary>Calculates the Cipher-based Message Authentication Code.</fsummary> - <desc> - <p>Computes a CMAC of type <c>Type</c> from <c>Data</c> using - <c>Key</c> as the authentication key.</p> <p><c>MacLength</c> - will limit the size of the resultant <c>Mac</c>.</p> - </desc> - </func> - - <func> <name name="info_fips" arity="0" since="OTP 20.0"/> <fsummary>Provides information about the FIPS operating status.</fsummary> <desc> @@ -1068,15 +1192,6 @@ </func> <func> - <name name="poly1305" arity="2" since="OTP 21.1"/> - <fsummary></fsummary> - <desc> - <p>Computes a POLY1305 message authentication code (<c>Mac</c>) from <c>Data</c> using - <c>Key</c> as the authentication key.</p> - </desc> - </func> - - <func> <name name="private_decrypt" arity="4" since="OTP R16B01"/> <fsummary>Decrypts CipherText using the private Key.</fsummary> <desc> @@ -1901,7 +2016,7 @@ FloatValue = rand:uniform(). % again <seealso marker="#stream_encrypt-2">stream_encrypt</seealso> and <seealso marker="#stream_decrypt-2">stream_decrypt</seealso></p> <p>For keylengths see the - <seealso marker="crypto:algorithm_details#stream-ciphers">User's Guide</seealso>. + <seealso marker="crypto:algorithm_details#ciphers">User's Guide</seealso>. </p> </desc> </func> @@ -1917,7 +2032,7 @@ FloatValue = rand:uniform(). % again <seealso marker="#stream_encrypt-2">stream_encrypt</seealso> and <seealso marker="#stream_decrypt-2">stream_decrypt</seealso>.</p> <p>For keylengths and iv-sizes see the - <seealso marker="crypto:algorithm_details#stream-ciphers">User's Guide</seealso>. + <seealso marker="crypto:algorithm_details#ciphers">User's Guide</seealso>. </p> </desc> </func> @@ -1961,6 +2076,115 @@ FloatValue = rand:uniform(). % again </desc> </func> + <func> + <name name="hmac" arity="3" since="OTP R16B"/> + <name name="hmac" arity="4" since="OTP R16B"/> + <fsummary></fsummary> + <desc> + <dont><p>Don't use this function for new programs! Use + <seealso marker="crypto#mac-4">mac/4</seealso> or + <seealso marker="crypto#macN-5">macN/5</seealso> in + <seealso marker="crypto:new_api">the new api</seealso>.</p> + </dont> + <p>Computes a HMAC of type <c>Type</c> from <c>Data</c> using + <c>Key</c> as the authentication key.</p> <p><c>MacLength</c> + will limit the size of the resultant <c>Mac</c>.</p> + </desc> + </func> + + <func> + <name name="hmac_init" arity="2" since="OTP R14B03"/> + <fsummary></fsummary> + <desc> + <dont><p>Don't use this function for new programs! Use + <seealso marker="crypto#mac_init-3">mac_init/3</seealso> in + <seealso marker="crypto:new_api">the new api</seealso>.</p> + </dont> + <p>Initializes the context for streaming HMAC operations. <c>Type</c> determines + which hash function to use in the HMAC operation. <c>Key</c> is the authentication + key. The key can be any length.</p> + </desc> + </func> + + <func> + <name name="hmac_update" arity="2" since="OTP R14B03"/> + <fsummary></fsummary> + <desc> + <dont><p>Don't use this function for new programs! Use + <seealso marker="crypto#mac_update-2">mac_update/2</seealso> in + <seealso marker="crypto:new_api">the new api</seealso>.</p> + </dont> + <p>Updates the HMAC represented by <c>Context</c> using the given <c>Data</c>. <c>Context</c> + must have been generated using an HMAC init function (such as + <seealso marker="#hmac_init-2">hmac_init</seealso>). <c>Data</c> can be any length. <c>NewContext</c> + must be passed into the next call to <c>hmac_update</c> + or to one of the functions <seealso marker="#hmac_final-1">hmac_final</seealso> and + <seealso marker="#hmac_final_n-2">hmac_final_n</seealso> + </p> + <warning><p>Do not use a <c>Context</c> as argument in more than one + call to hmac_update or hmac_final. The semantics of reusing old contexts + in any way is undefined and could even crash the VM in earlier releases. + The reason for this limitation is a lack of support in the underlying + libcrypto API.</p></warning> + </desc> + </func> + + <func> + <name name="hmac_final" arity="1" since="OTP R14B03"/> + <fsummary></fsummary> + <desc> + <dont><p>Don't use this function for new programs! Use + <seealso marker="crypto#mac_final-1">mac_final/1</seealso> in + <seealso marker="crypto:new_api">the new api</seealso>.</p> + </dont> + <p>Finalizes the HMAC operation referenced by <c>Context</c>. The size of the resultant MAC is + determined by the type of hash function used to generate it.</p> + </desc> + </func> + + <func> + <name name="hmac_final_n" arity="2" since="OTP R14B03"/> + <fsummary></fsummary> + <desc> + <dont><p>Don't use this function for new programs! Use + <seealso marker="crypto#mac_finalN-2">mac_finalN/2</seealso> in + <seealso marker="crypto:new_api">the new api</seealso>.</p> + </dont> + <p>Finalizes the HMAC operation referenced by <c>Context</c>. <c>HashLen</c> must be greater than + zero. <c>Mac</c> will be a binary with at most <c>HashLen</c> bytes. Note that if HashLen is greater than the actual number of bytes returned from the underlying hash, the returned hash will have fewer than <c>HashLen</c> bytes.</p> + </desc> + </func> + + <func> + <name name="cmac" arity="3" since="OTP 20.0"/> + <name name="cmac" arity="4" since="OTP 20.0"/> + <fsummary>Calculates the Cipher-based Message Authentication Code.</fsummary> + <desc> + <dont><p>Don't use this function for new programs! Use + <seealso marker="crypto#mac-4">mac/4</seealso> or + <seealso marker="crypto#macN-5">macN/5</seealso> in + <seealso marker="crypto:new_api">the new api</seealso>.</p> + </dont> + <p>Computes a CMAC of type <c>Type</c> from <c>Data</c> using + <c>Key</c> as the authentication key.</p> <p><c>MacLength</c> + will limit the size of the resultant <c>Mac</c>.</p> + </desc> + </func> + + <func> + <name name="poly1305" arity="2" since="OTP 21.1"/> + <fsummary></fsummary> + <desc> + <dont><p>Don't use this function for new programs! Use + <seealso marker="crypto#mac-3">mac/3</seealso> or + <seealso marker="crypto#macN-4">macN/4</seealso> in + <seealso marker="crypto:new_api">the new api</seealso>.</p> + </dont> + <p>Computes a POLY1305 message authentication code (<c>Mac</c>) from <c>Data</c> using + <c>Key</c> as the authentication key.</p> + </desc> + </func> + </funcs> diff --git a/lib/crypto/doc/src/new_api.xml b/lib/crypto/doc/src/new_api.xml index bd2334ac9f..aacf5e4f76 100644 --- a/lib/crypto/doc/src/new_api.xml +++ b/lib/crypto/doc/src/new_api.xml @@ -40,7 +40,7 @@ to maintain. </p> <p>It turned out that using the old api in the new way (more about that later), and still keep it - backwards compatible, was not possible. Specially as more precision in the error messages was wanted + backwards compatible, was not possible. Specially as more precision in the error messages is desired it could not be combined with the old standard. </p> <p>Therefore the old api (see next section) is kept for now but internally implemented with new primitives. @@ -49,7 +49,7 @@ <section> <title>The old API</title> - <p>The old functions - not recommended for new programs - are:</p> + <p>The old functions - not recommended for new programs - are for chipers:</p> <list> <item><seealso marker="crypto#block_encrypt-3">block_encrypt/3</seealso></item> <item><seealso marker="crypto#block_encrypt-4">block_encrypt/4</seealso></item> @@ -59,61 +59,101 @@ <item><seealso marker="crypto#stream_init-2">stream_init/3</seealso></item> <item><seealso marker="crypto#stream_encrypt-2">stream_encrypt/2</seealso></item> <item><seealso marker="crypto#stream_decrypt-2">stream_decrypt/2</seealso></item> + </list> + <p>for lists of supported algorithms:</p> + <list> <item><seealso marker="crypto#supports-0">supports/0</seealso></item> </list> + <p>and for MACs (Message Authentication Codes):</p> + <list> + <item><seealso marker="crypto#cmac-3">cmac/3</seealso></item> + <item><seealso marker="crypto#cmac-4">cmac/4</seealso></item> + <item><seealso marker="crypto#hmac-3">hmac/3</seealso></item> + <item><seealso marker="crypto#hmac-4">hmac/4</seealso></item> + <item><seealso marker="crypto#hmac_init-2">hmac_init/2</seealso></item> + <item><seealso marker="crypto#hmac_update-2">hmac_update/2</seealso></item> + <item><seealso marker="crypto#hmac_final-1">hmac_final/1</seealso></item> + <item><seealso marker="crypto#hmac_final_n-2">hmac_final_n/2</seealso></item> + <item><seealso marker="crypto#poly1305-2">poly1305/2</seealso></item> + </list> <p>They are not deprecated for now, but may be in a future release. </p> </section> <section> <title>The new API</title> - <p>The new functions for encrypting or decrypting one single binary are: - </p> - <list> - <item><seealso marker="crypto#crypto_one_time/4">crypto_one_time/4</seealso></item> - <item><seealso marker="crypto#crypto_one_time/5">crypto_one_time/5</seealso></item> - <item><seealso marker="crypto#crypto_one_time_aead/6">crypto_one_time_aead/6</seealso></item> - <item><seealso marker="crypto#crypto_one_time_aead/7">crypto_one_time_aead/7</seealso></item> - </list> - <p>In those functions the internal crypto state is first created and initialized - with the cipher type, the key and possibly other data. Then the single binary is encrypted - or decrypted, - the crypto state is de-allocated and the result of the crypto operation is returned. - </p> - <p>The <c>crypto_one_time_aead</c> functions are for the ciphers of mode <c>ccm</c> or - <c>gcm</c>, and for the cipher <c>chacha20-poly1305</c>. - </p> - <p>For repeated encryption or decryption of a text divided in parts, where the internal - crypto state is initialized once, and then many binaries are encrypted or decrypted with - the same state, the functions are: - </p> - <list> - <item><seealso marker="crypto#crypto_init/4">crypto_init/4</seealso></item> - <item><seealso marker="crypto#crypto_init/3">crypto_init/3</seealso></item> - <item><seealso marker="crypto#crypto_update/2">crypto_update/2</seealso></item> - </list> - <p>The <c>crypto_init</c> initialies an internal cipher state, and one or more calls of - <c>crypto_update</c> does the acual encryption or decryption. Note that AEAD ciphers - can't be handled this way due to their nature. - </p> - <p>For repeated encryption or decryption of a text divided in parts where the - same cipher and same key is used, but a new initialization vector (nounce) should be applied - for each part, the functions are: - </p> - <list> - <item><seealso marker="crypto#crypto_dyn_iv_init/3">crypto_dyn_iv_init/3</seealso></item> - <item><seealso marker="crypto#crypto_dyn_iv_update/3">crypto_dyn_iv_update/3</seealso></item> - </list> - <p>An example of where those functions are needed, is when handling the TLS protocol.</p> - <p>For information about available algorithms, use: - </p> - <list> - <item><seealso marker="crypto#supports-1">supports/1</seealso></item> - <item><seealso marker="crypto#hash_info-1">hash_info/1</seealso></item> - <item><seealso marker="crypto#cipher_info-1">cipher_info/1</seealso></item> - </list> + <section> + <title>Encryption and decryption</title> + <p>The new functions for encrypting or decrypting one single binary are: + </p> + <list> + <item><seealso marker="crypto#crypto_one_time/4">crypto_one_time/4</seealso></item> + <item><seealso marker="crypto#crypto_one_time/5">crypto_one_time/5</seealso></item> + <item><seealso marker="crypto#crypto_one_time_aead/6">crypto_one_time_aead/6</seealso></item> + <item><seealso marker="crypto#crypto_one_time_aead/7">crypto_one_time_aead/7</seealso></item> + </list> + <p>In those functions the internal crypto state is first created and initialized + with the cipher type, the key and possibly other data. Then the single binary is encrypted + or decrypted, + the crypto state is de-allocated and the result of the crypto operation is returned. + </p> + <p>The <c>crypto_one_time_aead</c> functions are for the ciphers of mode <c>ccm</c> or + <c>gcm</c>, and for the cipher <c>chacha20-poly1305</c>. + </p> + <p>For repeated encryption or decryption of a text divided in parts, where the internal + crypto state is initialized once, and then many binaries are encrypted or decrypted with + the same state, the functions are: + </p> + <list> + <item><seealso marker="crypto#crypto_init/4">crypto_init/4</seealso></item> + <item><seealso marker="crypto#crypto_init/3">crypto_init/3</seealso></item> + <item><seealso marker="crypto#crypto_update/2">crypto_update/2</seealso></item> + </list> + <p>The <c>crypto_init</c> initialies an internal cipher state, and one or more calls of + <c>crypto_update</c> does the acual encryption or decryption. Note that AEAD ciphers + can't be handled this way due to their nature. + </p> + <p>For repeated encryption or decryption of a text divided in parts where the + same cipher and same key is used, but a new initialization vector (nounce) should be applied + for each part, the functions are: + </p> + <list> + <item><seealso marker="crypto#crypto_dyn_iv_init/3">crypto_dyn_iv_init/3</seealso></item> + <item><seealso marker="crypto#crypto_dyn_iv_update/3">crypto_dyn_iv_update/3</seealso></item> + </list> + <p>An example of where those functions are needed, is when handling the TLS protocol.</p> + <p>For information about available algorithms, use: + </p> + <list> + <item><seealso marker="crypto#supports-1">supports/1</seealso></item> + <item><seealso marker="crypto#hash_info-1">hash_info/1</seealso></item> + <item><seealso marker="crypto#cipher_info-1">cipher_info/1</seealso></item> + </list> + </section> <section> + <title>MACs (Message Authentication Codes)</title> + <p>The new functions for calculating a MAC of a single piece of text are:</p> + <list> + <item><seealso marker="crypto#mac-3">mac/3</seealso></item> + <item><seealso marker="crypto#mac-4">mac/4</seealso></item> + <item><seealso marker="crypto#macN-4">macN/4</seealso></item> + <item><seealso marker="crypto#macN-5">macN/5</seealso></item> + </list> + <p>For calculating a MAC of a text divided in parts use:</p> + <list> + <item><seealso marker="crypto#mac_init-2">mac_init/2</seealso></item> + <item><seealso marker="crypto#mac_init-3">mac_init/3</seealso></item> + <item><seealso marker="crypto#mac_update-2">mac_update/2</seealso></item> + <item><seealso marker="crypto#mac_final-1">mac_final/1</seealso></item> + <item><seealso marker="crypto#mac_finalN-2">mac_finalN/2</seealso></item> + </list> + </section> + </section> + + <section> + <title>Examples of the new api</title> + <section> <title>Examples of crypto_init/4 and crypto_update/2</title> <p>The functions <seealso marker="crypto#crypto_init/4">crypto_init/4</seealso> and <seealso marker="crypto#crypto_update/2">crypto_update/2</seealso> are intended @@ -143,7 +183,7 @@ 8> crypto:crypto_update(StateDec, <<67,44,216,166,25,130,203>>). <<"First b">> 9> crypto:crypto_update(StateDec, <<5,66,6,162,16,79,94,115,234,197, - 94,253,16,144,151>>). + 94,253,16,144,151>>). <<"ytesSecond byte">> 10> crypto:crypto_update(StateDec, <<41>>). <<"s">> @@ -159,16 +199,16 @@ </p> <code type="erl"> encode(Crypto, Key, IV) -> - crypto_loop(crypto:crypto_init(Crypto, Key, IV, true)). + crypto_loop(crypto:crypto_init(Crypto, Key, IV, true)). crypto_loop(State) -> - receive - {Text, Requester} -> - Requester ! crypto:crypto_update(State, Text), - loop(State) - end. + receive + {Text, Requester} -> + Requester ! crypto:crypto_update(State, Text), + loop(State) + end. </code> - </section> + </section> <section> <title>Example of crypto_one_time/5</title> @@ -219,6 +259,35 @@ </p> </section> + <section> + <title>Example of mac_init mac_update and mac_final</title> + <code> + 1> Key = <<1:128>>. + <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1>> + 2> StateMac = crypto:mac_init(cmac, aes_128_cbc, Key). + #Ref<0.2424664121.2781478916.232610> + 3> crypto:mac_update(StateMac, <<"First bytes">>). + #Ref<0.2424664121.2781478916.232610> + 4> crypto:mac_update(StateMac, " "). + #Ref<0.2424664121.2781478916.232610> + 5> crypto:mac_update(StateMac, <<"last bytes">>). + #Ref<0.2424664121.2781478916.232610> + 6> crypto:mac_final(StateMac). + <<68,191,219,128,84,77,11,193,197,238,107,6,214,141,160, + 249>> + 7> + </code> + <p>and compare the result with a single calculation just for this example:</p> + <code> + 7> crypto:mac(cmac, aes_128_cbc, Key, "First bytes last bytes"). + <<68,191,219,128,84,77,11,193,197,238,107,6,214,141,160, + 249>> + 8> v(7) == v(6). + true + 9> + </code> + </section> + </section> <section> @@ -233,7 +302,7 @@ on the mode. An example is the ccm mode which has a variant called ccm8 where the so called tag has a length of eight bits. </p> - <p>The old names had by time lost any common naming which the new names now introduces. The new names include + <p>The old names had by time lost any common naming convention which the new names now introduces. The new names include the key length which improves the error checking in the lower levels of the crypto application. </p> diff --git a/lib/crypto/doc/src/notes.xml b/lib/crypto/doc/src/notes.xml index b69657bfa8..5f47981855 100644 --- a/lib/crypto/doc/src/notes.xml +++ b/lib/crypto/doc/src/notes.xml @@ -31,6 +31,23 @@ </header> <p>This document describes the changes made to the Crypto application.</p> +<section><title>Crypto 4.5.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + The cipher aes-ctr was disabled by misstake in + crypto:supports for cryptolibs before 1.0.1. It worked + however in the encrypt and decrypt functions.</p> + <p> + Own Id: OTP-15829</p> + </item> + </list> + </section> + +</section> + <section><title>Crypto 4.5</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl index 8ffdde2b90..965697578d 100644 --- a/lib/crypto/src/crypto.erl +++ b/lib/crypto/src/crypto.erl @@ -28,9 +28,6 @@ -export([hash/2, hash_init/1, hash_update/2, hash_final/1]). -export([sign/4, sign/5, verify/5, verify/6]). -export([generate_key/2, generate_key/3, compute_key/4]). --export([hmac/3, hmac/4, hmac_init/2, hmac_update/2, hmac_final/1, hmac_final_n/2]). --export([cmac/3, cmac/4]). --export([poly1305/2]). -export([exor/2, strong_rand_bytes/1, mod_pow/3]). -export([rand_seed/0, rand_seed_alg/1, rand_seed_alg/2]). -export([rand_seed_s/0, rand_seed_alg_s/1, rand_seed_alg_s/2]). @@ -48,6 +45,9 @@ -export([rand_seed/1]). %% Old interface. Now implemented with the New interface +-export([hmac/3, hmac/4, hmac_init/2, hmac_update/2, hmac_final/1, hmac_final_n/2]). +-export([cmac/3, cmac/4]). +-export([poly1305/2]). -export([stream_init/2, stream_init/3, stream_encrypt/2, stream_decrypt/2, @@ -62,7 +62,9 @@ crypto_one_time_aead/6, crypto_one_time_aead/7, crypto_dyn_iv_init/3, crypto_dyn_iv_update/3, - supports/1 + supports/1, + mac/3, mac/4, macN/4, macN/5, + mac_init/2, mac_init/3, mac_update/2, mac_final/1, mac_finalN/2 ]). @@ -109,9 +111,10 @@ stream_state/0, hmac_state/0, hash_state/0, - crypto_state/0 + crypto_state/0, + mac_state/0 ]). - + %% Private. For tests. -export([packed_openssl_version/4, engine_methods_convert_to_bitmask/2, get_test_engine/0]). @@ -136,7 +139,7 @@ -type rsa_private() :: [key_integer()] . % [E, N, D] | [E, N, D, P1, P2, E1, E2, C] -type rsa_params() :: {ModulusSizeInBits::integer(), PublicExponent::key_integer()} . --type dss_public() :: [key_integer()] . % [P, Q, G, Y] +-type dss_public() :: [key_integer()] . % [P, Q, G, Y] -type dss_private() :: [key_integer()] . % [P, Q, G, X] -type ecdsa_public() :: key_integer() . @@ -282,7 +285,7 @@ %%% New cipher schema %%% -type cipher() :: cipher_no_iv() - | cipher_iv() + | cipher_iv() | cipher_aead() . -type cipher_no_iv() :: aes_128_ecb @@ -326,7 +329,7 @@ -type cipher_aead() :: aes_128_ccm | aes_192_ccm | aes_256_ccm - + | aes_128_gcm | aes_192_gcm | aes_256_gcm @@ -334,23 +337,6 @@ | chacha20_poly1305 . -%% -type retired_cipher_no_iv_aliases() :: aes_ecb . - -%% -type retired_cipher_iv_aliases() :: aes_cbc -%% | aes_cbc128 % aes_128_cbc -%% | aes_cbc256 % aes_256_cbc -%% | aes_cfb128 -%% | aes_cfb8 -%% | aes_ctr -%% | des3_cbc % des_ede3_cbc -%% | des_ede3 % des_ede3_cbc -%% | des_ede3_cbf % des_ede3_cfb -%% | des3_cbf % des_ede3_cfb -%% | des3_cfb . % des_ede3_cfb - -%% -type retired_cipher_aead_aliases() :: aes_ccm -%% | aes_gcm . - %%%---------------------------------------------------------------- %%% Old cipher scheme %%% @@ -365,7 +351,7 @@ -type stream_cipher() :: ctr_cipher() | chacha20 | rc4 . - + %%%---- -type cbc_cipher() :: aes_128_cbc @@ -374,7 +360,7 @@ | blowfish_cbc | des_cbc | des_ede3_cbc - | rc2_cbc + | rc2_cbc | retired_cbc_cipher_aliases() . -type retired_cbc_cipher_aliases() :: aes_cbc % aes_*_cbc @@ -382,7 +368,7 @@ | aes_cbc256 % aes_256_cbc | des3_cbc % des_ede3_cbc | des_ede3 . % des_ede3_cbc - + %%%---- -type cfb_cipher() :: aes_128_cfb128 | aes_192_cfb128 @@ -398,7 +384,7 @@ -type retired_cfb_cipher_aliases() :: aes_cfb8 % aes_*_cfb8 | aes_cfb128 % aes_*_cfb128 | des3_cbf % des_ede3_cfb, cfb misspelled - | des3_cfb % des_ede3_cfb + | des3_cfb % des_ede3_cfb | des_ede3_cbf .% cfb misspelled @@ -457,6 +443,19 @@ %%-------------------------------------------------------------------- +%% +%% Make the new descriptive_error() look like the old run_time_error() +%% +-define(COMPAT(CALL), + try begin CALL end + catch + error:{error, {_File,_Line}, _Reason} -> + error(badarg); + error:{E, {_File,_Line}, _Reason} when E==notsup ; E==badarg -> + error(E) + end). + +%%-------------------------------------------------------------------- -compile(no_native). -on_load(on_load/0). -define(CRYPTO_NIF_VSN,302). @@ -580,7 +579,7 @@ hash(Type, Data) -> -spec hash_init(Type) -> State when Type :: hash_algorithm(), State :: hash_state(). -hash_init(Type) -> +hash_init(Type) -> notsup_to_error(hash_init_nif(Type)). -spec hash_update(State, Data) -> NewState when State :: hash_state(), @@ -599,25 +598,139 @@ hash_final(Context) -> %%%================================================================ %%% %%% MACs (Message Authentication Codes) -%%% +%%% %%%================================================================ -%%%---- HMAC - -type hmac_hash_algorithm() :: sha1() | sha2() | sha3() | compatibility_only_hash(). -%%%---- hmac/3,4 +-type cmac_cipher_algorithm() :: aes_128_cbc | aes_192_cbc | aes_256_cbc | blowfish_cbc + | des_cbc | des_ede3_cbc | rc2_cbc + | aes_128_cfb128 | aes_192_cfb128 | aes_256_cfb128 + | aes_128_cfb8 | aes_192_cfb8 | aes_256_cfb8 + . + +%%%---------------------------------------------------------------- +%%% Calculate MAC for the whole text at once + +-spec mac(Type :: poly1305, Key, Data) -> Mac | descriptive_error() + when Key :: iodata(), + Data :: iodata(), + Mac :: binary(). + +mac(poly1305, Key, Data) -> mac(poly1305, undefined, Key, Data). + + +-spec mac(Type, SubType, Key, Data) -> Mac | descriptive_error() + when Type :: hmac | cmac | poly1305, + SubType :: hmac_hash_algorithm() | cmac_cipher_algorithm() | undefined, + Key :: iodata(), + Data :: iodata(), + Mac :: binary(). + +mac(Type, SubType, Key, Data) -> mac_nif(Type, SubType, Key, Data). + + + +-spec macN(Type :: poly1305, Key, Data, MacLength) -> Mac | descriptive_error() + when Key :: iodata(), + Data :: iodata(), + Mac :: binary(), + MacLength :: pos_integer(). + +macN(Type, Key, Data, MacLength) -> + macN(Type, undefined, Key, Data, MacLength). + + +-spec macN(Type, SubType, Key, Data, MacLength) -> Mac | descriptive_error() + when Type :: hmac | cmac | poly1305, + SubType :: hmac_hash_algorithm() | cmac_cipher_algorithm() | undefined, + Key :: iodata(), + Data :: iodata(), + Mac :: binary(), + MacLength :: pos_integer(). + +macN(Type, SubType, Key, Data, MacLength) -> + erlang:binary_part(mac(Type,SubType,Key,Data), 0, MacLength). + + +%%%---------------------------------------------------------------- +%%% Calculate the MAC by uppdating by pieces of the text + +-opaque mac_state() :: reference() . + +-spec mac_init(Type :: poly1305, Key) -> State | descriptive_error() + when Key :: iodata(), + State :: mac_state() . +mac_init(poly1305, Key) -> + mac_init_nif(poly1305, undefined, Key). + + +-spec mac_init(Type, SubType, Key) -> State | descriptive_error() + when Type :: hmac | cmac | poly1305, + SubType :: hmac_hash_algorithm() | cmac_cipher_algorithm() | undefined, + Key :: iodata(), + State :: mac_state() . +mac_init(Type, SubType, Key) -> + mac_init_nif(Type, SubType, Key). + + +-spec mac_update(State0, Data) -> State | descriptive_error() + when Data :: iodata(), + State0 :: mac_state(), + State :: mac_state(). +mac_update(Ref, Data) -> + mac_update_nif(Ref, Data). + + + +-spec mac_final(State) -> Mac | descriptive_error() + when State :: mac_state(), + Mac :: binary(). +mac_final(Ref) -> + mac_final_nif(Ref). + + +-spec mac_finalN(State, MacLength) -> Mac | descriptive_error() + when State :: mac_state(), + MacLength :: pos_integer(), + Mac :: binary(). +mac_finalN(Ref, MacLength) -> + erlang:binary_part(mac_final(Ref), 0, MacLength). + --spec hmac(Type, Key, Data) -> +%%%---------------------------------------------------------------- +%%% NIFs for the functions above + +mac_nif(_Type, _SubType, _Key, _Data) -> ?nif_stub. + +mac_init_nif(_Type, _SubType, _Key) -> ?nif_stub. +mac_update_nif(_Ref, _Data) -> ?nif_stub. +mac_final_nif(_Ref) -> ?nif_stub. + +%%%================================================================ +%%% +%%% The "Old API", kept for compatibility +%%% +%%%================================================================ + +%%%---------------------------------------------------------------- +%%%---------------------------------------------------------------- +%%% Message Authentication Codes, MAC +%%% + +%%%---- HMAC + +%%%---- hmac/3,4 + +-spec hmac(Type, Key, Data) -> Mac when Type :: hmac_hash_algorithm(), Key :: iodata(), Data :: iodata(), Mac :: binary() . hmac(Type, Key, Data) -> - Data1 = iolist_to_binary(Data), - hmac(Type, Key, Data1, undefined, erlang:byte_size(Data1), max_bytes()). + ?COMPAT(mac(hmac, Type, Key, Data)). --spec hmac(Type, Key, Data, MacLength) -> +-spec hmac(Type, Key, Data, MacLength) -> Mac when Type :: hmac_hash_algorithm(), Key :: iodata(), Data :: iodata(), @@ -625,45 +738,43 @@ hmac(Type, Key, Data) -> Mac :: binary() . hmac(Type, Key, Data, MacLength) -> - Data1 = iolist_to_binary(Data), - hmac(Type, Key, Data1, MacLength, erlang:byte_size(Data1), max_bytes()). + ?COMPAT(macN(hmac, Type, Key, Data, MacLength)). %%%---- hmac_init, hamc_update, hmac_final --opaque hmac_state() :: binary(). +-opaque hmac_state() :: mac_state(). % Was: binary(). -spec hmac_init(Type, Key) -> State when Type :: hmac_hash_algorithm(), Key :: iodata(), State :: hmac_state() . hmac_init(Type, Key) -> - notsup_to_error(hmac_init_nif(Type, Key)). + ?COMPAT(mac_init(hmac, Type, Key)). %%%---- hmac_update -spec hmac_update(State, Data) -> NewState when Data :: iodata(), State :: hmac_state(), NewState :: hmac_state(). -hmac_update(State, Data0) -> - Data = iolist_to_binary(Data0), - hmac_update(State, Data, erlang:byte_size(Data), max_bytes()). +hmac_update(State, Data) -> + ?COMPAT(mac_update(State, Data)). %%%---- hmac_final -spec hmac_final(State) -> Mac when State :: hmac_state(), Mac :: binary(). hmac_final(Context) -> - notsup_to_error(hmac_final_nif(Context)). + ?COMPAT(mac_final(Context)). -spec hmac_final_n(State, HashLen) -> Mac when State :: hmac_state(), HashLen :: integer(), Mac :: binary(). hmac_final_n(Context, HashLen) -> - notsup_to_error(hmac_final_nif(Context, HashLen)). + ?COMPAT(mac_finalN(Context, HashLen)). %%%---- CMAC --define(CMAC_CIPHER_ALGORITHM, cbc_cipher() | cfb_cipher() | blowfish_cbc | des_ede3 | rc2_cbc ). +-define(CMAC_CIPHER_ALGORITHM, cbc_cipher() | cfb_cipher() | blowfish_cbc | des_ede3 | rc2_cbc ). -spec cmac(Type, Key, Data) -> Mac when Type :: ?CMAC_CIPHER_ALGORITHM, @@ -671,42 +782,31 @@ hmac_final_n(Context, HashLen) -> Data :: iodata(), Mac :: binary(). cmac(Type, Key, Data) -> - notsup_to_error(cmac_nif(alias(Type), Key, Data)). + ?COMPAT(mac(cmac, alias(Type), Key, Data)). -spec cmac(Type, Key, Data, MacLength) -> Mac when Type :: ?CMAC_CIPHER_ALGORITHM, Key :: iodata(), Data :: iodata(), - MacLength :: integer(), + MacLength :: integer(), Mac :: binary(). cmac(Type, Key, Data, MacLength) -> - erlang:binary_part(cmac(alias(Type), Key, Data), 0, MacLength). + ?COMPAT(macN(cmac, alias(Type), Key, Data, MacLength)). %%%---- POLY1305 -spec poly1305(iodata(), iodata()) -> Mac when Mac :: binary(). poly1305(Key, Data) -> - poly1305_nif(Key, Data). + ?COMPAT(mac(poly1305, Key, Data)). -%%%================================================================ -%%% -%%% Encrypt/decrypt, The "Old API" -%%% -%%%================================================================ +%%%---------------------------------------------------------------- +%%%---------------------------------------------------------------- +%%% Ciphers --define(COMPAT(CALL), - try begin CALL end - catch - error:{error, {_File,_Line}, _Reason} -> - error(badarg); - error:{E, {_File,_Line}, _Reason} when E==notsup ; E==badarg -> - error(E) - end). %%%---- Cipher info -%%%---------------------------------------------------------------- -spec cipher_info(Type) -> Result | run_time_error() when Type :: cipher(), Result :: #{key_length := integer(), @@ -845,7 +945,7 @@ block_decrypt(Type, Key0, CryptoText) -> Key :: iodata(), IVec ::binary(), State :: stream_state() . -stream_init(Type, Key0, IVec) when is_binary(IVec) -> +stream_init(Type, Key0, IVec) when is_binary(IVec) -> Key = iolist_to_binary(Key0), Ref = ?COMPAT(ng_crypto_init_nif(alias(Type,Key), Key, iolist_to_binary(IVec), @@ -933,7 +1033,7 @@ next_iv(Type, Data, _Ivec) -> %%%---------------------------------------------------------------- %%% %%% Create and initialize a new state for encryption or decryption -%%% +%%% -spec crypto_init(Cipher, Key, EncryptFlag) -> State | descriptive_error() when Cipher :: cipher_no_iv(), @@ -971,12 +1071,12 @@ crypto_dyn_iv_init(Cipher, Key, EncryptFlag) -> %%% Encrypt/decrypt a sequence of bytes. The sum of the sizes %%% of all blocks must be an integer multiple of the crypto's %%% blocksize. -%%% +%%% -spec crypto_update(State, Data) -> Result | descriptive_error() - when State :: crypto_state(), - Data :: iodata(), - Result :: binary() . + when State :: crypto_state(), + Data :: iodata(), + Result :: binary() . crypto_update(State, Data0) -> case iolist_to_binary(Data0) of <<>> -> @@ -1005,7 +1105,7 @@ crypto_dyn_iv_update(State, Data0, IV) -> %%% %%% Encrypt/decrypt one set bytes. %%% The size must be an integer multiple of the crypto's blocksize. -%%% +%%% -spec crypto_one_time(Cipher, Key, Data, EncryptFlag) -> Result | descriptive_error() @@ -1015,8 +1115,15 @@ crypto_dyn_iv_update(State, Data0, IV) -> EncryptFlag :: boolean(), Result :: binary() . -crypto_one_time(Cipher, Key, Data, EncryptFlag) -> - crypto_one_time(Cipher, Key, <<>>, Data, EncryptFlag). +crypto_one_time(Cipher, Key, Data0, EncryptFlag) -> + case iolist_to_binary(Data0) of + <<>> -> + <<>>; % Known to fail on OpenSSL 0.9.8h + Data -> + ng_crypto_one_time_nif(Cipher, + iolist_to_binary(Key), <<>>, Data, + EncryptFlag) + end. -spec crypto_one_time(Cipher, Key, IV, Data, EncryptFlag) -> Result | descriptive_error() @@ -1121,7 +1228,7 @@ ng_crypto_one_time_nif(_Cipher, _Key, _IVec, _Data, _EncryptFlg) -> ?nif_stub. false -> Ciphers end). - + prepend_old_aliases(L0) -> L1 = ?if_also(des_ede3_cbc, L0, @@ -1465,7 +1572,7 @@ rand_seed_nif(_Seed) -> ?nif_stub. %%% Sign -spec sign(Algorithm, DigestType, Msg, Key) - -> Signature + -> Signature when Algorithm :: pk_sign_verify_algs(), DigestType :: rsa_digest_type() | dss_digest_type() @@ -1483,7 +1590,7 @@ sign(Algorithm, Type, Data, Key) -> -spec sign(Algorithm, DigestType, Msg, Key, Options) - -> Signature + -> Signature when Algorithm :: pk_sign_verify_algs(), DigestType :: rsa_digest_type() | dss_digest_type() @@ -1580,7 +1687,7 @@ sign_verify_compatibility(Algorithm0, Type0, _Digest) -> | rsa_x931_padding | rsa_no_padding. --type rsa_opt() :: {rsa_padding, rsa_padding()} +-type rsa_opt() :: {rsa_padding, rsa_padding()} | {signature_md, atom()} | {rsa_mgf1_md, sha} | {rsa_oaep_label, binary()} @@ -1653,7 +1760,7 @@ pkey_crypt_nif(_Algorithm, _In, _Key, _Options, _IsPrivate, _IsEncrypt) -> ?nif_ %%%================================================================ -spec generate_key(Type, Params) - -> {PublicKey, PrivKeyOut} + -> {PublicKey, PrivKeyOut} when Type :: dh | ecdh | rsa | srp, PublicKey :: dh_public() | ecdh_public() | rsa_public() | srp_public(), PrivKeyOut :: dh_private() | ecdh_private() | rsa_private() | {srp_public(),srp_private()}, @@ -1663,7 +1770,7 @@ generate_key(Type, Params) -> generate_key(Type, Params, undefined). -spec generate_key(Type, Params, PrivKeyIn) - -> {PublicKey, PrivKeyOut} + -> {PublicKey, PrivKeyOut} when Type :: dh | ecdh | rsa | srp, PublicKey :: dh_public() | ecdh_public() | rsa_public() | srp_public(), PrivKeyIn :: undefined | dh_private() | ecdh_private() | rsa_private() | {srp_public(),srp_private()}, @@ -1814,7 +1921,7 @@ mod_pow(Base, Exponent, Prime) -> %%%====================================================================== %%% %%% Engine functions -%%% +%%% %%%====================================================================== %%%---- Refering to keys stored in an engine: @@ -2121,7 +2228,7 @@ ensure_engine_unloaded(Engine) -> %%---------------------------------------------------------------------- %% Function: ensure_engine_unloaded/2 %%---------------------------------------------------------------------- --spec ensure_engine_unloaded(Engine, EngineMethods) -> +-spec ensure_engine_unloaded(Engine, EngineMethods) -> Result when Engine :: engine_ref(), EngineMethods :: [engine_method_type()], Result :: ok | {error, Reason::term()}. @@ -2203,7 +2310,7 @@ path2bin(Path) when is_list(Path) -> %%%================================================================ %%% %%% Internal functions -%%% +%%% %%%================================================================ max_bytes() -> @@ -2235,43 +2342,6 @@ hash_init_nif(_Hash) -> ?nif_stub. hash_update_nif(_State, _Data) -> ?nif_stub. hash_final_nif(_State) -> ?nif_stub. -%% HMAC -------------------------------------------------------------------- - -hmac(Type, Key, Data, MacSize, Size, MaxBytes) when Size =< MaxBytes -> - notsup_to_error( - case MacSize of - undefined -> hmac_nif(Type, Key, Data); - _ -> hmac_nif(Type, Key, Data, MacSize) - end); -hmac(Type, Key, Data, MacSize, Size, MaxBytes) -> - State0 = hmac_init(Type, Key), - State1 = hmac_update(State0, Data, Size, MaxBytes), - case MacSize of - undefined -> hmac_final(State1); - _ -> hmac_final_n(State1, MacSize) - end. - -hmac_update(State, Data, Size, MaxBytes) when Size =< MaxBytes -> - notsup_to_error(hmac_update_nif(State, Data)); -hmac_update(State0, Data, _, MaxBytes) -> - <<Increment:MaxBytes/binary, Rest/binary>> = Data, - State = notsup_to_error(hmac_update_nif(State0, Increment)), - hmac_update(State, Rest, erlang:byte_size(Rest), MaxBytes). - -hmac_nif(_Type, _Key, _Data) -> ?nif_stub. -hmac_nif(_Type, _Key, _Data, _MacSize) -> ?nif_stub. -hmac_init_nif(_Type, _Key) -> ?nif_stub. -hmac_update_nif(_Context, _Data) -> ?nif_stub. -hmac_final_nif(_Context) -> ?nif_stub. -hmac_final_nif(_Context, _MacSize) -> ?nif_stub. - -%% CMAC -cmac_nif(_Type, _Key, _Data) -> ?nif_stub. - -%% POLY1305 -poly1305_nif(_Key, _Data) -> ?nif_stub. - - %% CIPHERS -------------------------------------------------------------------- cipher_info_nif(_Type) -> ?nif_stub. diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl index 56691223c4..0da70d5592 100644 --- a/lib/crypto/test/crypto_SUITE.erl +++ b/lib/crypto/test/crypto_SUITE.erl @@ -176,19 +176,19 @@ groups() -> ]}, {md4, [], [hash]}, - {md5, [], [hash, hmac]}, + {md5, [], [hash, hmac, hmac_update]}, {ripemd160, [], [hash]}, - {sha, [], [hash, hmac]}, - {sha224, [], [hash, hmac]}, - {sha256, [], [hash, hmac]}, - {sha384, [], [hash, hmac]}, - {sha512, [], [hash, hmac]}, - {sha3_224, [], [hash, hmac]}, - {sha3_256, [], [hash, hmac]}, - {sha3_384, [], [hash, hmac]}, - {sha3_512, [], [hash, hmac]}, - {blake2b, [], [hash, hmac]}, - {blake2s, [], [hash, hmac]}, + {sha, [], [hash, hmac, hmac_update]}, + {sha224, [], [hash, hmac, hmac_update]}, + {sha256, [], [hash, hmac, hmac_update]}, + {sha384, [], [hash, hmac, hmac_update]}, + {sha512, [], [hash, hmac, hmac_update]}, + {sha3_224, [], [hash, hmac, hmac_update]}, + {sha3_256, [], [hash, hmac, hmac_update]}, + {sha3_384, [], [hash, hmac, hmac_update]}, + {sha3_512, [], [hash, hmac, hmac_update]}, + {blake2b, [], [hash, hmac, hmac_update]}, + {blake2s, [], [hash, hmac, hmac_update]}, {no_blake2b, [], [no_hash, no_hmac]}, {no_blake2s, [], [no_hash, no_hmac]}, {rsa, [], [sign_verify, @@ -265,9 +265,9 @@ groups() -> %% New cipher nameing schema {des_ede3_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls]}, {des_ede3_cfb, [], [api_ng, api_ng_one_shot, api_ng_tls]}, - {aes_128_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls]}, + {aes_128_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac]}, {aes_192_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls]}, - {aes_256_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls]}, + {aes_256_cbc, [], [api_ng, api_ng_one_shot, api_ng_tls, cmac]}, {aes_128_ctr, [], [api_ng, api_ng_one_shot, api_ng_tls]}, {aes_192_ctr, [], [api_ng, api_ng_one_shot, api_ng_tls]}, {aes_256_ctr, [], [api_ng, api_ng_one_shot, api_ng_tls]}, @@ -386,7 +386,7 @@ init_per_testcase(info, Config) -> init_per_testcase(cmac, Config) -> case is_supported(cmac) of true -> - Config; + configure_mac(cmac, proplists:get_value(type,Config), Config); false -> {skip, "CMAC is not supported"} end; @@ -405,6 +405,8 @@ init_per_testcase(generate, Config) -> end; _ -> Config end; +init_per_testcase(hmac, Config) -> + configure_mac(hmac, proplists:get_value(type,Config), Config); init_per_testcase(_Name,Config) -> Config. @@ -452,27 +454,41 @@ no_hash(Config) when is_list(Config) -> notsup(fun crypto:hash_init/1, [Type]). %%-------------------------------------------------------------------- hmac() -> - [{doc, "Test all different hmac functions"}]. + [{doc, "Test hmac function"}]. hmac(Config) when is_list(Config) -> - {Type, Keys, DataLE, Expected} = proplists:get_value(hmac, Config), - Data = lazy_eval(DataLE), - hmac(Type, Keys, Data, Expected), - hmac(Type, lists:map(fun iolistify/1, Keys), lists:map(fun iolistify/1, Data), Expected), - hmac_increment(Type). + Tuples = lazy_eval(proplists:get_value(hmac, Config)), + lists:foreach(fun hmac_check/1, Tuples), + lists:foreach(fun hmac_check/1, mac_listify(Tuples)). + %%-------------------------------------------------------------------- no_hmac() -> [{doc, "Test all disabled hmac functions"}]. no_hmac(Config) when is_list(Config) -> Type = ?config(type, Config), - notsup(fun crypto:hmac/3, [Type, <<"Key">>, <<"Hi There">>]), + notsup(fun crypto:hmac/3, [Type, <<"Key">>, <<"Hi There">>]). + +%%-------------------------------------------------------------------- +hmac_update() -> + [{doc, "Test all incremental hmac functions"}]. +hmac_update(Config) -> + Type = ?config(type, Config), + hmac_increment(Type). + +%%-------------------------------------------------------------------- +no_hmac_update() -> + [{doc, "Test all disabled incremental hmac functions"}]. +no_hmac_update(Config) -> + Type = ?config(type, Config), notsup(fun crypto:hmac_init/2, [Type, <<"Key">>]). + %%-------------------------------------------------------------------- cmac() -> [{doc, "Test all different cmac functions"}]. cmac(Config) when is_list(Config) -> Pairs = lazy_eval(proplists:get_value(cmac, Config)), lists:foreach(fun cmac_check/1, Pairs), - lists:foreach(fun cmac_check/1, cmac_iolistify(Pairs)). + lists:foreach(fun cmac_check/1, mac_listify(Pairs)). + %%-------------------------------------------------------------------- poly1305() -> [{doc, "Test poly1305 function"}]. @@ -957,33 +973,46 @@ hash_increment(State0, [Increment | Rest]) -> State = crypto:hash_update(State0, Increment), hash_increment(State, Rest). -hmac(_, [],[],[]) -> - ok; -hmac(sha = Type, [Key | Keys], [ <<"Test With Truncation">> = Data| Rest], [Expected | Expects]) -> - call_crypto_hmac([Type, Key, Data, 20], Type, Expected), - hmac(Type, Keys, Rest, Expects); -hmac(Type, [Key | Keys], [ <<"Test With Truncation">> = Data| Rest], [Expected | Expects]) -> - call_crypto_hmac([Type, Key, Data, 16], Type, Expected), - hmac(Type, Keys, Rest, Expects); -hmac(Type, [Key | Keys], [Data| Rest], [Expected | Expects]) -> - call_crypto_hmac([Type, Key, Data], Type, Expected), - hmac(Type, Keys, Rest, Expects). - -call_crypto_hmac(Args, Type, Expected) -> - try apply(crypto, hmac, Args) + +%%%---------------------------------------------------------------- +hmac_check({hmac, sha=Type, Key, <<"Test With Truncation">>=Data, Expected}) -> + do_hmac_check(Type, Key, Data, 20, Expected); +hmac_check({hmac, Type, Key, <<"Test With Truncation">>=Data, Expected}) -> + do_hmac_check(Type, Key, Data, 16, Expected); +hmac_check({hmac, Type, Key, Data, Expected}) -> + do_hmac_check(Type, Key, Data, Expected). + + +do_hmac_check(Type, Key, Data, Expected) -> + try crypto:hmac(Type, Key, Data) of Expected -> ok; Other -> - ct:fail({{crypto,hmac,Args}, {expected,Expected}, {got,Other}}) + ct:fail({{crypto,hmac,[Type,Key,Data]}, {expected,Expected}, {got,Other}}) catch error:notsup -> ct:fail("HMAC ~p not supported", [Type]); Class:Cause -> - ct:fail({{crypto,hmac,Args}, {expected,Expected}, {got,{Class,Cause}}}) + ct:fail({{crypto,hmac,[Type,Key,Data]}, {expected,Expected}, {got,{Class,Cause}}}) end. +do_hmac_check(Type, Key, Data, MacLength, Expected) -> + try crypto:hmac(Type, Key, Data, MacLength) + of + Expected -> + ok; + Other -> + ct:fail({{crypto,hmac,[Type,Key,Data,MacLength]}, {expected,Expected}, {got,Other}}) + catch + error:notsup -> + ct:fail("HMAC ~p not supported", [Type]); + Class:Cause -> + ct:fail({{crypto,hmac,[Type,Key,Data,MacLength]}, {expected,Expected}, {got,{Class,Cause}}}) + end. + +%%%---------------------------------------------------------------- hmac_increment(Type) -> Key = hmac_key(Type), Increments = hmac_inc(Type), @@ -1002,7 +1031,8 @@ hmac_increment(State0, [Increment | Rest]) -> State = crypto:hmac_update(State0, Increment), hmac_increment(State, Rest). -cmac_check({Type, Key, Text, CMac}) -> +%%%---------------------------------------------------------------- +cmac_check({cmac, Type, Key, Text, CMac}) -> ExpCMac = iolist_to_binary(CMac), case crypto:cmac(Type, Key, Text) of ExpCMac -> @@ -1010,7 +1040,7 @@ cmac_check({Type, Key, Text, CMac}) -> Other -> ct:fail({{crypto, cmac, [Type, Key, Text]}, {expected, ExpCMac}, {got, Other}}) end; -cmac_check({Type, Key, Text, Size, CMac}) -> +cmac_check({cmac, Type, Key, Text, Size, CMac}) -> ExpCMac = iolist_to_binary(CMac), case crypto:cmac(Type, Key, Text, Size) of ExpCMac -> @@ -1020,6 +1050,24 @@ cmac_check({Type, Key, Text, Size, CMac}) -> end. +mac_check({MacType, SubType, Key, Text, Mac}) -> + ExpMac = iolist_to_binary(Mac), + case crypto:mac(MacType, SubType, Key, Text) of + ExpMac -> + ok; + Other -> + ct:fail({{crypto, mac, [MacType, SubType, Key, Text]}, {expected, ExpMac}, {got, Other}}) + end; +mac_check({MacType, SubType, Key, Text, Size, Mac}) -> + ExpMac = iolist_to_binary(Mac), + case crypto:mac(MacType, SubType, Key, Text, Size) of + ExpMac -> + ok; + Other -> + ct:fail({{crypto, mac, [MacType, SubType, Key, Text]}, {expected, ExpMac}, {got, Other}}) + end. + + block_cipher({Type, Key, PlainText}) -> Plain = iolist_to_binary(PlainText), CipherText = crypto:block_encrypt(Type, Key, PlainText), @@ -1450,17 +1498,17 @@ decstr2int(S) -> is_supported(Group) -> lists:member(Group, lists:append([Algo || {_, Algo} <- crypto:supports()])). -cmac_iolistify(Blocks) -> - lists:map(fun do_cmac_iolistify/1, Blocks). +mac_listify(Blocks) -> + lists:map(fun do_mac_listify/1, Blocks). block_iolistify(Blocks) -> lists:map(fun do_block_iolistify/1, Blocks). stream_iolistify(Streams) -> lists:map(fun do_stream_iolistify/1, Streams). -do_cmac_iolistify({Type, Key, Text, CMac}) -> - {Type, iolistify(Key), iolistify(Text), CMac}; -do_cmac_iolistify({Type, Key, Text, Size, CMac}) -> - {Type, iolistify(Key), iolistify(Text), Size, CMac}. +do_mac_listify({MType, Type, Key, Text, CMac}) -> + {MType, Type, iolistify(Key), iolistify(Text), CMac}; +do_mac_listify({MType, Type, Key, Text, Size, CMac}) -> + {MType, Type, iolistify(Key), iolistify(Text), Size, CMac}. do_stream_iolistify({Type, Key, PlainText}) -> {Type, iolistify(Key), iolistify(PlainText)}; @@ -1694,10 +1742,7 @@ group_config(md4 = Type, Config) -> group_config(md5 = Type, Config) -> Msgs = rfc_1321_msgs(), Digests = rfc_1321_md5_digests(), - Keys = rfc_2202_md5_keys() ++ [long_hmac_key(md5)], - Data = rfc_2202_msgs() ++ [long_msg()], - Hmac = rfc_2202_hmac_md5() ++ [long_hmac(md5)], - [{hash, {Type, Msgs, Digests}}, {hmac, {Type, Keys, Data, Hmac}} | Config]; + [{hash, {Type, Msgs, Digests}} | Config]; group_config(ripemd160 = Type, Config) -> Msgs = ripemd160_msgs(), Digests = ripemd160_digests(), @@ -1705,56 +1750,41 @@ group_config(ripemd160 = Type, Config) -> group_config(sha = Type, Config) -> Msgs = [rfc_4634_test1(), rfc_4634_test2_1(),long_msg()], Digests = rfc_4634_sha_digests() ++ [long_sha_digest()], - Keys = rfc_2202_sha_keys() ++ [long_hmac_key(sha)], - Data = rfc_2202_msgs() ++ [long_msg()], - Hmac = rfc_2202_hmac_sha() ++ [long_hmac(sha)], - [{hash, {Type, Msgs, Digests}}, {hmac, {Type, Keys, Data, Hmac}} | Config]; + [{hash, {Type, Msgs, Digests}} | Config]; group_config(sha224 = Type, Config) -> Msgs = [rfc_4634_test1(), rfc_4634_test2_1()], Digests = rfc_4634_sha224_digests(), - Keys = rfc_4231_keys(), - Data = rfc_4231_msgs(), - Hmac = rfc4231_hmac_sha224(), - [{hash, {Type, Msgs, Digests}}, {hmac, {Type, Keys, Data, Hmac}} | Config]; + [{hash, {Type, Msgs, Digests}} | Config]; group_config(sha256 = Type, Config) -> Msgs = [rfc_4634_test1(), rfc_4634_test2_1(), long_msg()], Digests = rfc_4634_sha256_digests() ++ [long_sha256_digest()], - Keys = rfc_4231_keys() ++ [long_hmac_key(sha256)], - Data = rfc_4231_msgs() ++ [long_msg()], - Hmac = rfc4231_hmac_sha256() ++ [long_hmac(sha256)], - [{hash, {Type, Msgs, Digests}}, {hmac, {Type, Keys, Data, Hmac}} | Config]; + [{hash, {Type, Msgs, Digests}} | Config]; group_config(sha384 = Type, Config) -> Msgs = [rfc_4634_test1(), rfc_4634_test2(), long_msg()], Digests = rfc_4634_sha384_digests() ++ [long_sha384_digest()], - Keys = rfc_4231_keys() ++ [long_hmac_key(sha384)], - Data = rfc_4231_msgs() ++ [long_msg()], - Hmac = rfc4231_hmac_sha384() ++ [long_hmac(sha384)], - [{hash, {Type, Msgs, Digests}}, {hmac, {Type, Keys, Data, Hmac}} | Config]; + [{hash, {Type, Msgs, Digests}} | Config]; group_config(sha512 = Type, Config) -> Msgs = [rfc_4634_test1(), rfc_4634_test2(), long_msg()], Digests = rfc_4634_sha512_digests() ++ [long_sha512_digest()], - Keys = rfc_4231_keys() ++ [long_hmac_key(sha512)], - Data = rfc_4231_msgs() ++ [long_msg()], - Hmac = rfc4231_hmac_sha512() ++ [long_hmac(sha512)], - [{hash, {Type, Msgs, Digests}}, {hmac, {Type, Keys, Data, Hmac}} | Config]; + [{hash, {Type, Msgs, Digests}} | Config]; group_config(sha3_224 = Type, Config) -> {Msgs,Digests} = sha3_test_vectors(Type), - [{hash, {Type, Msgs, Digests}}, {hmac, hmac_sha3(Type)} | Config]; + [{hash, {Type, Msgs, Digests}} | Config]; group_config(sha3_256 = Type, Config) -> {Msgs,Digests} = sha3_test_vectors(Type), - [{hash, {Type, Msgs, Digests}}, {hmac, hmac_sha3(Type)} | Config]; + [{hash, {Type, Msgs, Digests}} | Config]; group_config(sha3_384 = Type, Config) -> {Msgs,Digests} = sha3_test_vectors(Type), - [{hash, {Type, Msgs, Digests}}, {hmac, hmac_sha3(Type)} | Config]; + [{hash, {Type, Msgs, Digests}} | Config]; group_config(sha3_512 = Type, Config) -> {Msgs,Digests} = sha3_test_vectors(Type), - [{hash, {Type, Msgs, Digests}}, {hmac, hmac_sha3(Type)} | Config]; + [{hash, {Type, Msgs, Digests}} | Config]; group_config(blake2b = Type, Config) -> {Msgs, Digests} = blake2_test_vectors(Type), - [{hash, {Type, Msgs, Digests}}, {hmac, blake2_hmac(Type)} | Config]; + [{hash, {Type, Msgs, Digests}} | Config]; group_config(blake2s = Type, Config) -> {Msgs, Digests} = blake2_test_vectors(Type), - [{hash, {Type, Msgs, Digests}}, {hmac, blake2_hmac(Type)} | Config]; + [{hash, {Type, Msgs, Digests}} | Config]; group_config(rsa, Config) -> Msg = rsa_plain(), Public = rsa_public(), @@ -1828,7 +1858,6 @@ group_config(Type, Config) when Type == ed25519 ; Type == ed448 -> group_config(srp, Config) -> GenerateCompute = [srp3(), srp6(), srp6a(), srp6a_smaller_prime()], [{generate_compute, GenerateCompute} | Config]; - group_config(ecdh, Config) -> Compute = ecdh(), Generate = ecc(), @@ -1836,19 +1865,6 @@ group_config(ecdh, Config) -> group_config(dh, Config) -> GenerateCompute = [dh()], [{generate_compute, GenerateCompute} | Config]; - -group_config(aes_cbc128 = Type, Config) -> - Block = fun() -> aes_cbc128(Config) end, - Pairs = fun() -> cmac_nist(Config, Type) end, - [{cipher, Block}, {cmac, Pairs} | Config]; -group_config(aes_cbc256 = Type, Config) -> - Block = fun() -> aes_cbc256(Config) end, - Pairs = fun() -> cmac_nist(Config, Type) end, - [{cipher, Block}, {cmac, Pairs} | Config]; -group_config(chacha20_poly1305, Config) -> - AEAD = chacha20_poly1305(Config), - [{cipher, AEAD} | Config]; - group_config(poly1305, Config) -> V = [%% {Key, Txt, Expect} {%% RFC7539 2.5.2 @@ -1864,6 +1880,76 @@ group_config(F, Config) -> [{cipher, TestVectors} | Config]. +configure_mac(MacType, SubType, Config) -> + case do_configure_mac(MacType, SubType, Config) of + undefined -> + {skip, io:format("No ~p test vectors for ~p", [MacType, SubType])}; + Pairs -> + [{MacType, Pairs} | Config] + end. + +do_configure_mac(hmac, Type, _Config) -> + case Type of + md5 -> + Keys = rfc_2202_md5_keys() ++ [long_hmac_key(md5)], + Data = rfc_2202_msgs() ++ [long_msg()], + Hmac = rfc_2202_hmac_md5() ++ [long_hmac(md5)], + zip3_special(hmac, Type, Keys, Data, Hmac); + sha -> + Keys = rfc_2202_sha_keys() ++ [long_hmac_key(sha)], + Data = rfc_2202_msgs() ++ [long_msg()], + Hmac = rfc_2202_hmac_sha() ++ [long_hmac(sha)], + zip3_special(hmac, Type, Keys, Data, Hmac); + sha224 -> + Keys = rfc_4231_keys(), + Data = rfc_4231_msgs(), + Hmac = rfc4231_hmac_sha224(), + zip3_special(hmac, Type, Keys, Data, Hmac); + sha256 -> + Keys = rfc_4231_keys() ++ [long_hmac_key(sha256)], + Data = rfc_4231_msgs() ++ [long_msg()], + Hmac = rfc4231_hmac_sha256() ++ [long_hmac(sha256)], + zip3_special(hmac, Type, Keys, Data, Hmac); + sha384 -> + Keys = rfc_4231_keys() ++ [long_hmac_key(sha384)], + Data = rfc_4231_msgs() ++ [long_msg()], + Hmac = rfc4231_hmac_sha384() ++ [long_hmac(sha384)], + zip3_special(hmac, Type, Keys, Data, Hmac); + sha512 -> + Keys = rfc_4231_keys() ++ [long_hmac_key(sha512)], + Data = rfc_4231_msgs() ++ [long_msg()], + Hmac = rfc4231_hmac_sha512() ++ [long_hmac(sha512)], + zip3_special(hmac, Type, Keys, Data, Hmac); + sha3_224 -> + hmac_sha3(Type); + sha3_256 -> + hmac_sha3(Type); + sha3_384 -> + hmac_sha3(Type); + sha3_512 -> + hmac_sha3(Type); + blake2b -> + blake2_hmac(Type); + blake2s -> + blake2_hmac(Type); + _ -> + undefined + end; +do_configure_mac(cmac, Cipher, Config) -> + case Cipher of + aes_128_cbc -> + fun() -> read_rsp(Config, Cipher, ["CMACGenAES128.rsp", "CMACVerAES128.rsp"]) end; + aes_256_cbc -> + fun() -> read_rsp(Config, Cipher, ["CMACGenAES256.rsp", "CMACVerAES256.rsp"]) end; + _ -> + undefined + end. + + +zip3_special(Type, SubType, As, Bs, Cs) -> + [{Type, SubType, A, B, C} + || {A,B,C} <- lists:zip3(As, Bs, Cs)]. + rsa_sign_verify_tests(Config, Msg, Public, Private, PublicS, PrivateS, OptsToTry) -> case ?config(fips, Config) of @@ -1981,10 +2067,8 @@ blake2_test_vectors(blake2s) -> ]}. blake2_hmac(Type) -> - {Ks, Ds, Hs} = lists:unzip3( - [ {hexstr2bin(K), hexstr2bin(D), H} - || {{K, D}, H} <- lists:zip(blake2_hmac_key_data(), blake2_hmac_hmac(Type)) ]), - {Type, Ks, Ds, Hs}. + [{hmac, Type, hexstr2bin(K), hexstr2bin(D), H} + || {{K, D}, H} <- lists:zip(blake2_hmac_key_data(), blake2_hmac_hmac(Type)) ]. blake2_hmac_key_data() -> [ {"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b 0b0b0b0b", @@ -2083,12 +2167,8 @@ hmac_sha3(Type) -> sha3_384 -> 3; sha3_512 -> 4 end, - {Keys, Datas, Hmacs} = - lists:unzip3( - [{hexstr2bin(Key), hexstr2bin(Data), hexstr2bin(element(N,Hmacs))} - || {Key,Data,Hmacs} <- hmac_sha3_data()]), - {Type, Keys, Datas, Hmacs}. - + [{hmac, Type, hexstr2bin(Key), hexstr2bin(Data), hexstr2bin(element(N,Hmacs))} + || {Key,Data,Hmacs} <- hmac_sha3_data()]. hmac_sha3_data() -> [ @@ -3843,14 +3923,6 @@ ecc() -> end, TestCases). -cmac_nist(Config, aes_cbc128 = Type) -> - read_rsp(Config, Type, - ["CMACGenAES128.rsp", "CMACVerAES128.rsp"]); - -cmac_nist(Config, aes_cbc256 = Type) -> - read_rsp(Config, Type, - ["CMACGenAES256.rsp", "CMACVerAES256.rsp"]). - int_to_bin(X) when X < 0 -> int_to_bin_neg(X, []); int_to_bin(X) -> int_to_bin_pos(X, []). @@ -4068,12 +4140,11 @@ parse_rsp_cmac(Type, Key0, Msg0, Mlen0, Tlen, MAC0, Next, State, Acc) -> Mlen = binary_to_integer(Mlen0), <<Msg:Mlen/bytes, _/binary>> = hexstr2bin(Msg0), MAC = hexstr2bin(MAC0), - case binary_to_integer(Tlen) of 0 -> - parse_rsp(Type, Next, State, [{Type, Key, Msg, MAC}|Acc]); + parse_rsp(Type, Next, State, [{cmac, Type, Key, Msg, MAC}|Acc]); I -> - parse_rsp(Type, Next, State, [{Type, Key, Msg, I, MAC}|Acc]) + parse_rsp(Type, Next, State, [{cmac, Type, Key, Msg, I, MAC}|Acc]) end. api_errors_ecdh(Config) when is_list(Config) -> diff --git a/lib/crypto/vsn.mk b/lib/crypto/vsn.mk index 72a51bfec9..2315cb3c48 100644 --- a/lib/crypto/vsn.mk +++ b/lib/crypto/vsn.mk @@ -1 +1 @@ -CRYPTO_VSN = 4.5 +CRYPTO_VSN = 4.5.1 diff --git a/lib/dialyzer/doc/src/dialyzer.xml b/lib/dialyzer/doc/src/dialyzer.xml index 443de7b0dd..8dd814982d 100644 --- a/lib/dialyzer/doc/src/dialyzer.xml +++ b/lib/dialyzer/doc/src/dialyzer.xml @@ -537,7 +537,10 @@ Option :: {files, [Filename :: string()]} 'plt_check' | 'plt_remove'} | {warnings, [WarnOpts]} - | {get_warnings, bool()} + | {get_warnings, boolean()} + | {native, boolean()} + %% Defaults to false when invoked from Erlang + | {native_cache, boolean()} WarnOpts :: error_handling | no_behaviours diff --git a/lib/dialyzer/doc/src/notes.xml b/lib/dialyzer/doc/src/notes.xml index dd0a2bfd7d..dad9fd18c7 100644 --- a/lib/dialyzer/doc/src/notes.xml +++ b/lib/dialyzer/doc/src/notes.xml @@ -32,6 +32,58 @@ <p>This document describes the changes made to the Dialyzer application.</p> +<section><title>Dialyzer 4.0.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>The HiPE compiler would badly miscompile certain + try/catch expressions, so it will now refuse to compile + modules containing try or catch.</p> <p>As a consequence + of this, <c>dialyzer</c> will no longer compile key + modules to native code.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-15949</p> + </item> + </list> + </section> + +</section> + +<section><title>Dialyzer 4.0.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> Make sure Dialyzer does not crash if the formatting + of results fails. Instead of crashing, an unformatted + version of the results is returned. </p> + <p> + Own Id: OTP-15922 Aux Id: PR-2240, ERL-949 </p> + </item> + </list> + </section> + +</section> + +<section><title>Dialyzer 4.0.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> Fix a bug that caused a crash when indenting a + Dialyzer warning mentioning more than one record field. + </p> + <p> + Own Id: OTP-15861 Aux Id: ERL-953 </p> + </item> + </list> + </section> + +</section> + <section><title>Dialyzer 4.0</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl index cfe5fa9b3f..c1bc5ff597 100644 --- a/lib/dialyzer/src/dialyzer.erl +++ b/lib/dialyzer/src/dialyzer.erl @@ -605,11 +605,9 @@ ordinal(N) when is_integer(N) -> io_lib:format("~wth", [N]). %% Functions that parse type strings, literal strings, and contract %% strings. Return strings formatted by erl_pp. -%% If lib/hipe/cerl/erl_types.erl is compiled with DEBUG=true, -%% the contents of opaque types are showed inside brackets. -%% Since erl_parse:parse_form() cannot handle the bracket syntax, -%% no indentation is done. -%%-define(DEBUG , true). +%% Note we always have to catch any error when trying to parse +%% the syntax because other BEAM languages may not emit an +%% Erlang AST that transforms into valid Erlang Source Code. -define(IND, 10). @@ -620,13 +618,15 @@ con(M, F, Src, I) -> sig(Src, false) -> Src; sig(Src, true) -> - Str = lists:flatten(io_lib:format("-spec ~w:~tw~ts.", [a, b, Src])), - {ok, Tokens, _EndLocation} = erl_scan:string(Str), - exec(fun() -> - {ok, {attribute, _, spec, {_MFA, Types}}} = - erl_parse:parse_form(Tokens), - indentation(?IND) ++ pp_spec(Types) - end, Src). + try + Str = lists:flatten(io_lib:format("-spec ~w:~tw~ts.", [a, b, Src])), + {ok, Tokens, _EndLocation} = erl_scan:string(Str), + {ok, {attribute, _, spec, {_MFA, Types}}} = + erl_parse:parse_form(Tokens), + indentation(?IND) ++ pp_spec(Types) + catch + _:_ -> Src + end. %% Argument(list)s are a mix of types and Erlang code. Note: sometimes %% (contract_range, call_without_opaque, opaque_type_test), the initial @@ -642,11 +642,11 @@ c(Cerl, _I) -> field_diffs(Src, false) -> Src; field_diffs(Src, true) -> - Fields = string:split(Src, " and "), + Fields = string:split(Src, " and ", all), lists:join(" and ", [field_diff(Field) || Field <- Fields]). field_diff(Field) -> - [F | Ts] = string:split(Field, "::"), + [F | Ts] = string:split(Field, "::", all), F ++ " ::" ++ t(lists:flatten(lists:join("::", Ts)), true). rec_type("record "++Src, I) -> @@ -658,7 +658,7 @@ ps("pattern "++Src, I) -> ps("variable "++_=Src, _I) -> Src; ps("record field"++Rest, I) -> - [S, TypeStr] = string:split(Rest, "of type "), + [S, TypeStr] = string:split(Rest, "of type ", all), "record field" ++ S ++ "of type " ++ t(TypeStr, I). %% Scan and parse a type or a literal, and pretty-print it using erl_pp. @@ -681,21 +681,15 @@ ts(Src) -> [C1|Src1] = Src, % $< (product) or $( (arglist) [C2|RevSrc2] = lists:reverse(Src1), Src2 = lists:reverse(RevSrc2), - exec(fun() -> - Types = parse_types_and_literals(Src2), - CommaInd = [$, | Ind], - (indentation(?IND-1) ++ - [C1 | lists:join(CommaInd, [pp_type(Type) || Type <- Types])] ++ - [C2]) - end, Src). - --ifdef(DEBUG). -exec(F, R) -> - try F() catch _:_ -> R end. --else. -exec(F, _) -> - F(). --endif. + try + Types = parse_types_and_literals(Src2), + CommaInd = [$, | Ind], + (indentation(?IND-1) ++ + [C1 | lists:join(CommaInd, [pp_type(Type) || Type <- Types])] ++ + [C2]) + catch + _:_ -> Src + end. indentation(I) -> [$\n | lists:duplicate(I, $\s)]. diff --git a/lib/dialyzer/src/dialyzer.hrl b/lib/dialyzer/src/dialyzer.hrl index 4a12b9b671..e1821f10eb 100644 --- a/lib/dialyzer/src/dialyzer.hrl +++ b/lib/dialyzer/src/dialyzer.hrl @@ -160,7 +160,9 @@ indent_opt = ?INDENT_OPT :: iopt(), callgraph_file = "" :: file:filename(), check_plt = true :: boolean(), - solvers = [] :: [solver()]}). + solvers = [] :: [solver()], + native = maybe :: boolean() | 'maybe', + native_cache = true :: boolean()}). -record(contract, {contracts = [] :: [contract_pair()], args = [] :: [erl_types:erl_type()], diff --git a/lib/dialyzer/src/dialyzer_cl.erl b/lib/dialyzer/src/dialyzer_cl.erl index f887f661bd..5e680062fb 100644 --- a/lib/dialyzer/src/dialyzer_cl.erl +++ b/lib/dialyzer/src/dialyzer_cl.erl @@ -320,12 +320,6 @@ report_analysis_start(#options{analysis_type = Type, end end. -report_native_comp(#options{report_mode = ReportMode}) -> - case ReportMode of - quiet -> ok; - _ -> io:format(" Compiling some key modules to native code...") - end. - report_elapsed_time(T1, T2, #options{report_mode = ReportMode}) -> case ReportMode of quiet -> ok; @@ -375,7 +369,6 @@ do_analysis(Options) -> do_analysis(Files, Options, Plt, PltInfo) -> assert_writable(Options#options.output_plt), - hipe_compile(Files, Options), report_analysis_start(Options), State0 = new_state(), State1 = init_output(State0, Options), @@ -484,106 +477,6 @@ expand_dependent_modules_1([Mod|Mods], Included, ModDeps) -> expand_dependent_modules_1([], Included, _ModDeps) -> Included. --define(MIN_PARALLELISM, 7). --define(MIN_FILES_FOR_NATIVE_COMPILE, 20). - --spec hipe_compile([file:filename()], #options{}) -> 'ok'. - -hipe_compile(Files, #options{erlang_mode = ErlangMode} = Options) -> - NoNative = (get(dialyzer_options_native) =:= false), - FewFiles = (length(Files) < ?MIN_FILES_FOR_NATIVE_COMPILE), - case NoNative orelse FewFiles orelse ErlangMode of - true -> ok; - false -> - case erlang:system_info(hipe_architecture) of - undefined -> ok; - _ -> - Mods = [lists, dict, digraph, digraph_utils, ets, - gb_sets, gb_trees, ordsets, sets, sofs, - cerl, erl_types, cerl_trees, erl_bif_types, - dialyzer_analysis_callgraph, dialyzer, dialyzer_behaviours, - dialyzer_codeserver, dialyzer_contracts, - dialyzer_coordinator, dialyzer_dataflow, dialyzer_dep, - dialyzer_plt, dialyzer_succ_typings, dialyzer_typesig, - dialyzer_worker], - report_native_comp(Options), - {T1, _} = statistics(wall_clock), - Cache = (get(dialyzer_options_native_cache) =/= false), - native_compile(Mods, Cache), - {T2, _} = statistics(wall_clock), - report_elapsed_time(T1, T2, Options) - end - end. - -native_compile(Mods, Cache) -> - case dialyzer_utils:parallelism() > ?MIN_PARALLELISM of - true -> - Parent = self(), - Pids = [spawn(fun () -> Parent ! {self(), hc(M, Cache)} end) || M <- Mods], - lists:foreach(fun (Pid) -> receive {Pid, Res} -> Res end end, Pids); - false -> - lists:foreach(fun (Mod) -> hc(Mod, Cache) end, Mods) - end. - -hc(Mod, Cache) -> - {module, Mod} = code:ensure_loaded(Mod), - case code:is_module_native(Mod) of - true -> ok; - false -> - %% io:format(" ~w", [Mod]), - case Cache of - false -> - {ok, Mod} = hipe:c(Mod), - ok; - true -> - hc_cache(Mod) - end - end. - -hc_cache(Mod) -> - CacheBase = cache_base_dir(), - %% Use HiPE architecture, version and erts checksum in directory name, - %% to avoid clashes between incompatible binaries. - HipeArchVersion = - lists:concat( - [erlang:system_info(hipe_architecture), "-", - hipe:version(), "-", - hipe:erts_checksum()]), - CacheDir = filename:join(CacheBase, HipeArchVersion), - OrigBeamFile = code:which(Mod), - {ok, {Mod, <<Checksum:128>>}} = beam_lib:md5(OrigBeamFile), - CachedBeamFile = filename:join(CacheDir, lists:concat([Mod, "-", Checksum, ".beam"])), - ok = filelib:ensure_dir(CachedBeamFile), - ModBin = - case filelib:is_file(CachedBeamFile) of - true -> - {ok, BinFromFile} = file:read_file(CachedBeamFile), - BinFromFile; - false -> - {ok, Mod, CompiledBin} = compile:file(OrigBeamFile, [from_beam, native, binary]), - ok = file:write_file(CachedBeamFile, CompiledBin), - CompiledBin - end, - code:unstick_dir(filename:dirname(OrigBeamFile)), - {module, Mod} = code:load_binary(Mod, CachedBeamFile, ModBin), - true = code:is_module_native(Mod), - ok. - -cache_base_dir() -> - %% http://standards.freedesktop.org/basedir-spec/basedir-spec-0.7.html - %% If XDG_CACHE_HOME is set to an absolute path, use it as base. - XdgCacheHome = os:getenv("XDG_CACHE_HOME"), - CacheHome = - case is_list(XdgCacheHome) andalso filename:pathtype(XdgCacheHome) =:= absolute of - true -> - XdgCacheHome; - false -> - %% Otherwise, the default is $HOME/.cache. - {ok, [[Home]]} = init:get_argument(home), - filename:join(Home, ".cache") - end, - filename:join([CacheHome, "dialyzer_hipe_cache"]). - new_state() -> #cl_state{}. diff --git a/lib/dialyzer/src/dialyzer_cl_parse.erl b/lib/dialyzer/src/dialyzer_cl_parse.erl index 280cae81d5..cadc2116b0 100644 --- a/lib/dialyzer/src/dialyzer_cl_parse.erl +++ b/lib/dialyzer/src/dialyzer_cl_parse.erl @@ -316,7 +316,9 @@ common_options() -> {use_spec, get(dialyzer_options_use_contracts)}, {warnings, get(dialyzer_warnings)}, {check_plt, get(dialyzer_options_check_plt)}, - {solvers, get(dialyzer_solvers)}]. + {solvers, get(dialyzer_solvers)}, + {native, get(dialyzer_options_native)}, + {native_cache, get(dialyzer_options_native_cache)}]. %%----------------------------------------------------------------------- diff --git a/lib/dialyzer/src/dialyzer_options.erl b/lib/dialyzer/src/dialyzer_options.erl index 3b30036c1c..f88f4f8ea2 100644 --- a/lib/dialyzer/src/dialyzer_options.erl +++ b/lib/dialyzer/src/dialyzer_options.erl @@ -197,6 +197,10 @@ build_options([{OptionName, Value} = Term|Rest], Options) -> solvers -> assert_solvers(Value), build_options(Rest, Options#options{solvers = Value}); + native -> + build_options(Rest, Options#options{native = Value}); + native_cache -> + build_options(Rest, Options#options{native_cache = Value}); _ -> bad_option("Unknown dialyzer command line option", Term) end; diff --git a/lib/dialyzer/test/small_SUITE_data/results/union_paren b/lib/dialyzer/test/small_SUITE_data/results/union_paren index 3a3526df89..1766773f2d 100644 --- a/lib/dialyzer/test/small_SUITE_data/results/union_paren +++ b/lib/dialyzer/test/small_SUITE_data/results/union_paren @@ -1,7 +1,25 @@ -union_paren.erl:12: Function t2/0 has no local return -union_paren.erl:13: The call union_paren:t2(3.14) breaks the contract (integer() | atom()) -> integer() -union_paren.erl:19: Function t3/0 has no local return -union_paren.erl:20: The pattern 3.14 can never match the type atom() | integer() -union_paren.erl:5: Function t1/0 has no local return -union_paren.erl:6: The call union_paren:t1(3.14) breaks the contract ((A::integer()) | (B::atom())) -> integer() +union_paren.erl:20: Function r1/0 has no local return +union_paren.erl:21: Record construction #r1{f1::[4,...],f2::'undefined',f3::'undefined',f8::float()} violates the declared type of field f2::[atom() | pid() | integer()] and f3::[atom() | pid() | integer()] and f8::[atom() | pid() | integer()] +union_paren.erl:23: Function t1/0 has no local return +union_paren.erl:24: The call union_paren:t1(3.14) breaks the contract ((A::integer()) | (B::atom())) -> integer() +union_paren.erl:30: Function t2/0 has no local return +union_paren.erl:31: The call union_paren:t2(3.14) breaks the contract (integer() | atom()) -> integer() +union_paren.erl:37: Function t3/0 has no local return +union_paren.erl:38: The pattern 3.14 can never match the type atom() | integer() +union_paren.erl:44: Function c1/0 has no local return +union_paren.erl:45: The call union_paren:c1({'r0', 'a', 'undefined', 'undefined'}) breaks the contract (#r0{f1::integer() | pid()}) -> atom() +union_paren.erl:51: Function c2/0 has no local return +union_paren.erl:52: The call union_paren:c2({'r0', 'a', 'undefined', 'undefined'}) breaks the contract (#r0{f1::A::integer() | pid()}) -> atom() +union_paren.erl:58: Function c3/0 has no local return +union_paren.erl:59: The call union_paren:c3({'r0', 'a', 'undefined', 'undefined'}) breaks the contract (#r0{f1::(A::integer()) | (B::pid())}) -> atom() +union_paren.erl:65: Function c4/0 has no local return +union_paren.erl:66: The call union_paren:c4({'r0', 'a', 'undefined', 'undefined'}) breaks the contract (#r0{f1::X::(A::integer()) | (B::pid())}) -> atom() +union_paren.erl:72: Function c5/0 has no local return +union_paren.erl:73: The call union_paren:c5({'r1', ['a'], [1], ['a'], ['u']}) breaks the contract (#r1{f1::[integer()] | [pid()]}) -> atom() +union_paren.erl:79: Function c6/0 has no local return +union_paren.erl:80: The call union_paren:c6({'r1', ['a'], [1], ['a'], ['u']}) breaks the contract (#r1{f1::A::[integer()] | [pid()]}) -> atom() +union_paren.erl:86: Function c7/0 has no local return +union_paren.erl:87: The call union_paren:c7({'r1', ['a'], [1], ['a'], ['u']}) breaks the contract (#r1{f1::(A::[integer()]) | (B::[pid()])}) -> atom() +union_paren.erl:93: Function c8/0 has no local return +union_paren.erl:94: The call union_paren:c8({'r1', ['a'], [1], ['a'], ['u']}) breaks the contract (#r1{f1::X::(A::[integer()]) | (B::[pid()])}) -> atom() diff --git a/lib/dialyzer/test/small_SUITE_data/src/union_paren.erl b/lib/dialyzer/test/small_SUITE_data/src/union_paren.erl index 4691a57d98..65bda1876e 100644 --- a/lib/dialyzer/test/small_SUITE_data/src/union_paren.erl +++ b/lib/dialyzer/test/small_SUITE_data/src/union_paren.erl @@ -2,6 +2,24 @@ -compile(export_all). +-record(r0, + { + f1 = 4 :: atom () | integer() | pid(), + f2 :: atom() | integer() | pid(), + f3 :: A :: atom() | integer() | pid + }). + +-record(r1, + { + f1 = [4] :: [atom ()] | [integer()] | [pid()], + f2 :: [atom()] | [integer()] | [pid()], + f3 :: A :: [atom()] | [integer()] | [pid()], + f8 = [u] :: X :: [A :: atom()] | [B :: integer()] | (C :: [pid()]) + }). + +r1() -> + #r1{f8 = 3.14}. + t1() -> t1(3.14). @@ -22,3 +40,59 @@ t3() -> -spec t3(_) -> (I :: integer()) | (A :: atom()). t3(A) when is_atom(A) -> A; t3(I) when is_integer(I) -> I. + +c1() -> + c1(#r0{f1 = a}). + +-spec c1(#r0{f1 :: integer() | pid()}) -> atom(). +c1(_) -> + a. + +c2() -> + c2(#r0{f1 = a}). + +-spec c2(#r0{f1 :: A :: integer() | pid()}) -> atom(). +c2(_) -> + a. + +c3() -> + c3(#r0{f1 = a}). + +-spec c3(#r0{f1 :: (A :: integer()) | (B :: pid())}) -> atom(). +c3(_) -> + a. + +c4() -> + c4(#r0{f1 = a}). + +-spec c4(#r0{f1 :: X :: (A :: integer()) | (B :: pid())}) -> atom(). +c4(_) -> + a. + +c5() -> + c5(#r1{f1 = [a], f2 = [1], f3 = [a]}). + +-spec c5(#r1{f1 :: [integer()] | [pid()]}) -> atom(). +c5(_) -> + a. + +c6() -> + c6(#r1{f1 = [a], f2 = [1], f3 = [a]}). + +-spec c6(#r1{f1 :: A :: [integer()] | [pid()]}) -> atom(). +c6(_) -> + a. + +c7() -> + c7(#r1{f1 = [a], f2 = [1], f3 = [a]}). + +-spec c7(#r1{f1 :: (A :: [integer()]) | (B :: [pid()])}) -> atom(). +c7(_) -> + a. + +c8() -> + c8(#r1{f1 = [a], f2 = [1], f3 = [a]}). + +-spec c8(#r1{f1 :: X :: (A :: [integer()]) | (B :: [pid()])}) -> atom(). +c8(_) -> + a. diff --git a/lib/dialyzer/vsn.mk b/lib/dialyzer/vsn.mk index 95984c7c85..a77c74c717 100644 --- a/lib/dialyzer/vsn.mk +++ b/lib/dialyzer/vsn.mk @@ -1 +1 @@ -DIALYZER_VSN = 4.0 +DIALYZER_VSN = 4.0.3 diff --git a/lib/erl_interface/src/decode/decode_fun.c b/lib/erl_interface/src/decode/decode_fun.c index a7ceaa069e..76dc0e2ab8 100644 --- a/lib/erl_interface/src/decode/decode_fun.c +++ b/lib/erl_interface/src/decode/decode_fun.c @@ -80,10 +80,12 @@ int ei_decode_fun(const char *buf, int *index, erlang_fun *p) } if (p != NULL) { p->u.closure.n_free_vars = n; - p->u.closure.free_var_len = ix - ix0; - p->u.closure.free_vars = ei_malloc(ix - ix0); - if (!(p->u.closure.free_vars)) return -1; - memcpy(p->u.closure.free_vars, s + ix0, ix - ix0); + p->u.closure.free_var_len = ix - ix0; + if (p->u.closure.free_var_len > 0) { + p->u.closure.free_vars = ei_malloc(p->u.closure.free_var_len); + if (!(p->u.closure.free_vars)) return -1; + memcpy(p->u.closure.free_vars, s + ix0, p->u.closure.free_var_len); + } } s += ix; *index += s-s0; @@ -149,6 +151,7 @@ int ei_decode_fun(const char *buf, int *index, erlang_fun *p) else { p_arity = NULL; } + ix = 0; if (ei_decode_atom_as(s, &ix, p_module, MAXATOMLEN_UTF8, ERLANG_UTF8, NULL, NULL) < 0) return -1; @@ -174,6 +177,8 @@ int ei_decode_fun(const char *buf, int *index, erlang_fun *p) } if (ei_decode_long(s, &ix, p_arity) < 0) return -1; + s += ix; + *index += s - s0; return 0; } default: diff --git a/lib/erl_interface/src/decode/decode_skip.c b/lib/erl_interface/src/decode/decode_skip.c index 736c00e074..0622ce7d59 100644 --- a/lib/erl_interface/src/decode/decode_skip.c +++ b/lib/erl_interface/src/decode/decode_skip.c @@ -97,6 +97,7 @@ int ei_skip_term(const char* buf, int* index) break; case ERL_FUN_EXT: case ERL_NEW_FUN_EXT: + case ERL_EXPORT_EXT: if (ei_decode_fun(buf, index, NULL) < 0) return -1; break; default: diff --git a/lib/erl_interface/src/misc/ei_printterm.c b/lib/erl_interface/src/misc/ei_printterm.c index 5c40fb7747..aee7f7eeb0 100644 --- a/lib/erl_interface/src/misc/ei_printterm.c +++ b/lib/erl_interface/src/misc/ei_printterm.c @@ -121,8 +121,10 @@ static int print_term(FILE* fp, ei_x_buff* x, erlang_pid pid; erlang_port port; erlang_ref ref; + erlang_fun fun; double d; long l; + const char* delim; int tindex = *index; @@ -239,41 +241,47 @@ static int print_term(FILE* fp, ei_x_buff* x, m = BINPRINTSIZE; else m = l; - --m; + delim = ""; for (i = 0; i < m; ++i) { - ch_written += xprintf(fp, x, "%d,", p[i]); + ch_written += xprintf(fp, x, "%s%u", delim, (unsigned char)p[i]); + delim = ","; } - ch_written += xprintf(fp, x, "%d", p[i]); if (l > BINPRINTSIZE) ch_written += xprintf(fp, x, ",..."); xputc('>', fp, x); ++ch_written; ei_free(p); break; case ERL_BIT_BINARY_EXT: { - const char* cp; + const unsigned char* cp; size_t bits; unsigned int bitoffs; int trunc = 0; - if (ei_decode_bitstring(buf, index, &cp, &bitoffs, &bits) < 0 + if (ei_decode_bitstring(buf, index, (const char**)&cp, &bitoffs, &bits) < 0 || bitoffs != 0) { goto err; } ch_written += xprintf(fp, x, "#Bits<"); - m = (bits+7) / 8; - if (m > BINPRINTSIZE) { + if ((bits+7) / 8 > BINPRINTSIZE) { m = BINPRINTSIZE; trunc = 1; } - --m; + else + m = bits / 8; + + delim = ""; for (i = 0; i < m; ++i) { - ch_written += xprintf(fp, x, "%d,", cp[i]); + ch_written += xprintf(fp, x, "%s%u", delim, cp[i]); + delim = ","; } - ch_written += xprintf(fp, x, "%d", cp[i]); if (trunc) ch_written += xprintf(fp, x, ",..."); - else if (bits % 8 != 0) - ch_written += xprintf(fp, x, ":%u", (unsigned)(bits % 8)); + else { + bits %= 8; + if (bits) + ch_written += xprintf(fp, x, "%s%u:%u", delim, + (cp[i] >> (8-bits)), bits); + } xputc('>', fp, x); ++ch_written; break; } @@ -306,12 +314,46 @@ static int print_term(FILE* fp, ei_x_buff* x, } break; - case ERL_FLOAT_EXT: case NEW_FLOAT_EXT: if (ei_decode_double(buf, index, &d) < 0) goto err; ch_written += xprintf(fp, x, "%f", d); break; + case ERL_MAP_EXT: + if (ei_decode_map_header(buf, &tindex, &n) < 0) goto err; + ch_written += xprintf(fp, x, "#{"); + for (i = 0; i < n; ++i) { + r = print_term(fp, x, buf, &tindex); + if (r < 0) goto err; + ch_written += r; + ch_written += xprintf(fp, x, " => "); + r = print_term(fp, x, buf, &tindex); + if (r < 0) goto err; + ch_written += r; + if (i < n-1) { + xputs(", ", fp, x); ch_written += 2; + } + } + *index = tindex; + xputc('}', fp, x); ch_written++; + break; + case ERL_FUN_EXT: + case ERL_NEW_FUN_EXT: + case ERL_EXPORT_EXT: + if (ei_decode_fun(buf, &tindex, &fun) < 0) goto err; + if (fun.type == EI_FUN_EXPORT) { + ch_written += xprintf(fp, x, "fun %s:%s/%ld", + fun.module, + fun.u.exprt.func, + fun.arity); + } else { + ch_written += xprintf(fp, x, "#Fun{%s.%ld.%lu}", + fun.module, + fun.u.closure.index, + fun.u.closure.uniq); + } + *index = tindex; + break; default: goto err; } diff --git a/lib/erl_interface/test/ei_decode_encode_SUITE.erl b/lib/erl_interface/test/ei_decode_encode_SUITE.erl index 3451d9f503..0204b4cfd6 100644 --- a/lib/erl_interface/test/ei_decode_encode_SUITE.erl +++ b/lib/erl_interface/test/ei_decode_encode_SUITE.erl @@ -48,7 +48,8 @@ init_per_testcase(Case, Config) -> test_ei_decode_encode(Config) when is_list(Config) -> P = runner:start(Config, ?test_ei_decode_encode), - Fun = fun (X) -> {X,true} end, + Fun1 = fun (X) -> {X,true} end, + Fun2 = fun runner:init_per_testcase/3, Pid = self(), Port = case os:type() of {win32,_} -> @@ -70,7 +71,8 @@ test_ei_decode_encode(Config) when is_list(Config) -> BigLargeB = 1 bsl 11112 + BigSmallB, BigLargeC = BigSmallA * BigSmallB * BigSmallC * BigSmallA, - send_rec(P, Fun), + send_rec(P, Fun1), + send_rec(P, Fun2), send_rec(P, Pid), send_rec(P, Port), send_rec(P, Ref), @@ -115,7 +117,7 @@ test_ei_decode_encode(Config) when is_list(Config) -> send_rec(P, {}), send_rec(P, {atom, Pid, Port, Ref}), send_rec(P, [atom, Pid, Port, Ref]), - send_rec(P, [atom | Fun]), + send_rec(P, [atom | Fun1]), send_rec(P, #{}), send_rec(P, #{key => value}), send_rec(P, maps:put(Port, Ref, #{key => value, key2 => Pid})), diff --git a/lib/erl_interface/test/ei_decode_encode_SUITE_data/ei_decode_encode_test.c b/lib/erl_interface/test/ei_decode_encode_SUITE_data/ei_decode_encode_test.c index 85ca6c56e9..512f9ed0c7 100644 --- a/lib/erl_interface/test/ei_decode_encode_SUITE_data/ei_decode_encode_test.c +++ b/lib/erl_interface/test/ei_decode_encode_SUITE_data/ei_decode_encode_test.c @@ -564,6 +564,7 @@ TESTCASE(test_ei_decode_encode) ei_init(); decode_encode_one(&fun_type); + decode_encode_one(&fun_type); decode_encode_one(&pid_type); decode_encode_one(&port_type); decode_encode_one(&ref_type); diff --git a/lib/erl_interface/test/ei_print_SUITE.erl b/lib/erl_interface/test/ei_print_SUITE.erl index c75ce55a7d..8a35b22ae5 100644 --- a/lib/erl_interface/test/ei_print_SUITE.erl +++ b/lib/erl_interface/test/ei_print_SUITE.erl @@ -26,7 +26,8 @@ -export([all/0, suite/0, init_per_testcase/2, - atoms/1, tuples/1, lists/1, strings/1]). + atoms/1, tuples/1, lists/1, strings/1, + maps/1, funs/1, binaries/1, bitstrings/1]). -import(runner, [get_term/1]). @@ -36,8 +37,8 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. -all() -> - [atoms, tuples, lists, strings]. +all() -> + [atoms, tuples, lists, strings, maps, funs, binaries, bitstrings]. init_per_testcase(Case, Config) -> runner:init_per_testcase(?MODULE, Case, Config). @@ -142,3 +143,64 @@ strings(Config) when is_list(Config) -> runner:recv_eot(P), ok. + +maps(Config) -> + P = runner:start(Config, ?maps), + + {term, "#{}"} = get_term(P), + {term, "#{key => value}"} = get_term(P), + {term, "#{key => value, another_key => {ok, 42}}"} = get_term(P), + + runner:recv_eot(P), + ok. + +funs(Config) -> + P = runner:start(Config, ?funs), + + {term, "#Fun{some_module.42.3735928559}"} = get_term(P), + {term, "#Fun{some_module.37.195935983}"} = get_term(P), + {term, "fun erlang:abs/1"} = get_term(P), + + runner:recv_eot(P), + ok. + +binaries(Config) -> + P = runner:start(Config, ?binaries), + + "#Bin<>" = send_term_get_printed(P, <<>>), + "#Bin<1,2,3>" = send_term_get_printed(P, <<1,2,3>>), + "#Bin<0,127,128,255>" = send_term_get_printed(P, <<0,127,128,255>>), + Bin30 = <<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30>>, + "#Bin<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30>" + = send_term_get_printed(P, Bin30), + "#Bin<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,...>" + = send_term_get_printed(P, <<Bin30/binary,31>>), + + runner:recv_eot(P), + ok. + +bitstrings(Config) -> + P = runner:start(Config, ?bitstrings), + + "#Bits<1:1>" = send_term_get_printed(P, <<1:1>>), + "#Bits<123:7>" = send_term_get_printed(P, <<123:7>>), + "#Bits<1,2,3:4>" = send_term_get_printed(P, <<1,2,3:4>>), + "#Bits<0,127,128,255,126:7>" = send_term_get_printed(P, <<0,127,128,255,-2:7>>), + Bits30 = <<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19, + 20,21,22,23,24,25,26,27,28,29,30:5>>, + "#Bits<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30:5>" + = send_term_get_printed(P, Bits30), + "#Bin<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,241>" + = send_term_get_printed(P, <<Bits30/bits,1:3>>), + "#Bits<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,240,...>" + = send_term_get_printed(P, <<Bits30/bits,1:4>>), + + runner:recv_eot(P), + ok. + + + +send_term_get_printed(Port, Term) -> + Port ! {self(), {command, term_to_binary(Term)}}, + {term, String} = get_term(Port), + String. diff --git a/lib/erl_interface/test/ei_print_SUITE_data/ei_print_test.c b/lib/erl_interface/test/ei_print_SUITE_data/ei_print_test.c index 80be3016e6..27d4153250 100644 --- a/lib/erl_interface/test/ei_print_SUITE_data/ei_print_test.c +++ b/lib/erl_interface/test/ei_print_SUITE_data/ei_print_test.c @@ -29,6 +29,46 @@ */ static void +send_printed_buf(ei_x_buff* x) +{ + char* b = NULL; + char fn[256]; + char *tmp = getenv("temp"); + FILE* f; + int n, index = 0, ver; + +#ifdef VXWORKS + tmp = "."; +#else + if (tmp == NULL) { + tmp = "/tmp"; + } +#endif + strcpy(fn, tmp); + strcat(fn, "/ei_print_test.txt"); + f = fopen(fn, "w+"); + ei_decode_version(x->buff, &index, &ver); + n = ei_print_term(f, x->buff, &index); + if (n < 0) { + fclose(f); + x->index = 0; + ei_x_format(x, "~s", "ERROR: term decoding failed"); + send_bin_term(x); + } else { + fseek(f, 0, SEEK_SET); + b = malloc(n+1); + fread(b, 1, n, f); + b[n] = '\0'; + fclose(f); + x->index = 0; + ei_x_format(x, "~s", b); + send_bin_term(x); + free(b); + } +} + + +static void send_printed3(char* format, char* p1, char* p2, int fl) { char* b = NULL; @@ -43,25 +83,7 @@ send_printed3(char* format, char* p1, char* p2, int fl) } else { ei_x_format(&x, format, p1, p2); } -#ifdef VXWORKS - tmp = "."; -#else - if (tmp == NULL) tmp = "/tmp"; -#endif - strcpy(fn, tmp); - strcat(fn, "/ei_print_test.txt"); - f = fopen(fn, "w+"); - ei_decode_version(x.buff, &index, &ver); - n = ei_print_term(f, x.buff, &index); - fseek(f, 0, SEEK_SET); - b = malloc(n+1); - fread(b, 1, n, f); - b[n] = '\0'; - fclose(f); - x.index = 0; - ei_x_format(&x, "~s", b); - send_bin_term(&x); - free(b); + send_printed_buf(&x); ei_x_free(&x); } @@ -184,4 +206,146 @@ TESTCASE(strings) report(1); } +TESTCASE(maps) +{ + ei_x_buff x; + + ei_init(); + + ei_x_new_with_version(&x); + ei_x_encode_map_header(&x, 0); + send_printed_buf(&x); + ei_x_free(&x); + + ei_x_new_with_version(&x); + ei_x_encode_map_header(&x, 1); + ei_x_encode_atom(&x, "key"); + ei_x_encode_atom(&x, "value"); + send_printed_buf(&x); + ei_x_free(&x); + + ei_x_new_with_version(&x); + ei_x_encode_map_header(&x, 2); + ei_x_encode_atom(&x, "key"); + ei_x_encode_atom(&x, "value"); + ei_x_encode_atom(&x, "another_key"); + ei_x_encode_tuple_header(&x, 2); + ei_x_encode_atom(&x, "ok"); + ei_x_encode_long(&x, 42L); + send_printed_buf(&x); + ei_x_free(&x); + + report(1); +} + +TESTCASE(funs) +{ + ei_x_buff x; + erlang_pid self; + erlang_fun fun; + + strcpy(self.node, "node@host"); + self.num = 9; + self.serial = 99; + self.creation = 1; + + ei_init(); + + ei_x_new_with_version(&x); + fun.arity = -1; /* Will encode as FUN_EXT */ + strcpy(fun.module, "some_module"); + fun.type = EI_FUN_CLOSURE; + fun.u.closure.pid = self; + fun.u.closure.index = fun.u.closure.old_index = 42; + fun.u.closure.uniq = 0xDEADBEEF; + fun.u.closure.n_free_vars = 0; + fun.u.closure.free_var_len = 0; + ei_x_encode_fun(&x, &fun); + send_printed_buf(&x); + ei_x_free(&x); + + ei_x_new_with_version(&x); + fun.arity = 0; /* Will encode as NEW_FUN_EXT */ + strcpy(fun.module, "some_module"); + fun.type = EI_FUN_CLOSURE; + fun.u.closure.pid = self; + fun.u.closure.index = fun.u.closure.old_index = 37; + fun.u.closure.uniq = 0xBADBEEF; + fun.u.closure.n_free_vars = 0; + fun.u.closure.free_var_len = 0; + ei_x_encode_fun(&x, &fun); + send_printed_buf(&x); + ei_x_free(&x); + + ei_x_new_with_version(&x); + fun.arity = 1; + strcpy(fun.module, "erlang"); + fun.type = EI_FUN_EXPORT; + fun.u.exprt.func = "abs"; + ei_x_encode_fun(&x, &fun); + send_printed_buf(&x); + ei_x_free(&x); + + report(1); +} + +TESTCASE(binaries) +{ + char *buf; + long len; + int err, n, index; + ei_x_buff x; + + ei_init(); + + for (n = 5; n; n--) { + buf = read_packet(NULL); + + index = 0; + err = ei_decode_version(buf, &index, NULL); + if (err != 0) + fail1("ei_decode_version returned %d", err); + err = ei_decode_binary(buf, &index, NULL, &len); + if (err != 0) + fail1("ei_decode_binary returned %d", err); + + ei_x_new(&x); + ei_x_append_buf(&x, buf, index); + send_printed_buf(&x); + ei_x_free(&x); + + free_packet(buf); + } + report(1); +} + +TESTCASE(bitstrings) +{ + char *buf; + long len; + int err, n, index; + ei_x_buff x; + + ei_init(); + + for (n = 7; n; n--) { + buf = read_packet(NULL); + + index = 0; + err = ei_decode_version(buf, &index, NULL); + if (err != 0) + fail1("ei_decode_version returned %d", err); + err = ei_decode_bitstring(buf, &index, NULL, NULL, NULL); + if (err != 0) + fail1("ei_decode_bitstring returned %d", err); + + ei_x_new(&x); + ei_x_append_buf(&x, buf, index); + send_printed_buf(&x); + ei_x_free(&x); + + free_packet(buf); + } + report(1); +} diff --git a/lib/hipe/doc/src/hipe_app.xml b/lib/hipe/doc/src/hipe_app.xml index 61d92fdffe..5ac445ac58 100644 --- a/lib/hipe/doc/src/hipe_app.xml +++ b/lib/hipe/doc/src/hipe_app.xml @@ -66,6 +66,10 @@ <item><p>The HiPE compiler will crash on modules containing binary matching.</p> </item> + <tag>try/catch</tag> + <item><p>The HiPE compiler will crash on modules containing 'try' or + 'catch'.</p> + </item> <tag>Stack traces</tag> <item><p>Stack traces returned from <seealso marker="erts:erlang#get_stacktrace/0"> diff --git a/lib/hipe/doc/src/notes.xml b/lib/hipe/doc/src/notes.xml index a2e0766bb7..3fad2ac53a 100644 --- a/lib/hipe/doc/src/notes.xml +++ b/lib/hipe/doc/src/notes.xml @@ -31,6 +31,26 @@ </header> <p>This document describes the changes made to HiPE.</p> +<section><title>Hipe 3.19.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>The HiPE compiler would badly miscompile certain + try/catch expressions, so it will now refuse to compile + modules containing try or catch.</p> <p>As a consequence + of this, <c>dialyzer</c> will no longer compile key + modules to native code.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-15949</p> + </item> + </list> + </section> + +</section> + <section><title>Hipe 3.19</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/hipe/icode/hipe_beam_to_icode.erl b/lib/hipe/icode/hipe_beam_to_icode.erl index 8e7e56b6c4..995c961e09 100644 --- a/lib/hipe/icode/hipe_beam_to_icode.erl +++ b/lib/hipe/icode/hipe_beam_to_icode.erl @@ -557,32 +557,21 @@ trans_fun([{move,Src,Dst}|Instructions], Env) -> Dst1 = mk_var(Dst), Src1 = trans_arg(Src), [hipe_icode:mk_move(Dst1,Src1) | trans_fun(Instructions,Env)]; -%%--- catch --- ITS PROCESSING IS POSTPONED -trans_fun([{'catch',N,{_,EndLabel}}|Instructions], Env) -> - NewContLbl = mk_label(new), - [{'catch',N,EndLabel},NewContLbl | trans_fun(Instructions,Env)]; -%%--- catch_end --- ITS PROCESSING IS POSTPONED -trans_fun([{catch_end,_N}=I|Instructions], Env) -> - [I | trans_fun(Instructions,Env)]; -%%--- try --- ITS PROCESSING IS POSTPONED -trans_fun([{'try',N,{_,EndLabel}}|Instructions], Env) -> - NewContLbl = mk_label(new), - [{'try',N,EndLabel},NewContLbl | trans_fun(Instructions,Env)]; -%%--- try_end --- -trans_fun([{try_end,_N}|Instructions], Env) -> - [hipe_icode:mk_end_try() | trans_fun(Instructions,Env)]; -%%--- try_case --- ITS PROCESSING IS POSTPONED -trans_fun([{try_case,_N}=I|Instructions], Env) -> - [I | trans_fun(Instructions,Env)]; -%%--- try_case_end --- -trans_fun([{try_case_end,Arg}|Instructions], Env) -> - BadArg = trans_arg(Arg), - ErrVar = mk_var(new), - Vs = [mk_var(new)], - Atom = hipe_icode:mk_move(ErrVar,hipe_icode:mk_const(try_clause)), - Tuple = hipe_icode:mk_primop(Vs,mktuple,[ErrVar,BadArg]), - Fail = hipe_icode:mk_fail(Vs,error), - [Atom,Tuple,Fail | trans_fun(Instructions,Env)]; +%% +%% try/catch -- THESE ARE KNOWN TO MISCOMPILE, SEE OTP-15949 +%% +trans_fun([{'catch'=Name,_,_}|_], _Env) -> + nyi(Name); +trans_fun([{catch_end=Name,_}|_], _Env) -> + nyi(Name); +trans_fun([{'try'=Name,_,_}|_], _Env) -> + nyi(Name); +trans_fun([{try_end=Name,_}|_], _Env) -> + nyi(Name); +trans_fun([{try_case=Name,_}|_], _Env) -> + nyi(Name); +trans_fun([{try_case_end=Name,_}|_], _Env) -> + nyi(Name); %%--- raise --- trans_fun([{raise,{f,0},[Reg1,Reg2],{x,0}}|Instructions], Env) -> V1 = trans_arg(Reg1), diff --git a/lib/hipe/vsn.mk b/lib/hipe/vsn.mk index a91d92ca14..3a22e07f57 100644 --- a/lib/hipe/vsn.mk +++ b/lib/hipe/vsn.mk @@ -1 +1 @@ -HIPE_VSN = 3.19 +HIPE_VSN = 3.19.1 diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index 03bd1d8042..45533c4f4b 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -33,7 +33,23 @@ <file>notes.xml</file> </header> - <section><title>Inets 7.0.8</title> + <section><title>Inets 7.0.9</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix a regression in http client that causes a crash when + request URI has no scheme.</p> + <p> + Own Id: OTP-15930 Aux Id: ERL-969 </p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 7.0.8</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/inets/src/http_client/httpc.erl b/lib/inets/src/http_client/httpc.erl index 24a205ced9..9967488f61 100644 --- a/lib/inets/src/http_client/httpc.erl +++ b/lib/inets/src/http_client/httpc.erl @@ -317,7 +317,7 @@ store_cookies(SetCookieHeaders, Url, Profile) {error, Bad, _} -> {error, {parse_failed, Bad}}; URI -> - Scheme = scheme_to_atom(maps:get(scheme, URI, '')), + Scheme = scheme_to_atom(maps:get(scheme, URI, undefined)), Host = maps:get(host, URI, ""), Port = maps:get(port, URI, default_port(Scheme)), Path = uri_string:recompose(#{path => maps:get(path, URI, "")}), @@ -536,7 +536,7 @@ handle_request(Method, Url, BracketedHost = proplists:get_value(ipv6_host_with_brackets, Options), - Scheme = scheme_to_atom(maps:get(scheme, URI, '')), + Scheme = scheme_to_atom(maps:get(scheme, URI, undefined)), Userinfo = maps:get(userinfo, URI, ""), Host = http_util:maybe_add_brackets(maps:get(host, URI, ""), BracketedHost), Port = maps:get(port, URI, default_port(Scheme)), @@ -591,8 +591,8 @@ scheme_to_atom("http") -> http; scheme_to_atom("https") -> https; -scheme_to_atom('') -> - ''; +scheme_to_atom(undefined) -> + throw({error, {no_scheme}}); scheme_to_atom(Scheme) -> throw({error, {bad_scheme, Scheme}}). diff --git a/lib/inets/src/http_server/httpd_request.erl b/lib/inets/src/http_server/httpd_request.erl index 9d7538a13d..f3e24263b8 100644 --- a/lib/inets/src/http_server/httpd_request.erl +++ b/lib/inets/src/http_server/httpd_request.erl @@ -196,9 +196,9 @@ parse_headers(<<?CR,?LF,?LF,Body/binary>>, [], [], Current, Max, Options, Result parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], Current, Max, Options, Result); -parse_headers(<<?LF,?LF,Body/binary>>, [], [], Current, Max, Options, Result) -> +parse_headers(<<?LF,?LF,Body/binary>>, Header, Headers, Current, Max, Options, Result) -> %% If ?CR is is missing RFC2616 section-19.3 - parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], Current, Max, + parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, Header, Headers, Current, Max, Options, Result); parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], _, _, _, Result) -> diff --git a/lib/inets/test/http_format_SUITE.erl b/lib/inets/test/http_format_SUITE.erl index 0a5aed67d5..3ff3ed4e97 100644 --- a/lib/inets/test/http_format_SUITE.erl +++ b/lib/inets/test/http_format_SUITE.erl @@ -414,6 +414,19 @@ http_request(Config) when is_list(Config) -> {max_content_length, ?HTTP_MAX_CONTENT_LENGTH} ]], HttpHead2), + %% If ?CR is is missing RFC2616 section-19.3 + HttpHead3 = ["GET http://www.erlang.org HTTP/1.1", [?LF], + "Accept: text/html", [?LF, ?LF]], + {"GET", + "http://www.erlang.org", + "HTTP/1.1", + {#http_request_h{}, [{"accept","text/html"}]}, <<>>} = + parse(httpd_request, parse, [[{max_header, ?HTTP_MAX_HEADER_SIZE}, + {max_version, ?HTTP_MAX_VERSION_STRING}, + {max_method, ?HTTP_MAX_METHOD_STRING}, + {max_content_length, ?HTTP_MAX_CONTENT_LENGTH} + ]], HttpHead3), + %% Note the following body is not related to the headers above HttpBody = ["<HTML>\n<HEAD>\n<TITLE> dummy </TITLE>\n</HEAD>\n<BODY>\n", "<H1>dummy</H1>\n</BODY>\n</HTML>\n"], diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl index d4b33ae2c6..8ca4f21928 100644 --- a/lib/inets/test/httpc_SUITE.erl +++ b/lib/inets/test/httpc_SUITE.erl @@ -106,7 +106,8 @@ real_requests()-> streaming_error, inet_opts, invalid_headers, - invalid_body + invalid_body, + no_scheme ]. real_requests_esi() -> @@ -1231,6 +1232,16 @@ invalid_body(Config) -> ok end. + +%%------------------------------------------------------------------------- + +no_scheme(_Config) -> + {error,{bad_scheme,"ftp"}} = httpc:request("ftp://foobar"), + {error,{no_scheme}} = httpc:request("//foobar"), + {error,{no_scheme}} = httpc:request("foobar"), + ok. + + %%------------------------------------------------------------------------- remote_socket_close(Config) when is_list(Config) -> URL = url(group_name(Config), "/just_close.html", Config), @@ -2180,7 +2191,7 @@ check_cookie([_Head | Tail]) -> content_length([]) -> 0; -content_length(["content-length:" ++ Value | _]) -> +content_length([{"content-length", Value}|_]) -> list_to_integer(string:strip(Value)); content_length([_Head | Tail]) -> content_length(Tail). diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index 5dbec9e7b3..d948204618 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -19,6 +19,6 @@ # %CopyrightEnd% APPLICATION = inets -INETS_VSN = 7.0.8 +INETS_VSN = 7.0.9 PRE_VSN = APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java index 187705a0b5..5e777c1164 100644 --- a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java @@ -106,9 +106,9 @@ public class OtpOutputStream extends ByteArrayOutputStream { } /** - * Trims the capacity of this <tt>OtpOutputStream</tt> instance to be the + * Trims the capacity of this <code>OtpOutputStream</code> instance to be the * buffer's current size. An application can use this operation to minimize - * the storage of an <tt>OtpOutputStream</tt> instance. + * the storage of an <code>OtpOutputStream</code> instance. */ public void trimToSize() { resize(super.count); @@ -125,7 +125,7 @@ public class OtpOutputStream extends ByteArrayOutputStream { } /** - * Increases the capacity of this <tt>OtpOutputStream</tt> instance, if + * Increases the capacity of this <code>OtpOutputStream</code> instance, if * necessary, to ensure that it can hold at least the number of elements * specified by the minimum capacity argument. * @@ -939,7 +939,7 @@ public class OtpOutputStream extends ByteArrayOutputStream { * @param o * the Erlang term to write. * @param level - * the compression level (<tt>0..9</tt>) + * the compression level (<code>0..9</code>) */ public void write_compressed(final OtpErlangObject o, final int level) { @SuppressWarnings("resource") diff --git a/lib/kernel/doc/src/Makefile b/lib/kernel/doc/src/Makefile index f8867ccf25..70623ab9aa 100644 --- a/lib/kernel/doc/src/Makefile +++ b/lib/kernel/doc/src/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1997-2018. All Rights Reserved. +# Copyright Ericsson AB 1997-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. @@ -36,6 +36,18 @@ RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN) # Target Specs # ---------------------------------------------------- XML_APPLICATION_FILES = ref_man.xml + +# The doc build has problems with if-defing out modules... +ifeq ($(USE_ESOCK),yes) +XML_REF3_ESOCK_FILES = net.xml +ESOCK_USE_NET_XML=<xi:include href="net.xml"\/> +ESOCK_USE_NET_SPECS_XML=<xi:include href="../specs/specs_net.xml"/> +else +XML_REF3_ESOCK_FILES = +ESOCK_USE_NET_SPECS_XML = +ESOCK_USE_NET_XML = +endif + XML_REF3_FILES = application.xml \ auth.xml \ code.xml \ @@ -62,6 +74,7 @@ XML_REF3_FILES = application.xml \ logger_disk_log_h.xml \ logger_filters.xml \ logger_formatter.xml \ + $(XML_REF3_ESOCK_FILES) \ net_adm.xml \ net_kernel.xml \ os.xml \ @@ -112,6 +125,7 @@ SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml) TOP_SPECS_FILE = specs.xml + # ---------------------------------------------------- # FIGURES # ---------------------------------------------------- @@ -138,7 +152,7 @@ SPECS_FLAGS = -I../../include $(HTMLDIR)/%: % $(INSTALL_DATA) $< $@ -docs: man pdf html +docs: ref_man specs man pdf html $(TOP_PDF_FILE): $(XML_FILES) @@ -148,19 +162,32 @@ html: images $(HTML_REF_MAN_FILE) man: $(MAN3_FILES) $(MAN4_FILES) $(MAN6_FILES) +ref_man: ref_man.xml +specs: specs.xml + images: $(IMAGE_FILES:%=$(HTMLDIR)/%) +info: + @echo "XML_APPLICATION_FILES: $(XML_APPLICATION_FILES)" + @echo "XML_REF3_ESOCK_FILES: $(XML_REF3_ESOCK_FILES)" + @echo "XML_REF3_FILES: $(XML_REF3_FILES)" + @echo "XML_REF4_FILES: $(XML_REF4_FILES)" + @echo "XML_REF6_FILES: $(XML_REF6_FILES)" + @echo "XML_PART_FILES: $(XML_PART_FILES)" + @echo "XML_CHAPTER_FILES: $(XML_CHAPTER_FILES)" + @echo "BOOK_FILES: $(BOOK_FILES)" + debug opt: clean clean_docs: rm -rf $(HTMLDIR)/* rm -rf $(XMLDIR) - rm -f $(MAN3DIR)/* - rm -f $(MAN4DIR)/* - rm -f $(MAN6DIR)/* - rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo) - rm -f $(SPECDIR)/* - rm -f errs core *~ *.eps + rm -f $(MAN3DIR)/* + rm -f $(MAN4DIR)/* + rm -f $(MAN6DIR)/* + rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo) + rm -f $(SPECDIR)/* + rm -f errs core *~ *.eps $(SPECDIR)/specs_erl_prim_loader_stub.xml: $(gen_verbose)escript $(SPECS_EXTRACTOR) $(SPECS_FLAGS) \ @@ -175,6 +202,14 @@ $(SPECDIR)/specs_zlib_stub.xml: $(gen_verbose)escript $(SPECS_EXTRACTOR) $(SPECS_FLAGS) \ -o$(dir $@) -module zlib_stub +ref_man.xml: ref_man.xml.src + ($(PERL) -p -e 's?%ESOCK_USE_NET_XML%?$(ESOCK_USE_NET_XML)?' \ + $<) > $@ +specs.xml: specs.xml.src + ($(PERL) -p -e 's?%ESOCK_USE_NET_SPECS_XML%?$(ESOCK_USE_NET_SPECS_XML)?' \ + $<) > $@ + + # ---------------------------------------------------- # Release Target # ---------------------------------------------------- diff --git a/lib/kernel/doc/src/gen_udp.xml b/lib/kernel/doc/src/gen_udp.xml index d20fc1fdfd..6c0d072fed 100644 --- a/lib/kernel/doc/src/gen_udp.xml +++ b/lib/kernel/doc/src/gen_udp.xml @@ -213,12 +213,93 @@ </func> <func> - <name name="send" arity="4" since=""/> + <name name="send" arity="3" since="OTP @OTP-15747@"/> <fsummary>Send a packet.</fsummary> <desc> <p> - Sends a packet to the specified address and port. Argument - <c><anno>Address</anno></c> can be a hostname or a socket address. + Sends a packet to the specified <c><anno>Destination</anno></c>. + </p> + <p> + This function is equivalent to + <seealso marker="#send-4-AncData"><c>send(<anno>Socket</anno>, <anno>Destination</anno>, [], <anno>Packet</anno>)</c></seealso>. + </p> + </desc> + </func> + + <func> + <name name="send" arity="4" clause_i="1" since=""/> + <fsummary>Send a packet.</fsummary> + <desc> + <p> + Sends a packet to the specified <c><anno>Host</anno></c> + and <c><anno>Port</anno></c>. + </p> + <p> + This clause is equivalent to + <seealso marker="#send/5"><c>send(<anno>Socket</anno>, <anno>Host</anno>, <anno>Port</anno>, [], <anno>Packet</anno>)</c></seealso>. + </p> + </desc> + </func> + + <func> + <name name="send" arity="4" clause_i="2" anchor="send-4-AncData" since="OTP @OTP-15747@"/> + <fsummary>Send a packet.</fsummary> + <desc> + <p> + Sends a packet to the specified <c><anno>Destination</anno></c> + with ancillary data <c><anno>AncData</anno></c>. + </p> + <note> + <p> + The ancillary data <c><anno>AncData</anno></c> + contains options that for this single message + override the default options for the socket, + an operation that may not be supported on all platforms, + and if so return <c>{error, einval}</c>. + Using more than one of an ancillary data item type + may also not be supported. + <c><anno>AncData</anno> =:= []</c> is always supported. + </p> + </note> + </desc> + </func> + + <func> + <name name="send" arity="4" clause_i="3" since="OTP @OTP-15747@"/> + <fsummary>Send a packet.</fsummary> + <desc> + <p> + Sends a packet to the specified <c><anno>Destination</anno></c>. + Since <c><anno>Destination</anno></c> is complete, + <c><anno>PortZero</anno></c> is redundant and has to be <c>0</c>. + </p> + <p> + This is a legacy clause mostly for + <c><anno>Destination</anno> = {local, Binary}</c> + where <c><anno>PortZero</anno></c> is superfluous. + It is equivalent to + <seealso marker="#send-4-AncData"><c>send(<anno>Socket</anno>, <anno>Destination</anno>, [], <anno>Packet</anno>)</c></seealso>, the clause right above here. + </p> + </desc> + </func> + + <func> + <name name="send" arity="5" since="OTP @OTP-15747@"/> + <fsummary>Send a packet.</fsummary> + <desc> + <p> + Sends a packet to the specified <c><anno>Host</anno></c> + and <c><anno>Port</anno></c>, + with ancillary data <c><anno>AncData</anno></c>. + </p> + <p> + Argument <c><anno>Host</anno></c> can be + a hostname or a socket address, + and <c><anno>Port</anno></c> can be a port number + or a service name atom. + These are resolved into a <c>Destination</c> and after that + this function is equivalent to + <seealso marker="#send-4-AncData"><c>send(<anno>Socket</anno>, Destination, <anno>AncData</anno>, <anno>Packet</anno>)</c></seealso>, read there about ancillary data. </p> </desc> </func> diff --git a/lib/kernel/doc/src/inet.xml b/lib/kernel/doc/src/inet.xml index d4678ca5db..1011befca0 100644 --- a/lib/kernel/doc/src/inet.xml +++ b/lib/kernel/doc/src/inet.xml @@ -118,6 +118,42 @@ fe80::204:acff:fe17:bf38 <name name="port_number"/> </datatype> <datatype> + <name name="family_address" since="OTP @OTP-15747@"/> + <desc> + <p> + A general address format on the form <c>{Family, Destination}</c> + where <c>Family</c> is an atom such as <c>local</c> + and the format of <c>Destination</c> depends on <c>Family</c>, + and is a complete address + (for example an IP address including port number). + </p> + </desc> + </datatype> + <datatype> + <name name="inet_address" since="OTP @OTP-15747@"/> + <desc> + <warning> + <p> + This address format is for now experimental + and for completeness to make all address families have a + <c>{Family, Destination}</c> representation. + </p> + </warning> + </desc> + </datatype> + <datatype> + <name name="inet6_address" since="OTP @OTP-15747@"/> + <desc> + <warning> + <p> + This address format is for now experimental + and for completeness to make all address families have a + <c>{Family, Destination}</c> representation. + </p> + </warning> + </desc> + </datatype> + <datatype> <name name="local_address"/> <desc> <p> @@ -180,12 +216,16 @@ fe80::204:acff:fe17:bf38 <name name="ancillary_data"/> <desc> <p> - Ancillary data received with the data packet - or read with the socket option + Ancillary data received with the data packet, + read with the socket option <seealso marker="gen_tcp#type-pktoptions_value"> <c>pktoptions</c> </seealso> - from a TCP socket. + from a TCP socket, + or to set in a call to + <seealso marker="gen_udp#send-4-AncData"><c>gen_udp:send/4</c></seealso> + or + <seealso marker="gen_udp#send/5"><c>gen_udp:send/5</c></seealso>. </p> <p> The value(s) correspond to the currently active socket @@ -193,7 +233,9 @@ fe80::204:acff:fe17:bf38 <seealso marker="inet#option-recvtos"><c>recvtos</c></seealso>, <seealso marker="inet#option-recvtclass"><c>recvtclass</c></seealso> and - <seealso marker="inet#option-recvttl"><c>recvttl</c></seealso>. + <seealso marker="inet#option-recvttl"><c>recvttl</c></seealso>, + or for a single send operation the option(s) to override + the currently active socket option(s). </p> </desc> </datatype> diff --git a/lib/kernel/doc/src/logger_chapter.xml b/lib/kernel/doc/src/logger_chapter.xml index 1aa4b7a3a2..5aa2caadf0 100644 --- a/lib/kernel/doc/src/logger_chapter.xml +++ b/lib/kernel/doc/src/logger_chapter.xml @@ -89,8 +89,8 @@ <p>Filter functions can be used for more sophisticated filtering than the log level check provides. A filter function can stop or pass a log event, based on any of the event's contents. It can - also modify all parts of the log event. See see - section <seealso marker="#filters">Filters</seealso> for more + also modify all parts of the log event. See section + <seealso marker="#filters">Filters</seealso> for more details.</p> <p>If a log event passes through all primary filters and all handler filters for a specific handler, Logger forwards the diff --git a/lib/kernel/doc/src/logger_disk_log_h.xml b/lib/kernel/doc/src/logger_disk_log_h.xml index aa577f3c62..3d8e82ce7c 100644 --- a/lib/kernel/doc/src/logger_disk_log_h.xml +++ b/lib/kernel/doc/src/logger_disk_log_h.xml @@ -133,7 +133,7 @@ logger:add_handler(my_disk_log_h, logger_disk_log_h, #{config => #{file => "./my_disk_log", type => wrap, max_no_files => 4, - max_no_bytes => 10000}, + max_no_bytes => 10000, filesync_repeat_interval => 1000}}). </code> <p>To use the disk_log handler instead of the default standard diff --git a/lib/kernel/doc/src/net.xml b/lib/kernel/doc/src/net.xml new file mode 100644 index 0000000000..6fbc37076c --- /dev/null +++ b/lib/kernel/doc/src/net.xml @@ -0,0 +1,129 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE erlref SYSTEM "erlref.dtd"> + +<erlref> + <header> + <copyright> + <year>2018</year><year>2018</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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. + + </legalnotice> + + <title>net</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + <file>net.xml</file> + </header> + <module since="OTP 22.0">net</module> + <modulesummary>Network interface.</modulesummary> + <description> + <p>This module provides an API for the network interface.</p> + <note> + <p>There is currently <em>no</em> support for Windows. </p> + </note> + </description> + + <datatypes> + <datatype> + <name name="address_info"/> + </datatype> + <datatype> + <name name="name_info"/> + </datatype> + <datatype> + <name name="name_info_flags"/> + </datatype> + <datatype> + <name name="name_info_flag"/> + </datatype> + <datatype> + <name name="name_info_flag_ext"/> + </datatype> + <datatype> + <name name="network_interface_name"/> + </datatype> + <datatype> + <name name="network_interface_index"/> + </datatype> + </datatypes> + + <funcs> + <func> + <name name="gethostname" arity="0"/> + <fsummary>Get hostname.</fsummary> + <desc> + <p>Returns the name of the current host.</p> + </desc> + </func> + + <func> + <name name="getnameinfo" arity="1" since="OTP 22.0"/> + <name name="getnameinfo" arity="2" since="OTP 22.0"/> + <fsummary>Address-to-name transaltion.</fsummary> + <desc> + <p>Address-to-name translation in a protocol-independant manner.</p> + <p>This function is the inverse of + <seealso marker="#getaddrinfo/1"><c>getaddrinfo</c></seealso>. + It converts a socket address to a corresponding host and service.</p> + </desc> + </func> + + <func> + <name name="getaddrinfo" arity="1" since="OTP 22.0"/> + <name name="getaddrinfo" arity="2" clause_i="1" since="OTP 22.0"/> + <name name="getaddrinfo" arity="2" clause_i="2" since="OTP 22.0"/> + <name name="getaddrinfo" arity="2" clause_i="3" since="OTP 22.0"/> + <fsummary>Network address and service transation.</fsummary> + <desc> + <p>Network address and service translation.</p> + <p>This function is the inverse of + <seealso marker="#getnameinfo/1"><c>getnameinfo</c></seealso>. + It converts host and service to a corresponding socket address.</p> + <p>One of the <c>Host</c> and <c>Service</c> may be <c>undefined</c> + but <em>not</em> both.</p> + </desc> + </func> + + <func> + <name name="if_name2index" arity="1" since="OTP 22.0"/> + <fsummary>Mappings between network interface names and indexes.</fsummary> + <desc> + <p>Mappings between network interface names and indexes.</p> + </desc> + </func> + + <func> + <name name="if_index2name" arity="1" since="OTP 22.0"/> + <fsummary>Mappings between network interface index and names.</fsummary> + <desc> + <p>Mappings between network interface index and names.</p> + </desc> + </func> + + <func> + <name name="if_names" arity="0" since="OTP 22.0"/> + <fsummary>Get network interface names and indexes.</fsummary> + <desc> + <p>Get network interface names and indexes.</p> + </desc> + </func> + + </funcs> + +</erlref> + diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml index 6f68a67174..4d31eeea3d 100644 --- a/lib/kernel/doc/src/notes.xml +++ b/lib/kernel/doc/src/notes.xml @@ -31,6 +31,24 @@ </header> <p>This document describes the changes made to the Kernel application.</p> +<section><title>Kernel 6.4.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p><c>user</c>/<c>user_drv</c> could respond to io + requests before they had been processed, which could + cause data to be dropped if the emulator was halted soon + after a call to <c>io:format/2</c>, such as in an + escript.</p> + <p> + Own Id: OTP-15805</p> + </item> + </list> + </section> + +</section> + <section><title>Kernel 6.4</title> <section><title>Fixed Bugs and Malfunctions</title> @@ -129,6 +147,37 @@ </section> +<section><title>Kernel 6.3.1.2</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + The possibility to send ancillary data, in particular the + TOS field, has been added to <c>gen_udp:send/4,5</c>.</p> + <p> + Own Id: OTP-15747 Aux Id: ERIERL-294 </p> + </item> + </list> + </section> + +</section> + +<section><title>Kernel 6.3.1.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix type spec for <c>seq_trace:set_token/2</c>.</p> + <p> + Own Id: OTP-15858 Aux Id: ERL-700 </p> + </item> + </list> + </section> + +</section> + <section><title>Kernel 6.3.1</title> <section><title>Fixed Bugs and Malfunctions</title> @@ -6027,4 +6076,3 @@ </section> </section> </chapter> - diff --git a/lib/kernel/doc/src/ref_man.xml b/lib/kernel/doc/src/ref_man.xml.src index d3b947527f..72e3409123 100644 --- a/lib/kernel/doc/src/ref_man.xml +++ b/lib/kernel/doc/src/ref_man.xml.src @@ -4,7 +4,7 @@ <application xmlns:xi="http://www.w3.org/2001/XInclude"> <header> <copyright> - <year>1996</year><year>2018</year> + <year>1996</year><year>2019</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -60,6 +60,7 @@ <xi:include href="logger_formatter.xml"/> <xi:include href="logger_std_h.xml"/> <xi:include href="logger_disk_log_h.xml"/> + %ESOCK_USE_NET_XML% <xi:include href="net_adm.xml"/> <xi:include href="net_kernel.xml"/> <xi:include href="os.xml"/> diff --git a/lib/kernel/doc/src/specs.xml b/lib/kernel/doc/src/specs.xml.src index b8c25ca53b..ccb26b9458 100644 --- a/lib/kernel/doc/src/specs.xml +++ b/lib/kernel/doc/src/specs.xml.src @@ -26,6 +26,7 @@ <xi:include href="../specs/specs_logger_formatter.xml"/> <xi:include href="../specs/specs_logger_std_h.xml"/> <xi:include href="../specs/specs_logger_disk_log_h.xml"/> + %ESOCK_USE_NET_SPECS_XML% <xi:include href="../specs/specs_net_adm.xml"/> <xi:include href="../specs/specs_net_kernel.xml"/> <xi:include href="../specs/specs_os.xml"/> diff --git a/lib/kernel/src/Makefile b/lib/kernel/src/Makefile index fcb599859b..88752431eb 100644 --- a/lib/kernel/src/Makefile +++ b/lib/kernel/src/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1996-2018. 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. @@ -123,6 +123,7 @@ MODULES = \ logger_server \ logger_simple_h \ logger_sup \ + net \ net_adm \ net_kernel \ os \ @@ -180,6 +181,7 @@ ERL_COMPILE_FLAGS += -Werror endif ERL_COMPILE_FLAGS += -I../include + # ---------------------------------------------------- # Targets # ---------------------------------------------------- diff --git a/lib/kernel/src/code.erl b/lib/kernel/src/code.erl index 7faef93609..964ede9bc9 100644 --- a/lib/kernel/src/code.erl +++ b/lib/kernel/src/code.erl @@ -19,6 +19,8 @@ %% -module(code). +-include_lib("kernel/include/logger.hrl"). + %% This is the interface module to the code server. It also contains %% some implementation details. See also related modules: code_*.erl %% in this directory. @@ -707,8 +709,20 @@ do_s(Lib) -> start_get_mode() -> case init:get_argument(mode) of - {ok,[["embedded"]]} -> - embedded; + {ok, [FirstMode | Rest]} -> + case Rest of + [] -> + ok; + _ -> + ?LOG_WARNING("Multiple -mode given to erl, using the first, ~p", + [FirstMode]) + end, + case FirstMode of + ["embedded"] -> + embedded; + _ -> + interactive + end; _ -> interactive end. diff --git a/lib/kernel/src/gen_sctp.erl b/lib/kernel/src/gen_sctp.erl index d893d44079..a63df54ff9 100644 --- a/lib/kernel/src/gen_sctp.erl +++ b/lib/kernel/src/gen_sctp.erl @@ -217,24 +217,29 @@ peeloff(S, AssocId) when is_port(S), is_integer(AssocId) -> Error -> Error end. --spec connect(Socket, Addr, Port, Opts) -> {ok, Assoc} | {error, inet:posix()} when +-spec connect(Socket, Addr, Port, Opts) -> + {ok, #sctp_assoc_change{state :: 'comm_up'}} | + {error, #sctp_assoc_change{state :: 'cant_assoc'}} | + {error, inet:posix()} + when Socket :: sctp_socket(), Addr :: inet:ip_address() | inet:hostname(), Port :: inet:port_number(), - Opts :: [Opt :: option()], - Assoc :: #sctp_assoc_change{}. + Opts :: [Opt :: option()]. connect(S, Addr, Port, Opts) -> connect(S, Addr, Port, Opts, infinity). -spec connect(Socket, Addr, Port, Opts, Timeout) -> - {ok, Assoc} | {error, inet:posix()} when + {ok, #sctp_assoc_change{state :: 'comm_up'}} | + {error, #sctp_assoc_change{state :: 'cant_assoc'}} | + {error, inet:posix()} + when Socket :: sctp_socket(), Addr :: inet:ip_address() | inet:hostname(), Port :: inet:port_number(), Opts :: [Opt :: option()], - Timeout :: timeout(), - Assoc :: #sctp_assoc_change{}. + Timeout :: timeout(). connect(S, Addr, Port, Opts, Timeout) -> case do_connect(S, Addr, Port, Opts, Timeout, true) of diff --git a/lib/kernel/src/gen_udp.erl b/lib/kernel/src/gen_udp.erl index fad7b2f887..3001948209 100644 --- a/lib/kernel/src/gen_udp.erl +++ b/lib/kernel/src/gen_udp.erl @@ -20,7 +20,7 @@ -module(gen_udp). -export([open/1, open/2, close/1]). --export([send/2, send/4, recv/2, recv/3, connect/3]). +-export([send/2, send/3, send/4, send/5, recv/2, recv/3, connect/3]). -export([controlling_process/2]). -export([fdopen/2]). @@ -125,20 +125,80 @@ open(Port, Opts0) -> close(S) -> inet:udp_close(S). --spec send(Socket, Address, Port, Packet) -> ok | {error, Reason} when +-spec send(Socket, Destination, Packet) -> ok | {error, Reason} when Socket :: socket(), - Address :: inet:socket_address() | inet:hostname(), - Port :: inet:port_number(), + Destination :: {inet:ip_address(), inet:port_number()} | + inet:family_address(), + Packet :: iodata(), + Reason :: not_owner | inet:posix(). +%%% +send(Socket, Destination, Packet) -> + send(Socket, Destination, [], Packet). + +-spec send(Socket, Host, Port, Packet) -> ok | {error, Reason} when + Socket :: socket(), + Host :: inet:hostname() | inet:ip_address(), + Port :: inet:port_number() | atom(), + Packet :: iodata(), + Reason :: not_owner | inet:posix(); +%%% + (Socket, Destination, AncData, Packet) -> ok | {error, Reason} when + Socket :: socket(), + Destination :: {inet:ip_address(), inet:port_number()} | + inet:family_address(), + AncData :: inet:ancillary_data(), + Packet :: iodata(), + Reason :: not_owner | inet:posix(); +%%% + (Socket, Destination, PortZero, Packet) -> ok | {error, Reason} when + Socket :: socket(), + Destination :: {inet:ip_address(), inet:port_number()} | + inet:family_address(), + PortZero :: inet:port_number(), Packet :: iodata(), Reason :: not_owner | inet:posix(). +%%% +send(S, {_,_} = Destination, PortZero = AncData, Packet) when is_port(S) -> + %% Destination is {Family,Addr} | {IP,Port}, + %% so it is complete - argument PortZero is redundant + if + PortZero =:= 0 -> + case inet_db:lookup_socket(S) of + {ok, Mod} -> + Mod:send(S, Destination, [], Packet); + Error -> + Error + end; + is_integer(PortZero) -> + %% Redundant PortZero; must be 0 + {error, einval}; + is_list(AncData) -> + case inet_db:lookup_socket(S) of + {ok, Mod} -> + Mod:send(S, Destination, AncData, Packet); + Error -> + Error + end + end; +send(S, Host, Port, Packet) when is_port(S) -> + send(S, Host, Port, [], Packet). -send(S, Address, Port, Packet) when is_port(S) -> +-spec send(Socket, Host, Port, AncData, Packet) -> ok | {error, Reason} when + Socket :: socket(), + Host :: inet:hostname() | inet:ip_address() | inet:local_address(), + Port :: inet:port_number() | atom(), + AncData :: inet:ancillary_data(), + Packet :: iodata(), + Reason :: not_owner | inet:posix(). +%%% +send(S, Host, Port, AncData, Packet) + when is_port(S), is_list(AncData) -> case inet_db:lookup_socket(S) of {ok, Mod} -> - case Mod:getaddr(Address) of + case Mod:getaddr(Host) of {ok,IP} -> case Mod:getserv(Port) of - {ok,UP} -> Mod:send(S, IP, UP, Packet); + {ok,P} -> Mod:send(S, {IP,P}, AncData, Packet); {error,einval} -> exit(badarg); Error -> Error end; @@ -149,6 +209,7 @@ send(S, Address, Port, Packet) when is_port(S) -> Error end. +%% Connected send send(S, Packet) when is_port(S) -> case inet_db:lookup_socket(S) of {ok, Mod} -> diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl index 9f22eb6aaa..24aff83fbd 100644 --- a/lib/kernel/src/inet.erl +++ b/lib/kernel/src/inet.erl @@ -75,7 +75,8 @@ -export_type([address_family/0, socket_protocol/0, hostent/0, hostname/0, ip4_address/0, ip6_address/0, ip_address/0, port_number/0, - local_address/0, socket_address/0, returned_non_ip_address/0, + family_address/0, local_address/0, + socket_address/0, returned_non_ip_address/0, socket_setopt/0, socket_getopt/0, ancillary_data/0, posix/0, socket/0, stat_option/0]). %% imports @@ -100,11 +101,16 @@ 0..65535,0..65535,0..65535,0..65535}. -type ip_address() :: ip4_address() | ip6_address(). -type port_number() :: 0..65535. --type local_address() :: {local, File :: binary() | string()}. +-type family_address() :: inet_address() | inet6_address() | local_address(). +-type inet_address() :: + {'inet', {ip4_address() | 'any' | 'loopback', port_number()}}. +-type inet6_address() :: + {'inet6', {ip6_address() | 'any' | 'loopback', port_number()}}. +-type local_address() :: {'local', File :: binary() | string()}. -type returned_non_ip_address() :: - {local, binary()} | - {unspec, <<>>} | - {undefined, any()}. + {'local', binary()} | + {'unspec', <<>>} | + {'undefined', any()}. -type posix() :: 'eaddrinuse' | 'eaddrnotavail' | 'eafnosupport' | 'ealready' | 'econnaborted' | 'econnrefused' | 'econnreset' | @@ -1639,6 +1645,7 @@ fmt_addr({ok,Addr}, Proto) -> {{0,0,0,0,0,0,0,0},Port} -> "*:" ++ fmt_port(Port, Proto); {{127,0,0,1},Port} -> "localhost:" ++ fmt_port(Port, Proto); {{0,0,0,0,0,0,0,1},Port} -> "localhost:" ++ fmt_port(Port, Proto); + {local, Path} -> "local:" ++ binary_to_list(Path); {IP,Port} -> inet_parse:ntoa(IP) ++ ":" ++ fmt_port(Port, Proto) end. diff --git a/lib/kernel/src/inet6_udp.erl b/lib/kernel/src/inet6_udp.erl index 71db0357cd..cb95a69798 100644 --- a/lib/kernel/src/inet6_udp.erl +++ b/lib/kernel/src/inet6_udp.erl @@ -65,16 +65,25 @@ open(Port, Opts) -> {ok, _} -> exit(badarg) end. -send(S, Addr = {A,B,C,D,E,F,G,H}, P, Data) - when ?ip6(A,B,C,D,E,F,G,H), ?port(P) -> - prim_inet:sendto(S, Addr, P, Data). +send(S, {A,B,C,D,E,F,G,H} = IP, Port, Data) + when ?ip6(A,B,C,D,E,F,G,H), ?port(Port) -> + prim_inet:sendto(S, {IP, Port}, [], Data); +send(S, {{A,B,C,D,E,F,G,H}, Port} = Addr, AncData, Data) + when ?ip6(A,B,C,D,E,F,G,H), ?port(Port), is_list(AncData) -> + prim_inet:sendto(S, Addr, AncData, Data); +send(S, {?FAMILY, {{A,B,C,D,E,F,G,H}, Port}} = Address, AncData, Data) + when ?ip6(A,B,C,D,E,F,G,H), ?port(Port), is_list(AncData) -> + prim_inet:sendto(S, Address, AncData, Data); +send(S, {?FAMILY, {loopback, Port}} = Address, AncData, Data) + when ?port(Port), is_list(AncData) -> + prim_inet:sendto(S, Address, AncData, Data). send(S, Data) -> - prim_inet:sendto(S, {0,0,0,0,0,0,0,0}, 0, Data). + prim_inet:sendto(S, {any, 0}, [], Data). -connect(S, Addr = {A,B,C,D,E,F,G,H}, P) - when ?ip6(A,B,C,D,E,F,G,H), ?port(P) -> - prim_inet:connect(S, Addr, P). +connect(S, Addr = {A,B,C,D,E,F,G,H}, Port) + when ?ip6(A,B,C,D,E,F,G,H), ?port(Port) -> + prim_inet:connect(S, Addr, Port). recv(S, Len) -> prim_inet:recvfrom(S, Len). diff --git a/lib/kernel/src/inet_udp.erl b/lib/kernel/src/inet_udp.erl index 1e624b9e90..083059a2dc 100644 --- a/lib/kernel/src/inet_udp.erl +++ b/lib/kernel/src/inet_udp.erl @@ -66,16 +66,25 @@ open(Port, Opts) -> {ok, _} -> exit(badarg) end. -send(S, {A,B,C,D} = Addr, P, Data) - when ?ip(A,B,C,D), ?port(P) -> - prim_inet:sendto(S, Addr, P, Data). +send(S, {A,B,C,D} = IP, Port, Data) + when ?ip(A,B,C,D), ?port(Port) -> + prim_inet:sendto(S, {IP, Port}, [], Data); +send(S, {{A,B,C,D}, Port} = Addr, AncData, Data) + when ?ip(A,B,C,D), ?port(Port), is_list(AncData) -> + prim_inet:sendto(S, Addr, AncData, Data); +send(S, {?FAMILY, {{A,B,C,D}, Port}} = Address, AncData, Data) + when ?ip(A,B,C,D), ?port(Port), is_list(AncData) -> + prim_inet:sendto(S, Address, AncData, Data); +send(S, {?FAMILY, {loopback, Port}} = Address, AncData, Data) + when ?port(Port), is_list(AncData) -> + prim_inet:sendto(S, Address, AncData, Data). send(S, Data) -> - prim_inet:sendto(S, {0,0,0,0}, 0, Data). + prim_inet:sendto(S, {any, 0}, [], Data). -connect(S, Addr = {A,B,C,D}, P) - when ?ip(A,B,C,D), ?port(P) -> - prim_inet:connect(S, Addr, P). +connect(S, Addr = {A,B,C,D}, Port) + when ?ip(A,B,C,D), ?port(Port) -> + prim_inet:connect(S, Addr, Port). recv(S, Len) -> prim_inet:recvfrom(S, Len). diff --git a/lib/kernel/src/kernel.app.src b/lib/kernel/src/kernel.app.src index 8fe6bdd1ca..c2ff6b63e9 100644 --- a/lib/kernel/src/kernel.app.src +++ b/lib/kernel/src/kernel.app.src @@ -74,6 +74,7 @@ logger_simple_h, logger_std_h, logger_sup, + net, net_adm, net_kernel, os, diff --git a/lib/kernel/src/kernel.appup.src b/lib/kernel/src/kernel.appup.src index cd0397a98c..95853a7a8f 100644 --- a/lib/kernel/src/kernel.appup.src +++ b/lib/kernel/src/kernel.appup.src @@ -38,7 +38,9 @@ {<<"^6\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, {<<"^6\\.3$">>,[restart_new_emulator]}, {<<"^6\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, - {<<"^6\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}], + {<<"^6\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^6\\.4$">>,[restart_new_emulator]}, + {<<"^6\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}], [{<<"^6\\.0$">>,[restart_new_emulator]}, {<<"^6\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, {<<"^6\\.0\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, @@ -50,4 +52,6 @@ {<<"^6\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, {<<"^6\\.3$">>,[restart_new_emulator]}, {<<"^6\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, - {<<"^6\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}. + {<<"^6\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^6\\.4$">>,[restart_new_emulator]}, + {<<"^6\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}]}. diff --git a/lib/kernel/src/kernel.erl b/lib/kernel/src/kernel.erl index bfa091a036..c8c631ab23 100644 --- a/lib/kernel/src/kernel.erl +++ b/lib/kernel/src/kernel.erl @@ -116,7 +116,7 @@ init([]) -> restart => temporary, shutdown => 2000, type => supervisor, - modules => [user_sup]}, + modules => [standard_error]}, User = #{id => user, start => {user_sup, start, []}, @@ -141,7 +141,7 @@ init([]) -> modules => [logger_sup]}, case init:get_argument(mode) of - {ok, [["minimal"]]} -> + {ok, [["minimal"]|_]} -> {ok, {SupFlags, [Code, File, StdError, User, LoggerSup, Config, RefC, SafeSup]}}; _ -> diff --git a/lib/kernel/src/local_udp.erl b/lib/kernel/src/local_udp.erl index 481a8c4910..933e56228b 100644 --- a/lib/kernel/src/local_udp.erl +++ b/lib/kernel/src/local_udp.erl @@ -70,11 +70,13 @@ open(0, Opts) -> {ok, _} -> exit(badarg) end. -send(S, Addr = {?FAMILY,_}, 0, Data) -> - prim_inet:sendto(S, Addr, 0, Data). +send(S, {?FAMILY,_} = Addr, 0, Data) -> + prim_inet:sendto(S, Addr, [], Data); +send(S, {?FAMILY,_} = Addr, AncData, Data) when is_list(AncData) -> + prim_inet:sendto(S, Addr, AncData, Data). %% send(S, Data) -> - prim_inet:sendto(S, {?FAMILY,<<>>}, 0, Data). + prim_inet:sendto(S, {?FAMILY,<<>>}, [], Data). connect(S, Addr = {?FAMILY,_}, 0) -> prim_inet:connect(S, Addr, 0). diff --git a/lib/kernel/src/logger_std_h.erl b/lib/kernel/src/logger_std_h.erl index c8f1acfca4..2b078ef091 100644 --- a/lib/kernel/src/logger_std_h.erl +++ b/lib/kernel/src/logger_std_h.erl @@ -170,9 +170,11 @@ check_h_config(_Type,[]) -> ok. normalize_config(#{type:={file,File}}=HConfig) -> - HConfig#{type=>file,file=>File}; + normalize_config(HConfig#{type=>file,file=>File}); normalize_config(#{type:={file,File,Modes}}=HConfig) -> - HConfig#{type=>file,file=>File,modes=>Modes}; + normalize_config(HConfig#{type=>file,file=>File,modes=>Modes}); +normalize_config(#{file:=File}=HConfig) -> + HConfig#{file=>filename:absname(File)}; normalize_config(HConfig) -> HConfig. @@ -188,7 +190,7 @@ merge_default_config(Name,Type,HConfig) -> get_default_config(Name,file) -> #{type => file, - file => atom_to_list(Name), + file => filename:absname(atom_to_list(Name)), modes => [raw,append], file_check => 0, max_no_bytes => infinity, diff --git a/lib/kernel/src/net.erl b/lib/kernel/src/net.erl new file mode 100644 index 0000000000..b8ffa64043 --- /dev/null +++ b/lib/kernel/src/net.erl @@ -0,0 +1,324 @@ +%% +%% %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(net). + +%% We should really ifdef this module depending on if we actually built +%% the system with esock support (socket and prim_net), but our doc-building +%% can't handle the "variables" we need (USE_ESOCK). So instead, we just +%% leave everything hanging... +%% If one of the "hanging" functions is called when esock has been disabled, +%% the function will through a 'notsup' error (erlang:error/1). + +%% Administrative and utility functions +-export([ + info/0, + command/1 + ]). + +-export([ + gethostname/0, + getnameinfo/1, getnameinfo/2, + getaddrinfo/1, getaddrinfo/2, + + if_name2index/1, + if_index2name/1, + if_names/0 + ]). + +%% Deprecated functions from the "old" net module +-export([call/4, + cast/4, + broadcast/3, + ping/1, + relay/1, + sleep/1]). + +%% Should we define these here or refer to the prim_net module +-export_type([ + address_info/0, + name_info/0, + + name_info_flags/0, + name_info_flag/0, + name_info_flag_ext/0, + + network_interface_name/0, + network_interface_index/0 + ]). + + +-deprecated({call, 4, eventually}). +-deprecated({cast, 4, eventually}). +-deprecated({broadcast, 3, eventually}). +-deprecated({ping, 1, eventually}). +-deprecated({relay, 1, eventually}). +-deprecated({sleep, 1, eventually}). + + +-type name_info_flags() :: [name_info_flag()|name_info_flag_ext()]. +-type name_info_flag() :: namereqd | + dgram | + nofqdn | + numerichost | + nomericserv. +-type name_info_flag_ext() :: idn | + idna_allow_unassigned | + idna_use_std3_ascii_rules. +-type name_info() :: #{host := string(), + service := string()}. +-type address_info() :: #{family := socket:domain(), + socktype := socket:type(), + protocol := socket:protocol(), + address := socket:sockaddr()}. +-type network_interface_name() :: string(). +-type network_interface_index() :: non_neg_integer(). + + +%% =========================================================================== +%% +%% D E P R E C A T E D F U N C T I O N S +%% +%% =========================================================================== + +call(N,M,F,A) -> rpc:call(N,M,F,A). +cast(N,M,F,A) -> rpc:cast(N,M,F,A). +broadcast(M,F,A) -> rpc:eval_everywhere(M,F,A). +ping(Node) -> net_adm:ping(Node). +sleep(T) -> receive after T -> ok end. +relay(X) -> slave:relay(X). + + +%% =========================================================================== +%% +%% Administrative and utility API +%% +%% =========================================================================== + +-spec info() -> list(). + +-ifdef(USE_ESOCK). +info() -> + prim_net:info(). +-else. +-dialyzer({nowarn_function, info/0}). +info() -> + erlang:error(notsup). +-endif. + + +-spec command(Cmd :: term()) -> term(). + +-ifdef(USE_ESOCK). +command(Cmd) -> + prim_net:command(Cmd). +-else. +-dialyzer({nowarn_function, command/1}). +command(_Cmd) -> + erlang:error(notsup). +-endif. + + + +%% =========================================================================== +%% +%% The proper net API +%% +%% =========================================================================== + +%% =========================================================================== +%% +%% gethostname - Get the name of the current host. +%% +%% + +-spec gethostname() -> {ok, HostName} | {error, Reason} when + HostName :: string(), + Reason :: term(). + +-ifdef(USE_ESOCK). +gethostname() -> + prim_net:gethostname(). +-else. +-dialyzer({nowarn_function, gethostname/0}). +gethostname() -> + erlang:error(notsup). +-endif. + + +%% =========================================================================== +%% +%% getnameinfo - Address-to-name translation in protocol-independent manner. +%% +%% + +-spec getnameinfo(SockAddr) -> {ok, Info} | {error, Reason} when + SockAddr :: socket:sockaddr(), + Info :: name_info(), + Reason :: term(). + +getnameinfo(SockAddr) -> + getnameinfo(SockAddr, undefined). + +-spec getnameinfo(SockAddr, Flags) -> {ok, Info} | {error, Reason} when + SockAddr :: socket:sockaddr(), + Flags :: name_info_flags() | undefined, + Info :: name_info(), + Reason :: term(). + +-ifdef(USE_ESOCK). +getnameinfo(SockAddr, [] = _Flags) -> + getnameinfo(SockAddr, undefined); +getnameinfo(#{family := Fam, addr := _Addr} = SockAddr, Flags) + when ((Fam =:= inet) orelse (Fam =:= inet6)) andalso + (is_list(Flags) orelse (Flags =:= undefined)) -> + prim_net:getnameinfo(socket:ensure_sockaddr(SockAddr), Flags); +getnameinfo(#{family := Fam, path := _Path} = SockAddr, Flags) + when (Fam =:= local) andalso (is_list(Flags) orelse (Flags =:= undefined)) -> + prim_net:getnameinfo(SockAddr, Flags). +-else. +-dialyzer({nowarn_function, getnameinfo/2}). +getnameinfo(SockAddr, [] = _Flags) -> + getnameinfo(SockAddr, undefined); +getnameinfo(#{family := Fam, addr := _Addr} = _SockAddr, Flags) + when ((Fam =:= inet) orelse (Fam =:= inet6)) andalso + (is_list(Flags) orelse (Flags =:= undefined)) -> + erlang:error(notsup); +getnameinfo(#{family := Fam, path := _Path} = _SockAddr, Flags) + when (Fam =:= local) andalso (is_list(Flags) orelse (Flags =:= undefined)) -> + erlang:error(notsup). +-endif. + + +%% =========================================================================== +%% +%% getaddrinfo - Network address and service translation +%% +%% There is also a "hint" argument that we "at some point" should implement. + +-spec getaddrinfo(Host) -> {ok, Info} | {error, Reason} when + Host :: string(), + Info :: [address_info()], + Reason :: term(). + +getaddrinfo(Host) when is_list(Host) -> + getaddrinfo(Host, undefined). + + +-spec getaddrinfo(Host, undefined) -> {ok, Info} | {error, Reason} when + Host :: string(), + Info :: [address_info()], + Reason :: term() + ; (undefined, Service) -> {ok, Info} | {error, Reason} when + Service :: string(), + Info :: [address_info()], + Reason :: term() + ; (Host, Service) -> {ok, Info} | {error, Reason} when + Host :: string(), + Service :: string(), + Info :: [address_info()], + Reason :: term(). + +-ifdef(USE_ESOCK). +getaddrinfo(Host, Service) + when (is_list(Host) orelse (Host =:= undefined)) andalso + (is_list(Service) orelse (Service =:= undefined)) andalso + (not ((Service =:= undefined) andalso (Host =:= undefined))) -> + prim_net:getaddrinfo(Host, Service). +-else. +-dialyzer({nowarn_function, getaddrinfo/2}). +getaddrinfo(Host, Service) + when (is_list(Host) orelse (Host =:= undefined)) andalso + (is_list(Service) orelse (Service =:= undefined)) andalso + (not ((Service =:= undefined) andalso (Host =:= undefined))) -> + erlang:error(notsup). +-endif. + + + + +%% =========================================================================== +%% +%% if_name2index - Mappings between network interface names and indexes: +%% name -> idx +%% +%% + +-spec if_name2index(Name) -> {ok, Idx} | {error, Reason} when + Name :: network_interface_name(), + Idx :: network_interface_index(), + Reason :: term(). + +-ifdef(USE_ESOCK). +if_name2index(If) when is_list(If) -> + prim_net:if_name2index(If). +-else. +-dialyzer({nowarn_function, if_name2index/1}). +if_name2index(If) when is_list(If) -> + erlang:error(notsup). +-endif. + + + +%% =========================================================================== +%% +%% if_index2name - Mappings between network interface index and names: +%% idx -> name +%% +%% + +-spec if_index2name(Idx) -> {ok, Name} | {error, Reason} when + Idx :: network_interface_index(), + Name :: network_interface_name(), + Reason :: term(). + +-ifdef(USE_ESOCK). +if_index2name(Idx) when is_integer(Idx) -> + prim_net:if_index2name(Idx). +-else. +-dialyzer({nowarn_function, if_index2name/1}). +if_index2name(Idx) when is_integer(Idx) -> + erlang:error(notsup). +-endif. + + + +%% =========================================================================== +%% +%% if_names - Get network interface names and indexes +%% +%% + +-spec if_names() -> Names | {error, Reason} when + Names :: [{Idx, If}], + Idx :: network_interface_index(), + If :: network_interface_name(), + Reason :: term(). + +-ifdef(USE_ESOCK). +if_names() -> + prim_net:if_names(). +-else. +-dialyzer({nowarn_function, if_names/0}). +if_names() -> + erlang:error(notsup). +-endif. + + diff --git a/lib/kernel/src/seq_trace.erl b/lib/kernel/src/seq_trace.erl index 4f9d7b3e5c..f0bd1fabe9 100644 --- a/lib/kernel/src/seq_trace.erl +++ b/lib/kernel/src/seq_trace.erl @@ -59,7 +59,7 @@ set_token({Flags,Label,Serial,_From,Lastcnt}) -> F = decode_flags(Flags), set_token2([{label,Label},{serial,{Lastcnt, Serial}} | F]). --spec set_token(Component, Val) -> {Component, OldVal} when +-spec set_token(Component, Val) -> OldVal when Component :: component(), Val :: value(), OldVal :: value(). diff --git a/lib/kernel/src/user.erl b/lib/kernel/src/user.erl index 0c9e1ea303..5a3487a9ba 100644 --- a/lib/kernel/src/user.erl +++ b/lib/kernel/src/user.erl @@ -296,7 +296,8 @@ io_requests([], Stat, _) -> %% port. put_port(List, Port) -> - send_port(Port, {command, List}). + true = port_command(Port, List), + ok. %% send_port(Port, Command) diff --git a/lib/kernel/src/user_drv.erl b/lib/kernel/src/user_drv.erl index 69ff8e7971..644aa752b6 100644 --- a/lib/kernel/src/user_drv.erl +++ b/lib/kernel/src/user_drv.erl @@ -543,19 +543,14 @@ set_unicode_state(Iport, Bool) -> %% io_request(Request, InPort, OutPort) %% io_requests(Requests, InPort, OutPort) %% Note: InPort is unused. - -io_request(Request, Iport, Oport) -> - try io_command(Request) of - {command,_} = Command -> - Oport ! {self(),Command}, - ok; - {Command,Reply} -> - Oport ! {self(),Command}, - Reply - catch - {requests,Rs} -> - io_requests(Rs, Iport, Oport); - _ -> +io_request({requests,Rs}, Iport, Oport) -> + io_requests(Rs, Iport, Oport); +io_request(Request, _Iport, Oport) -> + case io_command(Request) of + {Data, Reply} -> + true = port_command(Oport, Data), + Reply; + unhandled -> ok end. @@ -575,19 +570,19 @@ put_int16(N, Tail) -> %% to the console before the vm stops when calling erlang:halt(integer()). -dialyzer({no_improper_lists, io_command/1}). io_command({put_chars_sync, unicode,Cs,Reply}) -> - {{command,[?OP_PUTC_SYNC|unicode:characters_to_binary(Cs,utf8)]},Reply}; + {[?OP_PUTC_SYNC|unicode:characters_to_binary(Cs,utf8)], Reply}; io_command({put_chars, unicode,Cs}) -> - {command,[?OP_PUTC|unicode:characters_to_binary(Cs,utf8)]}; + {[?OP_PUTC|unicode:characters_to_binary(Cs,utf8)], ok}; io_command({move_rel,N}) -> - {command,[?OP_MOVE|put_int16(N, [])]}; + {[?OP_MOVE|put_int16(N, [])], ok}; io_command({insert_chars,unicode,Cs}) -> - {command,[?OP_INSC|unicode:characters_to_binary(Cs,utf8)]}; + {[?OP_INSC|unicode:characters_to_binary(Cs,utf8)], ok}; io_command({delete_chars,N}) -> - {command,[?OP_DELC|put_int16(N, [])]}; + {[?OP_DELC|put_int16(N, [])], ok}; io_command(beep) -> - {command,[?OP_BEEP]}; -io_command(Else) -> - throw(Else). + {[?OP_BEEP], ok}; +io_command(_) -> + unhandled. %% gr_new() %% gr_get_num(Group, Index) diff --git a/lib/kernel/test/code_SUITE.erl b/lib/kernel/test/code_SUITE.erl index 4f0847084f..6b133f8d6b 100644 --- a/lib/kernel/test/code_SUITE.erl +++ b/lib/kernel/test/code_SUITE.erl @@ -41,7 +41,7 @@ big_boot_embedded/1, module_status/1, native_early_modules/1, get_mode/1, - normalized_paths/1]). + normalized_paths/1, mult_embedded_flags/1]). -export([init_per_testcase/2, end_per_testcase/2, init_per_suite/1, end_per_suite/1]). @@ -72,7 +72,8 @@ all() -> on_load_purge, on_load_self_call, on_load_pending, on_load_deleted, module_status, - big_boot_embedded, native_early_modules, get_mode, normalized_paths]. + big_boot_embedded, native_early_modules, get_mode, normalized_paths, + mult_embedded_flags]. %% These need to run in order groups() -> [{sequence, [sequence], [on_load_update, @@ -354,7 +355,7 @@ load_abs(Config) when is_list(Config) -> ensure_loaded(Config) when is_list(Config) -> {module, lists} = code:ensure_loaded(lists), case init:get_argument(mode) of - {ok, [["embedded"]]} -> + {ok, [["embedded"] | _]} -> {error, embedded} = code:ensure_loaded(code_b_test), {error, badarg} = code:ensure_loaded(34), ok; @@ -1836,6 +1837,28 @@ do_normalized_paths([M|Ms]) -> do_normalized_paths([]) -> ok. +%% Make sure that the extra -mode flags are ignored +mult_embedded_flags(_Config) -> + Modes = [{" -mode embedded", embedded}, + {" -mode interactive", interactive}, + {" -mode invalid", interactive}], + + [ begin + {ArgMode, ExpectedMode} = Mode, + {ok, Node} = start_node(mode_test, ArgMode), + ExpectedMode = rpc:call(Node, code, get_mode, []), + true = stop_node(Node) + end || Mode <- Modes], + + [ begin + {ArgIgnoredMode, _} = IgnoredMode, + {ArgRelevantMode, ExpectedMode} = RelevantMode, + {ok, Node} = start_node(mode_test, ArgRelevantMode ++ ArgIgnoredMode), + ExpectedMode = rpc:call(Node, code, get_mode, []), + true = stop_node(Node) + end || IgnoredMode <- Modes, RelevantMode <- Modes], + ok. + %% Test that module_status/1 behaves as expected module_status(_Config) -> case test_server:is_cover() of diff --git a/lib/kernel/test/gen_tcp_misc_SUITE.erl b/lib/kernel/test/gen_tcp_misc_SUITE.erl index edf30448c4..de87bd9472 100644 --- a/lib/kernel/test/gen_tcp_misc_SUITE.erl +++ b/lib/kernel/test/gen_tcp_misc_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2018. All Rights Reserved. +%% Copyright Ericsson AB 1998-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. @@ -25,6 +25,7 @@ init_per_group/2,end_per_group/2, controlling_process/1, controlling_process_self/1, no_accept/1, close_with_pending_output/1, active_n/1, + active_n_closed/1, data_before_close/1, iter_max_socks/0, iter_max_socks/1, get_status/1, @@ -36,7 +37,8 @@ show_econnreset_passive/1, econnreset_after_sync_send/1, econnreset_after_async_send_active/1, econnreset_after_async_send_active_once/1, - econnreset_after_async_send_passive/1, linger_zero/1, + econnreset_after_async_send_passive/1, + linger_zero/1, linger_zero_sndbuf/1, default_options/1, http_bad_packet/1, busy_send/1, busy_disconnect_passive/1, busy_disconnect_active/1, fill_sendq/1, partial_recv_and_close/1, @@ -73,14 +75,15 @@ suite() -> all() -> [controlling_process, controlling_process_self, no_accept, close_with_pending_output, data_before_close, - iter_max_socks, passive_sockets, active_n, + iter_max_socks, passive_sockets, active_n, active_n_closed, accept_closed_by_other_process, otp_3924, closed_socket, shutdown_active, shutdown_passive, shutdown_pending, show_econnreset_active, show_econnreset_active_once, show_econnreset_passive, econnreset_after_sync_send, econnreset_after_async_send_active, econnreset_after_async_send_active_once, - econnreset_after_async_send_passive, linger_zero, + econnreset_after_async_send_passive, + linger_zero, linger_zero_sndbuf, default_options, http_bad_packet, busy_send, busy_disconnect_passive, busy_disconnect_active, fill_sendq, partial_recv_and_close, @@ -1356,7 +1359,42 @@ linger_zero(Config) when is_list(Config) -> ok = gen_tcp:close(Client), ok = ct:sleep(1), undefined = erlang:port_info(Client, connected), - {error, econnreset} = gen_tcp:recv(S, PayloadSize). + {error, econnreset} = gen_tcp:recv(S, PayloadSize), + ok. + + +linger_zero_sndbuf(Config) when is_list(Config) -> + %% All the econnreset tests will prove that {linger, {true, 0}} aborts + %% a connection when the driver queue is empty. We will test here + %% that it also works when the driver queue is not empty + %% and the linger zero option is set on the listen socket. + {OS, _} = os:type(), + {ok, Listen} = + gen_tcp:listen(0, [{active, false}, + {recbuf, 4096}, + {show_econnreset, true}, + {linger, {true, 0}}]), + {ok, Port} = inet:port(Listen), + {ok, Client} = + gen_tcp:connect(localhost, Port, + [{active, false}, + {sndbuf, 4096}]), + {ok, Server} = gen_tcp:accept(Listen), + ok = gen_tcp:close(Listen), + PayloadSize = 1024 * 1024, + Payload = binary:copy(<<"0123456789ABCDEF">>, 256 * 1024), % 1 MB + ok = gen_tcp:send(Server, Payload), + case erlang:port_info(Server, queue_size) of + {queue_size, N} when N > 0 -> ok; + {queue_size, 0} when OS =:= win32 -> ok; + {queue_size, 0} = T -> ct:fail(T) + end, + {ok, [{linger, {true, 0}}]} = inet:getopts(Server, [linger]), + ok = gen_tcp:close(Server), + ok = ct:sleep(1), + undefined = erlang:port_info(Server, connected), + {error, closed} = gen_tcp:recv(Client, PayloadSize), + ok. %% Thanks to Luke Gorrie. Tests for a very specific problem with @@ -1981,10 +2019,10 @@ recvtclass(_Config) -> %% platforms - change {unix,_} to false? %% pktoptions is not supported for IPv4 -recvtos_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,4,0}); +recvtos_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,6,0}); recvtos_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {19,0,0}); %% Using the option returns einval, so it is not implemented. -recvtos_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {11,2,0}); +recvtos_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {12,1,0}); recvtos_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0}); %% Does not return any value - not implemented for pktoptions recvtos_ok({unix,linux}, OSVer) -> not semver_lt(OSVer, {3,1,0}); @@ -1993,10 +2031,10 @@ recvtos_ok({unix,_}, _) -> true; recvtos_ok(_, _) -> false. %% pktoptions is not supported for IPv4 -recvttl_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,4,0}); +recvttl_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,6,0}); recvttl_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {19,0,0}); %% Using the option returns einval, so it is not implemented. -recvttl_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {11,2,0}); +recvttl_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {12,1,0}); recvttl_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0}); %% Does not return any value - not implemented for pktoptions recvttl_ok({unix,linux}, OSVer) -> not semver_lt(OSVer, {2,7,0}); @@ -2005,11 +2043,11 @@ recvttl_ok({unix,_}, _) -> true; recvttl_ok(_, _) -> false. %% pktoptions is not supported for IPv6 -recvtclass_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,4,0}); +recvtclass_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,6,0}); recvtclass_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {19,0,0}); recvtclass_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0}); %% Using the option returns einval, so it is not implemented. -recvtclass_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {11,2,0}); +recvtclass_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {12,1,0}); %% Does not return any value - not implemented for pktoptions recvtclass_ok({unix,linux}, OSVer) -> not semver_lt(OSVer, {3,1,0}); %% @@ -2186,18 +2224,19 @@ collect_accepts(N,Tmo) -> A = millis(), receive {accepted,P,Msg} -> - [{P,Msg}] ++ collect_accepts(N-1,Tmo-(millis() - A)) + NextN = if N =:= infinity -> N; true -> N - 1 end, + [{P,Msg}] ++ collect_accepts(NextN, Tmo - (millis()-A)) after Tmo -> [] end. -define(EXPECT_ACCEPTS(Pattern,N,Timeout), (fun() -> - case collect_accepts(if N =:= infinity -> -1; true -> N end,Timeout) of + case collect_accepts((N), (Timeout)) of Pattern -> ok; - Other -> - {error,{unexpected,{Other,process_info(self(),messages)}}} + Other__ -> + {error,{unexpected,{Other__,process_info(self(),messages)}}} end end)()). @@ -2582,7 +2621,51 @@ active_once_closed(Config) when is_list(Config) -> ok = inet:setopts(A,[{active,once}]), ok = receive {tcp_closed, A} -> ok after 1000 -> error end end)(). - + +%% Check that active n and tcp_close messages behave as expected. +active_n_closed(Config) when is_list(Config) -> + {ok, L} = gen_tcp:listen(0, [binary, {active, false}]), + + P = self(), + + {ok,Port} = inet:port(L), + + spawn_link(fun() -> + Payload = <<0:50000/unit:8>>, + Cnt = 10000, + P ! {size,Cnt * byte_size(Payload)}, + {ok, S} = gen_tcp:connect("localhost", Port, [binary, {active, false}]), + _ = [gen_tcp:send(S, Payload) || _ <- lists:seq(1, Cnt)], + gen_tcp:close(S) + end), + + receive {size,SendSize} -> SendSize end, + {ok, S} = gen_tcp:accept(L), + inet:setopts(S, [{active, 10}]), + RecvSize = + (fun Server(Size) -> + receive + {tcp, S, Bin} -> + Server(byte_size(Bin) + Size); + {tcp_closed, S} -> + Size; + {tcp_passive, S} -> + inet:setopts(S, [{active, 10}]), + Server(Size); + Msg -> + io:format("~p~n", [Msg]), + Server(Size) + end + end)(0), + + gen_tcp:close(L), + + if SendSize =:= RecvSize -> + ok; + true -> + ct:fail("Send and Recv size not equal: ~p ~p",[SendSize, RecvSize]) + end. + %% Test the send_timeout socket option. send_timeout(Config) when is_list(Config) -> Dir = filename:dirname(code:which(?MODULE)), diff --git a/lib/kernel/test/gen_udp_SUITE.erl b/lib/kernel/test/gen_udp_SUITE.erl index af9985de45..70d8caf478 100644 --- a/lib/kernel/test/gen_udp_SUITE.erl +++ b/lib/kernel/test/gen_udp_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2018. All Rights Reserved. +%% Copyright Ericsson AB 1998-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,7 @@ buffer_size/1, binary_passive_recv/1, max_buffer_size/1, bad_address/1, read_packets/1, open_fd/1, connect/1, implicit_inet6/1, recvtos/1, recvtosttl/1, recvttl/1, recvtclass/1, + sendtos/1, sendtosttl/1, sendttl/1, sendtclass/1, local_basic/1, local_unbound/1, local_fdopen/1, local_fdopen_unbound/1, local_abstract/1]). @@ -49,6 +50,7 @@ all() -> bad_address, read_packets, open_fd, connect, implicit_inet6, active_n, recvtos, recvtosttl, recvttl, recvtclass, + sendtos, sendtosttl, sendttl, sendtclass, {group, local}]. groups() -> @@ -312,7 +314,6 @@ read_packets(Config) when is_list(Config) -> {ok,R} = gen_udp:open(0, [{read_packets,N1}]), {ok,RP} = inet:port(R), {ok,Node} = start_node(gen_udp_SUITE_read_packets), - Die = make_ref(), %% {V1, Trace1} = read_packets_test(R, RP, Msgs, Node), {ok,[{read_packets,N1}]} = inet:getopts(R, [read_packets]), @@ -324,7 +325,7 @@ read_packets(Config) when is_list(Config) -> stop_node(Node), ct:log("N1=~p, V1=~p vs N2=~p, V2=~p",[N1,V1,N2,V2]), - dump_terms(Config, "trace1.terms", Trace2), + dump_terms(Config, "trace1.terms", Trace1), dump_terms(Config, "trace2.terms", Trace2), %% Because of the inherit racy-ness of the feature it is @@ -348,15 +349,6 @@ dump_terms(Config, Name, Terms) -> file:write_file(FName, term_to_binary(Terms)), ct:log("Logged terms to ~s",[FName]). -infinite_loop(Die) -> - receive - Die -> - ok - after - 0 -> - infinite_loop(Die) - end. - read_packets_test(R, RP, Msgs, Node) -> Receiver = self(), Tracer = @@ -577,19 +569,19 @@ active_n(Config) when is_list(Config) -> recvtos(_Config) -> test_recv_opts( - inet, [{recvtos,tos,96}], + inet, [{recvtos,tos,96}], false, fun recvtos_ok/2). recvtosttl(_Config) -> test_recv_opts( - inet, [{recvtos,tos,96},{recvttl,ttl,33}], + inet, [{recvtos,tos,96},{recvttl,ttl,33}], false, fun (OSType, OSVer) -> recvtos_ok(OSType, OSVer) andalso recvttl_ok(OSType, OSVer) end). recvttl(_Config) -> test_recv_opts( - inet, [{recvttl,ttl,33}], + inet, [{recvttl,ttl,33}], false, fun recvttl_ok/2). recvtclass(_Config) -> @@ -601,15 +593,48 @@ recvtclass(_Config) -> of [_] -> test_recv_opts( - inet6, [{recvtclass,tclass,224}], + inet6, [{recvtclass,tclass,224}], false, fun recvtclass_ok/2); [] -> {skip,ipv6_not_supported,IFs} end. + +sendtos(_Config) -> + test_recv_opts( + inet, [{recvtos,tos,96}], true, + fun sendtos_ok/2). + +sendtosttl(_Config) -> + test_recv_opts( + inet, [{recvtos,tos,96},{recvttl,ttl,33}], true, + fun (OSType, OSVer) -> + sendtos_ok(OSType, OSVer) andalso sendttl_ok(OSType, OSVer) + end). + +sendttl(_Config) -> + test_recv_opts( + inet, [{recvttl,ttl,33}], true, + fun sendttl_ok/2). + +sendtclass(_Config) -> + {ok,IFs} = inet:getifaddrs(), + case + [Name || + {Name,Opts} <- IFs, + lists:member({addr,{0,0,0,0,0,0,0,1}}, Opts)] + of + [_] -> + test_recv_opts( + inet6, [{recvtclass,tclass,224}], true, + fun sendtclass_ok/2); + [] -> + {skip,ipv6_not_supported,IFs} + end. + %% These version numbers are just above the highest noted in daily tests %% where the test fails for a plausible reason, that is the lowest -%% where we can expect that the test mighe succeed, so +%% where we can expect that the test might succeed, so %% skip on platforms lower than this. %% %% On newer versions it might be fixed, but we'll see about that @@ -621,23 +646,65 @@ recvtclass(_Config) -> %% Using the option returns einval, so it is not implemented. recvtos_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {17,6,0}); %% Using the option returns einval, so it is not implemented. -recvtos_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,4,0}); +recvtos_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,6,0}); %% Using the option returns einval, so it is not implemented. recvtos_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0}); %% recvtos_ok({unix,_}, _) -> true; recvtos_ok(_, _) -> false. +%% Option has no effect +recvttl_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0}); +%% recvttl_ok({unix,_}, _) -> true; recvttl_ok(_, _) -> false. %% Using the option returns einval, so it is not implemented. recvtclass_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {9,9,0}); recvtclass_ok({unix,linux}, OSVer) -> not semver_lt(OSVer, {2,6,11}); +%% Option has no effect +recvtclass_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0}); %% recvtclass_ok({unix,_}, _) -> true; recvtclass_ok(_, _) -> false. + +%% To send ancillary data seems to require much higher version numbers +%% than receiving it... +%% + +%% Using the option returns einval, so it is not implemented. +sendtos_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {19,0,0}); +sendtos_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,6,0}); +sendtos_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0}); +sendtos_ok({unix,linux}, OSVer) -> not semver_lt(OSVer, {4,0,0}); +sendtos_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {12,1,0}); +%% +sendtos_ok({unix,_}, _) -> true; +sendtos_ok(_, _) -> false. + +%% Using the option returns einval, so it is not implemented. +sendttl_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {19,0,0}); +sendttl_ok({unix,linux}, OSVer) -> not semver_lt(OSVer, {4,0,0}); +%% Using the option returns enoprotoopt, so it is not implemented. +sendttl_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {12,1,0}); +%% Option has no effect +sendttl_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0}); +sendttl_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,6,0}); +%% +sendttl_ok({unix,_}, _) -> true; +sendttl_ok(_, _) -> false. + +%% Using the option returns einval, so it is not implemented. +sendtclass_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {9,9,0}); +sendtclass_ok({unix,linux}, OSVer) -> not semver_lt(OSVer, {2,6,11}); +%% Option has no effect +sendtclass_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0}); +%% +sendtclass_ok({unix,_}, _) -> true; +sendtclass_ok(_, _) -> false. + + semver_lt({X1,Y1,Z1}, {X2,Y2,Z2}) -> if X1 > X2 -> false; @@ -650,18 +717,18 @@ semver_lt({X1,Y1,Z1}, {X2,Y2,Z2}) -> end; semver_lt(_, {_,_,_}) -> false. -test_recv_opts(Family, Spec, OSFilter) -> +test_recv_opts(Family, Spec, TestSend, OSFilter) -> OSType = os:type(), OSVer = os:version(), case OSFilter(OSType, OSVer) of true -> io:format("Os: ~p, ~p~n", [OSType,OSVer]), - test_recv_opts(Family, Spec, OSType, OSVer); + test_recv_opts(Family, Spec, TestSend, OSType, OSVer); false -> {skip,{not_supported_for_os_version,{OSType,OSVer}}} end. %% -test_recv_opts(Family, Spec, _OSType, _OSVer) -> +test_recv_opts(Family, Spec, TestSend, _OSType, _OSVer) -> Timeout = 5000, RecvOpts = [RecvOpt || {RecvOpt,_,_} <- Spec], TrueRecvOpts = [{RecvOpt,true} || {RecvOpt,_,_} <- Spec], @@ -686,16 +753,33 @@ test_recv_opts(Family, Spec, _OSType, _OSVer) -> ok = inet:setopts(S1, TrueRecvOpts_OptsVals), {ok,TrueRecvOpts_OptsVals} = inet:getopts(S1, RecvOpts ++ Opts), %% + %% S1 now has true receive options and set option values + %% {ok,S2} = gen_udp:open(0, [Family,binary,{active,true}|FalseRecvOpts]), {ok,P2} = inet:port(S2), {ok,FalseRecvOpts_OptsVals2} = inet:getopts(S2, RecvOpts ++ Opts), OptsVals2 = FalseRecvOpts_OptsVals2 -- FalseRecvOpts, %% - ok = gen_udp:send(S2, Addr, P1, <<"abcde">>), + %% S2 now has false receive options and default option values, + %% OptsVals2 contains the default option values + %% + ok = gen_udp:send(S2, {Addr,P1}, <<"abcde">>), ok = gen_udp:send(S1, Addr, P2, <<"fghij">>), + TestSend andalso + begin + ok = gen_udp:send(S2, Addr, P1, OptsVals, <<"ABCDE">>), + ok = gen_udp:send(S2, {Addr,P1}, OptsVals, <<"12345">>) + end, {ok,{_,P2,OptsVals3,<<"abcde">>}} = gen_udp:recv(S1, 0, Timeout), verify_sets_eq(OptsVals3, OptsVals2), + TestSend andalso + begin + {ok,{_,P2,OptsVals0,<<"ABCDE">>}} = gen_udp:recv(S1, 0, Timeout), + {ok,{_,P2,OptsVals1,<<"12345">>}} = gen_udp:recv(S1, 0, Timeout), + verify_sets_eq(OptsVals0, OptsVals), + verify_sets_eq(OptsVals1, OptsVals) + end, receive {udp,S2,_,P1,<<"fghij">>} -> ok; @@ -710,8 +794,16 @@ test_recv_opts(Family, Spec, _OSType, _OSVer) -> ok = inet:setopts(S2, TrueRecvOpts), {ok,TrueRecvOpts} = inet:getopts(S2, RecvOpts), %% - ok = gen_udp:send(S2, Addr, P1, <<"klmno">>), - ok = gen_udp:send(S1, Addr, P2, <<"pqrst">>), + %% S1 now has false receive options and set option values + %% + %% S2 now has true receive options and default option values + %% + ok = gen_udp:send(S2, {Addr,P1}, [], <<"klmno">>), + ok = gen_udp:send(S1, {Family,{loopback,P2}}, <<"pqrst">>), + TestSend andalso + begin + ok = gen_udp:send(S1, {Family,{loopback,P2}}, OptsVals2, <<"PQRST">>) + end, {ok,{_,P2,<<"klmno">>}} = gen_udp:recv(S1, 0, Timeout), receive {udp,S2,_,P1,OptsVals4,<<"pqrst">>} -> @@ -721,9 +813,18 @@ test_recv_opts(Family, Spec, _OSType, _OSVer) -> after Timeout -> exit(timeout) end, + TestSend andalso + receive + {udp,S2,_,P1,OptsVals5,<<"PQRST">>} -> + verify_sets_eq(OptsVals5, OptsVals2); + Other3 -> + exit({unexpected,Other3}) + after Timeout -> + exit(timeout) + end, ok = gen_udp:close(S1), ok = gen_udp:close(S2), -%% exit({{OSType,OSVer},success}), % In search for the truth +%%% exit({{_OSType,_OSVer},success}), % In search for the truth ok. verify_sets_eq(L1, L2) -> @@ -877,6 +978,10 @@ connect(Config) when is_list(Config) -> implicit_inet6(Config) when is_list(Config) -> Host = ok(inet:gethostname()), case inet:getaddr(Host, inet6) of + {ok,{16#fe80,0,0,0,_,_,_,_} = Addr} -> + {skip, + "Got link local IPv6 address: " + ++inet:ntoa(Addr)}; {ok,Addr} -> implicit_inet6(Host, Addr); {error,Reason} -> @@ -927,11 +1032,12 @@ ok({ok,V}) -> V; ok(NotOk) -> try throw(not_ok) catch - throw:Thrown:Stacktrace -> - erlang:raise( - error, {Thrown, NotOk}, tl(Stacktrace)) + throw:not_ok:Stacktrace -> + raise_error({not_ok, NotOk}, tl(Stacktrace)) end. +raise_error(Reason, Stacktrace) -> + erlang:raise(error, Reason, Stacktrace). local_filename(Tag) -> "/tmp/" ?MODULE_STRING "_" ++ os:getpid() ++ "_" ++ atom_to_list(Tag). diff --git a/lib/kernel/test/global_SUITE.erl b/lib/kernel/test/global_SUITE.erl index 8eab36e308..5bff9cc292 100644 --- a/lib/kernel/test/global_SUITE.erl +++ b/lib/kernel/test/global_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2018. All Rights Reserved. +%% Copyright Ericsson AB 1997-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. @@ -2512,8 +2512,10 @@ re_register_name(Config) when is_list(Config) -> Me = self(), Pid1 = spawn(fun() -> proc(Me) end), yes = global:register_name(name, Pid1), + wait_for_monitor(Pid1), Pid2 = spawn(fun() -> proc(Me) end), _ = global:re_register_name(name, Pid2), + wait_for_monitor(Pid2), Pid2 ! die, Pid1 ! die, receive {Pid1, MonitoredBy1} -> [] = MonitoredBy1 end, @@ -2522,6 +2524,15 @@ re_register_name(Config) when is_list(Config) -> init_condition(Config), ok. +wait_for_monitor(Pid) -> + case process_info(Pid, monitored_by) of + {monitored_by, []} -> + timer:sleep(1), + wait_for_monitor(Pid); + {monitored_by, [_]} -> + ok + end. + proc(Parent) -> receive die -> ok end, {monitored_by, MonitoredBy} = process_info(self(), monitored_by), diff --git a/lib/kernel/test/interactive_shell_SUITE.erl b/lib/kernel/test/interactive_shell_SUITE.erl index 298a364a91..173e25c520 100644 --- a/lib/kernel/test/interactive_shell_SUITE.erl +++ b/lib/kernel/test/interactive_shell_SUITE.erl @@ -23,7 +23,8 @@ init_per_group/2,end_per_group/2, get_columns_and_rows/1, exit_initial/1, job_control_local/1, job_control_remote/1, - job_control_remote_noshell/1,ctrl_keys/1]). + job_control_remote_noshell/1,ctrl_keys/1, + get_columns_and_rows_escript/1]). -export([init_per_testcase/2, end_per_testcase/2]). %% For spawn @@ -40,7 +41,8 @@ suite() -> {timetrap,{minutes,3}}]. all() -> - [get_columns_and_rows, exit_initial, job_control_local, + [get_columns_and_rows_escript,get_columns_and_rows, + exit_initial, job_control_local, job_control_remote, job_control_remote_noshell, ctrl_keys]. @@ -72,6 +74,60 @@ end_per_group(_GroupName, Config) -> -define(dbg(Data),noop). -endif. +string_to_term(Str) -> + {ok,Tokens,_EndLine} = erl_scan:string(Str ++ "."), + {ok,AbsForm} = erl_parse:parse_exprs(Tokens), + {value,Value,_Bs} = erl_eval:exprs(AbsForm, erl_eval:new_bindings()), + Value. + +run_unbuffer_escript(Rows, Columns, EScript, NoTermStdIn, NoTermStdOut) -> + DataDir = filename:join(filename:dirname(code:which(?MODULE)), "interactive_shell_SUITE_data"), + TmpFile = filename:join(DataDir, "tmp"), + ok = file:write_file(TmpFile, <<>>), + CommandModifier = + case {NoTermStdIn, NoTermStdOut} of + {false, false} -> ""; + {true, false} -> io_lib:format(" < ~s", [TmpFile]); + {false, true} -> io_lib:format(" > ~s ; cat ~s", [TmpFile, TmpFile]); + {true, true} -> io_lib:format(" > ~s < ~s ; cat ~s", [TmpFile, TmpFile, TmpFile]) + end, + Command = io_lib:format("unbuffer -p bash -c \"stty rows ~p; stty columns ~p; escript ~s ~s\"", + [Rows, Columns, EScript, CommandModifier]), + %% io:format("Command: ~s ~n", [Command]), + Out = os:cmd(Command), + %% io:format("Out: ~p ~n", [Out]), + string_to_term(Out). + +get_columns_and_rows_escript(Config) when is_list(Config) -> + ExpectUnbufferInstalled = + try + "79" = string:trim(os:cmd("unbuffer -p bash -c \"stty columns 79 ; tput cols\"")), + true + catch + _:_ -> false + end, + case ExpectUnbufferInstalled of + false -> + {skip, + "The unbuffer tool (https://core.tcl-lang.org/expect/index) does not seem to be installed.~n" + "On Ubuntu/Debian: \"sudo apt-get install expect\""}; + true -> + DataDir = filename:join(filename:dirname(code:which(?MODULE)), "interactive_shell_SUITE_data"), + IoColumnsErl = filename:join(DataDir, "io_columns.erl"), + IoRowsErl = filename:join(DataDir, "io_rows.erl"), + [ + begin + {ok, 42} = run_unbuffer_escript(99, 42, IoColumnsErl, NoTermStdIn, NoTermStdOut), + {ok, 99} = run_unbuffer_escript(99, 42, IoRowsErl, NoTermStdIn, NoTermStdOut) + end + || + {NoTermStdIn, NoTermStdOut} <- [{false, false}, {true, false}, {false, true}] + ], + {error,enotsup} = run_unbuffer_escript(99, 42, IoRowsErl, true, true), + {error,enotsup} = run_unbuffer_escript(99, 42, IoColumnsErl, true, true), + ok + end. + %% Test that the shell can access columns and rows. get_columns_and_rows(Config) when is_list(Config) -> case proplists:get_value(default_shell,Config) of diff --git a/lib/kernel/test/interactive_shell_SUITE_data/.gitignore b/lib/kernel/test/interactive_shell_SUITE_data/.gitignore new file mode 100644 index 0000000000..1c2f433de1 --- /dev/null +++ b/lib/kernel/test/interactive_shell_SUITE_data/.gitignore @@ -0,0 +1 @@ +tmp
\ No newline at end of file diff --git a/lib/kernel/test/interactive_shell_SUITE_data/io_columns.erl b/lib/kernel/test/interactive_shell_SUITE_data/io_columns.erl new file mode 100644 index 0000000000..32d0cf25df --- /dev/null +++ b/lib/kernel/test/interactive_shell_SUITE_data/io_columns.erl @@ -0,0 +1,6 @@ +-module(io_columns). + +-export([main/1]). + +main(_) -> + io:format("~p",[io:columns()]). diff --git a/lib/kernel/test/interactive_shell_SUITE_data/io_rows.erl b/lib/kernel/test/interactive_shell_SUITE_data/io_rows.erl new file mode 100644 index 0000000000..53ceb464b0 --- /dev/null +++ b/lib/kernel/test/interactive_shell_SUITE_data/io_rows.erl @@ -0,0 +1,6 @@ +-module(io_rows). + +-export([main/1]). + +main(_) -> + io:format("~p",[io:rows()]). diff --git a/lib/kernel/test/logger_std_h_SUITE.erl b/lib/kernel/test/logger_std_h_SUITE.erl index 16ab0e97fc..2b2d509860 100644 --- a/lib/kernel/test/logger_std_h_SUITE.erl +++ b/lib/kernel/test/logger_std_h_SUITE.erl @@ -132,6 +132,7 @@ all() -> bad_input, reconfig, file_opts, + relative_file_path, sync, write_failure, sync_failure, @@ -693,6 +694,54 @@ file_opts(Config) -> file_opts(cleanup, _Config) -> logger:remove_handler(?MODULE). +relative_file_path(_Config) -> + {ok,Dir} = file:get_cwd(), + AbsName1 = filename:join(Dir,?MODULE), + ok = logger:add_handler(?MODULE, + logger_std_h, + #{config => #{type=>file}, + filter_default=>log, + filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]), + formatter=>{?MODULE,self()}}), + #{cb_state := #{handler_state := #{file:=AbsName1}}} = + logger_olp:info(h_proc_name()), + {ok,#{config := #{file:=AbsName1}}} = + logger:get_handler_config(?MODULE), + ok = logger:remove_handler(?MODULE), + + RelName2 = filename:join(atom_to_list(?FUNCTION_NAME), + lists:concat([?FUNCTION_NAME,".log"])), + AbsName2 = filename:join(Dir,RelName2), + ok = logger:add_handler(?MODULE, + logger_std_h, + #{config => #{file => RelName2}, + filter_default=>log, + filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]), + formatter=>{?MODULE,self()}}), + #{cb_state := #{handler_state := #{file:=AbsName2}}} = + logger_olp:info(h_proc_name()), + {ok,#{config := #{file:=AbsName2}}} = + logger:get_handler_config(?MODULE), + logger:notice(M1=?msg,?domain), + ?check(M1), + B1 = ?bin(M1), + try_read_file(AbsName2, {ok,B1}, filesync_rep_int()), + + ok = file:set_cwd(".."), + logger:notice(M2=?msg,?domain), + ?check(M2), + B20 = ?bin(M2), + B2 = <<B1/binary,B20/binary>>, + try_read_file(AbsName2, {ok,B2}, filesync_rep_int()), + + {error,_} = logger:update_handler_config(?MODULE,config,#{file=>RelName2}), + ok = logger:update_handler_config(?MODULE,config,#{file=>AbsName2}), + ok = file:set_cwd(Dir), + ok = logger:update_handler_config(?MODULE,config,#{file=>RelName2}), + ok. +relative_file_path(cleanup,_Config) -> + logger:remove_handler(?MODULE). + sync(Config) -> Dir = ?config(priv_dir,Config), diff --git a/lib/kernel/test/seq_trace_SUITE.erl b/lib/kernel/test/seq_trace_SUITE.erl index 663f910751..83a94ab087 100644 --- a/lib/kernel/test/seq_trace_SUITE.erl +++ b/lib/kernel/test/seq_trace_SUITE.erl @@ -26,6 +26,7 @@ init_per_group/2,end_per_group/2, init_per_testcase/2,end_per_testcase/2]). -export([token_set_get/1, tracer_set_get/1, print/1, + old_heap_token/1, send/1, distributed_send/1, recv/1, distributed_recv/1, trace_exit/1, distributed_exit/1, call/1, port/1, match_set_seq_token/1, gc_seq_token/1, label_capability_mismatch/1, @@ -50,6 +51,7 @@ suite() -> all() -> [token_set_get, tracer_set_get, print, send, send_literal, distributed_send, recv, distributed_recv, trace_exit, + old_heap_token, distributed_exit, call, port, match_set_seq_token, gc_seq_token, label_capability_mismatch]. @@ -149,17 +151,19 @@ tracer_set_get(Config) when is_list(Config) -> ok. print(Config) when is_list(Config) -> - lists:foreach(fun do_print/1, ?TIMESTAMP_MODES). + [do_print(TsType, Label) || TsType <- ?TIMESTAMP_MODES, + Label <- [17, "label"]]. -do_print(TsType) -> +do_print(TsType, Label) -> start_tracer(), + seq_trace:set_token(label, Label), set_token_flags([print, TsType]), - seq_trace:print(0,print1), + seq_trace:print(Label,print1), seq_trace:print(1,print2), seq_trace:print(print3), seq_trace:reset_trace(), - [{0,{print,_,_,[],print1}, Ts0}, - {0,{print,_,_,[],print3}, Ts1}] = stop_tracer(2), + [{Label,{print,_,_,[],print1}, Ts0}, + {Label,{print,_,_,[],print3}, Ts1}] = stop_tracer(2), check_ts(TsType, Ts0), check_ts(TsType, Ts1). @@ -563,6 +567,24 @@ get_port_message(Port) -> end. +%% OTP-15849 ERL-700 +%% Verify changing label on existing token when it resides on old heap. +%% Bug caused faulty ref from old to new heap. +old_heap_token(Config) when is_list(Config) -> + seq_trace:set_token(label, 1), + erlang:garbage_collect(self(), [{type, minor}]), + erlang:garbage_collect(self(), [{type, minor}]), + %% Now token tuple should be on old-heap. + %% Set a new non-literal label which should reside on new-heap. + NewLabel = {self(), "new label"}, + 1 = seq_trace:set_token(label, NewLabel), + + %% If bug, we now have a ref from old to new heap. Yet another minor gc + %% will make that a ref to deallocated memory. + erlang:garbage_collect(self(), [{type, minor}]), + {label,NewLabel} = seq_trace:get_token(label), + ok. + match_set_seq_token(doc) -> ["Tests that match spec function set_seq_token does not " diff --git a/lib/kernel/vsn.mk b/lib/kernel/vsn.mk index 765e890157..e5188aa9b5 100644 --- a/lib/kernel/vsn.mk +++ b/lib/kernel/vsn.mk @@ -1 +1 @@ -KERNEL_VSN = 6.4 +KERNEL_VSN = 6.4.1 diff --git a/lib/megaco/Makefile b/lib/megaco/Makefile index 1d0bb6778c..f385df6a5c 100644 --- a/lib/megaco/Makefile +++ b/lib/megaco/Makefile @@ -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. @@ -206,11 +206,18 @@ $(APP_TAR_FILE): $(APP_DIR) dialyzer_plt: $(DIA_PLT) -$(DIA_PLT): +$(DIA_PLT): Makefile @echo "Building $(APPLICATION) plt file" @dialyzer --build_plt \ --output_plt $@ \ -r ../$(APPLICATION)/ebin \ + ../../lib/kernel/ebin \ + ../../lib/stdlib/ebin \ + ../../lib/runtime_tools/ebin \ + ../../lib/asn1/ebin \ + ../../lib/debugger/ebin \ + ../../lib/et/ebin \ + ../../erts/preloaded/ebin \ --output $(DIA_ANALYSIS) \ --verbose diff --git a/lib/megaco/doc/src/megaco_edist_compress.xml b/lib/megaco/doc/src/megaco_edist_compress.xml index 16443e469c..8461c59a00 100644 --- a/lib/megaco/doc/src/megaco_edist_compress.xml +++ b/lib/megaco/doc/src/megaco_edist_compress.xml @@ -43,8 +43,8 @@ <name since="">Module:encode(R, Version) -> T</name> <fsummary>Encode (compress) a megaco component.</fsummary> <type> - <v>R = megaco_message() | transaction() | action_reply() | action_request() | command_request()</v> - <v>Version = integer()</v> + <v>R = megaco_encoder:megaco_message() | megaco_encoder:transaction() | megaco_encoder:action_reply() | megaco_encoder:action_request() | megaco_encoder:command_request()</v> + <v>Version = megaco_encoder:protocol_version()</v> <v>T = term()</v> </type> <desc> @@ -57,8 +57,8 @@ <fsummary>Decode (decompress) a megaco component.</fsummary> <type> <v>T = term()</v> - <v>Version = integer()</v> - <v>R = megaco_message() | transaction() | action_reply() | action_request() | command_request()</v> + <v>Version = megaco_encoder:protocol_version()</v> + <v>R = megaco_encoder:megaco_message() | megaco_encoder:transaction() | megaco_encoder:action_reply() | megaco_encoder:action_request() | megaco_encoder:command_request()</v> </type> <desc> <p>Decompress a megaco component. </p> diff --git a/lib/megaco/doc/src/megaco_encoder.xml b/lib/megaco/doc/src/megaco_encoder.xml index cc8270440b..0632a55d48 100644 --- a/lib/megaco/doc/src/megaco_encoder.xml +++ b/lib/megaco/doc/src/megaco_encoder.xml @@ -42,7 +42,16 @@ <section> <title>DATA TYPES</title> + <note> + <p>Note that the actual definition of (some of) these records depend on + the megaco protocol version used. For instance, the + <c>'TransactionReply'</c> record + has two more fields in version 3, so a simple erlang type definition + cannot be made here. </p> + </note> <code type="none"><![CDATA[ +protocol_version() = integer() +segment_no() = integer() megaco_message() = #'MegacoMessage{}' transaction() = {transactionRequest, transaction_request()} | {transactionPending, transaction_reply()} | @@ -57,6 +66,8 @@ transaction_ack() = #'TransactionAck'{} segment_reply() = #'SegmentReply'{} action_request() = #'ActionRequest'{} action_reply() = #'ActionReply'{} +command_request() = #'CommandRequest'{} +error_desc() = #'ErrorDescriptor'{} ]]></code> <marker id="encode_message"></marker> diff --git a/lib/megaco/doc/src/megaco_user.xml b/lib/megaco/doc/src/megaco_user.xml index 198f2aa24c..56d4d51cde 100644 --- a/lib/megaco/doc/src/megaco_user.xml +++ b/lib/megaco/doc/src/megaco_user.xml @@ -165,7 +165,7 @@ protocol_version() = integer() ]]></code> <funcs> <func> <name since="">handle_connect(ConnHandle, ProtocolVersion) -> ok | error | {error,ErrorDescr}</name> - <name since="">handle_connect(ConnHandle, ProtocolVersion, Extra]) -> ok | error | {error,ErrorDescr}</name> + <name since="">handle_connect(ConnHandle, ProtocolVersion, Extra) -> ok | error | {error,ErrorDescr}</name> <fsummary>Invoked when a new connection is established</fsummary> <type> <v>ConnHandle = conn_handle()</v> diff --git a/lib/megaco/examples/simple/megaco_simple_mgc.erl b/lib/megaco/examples/simple/megaco_simple_mgc.erl index f324e17a3a..8a78262b86 100644 --- a/lib/megaco/examples/simple/megaco_simple_mgc.erl +++ b/lib/megaco/examples/simple/megaco_simple_mgc.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2016. All Rights Reserved. +%% Copyright Ericsson AB 2001-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,15 +193,17 @@ stop(Mid) -> d("stop -> entry with~n Mid: ~p", [Mid]), Disco = fun(CH) -> d("stop -> CH: ~p", [CH]), - Reason = stopped_by_user, - Pid = megaco:conn_info(CH, control_pid), - SendMod = megaco:conn_info(CH, send_mod), + Reason = stopped_by_user, + Pid = megaco:conn_info(CH, control_pid), + SendMod = megaco:conn_info(CH, send_mod), SendHandle = megaco:conn_info(CH, send_handle), d("stop -> disconnect", []), megaco:disconnect(CH, Reason), + d("stop -> cancel", []), - megaco:cancel(CH, Reason), + megaco:cancel(CH, Reason), % see handle_disconnect + d("stop -> close transport" "~n SendMod: ~p" "~n SendHandle: ~p", [SendMod, SendHandle]), @@ -247,6 +249,7 @@ handle_disconnect(ConnHandle, ProtocolVersion, Reason) -> "~n ProtocolVersion: ~p" "~n Reason: ~p" "", [ConnHandle, ProtocolVersion, Reason]), + info_msg("handle_disconnect - cancel outstanding messages~n"), megaco:cancel(ConnHandle, Reason), % Cancel the outstanding messages ok. @@ -443,6 +446,12 @@ get_arg(Key, Args) -> %% DEBUGGING %%---------------------------------------------------------------------- +info_msg(F) -> + info_msg(F, []). +info_msg(F, A) -> + io:format("~p MGC: " ++ F ++ "~n", [self()|A]). + + d(F) -> d(F, []). diff --git a/lib/megaco/src/app/megaco.app.src b/lib/megaco/src/app/megaco.app.src index c54c80351c..5fb7273b4a 100644 --- a/lib/megaco/src/app/megaco.app.src +++ b/lib/megaco/src/app/megaco.app.src @@ -107,6 +107,7 @@ megaco_udp, megaco_udp_server, megaco_udp_sup, + megaco_user, megaco_user_default ]}, {registered, [megaco_config, megaco_monitor, diff --git a/lib/megaco/src/app/megaco.erl b/lib/megaco/src/app/megaco.erl index f0c209fd6c..9ed042401b 100644 --- a/lib/megaco/src/app/megaco.erl +++ b/lib/megaco/src/app/megaco.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. @@ -94,6 +94,11 @@ %% This is for XREF -deprecated([{format_versions, 1, eventually}]). +-export_type([ + void/0 + ]). + +-type void() :: term(). -include("megaco_internal.hrl"). @@ -686,13 +691,8 @@ sys_info() -> [{arch, SysArch}, {ver, SysVer}]. os_info() -> - V = os:version(), - case os:type() of - {OsFam, OsName} -> - [{fam, OsFam}, {name, OsName}, {ver, V}]; - OsFam -> - [{fam, OsFam}, {ver, V}] - end. + {OsFam, OsName} = os:type(), + [{fam, OsFam}, {name, OsName}, {ver, os:version()}]. ms() -> ms1(). diff --git a/lib/megaco/src/binary/megaco_binary_encoder_lib.erl b/lib/megaco/src/binary/megaco_binary_encoder_lib.erl index 5e9836dc48..fdbb42c2f8 100644 --- a/lib/megaco/src/binary/megaco_binary_encoder_lib.erl +++ b/lib/megaco/src/binary/megaco_binary_encoder_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-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. @@ -141,6 +141,7 @@ encode_transaction(_EC, T, _AsnMod, _TransMod, _Type) -> TransMod :: atom(), Type :: atom()) -> {'ok', binary()} | {'error', any()}. +-dialyzer({nowarn_function, do_encode_transaction/5}). % Future compat do_encode_transaction([native], _Trans, _AsnMod, _TransMod, binary) -> %% asn1rt:encode(AsnMod, element(1, T), T); {error, not_implemented}; @@ -173,6 +174,7 @@ do_encode_transaction(EC, _Trans, _AsnMod, _TransMod, _Type) -> TransMod :: atom(), Type :: atom()) -> {'ok', binary()} | {'error', any()}. +-dialyzer({nowarn_function, encode_action_requests/5}). % Future compat encode_action_requests([native], _ARs, _AsnMod, _TransMod, binary) -> %% asn1rt:encode(AsnMod, element(1, T), T); {error, not_implemented}; @@ -203,6 +205,7 @@ encode_action_requests(EC, _ARs, _AsnMod, _TransMod, _Type) -> TransMod :: atom(), Type :: atom()) -> {'ok', binary()} | {'error', any()}. +-dialyzer({nowarn_function, encode_action_request/5}). % Future compat encode_action_request([native], _AR, _AsnMod, _TransMod, binary) -> %% asn1rt:encode(AsnMod, element(1, T), T); {error, not_implemented}; @@ -226,6 +229,8 @@ encode_action_request(EC, _AR, _AsnMod, _TransMod, _Type) -> %% Convert a ActionReply record into a binary %% Return {ok, DeepIoList} | {error, Reason} %%---------------------------------------------------------------------- + +-dialyzer({nowarn_function, encode_action_reply/5}). % Future compat encode_action_reply([native], _ARs, _AsnMod, _TransMod, binary) -> %% asn1rt:encode(AsnMod, element(1, T), T); {error, not_implemented}; diff --git a/lib/megaco/src/binary/megaco_binary_name_resolver_prev3a.erl b/lib/megaco/src/binary/megaco_binary_name_resolver_prev3a.erl index af97056d5d..f9e3fe39e3 100644 --- a/lib/megaco/src/binary/megaco_binary_name_resolver_prev3a.erl +++ b/lib/megaco/src/binary/megaco_binary_name_resolver_prev3a.erl @@ -1,7 +1,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. @@ -1715,7 +1715,7 @@ decode_nt({event_parameter, Item}, SubItem) -> [16#00, 16#01] -> "cs" end; [16#00, 16#06] -> % Event qualert - case Item of + case SubItem of [16#00, 16#01] -> "th" end end; diff --git a/lib/megaco/src/binary/megaco_binary_name_resolver_prev3b.erl b/lib/megaco/src/binary/megaco_binary_name_resolver_prev3b.erl index b543abe7c8..141225f501 100644 --- a/lib/megaco/src/binary/megaco_binary_name_resolver_prev3b.erl +++ b/lib/megaco/src/binary/megaco_binary_name_resolver_prev3b.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-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. @@ -1715,7 +1715,7 @@ decode_nt({event_parameter, Item}, SubItem) -> [16#00, 16#01] -> "cs" end; [16#00, 16#06] -> % Event qualert - case Item of + case SubItem of [16#00, 16#01] -> "th" end end; diff --git a/lib/megaco/src/binary/megaco_binary_name_resolver_prev3c.erl b/lib/megaco/src/binary/megaco_binary_name_resolver_prev3c.erl index 827cb3920b..b27a0be26e 100644 --- a/lib/megaco/src/binary/megaco_binary_name_resolver_prev3c.erl +++ b/lib/megaco/src/binary/megaco_binary_name_resolver_prev3c.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-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. @@ -1716,7 +1716,7 @@ decode_nt({event_parameter, Item}, SubItem) -> [16#00, 16#01] -> "cs" end; [16#00, 16#06] -> % Event qualert - case Item of + case SubItem of [16#00, 16#01] -> "th" end end; diff --git a/lib/megaco/src/binary/megaco_binary_name_resolver_v1.erl b/lib/megaco/src/binary/megaco_binary_name_resolver_v1.erl index 1fba60fed6..aba64bb9f2 100644 --- a/lib/megaco/src/binary/megaco_binary_name_resolver_v1.erl +++ b/lib/megaco/src/binary/megaco_binary_name_resolver_v1.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2016. All Rights Reserved. +%% Copyright Ericsson AB 2000-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. @@ -1325,7 +1325,7 @@ decode_nt({event_parameter, Item}, SubItem) -> [16#00, 16#01] -> "cs" end; [16#00, 16#06] -> % Event qualert - case Item of + case SubItem of [16#00, 16#01] -> "th" end end; diff --git a/lib/megaco/src/binary/megaco_binary_name_resolver_v2.erl b/lib/megaco/src/binary/megaco_binary_name_resolver_v2.erl index 45b9b32772..dd07f8b404 100644 --- a/lib/megaco/src/binary/megaco_binary_name_resolver_v2.erl +++ b/lib/megaco/src/binary/megaco_binary_name_resolver_v2.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-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. @@ -1393,7 +1393,7 @@ decode_nt({event_parameter, Item}, SubItem) -> [16#00, 16#01] -> "cs" end; [16#00, 16#06] -> % Event qualert - case Item of + case SubItem of [16#00, 16#01] -> "th" end end; diff --git a/lib/megaco/src/binary/megaco_binary_name_resolver_v3.erl b/lib/megaco/src/binary/megaco_binary_name_resolver_v3.erl index f1482bc252..a8c4211235 100644 --- a/lib/megaco/src/binary/megaco_binary_name_resolver_v3.erl +++ b/lib/megaco/src/binary/megaco_binary_name_resolver_v3.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-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. @@ -1728,7 +1728,7 @@ decode_nt({event_parameter, Item}, SubItem) -> [16#00, 16#01] -> "cs" end; [16#00, 16#06] -> % Event qualert - case Item of + case SubItem of [16#00, 16#01] -> "th" end end; diff --git a/lib/megaco/src/engine/depend.mk b/lib/megaco/src/engine/depend.mk index 96ee337e3a..ba919659db 100644 --- a/lib/megaco/src/engine/depend.mk +++ b/lib/megaco/src/engine/depend.mk @@ -2,7 +2,7 @@ # %CopyrightBegin% # -# Copyright Ericsson AB 2003-2016. All Rights Reserved. +# Copyright Ericsson AB 2003-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. @@ -78,6 +78,10 @@ $(EBIN)/megaco_transport.$(EMULATOR): megaco_transport.erl $(EBIN)/megaco_user.$(EMULATOR): megaco_user.erl +$(EBIN)/megaco_user.$(EMULATOR): megaco_user.erl \ + ../../include/megaco.hrl \ + ../../include/megaco_message_v1.hrl + $(EBIN)/megaco_user_default.$(EMULATOR): megaco_user_default.erl \ ../../include/megaco.hrl \ ../../include/megaco_message_v1.hrl diff --git a/lib/megaco/src/engine/megaco_edist_compress.erl b/lib/megaco/src/engine/megaco_edist_compress.erl index 987a5ec717..968ab6f16e 100644 --- a/lib/megaco/src/engine/megaco_edist_compress.erl +++ b/lib/megaco/src/engine/megaco_edist_compress.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-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. @@ -25,10 +25,21 @@ -module(megaco_edist_compress). --export([behaviour_info/1]). +-callback encode(R, Version) -> T when + R :: megaco_encoder:megaco_message() | + megaco_encoder:transaction() | + megaco_encoder:action_reply() | + megaco_encoder:action_request() | + megaco_encoder:command_request(), + Version :: megaco_encoder:protocol_version(), + T :: term(). + +-callback decode(T, Version) -> R when + T :: term(), + Version :: megaco_encoder:protocol_version() | dynamic, + R :: megaco_encoder:megaco_message() | + megaco_encoder:transaction() | + megaco_encoder:action_reply() | + megaco_encoder:action_request() | + megaco_encoder:command_request(). -behaviour_info(callbacks) -> - [{encode,2}, - {decode,2}]; -behaviour_info(_) -> - undefined. diff --git a/lib/megaco/src/engine/megaco_encoder.erl b/lib/megaco/src/engine/megaco_encoder.erl index 7ade349083..dd5a3458fc 100644 --- a/lib/megaco/src/engine/megaco_encoder.erl +++ b/lib/megaco/src/engine/megaco_encoder.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-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. @@ -25,14 +25,108 @@ -module(megaco_encoder). --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{encode_message, 3}, - {decode_message, 3}, - {decode_mini_message, 3}, - {encode_transaction, 3}, - {encode_action_requests, 3}, - {encode_action_reply, 3}]; -behaviour_info(_) -> - undefined. +-export_type([ + protocol_version/0, + segment_no/0, + megaco_message/0, + transaction/0, + transaction_request/0, + transaction_pending/0, + transaction_reply/0, + transaction_response_ack/0, + transaction_ack/0, + segment_reply/0, + action_request/0, + action_reply/0, + command_request/0, + error_desc/0 + ]). + + +-include("megaco_message_internal.hrl"). + +-type protocol_version() :: integer(). +-type segment_no() :: integer(). +-type megaco_message() :: #'MegacoMessage'{}. +-type transaction() :: {transactionRequest, transaction_request()} | + {transactionPending, transaction_reply()} | + {transactionReply, transaction_pending()} | + {transactionResponseAck, transaction_response_ack()} | + {segmentReply, segment_reply()}. +-type transaction_request() :: #'TransactionRequest'{}. +-type transaction_pending() :: #'TransactionPending'{}. +%% The problem with TransactionReply is that its definition depend +%% on which version of the protocol we are using. As of version 3, +%% it has two more fields. +%% -type transaction_reply() :: #'TransactionReply'{}. +-type transaction_reply() :: {'TransactionReply', _, _} | + {'TransactionReply', _, _, _, _}. +-type transaction_response_ack() :: [transaction_ack()]. +-type transaction_ack() :: #'TransactionAck'{}. +-type segment_reply() :: #'SegmentReply'{}. +%% -type action_request() :: #'ActionRequest'{}. +-type action_request() :: {'ActionRequest', _, _, _, _}. +%% -type action_reply() :: #'ActionReply'{}. +-type action_reply() :: {'ActionReply', _, _, _}. +%% -type command_request() :: #'CommandRequest'{}. +-type command_request() :: {'CommandRequest', _, _, _}. +-type error_desc() :: #'ErrorDescriptor'{}. + +-callback encode_message(EncodingConfig, + Version, + Message) -> {ok, Bin} | Error when + EncodingConfig :: list(), + Version :: protocol_version(), + Message :: megaco_message(), + Bin :: binary(), + Error :: term(). + +-callback decode_message(EncodingConfig, + Version, + Bin) -> {ok, Message} | Error when + EncodingConfig :: list(), + Version :: protocol_version() | dynamic, + Bin :: binary(), + Message :: megaco_message(), + Error :: term(). + +-callback decode_mini_message(EncodingConfig, + Version, + Bin) -> {ok, Message} | Error when + EncodingConfig :: list(), + Version :: protocol_version() | dynamic, + Bin :: binary(), + Message :: megaco_message(), + Error :: term(). + +-callback encode_transaction(EncodingConfig, + Version, + Transaction) -> {ok, Bin} | {error, Reason} when + EncodingConfig :: list(), + Version :: protocol_version(), + Transaction :: transaction(), + Bin :: binary(), + Reason :: not_implemented | term(). + +-callback encode_action_requests(EncodingConfig, + Version, + ARs) -> {ok, Bin} | {error, Reason} when + EncodingConfig :: list(), + Version :: protocol_version(), + ARs :: [action_request()], + Bin :: binary(), + Reason :: not_implemented | term(). + +-callback encode_action_reply(EncodingConfig, + Version, + AR) -> {ok, Bin} | {error, Reason} when + EncodingConfig :: list(), + Version :: protocol_version(), + AR :: action_reply(), + Bin :: binary(), + Reason :: not_implemented | term(). + +-optional_callbacks( + [ + encode_action_reply/3 % Only used if segementation is used + ]). diff --git a/lib/megaco/src/engine/megaco_messenger.erl b/lib/megaco/src/engine/megaco_messenger.erl index 1d462b2140..2a9ecee2a7 100644 --- a/lib/megaco/src/engine/megaco_messenger.erl +++ b/lib/megaco/src/engine/megaco_messenger.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. @@ -1391,7 +1391,7 @@ prepare_request(ConnData, T, Rest, AckList, ReqList, Extra) -> %% don't restart the reply_timer. ConnData2 = ConnData#conn_data{protocol_version = Version}, ?report_trace(ConnData2, - "re-send trans reply", [T | {bytes, Bin}]), + "re-send trans reply", [T, {bytes, Bin}]), case megaco_messenger_misc:send_message(ConnData2, true, Bin) of {ok, _} -> ok; diff --git a/lib/megaco/src/engine/megaco_user.erl b/lib/megaco/src/engine/megaco_user.erl new file mode 100644 index 0000000000..47fb1a119d --- /dev/null +++ b/lib/megaco/src/engine/megaco_user.erl @@ -0,0 +1,386 @@ +%% +%% %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% +%% + +%% +%%------------------------------------------------------------------------- +%% Purpose: Megaco user behaviour module +%% +%% This callback functions are the default! Its possible for the user to +%% provide a arbitrary number of "extra" arguments via the user_args +%% config option. +%% So, for instance, the handle_connect/2 could instead become +%% handle_connect/4 if the user sets the user_args option to [foo, bar]. +%% This means that its impossible to define a proper behaviour. +%% So what we do here is to define a behaviour with the "default interface" +%% (the user_args option has the [] as the default value) and set them +%% all to be optional! +%%------------------------------------------------------------------------- + +-module(megaco_user). + +-export_type([ + receive_handle/0, + conn_handle/0, + megaco_timer/0 + ]). + +-include_lib("megaco/include/megaco.hrl"). +%% -include_lib("megaco/include/megaco_message_v1.hrl"). + +-type receive_handle() :: #megaco_receive_handle{}. +-type conn_handle() :: #megaco_conn_handle{}. +-type megaco_timer() :: infinity | non_neg_integer() | #megaco_incr_timer{}. + +-callback handle_connect(ConnHandle, ProtocolVersion) -> + ok | error | {error, ErrorDescr} when + ConnHandle :: conn_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + ErrorDescr :: megaco_encoder:error_desc(). +-callback handle_connect(ConnHandle, ProtocolVersion, Extra) -> + ok | error | {error, ErrorDescr} when + ConnHandle :: conn_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + Extra :: term(), + ErrorDescr :: megaco_encoder:error_desc(). + +-callback handle_disconnect(ConnHandle, ProtocolVersion, Reason) -> + megaco:void() when + ConnHandle :: conn_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + Reason :: term(). + +-callback handle_syntax_error(ReceiveHandle, ProtocolVersion, DefaultED) -> + reply | {reply, ED} | no_reply | {no_reply, ED} when + ReceiveHandle :: receive_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + DefaultED :: megaco_encoder:error_desc(), + ED :: megaco_encoder:error_desc(). +-callback handle_syntax_error(ReceiveHandle, ProtocolVersion, DefaultED, Extra) -> + reply | {reply, ED} | no_reply | {no_reply, ED} when + ReceiveHandle :: receive_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + DefaultED :: megaco_encoder:error_desc(), + ED :: megaco_encoder:error_desc(), + Extra :: term(). + +-callback handle_message_error(ConnHandle, ProtocolVersion, ErrorDescr) -> + megaco:void() when + ConnHandle :: conn_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + ErrorDescr :: megaco_encoder:error_desc(). +-callback handle_message_error(ConnHandle, ProtocolVersion, ErrorDescr, Extra) -> + megaco:void() when + ConnHandle :: conn_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + ErrorDescr :: megaco_encoder:error_desc(), + Extra :: term(). + +-callback handle_trans_request(ConnHandle, ProtocolVersion, ActionRequests) -> + Pending | Reply | ignore_trans_request when + ConnHandle :: conn_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + ActionRequests :: [megaco_encoder:action_request()], + Pending :: {pending, ReqData}, + ReqData :: term(), + Reply :: {AckAction, ActualReply} | + {AckAction, ActualReply, SendOptions}, + AckAction :: discard_ack | + {handle_ack, AckData} | + {handle_pending_ack, AckData} | + {handle_sloppy_ack, AckData}, + ActualReply :: [megaco_encoder:action_reply()] | + megaco_encoder:error_desc(), + AckData :: term(), + SendOptions :: [SendOption], + SendOption :: {reply_timer, megaco_timer()} | + {send_handle, term()} | + {protocol_version, integer()}. +-callback handle_trans_request(ConnHandle, + ProtocolVersion, + ActionRequests, + Extra) -> + Pending | Reply | ignore_trans_request when + ConnHandle :: conn_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + ActionRequests :: [megaco_encoder:action_request()], + Extra :: term(), + Pending :: {pending, ReqData}, + ReqData :: term(), + Reply :: {AckAction, ActualReply} | + {AckAction, ActualReply, SendOptions}, + AckAction :: discard_ack | + {handle_ack, AckData} | + {handle_pending_ack, AckData} | + {handle_sloppy_ack, AckData}, + ActualReply :: [megaco_encoder:action_reply()] | + megaco_encoder:error_desc(), + AckData :: term(), + SendOptions :: [SendOption], + SendOption :: {reply_timer, megaco_timer()} | + {send_handle, term()} | + {protocol_version, integer()}. + +-callback handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData) -> + Reply when + ConnHandle :: conn_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + ReqData :: term(), + Reply :: {AckAction, ActualReply} | + {AckAction, ActualReply, SendOptions}, + AckAction :: discard_ack | + {handle_ack, AckData} | + {handle_sloppy_ack, AckData}, + ActualReply :: [megaco_encoder:action_reply()] | + megaco_encoder:error_desc(), + AckData :: term(), + SendOptions :: [SendOption], + SendOption :: {reply_timer, megaco_timer()} | + {send_handle, term()} | + {protocol_version, megaco_encoder:protocol_version()}. +-callback handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData, Extra) -> + Reply when + ConnHandle :: conn_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + ReqData :: term(), + Extra :: term(), + Reply :: {AckAction, ActualReply} | + {AckAction, ActualReply, SendOptions}, + AckAction :: discard_ack | + {handle_ack, AckData} | + {handle_sloppy_ack, AckData}, + ActualReply :: [megaco_encoder:action_reply()] | + megaco_encoder:error_desc(), + AckData :: term(), + SendOptions :: [SendOption], + SendOption :: {reply_timer, megaco_timer()} | + {send_handle, term()} | + {protocol_version, megaco_encoder:protocol_version()}. + +-callback handle_trans_reply(ConnHandle, + ProtocolVersion, + UserReply, + ReplyData) -> + ok when + ConnHandle :: conn_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + UserReply :: Success | Failure, + ReplyData :: term(), + Success :: {ok, Result}, + Result :: TransactionResult | SegmentResult, + TransactionResult :: [megaco_encoder:action_reply()], + SegmentResult :: {megaco_encoder:segment_no(), + LastSegment, + [megaco_encoder:action_reply()]}, + Failure :: {error, Reason} | + {error, ReplyNo, Reason}, + Reason :: TransactionReason | + SegmentReason | + UserCancelReason | + SendReason | + OtherReason, + TransactionReason :: megaco_encoder:error_desc(), + SegmentReason :: {megaco_encoder:segment_no(), + LastSegment, + megaco_encoder:error_desc()}, + OtherReason :: timeout | + {segment_timeout, MissingSegments} | + exceeded_recv_pending_limit | term(), + LastSegment :: boolean(), + MissingSegments :: [megaco_encoder:segment_no()], + UserCancelReason :: {user_cancel, ReasonForUserCancel}, + ReasonForUserCancel :: term(), + SendReason :: SendCancelledReason | SendFailedReason, + SendCancelledReason :: {send_message_cancelled, + ReasonForSendCancel}, + ReasonForSendCancel :: term(), + SendFailedReason :: {send_message_failed, ReasonForSendFailure}, + ReasonForSendFailure :: term(), + ReplyNo :: pos_integer(). +-callback handle_trans_reply(ConnHandle, + ProtocolVersion, + UserReply, + ReplyData, + Extra) -> + ok when + ConnHandle :: conn_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + UserReply :: Success | Failure, + ReplyData :: term(), + Extra :: term(), + Success :: {ok, Result}, + Result :: TransactionResult | SegmentResult, + TransactionResult :: [megaco_encoder:action_reply()], + SegmentResult :: {megaco_encoder:segment_no(), + LastSegment, + [megaco_encoder:action_reply()]}, + Failure :: {error, Reason} | + {error, ReplyNo, Reason}, + Reason :: TransactionReason | + SegmentReason | + UserCancelReason | + SendReason | + OtherReason, + TransactionReason :: megaco_encoder:error_desc(), + SegmentReason :: {megaco_encoder:segment_no(), + LastSegment, + megaco_encoder:error_desc()}, + OtherReason :: timeout | + {segment_timeout, MissingSegments} | + exceeded_recv_pending_limit | term(), + LastSegment :: boolean(), + MissingSegments :: [megaco_encoder:segment_no()], + UserCancelReason :: {user_cancel, ReasonForUserCancel}, + ReasonForUserCancel :: term(), + SendReason :: SendCancelledReason | SendFailedReason, + SendCancelledReason :: {send_message_cancelled, + ReasonForSendCancel}, + ReasonForSendCancel :: term(), + SendFailedReason :: {send_message_failed, ReasonForSendFailure}, + ReasonForSendFailure :: term(), + ReplyNo :: pos_integer(). + + +-callback handle_trans_ack(ConnHandle, + ProtocolVersion, + AckStatus, + AckData) -> + ok when + ConnHandle :: conn_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + AckStatus :: ok | {error, Reason}, + AckData :: term(), + Reason :: UserCancelReason | SendReason | OtherReason, + UserCancelReason :: {user_cancel, ReasonForUserCancel}, + ReasonForUserCancel :: term(), + SendReason :: SendCancelledReason | SendFailedReason, + SendCancelledReason :: {send_message_cancelled, ReasonForSendCancel}, + ReasonForSendCancel :: term(), + SendFailedReason :: {send_message_failed, ReasonForSendFailure}, + ReasonForSendFailure :: term(), + OtherReason :: term(). +-callback handle_trans_ack(ConnHandle, + ProtocolVersion, + AckStatus, + AckData, + Extra) -> + ok when + ConnHandle :: conn_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + AckStatus :: ok | {error, Reason}, + AckData :: term(), + Extra :: term(), + Reason :: UserCancelReason | SendReason | OtherReason, + UserCancelReason :: {user_cancel, ReasonForUserCancel}, + ReasonForUserCancel :: term(), + SendReason :: SendCancelledReason | SendFailedReason, + SendCancelledReason :: {send_message_cancelled, ReasonForSendCancel}, + ReasonForSendCancel :: term(), + SendFailedReason :: {send_message_failed, ReasonForSendFailure}, + ReasonForSendFailure :: term(), + OtherReason :: term(). + +-callback handle_unexpected_trans(ConnHandle, ProtocolVersion, Trans) -> + ok when + ConnHandle :: conn_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + Trans :: megaco_encoder:transaction_pending() | + megaco_encoder:transaction_reply() | + megaco_encoder:transaction_response_ack(). +-callback handle_unexpected_trans(ConnHandle, ProtocolVersion, Trans, Extra) -> + ok when + ConnHandle :: conn_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + Trans :: megaco_encoder:transaction_pending() | + megaco_encoder:transaction_reply() | + megaco_encoder:transaction_response_ack(), + Extra :: term(). + +-callback handle_trans_request_abort(ConnHandle, + ProtocolVersion, + TransNo, + Pid) -> + ok when + ConnHandle :: conn_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + TransNo :: integer(), + Pid :: undefined | pid(). +-callback handle_trans_request_abort(ConnHandle, + ProtocolVersion, + TransNo, + Pid, + Extra) -> + ok when + ConnHandle :: conn_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + TransNo :: integer(), + Pid :: undefined | pid(), + Extra :: term(). + +-callback handle_segment_reply(ConnHandle, + ProtocolVersion, + TransNo, + SegNo, + SegCompl) -> + ok when + ConnHandle :: conn_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + TransNo :: integer(), + SegNo :: integer(), + SegCompl :: asn1_NOVALUE | 'NULL'. +-callback handle_segment_reply(ConnHandle, + ProtocolVersion, + TransNo, + SegNo, + SegCompl, + Extra) -> + ok when + ConnHandle :: conn_handle(), + ProtocolVersion :: megaco_encoder:protocol_version(), + TransNo :: integer(), + SegNo :: megaco_encoder:segment_no(), + SegCompl :: asn1_NOVALUE | 'NULL', + Extra :: term(). + +-optional_callbacks( + [ + %% The actual number of arguments to *all* functions, + %% depend of the user_args config option. + handle_connect/2, + handle_connect/3, + handle_disconnect/3, + handle_syntax_error/3, + handle_syntax_error/4, + handle_message_error/3, + handle_message_error/4, + handle_trans_request/3, + handle_trans_request/4, + handle_trans_long_request/3, + handle_trans_long_request/4, + handle_trans_reply/4, + handle_trans_reply/5, + handle_trans_ack/4, + handle_trans_ack/5, + handle_unexpected_trans/3, + handle_unexpected_trans/4, + handle_trans_request_abort/4, + handle_trans_request_abort/5, + handle_segment_reply/5, + handle_segment_reply/6 + ]). diff --git a/lib/megaco/src/engine/modules.mk b/lib/megaco/src/engine/modules.mk index b74a096e40..a7f82c1836 100644 --- a/lib/megaco/src/engine/modules.mk +++ b/lib/megaco/src/engine/modules.mk @@ -2,7 +2,7 @@ # %CopyrightBegin% # -# Copyright Ericsson AB 2001-2016. All Rights Reserved. +# Copyright Ericsson AB 2001-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. @@ -21,6 +21,7 @@ BEHAVIOUR_MODULES = \ megaco_edist_compress \ megaco_encoder \ + megaco_user \ megaco_transport MODULES = \ diff --git a/lib/megaco/src/text/megaco_text_gen_prev3a.hrl b/lib/megaco/src/text/megaco_text_gen_prev3a.hrl index db7507fd27..4f3c83c6c5 100644 --- a/lib/megaco/src/text/megaco_text_gen_prev3a.hrl +++ b/lib/megaco/src/text/megaco_text_gen_prev3a.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2017. 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. @@ -2841,6 +2841,7 @@ enc_integer(Val, _State, Min, Max) -> enc_list(List, State) -> enc_list(List, State, fun(_S) -> ?COMMA_INDENT(_S) end, false). +-dialyzer({nowarn_function, enc_list/4}). % Future compat enc_list([], _State, _SepEncoder, _NeedsSep) -> []; enc_list([{Elems, ElemEncoder} | Tail], State, SepEncoder, NeedsSep) -> diff --git a/lib/megaco/src/text/megaco_text_gen_prev3b.hrl b/lib/megaco/src/text/megaco_text_gen_prev3b.hrl index d5f29025c8..de64f8bbdf 100644 --- a/lib/megaco/src/text/megaco_text_gen_prev3b.hrl +++ b/lib/megaco/src/text/megaco_text_gen_prev3b.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2017. All Rights Reserved. +%% Copyright Ericsson AB 2005-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. @@ -2862,6 +2862,7 @@ enc_integer(Val, _State, Min, Max) -> enc_list(List, State) -> enc_list(List, State, fun(_S) -> ?COMMA_INDENT(_S) end, false). +-dialyzer({nowarn_function, enc_list/4}). % Future compat enc_list([], _State, _SepEncoder, _NeedsSep) -> []; enc_list([{Elems, ElemEncoder} | Tail], State, SepEncoder, NeedsSep) -> diff --git a/lib/megaco/src/text/megaco_text_gen_prev3c.hrl b/lib/megaco/src/text/megaco_text_gen_prev3c.hrl index 6452be25d0..f73318161f 100644 --- a/lib/megaco/src/text/megaco_text_gen_prev3c.hrl +++ b/lib/megaco/src/text/megaco_text_gen_prev3c.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2017. All Rights Reserved. +%% Copyright Ericsson AB 2005-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. @@ -3338,6 +3338,7 @@ enc_integer(Val, _State, Min, Max) -> enc_list(List, State) -> enc_list(List, State, fun(_S) -> ?COMMA_INDENT(_S) end, false). +-dialyzer({nowarn_function, enc_list/4}). % Future compat enc_list([], _State, _SepEncoder, _NeedsSep) -> []; enc_list([{Elems, ElemEncoder} | Tail], State, SepEncoder, NeedsSep) -> diff --git a/lib/megaco/src/text/megaco_text_gen_v1.hrl b/lib/megaco/src/text/megaco_text_gen_v1.hrl index 38a0f6fd6b..6da9ea94ff 100644 --- a/lib/megaco/src/text/megaco_text_gen_v1.hrl +++ b/lib/megaco/src/text/megaco_text_gen_v1.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2016. All Rights Reserved. +%% Copyright Ericsson AB 2000-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. @@ -2303,6 +2303,7 @@ enc_integer(Val, _State, Min, Max) -> enc_list(List, State) -> enc_list(List, State, fun(_S) -> ?COMMA_INDENT(_S) end, false). +-dialyzer({nowarn_function, enc_list/4}). % Future compat enc_list([], _State, _SepEncoder, _NeedsSep) -> []; enc_list([{Elems, ElemEncoder} | Tail], State, SepEncoder, NeedsSep) -> diff --git a/lib/megaco/src/text/megaco_text_gen_v2.hrl b/lib/megaco/src/text/megaco_text_gen_v2.hrl index d9443fb2e1..23afa85800 100644 --- a/lib/megaco/src/text/megaco_text_gen_v2.hrl +++ b/lib/megaco/src/text/megaco_text_gen_v2.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-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. @@ -2689,6 +2689,7 @@ enc_integer(Val, _State, Min, Max) -> enc_list(List, State) -> enc_list(List, State, fun(_S) -> ?COMMA_INDENT(_S) end, false). +-dialyzer({nowarn_function, enc_list/4}). % Future compat enc_list([], _State, _SepEncoder, _NeedsSep) -> []; enc_list([{Elems, ElemEncoder} | Tail], State, SepEncoder, NeedsSep) -> diff --git a/lib/megaco/src/text/megaco_text_gen_v3.hrl b/lib/megaco/src/text/megaco_text_gen_v3.hrl index dc1f2b9665..e440ec6651 100644 --- a/lib/megaco/src/text/megaco_text_gen_v3.hrl +++ b/lib/megaco/src/text/megaco_text_gen_v3.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-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. @@ -3353,6 +3353,7 @@ enc_integer(Val, _State, Min, Max) -> enc_list(List, State) -> enc_list(List, State, fun(_S) -> ?COMMA_INDENT(_S) end, false). +-dialyzer({nowarn_function, enc_list/4}). % Future compat enc_list([], _State, _SepEncoder, _NeedsSep) -> []; enc_list([{Elems, ElemEncoder} | Tail], State, SepEncoder, NeedsSep) -> diff --git a/lib/megaco/src/text/megaco_text_mini_parser.hrl b/lib/megaco/src/text/megaco_text_mini_parser.hrl index 487958af08..72d8168abf 100644 --- a/lib/megaco/src/text/megaco_text_mini_parser.hrl +++ b/lib/megaco/src/text/megaco_text_mini_parser.hrl @@ -1,7 +1,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. @@ -1167,6 +1167,7 @@ ensure_pathName({_TokenTag, _Line, Text}) -> % #'PropertyParm'{name = lists:reverse(Name), % value = [lists:reverse(Value)]}. +-dialyzer({nowarn_function, ensure_uint/3}). % Future compat ensure_uint({_TokenTag, Line, Val}, Min, Max) when is_integer(Val) -> if is_integer(Min) andalso (Val >= Min) -> diff --git a/lib/megaco/src/text/megaco_text_parser_prev3a.hrl b/lib/megaco/src/text/megaco_text_parser_prev3a.hrl index edda850c3a..d7e847993a 100644 --- a/lib/megaco/src/text/megaco_text_parser_prev3a.hrl +++ b/lib/megaco/src/text/megaco_text_parser_prev3a.hrl @@ -1,7 +1,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. @@ -1589,6 +1589,7 @@ ensure_uint(Token, Min, Max) -> -ifdef(megaco_parser_inline). -compile({inline,[{ensure_uint,4}]}). -endif. +-dialyzer({nowarn_function, ensure_uint/4}). % Future compat ensure_uint(Val, Min, Max, Line) -> if is_integer(Min) andalso (Val >= Min) -> diff --git a/lib/megaco/src/text/megaco_text_parser_prev3b.hrl b/lib/megaco/src/text/megaco_text_parser_prev3b.hrl index 4eaa3733c4..479f963c70 100644 --- a/lib/megaco/src/text/megaco_text_parser_prev3b.hrl +++ b/lib/megaco/src/text/megaco_text_parser_prev3b.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-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. @@ -1635,6 +1635,7 @@ ensure_uint(Token, Min, Max) -> -ifdef(megaco_parser_inline). -compile({inline,[{ensure_uint,4}]}). -endif. +-dialyzer({nowarn_function, ensure_uint/4}). % Future compat ensure_uint(Val, Min, Max, Line) -> if is_integer(Min) andalso (Val >= Min) -> diff --git a/lib/megaco/src/text/megaco_text_parser_prev3c.hrl b/lib/megaco/src/text/megaco_text_parser_prev3c.hrl index d2faad09d9..3ed9582308 100644 --- a/lib/megaco/src/text/megaco_text_parser_prev3c.hrl +++ b/lib/megaco/src/text/megaco_text_parser_prev3c.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-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. @@ -1898,6 +1898,7 @@ ensure_uint(Token, Min, Max) -> -ifdef(megaco_parser_inline). -compile({inline,[{ensure_uint,4}]}). -endif. +-dialyzer({nowarn_function, ensure_uint/4}). % Future compat ensure_uint(Val, Min, Max, Line) -> if is_integer(Min) andalso (Val >= Min) -> diff --git a/lib/megaco/src/text/megaco_text_parser_v1.hrl b/lib/megaco/src/text/megaco_text_parser_v1.hrl index f48ac745f0..3a19debba0 100644 --- a/lib/megaco/src/text/megaco_text_parser_v1.hrl +++ b/lib/megaco/src/text/megaco_text_parser_v1.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2016. All Rights Reserved. +%% Copyright Ericsson AB 2000-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. @@ -1337,6 +1337,7 @@ ensure_uint(Token, Min, Max) -> -ifdef(megaco_parser_inline). -compile({inline,[{ensure_uint,4}]}). -endif. +-dialyzer({nowarn_function, ensure_uint/4}). % Future compat ensure_uint(Val, Min, Max, Line) -> if is_integer(Min) andalso (Val >= Min) -> diff --git a/lib/megaco/src/text/megaco_text_parser_v2.hrl b/lib/megaco/src/text/megaco_text_parser_v2.hrl index f3c2f69193..270541d111 100644 --- a/lib/megaco/src/text/megaco_text_parser_v2.hrl +++ b/lib/megaco/src/text/megaco_text_parser_v2.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-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. @@ -1563,6 +1563,7 @@ ensure_uint(Token, Min, Max) -> -ifdef(megaco_parser_inline). -compile({inline,[{ensure_uint,4}]}). -endif. +-dialyzer({nowarn_function, ensure_uint/4}). % Future compat ensure_uint(Val, Min, Max, Line) -> if is_integer(Min) andalso (Val >= Min) -> diff --git a/lib/megaco/src/text/megaco_text_parser_v3.hrl b/lib/megaco/src/text/megaco_text_parser_v3.hrl index 38822e4952..7b8fff2080 100644 --- a/lib/megaco/src/text/megaco_text_parser_v3.hrl +++ b/lib/megaco/src/text/megaco_text_parser_v3.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-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. @@ -1920,6 +1920,7 @@ ensure_uint(Token, Min, Max) -> -ifdef(megaco_parser_inline). -compile({inline,[{ensure_uint,4}]}). -endif. +-dialyzer({nowarn_function, ensure_uint/4}). % Future compat ensure_uint(Val, Min, Max, Line) -> if is_integer(Min) andalso (Val >= Min) -> diff --git a/lib/megaco/test/Makefile b/lib/megaco/test/Makefile index 4ddd73eea1..b4e31765b8 100644 --- a/lib/megaco/test/Makefile +++ b/lib/megaco/test/Makefile @@ -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. @@ -102,6 +102,9 @@ endif ERL_COMPILE_FLAGS += $(MEGACO_ERL_COMPILE_FLAGS) +# We have a behaviour in the test catalog (megaco_test_generator) +ERL_COMPILE_FLAGS += -pa ../../megaco/test + ERL_PATH = -pa ../../megaco/examples/simple \ -pa ../../megaco/ebin \ -pa ../../et/ebin diff --git a/lib/megaco/test/megaco_SUITE.erl b/lib/megaco/test/megaco_SUITE.erl index 38590f9fee..f7b8ffe032 100644 --- a/lib/megaco/test/megaco_SUITE.erl +++ b/lib/megaco/test/megaco_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2016. All Rights Reserved. +%% Copyright Ericsson AB 2000-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. @@ -25,7 +25,21 @@ -module(megaco_SUITE). --compile(export_all). +-export([ + suite/0, + all/0, + groups/0, + + init_per_suite/1, + end_per_suite/1, + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2, + + t/0, t/1, + init/0 + ]). -include("megaco_test_lib.hrl"). -include_lib("megaco/include/megaco.hrl"). @@ -96,6 +110,20 @@ groups() -> {flex, [], [{megaco_flex_test, all}]}]. init_per_suite(Config) -> + io:format("~w:init_per_suite -> entry with" + "~n Config: ~p" + "~n OS Type: ~p" + "~n OS Version: ~s" + "~n", + [?MODULE, + Config, + os:type(), + case os:version() of + {Major, Minor, Release} -> + ?F("~w.~w.~w", [Major, Minor, Release]); + Str when is_list(Str) -> + Str + end]), Config. end_per_suite(_Config) -> diff --git a/lib/megaco/test/megaco_actions_test.erl b/lib/megaco/test/megaco_actions_test.erl index fcbe4f12fa..498e5c91cb 100644 --- a/lib/megaco/test/megaco_actions_test.erl +++ b/lib/megaco/test/megaco_actions_test.erl @@ -1,7 +1,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. @@ -20,13 +20,30 @@ %% %%---------------------------------------------------------------------- -%% Purpose: Verify that it is possible to separatelly encode +%% Purpose: Verify that it is possible to separately encode %% the action requests list. Do this with all codec's %% that supports partial encode. %%---------------------------------------------------------------------- -module(megaco_actions_test). --compile(export_all). +-export([ + all/0, + groups/0, + + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2, + + t/0, t/1, + + pretty_text/1, + flex_pretty_text/1, + compact_text/1, + flex_compact_text/1, + erl_dist/1, + erl_dist_mc/1 + ]). -include("megaco_test_lib.hrl"). -include_lib("megaco/include/megaco.hrl"). @@ -364,9 +381,6 @@ sleep(X) -> receive after X -> ok end. -error_msg(F,A) -> error_logger:error_msg(F ++ "~n",A). - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% i(F) -> @@ -376,8 +390,8 @@ i(F, A) -> print(info, get(verbosity), "", F, A). -d(F) -> - d(F, []). +%% d(F) -> +%% d(F, []). d(F, A) -> print(debug, get(verbosity), "DBG: ", F, A). @@ -391,20 +405,10 @@ print(Severity, Verbosity, P, F, A) -> print(printable(Severity,Verbosity), P, F, A). print(true, P, F, A) -> - io:format("~s~p:~s: " ++ F ++ "~n", [P, self(), get(sname) | A]); + io:format("*** [~s] ~s ~p ~s ***" + "~n " ++ F ++ "~n", + [?FTS(), P, self(), get(sname) | A]); print(_, _, _, _) -> ok. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -random_init() -> - {A,B,C} = now(), - random:seed(A,B,C). - -random() -> - 10 * random:uniform(50). - -apply_load_timer() -> - erlang:send_after(random(), self(), apply_load_timeout). - diff --git a/lib/megaco/test/megaco_app_test.erl b/lib/megaco/test/megaco_app_test.erl index 981d93f5dd..fff2d8c7d4 100644 --- a/lib/megaco/test/megaco_app_test.erl +++ b/lib/megaco/test/megaco_app_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2016. All Rights Reserved. +%% Copyright Ericsson AB 2002-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. @@ -20,29 +20,42 @@ %%---------------------------------------------------------------------- %% Purpose: Verify the application specifics of the Megaco application %%---------------------------------------------------------------------- + -module(megaco_app_test). --compile(export_all). +-export([ + all/0, + + app/0, app/1, + appup/0, appup/1 + ]). -include_lib("common_test/include/ct.hrl"). + %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- + all() -> [ app, appup ]. + %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- + app() -> [{doc, "Test that the megaco app file is ok"}]. app(Config) when is_list(Config) -> ok = test_server:app_test(megaco). + + %%-------------------------------------------------------------------- + appup() -> [{doc, "Test that the megaco appup file is ok"}]. appup(Config) when is_list(Config) -> diff --git a/lib/megaco/test/megaco_appup_test.erl b/lib/megaco/test/megaco_appup_test.erl index 8dc3ad51a0..a06d274844 100644 --- a/lib/megaco/test/megaco_appup_test.erl +++ b/lib/megaco/test/megaco_appup_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2016. All Rights Reserved. +%% Copyright Ericsson AB 2002-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,8 +22,23 @@ %%---------------------------------------------------------------------- -module(megaco_appup_test). --compile(export_all). --compile({no_auto_import,[error/1]}). +-export([ + all/0, + groups/0, + + init_per_suite/1, + end_per_suite/1, + + init_per_group/2, + end_per_group/2, + + init_per_testcase/2, + end_per_testcase/2, + + appup_file/1 + ]). + +-compile({no_auto_import, [error/1]}). -include_lib("common_test/include/ct.hrl"). -include("megaco_test_lib.hrl"). @@ -76,6 +91,10 @@ end_per_testcase(_Case, Config) when is_list(Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Perform a simple check of the appup file +appup_file(suite) -> + []; +appup_file(doc) -> + ["Perform a simple check of the appup file"]; appup_file(Config) when is_list(Config) -> ok = ?t:appup_test(megaco). diff --git a/lib/megaco/test/megaco_call_flow_test.erl b/lib/megaco/test/megaco_call_flow_test.erl index eb4574862d..03caf705ba 100644 --- a/lib/megaco/test/megaco_call_flow_test.erl +++ b/lib/megaco/test/megaco_call_flow_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2016. All Rights Reserved. +%% Copyright Ericsson AB 2000-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. @@ -38,7 +38,85 @@ -module(megaco_call_flow_test). --compile(export_all). +-export([ + all/0, + groups/0, + + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2, + + pretty/1, + compact/1, + pretty_flex/1, + compact_flex/1, + bin/1, + ber/1, + per/1, + standard_erl/1, + compressed_erl/1, + + t/0, t/1 + + ]). + +-export([ + msg1/0, msg1/1, + msg2/0, msg2/1, + msg3/0, msg3/1, + msg4/0, msg4/1, + msg5a/0, msg5a/1, + msg5b/0, msg5b/1, + msg6/0, msg6/1, + msg7/0, msg7/1, + msg9/0, msg9/1, + msg10/0, msg10/1, + msg11/0, msg11/1, + msg12/0, msg12/1, + msg13/0, msg13/1, + msg14/0, msg14/1, + msg15/0, msg15/1, + msg16/0, msg16/1, + msg17a/0, msg17a/1, + msg17b/0, msg17b/1, + msg18a/0, msg18a/1, + msg18b/0, msg18b/1, + msg18c/0, msg18c/1, + msg18d/0, msg18d/1, + msg19a/0, msg19a/1, + msg19b/0, msg19b/1, + msg20/0, msg20/1, + msg21/0, msg21/1, + msg22a/0, msg22a/1, + msg22b/0, msg22b/1, + msg23a/0, msg23a/1, + msg23b/0, msg23b/1 + + ]). + +-export([ + encoders/0, + msg_sizes/0, + coding_times/0, + encoding_times/0, + decoding_times/0, + coding_times_stat/0, + encoding_times_stat/0, + decoding_times_stat/0, + size_stat/0, + gnuplot_gif/0, + gnuplot_size_gif/0, + + gen_byte_msg/2, + gen_header_file_binary/1, + gen_ber_header/0, + gen_ber_bin_header/0, + gen_per_header/0, + single_meter/4, + count/2 + ]). + -include_lib("megaco/include/megaco.hrl"). -include_lib("megaco/include/megaco_message_v1.hrl"). -include("megaco_test_lib.hrl"). @@ -53,16 +131,20 @@ init_per_testcase(Case, Config) -> end_per_testcase(Case, Config) -> megaco_test_lib:end_per_testcase(Case, Config). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Top test case all() -> - [{group, text}, {group, binary}]. + [{group, text}, {group, binary}, {group, erl}]. groups() -> - [{text, [], [pretty, compact]}, - {flex, [], [pretty_flex, compact_flex]}, - {binary, [], [bin, ber, per]}]. + [ + {text, [], [pretty, compact]}, + {flex, [], [pretty_flex, compact_flex]}, + {binary, [], [bin, ber, per]}, + {erl, [], [standard_erl, compressed_erl]} + ]. init_per_group(_GroupName, Config) -> Config. diff --git a/lib/megaco/test/megaco_codec_test.erl b/lib/megaco/test/megaco_codec_test.erl index 007136f83e..0dfbabcc81 100644 --- a/lib/megaco/test/megaco_codec_test.erl +++ b/lib/megaco/test/megaco_codec_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-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. @@ -25,7 +25,18 @@ -module(megaco_codec_test). --compile(export_all). +-export([ + all/0, + groups/0, + + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2, + + t/0, t/1, + init/0 + ]). -include("megaco_test_lib.hrl"). -include_lib("megaco/include/megaco.hrl"). diff --git a/lib/megaco/test/megaco_codec_test_lib.erl b/lib/megaco/test/megaco_codec_test_lib.erl index 6eee5caaaa..7a3d4e6cf8 100644 --- a/lib/megaco/test/megaco_codec_test_lib.erl +++ b/lib/megaco/test/megaco_codec_test_lib.erl @@ -1,7 +1,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. @@ -993,15 +993,15 @@ expect_exec([#expect_instruction{description = Desc, skip({What, Why}) when is_atom(What) andalso is_list(Why) -> Reason = lists:flatten(io_lib:format("~p: ~s", [What, Why])), - exit({skipped, Reason}); + ?SKIP(Reason); skip({What, Why}) -> Reason = lists:flatten(io_lib:format("~p: ~p", [What, Why])), - exit({skipped, Reason}); + ?SKIP(Reason); skip(Reason) when is_list(Reason) -> - exit({skipped, Reason}); + ?SKIP(Reason); skip(Reason1) -> Reason2 = lists:flatten(io_lib:format("~p", [Reason1])), - exit({skipped, Reason2}). + ?SKIP(Reason2). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/megaco/test/megaco_config_test.erl b/lib/megaco/test/megaco_config_test.erl index 02e06a722a..d46806927a 100644 --- a/lib/megaco/test/megaco_config_test.erl +++ b/lib/megaco/test/megaco_config_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2016. All Rights Reserved. +%% Copyright Ericsson AB 2000-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. @@ -25,7 +25,24 @@ -module(megaco_config_test). --compile(export_all). +-export([ + all/0, + groups/0, + + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2, + + config/1, + transaction_id_counter_mg/1, + transaction_id_counter_mgc/1, + otp_7216/1, + otp_8167/1, + otp_8183/1, + + t/0, t/1 + ]). -include("megaco_test_lib.hrl"). -include_lib("megaco/include/megaco.hrl"). @@ -37,6 +54,19 @@ t(Case) -> megaco_test_lib:t({?MODULE, Case}). min(M) -> timer:minutes(M). %% Test server callbacks +init_per_testcase(Case, Config) when (Case =:= otp_7216) orelse + (Case =:= otp_8167) orelse + (Case =:= otp_8183) -> + i("try starting megaco_config"), + case megaco_config:start_link() of + {ok, _} -> + C = lists:keydelete(tc_timeout, 1, Config), + do_init_per_testcase(Case, [{tc_timeout, min(3)}|C]); + {error, Reason} -> + i("Failed starting megaco_config: " + "~n ~p", [Reason]), + {skip, ?F("Failed starting config: ~p", [Reason])} + end; init_per_testcase(Case, Config) -> C = lists:keydelete(tc_timeout, 1, Config), do_init_per_testcase(Case, [{tc_timeout, min(3)}|C]). @@ -45,6 +75,12 @@ do_init_per_testcase(Case, Config) -> process_flag(trap_exit, true), megaco_test_lib:init_per_testcase(Case, Config). +end_per_testcase(Case, Config) when (Case =:= otp_7216) orelse + (Case =:= otp_8167) orelse + (Case =:= otp_8183) -> + (catch megaco_config:stop()), + process_flag(trap_exit, false), + megaco_test_lib:end_per_testcase(Case, Config); end_per_testcase(Case, Config) -> process_flag(trap_exit, false), megaco_test_lib:end_per_testcase(Case, Config). @@ -60,14 +96,17 @@ end_per_testcase(Case, Config) -> %% Top test case all() -> - [config, {group, transaction_id_counter}, - {group, tickets}]. + [ + config, + {group, transaction_id_counter}, + {group, tickets} + ]. groups() -> - [{transaction_id_counter, [], - [transaction_id_counter_mg, - transaction_id_counter_mgc]}, - {tickets, [], [otp_7216, otp_8167, otp_8183]}]. + [ + {transaction_id_counter, [], transaction_id_counter_cases()}, + {tickets, [], tickets_cases()} + ]. init_per_group(_GroupName, Config) -> Config. @@ -75,6 +114,19 @@ init_per_group(_GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. +transaction_id_counter_cases() -> + [ + transaction_id_counter_mg, + transaction_id_counter_mgc + ]. + +tickets_cases() -> + [ + otp_7216, + otp_8167, + otp_8183 + ]. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Config test case @@ -736,9 +788,6 @@ otp_7216(Config) when is_list(Config) -> put(tc, otp_7216), p("start"), - p("start the megaco config process"), - megaco_config:start_link(), - LocalMid1 = {deviceName, "local-mid-1"}, %% LocalMid2 = {deviceName, "local-mid-2"}, RemoteMid1 = {deviceName, "remote-mid-1"}, @@ -859,9 +908,6 @@ otp_8167(Config) when is_list(Config) -> put(tc, otp8167), p("start"), - p("start the megaco config process"), - megaco_config:start_link(), - LocalMid1 = {deviceName, "local-mid-1"}, LocalMid2 = {deviceName, "local-mid-2"}, RemoteMid1 = {deviceName, "remote-mid-1"}, @@ -981,9 +1027,6 @@ otp_8183(Config) when is_list(Config) -> put(tc, otp8183), p("start"), - p("start the megaco config process"), - megaco_config:start_link(), - LocalMid1 = {deviceName, "local-mid-1"}, LocalMid2 = {deviceName, "local-mid-2"}, RemoteMid1 = {deviceName, "remote-mid-1"}, @@ -1122,28 +1165,18 @@ i(F) -> i(F, []). i(F, A) -> - print(info, get(verbosity), now(), get(tc), "INF", F, A). + print(info, get(verbosity), get(tc), "INF", F, A). printable(_, debug) -> true; printable(info, info) -> true; printable(_,_) -> false. -print(Severity, Verbosity, Ts, Tc, P, F, A) -> - print(printable(Severity,Verbosity), Ts, Tc, P, F, A). +print(Severity, Verbosity, Tc, P, F, A) -> + print(printable(Severity,Verbosity), Tc, P, F, A). -print(true, Ts, Tc, P, F, A) -> +print(true, Tc, P, F, A) -> io:format("*** [~s] ~s ~p ~s:~w ***" "~n " ++ F ++ "~n", - [format_timestamp(Ts), P, self(), get(sname), Tc | A]); -print(_, _, _, _, _, _) -> + [?FTS(), P, self(), get(sname), Tc | A]); +print(_, _, _, _, _) -> ok. - -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), - lists:flatten(FormatDate). - diff --git a/lib/megaco/test/megaco_digit_map_test.erl b/lib/megaco/test/megaco_digit_map_test.erl index 998e829b67..e03d38497c 100644 --- a/lib/megaco/test/megaco_digit_map_test.erl +++ b/lib/megaco/test/megaco_digit_map_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% Copyright Ericsson AB 2005-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,7 +24,27 @@ %%---------------------------------------------------------------------- -module(megaco_digit_map_test). --compile(export_all). +-export([ + all/0, + groups/0, + + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2, + + otp_5750_01/1, + otp_5750_02/1, + otp_5799_01/1, + otp_5826_01/1, + otp_5826_02/1, + otp_5826_03/1, + otp_7449_1/1, + otp_7449_2/1, + + t/0, t/1 + ]). + -include("megaco_test_lib.hrl"). @@ -44,16 +64,18 @@ end_per_testcase(Case, Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% all() -> - [{group, tickets}]. + [ + {group, tickets} + ]. groups() -> - [{tickets, [], - [{group, otp_5750}, {group, otp_5799}, - {group, otp_5826}, {group, otp_7449}]}, - {otp_5750, [], [otp_5750_01, otp_5750_02]}, - {otp_5799, [], [otp_5799_01]}, - {otp_5826, [], [otp_5826_01, otp_5826_02, otp_5826_03]}, - {otp_7449, [], [otp_7449_1, otp_7449_2]}]. + [ + {tickets, [], tickets_cases()}, + {otp_5750, [], otp_5750_cases()}, + {otp_5799, [], otp_5799_cases()}, + {otp_5826, [], otp_5826_cases()}, + {otp_7449, [], otp_7449_cases()} + ]. init_per_group(_GroupName, Config) -> Config. @@ -62,6 +84,38 @@ end_per_group(_GroupName, Config) -> Config. +tickets_cases() -> + [ + {group, otp_5750}, + {group, otp_5799}, + {group, otp_5826}, + {group, otp_7449} + ]. + +otp_5750_cases() -> + [ + otp_5750_01, + otp_5750_02 + ]. + +otp_5799_cases() -> + [ + otp_5799_01 + ]. + +otp_5826_cases() -> + [ + otp_5826_01, + otp_5826_02, + otp_5826_03 + ]. + +otp_7449_cases() -> + [ + otp_7449_1, + otp_7449_2 + ]. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/megaco/test/megaco_examples_test.erl b/lib/megaco/test/megaco_examples_test.erl index 45a6c5011a..fdf9fe29ff 100644 --- a/lib/megaco/test/megaco_examples_test.erl +++ b/lib/megaco/test/megaco_examples_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2016. All Rights Reserved. +%% Copyright Ericsson AB 2001-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. @@ -25,12 +25,27 @@ -module(megaco_examples_test). --compile(export_all). +-export([ + all/0, + groups/0, + + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2, + + simple/1, + + t/0, t/1 + ]). + -include("megaco_test_lib.hrl"). -include_lib("megaco/include/megaco.hrl"). -include_lib("megaco/include/megaco_message_v1.hrl"). +-define(LIB, megaco_test_lib). + t() -> megaco_test_lib:t(?MODULE). t(Case) -> megaco_test_lib:t({?MODULE, Case}). @@ -56,9 +71,19 @@ load_examples() -> {error, Reason} -> {error, Reason}; Dir -> - [code:load_abs(filename:join([Dir, examples, simple, M])) || M <- example_modules()] + SimpleDir = filename:join([Dir, examples, simple]), + case code:add_path(SimpleDir) of + true -> + ok; + {error, What} -> + error_logger:error_msg("failed adding examples path: " + "~n ~p" + "~n", [What]), + {error, {failed_add_path, What}} + end end. + purge_examples() -> case code:lib_dir(megaco) of {error, Reason} -> @@ -67,6 +92,7 @@ purge_examples() -> [code:purge(M) || M <- example_modules()] end. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Top test case @@ -87,94 +113,263 @@ end_per_group(_GroupName, Config) -> simple(suite) -> []; simple(Config) when is_list(Config) -> - ?ACQUIRE_NODES(1, Config), - d("simple -> proxy start",[]), - ProxyPid = megaco_test_lib:proxy_start({?MODULE, ?LINE}), + process_flag(trap_exit, true), + d("simple -> create node name(s)"), + [_Local, MGC, MG] = ?LIB:mk_nodes(3), %% Grrr + Nodes = [MGC, MG], - d("simple -> start megaco",[]), - ?VERIFY(ok, megaco:start()), + d("simple -> start nodes"), + ok = ?LIB:start_nodes(Nodes, ?MODULE, ?LINE), - d("simple -> start mgc",[]), - ?APPLY(ProxyPid, fun() -> megaco_simple_mgc:start() end), + MGCId = "MGC", + MGId = "MG", + + d("simple -> MGC proxy start (on ~p)", [MGC]), + MGCProxy = megaco_test_lib:proxy_start(MGC, "MGC"), + ?SLEEP(1000), + + d("simple -> MG proxy start (on ~p)", [MG]), + MGProxy = megaco_test_lib:proxy_start(MG, "MG"), + ?SLEEP(1000), + + MegacoStart = fun() -> megaco:start() end, + MegacoStartVerify = + fun(_, ok) -> ok; + (Id, Else) -> ?ERROR({failed_starting_megaco, Id, Else}) + end, + + d("simple -> start MGC megaco"), + exec(MGCProxy, MGCId, MegacoStart, + fun(Res) -> MegacoStartVerify(MGCId, Res) end), + %% ?APPLY(MGCProxy, fun() -> ok = megaco:start() end), + ?SLEEP(1000), + + d("simple -> start MG megaco"), + exec(MGProxy, MGId, MegacoStart, + fun(Res) -> MegacoStartVerify(MGId, Res) end), + %% ?APPLY(MGProxy, fun() -> ok = megaco:start() end), + ?SLEEP(1000), + + d("simple -> start mgc"), + start_mgc(MGCProxy), + ?SLEEP(1000), + + d("simple -> verify MGC info (no mg)"), + info(MGCProxy), + ?SLEEP(1000), + + d("simple -> verify MGC system_info(users) (no mg)"), + users(MGCProxy), + ?SLEEP(1000), + + d("simple -> start mg"), + start_mg(MGProxy), + ?SLEEP(1000), + + d("simple -> verify MGC info (mg)"), + info(MGCProxy), + ?SLEEP(1000), + + d("simple -> verify MGC system_info(users) (mg)"), + users(MGCProxy), + ?SLEEP(1000), + + d("simple -> verify MG info"), + info(MGProxy), + ?SLEEP(1000), + + d("simple -> verify MG system_info(users)"), + users(MGProxy), + ?SLEEP(1000), + + d("simple -> stop mgc"), + exec(MGCProxy, MGCId, + fun() -> megaco_simple_mgc:stop() end, + fun([_]) -> ok; + (L) when is_list(L) -> + ?ERROR({invalid_users, L}); + (X) -> + ?ERROR({invalid_result, X}) + end), + %% ?VERIFY(5, length()), + ?SLEEP(1000), + + d("simple -> verify MGC info (no mgc)"), + info(MGCProxy), + ?SLEEP(1000), + + d("simple -> verify MG info (no mgc)"), + info(MGProxy), + ?SLEEP(1000), + + d("simple -> verify MGC system_info(users) (no mgc)",[]), + users(MGCProxy), + ?SLEEP(1000), + + d("simple -> verify MG system_info(users) (no mgc)",[]), + users(MGProxy), + ?SLEEP(1000), + + MegacoStop = fun() -> megaco:stop() end, + MegacoStopVerify = + fun(_, ok) -> ok; + (Id, Else) -> ?ERROR({failed_stop_megaco, Id, Else}) + end, + + d("simple -> stop MG megaco",[]), + exec(MGProxy, MGId, MegacoStop, + fun(Res) -> MegacoStopVerify(MGId, Res) end), + %% ?VERIFY(ok, megaco:stop()), + ?SLEEP(1000), + + d("simple -> stop MGC megaco",[]), + exec(MGCProxy, MGCId, MegacoStop, + fun(Res) -> MegacoStopVerify(MGCId, Res) end), + %% ?VERIFY(ok, megaco:stop()), + ?SLEEP(1000), + + d("simple -> kill (exit) MG Proxy: ~p", [MGProxy]), + MGProxy ! {stop, self(), normal}, + receive + {'EXIT', MGProxy, _} -> + d("simple -> MG Proxy terminated"), + ok + end, + + d("simple -> kill (exit) MGC Proxy: ~p", [MGCProxy]), + MGCProxy ! {stop, self(), normal}, + receive + {'EXIT', MGCProxy, _} -> + d("simple -> MGC Proxy terminated"), + ok + end, + + d("simple -> stop ~p", [MGC]), + slave:stop(MGC), + + d("simple -> stop ~p", [MG]), + slave:stop(MG), + + d("simple -> done", []), + ok. + + +exec(Proxy, Id, Cmd, Verify) -> + ?APPLY(Proxy, Cmd), + receive + {res, Id, Res} -> + Verify(Res) + end. + + +start_mgc(Proxy) -> + ?APPLY(Proxy, + fun() -> + try megaco_simple_mgc:start() of + Res -> + Res + catch + C:E:S -> + {error, {{catched, C, E, S}}, code:get_path()} + end + end), receive {res, _, {ok, MgcAll}} when is_list(MgcAll) -> MgcBad = [MgcRes || MgcRes <- MgcAll, element(1, MgcRes) /= ok], ?VERIFY([], MgcBad), - %% MgcGood = MgcAll -- MgcBad, - %% MgcRecHandles = [MgcRH || {ok, _MgcPort, MgcRH} <- MgcGood], - - d("simple -> start mg",[]), - ?APPLY(ProxyPid, fun() -> megaco_simple_mg:start() end), - receive - {res, _, MgList} when is_list(MgList) andalso (length(MgList) =:= 4) -> - d("simple -> received res: ~p",[MgList]), - Verify = - fun({_MgMid, {TransId, Res}}) when TransId =:= 1 -> - case Res of - {ok, [AR]} when is_record(AR, 'ActionReply') -> - case AR#'ActionReply'.commandReply of - [{serviceChangeReply, SCR}] -> - case SCR#'ServiceChangeReply'.serviceChangeResult of - {serviceChangeResParms, MgcMid} when MgcMid /= asn1_NOVALUE -> - ok; - Error -> - ?ERROR(Error) - end; - Error -> - ?ERROR(Error) - end; - Error -> - ?ERROR(Error) - end; - (Error) -> - ?ERROR(Error) - end, - lists:map(Verify, MgList); - Error -> - ?ERROR(Error) - end; + ok; Error -> ?ERROR(Error) - end, - d("simple -> verify info()",[]), - info(), - d("simple -> verify system_info(users)",[]), - users(), - d("simple -> stop mgc",[]), - ?VERIFY(5, length(megaco_simple_mgc:stop())), - d("simple -> verify system_info(users)",[]), - users(), - d("simple -> stop megaco",[]), - ?VERIFY(ok, megaco:stop()), - d("simple -> kill (exit) ProxyPid: ~p",[ProxyPid]), - exit(ProxyPid, shutdown), % Controlled kill of transport supervisors + end. - ok. +start_mg(Proxy) -> + ?APPLY(Proxy, fun() -> + try megaco_simple_mg:start() of + Res -> + Res + catch + C:E:S -> + {error, {{catched, C, E, S}}, code:get_path()} + end + end), + receive + {res, _, MGs} when is_list(MGs) andalso (length(MGs) =:= 4) -> + verify_mgs(MGs); + Error -> + ?ERROR(Error) + end. -info() -> - case (catch megaco:info()) of - {'EXIT', _} = Error -> - ?ERROR(Error); - Info -> - ?LOG("Ok, ~p~n", [Info]) +verify_mgs(MGs) -> + Verify = + fun({_MgMid, {TransId, Res}}) when (TransId =:= 1) -> + case Res of + {ok, [AR]} when is_record(AR, 'ActionReply') -> + case AR#'ActionReply'.commandReply of + [{serviceChangeReply, SCR}] -> + case SCR#'ServiceChangeReply'.serviceChangeResult of + {serviceChangeResParms, MgcMid} + when (MgcMid =/= asn1_NOVALUE) -> + ok; + Error -> + ?ERROR(Error) + end; + Error -> + ?ERROR(Error) + end; + Error -> + ?ERROR(Error) + end; + (Error) -> + ?ERROR(Error) + end, + lists:map(Verify, MGs). + + +info(Proxy) -> + ?APPLY(Proxy, + fun() -> + try megaco:info() of + I -> I + catch + C:E:S -> + {error, {C, E, S}} + end + end), + receive + {res, _, Info} when is_list(Info) -> + ?LOG("Ok, ~p~n", [Info]); + {res, _, Error} -> + ?ERROR(Error) end. -users() -> - case (catch megaco:system_info(users)) of - {'EXIT', _} = Error -> - ?ERROR(Error); - Users -> - ?LOG("Ok, ~p~n", [Users]) + +users(Proxy) -> + ?APPLY(Proxy, + fun() -> + try megaco:system_info(users) of + I -> I + catch + C:E:S -> + {error, {C, E, S}} + end + end), + receive + {res, _, Info} when is_list(Info) -> + ?LOG("Ok, ~p~n", [Info]); + {res, _, Error} -> + ?ERROR(Error) end. -d(F,A) -> - d(get(dbg),F,A). +d(F) -> + d(F, []). +d(F, A) -> + d(get(dbg), F, A). -d(true,F,A) -> - io:format("DBG: " ++ F ++ "~n",A); +d(true, F, A) -> + io:format("DBG: ~s " ++ F ++ "~n", [?FTS() | A]); d(_, _F, _A) -> ok. diff --git a/lib/megaco/test/megaco_flex_test.erl b/lib/megaco/test/megaco_flex_test.erl index 999d1abc6c..27c46a3b47 100644 --- a/lib/megaco/test/megaco_flex_test.erl +++ b/lib/megaco/test/megaco_flex_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-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. @@ -219,18 +219,5 @@ p(F, A) -> TC = get(tc), io:format("*** [~s] ~p ~w ***" "~n " ++ F ++ "~n", - [formated_timestamp(), self(), TC | A]). - -formated_timestamp() -> - format_timestamp(erlang:now()). - -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY, MM, DD} = Date, - {Hour, Min, Sec} = Time, - FormatDate = - io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), - lists:flatten(FormatDate). - + [?FTS(), self(), TC | A]). diff --git a/lib/megaco/test/megaco_load_test.erl b/lib/megaco/test/megaco_load_test.erl index 511e5a2e8e..9cce9e70ce 100644 --- a/lib/megaco/test/megaco_load_test.erl +++ b/lib/megaco/test/megaco_load_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-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,7 +24,33 @@ %%---------------------------------------------------------------------- -module(megaco_load_test). --compile(export_all). +-export([ + all/0, + groups/0, + + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2, + + single_user_light_load/1, + single_user_medium_load/1, + single_user_heavy_load/1, + single_user_extreme_load/1, + + multi_user_light_load/1, + multi_user_medium_load/1, + multi_user_heavy_load/1, + multi_user_extreme_load/1, + + t/0, t/1 + ]). + +-export([ + do_multi_load/3, + multi_load_collector/7 + ]). + -include("megaco_test_lib.hrl"). -include_lib("megaco/include/megaco.hrl"). @@ -66,8 +92,6 @@ t() -> megaco_test_lib:t(?MODULE). t(Case) -> megaco_test_lib:t({?MODULE, Case}). -min(M) -> timer:minutes(M). - %% Test server callbacks init_per_testcase(single_user_light_load = Case, Config) -> C = lists:keydelete(tc_timeout, 1, Config), @@ -108,15 +132,33 @@ end_per_testcase(Case, Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% all() -> - [single_user_light_load, - single_user_medium_load, single_user_heavy_load, - single_user_extreme_load, multi_user_light_load, - multi_user_medium_load, multi_user_heavy_load, - multi_user_extreme_load]. + [ + {group, single}, + {group, multi} + ]. groups() -> - []. - + [ + {single, [], single_cases()}, + {multi, [], multi_cases()} + ]. + +single_cases() -> + [ + single_user_light_load, + single_user_medium_load, + single_user_heavy_load, + single_user_extreme_load + ]. + +multi_cases() -> + [ + multi_user_light_load, + multi_user_medium_load, + multi_user_heavy_load, + multi_user_extreme_load + ]. + init_per_group(_GroupName, Config) -> Config. @@ -326,6 +368,17 @@ load_controller(Config, Fun) when is_list(Config) and is_function(Fun) -> d("load_controller -> " "loader [~p] terminated with ok~n", [Loader]), ok; + {'EXIT', Loader, {skipped, {fatal, Reason, File, Line}}} -> + i("load_controller -> " + "loader [~p] terminated with fatal skip" + "~n Reason: ~p" + "~n At: ~p:~p", [Loader, Reason, File, Line]), + ?SKIP(Reason); + {'EXIT', Loader, {skipped, Reason}} -> + i("load_controller -> " + "loader [~p] terminated with skip" + "~n Reason: ~p", [Loader, Reason]), + ?SKIP(Reason); {'EXIT', Loader, Reason} -> i("load_controller -> " "loader [~p] terminated with" @@ -629,14 +682,6 @@ make_mids([MgNode|MgNodes], Mids) -> exit("Test node must be started with '-sname'") end. -tim() -> - {A,B,C} = erlang:now(), - A*1000000000+B*1000+(C div 1000). - -sleep(X) -> receive after X -> ok end. - -error_msg(F,A) -> error_logger:error_msg(F ++ "~n",A). - maybe_display_system_info(NumLoaders) when NumLoaders > 50 -> [{display_system_info, timer:seconds(2)}]; maybe_display_system_info(NumLoaders) when NumLoaders > 10 -> @@ -647,50 +692,32 @@ maybe_display_system_info(_) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +min(M) -> timer:minutes(M). + i(F) -> i(F, []). i(F, A) -> - print(info, get(verbosity), now(), get(tc), "INF", F, A). + print(info, get(verbosity), get(tc), "INF", F, A). d(F) -> d(F, []). d(F, A) -> - print(debug, get(verbosity), now(), get(tc), "DBG", F, A). + print(debug, get(verbosity), get(tc), "DBG", F, A). printable(_, debug) -> true; printable(info, info) -> true; printable(_,_) -> false. -print(Severity, Verbosity, Ts, Tc, P, F, A) -> - print(printable(Severity,Verbosity), Ts, Tc, P, F, A). +print(Severity, Verbosity, Tc, P, F, A) -> + print(printable(Severity,Verbosity), Tc, P, F, A). -print(true, Ts, Tc, P, F, A) -> +print(true, Tc, P, F, A) -> io:format("*** [~s] ~s ~p ~s:~w ***" "~n " ++ F ++ "~n", - [format_timestamp(Ts), P, self(), get(sname), Tc | A]); -print(_, _, _, _, _, _) -> + [?FTS(), P, self(), get(sname), Tc | A]); +print(_, _, _, _, _) -> ok. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -random_init() -> - {A,B,C} = now(), - random:seed(A,B,C). - -random() -> - 10 * random:uniform(50). - -apply_load_timer() -> - erlang:send_after(random(), self(), apply_load_timeout). - -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), - lists:flatten(FormatDate). diff --git a/lib/megaco/test/megaco_mess_test.erl b/lib/megaco/test/megaco_mess_test.erl index 7af6f26bf1..3fd39a9e58 100644 --- a/lib/megaco/test/megaco_mess_test.erl +++ b/lib/megaco/test/megaco_mess_test.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. @@ -281,6 +281,8 @@ -define(VERSION, 1). +-define(USER_MOD, megaco_mess_user_test). + -define(TEST_VERBOSITY, debug). -define(MGC_VERBOSITY, debug). -define(MG_VERBOSITY, debug). @@ -303,26 +305,48 @@ -define(MG_NOTIF_RAR(Pid), megaco_test_mg:notify_request_and_reply(Pid)). -define(SEND(Expr), - ?VERIFY(ok, megaco_mess_user_test:apply_proxy(fun() -> Expr end))). + ?VERIFY(ok, ?USER_MOD:apply_proxy(fun() -> Expr end))). -define(USER(Expected, Reply), - megaco_mess_user_test:reply(?MODULE, - ?LINE, - fun(Actual) -> - case ?VERIFY(Expected, Actual) of - Expected -> {ok, Reply}; - UnExpected -> {error, {reply_verify, - ?MODULE, - ?LINE, - UnExpected}} - end - end)). - -%% t() -> megaco_test_lib:t(?MODULE). -%% t(Case) -> megaco_test_lib:t({?MODULE, Case}). - - -min(M) -> timer:minutes(M). + ?USER_MOD:reply(?MODULE, + ?LINE, + fun(Actual) -> + case ?VERIFY(Expected, Actual) of + Expected -> {ok, Reply}; + UnExpected -> {error, {reply_verify, + ?MODULE, + ?LINE, + UnExpected}} + end + end)). + +%% Some generator (utility) macros +-define(GM_START(), megaco_start). +-define(GM_STOP(), megaco_stop). +-define(GM_START_USER(M, RI, C), {megaco_start_user, M, RI, C}). +-define(GM_START_USER(M, RI), ?GM_START_USER(M, RI, [])). +-define(GM_STOP_USER(), megaco_stop_user). +-define(GMSI(I), {megaco_system_info, I}). +-define(GMSI_USERS(), ?GMSI(users)). +-define(GMSI_CONNS(), ?GMSI(connections)). +-define(GMCAST(Reqs, Opts), {megaco_cast, Reqs, Opts}). +-define(GMCAST(Reqs), ?GMCAST(Reqs, [])). +-define(GMCB(CB, VF), {megaco_callback, CB, VF}). +-define(GMCB_CONNECT(VF), ?GMCB(handle_connect, VF)). +-define(GMCB_TRANS_REP(VF), ?GMCB(handle_trans_reply, VF)). +-define(GMT(T), {megaco_trace, T}). +-define(GMT_ENABLE(), ?GMT(enable)). +-define(GMT_DISABLE(), ?GMT(disable)). +-define(GD(D), {debug, D}). +-define(GD_ENABLE(), ?GD(true)). +-define(GD_DISABLE(), ?GD(false)). +-define(GS(T), {sleep, T}). + +-define(GSND(T, D), {send, T, D}). +-define(GERCV(T, VF, TO), {expect_receive, T, {VF, TO}}). + + +min(M) -> ?MINS(M). %% Test server callbacks init_per_testcase(otp_7189 = Case, Config) -> @@ -396,8 +420,19 @@ groups() -> init_per_suite(Config) -> io:format("~w:init_per_suite -> entry with" - "~n Config: ~p" - "~n", [?MODULE, Config]), + "~n Config: ~p" + "~n OS Type: ~p" + "~n OS Version: ~s" + "~n", + [?MODULE, + Config, + os:type(), + case os:version() of + {Major, Minor, Release} -> + ?F("~w.~w.~w", [Major, Minor, Release]); + Str when is_list(Str) -> + Str + end]), Config. end_per_suite(_Config) -> @@ -491,12 +526,12 @@ request_and_reply_plain(suite) -> request_and_reply_plain(Config) when is_list(Config) -> ?ACQUIRE_NODES(1, Config), d("request_and_reply_plain -> start proxy",[]), - megaco_mess_user_test:start_proxy(), + ?USER_MOD:start_proxy(), PrelMid = preliminary_mid, MgMid = ipv4_mid(4711), MgcMid = ipv4_mid(), - UserMod = megaco_mess_user_test, + UserMod = ?USER_MOD, d("request_and_reply_plain -> start megaco app",[]), ?VERIFY(ok, application:start(megaco)), UserConfig = [{user_mod, UserMod}, {send_mod, UserMod}, @@ -564,6 +599,7 @@ request_and_reply_plain(Config) when is_list(Config) -> ok. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% OTP-4760 @@ -597,6 +633,7 @@ request_and_no_reply(Config) when is_list(Config) -> ET = [{text,tcp}, {text,udp}, {binary,tcp}, {binary,udp}], {ok, Mgc} = ?MGC_START(MgcNode, {deviceName, "ctrl"}, ET, [], ?MGC_VERBOSITY), + ?SLEEP(?SECONDS(1)), i("[MG] start"), Mg1Mid = {deviceName, "mg1"}, @@ -621,9 +658,13 @@ request_and_no_reply(Config) when is_list(Config) -> {pending_timer, PendingTmr}, {reply_timer, ReplyTmr}], {ok, Mg1} = ?MG_START(Mg1Node, Mg1Mid, text, tcp, MgConfig, ?MG_VERBOSITY), + ?SLEEP(?SECONDS(1)), {ok, Mg2} = ?MG_START(Mg2Node, Mg2Mid, text, udp, MgConfig, ?MG_VERBOSITY), + ?SLEEP(?SECONDS(1)), {ok, Mg3} = ?MG_START(Mg3Node, Mg3Mid, binary, tcp, MgConfig, ?MG_VERBOSITY), + ?SLEEP(?SECONDS(1)), {ok, Mg4} = ?MG_START(Mg4Node, Mg4Mid, binary, udp, MgConfig, ?MG_VERBOSITY), + ?SLEEP(?SECONDS(1)), d("MG1 user info: ~p", [?MG_USER_INFO(Mg1, all)]), d("MG1 conn info: ~p", [?MG_CONN_INFO(Mg1, all)]), @@ -639,55 +680,68 @@ request_and_no_reply(Config) when is_list(Config) -> d("service change result: ~p", [ServChRes1]), d("MG1 user info: ~p", [?MG_USER_INFO(Mg1, all)]), d("MG1 conn info: ~p", [?MG_CONN_INFO(Mg1, all)]), + ?SLEEP(?SECONDS(1)), i("[MG2] connect to the MGC (service change)"), ServChRes2 = ?MG_SERV_CHANGE(Mg2), d("service change result: ~p", [ServChRes2]), d("MG2 user info: ~p", [?MG_USER_INFO(Mg2, all)]), d("MG2 conn info: ~p", [?MG_CONN_INFO(Mg2, all)]), + ?SLEEP(?SECONDS(1)), i("[MG3] connect to the MGC (service change)"), ServChRes3 = ?MG_SERV_CHANGE(Mg3), d("service change result: ~p", [ServChRes3]), d("MG3 user info: ~p", [?MG_USER_INFO(Mg3, all)]), d("MG3 conn info: ~p", [?MG_CONN_INFO(Mg3, all)]), + ?SLEEP(?SECONDS(1)), i("[MG4] connect to the MGC (service change)"), ServChRes4 = ?MG_SERV_CHANGE(Mg4), d("service change result: ~p", [ServChRes4]), d("MG4 user info: ~p", [?MG_USER_INFO(Mg4, all)]), d("MG4 conn info: ~p", [?MG_CONN_INFO(Mg4, all)]), + ?SLEEP(?SECONDS(1)), d("tell the MGC to ignore requests"), ?MGC_REQ_PEND(Mgc, infinity), + ?SLEEP(?SECONDS(1)), d("[MG1] send the notify"), ?MG_NOTIF_REQ(Mg1), + ?SLEEP(?SECONDS(1)), d("[MG2] send the notify"), ?MG_NOTIF_REQ(Mg2), + ?SLEEP(?SECONDS(1)), d("[MG3] send the notify"), ?MG_NOTIF_REQ(Mg3), + ?SLEEP(?SECONDS(1)), d("[MG4] send the notify"), ?MG_NOTIF_REQ(Mg4), + ?SLEEP(?SECONDS(1)), d("[MG1] await notify reply"), {ok, {_Vsn1, {error, timeout}}} = ?MG_AWAIT_NOTIF_REP(Mg1), d("[MG1] received expected reply"), + ?SLEEP(?SECONDS(1)), d("[MG2] await notify reply"), {ok, {_Vsn2, {error, timeout}}} = ?MG_AWAIT_NOTIF_REP(Mg2), d("[MG2] received expected reply"), + ?SLEEP(?SECONDS(1)), d("[MG3] await notify reply"), {ok, {_Vsn3, {error, timeout}}} = ?MG_AWAIT_NOTIF_REP(Mg3), d("[MG3] received expected reply"), + ?SLEEP(?SECONDS(1)), d("[MG4] await notify reply"), {ok, {_Vsn4, {error, timeout}}} = ?MG_AWAIT_NOTIF_REP(Mg4), d("[MG4] received expected reply"), + ?SLEEP(?SECONDS(1)), d("MG1 user info: ~p", [?MG_USER_INFO(Mg1, all)]), d("MG1 conn info: ~p", [?MG_CONN_INFO(Mg1, all)]), @@ -1697,9 +1751,6 @@ rarpaop_mg_event_sequence(Port, EncMod, EncConf) -> ScrVerifyFun = ?rarpaop_mg_verify_service_change_rep_msg_fun(), PendVerifyFun = ?rarpaop_mg_verify_pending_msg_fun(TransId), NrVerifyFun = ?rarpaop_mg_verify_notify_rep_msg_fun(TransId, TermId), -%% ScrVerifyFun = rarpaop_mg_verify_service_change_rep_msg_fun(), -%% PendVerifyFun = rarpaop_mg_verify_pending_msg_fun(TransId), -%% NrVerifyFun = rarpaop_mg_verify_notify_rep_msg_fun(TransId, TermId), EvSeq = [{debug, true}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -2341,9 +2392,6 @@ strar_mg_event_sequence(text, tcp) -> ConnectVerify = ?strar_mg_verify_handle_connect_fun(), ServiceChangeReplyVerify = ?strar_mg_verify_service_change_reply_fun(), NotifyReplyVerify = ?strar_mg_verify_notify_reply_fun(), -%% ConnectVerify = strar_mg_verify_handle_connect_fun(), -%% ServiceChangeReplyVerify = strar_mg_verify_service_change_reply_fun(), -%% NotifyReplyVerify = fun strar_mg_verify_notify_reply/1, EvSeq = [ {debug, true}, megaco_start, @@ -3419,9 +3467,6 @@ raraa_mg_event_sequence(text, tcp) -> ScrVerifyFun = ?raraa_mg_verify_service_change_rep_msg_fun(), NrVerifyFun = ?raraa_mg_verify_notify_rep_msg_fun(TermId, TransId, ReqId, CtxId), -%% ScrVerifyFun = raraa_mg_verify_service_change_rep_msg_fun(), -%% NrVerifyFun = raraa_mg_verify_notify_rep_msg_fun(TermId, -%% TransId, ReqId, CtxId), EvSeq = [{debug, true}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -4030,9 +4075,6 @@ rarana_mg_event_sequence(text, tcp) -> ScrVerifyFun = ?rarana_mg_verify_service_change_rep_msg_fun(), NrVerifyFun = ?rarana_mg_verify_notify_rep_msg_fun(TermId, TransId, ReqId, CtxId), -%% ScrVerifyFun = rarana_mg_verify_service_change_rep_msg_fun(), -%% NrVerifyFun = rarana_mg_verify_notify_rep_msg_fun(TermId, -%% TransId, ReqId, CtxId), EvSeq = [{debug, true}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -4645,9 +4687,6 @@ rarala_mg_event_sequence(text, tcp) -> ScrVerifyFun = ?rarala_mg_verify_service_change_rep_msg_fun(), NrVerifyFun = ?rarala_mg_verify_notify_rep_msg_fun(TermId, TransId, ReqId, CtxId), -%% ScrVerifyFun = rarala_mg_verify_service_change_rep_msg_fun(), -%% NrVerifyFun = rarala_mg_verify_notify_rep_msg_fun(TermId, -%% TransId, ReqId, CtxId), EvSeq = [{debug, true}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -5280,15 +5319,6 @@ trarar_mg_event_sequence(text, tcp) -> ?trarar_mg_verify_notify_rep_msg_fun(TermId, 2, 3, 3), NrVerifyFun4 = ?trarar_mg_verify_notify_rep_msg_fun(TermId, 2, 4, 4), -%% ScrVerifyFun = trarar_mg_verify_service_change_rep_msg_fun(), -%% NrVerifyFun1 = -%% trarar_mg_verify_notify_rep_msg_fun(TermId, 2, 1, 1), -%% NrVerifyFun2 = -%% trarar_mg_verify_notify_rep_msg_fun(TermId, 2, 2, 2), -%% NrVerifyFun3 = -%% trarar_mg_verify_notify_rep_msg_fun(TermId, 2, 3, 3), -%% NrVerifyFun4 = -%% trarar_mg_verify_notify_rep_msg_fun(TermId, 2, 4, 4), EvSeq = [{debug, true}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -5965,11 +5995,6 @@ pap_mg_event_sequence(text, tcp) -> ?pap_mg_verify_pending_msg_fun(TransId), NrVerifyFun = ?pap_mg_verify_notify_rep_msg_fun(TermId, TransId, ReqId, CtxId), -%% ScrVerifyFun = pap_mg_verify_service_change_rep_msg_fun(), -%% PendingVerifyFun = -%% pap_mg_verify_pending_msg_fun(TransId), -%% NrVerifyFun = -%% pap_mg_verify_notify_rep_msg_fun(TermId, TransId, ReqId, CtxId), EvSeq = [{debug, true}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -6362,10 +6387,6 @@ rapalr_mgc_event_sequence(text, tcp) -> NrVerifyFun = ?rapalr_mgc_verify_notify_req_msg_fun(TermId, TransId, ReqId, CtxId), AckVerifyFun = ?rapalr_mgc_verify_trans_ack_msg_fun(TransId), -%% ScrVerifyFun = rapalr_mgc_verify_service_change_req_msg_fun(), -%% NrVerifyFun = -%% rapalr_mgc_verify_notify_req_msg_fun(TermId, TransId, ReqId, CtxId), -%% AckVerifyFun = rapalr_mgc_verify_trans_ack_msg_fun(TransId), EvSeq = [{debug, false}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -6848,12 +6869,12 @@ dist(Config) when is_list(Config) -> ?SKIP("Needs a re-write..."), [_Local, Dist] = ?ACQUIRE_NODES(2, Config), d("dist -> start proxy",[]), - megaco_mess_user_test:start_proxy(), + ?USER_MOD:start_proxy(), PrelMid = preliminary_mid, MgMid = ipv4_mid(4711), MgcMid = ipv4_mid(), - UserMod = megaco_mess_user_test, + UserMod = ?USER_MOD, d("dist -> start megaco app",[]), ?VERIFY(ok, application:start(megaco)), UserConfig = [{user_mod, UserMod}, {send_mod, UserMod}, @@ -6959,7 +6980,7 @@ dist(Config) when is_list(Config) -> ?RECEIVE([]), d("dist -> stop proxy",[]), - megaco_mess_user_test:stop_proxy(), + ?USER_MOD:stop_proxy(), d("dist -> done", []), ok. @@ -7426,9 +7447,6 @@ otp_5805_mg_event_sequence(text, tcp) -> ?otp_5805_mg_verify_service_change_rep_msg_fun(), EDVerify = ?otp_5805_mg_verify_error_descriptor_msg_fun(), -%% ServiceChangeReplyVerifyFun = -%% otp_5805_mg_verify_service_change_rep_msg_fun(), -%% EDVerify = otp_5805_mg_verify_error_descriptor_msg_fun(), MgEvSeq = [{debug, true}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -7921,8 +7939,6 @@ otp_5881_mgc_event_sequence(text, tcp) -> %% Pending = otp_5881_pending_msg(Mid,2), ServiceChangeVerifyFun = ?otp_5881_mgc_verify_service_change_req_msg_fun(), NotifyReqVerifyFun = ?otp_5881_mgc_verify_notify_req_msg_fun(), -%% ServiceChangeVerifyFun = otp_5881_verify_service_change_req_msg_fun(), -%% NotifyReqVerifyFun = otp_5881_verify_notify_request_fun(), MgcEvSeq = [{debug, true}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -8193,8 +8209,6 @@ otp_5887_mgc_event_sequence(text, tcp) -> NotifyReply = otp_5887_notify_reply_msg(Mid, 2, 0, TermId), ServiceChangeVerifyFun = ?otp_5887_mgc_verify_service_change_req_msg_fun(), NotifyReqVerifyFun = ?otp_5887_mgc_verify_notify_req_msg_fun(), -%% ServiceChangeVerifyFun = otp_5887_verify_service_change_req_msg_fun(), -%% NotifyReqVerifyFun = otp_5887_verify_notify_request_fun(), MgcEvSeq = [{debug, true}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -8344,7 +8358,7 @@ otp_6253(Config) when is_list(Config) -> MgMid = ipv4_mid(4711), ?VERIFY(ok, application:start(megaco)), - ?VERIFY(ok, megaco:start_user(MgMid, [{send_mod, megaco_mess_user_test}, + ?VERIFY(ok, megaco:start_user(MgMid, [{send_mod, ?USER_MOD}, {request_timer, infinity}, {reply_timer, infinity}])), @@ -8650,8 +8664,6 @@ otp_6275_mgc_event_sequence(text, tcp) -> NotifyReq = otp_6275_mgc_notify_request_msg(Mid, 2, 1, TermId, 1), SCRVerifyFun = ?otp_6275_mgc_verify_service_change_req_msg_fun(), NotifyReplyVerifyFun = ?otp_6275_mgc_verify_notify_rep_msg_fun(), -%% SCRVerifyFun = otp_6275_mgc_verify_service_change_req_fun(), -%% NotifyReplyVerifyFun = otp_6275_mgc_verify_notify_reply_fun(), MgcEvSeq = [{debug, true}, {decode, DecodeFun}, @@ -10997,12 +11009,12 @@ otp_6865_request_and_reply_plain_extra1(Config) when is_list(Config) -> ok = megaco_tc_controller:insert(extra_transport_info, ExtraInfo), d("start proxy",[]), - megaco_mess_user_test:start_proxy(), + ?USER_MOD:start_proxy(), PrelMid = preliminary_mid, MgMid = ipv4_mid(4711), MgcMid = ipv4_mid(), - UserMod = megaco_mess_user_test, + UserMod = ?USER_MOD, d("start megaco app",[]), ?VERIFY(ok, application:start(megaco)), UserConfig = [{user_mod, UserMod}, {send_mod, UserMod}, @@ -12268,15 +12280,15 @@ otp_7189_mg_event_sequence(text, tcp) -> ?otp_7189_mg_verify_service_change_rep_msg_fun(), NotifyReqVerify = ?otp_7189_mg_verify_notify_req_msg_fun(TermId, TransId, ReqId, CtxId), EvSeq = [ - {debug, true}, + ?GD_ENABLE(), {decode, DecodeFun}, {encode, EncodeFun}, {connect, 2944}, - {send, "service-change-request", ServiceChangeReq}, - {expect_receive, "service-change-reply", {ServiceChangeReplyVerifyFun, 2000}}, - {expect_receive, "notify request", {NotifyReqVerify, 2000}}, - {sleep, 100}, - {send, "pending", Pending}, + ?GSND("service-change-request", ServiceChangeReq), + ?GERCV("service-change-reply", ServiceChangeReplyVerifyFun, ?SECS(5)), + ?GERCV("notify request", NotifyReqVerify, ?SECS(5)), + ?GS(100), + ?GSND("pending", Pending), {expect_closed, timer:seconds(120)}, disconnect ], @@ -12514,7 +12526,7 @@ otp_7259(Config) when is_list(Config) -> megaco_test_generic_transport:incomming_message(Pid, NotifyReply), d("[MG] await the generator reply"), - await_completion([MgId], 5000), + await_completion([MgId], 7000), %% Tell Mg to stop i("[MG] stop generator"), @@ -12814,13 +12826,13 @@ otp_7713(Config) when is_list(Config) -> i("starting"), d("start proxy",[]), - megaco_mess_user_test:start_proxy(), + ?USER_MOD:start_proxy(), Extra = otp7713_extra, PrelMid = preliminary_mid, MgMid = ipv4_mid(4711), MgcMid = ipv4_mid(), - UserMod = megaco_mess_user_test, + UserMod = ?USER_MOD, d("start megaco app",[]), ?VERIFY(ok, application:start(megaco)), UserConfig = [{user_mod, UserMod}, {send_mod, UserMod}, @@ -12936,10 +12948,11 @@ otp_8183_request1(Config) when is_list(Config) -> i("wait some before issuing the notify reply (twice)"), sleep(500), - i("send the notify reply, twice times"), + i("send the notify reply - twice"), NotifyReply = otp_8183_r1_mgc_notify_reply_msg(MgcMid, TransId2, Cid2, TermId2), megaco_test_generic_transport:incomming_message(Pid, NotifyReply), + sleep(100), %% This is to "make sure" the events come in the "right" order megaco_test_generic_transport:incomming_message(Pid, NotifyReply), d("await the generator reply"), @@ -13210,13 +13223,33 @@ otp_8183_r1_mg_verify_notify_rep_fun(Nr) -> end. -endif. -otp_8183_r1_mg_verify_notify_rep(Nr, +otp_8183_r1_mg_verify_notify_rep( + Nr, {handle_trans_reply, _CH, ?VERSION, {ok, Nr, [AR]}, _}) -> io:format("otp_8183_r1_mg_verify_notify_rep -> ok" "~n Nr: ~p" "~n AR: ~p" "~n", [Nr, AR]), {ok, AR, ok}; +otp_8183_r1_mg_verify_notify_rep( + ExpNr, + {handle_trans_reply, _CH, ?VERSION, {ok, ActNr, [AR]}, _}) -> + io:format("otp_8183_r1_mg_verify_notify_rep -> error" + "~n Expected Nr: ~p" + "~n Actual Nr: ~p" + "~n AR: ~p" + "~n", [ExpNr, ActNr, AR]), + Error = {unexpected_nr, ExpNr, ActNr}, + {error, Error, ok}; +otp_8183_r1_mg_verify_notify_rep( + Nr, + {handle_trans_reply, _CH, ?VERSION, Res, _}) -> + io:format("otp_8183_r1_mg_verify_notify_rep -> error" + "~n Nr: ~p" + "~n Res: ~p" + "~n", [Nr, Res]), + Error = {unexpected_result, Nr, Res}, + {error, Error, ok}; otp_8183_r1_mg_verify_notify_rep(Nr, Else) -> io:format("otp_8183_r1_mg_verify_notify_rep -> unknown" "~n Nr: ~p" @@ -13711,38 +13744,36 @@ i(F) -> i(F, []). i(F, A) -> - print(info, get(verbosity), now(), get(tc), "INF", F, A). + print(info, "INF", F, A). d(F) -> d(F, []). d(F, A) -> - print(debug, get(verbosity), now(), get(tc), "DBG", F, A). + print(debug, "DBG", F, A). -printable(_, debug) -> true; -printable(info, info) -> true; -printable(_,_) -> false. +print(Severity, PRE, FMT, ARGS) -> + print(Severity, get(verbosity), erlang:timestamp(), get(tc), PRE, FMT, ARGS). print(Severity, Verbosity, Ts, Tc, P, F, A) -> print(printable(Severity,Verbosity), Ts, Tc, P, F, A). -print(true, Ts, Tc, P, F, A) -> - io:format("*** [~s] ~s ~p ~s:~w ***" - "~n " ++ F ++ "~n", - [format_timestamp(Ts), P, self(), get(sname), Tc | A]); +print(true, TS, TC, P, F, A) -> + S = ?F("*** [~s] ~s ~p ~s:~w ***" + "~n " ++ F ++ "~n", + [megaco:format_timestamp(TS), P, self(), get(sname), TC | A]), + io:format("~s", [S]), + io:format(user, "~s", [S]); print(_, _, _, _, _, _) -> ok. -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), - lists:flatten(FormatDate). + +printable(_, debug) -> true; +printable(info, info) -> true; +printable(_,_) -> false. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -13752,13 +13783,6 @@ to(To, Start) -> %% Time in milli seconds mtime() -> - {A,B,C} = erlang:now(), + {A,B,C} = erlang:timestamp(), A*1000000000+B*1000+(C div 1000). -%% random_init() -> -%% {A,B,C} = now(), -%% random:seed(A,B,C). - -%% random() -> -%% 10 * random:uniform(50). - diff --git a/lib/megaco/test/megaco_mess_user_test.erl b/lib/megaco/test/megaco_mess_user_test.erl index b5a554112e..d2a9c0f290 100644 --- a/lib/megaco/test/megaco_mess_user_test.erl +++ b/lib/megaco/test/megaco_mess_user_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2016. All Rights Reserved. +%% Copyright Ericsson AB 2000-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. @@ -86,13 +86,13 @@ reply(Mod, Line, Fun) when is_function(Fun) -> {?MODULE, Pid, UserCallback} -> UserReply = Fun(UserCallback), Pid ! {?MODULE, self(), UserReply}, - UserReply; - Other -> - megaco_test_lib:error(Other, Mod, Line), - {error, Other} -%% after 1000 -> -%% megaco_test_lib:error(timeout, Mod, Line), -%% {error, timeout} + UserReply%% ; + %% Other -> + %% megaco_test_lib:error(Other, Mod, Line), + %% {error, Other} + after 10000 -> + megaco_test_lib:error(timeout, Mod, Line), + {error, timeout} end. call(UserCallback) -> diff --git a/lib/megaco/test/megaco_mib_test.erl b/lib/megaco/test/megaco_mib_test.erl index d644d6bc09..9d1d408f18 100644 --- a/lib/megaco/test/megaco_mib_test.erl +++ b/lib/megaco/test/megaco_mib_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2016. All Rights Reserved. +%% Copyright Ericsson AB 2002-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,7 +24,32 @@ %%---------------------------------------------------------------------- -module(megaco_mib_test). --compile(export_all). +-export([ + t/0, t/1, + + all/0, + groups/0, + init_per_testcase/2, + end_per_testcase/2, + init_per_group/2, + end_per_group/2, + + plain/1, + connect/1, + traffic/1, + + mg/3, + mgc/3, + + handle_connect/3, + handle_disconnect/4, + handle_syntax_error/4, + handle_message_error/4, + handle_trans_request/4, + handle_trans_long_request/4, + handle_trans_reply/5, + handle_trans_ack/5 + ]). -include("megaco_test_lib.hrl"). -include_lib("megaco/include/megaco.hrl"). @@ -54,6 +79,7 @@ t(Case) -> megaco_test_lib:t({?MODULE, Case}). %% Test server callbacks init_per_testcase(Case, Config) -> + progress("init_per_testcase -> ~w", [Case]), process_flag(trap_exit, true), case Case of traffic -> @@ -65,6 +91,7 @@ init_per_testcase(Case, Config) -> end. end_per_testcase(Case, Config) -> + progress("end_per_testcase -> ~w", [Case]), process_flag(trap_exit, false), megaco_test_lib:end_per_testcase(Case, Config). @@ -197,6 +224,7 @@ connect(Config) when is_list(Config) -> put(verbosity, ?TEST_VERBOSITY), put(sname, "TEST"), i("connect -> starting"), + progress("start nodes"), MgcNode = make_node_name(mgc), Mg1Node = make_node_name(mg1), Mg2Node = make_node_name(mg2), @@ -209,67 +237,85 @@ connect(Config) when is_list(Config) -> %% Start the MGC and MGs ET = [{text,tcp}, {text,udp}, {binary,tcp}, {binary,udp}], + progress("start MGC"), {ok, Mgc} = start_mgc(MgcNode, {deviceName, "ctrl"}, ET, ?MGC_VERBOSITY), + progress("start MG1"), {ok, Mg1} = start_mg(Mg1Node, {deviceName, "mg1"}, text, tcp, ?MG_VERBOSITY), + progress("start MG2"), {ok, Mg2} = start_mg(Mg2Node, {deviceName, "mg2"}, binary, udp, ?MG_VERBOSITY), %% Collect the initial statistics (should be zero if anything) + progress("collect initial MG1 stats"), {ok, Mg1Stats0} = get_stats(Mg1, 1), d("connect -> stats for Mg1: ~n~p", [Mg1Stats0]), + progress("collect initial MG2 stats"), {ok, Mg2Stats0} = get_stats(Mg2, 1), d("connect -> stats for Mg2: ~n~p", [Mg2Stats0]), + progress("collect initial MGC stats"), {ok, MgcStats0} = get_stats(Mgc, 1), d("connect -> stats for Mgc: ~n~p", [MgcStats0]), %% Ask Mg1 to do a service change + progress("perform MG1 service change"), {ok, Res1} = service_change(Mg1), d("connect -> (Mg1) service change result: ~p", [Res1]), %% Collect the statistics + progress("collect MG1 statistics (after service change)"), {ok, Mg1Stats1} = get_stats(Mg1, 1), d("connect -> stats for Mg1: ~n~p", [Mg1Stats1]), + progress("collect MGC statistics (after MG1 service change)"), {ok, MgcStats1} = get_stats(Mgc, 1), d("connect -> stats (1) for Mgc: ~n~p", [MgcStats1]), {ok, MgcStats2} = get_stats(Mgc, 2), d("connect -> stats (2) for Mgc: ~n~p", [MgcStats2]), %% Ask Mg2 to do a service change + progress("perform MG2 service change"), {ok, Res2} = service_change(Mg2), d("connect -> (Mg2) service change result: ~p", [Res2]), %% Collect the statistics + progress("collect MG2 statistics (after service change)"), {ok, Mg2Stats1} = get_stats(Mg2, 1), d("connect -> stats for Mg1: ~n~p", [Mg2Stats1]), + progress("collect MGC statistics (after MG2 service change)"), {ok, MgcStats3} = get_stats(Mgc, 1), d("connect -> stats (1) for Mgc: ~n~p", [MgcStats3]), {ok, MgcStats4} = get_stats(Mgc, 2), d("connect -> stats (2) for Mgc: ~n~p", [MgcStats4]), %% Tell Mg1 to stop + progress("stop MG1"), stop(Mg1), %% Collect the statistics + progress("collect MGC statistics (after MG1 stop)"), {ok, MgcStats5} = get_stats(Mgc, 1), d("connect -> stats (1) for Mgc: ~n~p", [MgcStats5]), {ok, MgcStats6} = get_stats(Mgc, 2), d("connect -> stats (2) for Mgc: ~n~p", [MgcStats6]), %% Tell Mg2 to stop + progress("stop MG2"), stop(Mg2), %% Collect the statistics + progress("collect MGC statistics (after MG2 stop)"), {ok, MgcStats7} = get_stats(Mgc, 1), d("connect -> stats (1) for Mgc: ~n~p", [MgcStats7]), {ok, MgcStats8} = get_stats(Mgc, 2), d("connect -> stats (2) for Mgc: ~n~p", [MgcStats8]), %% Tell Mgc to stop + progress("stop MGC"), stop(Mgc), i("connect -> done", []), + progress("done"), ok. @@ -284,6 +330,7 @@ traffic(Config) when is_list(Config) -> put(verbosity, ?TEST_VERBOSITY), put(sname, "TEST"), i("traffic -> starting"), + progress("start nodes"), MgcNode = make_node_name(mgc), Mg1Node = make_node_name(mg1), Mg2Node = make_node_name(mg2), @@ -302,11 +349,13 @@ traffic(Config) when is_list(Config) -> %% Start the MGC and MGs i("traffic -> start the MGC"), + progress("start MGC"), ET = [{text,tcp}, {text,udp}, {binary,tcp}, {binary,udp}], {ok, Mgc} = start_mgc(MgcNode, {deviceName, "ctrl"}, ET, ?MGC_VERBOSITY), i("traffic -> start and connect the MGs"), + progress("start and connect MGs"), MgConf0 = [{Mg1Node, "mg1", text, tcp}, {Mg2Node, "mg2", text, udp}, {Mg3Node, "mg3", binary, tcp}, @@ -315,36 +364,42 @@ traffic(Config) when is_list(Config) -> %% Collect and check the MGs statistics i("traffic -> collect and check the MGs stats"), + progress("collect and verify MGs (initial) stats"), traffic_verify_mg_stats(MgConf, 1, 1), %% Collect and check the MGC statistics - i("traffic -> collect and check the MGC stats"), + i("traffic -> collect and check the MGC (initial) stats"), + progress("collect and verify MGC stats"), {ok, MgcStats1} = get_stats(Mgc, 1), d("traffic -> stats (1) for Mgc: ~n~p~n", [MgcStats1]), traffic_verify_mgc_stats(Mgc, 1, 1), - sleep(1000), + ?SLEEP(1000), %% And apply some load - i("traffic -> apply traffic load"), + i("traffic -> apply traffic load (1)"), + progress("apply some load (1)"), ok = traffic_apply_load(MgConf), %% Await completion of load part and the collect traffic - i("traffic -> await load competion"), + i("traffic -> await load (1) competion"), + progress("await load (1) completion"), ok = traffic_await_load_complete(MgConf), - sleep(1000), + ?SLEEP(1000), i("traffic -> collect and check the MGs statistics"), + progress("collect and verify MGs (after load 1) stats"), traffic_verify_mg_stats(MgConf, 1 + ?LOAD_COUNTER_START, 1 + ?LOAD_COUNTER_START), i("traffic -> collect and check the MGC statistics"), + progress("collect and verify MGC (after load 1) stats"), {ok, MgcStats3} = get_stats(Mgc, 1), d("traffic -> stats (1) for Mgc: ~n~p~n", [MgcStats3]), traffic_verify_mgc_stats(Mgc, @@ -352,60 +407,70 @@ traffic(Config) when is_list(Config) -> 1 + ?LOAD_COUNTER_START), - sleep(1000), + ?SLEEP(1000), %% Reset counters i("traffic -> reset the MGs statistics"), + progress("reset MGs stats"), traffic_reset_mg_stats(MgConf), i("traffic -> collect and check the MGs statistics"), + progress("collect and verify MGs (after reset) stats"), traffic_verify_mg_stats(MgConf, 0, 0), i("traffic -> reset the MGC statistics"), + progress("reset MGC stats"), traffic_reset_mgc_stats(Mgc), i("traffic -> collect and check the MGC statistics"), + progress("collect and verify MGC (after reset) stats"), traffic_verify_mgc_stats(Mgc, 0, 0), - sleep(1000), + ?SLEEP(1000), %% And apply some load - i("traffic -> apply traffic load"), + i("traffic -> apply traffic load (2)"), + progress("apply some load (2)"), ok = traffic_apply_load(MgConf), %% Await completion of load part and the collect traffic - i("traffic -> await load competion"), + i("traffic -> await load (2) competion"), + progress("await load (2) completion"), ok = traffic_await_load_complete(MgConf), - sleep(1000), + ?SLEEP(1000), i("traffic -> collect and check the MGs statistics"), + progress("collect and verify MGs (after load 2) stats"), traffic_verify_mg_stats(MgConf, ?LOAD_COUNTER_START, ?LOAD_COUNTER_START), i("traffic -> collect and check the MGC statistics"), + progress("collect and verify MGC (after load 2) stats"), traffic_verify_mgc_stats(Mgc, ?LOAD_COUNTER_START, ?LOAD_COUNTER_START), - sleep(1000), + ?SLEEP(1000), %% Tell MGs to stop i("traffic -> stop the MGs"), + progress("stop MGs"), traffic_stop_mg(MgConf), - sleep(1000), + ?SLEEP(1000), %% Collect the statistics i("traffic -> collect the MGC statistics"), + progress("collect and verify MGC (after MGs stop) stats"), {ok, MgcStats7} = get_stats(Mgc, 1), d("traffic -> stats (1) for Mgc: ~n~p~n", [MgcStats7]), {ok, MgcStats8} = get_stats(Mgc, 2), @@ -413,9 +478,11 @@ traffic(Config) when is_list(Config) -> %% Tell Mgc to stop i("traffic -> stop the MGC"), + progress("stop MGC"), stop(Mgc), i("traffic -> done", []), + progress("done"), ok. @@ -516,10 +583,15 @@ traffic_verify_get_stats(S, Stats) -> traffic_verify_counter(Name, Counter, Counters, Expected) -> case lists:keysearch(Counter, 1, Counters) of {value, {Counter, Expected}} -> + i("counter ~w verified for ~p", [Counter, Name]), ok; {value, {Counter, Val}} -> + i("counter ~w *not* verified for ~p: " + "~n Expected: ~w" + "~n Actual: ~w", [Counter, Name, Expected, Val]), exit({illegal_counter_value, Counter, Val, Expected, Name}); false -> + i("counter ~w *not* found for ~p", [Counter, Name]), exit({not_found, Counter, Counters, Name, Expected}) end. @@ -536,8 +608,7 @@ traffic_connect_mg(Node, Name, Coding, Trans) -> %% Ask the MGs to do a service change {ok, Res} = service_change(Pid), - d("traffic_connect_mg -> (~s) service change result: ~p", [Name,Res]), - + d("traffic_connect_mg -> (~s) service change result: ~p", [Name, Res]), Pid. @@ -549,7 +620,9 @@ traffic_get_mg_stats([], Acc) -> lists:reverse(Acc); traffic_get_mg_stats([{Name, Pid}|Mgs], Acc) -> {ok, Stats} = get_stats(Pid, 1), - d("traffic_get_mg_stats -> stats for ~s: ~n~p~n", [Name, Stats]), + d("traffic_get_mg_stats -> stats for ~s: " + "~n ~p" + "~n", [Name, Stats]), traffic_get_mg_stats(Mgs, [{Name, Stats}|Acc]). @@ -678,7 +751,7 @@ mgc_init(Config) -> d("mgc_init -> entry"), Mid = get_conf(local_mid, Config), RI = get_conf(receive_info, Config), - d("mgc_init -> start megaco"), + i("mgc_init -> start megaco"), application:start(megaco), d("mgc_init -> start megaco user"), megaco:start_user(Mid, []), @@ -690,7 +763,7 @@ mgc_init(Config) -> RH = megaco:user_info(Mid,receive_handle), d("mgc_init -> parse receive info"), ListenTo = mgc_parse_receive_info(RI, RH), - d("mgc_init -> start transports"), + i("mgc_init -> start transport(s)"), {Tcp, Udp} = mgc_start_transports(ListenTo), {Mid, Tcp, Udp}. @@ -875,7 +948,7 @@ mgc_start_transports([{_Port, RH}|_ListenTo], _TcpSup, _UdpSup) -> mgc_start_tcp(RH, Port, undefined) -> - d("start tcp transport"), + i("start tcp transport"), case megaco_tcp:start_transport() of {ok, Sup} -> mgc_start_tcp(RH, Port, Sup); @@ -883,7 +956,7 @@ mgc_start_tcp(RH, Port, undefined) -> throw({error, {failed_starting_tcp_transport, Else}}) end; mgc_start_tcp(RH, Port, Sup) when is_pid(Sup) -> - d("tcp listen on ~p", [Port]), + i("tcp listen on ~p", [Port]), Opts = [{port, Port}, {receive_handle, RH}, {tcp_options, [{nodelay, true}]}], @@ -903,7 +976,7 @@ mgc_tcp_create_listen(Sup, Opts, MaxN, N, _InitialReason) ok -> Sup; {error, {could_not_start_listener, {gen_tcp_listen, eaddrinuse} = Reason}} -> - sleep(N * 200), + ?SLEEP(N * 200), mgc_tcp_create_listen(Sup, Opts, MaxN, N + 1, Reason); {error, Reason} -> throw({error, {failed_starting_tcp_listen, Reason}}); @@ -913,7 +986,7 @@ mgc_tcp_create_listen(Sup, Opts, MaxN, N, _InitialReason) mgc_start_udp(RH, Port, undefined) -> - d("start udp transport"), + i("start udp transport"), case megaco_udp:start_transport() of {ok, Sup} -> mgc_start_udp(RH, Port, Sup); @@ -921,7 +994,7 @@ mgc_start_udp(RH, Port, undefined) -> throw({error, {failed_starting_udp_transport, Else}}) end; mgc_start_udp(RH, Port, Sup) -> - d("open udp ~p", [Port]), + i("open udp ~p", [Port]), Opts = [{port, Port}, {receive_handle, RH}], case megaco_udp:open(Sup, Opts) of {ok, _SendHandle, _ControlPid} -> @@ -1044,10 +1117,9 @@ mg(Parent, Verbosity, Config) -> mg_init(Config) -> d("mg_init -> entry"), - random_init(), Mid = get_conf(local_mid, Config), RI = get_conf(receive_info, Config), - d("mg_init -> start megaco"), + i("mg_init -> start megaco"), application:start(megaco), d("mg_init -> start megaco user"), megaco:start_user(Mid, []), @@ -1059,7 +1131,7 @@ mg_init(Config) -> RH = megaco:user_info(Mid,receive_handle), d("mg_init -> parse receive info"), {MgcPort,RH1} = mg_parse_receive_info(RI, RH), - d("mg_init -> start transport with"), + i("mg_init -> start transport(s)"), ConnHandle = mg_start_transport(MgcPort, RH1), {Mid, ConnHandle}. @@ -1088,12 +1160,12 @@ mg_loop(#mg{state = State} = S) -> %% Give me statistics {statistics, 1, Parent} when S#mg.parent == Parent -> i("mg_loop(~p) -> got request for statistics 1", [State]), - {ok, Gen} = megaco:get_stats(), - CH = S#mg.conn_handle, - Reason = {statistics, CH}, - Pid = megaco:conn_info(CH, control_pid), - SendMod = megaco:conn_info(CH, send_mod), - SendHandle = megaco:conn_info(CH, send_handle), + {ok, Gen} = megaco:get_stats(), + CH = S#mg.conn_handle, + Reason = {statistics, CH}, + Pid = megaco:conn_info(CH, control_pid), + SendMod = megaco:conn_info(CH, send_mod), + SendHandle = megaco:conn_info(CH, send_handle), {ok, Trans} = case SendMod of megaco_tcp -> megaco_tcp:get_stats(SendHandle); @@ -1183,16 +1255,6 @@ mg_close_conn(CH) -> SendMod -> exit(Pid, Reason) end. -% display_tuple(T) when tuple(T), size(T) > 0 -> -% i("size(T): ~p", [size(T)]), -% display_tuple(T,1). - -% display_tuple(T,P) when P > size(T) -> -% ok; -% display_tuple(T,P) -> -% i("T[~p]: ~p", [P,element(P,T)]), -% display_tuple(T,P+1). - mg_parse_receive_info(RI, RH) -> d("mg_parse_receive_info -> get encoding module"), @@ -1220,7 +1282,7 @@ mg_start_transport(_, #megaco_receive_handle{send_mod = Mod}) -> mg_start_tcp(MgcPort, RH) -> - d("start tcp transport"), + i("start tcp transport"), case megaco_tcp:start_transport() of {ok, Sup} -> {ok, LocalHost} = inet:gethostname(), @@ -1244,16 +1306,19 @@ mg_start_tcp(MgcPort, RH) -> mg_start_udp(MgcPort, RH) -> - d("start udp transport"), + i("start udp transport"), case megaco_udp:start_transport() of {ok, Sup} -> - {ok, LocalHost} = inet:gethostname(), + %% Some linux (Ubuntu) has "crap" in their /etc/hosts, that + %% causes problem for us in this case (UDP). So we can't use + %% local host. Try instead to "figure out" tha actual address... + LocalAddr = which_local_addr(), Opts = [{port, 0}, {receive_handle, RH}], case megaco_udp:open(Sup, Opts) of {ok, Handle, ControlPid} -> MgcMid = preliminary_mid, SendHandle = megaco_udp:create_send_handle(Handle, - LocalHost, + LocalAddr, MgcPort), {ok, ConnHandle} = megaco:connect(RH, MgcMid, @@ -1528,10 +1593,6 @@ request(Pid, Request) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -sleep(X) -> - receive after X -> ok end. - - error_msg(F,A) -> error_logger:error_msg(F ++ "~n",A). @@ -1583,6 +1644,44 @@ get_conf(Key, Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +which_local_addr() -> + case inet:getifaddrs() of + {ok, IFs} -> + which_local_addr(IFs); + {error, Reason} -> + i("Failed get local address: " + "~n ~p", [Reason]), + ?SKIP({failed_get_local_addr, Reason}) + end. + +which_local_addr([]) -> + ?SKIP(failed_get_local_addr); +which_local_addr([{"lo" = _IfName, _IfOpts}|IFs]) -> + which_local_addr(IFs); +which_local_addr([{"br-" ++ _ = _IfName, _IfOpts}|IFs]) -> + which_local_addr(IFs); +which_local_addr([{"docker" ++ _ = _IfName, _IfOpts}|IFs]) -> + which_local_addr(IFs); +which_local_addr([{_IfName, IfOpts}|IFs]) -> + case which_local_addr2(IfOpts) of + {ok, Addr} -> + Addr; + error -> + which_local_addr(IFs) + end. + + +which_local_addr2([]) -> + error; +which_local_addr2([{addr, Addr}|_]) + when (size(Addr) =:= 4) andalso (element(1, Addr) =/= 127) -> + {ok, Addr}; +which_local_addr2([_|T]) -> + which_local_addr2(T). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + i(F) -> i(F, []). @@ -1605,19 +1704,23 @@ print(Severity, Verbosity, P, F, A) -> print(printable(Severity,Verbosity), P, F, A). print(true, P, F, A) -> - io:format("~s~p:~s: " ++ F ++ "~n", [P, self(), get(sname) | A]); + io:format("~s~p:~s:~s: " ++ F ++ "~n", [P, self(), get(sname), ?FTS() | A]); print(_, _, _, _) -> ok. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +progress(F) -> + progress(F, []). + +progress(F, A) -> + io:format("~s " ++ F ++ "~n", [?FTS()|A]), + io:format(user, "~s " ++ F ++ "~n", [?FTS()|A]). -random_init() -> - {A,B,C} = now(), - random:seed(A,B,C). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% random() -> - 10 * random:uniform(50). + 10 * rand:uniform(50). apply_load_timer() -> erlang:send_after(random(), self(), apply_load_timeout). diff --git a/lib/megaco/test/megaco_mreq_test.erl b/lib/megaco/test/megaco_mreq_test.erl index e6a5ed3181..ba20329ab3 100644 --- a/lib/megaco/test/megaco_mreq_test.erl +++ b/lib/megaco/test/megaco_mreq_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-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,7 +24,21 @@ %%---------------------------------------------------------------------- -module(megaco_mreq_test). --compile(export_all). +-export([ + all/0, + groups/0, + + init_per_group/2, + end_per_group/2, + init_per_testcase/2, + end_per_testcase/2, + + req_and_rep/1, + req_and_pending/1, + req_and_cancel/1, + + t/0, t/1 + ]). -include("megaco_test_lib.hrl"). -include_lib("megaco/include/megaco.hrl"). @@ -51,16 +65,19 @@ -define(MG_START(Pid, Mid, Enc, Transp, Verb), megaco_test_mg:start(Pid, Mid, Enc, Transp, Verb)). --define(MG_STOP(Pid), megaco_test_mg:stop(Pid)). --define(MG_GET_STATS(Pid, No), megaco_test_mg:get_stats(Pid, No)). --define(MG_RESET_STATS(Pid), megaco_test_mg:reset_stats(Pid)). --define(MG_SERV_CHANGE(Pid), megaco_test_mg:service_change(Pid)). --define(MG_NOTIF_RAR(Pid), megaco_test_mg:notify_request_and_reply(Pid)). --define(MG_NOTIF_REQ(Pid), megaco_test_mg:notify_request(Pid)). --define(MG_NOTIF_AR(Pid), megaco_test_mg:await_notify_reply(Pid)). --define(MG_CANCEL(Pid,R), megaco_test_mg:cancel_request(Pid,R)). +-define(MG_STOP(Pid), megaco_test_mg:stop(Pid)). +-define(MG_GET_STATS(Pid), megaco_test_mg:get_stats(Pid)). +-define(MG_RESET_STATS(Pid), megaco_test_mg:reset_stats(Pid)). +-define(MG_SERV_CHANGE(Pid), megaco_test_mg:service_change(Pid)). +-define(MG_NOTIF_RAR(Pid), megaco_test_mg:notify_request_and_reply(Pid)). +-define(MG_NOTIF_REQ(Pid), megaco_test_mg:notify_request(Pid)). +-define(MG_NOTIF_AR(Pid), megaco_test_mg:await_notify_reply(Pid)). +-define(MG_CANCEL(Pid,R), megaco_test_mg:cancel_request(Pid,R)). -define(MG_APPLY_LOAD(Pid,CntStart), megaco_test_mg:apply_load(Pid,CntStart)). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + t() -> megaco_test_lib:t(?MODULE). t(Case) -> megaco_test_lib:t({?MODULE, Case}). @@ -74,6 +91,7 @@ end_per_testcase(Case, Config) -> process_flag(trap_exit, false), megaco_test_lib:end_per_testcase(Case, Config). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% all() -> @@ -228,7 +246,7 @@ req_and_rep_stop_mg(MGs) -> req_and_rep_get_mg_stats([], Acc) -> lists:reverse(Acc); req_and_rep_get_mg_stats([{Name, Pid}|Mgs], Acc) -> - {ok, Stats} = ?MG_GET_STATS(Pid, 1), + {ok, Stats} = ?MG_GET_STATS(Pid), d("req_and_rep_get_mg_stats -> stats for ~s: ~n~p~n", [Name, Stats]), req_and_rep_get_mg_stats(Mgs, [{Name, Stats}|Acc]). @@ -399,11 +417,13 @@ req_and_cancel(Config) when is_list(Config) -> req_and_cancel_analyze_result({ok,{_PV,Res}}) -> - d("req_and_cancel -> notify request result: ~n ~p", [Res]), + i("req_and_cancel -> notify request result: ~n ~p", [Res]), req_and_cancel_analyze_result2(Res); req_and_cancel_analyze_result(Unexpected) -> exit({unexpected_result,Unexpected}). +req_and_cancel_analyze_result2({error,{user_cancel,req_and_cancel}}) -> + ok; req_and_cancel_analyze_result2([]) -> ok; req_and_cancel_analyze_result2([{error,{user_cancel,req_and_cancel}}|Res]) -> @@ -429,9 +449,6 @@ sleep(X) -> receive after X -> ok end. -error_msg(F,A) -> error_logger:error_msg(F ++ "~n",A). - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% i(F) -> @@ -441,8 +458,8 @@ i(F, A) -> print(info, get(verbosity), "", F, A). -d(F) -> - d(F, []). +%% d(F) -> +%% d(F, []). d(F, A) -> print(debug, get(verbosity), "DBG: ", F, A). @@ -456,20 +473,9 @@ print(Severity, Verbosity, P, F, A) -> print(printable(Severity,Verbosity), P, F, A). print(true, P, F, A) -> - io:format("~s~p:~s: " ++ F ++ "~n", [P, self(), get(sname) | A]); + io:format("*** [~s] ~s ~p ~s ***" + "~n " ++ F ++ "~n", + [?FTS(), P, self(), get(sname) | A]); print(_, _, _, _) -> ok. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -random_init() -> - {A,B,C} = now(), - random:seed(A,B,C). - -random() -> - 10 * random:uniform(50). - -apply_load_timer() -> - erlang:send_after(random(), self(), apply_load_timeout). - diff --git a/lib/megaco/test/megaco_pending_limit_test.erl b/lib/megaco/test/megaco_pending_limit_test.erl index ef3b7e51cf..e0c0468d99 100644 --- a/lib/megaco/test/megaco_pending_limit_test.erl +++ b/lib/megaco/test/megaco_pending_limit_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-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. @@ -2065,23 +2065,12 @@ await_completion(Ids) -> ?ERROR({failed, Reply}) end. -%% await_completion(Ids, Timeout) -> -%% case megaco_test_generator_lib:await_completion(Ids, Timeout) of -%% {ok, Reply} -> -%% d("OK => Reply: ~n~p", [Reply]), -%% ok; -%% {error, Reply} -> -%% d("ERROR => Reply: ~n~p", [Reply]), -%% ?ERROR({failed, Reply}) -%% end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% sleep(X) -> receive after X -> ok end. -%% error_msg(F,A) -> error_logger:error_msg(F ++ "~n",A). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -2089,49 +2078,30 @@ i(F) -> i(F, []). i(F, A) -> - print(info, get(verbosity), now(), get(tc), "INF", F, A). + print(info, get(verbosity), get(tc), "INF", F, A). d(F) -> d(F, []). d(F, A) -> - print(debug, get(verbosity), now(), get(tc), "DBG", F, A). + print(debug, get(verbosity), get(tc), "DBG", F, A). printable(_, debug) -> true; printable(info, info) -> true; printable(_,_) -> false. -print(Severity, Verbosity, Ts, Tc, P, F, A) -> - print(printable(Severity,Verbosity), Ts, Tc, P, F, A). +print(Severity, Verbosity, Tc, P, F, A) -> + print(printable(Severity,Verbosity), Tc, P, F, A). -print(true, Ts, Tc, P, F, A) -> +print(true, Tc, P, F, A) -> io:format("*** [~s] ~s ~p ~s:~w ***" "~n " ++ F ++ "~n", - [format_timestamp(Ts), P, self(), get(sname), Tc | A]); -print(_, _, _, _, _, _) -> + [?FTS(), P, self(), get(sname), Tc | A]); +print(_, _, _, _, _) -> ok. -%% print(F, A) -> -%% io:format("*** [~s] ***" -%% "~n " ++ F ++ "~n", -%% [format_timestamp(now()) | A]). - -format_timestamp(Now) -> megaco:format_timestamp(Now). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% random_init() -> -%% {A,B,C} = now(), -%% random:seed(A,B,C). - -%% random() -> -%% 10 * random:uniform(50). - -%% apply_load_timer() -> -%% erlang:send_after(random(), self(), apply_load_timeout). - - - diff --git a/lib/megaco/test/megaco_segment_test.erl b/lib/megaco/test/megaco_segment_test.erl index ddb8b9f06b..47f708a14b 100644 --- a/lib/megaco/test/megaco_segment_test.erl +++ b/lib/megaco/test/megaco_segment_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2016. All Rights Reserved. +%% Copyright Ericsson AB 2006-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. @@ -7718,29 +7718,9 @@ await_completion(Ids) -> ?ERROR({failed, Reply}) end. -%% await_completion(Ids, Timeout) -> -%% case megaco_test_generator_lib:await_completion(Ids, Timeout) of -%% {ok, Reply} -> -%% d("OK => Reply: ~n~p", [Reply]), -%% ok; -%% {error, {OK, ERROR}} -> -%% d("ERROR => " -%% "~n OK: ~p" -%% "~n ERROR: ~p", [OK, ERROR]), -%% ?ERROR({failed, ERROR}); -%% {error, Reply} -> -%% d("ERROR => Reply: ~n~p", [Reply]), -%% ?ERROR({failed, Reply}) -%% end. - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% tim() -> -%% {A,B,C} = erlang:now(), -%% A*1000000000+B*1000+(C div 1000). - - make_node_name(Name) -> case string:tokens(atom_to_list(node()), [$@]) of [_,Host] -> @@ -7754,8 +7734,6 @@ make_node_name(Name) -> sleep(X) -> receive after X -> ok end. -%% error_msg(F,A) -> error_logger:error_msg(F ++ "~n",A). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -7763,44 +7741,31 @@ i(F) -> i(F, []). i(F, A) -> - print(info, get(verbosity), now(), get(tc), "INF", F, A). + print(info, get(verbosity), get(tc), "INF", F, A). d(F) -> d(F, []). d(F, A) -> - print(debug, get(verbosity), now(), get(tc), "DBG", F, A). + print(debug, get(verbosity), get(tc), "DBG", F, A). printable(_, debug) -> true; printable(info, info) -> true; printable(_,_) -> false. -print(Severity, Verbosity, Ts, Tc, P, F, A) -> - print(printable(Severity,Verbosity), Ts, Tc, P, F, A). +print(Severity, Verbosity, Tc, P, F, A) -> + print(printable(Severity, Verbosity), Tc, P, F, A). -print(true, Ts, Tc, P, F, A) -> +print(true, Tc, P, F, A) -> io:format("*** [~s] ~s ~p ~s:~w ***" "~n " ++ F ++ "~n", - [format_timestamp(Ts), P, self(), get(sname), Tc | A]); -print(_, _, _, _, _, _) -> + [?FTS(), P, self(), get(sname), Tc | A]); +print(_, _, _, _, _) -> ok. -format_timestamp(Now) -> megaco:format_timestamp(Now). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% random_init() -> -%% {A,B,C} = now(), -%% random:seed(A,B,C). - -%% random() -> -%% 10 * random:uniform(50). - -%% apply_load_timer() -> -%% erlang:send_after(random(), self(), apply_load_timeout). - - diff --git a/lib/megaco/test/megaco_tcp_test.erl b/lib/megaco/test/megaco_tcp_test.erl index a1865ad690..cc66a40f43 100644 --- a/lib/megaco/test/megaco_tcp_test.erl +++ b/lib/megaco/test/megaco_tcp_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2016. All Rights Reserved. +%% Copyright Ericsson AB 2000-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. @@ -117,15 +117,39 @@ end_per_testcase(Case, Config) -> %% Test case definitions %%====================================================================== all() -> - [{group, start}, {group, sending}, {group, errors}]. + [ + {group, start}, + {group, sending}, + {group, errors} + ]. groups() -> - [{start, [], - [start_normal, start_invalid_opt, start_and_stop]}, - {sending, [], [sendreceive, block_unblock]}, - {errors, [], - [socket_failure, accept_process, accept_supervisor, - connection_supervisor, tcp_server]}]. + [{start, [], start_cases()}, + {sending, [], sending_cases()}, + {errors, [], errors_cases()}]. + +start_cases() -> + [ + start_normal, + start_invalid_opt, + start_and_stop + ]. + +sending_cases() -> + [ + sendreceive, + block_unblock + ]. + +errors_cases() -> + [ + socket_failure, + accept_process, + accept_supervisor, + connection_supervisor, + tcp_server + ]. + init_per_group(_GroupName, Config) -> Config. @@ -196,17 +220,8 @@ start_and_stop(Config) when is_list(Config) -> Client = client_start_command_handler(ClientNode, ClientCmds), p("client command handler started: ~p", [Client]), - ok = - receive - {listening, Server} -> - p("received listening message from server [~p] => " - "send continue to client [~p]~n", [Server, Client]), - Client ! {continue, self()}, - ok - after 5000 -> - {error, server_timeout} - end, - + await_server_listening(Server, Client), + await_command_handler_completion([Server, Client], timer:seconds(20)), p("done"), ok. @@ -335,16 +350,7 @@ sendreceive(Config) when is_list(Config) -> Client = client_start_command_handler(ClientNode, ClientCmds), p("client command handler started: ~p", [Client]), - ok = - receive - {listening, Server} -> - p("received listening message from server [~p] => " - "send continue to client [~p]~n", [Server, Client]), - Client ! {continue, self()}, - ok - after 5000 -> - {error, server_timeout} - end, + await_server_listening(Server, Client), await_command_handler_completion([Server, Client], timer:seconds(20)), p("done"), @@ -544,27 +550,9 @@ block_unblock(Config) when is_list(Config) -> Client = client_start_command_handler(ClientNode, ClientCmds), p("client command handler started: ~p", [Client]), - ok = - receive - {listening, Server} -> - p("received listening message from server [~p] => " - "send continue to client [~p]~n", [Server, Client]), - Client ! {continue, self()}, - ok - after 5000 -> - {error, server_timeout} - end, - - ok = - receive - {blocked, Client} -> - p("received blocked message from client [~p] => " - "send continue to server [~p]~n", [Client, Server]), - Server ! {continue, self()}, - ok - after 5000 -> - {error, timeout} - end, + await_server_listening(Server, Client), + + await_client_blocked(Server, Client), await_command_handler_completion([Server, Client], timer:seconds(30)), p("done"), @@ -908,6 +896,42 @@ process_received_message(ReceiveHandle, ControlPid, SendHandle, BinMsg) %% Internal functions %%====================================================================== +await_server_listening(Server, Client) -> + receive + {listening, Server} -> + p("received listening message from server [~p] => " + "send continue to client [~p]" + "~n", [Server, Client]), + Client ! {continue, self()}, + ok + after 5000 -> + %% There is no normal reason why this should take any time. + %% Normally, this takes a few milli seconds. So, if we are not + %% up and running after 5 seconds, we give up and skip!! + exit(Server, kill), + exit(Client, kill), + ?SKIP("Server timeout (listen)") + end. + + +await_client_blocked(Server, Client) -> + receive + {blocked, Client} -> + p("received blocked message from client [~p] => " + "send continue to server [~p]~n", [Client, Server]), + Server ! {continue, self()}, + ok + after 5000 -> + %% There is no normal reason why this should take any time. + %% Normally, this takes a few milli seconds. So, if we are not + %% up and running after 5 seconds, we give up and skip!! + exit(Client, kill), + exit(Server, kill), + ?SKIP("Client timeout (blocked)") + end. + + + %% ------- Server command handler and utility functions ---------- server_start_command_handler(Node, Commands) -> @@ -1214,23 +1238,14 @@ p(F, A) -> p(S, F, A) when is_list(S) -> io:format("*** [~s] ~p ~s ***" "~n " ++ F ++ "~n", - [format_timestamp(now()), self(), S | A]); + [?FTS(), self(), S | A]); p(_S, F, A) -> io:format("*** [~s] ~p ~s *** " "~n " ++ F ++ "~n", - [format_timestamp(now()), self(), "undefined" | A]). + [?FTS(), self(), "undefined" | A]). ms() -> - {A,B,C} = erlang:now(), - A*1000000000+B*1000+(C div 1000). - + erlang:monotonic_time(milli_seconds). + -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), - lists:flatten(FormatDate). diff --git a/lib/megaco/test/megaco_test_deliver.erl b/lib/megaco/test/megaco_test_deliver.erl index 78033f0e36..c042d9c9a7 100644 --- a/lib/megaco/test/megaco_test_deliver.erl +++ b/lib/megaco/test/megaco_test_deliver.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-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. @@ -186,4 +186,4 @@ i(F) -> i(F, []). i(F, A) -> - io:format("~p ~w:" ++ F ++ "~n", [self(), ?MODULE | A]). + io:format("*** [~s] ~p ~w:" ++ F ++ "~n", [?FTS(), self(), ?MODULE | A]). diff --git a/lib/megaco/test/megaco_test_generator.erl b/lib/megaco/test/megaco_test_generator.erl index 63f66bda07..8ea7f5ddf7 100644 --- a/lib/megaco/test/megaco_test_generator.erl +++ b/lib/megaco/test/megaco_test_generator.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-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. @@ -47,7 +47,6 @@ print/3, print/4 ]). --export([behaviour_info/1]). %% Internal exports -export([start/4]). @@ -65,6 +64,7 @@ -include_lib("megaco/include/megaco.hrl"). +-include("megaco_test_lib.hrl"). %%---------------------------------------------------------------------- @@ -90,15 +90,32 @@ %%% API %%%========================================================================= -behaviour_info(callbacks) -> - [ - {init, 1}, - {handle_parse, 2}, - {handle_exec, 2}, - {terminate, 2} - ]; -behaviour_info(_Other) -> - undefined. +-callback init(Args) -> {ok, State} | {error, Reason} when + Args :: term(), + State :: term(), + Reason :: term(). + +-callback handle_parse(Instruction, State) -> + {ok, NewInstruction, NewState} | + {error, Reason} when + Instruction :: term(), + State :: term(), + NewInstruction :: term(), + NewState :: term(), + Reason :: term(). + +-callback handle_exec(Instruction, State) -> + {ok, NewState} | + {error, Reason} when + Instruction :: term(), + State :: term(), + NewState :: term(), + Reason :: term(). + +-callback terminate(Reason, State) -> + megaco:void() when + Reason :: term(), + State :: term(). %%---------------------------------------------------------------------- @@ -533,22 +550,13 @@ print(P, F, A) -> print([], undefined, F, A) -> io:format("*** [~s] ~p *** " ++ "~n " ++ F ++ "~n", - [format_timestamp(now()),self()|A]); + [?FTS(), self() | A]); print(P, undefined, F, A) -> io:format("*** [~s] ~p ~s *** " ++ "~n " ++ F ++ "~n", - [format_timestamp(now()),self(),P|A]); + [?FTS(), self(), P | A]); print(P, N, F, A) -> io:format("*** [~s] ~p ~s~s *** " ++ "~n " ++ F ++ "~n", - [format_timestamp(now()),self(),N,P|A]). - + [?FTS(), self(), N, P | A]). -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY, MM, DD} = Date, - {Hour, Min, Sec} = Time, - FormatDate = - io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY, MM, DD, Hour, Min, Sec, round(N3/1000)]), - lists:flatten(FormatDate). diff --git a/lib/megaco/test/megaco_test_generic_transport.erl b/lib/megaco/test/megaco_test_generic_transport.erl index 3185e4c6b6..cd387f748a 100644 --- a/lib/megaco/test/megaco_test_generic_transport.erl +++ b/lib/megaco/test/megaco_test_generic_transport.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-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. @@ -59,7 +59,7 @@ receive_handle}). -include_lib("megaco/include/megaco.hrl"). -%% -include("megaco_test_lib.hrl"). +-include("megaco_test_lib.hrl"). -define(SERVER, ?MODULE). @@ -335,21 +335,11 @@ d(F) -> d(F, []). d(F, A) -> - print(now(), F, A). + print(F, A). -print(Ts, F, A) -> +print(F, A) -> io:format("*** [~s] GENERIC TRANSPORT [~p] ***" "~n " ++ F ++ "~n", - [format_timestamp(Ts), self() | A]). - -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), - lists:flatten(FormatDate). - + [?FTS(), self() | A]). diff --git a/lib/megaco/test/megaco_test_lib.erl b/lib/megaco/test/megaco_test_lib.erl index 3934a3a957..7c8c287e7c 100644 --- a/lib/megaco/test/megaco_test_lib.erl +++ b/lib/megaco/test/megaco_test_lib.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. @@ -26,7 +26,47 @@ -module(megaco_test_lib). --compile(export_all). +%% -compile(export_all). + +-export([ + log/4, + error/3, + + sleep/1, + hours/1, minutes/1, seconds/1, + formated_timestamp/0, format_timestamp/1, + + skip/3, + non_pc_tc_maybe_skip/4, + os_based_skip/1, + + flush/0, + still_alive/1, + watchdog/2, + + display_alloc_info/0, + display_system_info/1, display_system_info/2, display_system_info/3, + + tickets/1, + prepare_test_case/5, + + t/1, + groups/1, + init_suite/2, + end_suite/2, + init_group/3, + end_group/3, + t/2, + init_per_testcase/2, + end_per_testcase/2, + + proxy_start/1, proxy_start/2, + + mk_nodes/1, + start_nodes/3 + ]). + +-export([do_eval/4, proxy_init/2]). -include("megaco_test_lib.hrl"). @@ -53,6 +93,13 @@ minutes(N) -> trunc(N * 1000 * 60). seconds(N) -> trunc(N * 1000). +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp(TS) -> + megaco:format_timestamp(TS). + + %% ---------------------------------------------------------------- %% Conditional skip of testcases %% @@ -271,9 +318,9 @@ t({Mod, {group, Name} = Group, Groups}, Config) [Name, Error]), [{failed, {Mod, Group}, Error}] catch - exit:{skipped, SkipReason} -> + exit:{skip, SkipReason} -> io:format(" => skipping group: ~p~n", [SkipReason]), - [{skipped, {Mod, Group}, SkipReason, 0}]; + [{skip, {Mod, Group}, SkipReason, 0}]; error:undef -> [t({Mod, Case, Groups}, Config) || Case <- GroupsAndCases]; @@ -336,9 +383,9 @@ t(Mod, Config) when is_atom(Mod) -> io:format(" => suite init failed: ~p~n", [Error]), [{failed, {Mod, init_per_suite}, Error}] catch - exit:{skipped, SkipReason} -> + exit:{skip, SkipReason} -> io:format(" => skipping suite: ~p~n", [SkipReason]), - [{skipped, {Mod, init_per_suite}, SkipReason, 0}]; + [{skip, {Mod, init_per_suite}, SkipReason, 0}]; error:undef -> [t({Mod, Case, Groups}, Config) || Case <- Cases]; T:E -> @@ -367,7 +414,7 @@ eval(Mod, Fun, Config) -> Flag = process_flag(trap_exit, true), put(megaco_test_server, true), Config2 = Mod:init_per_testcase(Fun, Config), - Pid = spawn_link(?MODULE, do_eval, [self(), Mod, Fun, Config2]), + Pid = spawn_link(fun() -> do_eval(self(), Mod, Fun, Config2) end), R = wait_for_evaluator(Pid, Mod, Fun, Config2, []), Mod:end_per_testcase(Fun, Config2), erase(megaco_test_server), @@ -411,10 +458,10 @@ wait_for_evaluator(Pid, Mod, Fun, Config, Errors, AccTime) -> megaco:report_event(20, Mod, ?MODULE, Label ++ " failed", [TestCase, Config, {return, Fail}, Errors]), {failed, {Mod,Fun}, Fail, Time}; - {'EXIT', Pid, {skipped, Reason}, Time} -> + {'EXIT', Pid, {skip, Reason}, Time} -> megaco:report_event(20, Mod, ?MODULE, Label ++ " skipped", - [TestCase, Config, {skipped, Reason}]), - {skipped, {Mod, Fun}, Errors, Time}; + [TestCase, Config, {skip, Reason}]), + {skip, {Mod, Fun}, Errors, Time}; {'EXIT', Pid, Reason, Time} -> megaco:report_event(20, Mod, ?MODULE, Label ++ " crashed", [TestCase, Config, {'EXIT', Reason}]), @@ -445,14 +492,14 @@ do_eval(ReplyTo, Mod, Fun, Config) -> error:undef -> %% p("do_eval -> error - undef", []), ReplyTo ! {'EXIT', self(), undef, 0}; - exit:{skipped, Reason} -> + exit:{skip, Reason} -> %% p("do_eval -> exit - skipped" %% "~n Reason: ~p", [Reason]), T2 = os:timestamp(), Time = timer:now_diff(T2, T1), display_tc_time(Time), display_system_info("after (skipped)", Mod, Fun), - ReplyTo ! {'EXIT', self(), {skipped, Reason}, Time}; + ReplyTo ! {'EXIT', self(), {skip, Reason}, Time}; exit:{suite_failed, Reason} -> %% p("do_eval -> exit - suite-failed" %% "~n Reason: ~p", [Reason]), @@ -569,9 +616,9 @@ display_result(Res) when is_list(Res) -> Ok = [{MF, Time} || {ok, MF, _, Time} <- Res], Nyi = [MF || {nyi, MF, _, _Time} <- Res], SkippedGrps = [{{M,G}, Reason} || - {skipped, {M, {group, G}}, Reason, _Time} <- Res], + {skip, {M, {group, G}}, Reason, _Time} <- Res], SkippedCases = [{MF, Reason} || - {skipped, {_M, F} = MF, Reason, _Time} <- Res, + {skip, {_M, F} = MF, Reason, _Time} <- Res, is_atom(F)], FailedGrps = [{{M,G}, Reason} || {failed, {M, {group, G}}, Reason, _Time} <- Res], @@ -683,15 +730,18 @@ log(Format, Args, Mod, Line) -> [self(), Mod, Line] ++ Args) end. +skip(Reason) -> + exit({skip, Reason}). + skip(Actual, File, Line) -> - log("Skipping test case~n", [], File, Line), - String = lists:flatten(io_lib:format("Skipping test case ~p(~p): ~p~n", - [File, Line, Actual])), - exit({skipped, String}). + log("Skipping test case: ~p~n", [Actual], File, Line), + String = f("~p(~p): ~p~n", [File, Line, Actual]), + skip(String). fatal_skip(Actual, File, Line) -> error(Actual, File, Line), - exit({skipped, {fatal, Actual, File, Line}}). + skip({fatal, Actual, File, Line}). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -703,7 +753,8 @@ flush() -> after 1000 -> [] end. - + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Check if process is alive and kicking still_alive(Pid) -> @@ -719,6 +770,7 @@ still_alive(Pid) -> end end. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% The proxy process @@ -730,32 +782,51 @@ proxy_start(Node, ProxyId) -> proxy_init(ProxyId, Controller) -> process_flag(trap_exit, true), - ?LOG("[~p] proxy started by ~p~n",[ProxyId, Controller]), + IdStr = proxyid2string(ProxyId), + put(id, IdStr), + ?LOG("[~s] proxy started by ~p~n", [IdStr, Controller]), proxy_loop(ProxyId, Controller). proxy_loop(OwnId, Controller) -> receive {'EXIT', Controller, Reason} -> - p("proxy_loop -> received exit from controller" + pprint("proxy_loop -> received exit from controller" + "~n Reason: ~p", [Reason]), + exit(Reason); + {stop, Controller, Reason} -> + p("proxy_loop -> received stop from controller" "~n Reason: ~p" "~n", [Reason]), exit(Reason); + {apply, Fun} -> - p("proxy_loop -> received apply request~n", []), + pprint("proxy_loop -> received apply request"), Res = Fun(), - p("proxy_loop -> apply result: " - "~n ~p" - "~n", [Res]), + pprint("proxy_loop -> apply result: " + "~n ~p", [Res]), Controller ! {res, OwnId, Res}, proxy_loop(OwnId, Controller); OtherMsg -> - p("proxy_loop -> received unknown message: " - "~n OtherMsg: ~p" - "~n", [OtherMsg]), + pprint("proxy_loop -> received unknown message: " + "~n ~p", [OtherMsg]), Controller ! {msg, OwnId, OtherMsg}, proxy_loop(OwnId, Controller) end. +proxyid2string(Id) when is_list(Id) -> + Id; +proxyid2string(Id) when is_atom(Id) -> + atom_to_list(Id); +proxyid2string(Id) -> + f("~p", [Id]). + +pprint(F) -> + pprint(F, []). + +pprint(F, A) -> + io:format("[~s] ~p ~s " ++ F ++ "~n", + [get(id), self(), formated_timestamp() | A]). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Test server callbacks @@ -837,7 +908,7 @@ reset_kill_timer(Config) -> end. watchdog(Pid, Time) -> - erlang:now(), + _ = os:timestamp(), receive stop -> ok @@ -897,7 +968,10 @@ default_config() -> [{nodes, default_nodes()}, {ts, megaco}]. default_nodes() -> - mk_nodes(2, []). + mk_nodes(3, []). + +mk_nodes(N) when (N > 0) -> + mk_nodes(N, []). mk_nodes(0, Nodes) -> Nodes; @@ -918,11 +992,14 @@ node_to_name_and_host(Node) -> start_nodes([Node | Nodes], File, Line) -> case net_adm:ping(Node) of pong -> + p("node ~p already running", [Node]), start_nodes(Nodes, File, Line); pang -> [Name, Host] = node_to_name_and_host(Node), + p("try start node ~p", [Node]), case slave:start_link(Host, Name) of {ok, NewNode} when NewNode =:= Node -> + p("node ~p started - now set path, cwd and sync", [Node]), Path = code:get_path(), {ok, Cwd} = file:get_cwd(), true = rpc:call(Node, code, set_path, [Path]), @@ -931,11 +1008,18 @@ start_nodes([Node | Nodes], File, Line) -> {_, []} = rpc:multicall(global, sync, []), start_nodes(Nodes, File, Line); Other -> + p("failed starting node ~p: ~p", [Node, Other]), fatal_skip({cannot_start_node, Node, Other}, File, Line) end end; start_nodes([], _File, _Line) -> ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + p(F, A) -> - io:format("~p~w:" ++ F ++ "~n", [self(), ?MODULE |A]). + io:format("~s ~p " ++ F ++ "~n", [?FTS(), self() | A]). diff --git a/lib/megaco/test/megaco_test_lib.hrl b/lib/megaco/test/megaco_test_lib.hrl index 79a1493c40..546ecaab9a 100644 --- a/lib/megaco/test/megaco_test_lib.hrl +++ b/lib/megaco/test/megaco_test_lib.hrl @@ -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. @@ -80,9 +80,11 @@ -define(SLEEP(MSEC), megaco_test_lib:sleep(MSEC)). --define(M(), megaco_test_lib:millis()). --define(MDIFF(A,B), megaco_test_lib:millis_diff(A,B)). - -define(HOURS(T), megaco_test_lib:hours(T)). --define(MINUTES(T), megaco_test_lib:minutes(T)). --define(SECONDS(T), megaco_test_lib:seconds(T)). +-define(MINS(T), megaco_test_lib:minutes(T)). +-define(MINUTES(T), ?MINS(T)). +-define(SECS(T), megaco_test_lib:seconds(T)). +-define(SECONDS(T), ?SECS(T)). +-define(FTS(), megaco:format_timestamp(erlang:timestamp())). +-define(FTS(TS), megaco:format_timestamp(TS)). +-define(F(F,A), lists:flatten(io_lib:format(F, A))). diff --git a/lib/megaco/test/megaco_test_megaco_generator.erl b/lib/megaco/test/megaco_test_megaco_generator.erl index 8a37fa33fe..4e0f2e334c 100644 --- a/lib/megaco/test/megaco_test_megaco_generator.erl +++ b/lib/megaco/test/megaco_test_megaco_generator.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-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. @@ -118,7 +118,7 @@ init([]) -> %% ----- instruction parser ----- handle_parse({debug, Debug} = Instruction, State) - when (Debug == true) orelse (Debug == false) -> + when is_boolean(Debug) -> {ok, Instruction, State}; handle_parse({expect_nothing, To} = Instruction, State) @@ -126,9 +126,9 @@ handle_parse({expect_nothing, To} = Instruction, State) {ok, Instruction, State}; handle_parse({megaco_trace, Level} = Instruction, State) - when (Level == disable) orelse - (Level == max) orelse - (Level == min) orelse + when (Level =:= disable) orelse + (Level =:= max) orelse + (Level =:= min) orelse is_integer(Level) -> {ok, Instruction, State}; @@ -1096,11 +1096,10 @@ handle_megaco_callback_reply(_, _, _, _) -> %%---------------------------------------------------------------------- random_init() -> - {A,B,C} = now(), - random:seed(A,B,C). + ok. random(N) -> - random:uniform(N). + rand:uniform(N). get_config(Key, Opts) -> diff --git a/lib/megaco/test/megaco_test_mg.erl b/lib/megaco/test/megaco_test_mg.erl index d6a9a8c314..38883c94f0 100644 --- a/lib/megaco/test/megaco_test_mg.erl +++ b/lib/megaco/test/megaco_test_mg.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-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. @@ -79,7 +79,10 @@ reply_counter = 0, mload_info = undefined, parent = undefined, - dsi_timer}). + dsi_timer, + evs = []}). + +-define(EVS_MAX, 10). %%% -------------------------------------------------------------------- @@ -364,7 +367,7 @@ mg(Parent, Verbosity, Config) -> MG = #mg{parent = Parent, mid = Mid, dsi_timer = DSITimer}, i("mg -> started"), put(verbosity, Verbosity), - case (catch loop(MG)) of + case (catch loop(evs(MG, started))) of {'EXIT', normal} -> exit(normal); {'EXIT', Reason} -> @@ -438,12 +441,12 @@ loop(#mg{parent = Parent, mid = Mid} = S) -> {display_system_info, Time} -> display_system_info(S#mg.mid), NewTimer = create_timer(Time, display_system_info), - loop(S#mg{dsi_timer = NewTimer}); + loop(evs(S#mg{dsi_timer = NewTimer}, {dsi, Time})); {verbosity, V, Parent} -> i("loop -> received new verbosity: ~p", [V]), put(verbosity,V), - loop(S); + loop(evs(S, {verb, V})); {stop, Parent} -> @@ -453,7 +456,7 @@ loop(#mg{parent = Parent, mid = Mid} = S) -> Res = do_stop(Mid), d("loop -> stop result: ~p", [Res]), server_reply(Parent, stopped, {ok, Res}), - exit(normal); + done(evs(S, stop), normal); {{enable_test_code, Tag, Fun}, Parent} -> i("loop -> enable_test_code: ~p, ~p", [Tag, Fun]), @@ -463,52 +466,52 @@ loop(#mg{parent = Parent, mid = Mid} = S) -> "~n ets:tab2list(megaco_test_data): ~p", [Reply,ets:tab2list(megaco_test_data)]), server_reply(Parent, enable_test_code_reply, Reply), - loop(S); + loop(evs(S, {enable_test_code, Tag})); {{encode_ar_first, EAF}, Parent} -> i("loop -> encode_ar_first: ~p", [EAF]), {Reply, S1} = handle_encode_ar_first(S, EAF), server_reply(Parent, encode_ar_first_reply, Reply), - loop(S1#mg{encode_ar_first = EAF}); + loop(evs(S1#mg{encode_ar_first = EAF}, {enc_arf, EAF})); %% Give me statistics {statistics, Parent} -> i("loop -> got request for statistics", []), Stats = do_get_statistics(Mid), server_reply(Parent, statistics_reply, {ok, Stats}), - loop(S); + loop(evs(S, stats)); {reset_stats, Parent} -> i("loop -> got request to reset stats counters", []), do_reset_stats(Mid), server_reply(Parent, reset_stats_ack, ok), - loop(S); + loop(evs(S, rst_stats)); {{user_info, Tag}, Parent} -> i("loop -> got user_info request for ~w", [Tag]), Res = do_get_user_info(Mid, Tag), d("loop -> Res: ~p", [Res]), server_reply(Parent, user_info_ack, Res), - loop(S); + loop(evs(S, {ui, Tag})); {{update_user_info, Tag, Val}, Parent} -> i("loop -> got update_user_info: ~w -> ~p", [Tag, Val]), Res = do_update_user_info(Mid, Tag, Val), d("loop -> Res: ~p", [Res]), server_reply(Parent, update_user_info_ack, Res), - loop(S); + loop(evs(S, {uui, {Tag, Val}})); {{conn_info, Tag}, Parent} -> i("loop -> got conn_info request for ~w", [Tag]), Res = do_get_conn_info(Mid, Tag), server_reply(Parent, conn_info_ack, Res), - loop(S); + loop(evs(S, {ci, Tag})); {{update_conn_info, Tag, Val}, Parent} -> i("loop -> got update_conn_info: ~w -> ~p", [Tag, Val]), Res = do_update_conn_info(Mid, Tag, Val), server_reply(Parent, update_conn_info_ack, Res), - loop(S); + loop(evs(S, {uci, {Tag, Val}})); %% Do a service change @@ -526,28 +529,28 @@ loop(#mg{parent = Parent, mid = Mid} = S) -> server_reply(Parent, service_change_reply, Error), S end, - loop(S1); + loop(evs(S1, svc_ch)); {{group_requests, N}, Parent} when N > 0 -> i("loop -> received group_requests ~p", [N]), Reply = {ok, S#mg.group_size}, server_reply(Parent, group_requests_reply, Reply), - loop(S#mg{group_size = N}); + loop(evs(S#mg{group_size = N}, {grp_reqs, N})); {{ack_info, To}, Parent} -> i("loop -> received request to inform about received ack's ", []), - loop(S#mg{ack_info = To}); + loop(evs(S#mg{ack_info = To}, {acki, To})); {{rep_info, To}, Parent} -> i("loop -> received request to inform about received rep's ", []), - loop(S#mg{rep_info = To}); + loop(evs(S#mg{rep_info = To}, {repi, To})); %% Make a sync-call {notify_request, Parent} -> i("loop -> received request to send notify request ", []), {Res, S1} = do_handle_notify_request(S), d("loop -> notify request result: ~p", [Res]), - loop(S1); + loop(evs(S1, not_req)); %% sync-call complete {notify_request_complete, NotifyReply, Pid} -> @@ -556,7 +559,7 @@ loop(#mg{parent = Parent, mid = Mid} = S) -> "~n NotifyReply: ~p", [Pid, NotifyReply]), server_reply(Parent, notify_request_reply, NotifyReply), - loop(S#mg{req_handler = undefined}); + loop(evs(S#mg{req_handler = undefined}, {not_reqc, NotifyReply})); %% cancel requests @@ -564,14 +567,14 @@ loop(#mg{parent = Parent, mid = Mid} = S) -> i("loop -> received request to cancel (all) megaco requests ", []), Res = do_cancel_requests(Mid, Reason), server_reply(Parent, cancel_request_reply, Res), - loop(S); + loop(evs(S, {creq, Reason})); %% Apply multi-load {apply_multi_load, {NL, NR}, Parent} -> i("loop -> received apply_multi_load request: ~w, ~w", [NL, NR]), S1 = start_loaders(S, NL, NR), - loop(S1); + loop(evs(S1, {apply_mload, {NL, NR}})); %% Apply some load @@ -587,12 +590,12 @@ loop(#mg{parent = Parent, mid = Mid} = S) -> server_reply(Parent, apply_load_ack, Error), S end, - loop(S1); + loop(evs(S1, {apply_load, Times})); {apply_load_timeout, _} -> d("loop -> received apply_load timeout", []), S1 = do_apply_load(S), - loop(S1); + loop(evs(S1, apply_loadto)); %% Megaco callback messages @@ -604,22 +607,35 @@ loop(#mg{parent = Parent, mid = Mid} = S) -> {Reply, S1} = handle_megaco_request(S, Request), d("loop -> send (megaco callback) request reply: ~n~p", [Reply]), From ! {reply, Reply, self()}, - loop(S1); + loop(evs(S1, {req, {Request, Mid, From}})); {'EXIT', Pid, Reason} -> - i("loop -> received exit signal from ~p: ~n~p", [Pid, Reason]), + i("loop -> received exit signal from ~p: " + "~n ~p", [Pid, Reason]), S1 = handle_exit(S, Pid, Reason), - loop(S1); + loop(evs(S1, {exit, {Pid, Reason}})); Invalid -> error_msg("received invalid request: ~n~p", [Invalid]), - loop(S) + loop(evs(S, {invalid, Invalid})) end. +evs(#mg{evs = EVS} = S, Ev) when (length(EVS) < ?EVS_MAX) -> + S#mg{evs = [{?FTS(), Ev}|EVS]}; +evs(#mg{evs = EVS} = S, Ev) -> + S#mg{evs = [{?FTS(), Ev}|lists:droplast(EVS)]}. + +done(#mg{evs = EVS}, Reason) -> + info_msg("Exiting with latest event(s): " + "~n ~p" + "~n", [EVS]), + exit(Reason). + + handle_encode_ar_first(#mg{encode_ar_first = Old} = MG, New) when (New =:= true) orelse (New =:= false) -> {{ok, Old}, MG#mg{encode_ar_first = New}}; @@ -776,7 +792,7 @@ do_service_change(#mg{state = State} = MG) -> {{error, {invalid_state, State}}, MG}. do_service_change(ConnHandle, Method, EAF, Reason) -> - d("sending service change using:" + d("send service change using:" "~n ConnHandle: ~p" "~n Method: ~p" "~n EAF: ~p" @@ -844,10 +860,10 @@ loader_main(EAF, N, CH) -> -handle_exit(#mg{parent = Pid}, Pid, Reason) -> +handle_exit(#mg{parent = Pid} = S, Pid, Reason) -> error_msg("received exit from the parent:" "~n ~p", [Reason]), - exit({parent_terminated, Reason}); + done(S, {parent_terminated, Reason}); handle_exit(#mg{parent = Parent, req_handler = Pid} = MG, Pid, Reason) -> error_msg("received unexpected exit from the request handler:" @@ -1423,6 +1439,7 @@ sleep(X) -> receive after X -> ok end. +info_msg(F,A) -> error_logger:info_msg("MG: " ++ F ++ "~n",A). error_msg(F,A) -> error_logger:error_msg("MG: " ++ F ++ "~n",A). @@ -1536,37 +1553,28 @@ print(Severity, Verbosity, P, F, A) -> print(true, P, F, A) -> io:format("*** [~s] ~s ~p ~s ***" "~n " ++ F ++ "~n~n", - [format_timestamp(now()), P, self(), get(sname) | A]); + [?FTS(), P, self(), get(sname) | A]); print(_, _, _, _) -> ok. -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), - lists:flatten(FormatDate). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% random_init() -> - {A,B,C} = now(), - random:seed(A,B,C). + ok. random() -> random(50). random(N) -> - random:uniform(N). + rand:uniform(N). display_system_info(Mid) -> display_system_info(Mid, ""). display_system_info(Mid, Pre) -> - TimeStr = format_timestamp(now()), + TimeStr = ?FTS(), MibStr = lists:flatten(io_lib:format("~p ", [Mid])), megaco_test_lib:display_system_info(MibStr ++ Pre ++ TimeStr). diff --git a/lib/megaco/test/megaco_test_mgc.erl b/lib/megaco/test/megaco_test_mgc.erl index 045bc7c9fd..a9027ca68e 100644 --- a/lib/megaco/test/megaco_test_mgc.erl +++ b/lib/megaco/test/megaco_test_mgc.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-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. @@ -75,7 +75,10 @@ abort_info = undefined, req_info = undefined, mg = [], - dsi_timer}). + dsi_timer, + evs = []}). + +-define(EVS_MAX, 10). %%% ------------------------------------------------------------------ @@ -297,7 +300,7 @@ mgc(Parent, Verbosity, Config) -> dsi_timer = DSITimer}, i("mgc -> started"), display_system_info("at start "), - loop(S) + loop(evs(S, started)) end. init(Config) -> @@ -359,7 +362,7 @@ loop(S) -> {display_system_info, Time} -> display_system_info(S#mgc.mid), NewTimer = create_timer(Time, display_system_info), - loop(S#mgc{dsi_timer = NewTimer}); + loop(evs(S#mgc{dsi_timer = NewTimer}, {dsi, Time})); {stop, Parent} when S#mgc.parent =:= Parent -> i("loop -> stopping", []), @@ -371,7 +374,7 @@ loop(S) -> application:stop(megaco), i("loop -> stopped", []), server_reply(Parent, stopped, ok), - exit(normal); + done(evs(S, stop), normal); {{disconnect, Reason}, Parent} when S#mgc.parent == Parent -> i("loop -> disconnecting", []), @@ -379,22 +382,22 @@ loop(S) -> [Conn|_] = megaco:user_info(Mid, connections), Res = megaco:disconnect(Conn, {self(), Reason}), server_reply(Parent, disconnected, Res), - loop(S); + loop(evs(S, {disconnect, Reason})); {{update_user_info, Tag, Val}, Parent} when S#mgc.parent == Parent -> i("loop -> got update_user_info: ~w -> ~p", [Tag, Val]), Res = (catch megaco:update_user_info(S#mgc.mid, Tag, Val)), d("loop -> Res: ~p", [Res]), server_reply(Parent, update_user_info_ack, Res), - loop(S); - - {{user_info, Tag}, Parent} when S#mgc.parent == Parent -> - i("loop -> got user_info request for ~w", [Tag]), - Res = (catch megaco:user_info(S#mgc.mid, Tag)), - d("loop -> Res: ~p", [Res]), - server_reply(Parent, user_info_ack, Res), - loop(S); - + loop(evs(S, {uui, {Tag, Val}})); + + {{user_info, Tag}, Parent} when S#mgc.parent == Parent -> + i("loop -> got user_info request for ~w", [Tag]), + Res = (catch megaco:user_info(S#mgc.mid, Tag)), + d("loop -> Res: ~p", [Res]), + server_reply(Parent, user_info_ack, Res), + loop(evs(S, {ui, Tag})); + {{update_conn_info, Tag, Val}, Parent} when S#mgc.parent == Parent -> i("loop -> got update_conn_info: ~w -> ~p", [Tag, Val]), Conns = megaco:user_info(S#mgc.mid, connections), @@ -404,7 +407,7 @@ loop(S) -> Res = lists:map(Fun, Conns), d("loop -> Res: ~p", [Res]), server_reply(Parent, update_conn_info_ack, Res), - loop(S); + loop(evs(S, {uci, {Tag, Val}})); {{conn_info, Tag}, Parent} when S#mgc.parent == Parent -> i("loop -> got conn_info request for ~w", [Tag]), @@ -415,11 +418,11 @@ loop(S) -> Res = lists:map(Fun, Conns), d("loop -> Res: ~p", [Res]), server_reply(Parent, conn_info_ack, Res), - loop(S); + loop(evs(S, {ci, Tag})); %% - {request_action, {Action, To}, Parent} when S#mgc.parent == Parent -> + {request_action, {Action, To}, Parent} when S#mgc.parent == Parent -> i("loop -> got new request_action: ~p:~w", [Action,To]), {Reply, S1} = case lists:member(Action, ?valid_actions) of @@ -432,7 +435,7 @@ loop(S) -> {{error, {invalid_action, Action}}, S} end, server_reply(Parent, request_action_ack, Reply), - loop(S1); + loop(evs(S1, {req_act, {Action, To}})); %% Reset stats @@ -440,7 +443,7 @@ loop(S) -> i("loop -> got request to reset stats counters"), do_reset_stats(S#mgc.mid), server_reply(Parent, reset_stats_ack, ok), - loop(S); + loop(evs(S, rst_stats)); %% Give me statistics @@ -466,7 +469,7 @@ loop(S) -> lists:map(GetTrans, megaco:user_info(Mid, connections)), Reply = {ok, [{gen, Gen}, {trans, Trans}]}, server_reply(Parent, {statistics_reply, 1}, Reply), - loop(S); + loop(evs(S, {stats, 1})); {{statistics, 2}, Parent} when S#mgc.parent == Parent -> @@ -477,7 +480,7 @@ loop(S) -> UdpStats = get_trans_stats(UdpSup, megaco_udp), Reply = {ok, [{gen, Gen}, {trans, [TcpStats, UdpStats]}]}, server_reply(Parent, {statistics_reply, 2}, Reply), - loop(S); + loop(evs(S, {stats, 2})); %% Megaco callback messages @@ -487,28 +490,28 @@ loop(S) -> {Reply, S1} = handle_megaco_request(Request, S), d("loop -> send request reply: ~n~p", [Reply]), reply(From, Reply), - loop(S1); + loop(evs(S1, {req, Request})); {ack_info, To, Parent} when S#mgc.parent == Parent -> i("loop -> received request to inform about received ack's ", []), - loop(S#mgc{ack_info = To}); + loop(evs(S#mgc{ack_info = To}, {acki, To})); {abort_info, To, Parent} when S#mgc.parent == Parent -> i("loop -> received request to inform about received aborts ", []), - loop(S#mgc{abort_info = To}); + loop(evs(S#mgc{abort_info = To}, {abi, To})); {req_info, To, Parent} when S#mgc.parent == Parent -> i("loop -> received request to inform about received req's ", []), - loop(S#mgc{req_info = To}); + loop(evs(S#mgc{req_info = To}, {reqi, To})); {verbosity, V, Parent} when S#mgc.parent == Parent -> i("loop -> received new verbosity: ~p", [V]), put(verbosity,V), - loop(S); + loop(evs(S, {verb, V})); {'EXIT', Pid, Reason} when S#mgc.tcp_sup =:= Pid -> @@ -525,7 +528,7 @@ loop(S) -> i("loop -> stopped", []), StopReason = {error, {tcp_terminated, Pid, Reason}}, server_reply(S#mgc.parent, stopped, StopReason), - exit(StopReason); + done(evs(S, {tcp_sup_exit, Reason}), StopReason); {'EXIT', Pid, Reason} when S#mgc.udp_sup =:= Pid -> @@ -542,15 +545,27 @@ loop(S) -> i("loop -> stopped", []), StopReason = {error, {udp_terminated, Pid, Reason}}, server_reply(S#mgc.parent, stopped, StopReason), - exit(StopReason); + done(evs(S, {udp_sup_exit, Reason}), StopReason); Invalid -> i("loop -> received invalid request: ~p", [Invalid]), - loop(S) + loop(evs(S, {invalid, Invalid})) end. +evs(#mgc{evs = EVS} = S, Ev) when (length(EVS) < ?EVS_MAX) -> + S#mgc{evs = [{?FTS(), Ev}|EVS]}; +evs(#mgc{evs = EVS} = S, Ev) -> + S#mgc{evs = [{?FTS(), Ev}|lists:droplast(EVS)]}. + +done(#mgc{evs = EVS}, Reason) -> + info_msg("Exiting with latest event(s): " + "~n ~p" + "~n", [EVS]), + exit(Reason). + + do_reset_stats(Mid) -> megaco:reset_stats(), do_reset_trans_stats(megaco:user_info(Mid, connections), []). @@ -825,10 +840,10 @@ handle_megaco_request({handle_trans_ack, CH, PV, AS, AD}, handle_megaco_request({handle_trans_ack, CH, PV, AS, AD}, S) -> d("handle_megaco_request(handle_trans_ack) -> entry with" - "~n CH: ~p" - "~n PV: ~p" - "~n AS: ~p" - "~n AD: ~p", [CH, PV, AS, AD]), + "~n Conn Handle: ~p" + "~n Prot Version: ~p" + "~n Ack Status: ~p" + "~n Ack Data: ~p", [CH, PV, AS, AD]), {ok, S}; handle_megaco_request({handle_unexpected_trans, CH, PV, TR}, S) -> @@ -1090,6 +1105,7 @@ sleep(X) -> receive after X -> ok end. +info_msg(F,A) -> error_logger:info_msg("MGC: " ++ F ++ "~n",A). error_msg(F,A) -> error_logger:error_msg("MGC: " ++ F ++ "~n",A). @@ -1153,18 +1169,17 @@ get_conf(Key, Config, Default) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% random_init() -> - {A,B,C} = now(), - random:seed(A,B,C). + ok. random(N) -> - random:uniform(N). + rand:uniform(N). display_system_info(Mid) -> display_system_info(Mid, ""). display_system_info(Mid, Pre) -> - TimeStr = format_timestamp(now()), + TimeStr = ?FTS(), MibStr = lists:flatten(io_lib:format("~p ", [Mid])), megaco_test_lib:display_system_info(MibStr ++ Pre ++ TimeStr). @@ -1209,14 +1224,6 @@ print(_, _, _, _) -> print(P, F, A) -> io:format("*** [~s] ~s ~p ~s ***" "~n " ++ F ++ "~n~n", - [format_timestamp(now()), P, self(), get(sname) | A]). - -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), - lists:flatten(FormatDate). + [?FTS(), P, self(), get(sname) | A]). + diff --git a/lib/megaco/test/megaco_timer_test.erl b/lib/megaco/test/megaco_timer_test.erl index 34479f7838..84c314d8ed 100644 --- a/lib/megaco/test/megaco_timer_test.erl +++ b/lib/megaco/test/megaco_timer_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% Copyright Ericsson AB 2007-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. @@ -332,7 +332,7 @@ integer_timer_start_and_expire(Config) when is_list(Config) -> receive {timeout, Timeout} -> ok - after Timeout + 100 -> + after Timeout + 500 -> tmr_stop(Ref), error(no_timeout) end, @@ -359,7 +359,14 @@ integer_timer_start_and_stop(Config) when is_list(Config) -> {timeout, Timeout} -> error(bad_timeout) after Timeout - 100 -> - tmr_stop(Ref) + case tmr_stop(Ref) of + ok -> + ok; + {ok, _} -> + ok; + CancelRes -> + ?SKIP({cancel_failed, CancelRes}) + end end, %% Make sure it does not reach us after we attempted to stop it. @@ -438,21 +445,6 @@ print1(_, _, _, _) -> print(Prefix, F, A) -> io:format("*** [~s] ~s ~p ~s:~w ***" "~n " ++ F ++ "~n", - [formated_timestamp(), Prefix, self(), get(sname), get(tc) | A]). + [?FTS(), Prefix, self(), get(sname), get(tc) | A]). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -formated_timestamp() -> - format_timestamp(now()). - -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), - lists:flatten(FormatDate). diff --git a/lib/megaco/test/megaco_trans_test.erl b/lib/megaco/test/megaco_trans_test.erl index 9786307860..37bc134c8d 100644 --- a/lib/megaco/test/megaco_trans_test.erl +++ b/lib/megaco/test/megaco_trans_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-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. @@ -27,7 +27,46 @@ %%---------------------------------------------------------------------- -module(megaco_trans_test). --compile(export_all). +%% -compile(export_all). +-export([ + all/0, + groups/0, + init_per_group/2, end_per_group/2, + init_per_testcase/2, end_per_testcase/2, + + single_ack/1, + multi_ack_timeout/1, + multi_ack_maxcount/1, + + single_trans_req/1, + multi_trans_req_timeout/1, + multi_trans_req_maxcount1/1, + multi_trans_req_maxcount2/1, + multi_trans_req_maxsize1/1, + multi_trans_req_maxsize2/1, + + single_trans_req_and_ack/1, + multi_trans_req_and_ack_timeout/1, + multi_trans_req_and_ack_ackmaxcount/1, + multi_trans_req_and_ack_reqmaxcount/1, + multi_trans_req_and_ack_maxsize1/1, + multi_trans_req_and_ack_maxsize2/1, + + single_trans_req_and_pending/1, + multi_trans_req_and_pending/1, + multi_trans_req_and_ack_and_pending/1, + multi_ack_and_pending/1, + + multi_trans_req_and_reply/1, + multi_trans_req_and_ack_and_reply/1, + multi_ack_and_reply/1, + + otp_7192_1/1, + otp_7192_2/1, + otp_7192_3/1, + + t/0, t/1 + ]). -include("megaco_test_lib.hrl"). -include_lib("megaco/include/megaco.hrl"). @@ -44,44 +83,42 @@ -define(A5555, ["11111111", "11111111", "00000000"]). -define(A5556, ["11111111", "11111111", "11111111"]). --define(MGC_START(Pid, Mid, ET, Verb), - megaco_test_mgc:start(Pid, Mid, ET, Verb)). --define(MGC_STOP(Pid), megaco_test_mgc:stop(Pid)). --define(MGC_GET_STATS(Pid, No), megaco_test_mgc:get_stats(Pid, No)). --define(MGC_RESET_STATS(Pid), megaco_test_mgc:reset_stats(Pid)). --define(MGC_REQ_DISC(Pid,To), megaco_test_mgc:request_discard(Pid,To)). --define(MGC_REQ_PEND(Pid,To), megaco_test_mgc:request_pending(Pid,To)). --define(MGC_REQ_HAND(Pid), megaco_test_mgc:request_handle(Pid)). --define(MGC_REQ_HANDS(Pid), megaco_test_mgc:request_handle_sloppy(Pid)). --define(MGC_UPDATE_UI(Pid,Tag,Val), - megaco_test_mgc:update_user_info(Pid,Tag,Val)). --define(MGC_UPDATE_CI(Pid,Tag,Val), - megaco_test_mgc:update_conn_info(Pid,Tag,Val)). --define(MGC_USER_INFO(Pid,Tag), megaco_test_mgc:user_info(Pid,Tag)). --define(MGC_CONN_INFO(Pid,Tag), megaco_test_mgc:conn_info(Pid,Tag)). --define(MGC_ACK_INFO(Pid,To), megaco_test_mgc:ack_info(Pid,To)). --define(MGC_REQ_INFO(Pid,To), megaco_test_mgc:req_info(Pid,To)). +-define(MG, megaco_test_mg). +-define(MGC, megaco_test_mgc). + +-define(MGC_START(Pid, Mid, ET, Verb), ?MGC:start(Pid, Mid, ET, Verb)). +-define(MGC_STOP(Pid), ?MGC:stop(Pid)). +-define(MGC_GET_STATS(Pid, No), ?MGC:get_stats(Pid, No)). +-define(MGC_RESET_STATS(Pid), ?MGC:reset_stats(Pid)). +-define(MGC_REQ_DISC(Pid,To), ?MGC:request_discard(Pid,To)). +-define(MGC_REQ_PEND(Pid,To), ?MGC:request_pending(Pid,To)). +-define(MGC_REQ_HAND(Pid), ?MGC:request_handle(Pid)). +-define(MGC_REQ_HANDS(Pid), ?MGC:request_handle_sloppy(Pid)). +-define(MGC_UPDATE_UI(Pid,Tag,Val), ?MGC:update_user_info(Pid,Tag,Val)). +-define(MGC_UPDATE_CI(Pid,Tag,Val), ?MGC:update_conn_info(Pid,Tag,Val)). +-define(MGC_USER_INFO(Pid,Tag), ?MGC:user_info(Pid,Tag)). +-define(MGC_CONN_INFO(Pid,Tag), ?MGC:conn_info(Pid,Tag)). +-define(MGC_ACK_INFO(Pid,To), ?MGC:ack_info(Pid,To)). +-define(MGC_REQ_INFO(Pid,To), ?MGC:req_info(Pid,To)). -define(MG_START(Pid, Mid, Enc, Transp, Conf, Verb), - megaco_test_mg:start(Pid, Mid, Enc, Transp, Conf, Verb)). --define(MG_STOP(Pid), megaco_test_mg:stop(Pid)). --define(MG_GET_STATS(Pid), megaco_test_mg:get_stats(Pid)). --define(MG_RESET_STATS(Pid), megaco_test_mg:reset_stats(Pid)). --define(MG_SERV_CHANGE(Pid), megaco_test_mg:service_change(Pid)). --define(MG_NOTIF_RAR(Pid), megaco_test_mg:notify_request_and_reply(Pid)). --define(MG_NOTIF_REQ(Pid), megaco_test_mg:notify_request(Pid)). --define(MG_NOTIF_AR(Pid), megaco_test_mg:await_notify_reply(Pid)). --define(MG_CANCEL(Pid,R), megaco_test_mg:cancel_request(Pid,R)). --define(MG_APPLY_LOAD(Pid,CntStart), megaco_test_mg:apply_load(Pid,CntStart)). --define(MG_UPDATE_UI(Pid,Tag,Val), - megaco_test_mg:update_user_info(Pid,Tag,Val)). --define(MG_UPDATE_CI(Pid,Tag,Val), - megaco_test_mg:update_conn_info(Pid,Tag,Val)). --define(MG_USER_INFO(Pid,Tag), megaco_test_mg:user_info(Pid,Tag)). --define(MG_CONN_INFO(Pid,Tag), megaco_test_mg:conn_info(Pid,Tag)). --define(MG_GRP_REQ(Pid,N), megaco_test_mg:group_requests(Pid,N)). --define(MG_ACK_INFO(Pid,To), megaco_test_mg:ack_info(Pid,To)). --define(MG_REP_INFO(Pid,To), megaco_test_mg:rep_info(Pid,To)). + ?MG:start(Pid, Mid, Enc, Transp, Conf, Verb)). +-define(MG_STOP(Pid), ?MG:stop(Pid)). +-define(MG_GET_STATS(Pid), ?MG:get_stats(Pid)). +-define(MG_RESET_STATS(Pid), ?MG:reset_stats(Pid)). +-define(MG_SERV_CHANGE(Pid), ?MG:service_change(Pid)). +-define(MG_NOTIF_RAR(Pid), ?MG:notify_request_and_reply(Pid)). +-define(MG_NOTIF_REQ(Pid), ?MG:notify_request(Pid)). +-define(MG_NOTIF_AR(Pid), ?MG:await_notify_reply(Pid)). +-define(MG_CANCEL(Pid,R), ?MG:cancel_request(Pid,R)). +-define(MG_APPLY_LOAD(Pid,CntStart), ?MG:apply_load(Pid,CntStart)). +-define(MG_UPDATE_UI(Pid,Tag,Val), ?MG:update_user_info(Pid,Tag,Val)). +-define(MG_UPDATE_CI(Pid,Tag,Val), ?MG:update_conn_info(Pid,Tag,Val)). +-define(MG_USER_INFO(Pid,Tag), ?MG:user_info(Pid,Tag)). +-define(MG_CONN_INFO(Pid,Tag), ?MG:conn_info(Pid,Tag)). +-define(MG_GRP_REQ(Pid,N), ?MG:group_requests(Pid,N)). +-define(MG_ACK_INFO(Pid,To), ?MG:ack_info(Pid,To)). +-define(MG_REP_INFO(Pid,To), ?MG:rep_info(Pid,To)). t() -> megaco_test_lib:t(?MODULE). t(Case) -> megaco_test_lib:t({?MODULE, Case}). @@ -104,35 +141,77 @@ end_per_testcase(Case, Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% all() -> - [{group, ack}, {group, trans_req}, - {group, trans_req_and_ack}, {group, pending}, - {group, reply}, {group, tickets}]. + [{group, ack}, + {group, trans_req}, + {group, trans_req_and_ack}, + {group, pending}, + {group, reply}, + {group, tickets}]. groups() -> - [{ack, [], - [single_ack, multi_ack_timeout, multi_ack_maxcount]}, - {trans_req, [], - [single_trans_req, multi_trans_req_timeout, - multi_trans_req_maxcount1, multi_trans_req_maxcount2, - multi_trans_req_maxsize1, multi_trans_req_maxsize2]}, - {trans_req_and_ack, [], - [single_trans_req_and_ack, - multi_trans_req_and_ack_timeout, - multi_trans_req_and_ack_ackmaxcount, - multi_trans_req_and_ack_reqmaxcount, - multi_trans_req_and_ack_maxsize1, - multi_trans_req_and_ack_maxsize2]}, - {pending, [], - [single_trans_req_and_pending, - multi_trans_req_and_pending, - multi_trans_req_and_ack_and_pending, - multi_ack_and_pending]}, - {reply, [], - [multi_trans_req_and_reply, - multi_trans_req_and_ack_and_reply, - multi_ack_and_reply]}, - {tickets, [], [{group, otp_7192}]}, - {otp_7192, [], [otp_7192_1, otp_7192_2, otp_7192_3]}]. + [ + {ack, [], ack_cases()}, + {trans_req, [], trans_req_cases()}, + {trans_req_and_ack, [], trans_req_and_ack_cases()}, + {pending, [], pending_cases()}, + {reply, [], reply_cases()}, + {tickets, [], tickets_cases()}, + {otp_7192, [], otp_7192_cases()} + ]. + +ack_cases() -> + [ + single_ack, + multi_ack_timeout, + multi_ack_maxcount + ]. + +trans_req_cases() -> + [ + single_trans_req, + multi_trans_req_timeout, + multi_trans_req_maxcount1, + multi_trans_req_maxcount2, + multi_trans_req_maxsize1, + multi_trans_req_maxsize2 + ]. + +trans_req_and_ack_cases() -> + [ + single_trans_req_and_ack, + multi_trans_req_and_ack_timeout, + multi_trans_req_and_ack_ackmaxcount, + multi_trans_req_and_ack_reqmaxcount, + multi_trans_req_and_ack_maxsize1, + multi_trans_req_and_ack_maxsize2 + ]. + +pending_cases() -> + [ + single_trans_req_and_pending, + multi_trans_req_and_pending, + multi_trans_req_and_ack_and_pending, + multi_ack_and_pending + ]. + +reply_cases() -> + [ + multi_trans_req_and_reply, + multi_trans_req_and_ack_and_reply, + multi_ack_and_reply + ]. + +tickets_cases() -> + [ + {group, otp_7192} + ]. + +otp_7192_cases() -> + [ + otp_7192_1, + otp_7192_2, + otp_7192_3 + ]. init_per_group(_GroupName, Config) -> Config. @@ -156,8 +235,8 @@ single_ack(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), ok = megaco_test_lib:start_nodes([MgcNode, MgNode], ?FILE, ?LINE), @@ -216,7 +295,7 @@ multi_ack_timeout(doc) -> []; multi_ack_timeout(Config) when is_list(Config) -> %% <CONDITIONAL-SKIP> - Skippable = [win32, {unix, [darwin, linux]}], + Skippable = [win32, {unix, [darwin, linux, sunos]}], % Is there any left? Condition = fun() -> ?OS_BASED_SKIP(Skippable) end, ?NON_PC_TC_MAYBE_SKIP(Config, Condition), %% </CONDITIONAL-SKIP> @@ -231,8 +310,8 @@ multi_ack_timeout(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), ok = megaco_test_lib:start_nodes([MgcNode, MgNode], ?FILE, ?LINE), @@ -308,8 +387,8 @@ multi_ack_maxcount(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), ok = megaco_test_lib:start_nodes([MgcNode, MgNode], ?FILE, ?LINE), @@ -393,8 +472,8 @@ single_trans_req(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), ok = megaco_test_lib:start_nodes([MgcNode, MgNode], ?FILE, ?LINE), @@ -628,26 +707,26 @@ str_mgc_service_change_reply_ar(Mid, Cid) -> CR = cre_cmdReply(SCR), cre_actionReply(Cid, [CR]). -str_mgc_service_change_reply_msg(Mid, TransId, Cid) -> - AR = str_mgc_service_change_reply_ar(Mid, Cid), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% str_mgc_service_change_reply_msg(Mid, TransId, Cid) -> +%% AR = str_mgc_service_change_reply_ar(Mid, Cid), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). str_mgc_notify_reply_ar(Cid, TermId) -> NR = cre_notifyReply([TermId]), CR = cre_cmdReply(NR), cre_actionReply(Cid, [CR]). -str_mgc_notify_reply(Mid, TransId, Cid, TermId) -> - AR = str_mgc_notify_reply_ar(Cid, TermId), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% str_mgc_notify_reply(Mid, TransId, Cid, TermId) -> +%% AR = str_mgc_notify_reply_ar(Cid, TermId), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -778,12 +857,12 @@ str_mg_service_change_request_ar(_Mid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -str_mg_service_change_request_msg(Mid, TransId, Cid) -> - AR = str_mg_service_change_request_ar(Mid, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% str_mg_service_change_request_msg(Mid, TransId, Cid) -> +%% AR = str_mg_service_change_request_ar(Mid, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). str_mg_notify_request_ar(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "22000000"), @@ -794,12 +873,12 @@ str_mg_notify_request_ar(Rid, Tid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -str_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> - AR = str_mg_notify_request_ar(Rid, TermId, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% str_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> +%% AR = str_mg_notify_request_ar(Rid, TermId, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -827,8 +906,8 @@ multi_trans_req_timeout(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), ok = megaco_test_lib:start_nodes([MgcNode, MgNode], ?FILE, ?LINE), @@ -1063,26 +1142,26 @@ mtrt_mgc_service_change_reply_ar(Mid, Cid) -> CR = cre_cmdReply(SCR), cre_actionReply(Cid, [CR]). -mtrt_mgc_service_change_reply_msg(Mid, TransId, Cid) -> - AR = mtrt_mgc_service_change_reply_ar(Mid, Cid), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrt_mgc_service_change_reply_msg(Mid, TransId, Cid) -> +%% AR = mtrt_mgc_service_change_reply_ar(Mid, Cid), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrt_mgc_notify_reply_ar(Cid, TermId) -> NR = cre_notifyReply([TermId]), CR = cre_cmdReply(NR), cre_actionReply(Cid, [CR]). -mtrt_mgc_notify_reply(Mid, TransId, Cid, TermId) -> - AR = mtrt_mgc_notify_reply_ar(Cid, TermId), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrt_mgc_notify_reply(Mid, TransId, Cid, TermId) -> +%% AR = mtrt_mgc_notify_reply_ar(Cid, TermId), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -1224,12 +1303,12 @@ mtrt_mg_service_change_request_ar(_Mid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtrt_mg_service_change_request_msg(Mid, TransId, Cid) -> - AR = mtrt_mg_service_change_request_ar(Mid, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrt_mg_service_change_request_msg(Mid, TransId, Cid) -> +%% AR = mtrt_mg_service_change_request_ar(Mid, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrt_mg_notify_request_ar(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "22000000"), @@ -1240,12 +1319,12 @@ mtrt_mg_notify_request_ar(Rid, Tid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtrt_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> - AR = mtrt_mg_notify_request_ar(Rid, TermId, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrt_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> +%% AR = mtrt_mg_notify_request_ar(Rid, TermId, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -1273,8 +1352,8 @@ multi_trans_req_maxcount1(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), ok = megaco_test_lib:start_nodes([MgcNode, MgNode], ?FILE, ?LINE), @@ -1509,26 +1588,26 @@ mtrmc1_mgc_service_change_reply_ar(Mid, Cid) -> CR = cre_cmdReply(SCR), cre_actionReply(Cid, [CR]). -mtrmc1_mgc_service_change_reply_msg(Mid, TransId, Cid) -> - AR = mtrmc1_mgc_service_change_reply_ar(Mid, Cid), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrmc1_mgc_service_change_reply_msg(Mid, TransId, Cid) -> +%% AR = mtrmc1_mgc_service_change_reply_ar(Mid, Cid), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrmc1_mgc_notify_reply_ar(Cid, TermId) -> NR = cre_notifyReply([TermId]), CR = cre_cmdReply(NR), cre_actionReply(Cid, [CR]). -mtrmc1_mgc_notify_reply(Mid, TransId, Cid, TermId) -> - AR = mtrmc1_mgc_notify_reply_ar(Cid, TermId), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrmc1_mgc_notify_reply(Mid, TransId, Cid, TermId) -> +%% AR = mtrmc1_mgc_notify_reply_ar(Cid, TermId), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -1675,12 +1754,12 @@ mtrmc1_mg_service_change_request_ar(_Mid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtrmc1_mg_service_change_request_msg(Mid, TransId, Cid) -> - AR = mtrmc1_mg_service_change_request_ar(Mid, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrmc1_mg_service_change_request_msg(Mid, TransId, Cid) -> +%% AR = mtrmc1_mg_service_change_request_ar(Mid, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrmc1_mg_notify_request_ar(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "22000000"), @@ -1691,12 +1770,12 @@ mtrmc1_mg_notify_request_ar(Rid, Tid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtrmc1_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> - AR = mtrmc1_mg_notify_request_ar(Rid, TermId, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrmc1_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> +%% AR = mtrmc1_mg_notify_request_ar(Rid, TermId, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -1725,8 +1804,8 @@ multi_trans_req_maxcount2(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), ok = megaco_test_lib:start_nodes([MgcNode, MgNode], ?FILE, ?LINE), @@ -1978,13 +2057,13 @@ mtrmc2_mgc_service_change_reply_ar(Mid, Cid) -> CR = cre_cmdReply(SCR), cre_actionReply(Cid, [CR]). -mtrmc2_mgc_service_change_reply_msg(Mid, TransId, Cid) -> - AR = mtrmc2_mgc_service_change_reply_ar(Mid, Cid), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrmc2_mgc_service_change_reply_msg(Mid, TransId, Cid) -> +%% AR = mtrmc2_mgc_service_change_reply_ar(Mid, Cid), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrmc2_mgc_notify_reply_ar1(Cid, Tid) -> NR = cre_notifyReply([Tid]), @@ -1995,13 +2074,13 @@ mtrmc2_mgc_notify_reply_ar2(Cid, Tids) -> CRs = [cre_cmdReply(cre_notifyReply([Tid])) || Tid <- Tids], cre_actionReply(Cid, CRs). -mtrmc2_mgc_notify_reply(Mid, TransId, Cid, TermId) -> - AR = mtrmc2_mgc_notify_reply_ar1(Cid, TermId), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrmc2_mgc_notify_reply(Mid, TransId, Cid, TermId) -> +%% AR = mtrmc2_mgc_notify_reply_ar1(Cid, TermId), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -2163,12 +2242,12 @@ mtrmc2_mg_service_change_request_ar(_Mid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtrmc2_mg_service_change_request_msg(Mid, TransId, Cid) -> - AR = mtrmc2_mg_service_change_request_ar(Mid, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrmc2_mg_service_change_request_msg(Mid, TransId, Cid) -> +%% AR = mtrmc2_mg_service_change_request_ar(Mid, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrmc2_mg_notify_request_ar1(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "22000000"), @@ -2194,12 +2273,12 @@ mtrmc2_mg_notify_request_ar2(Rid, Tid, Cid) -> CRs = [F(N) || N <- Ns], cre_actionReq(Cid, CRs). -mtrmc2_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> - AR = mtrmc2_mg_notify_request_ar1(Rid, TermId, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrmc2_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> +%% AR = mtrmc2_mg_notify_request_ar1(Rid, TermId, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -2229,8 +2308,8 @@ multi_trans_req_maxsize1(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), ok = megaco_test_lib:start_nodes([MgcNode, MgNode], ?FILE, ?LINE), @@ -2466,26 +2545,26 @@ mtrms1_mgc_service_change_reply_ar(Mid, Cid) -> CR = cre_cmdReply(SCR), cre_actionReply(Cid, [CR]). -mtrms1_mgc_service_change_reply_msg(Mid, TransId, Cid) -> - AR = mtrms1_mgc_service_change_reply_ar(Mid, Cid), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrms1_mgc_service_change_reply_msg(Mid, TransId, Cid) -> +%% AR = mtrms1_mgc_service_change_reply_ar(Mid, Cid), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrms1_mgc_notify_reply_ar1(Cid, Tid) -> NR = cre_notifyReply([Tid]), CR = cre_cmdReply(NR), cre_actionReply(Cid, [CR]). -mtrms1_mgc_notify_reply(Mid, TransId, Cid, TermId) -> - AR = mtrms1_mgc_notify_reply_ar1(Cid, TermId), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrms1_mgc_notify_reply(Mid, TransId, Cid, TermId) -> +%% AR = mtrms1_mgc_notify_reply_ar1(Cid, TermId), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -2633,12 +2712,12 @@ mtrms1_mg_service_change_request_ar(_Mid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtrms1_mg_service_change_request_msg(Mid, TransId, Cid) -> - AR = mtrms1_mg_service_change_request_ar(Mid, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrms1_mg_service_change_request_msg(Mid, TransId, Cid) -> +%% AR = mtrms1_mg_service_change_request_ar(Mid, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrms1_mg_notify_request_ar1(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "22000000"), @@ -2649,18 +2728,16 @@ mtrms1_mg_notify_request_ar1(Rid, Tid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtrms1_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> - AR = mtrms1_mg_notify_request_ar1(Rid, TermId, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrms1_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> +%% AR = mtrms1_mg_notify_request_ar1(Rid, TermId, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrms1_err_desc(T) -> - EC = ?megaco_internal_gateway_error, - ET = lists:flatten(io_lib:format("~w",[T])), - #'ErrorDescriptor'{errorCode = EC, errorText = ET}. + cre_ErrDesc(T). @@ -2681,8 +2758,8 @@ multi_trans_req_maxsize2(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), ok = megaco_test_lib:start_nodes([MgcNode, MgNode], ?FILE, ?LINE), @@ -2934,13 +3011,13 @@ mtrms2_mgc_service_change_reply_ar(Mid, Cid) -> CR = cre_cmdReply(SCR), cre_actionReply(Cid, [CR]). -mtrms2_mgc_service_change_reply_msg(Mid, TransId, Cid) -> - AR = mtrms2_mgc_service_change_reply_ar(Mid, Cid), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrms2_mgc_service_change_reply_msg(Mid, TransId, Cid) -> +%% AR = mtrms2_mgc_service_change_reply_ar(Mid, Cid), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrms2_mgc_notify_reply_ar1(Cid, Tid) -> NR = cre_notifyReply([Tid]), @@ -2951,13 +3028,13 @@ mtrms2_mgc_notify_reply_ar2(Cid, Tids) -> CRs = [cre_cmdReply(cre_notifyReply([Tid])) || Tid <- Tids], cre_actionReply(Cid, CRs). -mtrms2_mgc_notify_reply(Mid, TransId, Cid, TermId) -> - AR = mtrms2_mgc_notify_reply_ar1(Cid, TermId), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrms2_mgc_notify_reply(Mid, TransId, Cid, TermId) -> +%% AR = mtrms2_mgc_notify_reply_ar1(Cid, TermId), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -3115,12 +3192,12 @@ mtrms2_mg_service_change_request_ar(_Mid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtrms2_mg_service_change_request_msg(Mid, TransId, Cid) -> - AR = mtrms2_mg_service_change_request_ar(Mid, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrms2_mg_service_change_request_msg(Mid, TransId, Cid) -> +%% AR = mtrms2_mg_service_change_request_ar(Mid, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrms2_mg_notify_request_ar1(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "22000000"), @@ -3146,18 +3223,16 @@ mtrms2_mg_notify_request_ar2(Rid, Tid, Cid) -> CRs = [F(N) || N <- Ns], cre_actionReq(Cid, CRs). -mtrms2_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> - AR = mtrms2_mg_notify_request_ar1(Rid, TermId, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrms2_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> +%% AR = mtrms2_mg_notify_request_ar1(Rid, TermId, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrms2_err_desc(T) -> - EC = ?megaco_internal_gateway_error, - ET = lists:flatten(io_lib:format("~w",[T])), - #'ErrorDescriptor'{errorCode = EC, errorText = ET}. + cre_ErrDesc(T). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -3175,8 +3250,8 @@ single_trans_req_and_ack(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), ok = megaco_test_lib:start_nodes([MgcNode, MgNode], ?FILE, ?LINE), @@ -3439,26 +3514,26 @@ straa_mgc_service_change_reply_ar(Mid, Cid) -> CR = cre_cmdReply(SCR), cre_actionReply(Cid, [CR]). -straa_mgc_service_change_reply_msg(Mid, TransId, Cid) -> - AR = straa_mgc_service_change_reply_ar(Mid, Cid), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% straa_mgc_service_change_reply_msg(Mid, TransId, Cid) -> +%% AR = straa_mgc_service_change_reply_ar(Mid, Cid), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). straa_mgc_notify_reply_ar(Cid, TermId) -> NR = cre_notifyReply([TermId]), CR = cre_cmdReply(NR), cre_actionReply(Cid, [CR]). -straa_mgc_notify_reply(Mid, TransId, Cid, TermId) -> - AR = straa_mgc_notify_reply_ar(Cid, TermId), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% straa_mgc_notify_reply(Mid, TransId, Cid, TermId) -> +%% AR = straa_mgc_notify_reply_ar(Cid, TermId), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -3605,12 +3680,12 @@ straa_mg_service_change_request_ar(_Mid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -straa_mg_service_change_request_msg(Mid, TransId, Cid) -> - AR = straa_mg_service_change_request_ar(Mid, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% straa_mg_service_change_request_msg(Mid, TransId, Cid) -> +%% AR = straa_mg_service_change_request_ar(Mid, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). straa_mg_notify_request_ar(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "22000000"), @@ -3621,12 +3696,12 @@ straa_mg_notify_request_ar(Rid, Tid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -straa_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> - AR = straa_mg_notify_request_ar(Rid, TermId, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% straa_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> +%% AR = straa_mg_notify_request_ar(Rid, TermId, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -3634,9 +3709,7 @@ straa_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> %% straa_err_desc(T) -> - EC = ?megaco_internal_gateway_error, - ET = lists:flatten(io_lib:format("~w",[T])), - #'ErrorDescriptor'{errorCode = EC, errorText = ET}. + cre_ErrDesc(T). @@ -3656,8 +3729,8 @@ multi_trans_req_and_ack_timeout(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), ok = megaco_test_lib:start_nodes([MgcNode, MgNode], ?FILE, ?LINE), @@ -3926,26 +3999,26 @@ mtrtaat_mgc_service_change_reply_ar(Mid, Cid) -> CR = cre_cmdReply(SCR), cre_actionReply(Cid, [CR]). -mtrtaat_mgc_service_change_reply_msg(Mid, TransId, Cid) -> - AR = mtrtaat_mgc_service_change_reply_ar(Mid, Cid), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaat_mgc_service_change_reply_msg(Mid, TransId, Cid) -> +%% AR = mtrtaat_mgc_service_change_reply_ar(Mid, Cid), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrtaat_mgc_notify_reply_ar(Cid, TermId) -> NR = cre_notifyReply([TermId]), CR = cre_cmdReply(NR), cre_actionReply(Cid, [CR]). -mtrtaat_mgc_notify_reply(Mid, TransId, Cid, TermId) -> - AR = mtrtaat_mgc_notify_reply_ar(Cid, TermId), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaat_mgc_notify_reply(Mid, TransId, Cid, TermId) -> +%% AR = mtrtaat_mgc_notify_reply_ar(Cid, TermId), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -4098,12 +4171,12 @@ mtrtaat_mg_service_change_request_ar(_Mid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtrtaat_mg_service_change_request_msg(Mid, TransId, Cid) -> - AR = mtrtaat_mg_service_change_request_ar(Mid, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaat_mg_service_change_request_msg(Mid, TransId, Cid) -> +%% AR = mtrtaat_mg_service_change_request_ar(Mid, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrtaat_mg_notify_request_ar(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "22000000"), @@ -4114,12 +4187,12 @@ mtrtaat_mg_notify_request_ar(Rid, Tid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtrtaat_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> - AR = mtrtaat_mg_notify_request_ar(Rid, TermId, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaat_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> +%% AR = mtrtaat_mg_notify_request_ar(Rid, TermId, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -4127,9 +4200,7 @@ mtrtaat_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> %% mtrtaat_err_desc(T) -> - EC = ?megaco_internal_gateway_error, - ET = lists:flatten(io_lib:format("~w",[T])), - #'ErrorDescriptor'{errorCode = EC, errorText = ET}. + cre_ErrDesc(T). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -4147,8 +4218,8 @@ multi_trans_req_and_ack_ackmaxcount(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), ok = megaco_test_lib:start_nodes([MgcNode, MgNode], ?FILE, ?LINE), @@ -4422,26 +4493,26 @@ mtrtaaamc_mgc_service_change_reply_ar(Mid, Cid) -> CR = cre_cmdReply(SCR), cre_actionReply(Cid, [CR]). -mtrtaaamc_mgc_service_change_reply_msg(Mid, TransId, Cid) -> - AR = mtrtaaamc_mgc_service_change_reply_ar(Mid, Cid), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaaamc_mgc_service_change_reply_msg(Mid, TransId, Cid) -> +%% AR = mtrtaaamc_mgc_service_change_reply_ar(Mid, Cid), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrtaaamc_mgc_notify_reply_ar(Cid, TermId) -> NR = cre_notifyReply([TermId]), CR = cre_cmdReply(NR), cre_actionReply(Cid, [CR]). -mtrtaaamc_mgc_notify_reply(Mid, TransId, Cid, TermId) -> - AR = mtrtaaamc_mgc_notify_reply_ar(Cid, TermId), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaaamc_mgc_notify_reply(Mid, TransId, Cid, TermId) -> +%% AR = mtrtaaamc_mgc_notify_reply_ar(Cid, TermId), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -4596,12 +4667,12 @@ mtrtaaamc_mg_service_change_request_ar(_Mid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtrtaaamc_mg_service_change_request_msg(Mid, TransId, Cid) -> - AR = mtrtaaamc_mg_service_change_request_ar(Mid, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaaamc_mg_service_change_request_msg(Mid, TransId, Cid) -> +%% AR = mtrtaaamc_mg_service_change_request_ar(Mid, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrtaaamc_mg_notify_request_ar(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "22000000"), @@ -4612,12 +4683,12 @@ mtrtaaamc_mg_notify_request_ar(Rid, Tid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtrtaaamc_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> - AR = mtrtaaamc_mg_notify_request_ar(Rid, TermId, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaaamc_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> +%% AR = mtrtaaamc_mg_notify_request_ar(Rid, TermId, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -4625,9 +4696,7 @@ mtrtaaamc_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> %% mtrtaaamc_err_desc(T) -> - EC = ?megaco_internal_gateway_error, - ET = lists:flatten(io_lib:format("~w",[T])), - #'ErrorDescriptor'{errorCode = EC, errorText = ET}. + cre_ErrDesc(T). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -4645,8 +4714,8 @@ multi_trans_req_and_ack_reqmaxcount(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), ok = megaco_test_lib:start_nodes([MgcNode, MgNode], ?FILE, ?LINE), @@ -4919,26 +4988,26 @@ mtrtaarac_mgc_service_change_reply_ar(Mid, Cid) -> CR = cre_cmdReply(SCR), cre_actionReply(Cid, [CR]). -mtrtaarac_mgc_service_change_reply_msg(Mid, TransId, Cid) -> - AR = mtrtaarac_mgc_service_change_reply_ar(Mid, Cid), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaarac_mgc_service_change_reply_msg(Mid, TransId, Cid) -> +%% AR = mtrtaarac_mgc_service_change_reply_ar(Mid, Cid), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrtaarac_mgc_notify_reply_ar(Cid, TermId) -> NR = cre_notifyReply([TermId]), CR = cre_cmdReply(NR), cre_actionReply(Cid, [CR]). -mtrtaarac_mgc_notify_reply(Mid, TransId, Cid, TermId) -> - AR = mtrtaarac_mgc_notify_reply_ar(Cid, TermId), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaarac_mgc_notify_reply(Mid, TransId, Cid, TermId) -> +%% AR = mtrtaarac_mgc_notify_reply_ar(Cid, TermId), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -5093,12 +5162,12 @@ mtrtaarac_mg_service_change_request_ar(_Mid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtrtaarac_mg_service_change_request_msg(Mid, TransId, Cid) -> - AR = mtrtaarac_mg_service_change_request_ar(Mid, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaarac_mg_service_change_request_msg(Mid, TransId, Cid) -> +%% AR = mtrtaarac_mg_service_change_request_ar(Mid, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrtaarac_mg_notify_request_ar(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "22000000"), @@ -5109,12 +5178,12 @@ mtrtaarac_mg_notify_request_ar(Rid, Tid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtrtaarac_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> - AR = mtrtaarac_mg_notify_request_ar(Rid, TermId, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaarac_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> +%% AR = mtrtaarac_mg_notify_request_ar(Rid, TermId, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -5142,8 +5211,8 @@ multi_trans_req_and_ack_maxsize1(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), ok = megaco_test_lib:start_nodes([MgcNode, MgNode], ?FILE, ?LINE), @@ -5416,26 +5485,26 @@ mtrtaams1_mgc_service_change_reply_ar(Mid, Cid) -> CR = cre_cmdReply(SCR), cre_actionReply(Cid, [CR]). -mtrtaams1_mgc_service_change_reply_msg(Mid, TransId, Cid) -> - AR = mtrtaams1_mgc_service_change_reply_ar(Mid, Cid), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaams1_mgc_service_change_reply_msg(Mid, TransId, Cid) -> +%% AR = mtrtaams1_mgc_service_change_reply_ar(Mid, Cid), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrtaams1_mgc_notify_reply_ar(Cid, TermId) -> NR = cre_notifyReply([TermId]), CR = cre_cmdReply(NR), cre_actionReply(Cid, [CR]). -mtrtaams1_mgc_notify_reply(Mid, TransId, Cid, TermId) -> - AR = mtrtaams1_mgc_notify_reply_ar(Cid, TermId), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaams1_mgc_notify_reply(Mid, TransId, Cid, TermId) -> +%% AR = mtrtaams1_mgc_notify_reply_ar(Cid, TermId), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -5589,12 +5658,12 @@ mtrtaams1_mg_service_change_request_ar(_Mid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtrtaams1_mg_service_change_request_msg(Mid, TransId, Cid) -> - AR = mtrtaams1_mg_service_change_request_ar(Mid, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaams1_mg_service_change_request_msg(Mid, TransId, Cid) -> +%% AR = mtrtaams1_mg_service_change_request_ar(Mid, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrtaams1_mg_notify_request_ar(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "22000000"), @@ -5605,12 +5674,12 @@ mtrtaams1_mg_notify_request_ar(Rid, Tid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtrtaams1_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> - AR = mtrtaams1_mg_notify_request_ar(Rid, TermId, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaams1_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> +%% AR = mtrtaams1_mg_notify_request_ar(Rid, TermId, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -5618,9 +5687,7 @@ mtrtaams1_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> %% mtrtaams1_err_desc(T) -> - EC = ?megaco_internal_gateway_error, - ET = lists:flatten(io_lib:format("~w",[T])), - #'ErrorDescriptor'{errorCode = EC, errorText = ET}. + cre_ErrDesc(T). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -5638,8 +5705,8 @@ multi_trans_req_and_ack_maxsize2(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), ok = megaco_test_lib:start_nodes([MgcNode, MgNode], ?FILE, ?LINE), @@ -5915,13 +5982,13 @@ mtrtaams2_mgc_service_change_reply_ar(Mid, Cid) -> CR = cre_cmdReply(SCR), cre_actionReply(Cid, [CR]). -mtrtaams2_mgc_service_change_reply_msg(Mid, TransId, Cid) -> - AR = mtrtaams2_mgc_service_change_reply_ar(Mid, Cid), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaams2_mgc_service_change_reply_msg(Mid, TransId, Cid) -> +%% AR = mtrtaams2_mgc_service_change_reply_ar(Mid, Cid), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrtaams2_mgc_notify_reply_ar1(Cid, TermId) -> NR = cre_notifyReply([TermId]), @@ -5932,13 +5999,13 @@ mtrtaams2_mgc_notify_reply_ar2(Cid, Tids) -> CRs = [cre_cmdReply(cre_notifyReply([Tid])) || Tid <- Tids], cre_actionReply(Cid, CRs). -mtrtaams2_mgc_notify_reply(Mid, TransId, Cid, TermId) -> - AR = mtrtaams2_mgc_notify_reply_ar1(Cid, TermId), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaams2_mgc_notify_reply(Mid, TransId, Cid, TermId) -> +%% AR = mtrtaams2_mgc_notify_reply_ar1(Cid, TermId), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -6093,12 +6160,12 @@ mtrtaams2_mg_service_change_request_ar(_Mid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtrtaams2_mg_service_change_request_msg(Mid, TransId, Cid) -> - AR = mtrtaams2_mg_service_change_request_ar(Mid, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaams2_mg_service_change_request_msg(Mid, TransId, Cid) -> +%% AR = mtrtaams2_mg_service_change_request_ar(Mid, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtrtaams2_mg_notify_request_ar1(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "22000000"), @@ -6124,12 +6191,12 @@ mtrtaams2_mg_notify_request_ar2(Rid, Tid, Cid) -> CRs = [F(N) || N <- Ns], cre_actionReq(Cid, CRs). -mtrtaams2_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> - AR = mtrtaams2_mg_notify_request_ar1(Rid, TermId, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtrtaams2_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> +%% AR = mtrtaams2_mg_notify_request_ar1(Rid, TermId, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -6137,9 +6204,7 @@ mtrtaams2_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> %% mtrtaams2_err_desc(T) -> - EC = ?megaco_internal_gateway_error, - ET = lists:flatten(io_lib:format("~w",[T])), - #'ErrorDescriptor'{errorCode = EC, errorText = ET}. + cre_ErrDesc(T). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -6177,8 +6242,8 @@ multi_trans_req_and_ack_and_pending(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), ok = megaco_test_lib:start_nodes([MgcNode, MgNode], ?FILE, ?LINE), @@ -6469,13 +6534,13 @@ mtraaap_mgc_service_change_reply_ar(Mid, Cid) -> CR = cre_cmdReply(SCR), cre_actionReply(Cid, [CR]). -mtraaap_mgc_service_change_reply_msg(Mid, TransId, Cid) -> - AR = mtraaap_mgc_service_change_reply_ar(Mid, Cid), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtraaap_mgc_service_change_reply_msg(Mid, TransId, Cid) -> +%% AR = mtraaap_mgc_service_change_reply_ar(Mid, Cid), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtraaap_mgc_notify_request_ar(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "44000000"), @@ -6491,13 +6556,13 @@ mtraaap_mgc_notify_reply_ar(Cid, TermId) -> CR = cre_cmdReply(NR), cre_actionReply(Cid, [CR]). -mtraaap_mgc_notify_reply(Mid, TransId, Cid, TermId) -> - AR = mtraaap_mgc_notify_reply_ar(Cid, TermId), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtraaap_mgc_notify_reply(Mid, TransId, Cid, TermId) -> +%% AR = mtraaap_mgc_notify_reply_ar(Cid, TermId), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -6639,38 +6704,38 @@ mtraaap_mg_verify_service_change_reply(Else) -> "~n Else: ~p~n", [Else]), {error, Else, ok}. -mtraaap_mg_verify_notify_request_fun() -> - fun(Ev) -> - mtraaap_mg_verify_notify_request(Ev) - end. - -mtraaap_mg_verify_notify_request( - {handle_trans_request, _, ?VERSION, [AR]}) -> - io:format("mtraaap_mg_verify_notify_request -> ok" - "~n AR: ~p~n", [AR]), - case AR of - #'ActionRequest'{contextId = 1 = Cid, - commandRequests = [CR]} -> - #'CommandRequest'{command = Cmd} = CR, - {notifyReq, NR} = Cmd, - #'NotifyRequest'{terminationID = [Tid], - observedEventsDescriptor = OED, - errorDescriptor = asn1_NOVALUE} = NR, - #'ObservedEventsDescriptor'{observedEventLst = [OE]} = OED, - #'ObservedEvent'{eventName = "al/of"} = OE, - Reply = {discard_ack, [mtraaap_mg_notify_reply_ar(Cid, Tid)]}, - {ok, 3000, AR, Reply}; - _ -> - ED = mtraaap_err_desc(AR), - ErrReply = {discard_ack, ED}, - {error, AR, ErrReply} - end; -mtraaap_mg_verify_notify_request(Else) -> - io:format("mtraaap_mg_verify_notify_request:fun -> unknown" - "~n Else: ~p~n", [Else]), - ED = mtraaap_err_desc(Else), - ErrReply = {discard_ack, ED}, - {error, Else, ErrReply}. +%% mtraaap_mg_verify_notify_request_fun() -> +%% fun(Ev) -> +%% mtraaap_mg_verify_notify_request(Ev) +%% end. + +%% mtraaap_mg_verify_notify_request( +%% {handle_trans_request, _, ?VERSION, [AR]}) -> +%% io:format("mtraaap_mg_verify_notify_request -> ok" +%% "~n AR: ~p~n", [AR]), +%% case AR of +%% #'ActionRequest'{contextId = 1 = Cid, +%% commandRequests = [CR]} -> +%% #'CommandRequest'{command = Cmd} = CR, +%% {notifyReq, NR} = Cmd, +%% #'NotifyRequest'{terminationID = [Tid], +%% observedEventsDescriptor = OED, +%% errorDescriptor = asn1_NOVALUE} = NR, +%% #'ObservedEventsDescriptor'{observedEventLst = [OE]} = OED, +%% #'ObservedEvent'{eventName = "al/of"} = OE, +%% Reply = {discard_ack, [mtraaap_mg_notify_reply_ar(Cid, Tid)]}, +%% {ok, 3000, AR, Reply}; +%% _ -> +%% ED = mtraaap_err_desc(AR), +%% ErrReply = {discard_ack, ED}, +%% {error, AR, ErrReply} +%% end; +%% mtraaap_mg_verify_notify_request(Else) -> +%% io:format("mtraaap_mg_verify_notify_request:fun -> unknown" +%% "~n Else: ~p~n", [Else]), +%% ED = mtraaap_err_desc(Else), +%% ErrReply = {discard_ack, ED}, +%% {error, Else, ErrReply}. mtraaap_mg_verify_notify_reply({handle_trans_reply, _CH, ?VERSION, {ok, [AR]}, _}) -> @@ -6691,17 +6756,17 @@ mtraaap_mg_service_change_request_ar(_Mid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtraaap_mg_service_change_request_msg(Mid, TransId, Cid) -> - AR = mtraaap_mg_service_change_request_ar(Mid, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtraaap_mg_service_change_request_msg(Mid, TransId, Cid) -> +%% AR = mtraaap_mg_service_change_request_ar(Mid, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). -mtraaap_mg_notify_reply_ar(Cid, TermId) -> - NR = cre_notifyReply([TermId]), - CR = cre_cmdReply(NR), - cre_actionReply(Cid, [CR]). +%% mtraaap_mg_notify_reply_ar(Cid, TermId) -> +%% NR = cre_notifyReply([TermId]), +%% CR = cre_cmdReply(NR), +%% cre_actionReply(Cid, [CR]). mtraaap_mg_notify_request_ar(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "22000000"), @@ -6712,12 +6777,12 @@ mtraaap_mg_notify_request_ar(Rid, Tid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtraaap_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> - AR = mtraaap_mg_notify_request_ar(Rid, TermId, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtraaap_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> +%% AR = mtraaap_mg_notify_request_ar(Rid, TermId, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -6725,9 +6790,7 @@ mtraaap_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> %% mtraaap_err_desc(T) -> - EC = ?megaco_internal_gateway_error, - ET = lists:flatten(io_lib:format("~w",[T])), - #'ErrorDescriptor'{errorCode = EC, errorText = ET}. + cre_ErrDesc(T). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -6765,8 +6828,8 @@ multi_trans_req_and_ack_and_reply(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), ok = megaco_test_lib:start_nodes([MgcNode, MgNode], ?FILE, ?LINE), @@ -7061,13 +7124,13 @@ mtraaar_mgc_service_change_reply_ar(Mid, Cid) -> CR = cre_cmdReply(SCR), cre_actionReply(Cid, [CR]). -mtraaar_mgc_service_change_reply_msg(Mid, TransId, Cid) -> - AR = mtraaar_mgc_service_change_reply_ar(Mid, Cid), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtraaar_mgc_service_change_reply_msg(Mid, TransId, Cid) -> +%% AR = mtraaar_mgc_service_change_reply_ar(Mid, Cid), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). mtraaar_mgc_notify_request_ar(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "44000000"), @@ -7083,13 +7146,13 @@ mtraaar_mgc_notify_reply_ar(Cid, TermId) -> CR = cre_cmdReply(NR), cre_actionReply(Cid, [CR]). -mtraaar_mgc_notify_reply(Mid, TransId, Cid, TermId) -> - AR = mtraaar_mgc_notify_reply_ar(Cid, TermId), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtraaar_mgc_notify_reply(Mid, TransId, Cid, TermId) -> +%% AR = mtraaar_mgc_notify_reply_ar(Cid, TermId), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -7232,38 +7295,38 @@ mtraaar_mg_verify_service_change_reply(Else) -> "~n Else: ~p~n", [Else]), {error, Else, ok}. -mtraaar_mg_verify_notify_request_fun() -> - fun(Ev) -> - mtraaar_mg_verify_notify_request(Ev) - end. - -mtraaar_mg_verify_notify_request( - {handle_trans_request, _, ?VERSION, [AR]}) -> - io:format("mtraaar_mg_verify_notify_request -> ok" - "~n AR: ~p~n", [AR]), - case AR of - #'ActionRequest'{contextId = 1 = Cid, - commandRequests = [CR]} -> - #'CommandRequest'{command = Cmd} = CR, - {notifyReq, NR} = Cmd, - #'NotifyRequest'{terminationID = [Tid], - observedEventsDescriptor = OED, - errorDescriptor = asn1_NOVALUE} = NR, - #'ObservedEventsDescriptor'{observedEventLst = [OE]} = OED, - #'ObservedEvent'{eventName = "al/of"} = OE, - Reply = {discard_ack, [mtraaar_mg_notify_reply_ar(Cid, Tid)]}, - {ok, AR, Reply}; - _ -> - ED = mtraaar_err_desc(AR), - ErrReply = {discard_ack, ED}, - {error, AR, ErrReply} - end; -mtraaar_mg_verify_notify_request(Else) -> - io:format("mtraaar_mg_verify_notify_request -> unknown" - "~n Else: ~p~n", [Else]), - ED = mtraaar_err_desc(Else), - ErrReply = {discard_ack, ED}, - {error, Else, ErrReply}. +%% mtraaar_mg_verify_notify_request_fun() -> +%% fun(Ev) -> +%% mtraaar_mg_verify_notify_request(Ev) +%% end. + +%% mtraaar_mg_verify_notify_request( +%% {handle_trans_request, _, ?VERSION, [AR]}) -> +%% io:format("mtraaar_mg_verify_notify_request -> ok" +%% "~n AR: ~p~n", [AR]), +%% case AR of +%% #'ActionRequest'{contextId = 1 = Cid, +%% commandRequests = [CR]} -> +%% #'CommandRequest'{command = Cmd} = CR, +%% {notifyReq, NR} = Cmd, +%% #'NotifyRequest'{terminationID = [Tid], +%% observedEventsDescriptor = OED, +%% errorDescriptor = asn1_NOVALUE} = NR, +%% #'ObservedEventsDescriptor'{observedEventLst = [OE]} = OED, +%% #'ObservedEvent'{eventName = "al/of"} = OE, +%% Reply = {discard_ack, [mtraaar_mg_notify_reply_ar(Cid, Tid)]}, +%% {ok, AR, Reply}; +%% _ -> +%% ED = mtraaar_err_desc(AR), +%% ErrReply = {discard_ack, ED}, +%% {error, AR, ErrReply} +%% end; +%% mtraaar_mg_verify_notify_request(Else) -> +%% io:format("mtraaar_mg_verify_notify_request -> unknown" +%% "~n Else: ~p~n", [Else]), +%% ED = mtraaar_err_desc(Else), +%% ErrReply = {discard_ack, ED}, +%% {error, Else, ErrReply}. mtraaar_mg_verify_notify_reply({handle_trans_reply, _CH, ?VERSION, {ok, [AR]}, _}) -> @@ -7284,17 +7347,17 @@ mtraaar_mg_service_change_request_ar(_Mid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtraaar_mg_service_change_request_msg(Mid, TransId, Cid) -> - AR = mtraaar_mg_service_change_request_ar(Mid, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtraaar_mg_service_change_request_msg(Mid, TransId, Cid) -> +%% AR = mtraaar_mg_service_change_request_ar(Mid, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). -mtraaar_mg_notify_reply_ar(Cid, TermId) -> - NR = cre_notifyReply([TermId]), - CR = cre_cmdReply(NR), - cre_actionReply(Cid, [CR]). +%% mtraaar_mg_notify_reply_ar(Cid, TermId) -> +%% NR = cre_notifyReply([TermId]), +%% CR = cre_cmdReply(NR), +%% cre_actionReply(Cid, [CR]). mtraaar_mg_notify_request_ar(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "22000000"), @@ -7305,12 +7368,12 @@ mtraaar_mg_notify_request_ar(Rid, Tid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -mtraaar_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> - AR = mtraaar_mg_notify_request_ar(Rid, TermId, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% mtraaar_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> +%% AR = mtraaar_mg_notify_request_ar(Rid, TermId, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -7318,9 +7381,7 @@ mtraaar_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> %% mtraaar_err_desc(T) -> - EC = ?megaco_internal_gateway_error, - ET = lists:flatten(io_lib:format("~w",[T])), - #'ErrorDescriptor'{errorCode = EC, errorText = ET}. + cre_ErrDesc(T). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -7348,8 +7409,8 @@ otp_7192_1(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), MgMid = {deviceName,"mg"}, @@ -7643,13 +7704,13 @@ otp71921_mgc_service_change_reply_ar(Mid, Cid) -> CR = cre_cmdReply(SCR), cre_actionReply(Cid, [CR]). -otp71921_mgc_service_change_reply_msg(Mid, TransId, Cid) -> - AR = otp71921_mgc_service_change_reply_ar(Mid, Cid), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% otp71921_mgc_service_change_reply_msg(Mid, TransId, Cid) -> +%% AR = otp71921_mgc_service_change_reply_ar(Mid, Cid), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). otp71921_mgc_notify_request_ar(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "44000000"), @@ -7665,13 +7726,13 @@ otp71921_mgc_notify_reply_ar(Cid, TermId) -> CR = cre_cmdReply(NR), cre_actionReply(Cid, [CR]). -otp71921_mgc_notify_reply(Mid, TransId, Cid, TermId) -> - AR = otp71921_mgc_notify_reply_ar(Cid, TermId), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% otp71921_mgc_notify_reply(Mid, TransId, Cid, TermId) -> +%% AR = otp71921_mgc_notify_reply_ar(Cid, TermId), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -7813,38 +7874,38 @@ otp71921_mg_verify_service_change_reply(Else) -> "~n Else: ~p~n", [Else]), {error, Else, ok}. -otp71921_mg_verify_notify_request_fun() -> - fun(Ev) -> - otp71921_mg_verify_notify_request(Ev) - end. - -otp71921_mg_verify_notify_request( - {handle_trans_request, _, ?VERSION, [AR]}) -> - io:format("otp71921_mg_verify_notify_request -> ok" - "~n AR: ~p~n", [AR]), - case AR of - #'ActionRequest'{contextId = 1 = Cid, - commandRequests = [CR]} -> - #'CommandRequest'{command = Cmd} = CR, - {notifyReq, NR} = Cmd, - #'NotifyRequest'{terminationID = [Tid], - observedEventsDescriptor = OED, - errorDescriptor = asn1_NOVALUE} = NR, - #'ObservedEventsDescriptor'{observedEventLst = [OE]} = OED, - #'ObservedEvent'{eventName = "al/of"} = OE, - Reply = {discard_ack, [otp71921_mg_notify_reply_ar(Cid, Tid)]}, - {ok, AR, Reply}; - _ -> - ED = otp71921_err_desc(AR), - ErrReply = {discard_ack, ED}, - {error, AR, ErrReply} - end; -otp71921_mg_verify_notify_request(Else) -> - io:format("otp71921_mg_verify_notify_request -> unknown" - "~n Else: ~p~n", [Else]), - ED = otp71921_err_desc(Else), - ErrReply = {discard_ack, ED}, - {error, Else, ErrReply}. +%% otp71921_mg_verify_notify_request_fun() -> +%% fun(Ev) -> +%% otp71921_mg_verify_notify_request(Ev) +%% end. + +%% otp71921_mg_verify_notify_request( +%% {handle_trans_request, _, ?VERSION, [AR]}) -> +%% io:format("otp71921_mg_verify_notify_request -> ok" +%% "~n AR: ~p~n", [AR]), +%% case AR of +%% #'ActionRequest'{contextId = 1 = Cid, +%% commandRequests = [CR]} -> +%% #'CommandRequest'{command = Cmd} = CR, +%% {notifyReq, NR} = Cmd, +%% #'NotifyRequest'{terminationID = [Tid], +%% observedEventsDescriptor = OED, +%% errorDescriptor = asn1_NOVALUE} = NR, +%% #'ObservedEventsDescriptor'{observedEventLst = [OE]} = OED, +%% #'ObservedEvent'{eventName = "al/of"} = OE, +%% Reply = {discard_ack, [otp71921_mg_notify_reply_ar(Cid, Tid)]}, +%% {ok, AR, Reply}; +%% _ -> +%% ED = otp71921_err_desc(AR), +%% ErrReply = {discard_ack, ED}, +%% {error, AR, ErrReply} +%% end; +%% otp71921_mg_verify_notify_request(Else) -> +%% io:format("otp71921_mg_verify_notify_request -> unknown" +%% "~n Else: ~p~n", [Else]), +%% ED = otp71921_err_desc(Else), +%% ErrReply = {discard_ack, ED}, +%% {error, Else, ErrReply}. otp71921_mg_verify_notify_reply({handle_trans_reply, _CH, ?VERSION, {ok, [AR]}, _}) -> @@ -7865,17 +7926,17 @@ otp71921_mg_service_change_request_ar(_Mid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -otp71921_mg_service_change_request_msg(Mid, TransId, Cid) -> - AR = otp71921_mg_service_change_request_ar(Mid, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% otp71921_mg_service_change_request_msg(Mid, TransId, Cid) -> +%% AR = otp71921_mg_service_change_request_ar(Mid, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). -otp71921_mg_notify_reply_ar(Cid, TermId) -> - NR = cre_notifyReply([TermId]), - CR = cre_cmdReply(NR), - cre_actionReply(Cid, [CR]). +%% otp71921_mg_notify_reply_ar(Cid, TermId) -> +%% NR = cre_notifyReply([TermId]), +%% CR = cre_cmdReply(NR), +%% cre_actionReply(Cid, [CR]). otp71921_mg_notify_request_ar(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "22000000"), @@ -7886,12 +7947,12 @@ otp71921_mg_notify_request_ar(Rid, Tid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -otp71921_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> - AR = otp71921_mg_notify_request_ar(Rid, TermId, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% otp71921_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> +%% AR = otp71921_mg_notify_request_ar(Rid, TermId, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -7919,8 +7980,8 @@ otp_7192_2(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), MgMid = {deviceName,"mg"}, @@ -8213,13 +8274,13 @@ otp71922_mgc_service_change_reply_ar(Mid, Cid) -> CR = cre_cmdReply(SCR), cre_actionReply(Cid, [CR]). -otp71922_mgc_service_change_reply_msg(Mid, TransId, Cid) -> - AR = otp71922_mgc_service_change_reply_ar(Mid, Cid), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% otp71922_mgc_service_change_reply_msg(Mid, TransId, Cid) -> +%% AR = otp71922_mgc_service_change_reply_ar(Mid, Cid), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). otp71922_mgc_notify_request_ar(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "44000000"), @@ -8235,13 +8296,13 @@ otp71922_mgc_notify_reply_ar(Cid, TermId) -> CR = cre_cmdReply(NR), cre_actionReply(Cid, [CR]). -otp71922_mgc_notify_reply(Mid, TransId, Cid, TermId) -> - AR = otp71922_mgc_notify_reply_ar(Cid, TermId), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% otp71922_mgc_notify_reply(Mid, TransId, Cid, TermId) -> +%% AR = otp71922_mgc_notify_reply_ar(Cid, TermId), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -8379,38 +8440,38 @@ otp71922_mg_verify_service_change_reply(Else) -> "~n Else: ~p~n", [Else]), {error, Else, ok}. -otp71922_mg_verify_notify_request_fun() -> - fun(Ev) -> - otp71922_mg_verify_notify_request(Ev) - end. - -otp71922_mg_verify_notify_request( - {handle_trans_request, _, ?VERSION, [AR]}) -> - io:format("otp71922_mg_verify_notify_request -> ok" - "~n AR: ~p~n", [AR]), - case AR of - #'ActionRequest'{contextId = 1 = Cid, - commandRequests = [CR]} -> - #'CommandRequest'{command = Cmd} = CR, - {notifyReq, NR} = Cmd, - #'NotifyRequest'{terminationID = [Tid], - observedEventsDescriptor = OED, - errorDescriptor = asn1_NOVALUE} = NR, - #'ObservedEventsDescriptor'{observedEventLst = [OE]} = OED, - #'ObservedEvent'{eventName = "al/of"} = OE, - Reply = {discard_ack, [otp71922_mg_notify_reply_ar(Cid, Tid)]}, - {ok, AR, Reply}; - _ -> - ED = otp71922_err_desc(AR), - ErrReply = {discard_ack, ED}, - {error, AR, ErrReply} - end; -otp71922_mg_verify_notify_request(Else) -> - io:format("otp71922_mg_verify_notify_request -> unknown" - "~n Else: ~p~n", [Else]), - ED = otp71922_err_desc(Else), - ErrReply = {discard_ack, ED}, - {error, Else, ErrReply}. +%% otp71922_mg_verify_notify_request_fun() -> +%% fun(Ev) -> +%% otp71922_mg_verify_notify_request(Ev) +%% end. + +%% otp71922_mg_verify_notify_request( +%% {handle_trans_request, _, ?VERSION, [AR]}) -> +%% io:format("otp71922_mg_verify_notify_request -> ok" +%% "~n AR: ~p~n", [AR]), +%% case AR of +%% #'ActionRequest'{contextId = 1 = Cid, +%% commandRequests = [CR]} -> +%% #'CommandRequest'{command = Cmd} = CR, +%% {notifyReq, NR} = Cmd, +%% #'NotifyRequest'{terminationID = [Tid], +%% observedEventsDescriptor = OED, +%% errorDescriptor = asn1_NOVALUE} = NR, +%% #'ObservedEventsDescriptor'{observedEventLst = [OE]} = OED, +%% #'ObservedEvent'{eventName = "al/of"} = OE, +%% Reply = {discard_ack, [otp71922_mg_notify_reply_ar(Cid, Tid)]}, +%% {ok, AR, Reply}; +%% _ -> +%% ED = otp71922_err_desc(AR), +%% ErrReply = {discard_ack, ED}, +%% {error, AR, ErrReply} +%% end; +%% otp71922_mg_verify_notify_request(Else) -> +%% io:format("otp71922_mg_verify_notify_request -> unknown" +%% "~n Else: ~p~n", [Else]), +%% ED = otp71922_err_desc(Else), +%% ErrReply = {discard_ack, ED}, +%% {error, Else, ErrReply}. otp71922_mg_verify_notify_reply({handle_trans_reply, _CH, ?VERSION, {ok, [AR]}, _}) -> @@ -8431,17 +8492,17 @@ otp71922_mg_service_change_request_ar(_Mid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -otp71922_mg_service_change_request_msg(Mid, TransId, Cid) -> - AR = otp71922_mg_service_change_request_ar(Mid, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% otp71922_mg_service_change_request_msg(Mid, TransId, Cid) -> +%% AR = otp71922_mg_service_change_request_ar(Mid, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). -otp71922_mg_notify_reply_ar(Cid, TermId) -> - NR = cre_notifyReply([TermId]), - CR = cre_cmdReply(NR), - cre_actionReply(Cid, [CR]). +%% otp71922_mg_notify_reply_ar(Cid, TermId) -> +%% NR = cre_notifyReply([TermId]), +%% CR = cre_cmdReply(NR), +%% cre_actionReply(Cid, [CR]). otp71922_mg_notify_request_ar(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "22000000"), @@ -8452,12 +8513,12 @@ otp71922_mg_notify_request_ar(Rid, Tid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -otp71922_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> - AR = otp71922_mg_notify_request_ar(Rid, TermId, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% otp71922_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> +%% AR = otp71922_mg_notify_request_ar(Rid, TermId, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -8465,9 +8526,7 @@ otp71922_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> %% otp71922_err_desc(T) -> - EC = ?megaco_internal_gateway_error, - ET = lists:flatten(io_lib:format("~w",[T])), - #'ErrorDescriptor'{errorCode = EC, errorText = ET}. + cre_ErrDesc(T). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -8485,8 +8544,8 @@ otp_7192_3(Config) when is_list(Config) -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " - "~n MgcNode: ~p" - "~n MgNode: ~p", + "~n MGC Node: ~p" + "~n MG Node: ~p", [MgcNode, MgNode]), MgMid = {deviceName,"mg"}, @@ -8779,13 +8838,13 @@ otp72923_mgc_service_change_reply_ar(Mid, Cid) -> CR = cre_cmdReply(SCR), cre_actionReply(Cid, [CR]). -otp72923_mgc_service_change_reply_msg(Mid, TransId, Cid) -> - AR = otp72923_mgc_service_change_reply_ar(Mid, Cid), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% otp72923_mgc_service_change_reply_msg(Mid, TransId, Cid) -> +%% AR = otp72923_mgc_service_change_reply_ar(Mid, Cid), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). otp72923_mgc_notify_request_ar(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "44000000"), @@ -8801,13 +8860,13 @@ otp72923_mgc_notify_reply_ar(Cid, TermId) -> CR = cre_cmdReply(NR), cre_actionReply(Cid, [CR]). -otp72923_mgc_notify_reply(Mid, TransId, Cid, TermId) -> - AR = otp72923_mgc_notify_reply_ar(Cid, TermId), - TRes = cre_transResult([AR]), - TR = cre_transReply(TransId, TRes), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% otp72923_mgc_notify_reply(Mid, TransId, Cid, TermId) -> +%% AR = otp72923_mgc_notify_reply_ar(Cid, TermId), +%% TRes = cre_transResult([AR]), +%% TR = cre_transReply(TransId, TRes), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -8946,38 +9005,38 @@ otp72923_mg_verify_service_change_reply(Else) -> "~n Else: ~p~n", [Else]), {error, Else, ok}. -otp72923_mg_verify_notify_request_fun() -> - fun(Ev) -> - otp72923_mg_verify_notify_request(Ev) - end. - -otp72923_mg_verify_notify_request( - {handle_trans_request, _, ?VERSION, [AR]}) -> - io:format("otp72923_mg_verify_notify_request -> ok" - "~n AR: ~p~n", [AR]), - case AR of - #'ActionRequest'{contextId = 1 = Cid, - commandRequests = [CR]} -> - #'CommandRequest'{command = Cmd} = CR, - {notifyReq, NR} = Cmd, - #'NotifyRequest'{terminationID = [Tid], - observedEventsDescriptor = OED, - errorDescriptor = asn1_NOVALUE} = NR, - #'ObservedEventsDescriptor'{observedEventLst = [OE]} = OED, - #'ObservedEvent'{eventName = "al/of"} = OE, - Reply = {discard_ack, [otp72923_mg_notify_reply_ar(Cid, Tid)]}, - {ok, AR, Reply}; - _ -> - ED = otp72923_err_desc(AR), - ErrReply = {discard_ack, ED}, - {error, AR, ErrReply} - end; -otp72923_mg_verify_notify_request(Else) -> - io:format("otp72923_mg_verify_notify_request -> unknown" - "~n Else: ~p~n", [Else]), - ED = otp72923_err_desc(Else), - ErrReply = {discard_ack, ED}, - {error, Else, ErrReply}. +%% otp72923_mg_verify_notify_request_fun() -> +%% fun(Ev) -> +%% otp72923_mg_verify_notify_request(Ev) +%% end. + +%% otp72923_mg_verify_notify_request( +%% {handle_trans_request, _, ?VERSION, [AR]}) -> +%% io:format("otp72923_mg_verify_notify_request -> ok" +%% "~n AR: ~p~n", [AR]), +%% case AR of +%% #'ActionRequest'{contextId = 1 = Cid, +%% commandRequests = [CR]} -> +%% #'CommandRequest'{command = Cmd} = CR, +%% {notifyReq, NR} = Cmd, +%% #'NotifyRequest'{terminationID = [Tid], +%% observedEventsDescriptor = OED, +%% errorDescriptor = asn1_NOVALUE} = NR, +%% #'ObservedEventsDescriptor'{observedEventLst = [OE]} = OED, +%% #'ObservedEvent'{eventName = "al/of"} = OE, +%% Reply = {discard_ack, [otp72923_mg_notify_reply_ar(Cid, Tid)]}, +%% {ok, AR, Reply}; +%% _ -> +%% ED = otp72923_err_desc(AR), +%% ErrReply = {discard_ack, ED}, +%% {error, AR, ErrReply} +%% end; +%% otp72923_mg_verify_notify_request(Else) -> +%% io:format("otp72923_mg_verify_notify_request -> unknown" +%% "~n Else: ~p~n", [Else]), +%% ED = otp72923_err_desc(Else), +%% ErrReply = {discard_ack, ED}, +%% {error, Else, ErrReply}. otp72923_mg_verify_notify_reply({handle_trans_reply, _CH, ?VERSION, {ok, [AR]}, _}) -> @@ -8998,17 +9057,17 @@ otp72923_mg_service_change_request_ar(_Mid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -otp72923_mg_service_change_request_msg(Mid, TransId, Cid) -> - AR = otp72923_mg_service_change_request_ar(Mid, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% otp72923_mg_service_change_request_msg(Mid, TransId, Cid) -> +%% AR = otp72923_mg_service_change_request_ar(Mid, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). -otp72923_mg_notify_reply_ar(Cid, TermId) -> - NR = cre_notifyReply([TermId]), - CR = cre_cmdReply(NR), - cre_actionReply(Cid, [CR]). +%% otp72923_mg_notify_reply_ar(Cid, TermId) -> +%% NR = cre_notifyReply([TermId]), +%% CR = cre_cmdReply(NR), +%% cre_actionReply(Cid, [CR]). otp72923_mg_notify_request_ar(Rid, Tid, Cid) -> TT = cre_timeNotation("19990729", "22000000"), @@ -9019,12 +9078,12 @@ otp72923_mg_notify_request_ar(Rid, Tid, Cid) -> CR = cre_cmdReq(CMD), cre_actionReq(Cid, [CR]). -otp72923_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> - AR = otp72923_mg_notify_request_ar(Rid, TermId, Cid), - TR = cre_transReq(TransId, [AR]), - Trans = cre_transaction(TR), - Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), - cre_megacoMessage(Mess). +%% otp72923_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> +%% AR = otp72923_mg_notify_request_ar(Rid, TermId, Cid), +%% TR = cre_transReq(TransId, [AR]), +%% Trans = cre_transaction(TR), +%% Mess = cre_message(?VERSION, Mid, cre_transactions([Trans])), +%% cre_megacoMessage(Mess). %% @@ -9032,9 +9091,7 @@ otp72923_notify_request_msg(Mid, TransId, Rid, TermId, Cid) -> %% otp72923_err_desc(T) -> - EC = ?megaco_internal_gateway_error, - ET = lists:flatten(io_lib:format("~w",[T])), - #'ErrorDescriptor'{errorCode = EC, errorText = ET}. + cre_ErrDesc(T). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -9065,10 +9122,10 @@ cre_timeNotation(D,T) -> cre_obsEvent(Name, Not) -> #'ObservedEvent'{eventName = Name, timeNotation = Not}. -cre_obsEvent(Name, Not, Par) -> - #'ObservedEvent'{eventName = Name, - timeNotation = Not, - eventParList = Par}. +%% cre_obsEvent(Name, Not, Par) -> +%% #'ObservedEvent'{eventName = Name, +%% timeNotation = Not, +%% eventParList = Par}. cre_obsEvsDesc(Id, EvList) -> #'ObservedEventsDescriptor'{requestId = Id, @@ -9090,9 +9147,9 @@ cre_actionReq(CtxId, CmdReqs) when is_list(CmdReqs) -> #'ActionRequest'{contextId = CtxId, commandRequests = CmdReqs}. -cre_transReq(TransId, ARs) when is_list(ARs) -> - #'TransactionRequest'{transactionId = TransId, - actions = ARs}. +%% cre_transReq(TransId, ARs) when is_list(ARs) -> +%% #'TransactionRequest'{transactionId = TransId, +%% actions = ARs}. %% -- @@ -9120,14 +9177,14 @@ cre_actionReply(CtxId, CmdRep) -> #'ActionReply'{contextId = CtxId, commandReply = CmdRep}. -cre_transResult(ED) when is_record(ED, 'ErrorDescriptor') -> - {transactionError, ED}; -cre_transResult([AR|_] = ARs) when is_record(AR, 'ActionReply') -> - {actionReplies, ARs}. +%% cre_transResult(ED) when is_record(ED, 'ErrorDescriptor') -> +%% {transactionError, ED}; +%% cre_transResult([AR|_] = ARs) when is_record(AR, 'ActionReply') -> +%% {actionReplies, ARs}. -cre_transReply(TransId, Res) -> - #'TransactionReply'{transactionId = TransId, - transactionResult = Res}. +%% cre_transReply(TransId, Res) -> +%% #'TransactionReply'{transactionId = TransId, +%% transactionResult = Res}. %% -- @@ -9136,48 +9193,48 @@ cre_serviceChangeProf(Name, Ver) when is_list(Name) andalso is_integer(Ver) -> #'ServiceChangeProfile'{profileName = Name, version = Ver}. -cre_transaction(Trans) when is_record(Trans, 'TransactionRequest') -> - {transactionRequest, Trans}; -cre_transaction(Trans) when is_record(Trans, 'TransactionPending') -> - {transactionPending, Trans}; -cre_transaction(Trans) when is_record(Trans, 'TransactionReply') -> - {transactionReply, Trans}; -cre_transaction(Trans) when is_record(Trans, 'TransactionAck') -> - {transactionResponseAck, Trans}. - -cre_transactions(Trans) when is_list(Trans) -> - {transactions, Trans}. - -cre_message(Version, Mid, Body) -> - #'Message'{version = Version, - mId = Mid, - messageBody = Body}. - -cre_megacoMessage(Mess) -> - #'MegacoMessage'{mess = Mess}. +%% cre_transaction(Trans) when is_record(Trans, 'TransactionRequest') -> +%% {transactionRequest, Trans}; +%% cre_transaction(Trans) when is_record(Trans, 'TransactionPending') -> +%% {transactionPending, Trans}; +%% cre_transaction(Trans) when is_record(Trans, 'TransactionReply') -> +%% {transactionReply, Trans}; +%% cre_transaction(Trans) when is_record(Trans, 'TransactionAck') -> +%% {transactionResponseAck, Trans}. + +%% cre_transactions(Trans) when is_list(Trans) -> +%% {transactions, Trans}. + +%% cre_message(Version, Mid, Body) -> +%% #'Message'{version = Version, +%% mId = Mid, +%% messageBody = Body}. + +%% cre_megacoMessage(Mess) -> +%% #'MegacoMessage'{mess = Mess}. %% %% Common functions %% -encode_msg_fun(Mod, Conf) -> - fun(M) -> - Mod:encode_message(Conf, M) - end. -encode_msg_fun(Mod, Conf, Ver) -> - fun(M) -> - Mod:encode_message(Conf, Ver, M) - end. - -decode_msg_fun(Mod, Conf) -> - fun(M) -> - Mod:decode_message(Conf, M) - end. -decode_msg_fun(Mod, Conf, Ver) -> - fun(M) -> - Mod:decode_message(Conf, Ver, M) - end. +%% encode_msg_fun(Mod, Conf) -> +%% fun(M) -> +%% Mod:encode_message(Conf, M) +%% end. +%% encode_msg_fun(Mod, Conf, Ver) -> +%% fun(M) -> +%% Mod:encode_message(Conf, Ver, M) +%% end. + +%% decode_msg_fun(Mod, Conf) -> +%% fun(M) -> +%% Mod:decode_message(Conf, M) +%% end. +%% decode_msg_fun(Mod, Conf, Ver) -> +%% fun(M) -> +%% Mod:decode_message(Conf, Ver, M) +%% end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -9190,10 +9247,10 @@ await_ack(User, N, Timeout, Expected) when (N > 0) andalso is_integer(Timeout) - T = tim(), receive {ack_received, User, Expected} -> - d("await_ack -> received another ack"), + d("await_ack -> received another expected ack"), await_ack(User, N-1, Timeout - (tim() - T), Expected); {ack_received, User, UnExpected} -> - d("await_ack -> unexpected ack result: ~p", [UnExpected]), + e("await_ack -> received unexpected ack result: ~p", [UnExpected]), exit({unexpected_ack_result, UnExpected, Expected}) after Timeout -> exit({await_ack_timeout, N}) @@ -9205,72 +9262,13 @@ await_ack(User, N, infinity, Expected) when N > 0 -> d("await_ack -> received another ack"), await_ack(User, N-1, infinity, Expected); {ack_received, User, UnExpected} -> - d("await_ack -> unexpected ack result: ~p", [UnExpected]), + e("await_ack -> unexpected ack result: ~p", [UnExpected]), exit({unexpected_ack_result, UnExpected, Expected}) end. -await_req(_User, 0, Timeout) -> - d("await_req -> done when Timeout = ~p", [Timeout]), - ok; -await_req(User, N, Timeout) when (N > 0) andalso is_integer(Timeout) -> - d("await_req -> entry with N: ~p, Timeout: ~p", [N,Timeout]), - T = tim(), - receive - {req_received, User, ARs} -> - d("await_req -> received req(s) when N = ~w", [N]), - N1 = await_req1(N, ARs), - await_req(User, N1, Timeout - (tim() - T)) - after Timeout -> - exit({await_req_timeout, N}) - end; -await_req(User, N, infinity) when N > 0 -> - d("await_req -> entry with N: ~p", [N]), - receive - {req_received, User, ARs} -> - d("await_req -> received req(s) when N = ~2",[N]), - N1 = await_req1(N, ARs), - await_req(User, N1, infinity) - end. - -await_req1(N, []) when N >= 0 -> - N; -await_req1(N, [AR|ARs]) when (N > 0) andalso is_record(AR, 'ActionRequest') -> - await_req1(N-1, ARs); -await_req1(N, ARs) -> - exit({unexpected_req_result, N, ARs}). - -% await_rep(_User, 0, Timeout) -> -% d("await_rep -> done when Timeout = ~p", [Timeout]), -% ok; -% await_rep(User, N, Timeout) when N > 0, integer(Timeout) -> -% d("await_rep -> entry with N: ~p, Timeout: ~p", [N,Timeout]), -% T = tim(), -% receive -% {rep_received, User, ARs} -> -% d("await_rep -> received rep(s)"), -% N1 = await_rep1(N, ARs), -% await_rep(User, N1, Timeout - (tim() - T)) -% after Timeout -> -% exit({await_rep_timeout, N}) -% end; -% await_rep(User, N, infinity) when N > 0 -> -% d("await_rep -> entry with N: ~p", [N]), -% receive -% {rep_received, User, ARs} -> -% d("await_rep -> received rep(s)"), -% N1 = await_rep1(N, ARs), -% await_rep(User, N1, infinity) -% end. - -% await_rep1(N, []) when N >= 0 -> -% N; -% await_rep1(N, [AR|ARs]) when N > 0, record(AR, 'ActionReply') -> -% await_rep1(N-1, ARs); -% await_rep1(N, ARs) -> -% exit({unexpected_rep_result, N, ARs}). tim() -> - {A,B,C} = erlang:now(), + {A,B,C} = erlang:timestamp(), A*1000000000+B*1000+(C div 1000). @@ -9291,7 +9289,8 @@ await_completion(Ids) -> d("OK => Reply: ~n~p", [Reply]), ok; {error, Reply} -> - d("ERROR => Reply: ~n~p", [Reply]), + e("await completion failed: " + "~n ~p", [Reply]), ?ERROR({failed, Reply}) end. @@ -9301,7 +9300,9 @@ await_completion(Ids, Timeout) -> d("OK => Reply: ~n~p", [Reply]), ok; {error, Reply} -> - d("ERROR => Reply: ~n~p", [Reply]), + e("await completion failed: " + "~n ~p" + "~n ~p", [Timeout, Reply]), ?ERROR({failed, Reply}) end. @@ -9310,64 +9311,71 @@ await_completion(Ids, Timeout) -> sleep(X) -> receive after X -> ok end. -error_msg(F,A) -> error_logger:error_msg(F ++ "~n",A). +%% error_msg(F,A) -> error_logger:error_msg(F ++ "~n",A). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% e(F) -> +%% e(F, []). + +e(F, A) -> + print(error, "ERR", F, A). + + i(F) -> i(F, []). i(F, A) -> - print(info, get(verbosity), now(), get(tc), "INF", F, A). + print(info, "INF", F, A). d(F) -> d(F, []). d(F, A) -> - print(debug, get(verbosity), now(), get(tc), "DBG", F, A). + print(debug, "DBG", F, A). -printable(_, debug) -> true; -printable(info, info) -> true; -printable(_,_) -> false. +print(Severity, P, F, A) -> + print2(printable(Severity), P, F, A). -print(Severity, Verbosity, Ts, Tc, P, F, A) -> - print(printable(Severity,Verbosity), Ts, Tc, P, F, A). +printable(Sev) -> + printable(Sev, get(verbosity)). -print(true, Ts, Tc, P, F, A) -> - io:format("*** [~s] ~s ~p ~s:~w ***" - "~n " ++ F ++ "~n", - [format_timestamp(Ts), P, self(), get(tc), Tc | A]); -print(_, _, _, _, _, _) -> +printable(_, debug) -> true; +printable(info, info) -> true; +printable(error, _) -> true; +printable(_,_) -> false. + + +print2(true, P, F, A) -> + TS = erlang:timestamp(), + TC = get(tc), + S = ?F("*** [~s] ~s ~p ~w ***" + "~n " ++ F ++ "~n" + "~n", [megaco:format_timestamp(TS), P, self(), TC | A]), + io:format("~s", [S]), + io:format(user, "~s", [S]); +print2(_, _, _, _) -> ok. p(F, A) -> io:format("*** [~s] ***" "~n " ++ F ++ "~n", - [format_timestamp(now()) | A]). + [megaco:format_timestamp(erlang:timestamp()) | A]). -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -random_init() -> - {A,B,C} = now(), - random:seed(A,B,C). -random() -> - 10 * random:uniform(50). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -apply_load_timer() -> - erlang:send_after(random(), self(), apply_load_timeout). +%% random_init() -> +%% {A,B,C} = erlang:timestamp(), +%% random:seed(A,B,C). +%% random() -> +%% 10 * random:uniform(50). -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), - lists:flatten(FormatDate). +%% apply_load_timer() -> +%% erlang:send_after(random(), self(), apply_load_timeout). diff --git a/lib/megaco/test/megaco_udp_test.erl b/lib/megaco/test/megaco_udp_test.erl index cc03ec733a..39ff44709e 100644 --- a/lib/megaco/test/megaco_udp_test.erl +++ b/lib/megaco/test/megaco_udp_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2016. All Rights Reserved. +%% Copyright Ericsson AB 2000-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. @@ -1211,23 +1211,14 @@ p(F, A) -> p(S, F, A) when is_list(S) -> io:format("*** [~s] ~p ~s ***" "~n " ++ F ++ "~n", - [format_timestamp(now()), self(), S | A]); + [?FTS(), self(), S | A]); p(_S, F, A) -> io:format("*** [~s] ~p ~s *** " "~n " ++ F ++ "~n", - [format_timestamp(now()), self(), "undefined" | A]). + [?FTS(), self(), "undefined" | A]). ms() -> - {A,B,C} = erlang:now(), - A*1000000000+B*1000+(C div 1000). + erlang:monotonic_time(milli_seconds). -format_timestamp({_N1, _N2, N3} = Now) -> - {Date, Time} = calendar:now_to_datetime(Now), - {YYYY,MM,DD} = Date, - {Hour,Min,Sec} = Time, - FormatDate = - io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", - [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), - lists:flatten(FormatDate). diff --git a/lib/mnesia/doc/src/Mnesia_chap7.xmlsrc b/lib/mnesia/doc/src/Mnesia_chap7.xmlsrc index ffbbdadec0..968e89a745 100644 --- a/lib/mnesia/doc/src/Mnesia_chap7.xmlsrc +++ b/lib/mnesia/doc/src/Mnesia_chap7.xmlsrc @@ -295,7 +295,7 @@ ok</pre> if the log is large. Notice that the <c>Mnesia</c> system continues to operate during log dumps.</p> <p>By default <c>Mnesia</c> either dumps the log whenever - 100 records have + 1000 records have been written in the log or when three minutes have passed. This is controlled by the two application parameters <c>-mnesia dump_log_write_threshold WriteOperations</c> and diff --git a/lib/mnesia/src/mnesia_controller.erl b/lib/mnesia/src/mnesia_controller.erl index 882de0d613..0f221b0c1f 100644 --- a/lib/mnesia/src/mnesia_controller.erl +++ b/lib/mnesia/src/mnesia_controller.erl @@ -331,35 +331,39 @@ release_schema_commit_lock() -> %% Special for preparation of add table copy get_network_copy(Tid, Tab, Cs) -> -% We can't let the controller queue this one -% because that may cause a deadlock between schema_operations -% and initial tableloadings which both takes schema locks. -% But we have to get copier_done msgs when the other side -% goes down. - call({add_other, self()}), - Reason = {dumper,{add_table_copy, Tid}}, - Work = #net_load{table = Tab,reason = Reason,cstruct = Cs}, - %% I'll need this cause it's linked trough the subscriber - %% might be solved by using monitor in subscr instead. - process_flag(trap_exit, true), - Load = load_table_fun(Work), - Res = ?CATCH(Load()), - process_flag(trap_exit, false), - call({del_other, self()}), - case Res of - #loader_done{is_loaded = true} -> - Tab = Res#loader_done.table_name, - case Res#loader_done.needs_announce of - true -> - i_have_tab(Tab); - false -> - ignore - end, - Res#loader_done.reply; - #loader_done{} -> - Res#loader_done.reply; - Else -> - {not_loaded, Else} + %% We can't let the controller queue this one + %% because that may cause a deadlock between schema_operations + %% and initial tableloadings which both takes schema locks. + %% But we have to get copier_done msgs when the other side + %% goes down. + case call({add_other, self()}) of + ok -> + Reason = {dumper,{add_table_copy, Tid}}, + Work = #net_load{table = Tab,reason = Reason,cstruct = Cs}, + %% I'll need this cause it's linked trough the subscriber + %% might be solved by using monitor in subscr instead. + process_flag(trap_exit, true), + Load = load_table_fun(Work), + Res = ?CATCH(Load()), + process_flag(trap_exit, false), + call({del_other, self()}), + case Res of + #loader_done{is_loaded = true} -> + Tab = Res#loader_done.table_name, + case Res#loader_done.needs_announce of + true -> + i_have_tab(Tab); + false -> + ignore + end, + Res#loader_done.reply; + #loader_done{} -> + Res#loader_done.reply; + Else -> + {not_loaded, Else} + end; + {error, Else} -> + {not_loaded, Else} end. %% This functions is invoked from the dumper @@ -772,6 +776,18 @@ handle_call({unannounce_add_table_copy, [Tab, Node], From}, ReplyTo, State) -> noreply(State#state{early_msgs = [{call, Msg, undefined} | Msgs]}) end; +handle_call({add_other, Who}, _From, State = #state{others=Others0, schema_is_merged=SM}) -> + case SM of + true -> + Others = [Who|Others0], + {reply, ok, State#state{others=Others}}; + false -> + {reply, {error, {not_active,schema,node()}}, State} + end; +handle_call({del_other, Who}, _From, State = #state{others=Others0}) -> + Others = lists:delete(Who, Others0), + {reply, ok, State#state{others=Others}}; + handle_call(Msg, From, State) when State#state.schema_is_merged /= true -> %% Buffer early messages Msgs = State#state.early_msgs, @@ -803,13 +819,6 @@ handle_call({block_table, [Tab], From}, _Dummy, State) -> handle_call({check_w2r, _Node, Tab}, _From, State) -> {reply, val({Tab, where_to_read}), State}; -handle_call({add_other, Who}, _From, State = #state{others=Others0}) -> - Others = [Who|Others0], - {reply, ok, State#state{others=Others}}; -handle_call({del_other, Who}, _From, State = #state{others=Others0}) -> - Others = lists:delete(Who, Others0), - {reply, ok, State#state{others=Others}}; - handle_call(Msg, _From, State) -> error("~p got unexpected call: ~tp~n", [?SERVER_NAME, Msg]), noreply(State). diff --git a/lib/mnesia/src/mnesia_locker.erl b/lib/mnesia/src/mnesia_locker.erl index f68626413e..0222c5b1a0 100644 --- a/lib/mnesia/src/mnesia_locker.erl +++ b/lib/mnesia/src/mnesia_locker.erl @@ -774,10 +774,12 @@ do_sticky_lock(Tid, Store, {Tab, Key} = Oid, Lock) -> N = node(), receive {?MODULE, N, granted} -> + ?ets_insert(Store, {sticky, true}), ?ets_insert(Store, {{locks, Tab, Key}, write}), [?ets_insert(Store, {nodes, Node}) || Node <- WNodes], granted; {?MODULE, N, {granted, Val}} -> %% for rwlocks + ?ets_insert(Store, {sticky, true}), case opt_lookup_in_client(Val, Oid, write) of C = #cyclic{} -> exit({aborted, C}); diff --git a/lib/mnesia/src/mnesia_monitor.erl b/lib/mnesia/src/mnesia_monitor.erl index 4cfe16dec0..4e50b46da8 100644 --- a/lib/mnesia/src/mnesia_monitor.erl +++ b/lib/mnesia/src/mnesia_monitor.erl @@ -83,9 +83,9 @@ going_down = [], tm_started = false, early_connects = [], connecting, mq = [], remote_node_status = []}). --define(current_protocol_version, {8,3}). +-define(current_protocol_version, {8,4}). --define(previous_protocol_version, {8,2}). +-define(previous_protocol_version, {8,3}). start() -> gen_server:start_link({local, ?MODULE}, ?MODULE, @@ -196,7 +196,7 @@ protocol_version() -> %% A sorted list of acceptable protocols the %% preferred protocols are first in the list acceptable_protocol_versions() -> - [protocol_version(), ?previous_protocol_version, {8,1}]. + [protocol_version(), ?previous_protocol_version]. needs_protocol_conversion(Node) -> case {?catch_val({protocol, Node}), protocol_version()} of diff --git a/lib/mnesia/src/mnesia_schema.erl b/lib/mnesia/src/mnesia_schema.erl index ef38adca1e..d0f5d0e07b 100644 --- a/lib/mnesia/src/mnesia_schema.erl +++ b/lib/mnesia/src/mnesia_schema.erl @@ -697,7 +697,7 @@ schema_coordinator(Client, _Fun, undefined) -> schema_coordinator(Client, Fun, Controller) when is_pid(Controller) -> %% Do not trap exit in order to automatically die %% when the controller dies - + put(transaction_client, Client), %% debug link(Controller), unlink(Client), @@ -730,7 +730,10 @@ api_list2cs(Other) -> mnesia:abort({badarg, Other}). vsn_cs2list(Cs) -> - cs2list(need_old_cstructs(), Cs). + cs2list(Cs). + +cs2list(false, Cs) -> + cs2list(Cs). cs2list(Cs) when is_record(Cs, cstruct) -> Tags = record_info(fields, cstruct), @@ -755,25 +758,6 @@ cs2list(Cs) when element(1, Cs) == cstruct, tuple_size(Cs) == 19 -> cookie,version], rec2list(Tags, Tags, 2, Cs). -cs2list(false, Cs) -> - cs2list(Cs); -cs2list({8,3}, Cs) -> - cs2list(Cs); -cs2list({8,Minor}, Cs) when Minor =:= 2; Minor =:= 1 -> - Orig = record_info(fields, cstruct), - Tags = [name,type,ram_copies,disc_copies,disc_only_copies, - load_order,access_mode,majority,index,snmp,local_content, - record_name,attributes, - user_properties,frag_properties,storage_properties, - cookie,version], - CsList = rec2list(Tags, Orig, 2, Cs), - case proplists:get_value(index, CsList, []) of - [] -> CsList; - NewFormat -> - OldFormat = [Pos || {Pos, _Pref} <- NewFormat], - lists:keyreplace(index, 1, CsList, {index, OldFormat}) - end. - rec2list([index | Tags], [index|Orig], Pos, Rec) -> Val = element(Pos, Rec), [{index, lists:map( @@ -796,19 +780,8 @@ rec2list([], _, _Pos, _Rec) -> rec2list(Tags, [_|Orig], Pos, Rec) -> rec2list(Tags, Orig, Pos+1, Rec). -normalize_cs(Cstructs, Node) -> - %% backward-compatibility hack; normalize before returning - case need_old_cstructs([Node]) of - false -> - Cstructs; - Version -> - %% some other format - [convert_cs(Version, Cs) || Cs <- Cstructs] - end. - -convert_cs(Version, Cs) -> - Fields = [Value || {_, Value} <- cs2list(Version, Cs)], - list_to_tuple([cstruct|Fields]). +normalize_cs(Cstructs, _Node) -> + Cstructs. list2cs(List) -> list2cs(List, get_ext_types()). @@ -1864,11 +1837,7 @@ do_move_table(schema, _FromNode, _ToNode) -> mnesia:abort({bad_type, schema}); do_move_table(Tab, FromNode, ToNode) when is_atom(FromNode), is_atom(ToNode) -> TidTs = get_tid_ts_and_lock(schema, write), - AnyOld = lists:any(fun(Node) -> mnesia_monitor:needs_protocol_conversion(Node) end, - [ToNode|val({Tab, where_to_write})]), - if AnyOld -> ignore; %% Leads to deadlock on old nodes - true -> get_tid_ts_and_lock(Tab, write) - end, + get_tid_ts_and_lock(Tab, write), insert_schema_ops(TidTs, make_move_table(Tab, FromNode, ToNode)); do_move_table(Tab, FromNode, ToNode) -> mnesia:abort({badarg, Tab, FromNode, ToNode}). @@ -3438,15 +3407,14 @@ do_merge_schema(LockTabs0) -> mnesia_lib:intersect(Ns,NeedsLock)) || {T,Ns} <- LockTabs], - NeedsConversion = need_old_cstructs(NeedsLock ++ LockedAlready), {value, SchemaCs} = lists:keysearch(schema, #cstruct.name, Cstructs), - SchemaDef = cs2list(NeedsConversion, SchemaCs), + SchemaDef = cs2list(false, SchemaCs), %% Announce that Node is running A = [{op, announce_im_running, node(), SchemaDef, Running, RemoteRunning}], do_insert_schema_ops(Store, A), %% Introduce remote tables to local node - do_insert_schema_ops(Store, make_merge_schema(Node, NeedsConversion, Cstructs)), + do_insert_schema_ops(Store, make_merge_schema(Node, false, Cstructs)), %% Introduce local tables to remote nodes Tabs = val({schema, tables}), @@ -3471,23 +3439,7 @@ do_merge_schema(LockTabs0) -> end. fetch_cstructs(Node) -> - Convert = mnesia_monitor:needs_protocol_conversion(Node), - case rpc:call(Node, mnesia_controller, get_remote_cstructs, []) of - {cstructs, Cs0, RemoteRunning1} when Convert -> - {cstructs, [list2cs(cs2list(Cs)) || Cs <- Cs0], RemoteRunning1}; - Result -> - Result - end. - -need_old_cstructs() -> - need_old_cstructs(val({schema, where_to_write})). - -need_old_cstructs(Nodes) -> - Filter = fun(Node) -> mnesia_monitor:needs_protocol_conversion(Node) end, - case lists:filter(Filter, Nodes) of - [] -> false; - Ns -> lists:min([element(1, ?catch_val({protocol, Node})) || Node <- Ns]) - end. + rpc:call(Node, mnesia_controller, get_remote_cstructs, []). tab_to_nodes(Tab) when is_atom(Tab) -> Cs = val({Tab, cstruct}), diff --git a/lib/mnesia/src/mnesia_tm.erl b/lib/mnesia/src/mnesia_tm.erl index 8b79fca1d7..8a4113422a 100644 --- a/lib/mnesia/src/mnesia_tm.erl +++ b/lib/mnesia/src/mnesia_tm.erl @@ -26,7 +26,7 @@ init/1, non_transaction/5, transaction/6, - commit_participant/5, + commit_participant/6, dirty/2, display_info/2, do_update_op/3, @@ -62,13 +62,14 @@ %% Format on coordinators is [{Tid, EtsTabList} ..... -record(prep, {protocol = sym_trans, - %% async_dirty | sync_dirty | sym_trans | sync_sym_trans | asym_trans + %% async_dirty | sync_dirty | sym_trans | sync_sym_trans | asym_trans | sync_asym_trans records = [], prev_tab = [], % initiate to a non valid table name prev_types, prev_snmp, types, - majority = [] + majority = [], + sync = false }). -record(participant, {tid, pid, commit, disc_nodes = [], @@ -250,11 +251,13 @@ doit_loop(#state{coordinators=Coordinators,participants=Participants,supervisor= mnesia_checkpoint:tm_enter_pending(Tid, DiscNs, RamNs), Commit = new_cr_format(Commit0), Pid = - case Protocol of - asym_trans when node(Tid#tid.pid) /= node() -> - Args = [tmpid(From), Tid, Commit, DiscNs, RamNs], + if + node(Tid#tid.pid) =:= node() -> + error({internal_error, local_node}); + Protocol =:= asym_trans orelse Protocol =:= sync_asym_trans -> + Args = [Protocol, tmpid(From), Tid, Commit, DiscNs, RamNs], spawn_link(?MODULE, commit_participant, Args); - _ when node(Tid#tid.pid) /= node() -> %% *_sym_trans + true -> %% *_sym_trans reply(From, {vote_yes, Tid}), nopid end, @@ -1190,7 +1193,15 @@ do_arrange(Tid, Store, RestoreKey, Prep, N) when RestoreKey == restore_op -> P2 = Prep#prep{protocol = asym_trans, records = Recs2}, do_arrange(Tid, Store, ?ets_next(Store, RestoreKey), P2, N + 1); do_arrange(_Tid, _Store, '$end_of_table', Prep, N) -> - {N, Prep}; + case Prep of + #prep{sync=true, protocol=asym_trans} -> + {N, Prep#prep{protocol=sync_asym_trans}}; + _ -> + {N, Prep} + end; +do_arrange(Tid, Store, sticky, Prep, N) -> + P2 = Prep#prep{sync=true}, + do_arrange(Tid, Store, ?ets_next(Store, sticky), P2, N); do_arrange(Tid, Store, IgnoredKey, Prep, N) -> %% locks, nodes ... local atoms... do_arrange(Tid, Store, ?ets_next(Store, IgnoredKey), Prep, N). @@ -1448,7 +1459,8 @@ multi_commit(sync_sym_trans, _Maj = [], Tid, CR, Store) -> [{tid, Tid}, {outcome, Outcome}]), Outcome; -multi_commit(asym_trans, Majority, Tid, CR, Store) -> +multi_commit(Protocol, Majority, Tid, CR, Store) + when Protocol =:= asym_trans; Protocol =:= sync_asym_trans -> %% This more expensive commit protocol is used when %% table definitions are changed (schema transactions). %% It is also used when the involved tables are @@ -1515,7 +1527,7 @@ multi_commit(asym_trans, Majority, Tid, CR, Store) -> end, Pending = mnesia_checkpoint:tm_enter_pending(Tid, DiscNs, RamNs), ?ets_insert(Store, Pending), - {WaitFor, Local} = ask_commit(asym_trans, Tid, CR2, DiscNs, RamNs), + {WaitFor, Local} = ask_commit(Protocol, Tid, CR2, DiscNs, RamNs), SchemaPrep = ?CATCH(mnesia_schema:prepare_commit(Tid, Local, {coord, WaitFor})), {Votes, Pids} = rec_all(WaitFor, Tid, do_commit, []), @@ -1563,38 +1575,38 @@ multi_commit(asym_trans, Majority, Tid, CR, Store) -> %% Returns do_commit or {do_abort, Reason} rec_acc_pre_commit([Pid | Tail], Tid, Store, Commit, Res, DumperMode, - GoodPids, SchemaAckPids) -> + GoodPids, AckPids) -> receive {?MODULE, _, {acc_pre_commit, Tid, Pid, true}} -> rec_acc_pre_commit(Tail, Tid, Store, Commit, Res, DumperMode, - [Pid | GoodPids], [Pid | SchemaAckPids]); + [Pid | GoodPids], [Pid | AckPids]); {?MODULE, _, {acc_pre_commit, Tid, Pid, false}} -> rec_acc_pre_commit(Tail, Tid, Store, Commit, Res, DumperMode, - [Pid | GoodPids], SchemaAckPids); + [Pid | GoodPids], AckPids); {?MODULE, _, {acc_pre_commit, Tid, Pid}} -> %% Kept for backwards compatibility. Remove after Mnesia 4.x rec_acc_pre_commit(Tail, Tid, Store, Commit, Res, DumperMode, - [Pid | GoodPids], [Pid | SchemaAckPids]); + [Pid | GoodPids], [Pid | AckPids]); {?MODULE, _, {do_abort, Tid, Pid, _Reason}} -> AbortRes = {do_abort, {bad_commit, node(Pid)}}, rec_acc_pre_commit(Tail, Tid, Store, Commit, AbortRes, DumperMode, - GoodPids, SchemaAckPids); + GoodPids, AckPids); {mnesia_down, Node} when Node == node(Pid) -> AbortRes = {do_abort, {bad_commit, Node}}, ?SAFE(Pid ! {Tid, AbortRes}), %% Tell him that he has died rec_acc_pre_commit(Tail, Tid, Store, Commit, AbortRes, DumperMode, - GoodPids, SchemaAckPids) + GoodPids, AckPids) end; -rec_acc_pre_commit([], Tid, Store, {Commit,OrigC}, Res, DumperMode, GoodPids, SchemaAckPids) -> +rec_acc_pre_commit([], Tid, Store, {Commit,OrigC}, Res, DumperMode, GoodPids, AckPids) -> D = Commit#commit.decision, case Res of do_commit -> %% Now everybody knows that the others %% has voted yes. We also know that %% everybody are uncertain. - prepare_sync_schema_commit(Store, SchemaAckPids), + prepare_sync_schema_commit(Store, AckPids), tell_participants(GoodPids, {Tid, committed}), D2 = D#decision{outcome = committed}, mnesia_recover:log_decision(D2), @@ -1606,7 +1618,7 @@ rec_acc_pre_commit([], Tid, Store, {Commit,OrigC}, Res, DumperMode, GoodPids, Sc do_commit(Tid, Commit, DumperMode), ?eval_debug_fun({?MODULE, rec_acc_pre_commit_done_commit}, [{tid, Tid}]), - sync_schema_commit(Tid, Store, SchemaAckPids), + sync_schema_commit(Tid, Store, AckPids), mnesia_locker:release_tid(Tid), ?MODULE ! {delete_transaction, Tid}; @@ -1623,6 +1635,7 @@ rec_acc_pre_commit([], Tid, Store, {Commit,OrigC}, Res, DumperMode, GoodPids, Sc Res. %% Note all nodes in case of mnesia_down mgt +%% sync_schema_commit is (ab)used for sync_asym_trans as well. prepare_sync_schema_commit(_Store, []) -> ok; prepare_sync_schema_commit(Store, [Pid | Pids]) -> @@ -1648,17 +1661,17 @@ tell_participants([Pid | Pids], Msg) -> tell_participants([], _Msg) -> ok. --spec commit_participant(_, _, _, _, _) -> no_return(). +-spec commit_participant(_, _, _, _, _, _) -> no_return(). %% Trap exit because we can get a shutdown from application manager -commit_participant(Coord, Tid, Bin, DiscNs, RamNs) when is_binary(Bin) -> +commit_participant(Protocol, Coord, Tid, Bin, DiscNs, RamNs) when is_binary(Bin) -> process_flag(trap_exit, true), Commit = binary_to_term(Bin), - commit_participant(Coord, Tid, Bin, Commit, DiscNs, RamNs); -commit_participant(Coord, Tid, C = #commit{}, DiscNs, RamNs) -> + commit_participant(Protocol, Coord, Tid, Bin, Commit, DiscNs, RamNs); +commit_participant(Protocol, Coord, Tid, C = #commit{}, DiscNs, RamNs) -> process_flag(trap_exit, true), - commit_participant(Coord, Tid, C, C, DiscNs, RamNs). + commit_participant(Protocol, Coord, Tid, C, C, DiscNs, RamNs). -commit_participant(Coord, Tid, Bin, C0, DiscNs, _RamNs) -> +commit_participant(Protocol, Coord, Tid, Bin, C0, DiscNs, _RamNs) -> ?eval_debug_fun({?MODULE, commit_participant, pre}, [{tid, Tid}]), try mnesia_schema:prepare_commit(Tid, C0, {part, Coord}) of {Modified, C = #commit{}, DumperMode} -> @@ -1683,8 +1696,9 @@ commit_participant(Coord, Tid, Bin, C0, DiscNs, _RamNs) -> mnesia_recover:log_decision(D#decision{outcome = unclear}), ?eval_debug_fun({?MODULE, commit_participant, pre_commit}, [{tid, Tid}]), - Expect_schema_ack = C#commit.schema_ops /= [], - reply(Coord, {acc_pre_commit, Tid, self(), Expect_schema_ack}), + ExpectAck = C#commit.schema_ops /= [] + orelse Protocol =:= sync_asym_trans, + reply(Coord, {acc_pre_commit, Tid, self(), ExpectAck}), %% Now we are vulnerable for failures, since %% we cannot decide without asking others @@ -1694,7 +1708,7 @@ commit_participant(Coord, Tid, Bin, C0, DiscNs, _RamNs) -> ?eval_debug_fun({?MODULE, commit_participant, log_commit}, [{tid, Tid}]), do_commit(Tid, C, DumperMode), - case Expect_schema_ack of + case ExpectAck of false -> ignore; true -> reply(Coord, {schema_commit, Tid, self()}) end, @@ -1978,7 +1992,7 @@ sync_send_dirty(Tid, [Head | Tail], Tab, WaitFor) -> Res = do_dirty(Tid, Head), {WF, Res}; true -> - {?MODULE, Node} ! {self(), {sync_dirty, Tid, ext_format(Head), Tab}}, + {?MODULE, Node} ! {self(), {sync_dirty, Tid, Head, Tab}}, sync_send_dirty(Tid, Tail, Tab, [Node | WaitFor]) end; sync_send_dirty(_Tid, [], _Tab, WaitFor) -> @@ -1997,11 +2011,11 @@ async_send_dirty(Tid, [Head | Tail], Tab, ReadNode, WaitFor, Res) -> NewRes = do_dirty(Tid, Head), async_send_dirty(Tid, Tail, Tab, ReadNode, WaitFor, NewRes); ReadNode == Node -> - {?MODULE, Node} ! {self(), {sync_dirty, Tid, ext_format(Head), Tab}}, + {?MODULE, Node} ! {self(), {sync_dirty, Tid, Head, Tab}}, NewRes = {'EXIT', {aborted, {node_not_running, Node}}}, async_send_dirty(Tid, Tail, Tab, ReadNode, [Node | WaitFor], NewRes); true -> - {?MODULE, Node} ! {self(), {async_dirty, Tid, ext_format(Head), Tab}}, + {?MODULE, Node} ! {self(), {async_dirty, Tid, Head, Tab}}, async_send_dirty(Tid, Tail, Tab, ReadNode, WaitFor, Res) end; async_send_dirty(_Tid, [], _Tab, _ReadNode, WaitFor, Res) -> @@ -2058,24 +2072,20 @@ ask_commit(Protocol, Tid, [Head | Tail], DiscNs, RamNs, WaitFor, Local) -> Node == node() -> ask_commit(Protocol, Tid, Tail, DiscNs, RamNs, WaitFor, Head); true -> - CR = ext_format(Head), - Msg = {ask_commit, Protocol, Tid, CR, DiscNs, RamNs}, + Msg = {ask_commit, convert_old(Protocol, Node), Tid, Head, DiscNs, RamNs}, {?MODULE, Node} ! {self(), Msg}, ask_commit(Protocol, Tid, Tail, DiscNs, RamNs, [Node | WaitFor], Local) end; ask_commit(_Protocol, _Tid, [], _DiscNs, _RamNs, WaitFor, Local) -> {WaitFor, Local}. -ext_format(#commit{ext=[]}=CR) -> CR; -ext_format(#commit{node=Node, ext=Ext}=CR) -> +convert_old(sync_asym_trans, Node) -> case mnesia_monitor:needs_protocol_conversion(Node) of - true -> - case lists:keyfind(snmp, 1, Ext) of - false -> CR#commit{ext=[]}; - {snmp, List} -> CR#commit{ext=List} - end; - false -> CR - end. + true -> asym_trans; + false -> sync_asym_trans + end; +convert_old(Protocol, _) -> + Protocol. new_cr_format(#commit{ext=[]}=Cr) -> Cr; new_cr_format(#commit{ext=[{_,_}|_]}=Cr) -> Cr; @@ -2304,7 +2314,7 @@ reconfigure_participants(_, []) -> %% tell mnesia_tm on all involved nodes (including the local node) %% about the outcome. tell_outcome(Tid, Protocol, Node, CheckNodes, TellNodes) -> - Outcome = mnesia_recover:what_happened(Tid, Protocol, CheckNodes), + Outcome = mnesia_recover:what_happened(Tid, proto(Protocol), CheckNodes), case Outcome of aborted -> rpc:abcast(TellNodes, ?MODULE, {Tid,{do_abort, {mnesia_down, Node}}}); @@ -2313,6 +2323,9 @@ tell_outcome(Tid, Protocol, Node, CheckNodes, TellNodes) -> end, Outcome. +proto(sync_asym_trans) -> asym_trans; +proto(Proto) -> Proto. + do_stop(#state{coordinators = Coordinators}) -> Msg = {mnesia_down, node()}, lists:foreach(fun({Tid, _}) -> Tid#tid.pid ! Msg end, gb_trees:to_list(Coordinators)), diff --git a/lib/mnesia/test/mnesia_isolation_test.erl b/lib/mnesia/test/mnesia_isolation_test.erl index 49bcec14af..c99158945d 100644 --- a/lib/mnesia/test/mnesia_isolation_test.erl +++ b/lib/mnesia/test/mnesia_isolation_test.erl @@ -29,7 +29,7 @@ -export([no_conflict/1, simple_queue_conflict/1, advanced_queue_conflict/1, simple_deadlock_conflict/1, advanced_deadlock_conflict/1, schema_deadlock/1, lock_burst/1, - nasty/1, basic_sticky_functionality/1, + nasty/1, basic_sticky_functionality/1, sticky_sync/1, unbound1/1, unbound2/1, create_table/1, delete_table/1, move_table_copy/1, add_table_index/1, del_table_index/1, transform_table/1, @@ -71,7 +71,8 @@ groups() -> advanced_deadlock_conflict, schema_deadlock, lock_burst, {group, sticky_locks}, {group, unbound_locking}, {group, admin_conflict}, nasty]}, - {sticky_locks, [], [basic_sticky_functionality]}, + {sticky_locks, [], + [basic_sticky_functionality,sticky_sync]}, {unbound_locking, [], [unbound1, unbound2]}, {admin_conflict, [], [create_table, delete_table, move_table_copy, @@ -594,9 +595,49 @@ get_held() -> mnesia_locker ! {get_table, self(), mnesia_sticky_locks}, receive {mnesia_sticky_locks, Locks} -> Locks end. +sticky_sync(suite) -> []; +sticky_sync(Config) when is_list(Config) -> + %% BUG ERIERL-768 + Nodes = [N1, N2] = ?acquire_nodes(2, Config), + + mnesia:create_table(dc, [{type, ordered_set}, {disc_copies, Nodes}]), + mnesia:create_table(ec, [{type, ordered_set}, {ram_copies, [N2]}]), + + TestFun = + fun(I) -> + %% In first transaction we initialise {dc, I} record with value 0 + First = fun() -> + %% Do a lot of writes into ram copies table + %% which on the Slave in do_commit will be + %% processed first + lists:foreach(fun(J) -> ok = mnesia:write(ec, {ec, J, 0}, write) end, + lists:seq(1, 750)), + %% Then set initial value of {dc, I} record to 0 with sticky_write + mnesia:write(dc, {dc, I, 0}, sticky_write) + end, + ok = mnesia:activity(transaction, First), + %% In second transaction we set value of {dc, I} record to 1 + Upd = fun() -> + %% Modify a single ram copies record with ensured lock grant + %% (key not used in previous transactions) + %% we use this second table only to force asym_trans protocol + mnesia:write(ec, {ec, 1001 + I, 0}, write), + %% And set final version of {dc, I} record to 1 with sticky_write + mnesia:write(dc, {dc, I, 1}, sticky_write) + end, + ok = mnesia:activity(transaction, Upd) + end, + + %% Fill 1000 dc records. At the end all dc records should have value 1. + lists:foreach(TestFun, lists:seq(1,1000)), + io:format("Written, check content~n",[]), + All = fun() -> mnesia:select(dc, [ {{dc, '_', 0}, [] ,['$_']} ]) end, + ?match({atomic, []}, rpc:call(N1, mnesia, sync_transaction, [All])), + ?match({atomic, []}, rpc:call(N2, mnesia, sync_transaction, [All])), -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + ?verify_mnesia(Nodes, []). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% unbound1(suite) -> []; unbound1(Config) when is_list(Config) -> @@ -1036,29 +1077,57 @@ add_table_copy(Config) when is_list(Config) -> Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}], ?match({atomic, ok}, mnesia:create_table(Tab, Def)), insert(Tab, 50), - {success, [A]} = ?start_activities([ThisNode]), + {success, [A]} = ?start_activities([ThisNode]), mnesia_test_lib:start_sync_transactions([A], 0), A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end, ?match_receive({A, ok}), %% A is executed - Pid = spawn_link(?MODULE, op, [self(), mnesia, add_table_copy, + Pid = spawn_link(?MODULE, op, [self(), mnesia, add_table_copy, [Tab, Node2, ram_copies]]), - + ?match_receive(timeout), %% op waits for locks occupied by A A ! end_trans, %% Kill A, locks should be released - ?match_receive({A,{atomic,end_trans}}), - - receive + ?match_receive({A,{atomic,end_trans}}), + + receive Msg -> ?match({Pid, {atomic, ok}}, Msg) after timer:seconds(20) -> ?error("Operation timed out", []) end, + ?match_receive({'EXIT', Pid, normal}), sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async - ?match([], mnesia:system_info(held_locks)), - ?match([], mnesia:system_info(lock_queue)), + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), + + {atomic, ok} = mnesia:del_table_copy(Tab, Node2), + Self = self(), + New = spawn_link(Node2, + fun () -> + application:stop(mnesia), + Self ! {self(), ok}, + io:format(user, "restart mnesia~n", []), + Self ! {self(), catch application:start(mnesia)} + end), + receive {New,ok} -> ok end, + + Add = fun Add() -> + case mnesia:add_table_copy(Tab, Node2, disc_copies) of + {atomic, ok} -> ok; + _R -> io:format(user, "aborted with reason ~p~n", [_R]), + timer:sleep(10), + Add() + end + end, + + ?match(ok, Add()), + ?match_receive({New,ok}), + + sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async + ?match([], mnesia:system_info(held_locks)), + ?match([], mnesia:system_info(lock_queue)), ok. del_table_copy(suite) -> []; diff --git a/lib/mnesia/test/mt b/lib/mnesia/test/mt index a398ee0422..b169734f56 100755 --- a/lib/mnesia/test/mt +++ b/lib/mnesia/test/mt @@ -34,8 +34,35 @@ erlcmd="erl -sname a $p $args -mnesia_test_timeout" erlcmd1="erl -sname a1 $p $args" erlcmd2="erl -sname a2 $p $args" -xterm -geometry 70x20+0+550 -T a1 -e $erlcmd1 & -xterm -geometry 70x20+450+550 -T a2 -e $erlcmd2 & +if test z"$MT_TERM" = z ; then + MT_TERM=xterm +fi + +case $MT_TERM in + xterm) + geom0="-geometry 142x40+0+0" + geom1="-geometry 70x20+0+550" + geom2="-geometry 70x20+480+550" + title="-T" + exec="-e" + ;; + gnome-terminal) + geom0="--geometry 142x40+0+0" + geom1="--geometry 70x20+0+740" + geom2="--geometry 70x20+700+740" + title="--title" + exec="--hide-menubar --" + ;; + *rxvt) + geom0="-geometry 142x40+0+0" + geom1="-geometry 70x20+0+680" + geom2="-geometry 70x20+630+680" + title="-title" + exec="-e" +esac + +$MT_TERM $geom1 $title a1 $exec $erlcmd1 & +$MT_TERM $geom2 $title a2 $exec $erlcmd2 & rm "$latest" 2>/dev/null ln -s "$log" "$latest" @@ -51,11 +78,6 @@ echo "Give the following command in order to see the outcome from node a@$h"":" echo "" echo " less test_log$$" -ostype=`uname -s` -if [ "$ostype" = "SunOS" ] ; then - /usr/openwin/bin/xterm -geometry 145x40+0+0 -T a -l -lf "$log" -e $erlcmd & -else - xterm -geometry 145x40+0+0 -T a -e script -f -c "$erlcmd" "$log" & -fi +$MT_TERM $geom0 $title a $exec script -f -c "$erlcmd" "$log" & tail -f "$log" | egrep 'Eval|<>ERROR|NYI' diff --git a/lib/observer/src/cdv_bin_cb.erl b/lib/observer/src/cdv_bin_cb.erl index 91d33474c8..819596b483 100644 --- a/lib/observer/src/cdv_bin_cb.erl +++ b/lib/observer/src/cdv_bin_cb.erl @@ -33,42 +33,43 @@ detail_pages() -> [{"Binary", fun init_bin_page/2}]. init_bin_page(Parent,{Type,Bin}) -> + Cs = observer_lib:colors(Parent), cdv_multi_wx:start_link( Parent, - [{"Format \~p",cdv_html_wx,{Type,format_bin_fun("~p",Bin)}}, - {"Format \~tp",cdv_html_wx,{Type,format_bin_fun("~tp",Bin)}}, - {"Format \~w",cdv_html_wx,{Type,format_bin_fun("~w",Bin)}}, - {"Format \~tw",cdv_html_wx,{Type,format_bin_fun("~tw",Bin)}}, - {"Format \~s",cdv_html_wx,{Type,format_bin_fun("~s",Bin)}}, - {"Format \~ts",cdv_html_wx,{Type,format_bin_fun("~ts",Bin)}}, - {"Hex",cdv_html_wx,{Type,hex_binary_fun(Bin)}}, - {"Term",cdv_html_wx,{Type,binary_to_term_fun(Bin)}}]). + [{"Format \~p",cdv_html_wx,{Type,format_bin_fun("~p",Bin,Cs)}}, + {"Format \~tp",cdv_html_wx,{Type,format_bin_fun("~tp",Bin,Cs)}}, + {"Format \~w",cdv_html_wx,{Type,format_bin_fun("~w",Bin,Cs)}}, + {"Format \~tw",cdv_html_wx,{Type,format_bin_fun("~tw",Bin,Cs)}}, + {"Format \~s",cdv_html_wx,{Type,format_bin_fun("~s",Bin,Cs)}}, + {"Format \~ts",cdv_html_wx,{Type,format_bin_fun("~ts",Bin,Cs)}}, + {"Hex",cdv_html_wx,{Type,hex_binary_fun(Bin,Cs)}}, + {"Term",cdv_html_wx,{Type,binary_to_term_fun(Bin,Cs)}}]). -format_bin_fun(Format,Bin) -> +format_bin_fun(Format,Bin,Cs) -> fun() -> try io_lib:format(Format,[Bin]) of - Str -> plain_html(lists:flatten(Str)) + Str -> plain_html(lists:flatten(Str),Cs) catch error:badarg -> Warning = "This binary cannot be formatted with " ++ Format, - observer_html_lib:warning(Warning) + observer_html_lib:warning(Warning,Cs) end end. -binary_to_term_fun(Bin) -> +binary_to_term_fun(Bin,Cs) -> fun() -> try binary_to_term(Bin) of - Term -> plain_html(io_lib:format("~tp",[Term])) + Term -> plain_html(io_lib:format("~tp",[Term]),Cs) catch error:badarg -> Warning = "This binary cannot be converted to an Erlang term", - observer_html_lib:warning(Warning) + observer_html_lib:warning(Warning,Cs) end end. -define(line_break,25). -hex_binary_fun(Bin) -> +hex_binary_fun(Bin,Cs) -> fun() -> S = "<<" ++ format_hex(Bin,?line_break) ++ ">>", - plain_html(io_lib:format("~s",[S])) + plain_html(io_lib:format("~s",[S]), Cs) end. format_hex(<<>>,_) -> @@ -82,5 +83,5 @@ format_hex(<<B1:4,B2:4,Bin/binary>>,N) -> [integer_to_list(B1,16),integer_to_list(B2,16),$, | format_hex(Bin,N-1)]. -plain_html(Text) -> - observer_html_lib:plain_page(Text). +plain_html(Text,Cs) -> + observer_html_lib:plain_page(Text,Cs). diff --git a/lib/observer/src/cdv_html_wx.erl b/lib/observer/src/cdv_html_wx.erl index 8956173c93..83ee98de6e 100644 --- a/lib/observer/src/cdv_html_wx.erl +++ b/lib/observer/src/cdv_html_wx.erl @@ -30,7 +30,8 @@ %% Records -record(state, - {panel, + {parent, + panel, app, %% which tool is the user expand_table, expand_wins=[], @@ -62,7 +63,7 @@ init(ParentWin, HtmlText, Tab, App) -> HtmlWin = observer_lib:html_window(ParentWin), wxHtmlWindow:setPage(HtmlWin,HtmlText), wx_misc:endBusyCursor(), - {HtmlWin, #state{panel=HtmlWin,expand_table=Tab,app=App}}. + {HtmlWin, #state{parent=ParentWin, panel=HtmlWin,expand_table=Tab,app=App}}. init(ParentWin, Callback) -> {HtmlWin, State} = init(ParentWin, "", undefined, cdv), @@ -70,12 +71,15 @@ init(ParentWin, Callback) -> %%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -handle_info(active, #state{panel=HtmlWin,delayed_fetch=Callback}=State) +handle_info(active, #state{parent=Parent, panel=HtmlWin,delayed_fetch=Callback}=State) when Callback=/=undefined -> observer_lib:display_progress_dialog(HtmlWin, "Crashdump Viewer", "Reading data"), - {{expand,HtmlText,Tab},TW} = Callback:get_info(), + {{expand,Title,Info,Tab},TW} = Callback:get_info(), + Cs = observer_lib:colors(Parent), + HtmlText = observer_html_lib:expandable_term(Title,Info,Tab,Cs), + observer_lib:sync_destroy_progress_dialog(), wx_misc:beginBusyCursor(), wxHtmlWindow:setPage(HtmlWin,HtmlText), @@ -138,7 +142,8 @@ handle_event(#wx{event=#wxHtmlLink{type=command_html_link_clicked, list_to_integer(Key3)}}}, expand(Id,cdv_term_cb,State); _ when App =:= obs -> - observer ! {open_link, Target}; + observer ! {open_link, Target}, + State; _ -> cdv_virtual_list_wx:start_detail_win(Target), State diff --git a/lib/observer/src/cdv_mod_cb.erl b/lib/observer/src/cdv_mod_cb.erl index 2183e1aa3d..7f2cd0cf87 100644 --- a/lib/observer/src/cdv_mod_cb.erl +++ b/lib/observer/src/cdv_mod_cb.erl @@ -85,7 +85,8 @@ init_old_comp_page(Parent, Info) -> init_info_page(Parent, undefined) -> init_info_page(Parent, ""); init_info_page(Parent, String) -> - cdv_html_wx:start_link(Parent,observer_html_lib:plain_page(String)). + Cs = observer_lib:colors(Parent), + cdv_html_wx:start_link(Parent,observer_html_lib:plain_page(String,Cs)). format({Bin,q}) when is_binary(Bin) -> [$'|binary_to_list(Bin)]; diff --git a/lib/observer/src/cdv_persistent_cb.erl b/lib/observer/src/cdv_persistent_cb.erl index d5da18f7fc..90abc6a4f5 100644 --- a/lib/observer/src/cdv_persistent_cb.erl +++ b/lib/observer/src/cdv_persistent_cb.erl @@ -26,7 +26,4 @@ get_info() -> Tab = ets:new(pt_expand,[set,public]), {ok,PT,TW} = crashdump_viewer:persistent_terms(), - {{expand, - observer_html_lib:expandable_term("Persistent Terms",PT,Tab), - Tab}, - TW}. + {{expand, "Persistent Terms", PT, Tab}, TW}. diff --git a/lib/observer/src/cdv_proc_cb.erl b/lib/observer/src/cdv_proc_cb.erl index 2497b4889e..61bd86f188 100644 --- a/lib/observer/src/cdv_proc_cb.erl +++ b/lib/observer/src/cdv_proc_cb.erl @@ -108,7 +108,7 @@ init_stack_page(Parent, Info) -> init_memory_page(Parent, Info0, Tag, Heading) -> Info = proplists:get_value(Tag,Info0), Tab = proplists:get_value(expand_table,Info0), - Html = observer_html_lib:expandable_term(Heading,Info,Tab), + Html = observer_html_lib:expandable_term(Heading,Info,Tab, observer_lib:colors(Parent)), cdv_html_wx:start_link(Parent,{expand,Html,Tab}). init_ets_page(Parent, Info) -> diff --git a/lib/observer/src/cdv_term_cb.erl b/lib/observer/src/cdv_term_cb.erl index 85da1d227a..a2a7a8750d 100644 --- a/lib/observer/src/cdv_term_cb.erl +++ b/lib/observer/src/cdv_term_cb.erl @@ -35,31 +35,32 @@ init_term_page(ParentWin, {Type, [Term, Tab]}) -> Expanded = expand(Term, true), BinSaved = expand(Term, Tab), observer_lib:report_progress({ok,stop_pulse}), + Cs = observer_lib:colors(ParentWin), cdv_multi_wx:start_link( ParentWin, - [{"Format \~p",cdv_html_wx,{Type, format_term_fun("~p",BinSaved,Tab)}}, - {"Format \~tp",cdv_html_wx,{Type,format_term_fun("~tp",BinSaved,Tab)}}, - {"Format \~w",cdv_html_wx,{Type,format_term_fun("~w",BinSaved,Tab)}}, - {"Format \~tw",cdv_html_wx,{Type,format_term_fun("~tw",BinSaved,Tab)}}, - {"Format \~s",cdv_html_wx,{Type,format_term_fun("~s",Expanded,Tab)}}, - {"Format \~ts",cdv_html_wx,{Type,format_term_fun("~ts",Expanded,Tab)}}]). + [{"Format \~p",cdv_html_wx,{Type, format_term_fun("~p",BinSaved,Tab,Cs)}}, + {"Format \~tp",cdv_html_wx,{Type,format_term_fun("~tp",BinSaved,Tab,Cs)}}, + {"Format \~w",cdv_html_wx,{Type,format_term_fun("~w",BinSaved,Tab,Cs)}}, + {"Format \~tw",cdv_html_wx,{Type,format_term_fun("~tw",BinSaved,Tab,Cs)}}, + {"Format \~s",cdv_html_wx,{Type,format_term_fun("~s",Expanded,Tab,Cs)}}, + {"Format \~ts",cdv_html_wx,{Type,format_term_fun("~ts",Expanded,Tab,Cs)}}]). -format_term_fun(Format,Term,Tab) -> +format_term_fun(Format,Term,Tab,Cs) -> fun() -> observer_lib:report_progress({ok,"Formatting term"}), observer_lib:report_progress({ok,start_pulse}), try io_lib:format(Format,[Term]) of - Str -> {expand, plain_html(Str), Tab} + Str -> {expand, plain_html(Str,Cs), Tab} catch error:badarg -> Warning = "This term cannot be formatted with " ++ Format, - observer_html_lib:warning(Warning) + observer_html_lib:warning(Warning,Cs) after observer_lib:report_progress({ok,stop_pulse}) end end. -plain_html(Text) -> - observer_html_lib:plain_page(Text). +plain_html(Text,Cs) -> + observer_html_lib:plain_page(Text,Cs). expand(['#CDVBin',Offset,Size,Pos], true) -> {ok,Bin} = crashdump_viewer:expand_binary({Offset,Size,Pos}), diff --git a/lib/observer/src/cdv_virtual_list_wx.erl b/lib/observer/src/cdv_virtual_list_wx.erl index 14877b7eab..51e85e17c1 100644 --- a/lib/observer/src/cdv_virtual_list_wx.erl +++ b/lib/observer/src/cdv_virtual_list_wx.erl @@ -96,8 +96,9 @@ start_detail_win_2(Callback,Id) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init([ParentWin, Callback, Owner]) -> - {Holder,TW} = spawn_table_holder(Callback, Owner), Panel = wxPanel:new(ParentWin), + Attrs = observer_lib:create_attrs(Panel), + {Holder,TW} = spawn_table_holder(Callback, Owner, Attrs), {Grid,MenuCols} = create_list_box(Panel, Holder, Callback, Owner), Sizer = wxBoxSizer:new(?wxVERTICAL), wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxALL}, @@ -233,7 +234,8 @@ handle_call(new_dump, _From, Ref = erlang:monitor(process,Holder), Holder ! stop, receive {'DOWN',Ref,_,_,_} -> ok end, - {NewHolder,TW} = spawn_table_holder(Callback, all), + Attrs = observer_lib:create_attrs(Grid), + {NewHolder,TW} = spawn_table_holder(Callback, all, Attrs), {reply, ok, State#state{detail_wins=[],holder=NewHolder,trunc_warn=TW}}; handle_call(Msg, _From, State) -> @@ -329,9 +331,8 @@ handle_event(Event, State) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%TABLE HOLDER%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -spawn_table_holder(Callback, Owner) -> +spawn_table_holder(Callback, Owner, Attrs) -> {Info,TW} = Callback:get_info(Owner), - Attrs = observer_lib:create_attrs(), Parent = self(), Holder = case Owner of diff --git a/lib/observer/src/cdv_wx.erl b/lib/observer/src/cdv_wx.erl index 7100cc8790..8ad5da857e 100644 --- a/lib/observer/src/cdv_wx.erl +++ b/lib/observer/src/cdv_wx.erl @@ -197,8 +197,7 @@ setup(#state{frame=Frame, notebook=Notebook}=State) -> MemPanel = add_page(Notebook, ?MEM_STR, cdv_multi_wx, cdv_mem_cb), %% Persistent Terms Panel - PersistentPanel = add_page(Notebook, ?PERSISTENT_STR, - cdv_html_wx, cdv_persistent_cb), + PersistentPanel = add_page(Notebook, ?PERSISTENT_STR, cdv_html_wx, cdv_persistent_cb), %% Memory Panel IntPanel = add_page(Notebook, ?INT_STR, cdv_multi_wx, cdv_int_tab_cb), diff --git a/lib/observer/src/observer_app_wx.erl b/lib/observer/src/observer_app_wx.erl index 8c3eef5411..c4527ba063 100644 --- a/lib/observer/src/observer_app_wx.erl +++ b/lib/observer/src/observer_app_wx.erl @@ -48,7 +48,7 @@ usegc = false }). --record(paint, {font, pen, brush, sel, links}). +-record(paint, {font, fg, pen, brush, sel, links}). -record(app, {ptree, n2p, links, dim}). -record(box, {x,y, w,h, s1}). @@ -92,7 +92,8 @@ init([Notebook, Parent, _Config]) -> Extra = wxBoxSizer:new(?wxVERTICAL), DrawingArea = wxScrolledWindow:new(P2, [{winid, ?DRAWAREA}, {style,?wxFULL_REPAINT_ON_RESIZE}]), - wxWindow:setBackgroundColour(DrawingArea, ?wxWHITE), + BG = wxWindow:getBackgroundColour(Apps), + wxWindow:setBackgroundStyle(DrawingArea, ?wxBG_STYLE_SYSTEM), wxWindow:setVirtualSize(DrawingArea, 800, 800), wxSplitterWindow:setMinimumPaneSize(Splitter,50), wxSizer:add(Extra, DrawingArea, [{flag, ?wxEXPAND},{proportion, 1}]), @@ -127,7 +128,17 @@ init([Notebook, Parent, _Config]) -> Font0 end, SelCol = wxSystemSettings:getColour(?wxSYS_COLOUR_HIGHLIGHT), - GreyBrush = wxBrush:new({230,230,240}), + {Fg,BGBrush,Pen} = + case observer_lib:is_darkmode(BG) of + false -> + {wxSystemSettings:getColour(?wxSYS_COLOUR_BTNTEXT), + wxBrush:new(wxSystemSettings:getColour(?wxSYS_COLOUR_BTNSHADOW)), + wxPen:new({80,80,80}, [{width, Scale * 2}])}; + true -> + {wxSystemSettings:getColour(?wxSYS_COLOUR_BTNTEXT), + wxBrush:new(wxSystemSettings:getColour(?wxSYS_COLOUR_BTNSHADOW)), + wxPen:new({0,0,0}, [{width, Scale * 2}])} + end, SelBrush = wxBrush:new(SelCol), LinkPen = wxPen:new(SelCol, [{width, Scale * 2}]), process_flag(trap_exit, true), @@ -137,8 +148,9 @@ init([Notebook, Parent, _Config]) -> app_w =DrawingArea, usegc = UseGC, paint=#paint{font = Font, - pen = wxPen:new({80,80,80}, [{width, Scale * 2}]), - brush= GreyBrush, + fg = Fg, + pen = Pen, + brush= BGBrush, sel = SelBrush, links= LinkPen } @@ -306,11 +318,11 @@ handle_info({delivery, _Pid, app, _Curr, {[], [], [], []}}, handle_info({delivery, Pid, app, Curr, AppData}, State = #state{panel=Panel, appmon=Pid, current=Curr, usegc=UseGC, - app_w=AppWin, paint=#paint{font=Font}}) -> + app_w=AppWin, paint=#paint{fg=Fg, font=Font}}) -> GC = if UseGC -> {?wxGC:create(AppWin), false}; true -> {false, wxWindowDC:new(AppWin)} end, - setFont(GC, Font, {0,0,0}), + setFont(GC, Font, Fg), App = build_tree(AppData, GC), destroy_gc(GC), setup_scrollbar(AppWin, App), @@ -508,13 +520,13 @@ tree_map([], _ , Acc) -> Acc. draw(_DC, undefined, _, _) -> ok; draw(DC, #app{dim={_W,_H}, ptree=Tree, links=Links}, Sel, - #paint{font=Font, pen=Pen, brush=Brush, links=LPen, sel=SelBrush}) -> + #paint{font=Font, fg=Fg, pen=Pen, brush=Brush, links=LPen, sel=SelBrush}) -> setPen(DC, LPen), [draw_xlink(Link, DC) || Link <- Links], setPen(DC, Pen), %% ?wxGC:drawRectangle(DC, 2,2, _W-2,_H-2), %% DEBUG setBrush(DC, Brush), - setFont(DC, Font, {0,0,0}), + setFont(DC, Font, Fg), draw_tree(Tree, root, DC), case Sel of undefined -> ok; diff --git a/lib/observer/src/observer_defs.hrl b/lib/observer/src/observer_defs.hrl index 504d0877d9..7902b32cba 100644 --- a/lib/observer/src/observer_defs.hrl +++ b/lib/observer/src/observer_defs.hrl @@ -36,6 +36,7 @@ check = false }). +-record(colors, {fg, even, odd}). -record(attrs, {even, odd, searched, deleted, changed_odd, changed_even, new_odd, new_even}). -define(EVEN(Row), ((Row rem 2) =:= 0)). -define(BG_EVEN, {230,230,250}). diff --git a/lib/observer/src/observer_html_lib.erl b/lib/observer/src/observer_html_lib.erl index c67fa28c6d..4c92a8faab 100644 --- a/lib/observer/src/observer_html_lib.erl +++ b/lib/observer/src/observer_html_lib.erl @@ -24,9 +24,9 @@ %% viewer. No logic or states are kept by this module. %% --export([plain_page/1, - expandable_term/3, - warning/1]). +-export([plain_page/2, + expandable_term/4, + warning/2]). -include("crashdump_viewer.hrl"). -include("observer_defs.hrl"). @@ -34,8 +34,9 @@ %%%----------------------------------------------------------------- %%% Display the given information as is, no heading %%% Empty body if no info exists. -warning(Info) -> - header(body(warning_body(Info))). +warning(Info, Colors0) -> + Colors = convert(Colors0), + header(body(warning_body(Info), Colors)). warning_body(Info) -> [warn(Info)]. @@ -43,18 +44,22 @@ warning_body(Info) -> %%%----------------------------------------------------------------- %%% Display the given information as is, no heading %%% Empty body if no info exists. -plain_page(Info) -> - header(body(plain_body(Info))). +plain_page(Info, Colors0) -> + Colors = convert(Colors0), + header(body(plain_body(Info), Colors)). plain_body(Info) -> [pre(href_proc_port(lists:flatten(Info)))]. %%%----------------------------------------------------------------- %%% Expanded memory -expandable_term(Heading,Expanded,Tab) -> - header(Heading,body(expandable_term_body(Heading,Expanded,Tab))). +expandable_term(Heading,Expanded,Tab, Colors0) -> + Colors = convert(Colors0), + header(Heading, + body(expandable_term_body(Heading,Expanded,Tab,Colors), + Colors)). -expandable_term_body(Heading,[],_Tab) -> +expandable_term_body(Heading,[],_Tab, _) -> [case Heading of "MsgQueue" -> "No messages were found"; "Message Queue" -> "No messages were found"; @@ -65,7 +70,7 @@ expandable_term_body(Heading,[],_Tab) -> "SaslLog" -> "No log entry was found"; "Persistent Terms" -> "No persistent terms were found" end]; -expandable_term_body(Heading,Expanded,Tab) -> +expandable_term_body(Heading,Expanded,Tab, Colors) -> Attr = "BORDER=0 CELLPADDING=0 CELLSPACING=1 WIDTH=100%", [case Heading of "MsgQueue" -> @@ -74,7 +79,7 @@ expandable_term_body(Heading,Expanded,Tab) -> [th("WIDTH=70%","Message"), th("WIDTH=30%","SeqTraceToken")]) | element(1, lists:mapfoldl(fun(Msg, Even) -> - {msgq_table(Tab, Msg, Even), + {msgq_table(Tab, Msg, Even, Colors), not Even} end, true, Expanded))]); @@ -84,7 +89,7 @@ expandable_term_body(Heading,Expanded,Tab) -> [th("WIDTH=10%","Id"), th("WIDTH=90%","Message")]) | element(1, lists:mapfoldl(fun(Msg, {Even,N}) -> - {msgq_table(Tab, Msg, N, Even), + {msgq_table(Tab, Msg, N, Even, Colors), {not Even, N+1}} end, {true,1}, Expanded))]); @@ -94,7 +99,7 @@ expandable_term_body(Heading,Expanded,Tab) -> [th("WIDTH=20%","Label"), th("WIDTH=80%","Term")]) | element(1, lists:mapfoldl(fun(Entry, Even) -> - {stackdump_table(Tab, Entry, Even), + {stackdump_table(Tab, Entry, Even, Colors), not Even} end, true, Expanded))]); "ProcState" -> @@ -103,7 +108,7 @@ expandable_term_body(Heading,Expanded,Tab) -> [th("WIDTH=20%","Label"), th("WIDTH=80%","Information")]) | element(1, lists:mapfoldl(fun(Entry, Even) -> - {proc_state(Tab, Entry,Even), + {proc_state(Tab, Entry,Even, Colors), not Even} end, true, Expanded))]); "SaslLog" -> @@ -115,37 +120,37 @@ expandable_term_body(Heading,Expanded,Tab) -> [th("WIDTH=30%","Key"), th("WIDTH=70%","Value")]) | element(1, lists:mapfoldl(fun(Entry, Even) -> - {dict_table(Tab, Entry,Even), + {dict_table(Tab, Entry, Even, Colors), not Even} end, true, Expanded))]) end]. -msgq_table(Tab,{Msg0,Token0}, Even) -> +msgq_table(Tab,{Msg0,Token0}, Even, Colors) -> Token = case Token0 of [] -> ""; _ -> io_lib:fwrite("~w",[Token0]) end, Msg = all_or_expand(Tab,Msg0), - tr(color(Even),[td(pre(Msg)), td(Token)]). + tr(color(Even, Colors),[td(pre(Msg)), td(Token)]). -msgq_table(Tab,Msg0, Id, Even) -> +msgq_table(Tab,Msg0, Id, Even, Colors) -> Msg = all_or_expand(Tab,Msg0), - tr(color(Even),[td(integer_to_list(Id)), td(pre(Msg))]). + tr(color(Even, Colors),[td(integer_to_list(Id)), td(pre(Msg))]). -stackdump_table(Tab,{Label0,Term0},Even) -> +stackdump_table(Tab,{Label0,Term0},Even, Colors) -> Label = io_lib:format("~w",[Label0]), Term = all_or_expand(Tab,Term0), - tr(color(Even), [td("VALIGN=center",pre(Label)), td(pre(Term))]). + tr(color(Even, Colors), [td("VALIGN=center",pre(Label)), td(pre(Term))]). -dict_table(Tab,{Key0,Value0}, Even) -> +dict_table(Tab,{Key0,Value0}, Even, Colors) -> Key = all_or_expand(Tab,Key0), Value = all_or_expand(Tab,Value0), - tr(color(Even), [td("VALIGN=center",pre(Key)), td(pre(Value))]). + tr(color(Even, Colors), [td("VALIGN=center",pre(Key)), td(pre(Value))]). -proc_state(Tab,{Key0,Value0}, Even) -> +proc_state(Tab,{Key0,Value0}, Even, Colors) -> Key = lists:flatten(io_lib:format("~ts",[Key0])), Value = all_or_expand(Tab,Value0), - tr(color(Even), [td("VALIGN=center",Key), td(pre(Value))]). + tr(color(Even, Colors), [td("VALIGN=center",Key), td(pre(Value))]). all_or_expand(Tab,Term) -> Preview = io_lib:format("~tP",[Term,8]), @@ -171,8 +176,8 @@ all_or_expand(Tab,Bin,_PreviewStr,_Expand) Term = io_lib:format("~tp", [OBSBin]), href_proc_port(lists:flatten(Term), true). -color(true) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_EVEN)); -color(false) -> io_lib:format("BGCOLOR=\"#~2.16.0B~2.16.0B~2.16.0B\"", tuple_to_list(?BG_ODD)). +color(true, #colors{even=Even}) -> "BGCOLOR="++Even; +color(false,#colors{odd=Odd}) -> "BGCOLOR="++Odd. %%%----------------------------------------------------------------- %%% Internal library @@ -180,10 +185,10 @@ start_html() -> "<HTML>\n". stop_html() -> "</HTML>". -start_html_body() -> - "<BODY BGCOLOR=\"#FFFFFF\">\n". +start_html_body(#colors{even=Even, fg=Fg}) -> + "<BODY BGCOLOR=" ++ Even ++ ">\n <FONT COLOR=" ++ Fg ++ ">\n". stop_html_body() -> - "</BODY>\n". + "</FONT> </BODY>\n". header(Body) -> header("","",Body). @@ -205,8 +210,8 @@ only_html_header(Title,JavaScript) -> JavaScript, "</HEAD>\n"]. -body(Text) -> - [start_html_body(), +body(Text, Colors) -> + [start_html_body(Colors), Text, stop_html_body()]. @@ -417,3 +422,8 @@ warn([]) -> []; warn(Warning) -> font("COLOR=\"#FF0000\"",p([Warning,br(),br()])). + +convert(#colors{fg={FR,FB,FG}, even={ER,EB,EG}, odd={OR,OG,OB}}) -> + #colors{fg = io_lib:format("\"#~2.16.0B~2.16.0B~2.16.0B\"", [FR,FB,FG]), + even = io_lib:format("\"#~2.16.0B~2.16.0B~2.16.0B\"", [ER,EB,EG]), + odd = io_lib:format("\"#~2.16.0B~2.16.0B~2.16.0B\"", [OR,OG,OB])}. diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index 7c68b0ebb6..7d115306bd 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -28,8 +28,8 @@ interval_dialog/4, start_timer/1, start_timer/2, stop_timer/1, timer_config/1, display_info/2, display_info/3, fill_info/2, update_info/2, to_str/1, create_menus/3, create_menu_item/3, - create_attrs/0, - set_listctrl_col_size/2, + is_darkmode/1, colors/1, create_attrs/1, + set_listctrl_col_size/2, mix/3, create_status_bar/1, html_window/1, html_window/2, make_obsbin/2, @@ -373,26 +373,44 @@ create_menu_item(separator, Menu, Index) -> wxMenu:insertSeparator(Menu, Index), Index+1. -create_attrs() -> - Font = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT), +colors(Window) -> + DarkMode = is_darkmode(wxWindow:getBackgroundColour(Window)), Text = case wxSystemSettings:getColour(?wxSYS_COLOUR_LISTBOXTEXT) of - {255,255,255,_} -> {10,10,10}; %% Is white on Mac for some reason - Color -> Color - end, - #attrs{even = wxListItemAttr:new(Text, ?BG_EVEN, Font), - odd = wxListItemAttr:new(Text, ?BG_ODD, Font), - deleted = wxListItemAttr:new(?FG_DELETED, ?BG_DELETED, Font), - changed_even = wxListItemAttr:new(Text, mix(?BG_CHANGED,?BG_EVEN), Font), - changed_odd = wxListItemAttr:new(Text, mix(?BG_CHANGED,?BG_ODD), Font), - new_even = wxListItemAttr:new(Text, mix(?BG_NEW,?BG_EVEN), Font), - new_odd = wxListItemAttr:new(Text, mix(?BG_NEW, ?BG_ODD), Font), - searched = wxListItemAttr:new(Text, ?BG_SEARCHED, Font) - }. - -mix(RGB,_) -> RGB. - -%% mix({R,G,B},{MR,MG,MB}) -> -%% {trunc(R*MR/255), trunc(G*MG/255), trunc(B*MB/255)}. + {255,255,255,_} when not DarkMode -> {10,10,10}; %% Is white on Mac for some reason + Color -> Color + end, + Even = wxSystemSettings:getColour(?wxSYS_COLOUR_LISTBOX), + Odd = mix(Even, wxSystemSettings:getColour(?wxSYS_COLOUR_HIGHLIGHT), 0.8), + #colors{fg=rgb(Text), even=rgb(Even), odd=rgb(Odd)}. + +create_attrs(Window) -> + Font = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT), + #colors{fg=Text, even=Even, odd=Odd} = colors(Window), + #attrs{even = wxListItemAttr:new(Text, Even, Font), + odd = wxListItemAttr:new(Text, Odd, Font), + deleted = wxListItemAttr:new(?FG_DELETED, ?BG_DELETED, Font), + changed_even = wxListItemAttr:new(Text, mix(?BG_CHANGED, ?BG_EVEN, 0.9), Font), + changed_odd = wxListItemAttr:new(Text, mix(?BG_CHANGED, ?BG_ODD, 0.9), Font), + new_even = wxListItemAttr:new(Text, mix(?BG_NEW, ?BG_EVEN, 0.9), Font), + new_odd = wxListItemAttr:new(Text, mix(?BG_NEW, ?BG_ODD, 0.9), Font), + searched = wxListItemAttr:new(Text, ?BG_SEARCHED, Font) + }. + +rgb({R,G,B,_}) -> {R,G,B}; +rgb({_,_,_}=RGB) -> RGB. + +mix(RGB,{MR,MG,MB,_}, V) -> + mix(RGB, {MR,MG,MB}, V); +mix({R,G,B,_}, RGB, V) -> + mix({R,G,B}, RGB, V); +mix({R,G,B},{MR,MG,MB}, V) when V =< 1.0 -> + {min(255, round(R*V+MR*(1.0-V))), + min(255, round(G*V+MG*(1.0-V))), + min(255, round(B*V+MB*(1.0-V)))}. + + +is_darkmode({R,G,B,_}) -> + ((R+G+B) div 3) < 100. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/observer/src/observer_perf_wx.erl b/lib/observer/src/observer_perf_wx.erl index 79271addf2..50a6d6a915 100644 --- a/lib/observer/src/observer_perf_wx.erl +++ b/lib/observer/src/observer_perf_wx.erl @@ -55,7 +55,7 @@ -define(wxGC, wxGraphicsContext). --record(paint, {font, small, pen, pen2, pens, dot_pens, usegc = false}). +-record(paint, {font, small, fg, pen, pen2, pens, dot_pens, usegc = false}). start_link(Notebook, Parent, Config) -> wx_object:start_link(?MODULE, [Notebook, Parent, Config], []). @@ -126,7 +126,16 @@ setup_graph_drawing(Panels) -> SF = wxFont:new(Scale * (DefSize-2), DefFamily, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL), {F, SF} end, - BlackPen = wxPen:new({0,0,0}, [{width, Scale}]), + BG = wxWindow:getBackgroundColour((hd(Panels))#win.panel), + Fg = case observer_lib:is_darkmode(BG) of + false -> {0,0,0}; + true -> wxSystemSettings:getColour(?wxSYS_COLOUR_BTNTEXT) + end, + + PenColor = case observer_lib:is_darkmode(BG) of + false -> {0,0,0}; + true -> {0,0,0} + end, Pens = [wxPen:new(Col, [{width, Scale}, {style, ?wxSOLID}]) || Col <- tuple_to_list(colors())], DotPens = [wxPen:new(Col, [{width, Scale}, {style, ?wxDOT}]) @@ -134,8 +143,9 @@ setup_graph_drawing(Panels) -> #paint{usegc = UseGC, font = Font, small = SmallFont, - pen = ?wxGREY_PEN, - pen2 = BlackPen, + fg = Fg, %% Text color + pen = wxPen:new(PenColor), + pen2 = wxPen:new(PenColor, [{width, Scale}]), pens = list_to_tuple(Pens), dot_pens = list_to_tuple(DotPens) }. @@ -525,7 +535,7 @@ draw_win(DC, #win{name=Name, no_samples=Samples, geom=#{scale:={WS,HS}}, DrawBs(), ok; -draw_win(DC, #win{no_samples=Samples} = Win,Ti, #paint{small=Small}=Paint) -> +draw_win(DC, #win{no_samples=Samples} = Win,Ti, #paint{fg=Fg, small=Small}=Paint) -> %% Draw Error Msg try draw_borders(DC, Ti, Win, Paint) of {X0,_Y0,DrawBs} -> @@ -533,7 +543,7 @@ draw_win(DC, #win{no_samples=Samples} = Win,Ti, #paint{small=Small}=Paint) -> true -> "Waiting for data"; false -> "Information not available" end, - setFont(DC, Small, {0,0,0}), + setFont(DC, Small, Fg), {_,WW} = getSize(DC), drawText(DC, Text, X0 + 100, WW div 2), DrawBs(), @@ -628,7 +638,7 @@ spline_tan(Y0, Y1, Y2, Y3) -> draw_borders(DC, #ti{secs=Secs, fetch=FetchFreq}, #win{name=Type, geom=Geom, info=Info, max={_,_,Unit,_}}, - #paint{pen=Pen, pen2=Pen2, font=Font, small=Small}) -> + #paint{pen=Pen, pen2=Pen2, fg=Fg, font=Font, small=Small}) -> #{p0:={GraphX0, GraphY0}, p1:={GraphX1,GraphY1}, scale:={ScaleW0,_}, txsz:={TW,TH,SpaceW}, txt:={BottomTextY, MaxTextY}, strs:={Str1,Str2,Str3}} = Geom, @@ -640,7 +650,7 @@ draw_borders(DC, #ti{secs=Secs, fetch=FetchFreq}, GraphY50 = GraphY0 + (GraphY1 - GraphY0) / 2, GraphY75 = GraphY0 + 3*(GraphY1 - GraphY0) / 4, - setFont(DC, Small, {0,0,0}), + setFont(DC, Small, Fg), Align = fun(Str, Y) -> {StrW, _} = getTextExtent(DC, Str), drawText(DC, Str, GraphX0 - StrW - ?BW, Y) @@ -670,11 +680,11 @@ draw_borders(DC, #ti{secs=Secs, fetch=FetchFreq}, strokeLine(DC, GraphX0-3, GraphY50, GraphX1, GraphY50), strokeLine(DC, GraphX0-3, GraphY75, GraphX1, GraphY75), - setFont(DC, Font, {0,0,0}), + setFont(DC, Font, Fg), Text = fun(X,Y, Str, PenId) -> if PenId == 0 -> - setFont(DC, Font, {0,0,0}); + setFont(DC, Font, Fg); PenId > 0 -> Id = 1 + ((PenId-1) rem tuple_size(colors())), setFont(DC, Font, element(Id, colors())) diff --git a/lib/observer/src/observer_port_wx.erl b/lib/observer/src/observer_port_wx.erl index 00cf1b5fba..5cb6d9bc22 100644 --- a/lib/observer/src/observer_port_wx.erl +++ b/lib/observer/src/observer_port_wx.erl @@ -61,7 +61,8 @@ inet}). -record(opt, {sort_key=2, - sort_incr=true + sort_incr=true, + odd_bg }). -record(state, @@ -111,7 +112,10 @@ init([Notebook, Parent, Config]) -> wxListCtrl:connect(Grid, size, [{skip, true}]), wxWindow:setFocus(Grid), - {Panel, #state{grid=Grid, parent=Parent, panel=Panel, timer=Config}}. + Even = wxSystemSettings:getColour(?wxSYS_COLOUR_LISTBOX), + Odd = observer_lib:mix(Even, wxSystemSettings:getColour(?wxSYS_COLOUR_HIGHLIGHT), 0.8), + Opt = #opt{odd_bg=Odd}, + {Panel, #state{grid=Grid, parent=Parent, panel=Panel, timer=Config, opt=Opt}}. handle_event(#wx{id=?ID_REFRESH}, State = #state{node=Node, grid=Grid, opt=Opt}) -> @@ -553,7 +557,7 @@ filter_monitor_info() -> update_grid(Grid, Sel, Opt, Ports) -> wx:batch(fun() -> update_grid2(Grid, Sel, Opt, Ports) end). -update_grid2(Grid, Sel, #opt{sort_key=Sort,sort_incr=Dir}, Ports) -> +update_grid2(Grid, Sel, #opt{sort_key=Sort,sort_incr=Dir, odd_bg=BG}, Ports) -> wxListCtrl:deleteAllItems(Grid), Update = fun(#port{id = Id, @@ -563,8 +567,8 @@ update_grid2(Grid, Sel, #opt{sort_key=Sort,sort_incr=Dir}, Ports) -> controls = Ctrl}, Row) -> _Item = wxListCtrl:insertItem(Grid, Row, ""), - if (Row rem 2) =:= 0 -> - wxListCtrl:setItemBackgroundColour(Grid, Row, ?BG_EVEN); + if (Row rem 2) =:= 1 -> + wxListCtrl:setItemBackgroundColour(Grid, Row, BG); true -> ignore end, diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index 4ab4a78462..6b359f3c44 100644 --- a/lib/observer/src/observer_pro_wx.erl +++ b/lib/observer/src/observer_pro_wx.erl @@ -94,7 +94,7 @@ start_link(Notebook, Parent, Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init([Notebook, Parent, Config]) -> - Attrs = observer_lib:create_attrs(), + Attrs = observer_lib:create_attrs(Notebook), Self = self(), Acc = maps:get(acc, Config, false), Holder = spawn_link(fun() -> init_table_holder(Self, Acc, Attrs) end), diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl index bd5fed0951..637a090a15 100644 --- a/lib/observer/src/observer_procinfo.erl +++ b/lib/observer/src/observer_procinfo.erl @@ -214,12 +214,14 @@ init_process_page(Panel, Pid) -> init_message_page(Parent, Pid, Table) -> Win = observer_lib:html_window(Parent), + Cs = observer_lib:colors(Parent), Update = fun() -> case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, messages]) of {messages, Messages} -> - Html = observer_html_lib:expandable_term("Message Queue", Messages, Table), + Html = observer_html_lib:expandable_term("Message Queue", Messages, + Table, Cs), wxHtmlWindow:setPage(Win, Html); _ -> throw(process_undefined) @@ -230,11 +232,12 @@ init_message_page(Parent, Pid, Table) -> init_dict_page(Parent, Pid, Table) -> Win = observer_lib:html_window(Parent), + Cs = observer_lib:colors(Parent), Update = fun() -> case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, dictionary]) of {dictionary,Dict} -> - Html = observer_html_lib:expandable_term("Dictionary", Dict, Table), + Html = observer_html_lib:expandable_term("Dictionary", Dict, Table, Cs), wxHtmlWindow:setPage(Win, Html); _ -> throw(process_undefined) @@ -254,6 +257,8 @@ init_stack_page(Parent, Pid) -> wxListCtrl:insertColumn(LCtrl, 1, Li), wxListCtrl:setColumnWidth(LCtrl, 1, Scale * 300), wxListItem:destroy(Li), + Even = wxSystemSettings:getColour(?wxSYS_COLOUR_LISTBOX), + Odd = observer_lib:mix(Even, wxSystemSettings:getColour(?wxSYS_COLOUR_HIGHLIGHT), 0.8), Update = fun() -> case observer_wx:try_rpc(node(Pid), erlang, process_info, [Pid, current_stacktrace]) @@ -262,8 +267,8 @@ init_stack_page(Parent, Pid) -> wxListCtrl:deleteAllItems(LCtrl), wx:foldl(fun({M, F, A, Info}, Row) -> _Item = wxListCtrl:insertItem(LCtrl, Row, ""), - ?EVEN(Row) andalso - wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN), + ?EVEN(Row) orelse + wxListCtrl:setItemBackgroundColour(LCtrl, Row, Odd), wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str({M,F,A})), FileLine = case Info of [{file,File},{line,Line}] -> @@ -288,9 +293,10 @@ init_stack_page(Parent, Pid) -> init_state_page(Parent, Pid, Table) -> Win = observer_lib:html_window(Parent), + Cs = observer_lib:colors(Parent), Update = fun() -> StateInfo = fetch_state_info(Pid), - Html = observer_html_lib:expandable_term("ProcState", StateInfo, Table), + Html = observer_html_lib:expandable_term("ProcState", StateInfo, Table, Cs), wxHtmlWindow:setPage(Win, Html) end, Update(), @@ -341,6 +347,7 @@ fetch_state_info2(Pid, M) -> init_log_page(Parent, Pid, Table) -> Win = observer_lib:html_window(Parent), + Cs = observer_lib:colors(Parent), Update = fun() -> Fd = spawn_link(fun() -> io_server() end), rpc:call(node(Pid), rb, rescan, [[{start_log, Fd}]]), @@ -353,7 +360,7 @@ init_log_page(Parent, Pid, Table) -> NbBlanks = length(Pref) - 1, Re = "(<" ++ Pref ++ "\.[^>]{1,}>)[ ]{"++ integer_to_list(NbBlanks) ++ "}", Look = re:replace(ExpPid, Re, "\\1", [global, {return, list}]), - Html = observer_html_lib:expandable_term("SaslLog", Look, Table), + Html = observer_html_lib:expandable_term("SaslLog", Look, Table, Cs), wxHtmlWindow:setPage(Win, Html) end, Update(), diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl index 7bd67a0f0b..32d75f77d4 100644 --- a/lib/observer/src/observer_tv_table.erl +++ b/lib/observer/src/observer_tv_table.erl @@ -116,8 +116,8 @@ init([Parent, Opts]) -> TabId = table_id(Table), ColumnNames = column_names(Node, Source, TabId), KeyPos = key_pos(Node, Source, TabId), - - Attrs = observer_lib:create_attrs(), + Panel = wxPanel:new(Frame), + Attrs = observer_lib:create_attrs(Panel), Self = self(), Holder = spawn_link(fun() -> @@ -125,7 +125,6 @@ init([Parent, Opts]) -> length(ColumnNames), Node, Attrs) end), - Panel = wxPanel:new(Frame), Sizer = wxBoxSizer:new(?wxVERTICAL), Style = ?wxLC_REPORT bor ?wxLC_VIRTUAL bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, Grid = wxListCtrl:new(Panel, [{style, Style}, diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl index 9743a6ed42..d622b1423b 100644 --- a/lib/observer/src/observer_tv_wx.erl +++ b/lib/observer/src/observer_tv_wx.erl @@ -71,7 +71,7 @@ init([Notebook, Parent, Config]) -> Style = ?wxLC_REPORT bor ?wxLC_VIRTUAL bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, Self = self(), - Attrs = observer_lib:create_attrs(), + Attrs = observer_lib:create_attrs(Panel), Holder = spawn_link(fun() -> init_table_holder(Self, Attrs) end), CBs = [{onGetItemText, fun(_, Item,Col) -> get_row(Holder, Item, Col) end}, {onGetItemAttr, fun(_, Item) -> get_attr(Holder, Item) end}], diff --git a/lib/observer/test/crashdump_helper.erl b/lib/observer/test/crashdump_helper.erl index 84ed99afa5..10d88c994a 100644 --- a/lib/observer/test/crashdump_helper.erl +++ b/lib/observer/test/crashdump_helper.erl @@ -48,7 +48,7 @@ n1_proc(Creator,_N2,Pid2,Port2,_L) -> Ref = make_ref(), Pid = self(), Bin = list_to_binary(lists:seq(1, 255)), - <<_:2,SubBin:17/binary,_/bits>> = Bin, + <<_:2,SubBin:65/binary,_/bits>> = Bin, register(named_port,Port), @@ -102,7 +102,7 @@ remote_proc(P1,Creator) -> end). create_binaries() -> - Sizes = lists:seq(60, 70) ++ lists:seq(120, 140), + Sizes = lists:seq(100, 120) ++ lists:seq(200, 240), [begin <<H:16/unit:8>> = erlang:md5(<<Size:32>>), Data = ((H bsl (8*150)) div (H+7919)), @@ -113,7 +113,7 @@ create_sub_binaries(Bins) -> [create_sub_binary(Bin, Start, LenSub) || Bin <- Bins, Start <- [0,1,2,3,4,5,10,22], - LenSub <- [0,1,2,3,4,6,9]]. + LenSub <- [0,1,2,3,4,6,9,65]]. create_sub_binary(Bin, Start, LenSub) -> Len = byte_size(Bin) - LenSub - Start, diff --git a/lib/os_mon/c_src/cpu_sup.c b/lib/os_mon/c_src/cpu_sup.c index c96a5c9f7c..98a2526aab 100644 --- a/lib/os_mon/c_src/cpu_sup.c +++ b/lib/os_mon/c_src/cpu_sup.c @@ -359,7 +359,7 @@ static cpu_t *read_procstat(FILE *fp, cpu_t *cpu) { memset(cpu, 0, sizeof(cpu_t)); return cpu; } - sscanf(buffer, "cpu%u %Lu %Lu %Lu %Lu %Lu %Lu %Lu %Lu", + sscanf(buffer, "cpu%u %llu %llu %llu %llu %llu %llu %llu %llu", &(cpu->id), &(cpu->user), &(cpu->nice_user), diff --git a/lib/os_mon/src/disksup.erl b/lib/os_mon/src/disksup.erl index 5118d807e1..4253067c90 100644 --- a/lib/os_mon/src/disksup.erl +++ b/lib/os_mon/src/disksup.erl @@ -264,7 +264,7 @@ check_disk_space({unix, irix}, Port, Threshold) -> Result = my_cmd("/usr/sbin/df -lk",Port), check_disks_irix(skip_to_eol(Result), Threshold); check_disk_space({unix, linux}, Port, Threshold) -> - Result = my_cmd("/bin/df -lk", Port), + Result = my_cmd("/bin/df -lk -x squashfs", Port), check_disks_solaris(skip_to_eol(Result), Threshold); check_disk_space({unix, posix}, Port, Threshold) -> Result = my_cmd("df -k -P", Port), diff --git a/lib/public_key/asn1/CMSAesRsaesOaep.asn1 b/lib/public_key/asn1/CMSAesRsaesOaep.asn1 new file mode 100644 index 0000000000..ca8c7b7f92 --- /dev/null +++ b/lib/public_key/asn1/CMSAesRsaesOaep.asn1 @@ -0,0 +1,39 @@ +CMSAesRsaesOaep {iso(1) member-body(2) us(840) rsadsi(113549) + pkcs(1) pkcs-9(9) smime(16) modules(0) id-mod-cms-aes(19) } + + +DEFINITIONS IMPLICIT TAGS ::= +BEGIN + +-- EXPORTS ALL -- +IMPORTS + -- PKIX + AlgorithmIdentifier + FROM PKIX1Explicit88 {iso(1) identified-organization(3) dod(6) + internet(1) security(5) mechanisms(5) pkix(7) id-mod(0) + id-pkix1-explicit(18)}; + +-- AES information object identifiers -- + +aes OBJECT IDENTIFIER ::= { joint-iso-itu-t(2) country(16) us(840) + organization(1) gov(101) csor(3) nistAlgorithms(4) 1 } + +-- AES using CBC-chaining mode for key sizes of 128, 192, 256 + +id-aes128-CBC OBJECT IDENTIFIER ::= { aes 2 } +id-aes192-CBC OBJECT IDENTIFIER ::= { aes 22 } +id-aes256-CBC OBJECT IDENTIFIER ::= { aes 42 } + +-- AES-IV is a the parameter for all the above object identifiers. + +AES-IV ::= OCTET STRING (SIZE(16)) + + +-- AES Key Wrap Algorithm Identifiers - Parameter is absent + +id-aes128-wrap OBJECT IDENTIFIER ::= { aes 5 } +id-aes192-wrap OBJECT IDENTIFIER ::= { aes 25 } +id-aes256-wrap OBJECT IDENTIFIER ::= { aes 45 } + + +END diff --git a/lib/public_key/asn1/Makefile b/lib/public_key/asn1/Makefile index a920ea87ea..10952106c6 100644 --- a/lib/public_key/asn1/Makefile +++ b/lib/public_key/asn1/Makefile @@ -42,7 +42,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/public_key-$(VSN) ASN_TOP = OTP-PUB-KEY PKCS-FRAME ASN_MODULES = PKIX1Explicit88 PKIX1Implicit88 PKIX1Algorithms88 \ PKIXAttributeCertificate PKCS-1 PKCS-3 PKCS-7 PKCS-8 PKCS-10 PKCS5v2-0 OTP-PKIX \ - InformationFramework RFC5639 + InformationFramework RFC5639 CMSAesRsaesOaep ASN_ASNS = $(ASN_MODULES:%=%.asn1) ASN_ERLS = $(ASN_TOP:%=%.erl) ASN_HRLS = $(ASN_TOP:%=%.hrl) diff --git a/lib/public_key/asn1/OTP-PUB-KEY.set.asn b/lib/public_key/asn1/OTP-PUB-KEY.set.asn index b3f3ccdb77..7ab1684ff3 100644 --- a/lib/public_key/asn1/OTP-PUB-KEY.set.asn +++ b/lib/public_key/asn1/OTP-PUB-KEY.set.asn @@ -10,3 +10,5 @@ ECPrivateKey.asn1 PKCS-7.asn1 PKCS-10.asn1 RFC5639.asn1 +CMSAesRsaesOaep.asn1 + diff --git a/lib/public_key/doc/src/notes.xml b/lib/public_key/doc/src/notes.xml index d13c9a520a..57d9898661 100644 --- a/lib/public_key/doc/src/notes.xml +++ b/lib/public_key/doc/src/notes.xml @@ -87,6 +87,21 @@ </section> +<section><title>Public_Key 1.6.6.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Support Pasword based encryption with AES</p> + <p> + Own Id: OTP-15870 Aux Id: ERL-952 </p> + </item> + </list> + </section> + +</section> + <section><title>Public_Key 1.6.6</title> <section><title>Improvements and New Features</title> @@ -1247,4 +1262,3 @@ </chapter> - diff --git a/lib/public_key/doc/src/public_key_app.xml b/lib/public_key/doc/src/public_key_app.xml index 923a9f1dfb..5f2c50711a 100644 --- a/lib/public_key/doc/src/public_key_app.xml +++ b/lib/public_key/doc/src/public_key_app.xml @@ -51,6 +51,9 @@ Diffie-Hellman Key Agreement Standard </item> <item>Supports <url href="http://www.ietf.org/rfc/rfc2898.txt"> PKCS-5</url> - Password-Based Cryptography Standard </item> + <item>Supports <url href="http://www.ietf.org/rfc/fc3565.txt"> AES </url> - + Use of the Advanced Encryption Standard (AES) Algorithm in Cryptographic Message Syntax (CMS) + </item> <item>Supports <url href="http://www.ietf.org/rfc/rfc5208.txt"> PKCS-8</url> - Private-Key Information Syntax Standard</item> <item>Supports <url href="http://www.ietf.org/rfc/rfc5967.txt"> PKCS-10</url> - diff --git a/lib/public_key/src/pubkey_pbe.erl b/lib/public_key/src/pubkey_pbe.erl index e6bcedd1b1..6003bf21d0 100644 --- a/lib/public_key/src/pubkey_pbe.erl +++ b/lib/public_key/src/pubkey_pbe.erl @@ -26,9 +26,7 @@ -export([encode/4, decode/4, decrypt_parameters/1, encrypt_parameters/1]). -export([pbdkdf1/4, pbdkdf2/7]). --define(DEFAULT_SHA_MAC_KEYLEN, 20). -define(ASN1_OCTET_STR_TAG, 4). --define(IV_LEN, 8). %%==================================================================== %% Internal application API @@ -41,14 +39,23 @@ %%-------------------------------------------------------------------- encode(Data, Password, "DES-CBC" = Cipher, KeyDevParams) -> {Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams), - crypto:block_encrypt(des_cbc, Key, IV, pbe_pad(Data, KeyDevParams)); + crypto:block_encrypt(des_cbc, Key, IV, pbe_pad(Data, block_size(des_cbc))); encode(Data, Password, "DES-EDE3-CBC" = Cipher, KeyDevParams) -> {Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams), <<Key1:8/binary, Key2:8/binary, Key3:8/binary>> = Key, - crypto:block_encrypt(des3_cbc, [Key1, Key2, Key3], IV, pbe_pad(Data)); + crypto:block_encrypt(des3_cbc, [Key1, Key2, Key3], IV, pbe_pad(Data, block_size(des_3ede))); encode(Data, Password, "RC2-CBC" = Cipher, KeyDevParams) -> {Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams), - crypto:block_encrypt(rc2_cbc, Key, IV, pbe_pad(Data, KeyDevParams)). + crypto:block_encrypt(rc2_cbc, Key, IV, pbe_pad(Data, block_size(rc2_cbc))); +encode(Data, Password, "AES-128-CBC" = Cipher, KeyDevParams) -> + {Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams), + crypto:block_encrypt(aes_128_cbc, Key, IV, pbe_pad(Data, block_size(aes_128_cbc))); +encode(Data, Password, "AES-192-CBC" = Cipher, KeyDevParams) -> + {Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams), + crypto:block_encrypt(aes_192_cbc, Key, IV, pbe_pad(Data, block_size(aes_192_cbc))); +encode(Data, Password, "AES-256-CBC"= Cipher, KeyDevParams) -> + {Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams), + crypto:block_encrypt(aes_256_cbc, Key, IV, pbe_pad(Data, block_size(aes_256_cbc))). %%-------------------------------------------------------------------- -spec decode(binary(), string(), string(), term()) -> binary(). @@ -67,14 +74,16 @@ decode(Data, Password,"RC2-CBC"= Cipher, KeyDevParams) -> crypto:block_decrypt(rc2_cbc, Key, IV, Data); decode(Data, Password,"AES-128-CBC"= Cipher, KeyDevParams) -> {Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams), - crypto:block_decrypt(aes_cbc128, Key, IV, Data); -decode(Data, Password,"AES-256-CBC"= Cipher, KeyDevParams) -> + crypto:block_decrypt(aes_128_cbc, Key, IV, Data); +decode(Data, Password,"AES-192-CBC"= Cipher, KeyDevParams) -> + {Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams), + crypto:block_decrypt(aes_192_cbc, Key, IV, Data); +decode(Data, Password,"AES-256-CBC"= Cipher, KeyDevParams) -> {Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams), - crypto:block_decrypt(aes_cbc256, Key, IV, Data). - + crypto:block_decrypt(aes_256_cbc, Key, IV, Data). %%-------------------------------------------------------------------- --spec pbdkdf1(string(), iodata(), integer(), atom()) -> binary(). +-spec pbdkdf1(iodata(), iodata(), integer(), atom()) -> binary(). %% %% Description: Implements password based decryption key derive function 1. %% Exported mainly for testing purposes. @@ -86,7 +95,7 @@ pbdkdf1(Password, Salt, Count, Hash) -> do_pbdkdf1(Result, Count-1, Result, Hash). %%-------------------------------------------------------------------- --spec pbdkdf2(string(), iodata(), integer(), integer(), fun(), atom(), integer()) +-spec pbdkdf2(iodata(), iodata(), integer(), integer(), fun(), atom(), integer()) -> binary(). %% %% Description: Implements password based decryption key derive function 2. @@ -150,17 +159,15 @@ do_pbdkdf1(Prev, Count, Acc, Hash) -> Result = crypto:hash(Hash, Prev), do_pbdkdf1(Result, Count-1 , <<Result/binary, Acc/binary>>, Hash). -iv(#'PBES2-params_encryptionScheme'{algorithm = Algo, - parameters = ASN1IV}) - when (Algo == ?'desCBC') or - (Algo == ?'des-EDE3-CBC') -> - <<?ASN1_OCTET_STR_TAG, ?IV_LEN, IV:?IV_LEN/binary>> = decode_handle_open_type_wrapper(ASN1IV), - IV; iv(#'PBES2-params_encryptionScheme'{algorithm = ?'rc2CBC', parameters = ASN1IV}) -> {ok, #'RC2-CBC-Parameter'{iv = IV}} = 'PKCS-FRAME':decode('RC2-CBC-Parameter', decode_handle_open_type_wrapper(ASN1IV)), - iolist_to_binary(IV). + iolist_to_binary(IV); +iv(#'PBES2-params_encryptionScheme'{algorithm = _Algo, + parameters = ASN1IV}) -> + <<?ASN1_OCTET_STR_TAG, Len:8/unsigned-big-integer, IV:Len/binary>> = decode_handle_open_type_wrapper(ASN1IV), + IV. blocks(1, N, Index, Password, Salt, Count, Prf, PrfHash, PrfLen, Acc) -> <<XorSum:N/binary, _/binary>> = xor_sum(Password, Salt, Count, Index, Prf, PrfHash, PrfLen), @@ -217,17 +224,9 @@ pbe1_oid("RC2-CBC", md5) -> pbe1_oid("DES-CBC", md5) -> ?'pbeWithMD5AndDES-CBC'. -pbe_pad(Data, {#'PBEParameter'{}, _}) -> - pbe_pad(Data); -pbe_pad(Data, #'PBES2-params'{}) -> - pbe_pad(Data); -pbe_pad(Data, _) -> -pbe_pad(Data).%% Data. - - -pbe_pad(Data) -> - N = 8 - (erlang:byte_size(Data) rem 8), - Pad = list_to_binary(lists:duplicate(N, N)), +pbe_pad(Data, BlockSize) -> + N = BlockSize - (erlang:byte_size(Data) rem BlockSize), + Pad = binary:copy(<<N>>, N), <<Data/binary, Pad/binary>>. key_derivation_params(#'PBES2-params'{keyDerivationFunc = KeyDerivationFunc, @@ -249,11 +248,27 @@ key_derivation_params(#'PBES2-params'{keyDerivationFunc = KeyDerivationFunc, pseudo_random_function(#'PBKDF2-params_prf'{algorithm = {_,_, _,'id-hmacWithSHA1'}}) -> {fun crypto:hmac/4, sha, pseudo_output_length(?'id-hmacWithSHA1')}; -pseudo_random_function(#'PBKDF2-params_prf'{algorithm = ?'id-hmacWithSHA1'}) -> - {fun crypto:hmac/4, sha, pseudo_output_length(?'id-hmacWithSHA1')}. +pseudo_random_function(#'PBKDF2-params_prf'{algorithm = ?'id-hmacWithSHA1' = Algo}) -> + {fun crypto:hmac/4, sha, pseudo_output_length(Algo)}; +pseudo_random_function(#'PBKDF2-params_prf'{algorithm = ?'id-hmacWithSHA224'= Algo}) -> + {fun crypto:hmac/4, sha224, pseudo_output_length(Algo)}; +pseudo_random_function(#'PBKDF2-params_prf'{algorithm = ?'id-hmacWithSHA256' = Algo}) -> + {fun crypto:hmac/4, sha256, pseudo_output_length(Algo)}; +pseudo_random_function(#'PBKDF2-params_prf'{algorithm = ?'id-hmacWithSHA384' = Algo}) -> + {fun crypto:hmac/4, sha384, pseudo_output_length(Algo)}; +pseudo_random_function(#'PBKDF2-params_prf'{algorithm = ?'id-hmacWithSHA512' = Algo}) -> + {fun crypto:hmac/4, sha512, pseudo_output_length(Algo)}. pseudo_output_length(?'id-hmacWithSHA1') -> - ?DEFAULT_SHA_MAC_KEYLEN. + 20; %%160/8 +pseudo_output_length(?'id-hmacWithSHA224') -> + 28; %%%224/8 +pseudo_output_length(?'id-hmacWithSHA256') -> + 32; %%256/8 +pseudo_output_length(?'id-hmacWithSHA384') -> + 48; %%384/8 +pseudo_output_length(?'id-hmacWithSHA512') -> + 64. %%512/8 derived_key_length(_, Len) when is_integer(Len) -> Len; @@ -266,11 +281,33 @@ derived_key_length(Cipher,_) when (Cipher == ?'rc2CBC') or derived_key_length(Cipher,_) when (Cipher == ?'des-EDE3-CBC') or (Cipher == "DES-EDE3-CBC") -> 24; -derived_key_length(Cipher,_) when (Cipher == "AES-128-CBC") -> + +derived_key_length(Cipher,_) when (Cipher == "AES-128-CBC"); + (Cipher == ?'id-aes128-CBC') -> 16; -derived_key_length(Cipher,_) when (Cipher == "AES-256-CBC") -> +derived_key_length(Cipher,_) when (Cipher == "AES-192-CBC"); + (Cipher == ?'id-aes192-CBC') -> + 24; + +derived_key_length(Cipher,_) when (Cipher == "AES-256-CBC"); + (Cipher == ?'id-aes256-CBC') -> 32. +block_size(Cipher) when Cipher == rc2_cbc; + Cipher == des_cbc; + Cipher == des_3ede -> + 8; +block_size(Cipher) when Cipher == aes_128_cbc; + Cipher == aes_192_cbc; + Cipher == aes_256_cbc -> + 16. + +cipher(#'PBES2-params_encryptionScheme'{algorithm = ?'id-aes128-CBC'}) -> + "AES-128-CBC"; +cipher(#'PBES2-params_encryptionScheme'{algorithm = ?'id-aes192-CBC'}) -> + "AES-192-CBC"; +cipher(#'PBES2-params_encryptionScheme'{algorithm = ?'id-aes256-CBC'}) -> + "AES-256-CBC"; cipher(#'PBES2-params_encryptionScheme'{algorithm = ?'desCBC'}) -> "DES-CBC"; cipher(#'PBES2-params_encryptionScheme'{algorithm = ?'des-EDE3-CBC'}) -> diff --git a/lib/public_key/test/pbe_SUITE.erl b/lib/public_key/test/pbe_SUITE.erl index 1136267411..61db282dfa 100644 --- a/lib/public_key/test/pbe_SUITE.erl +++ b/lib/public_key/test/pbe_SUITE.erl @@ -206,7 +206,10 @@ pbes2() -> [{doc,"Tests encode/decode EncryptedPrivateKeyInfo encrypted with different ciphers using PBES2"}]. pbes2(Config) when is_list(Config) -> decode_encode_key_file("pbes2_des_cbc_enc_key.pem", "password", "DES-CBC", Config), - decode_encode_key_file("pbes2_des_ede3_cbc_enc_key.pem", "password", "DES-EDE3-CBC", Config), + decode_encode_key_file("pbes2_des_ede3_cbc_enc_key.pem", "password", "DES-EDE3-CBC", Config), + decode_encode_key_file("pbes2_aes_128_enc_key.pem", "password", "AES-128-CBC", Config), + decode_encode_key_file("pbes2_aes_192_enc_key.pem", "password", "AES-192-CBC", Config), + decode_encode_key_file("pbes2_aes_256_enc_key.pem", "password", "AES-256-CBC", Config), case lists:member(rc2_cbc, proplists:get_value(ciphers, crypto:supports())) of true -> decode_encode_key_file("pbes2_rc2_cbc_enc_key.pem", "password", "RC2-CBC", Config); @@ -239,7 +242,6 @@ decode_encode_key_file(File, Password, Cipher, Config) -> {ok, PemKey} = file:read_file(filename:join(Datadir, File)), PemEntry = public_key:pem_decode(PemKey), - ct:pal("Pem entry: ~p" , [PemEntry]), [{Asn1Type, _, {Cipher,_} = CipherInfo} = PubEntry] = PemEntry, #'RSAPrivateKey'{} = KeyInfo = public_key:pem_entry_decode(PubEntry, Password), PemKey1 = public_key:pem_encode([public_key:pem_entry_encode(Asn1Type, KeyInfo, {CipherInfo, Password})]), diff --git a/lib/public_key/test/pbe_SUITE_data/pbes2_aes_128_enc_key.pem b/lib/public_key/test/pbe_SUITE_data/pbes2_aes_128_enc_key.pem new file mode 100644 index 0000000000..5702119ad6 --- /dev/null +++ b/lib/public_key/test/pbe_SUITE_data/pbes2_aes_128_enc_key.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIWrPgmqJqNpICAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBA/bbIMYqQMUDxMk9ifPR7ABIIE +0Drfqke1/ccFxk786hTh36yjVo48Xx7B3Scb92KtmyQpNaR6GbR+jhP9cxIcvmGN +YroCB896VJSIx8PraqGgIJ1hblZXyfLanB0mUnZvaaQ4xp3UJT53a0yOm5Lfd+fB +0TyaoEzca2jA5EVVh3yH6gzNsvQJRw6cQP5CAptLjiUv2jrwVGnO8x8X4egJDLZS +Sb8B5AW8h1sGsyKEEFto6gpBjVqnVn5veMoI/Cfs9qDr071+dhbps/m6pseKKp0z +8qeFM7+9Y4npD1VYg2gqOFi19QAI3gwq6tC8grOzRA8dPFUgpV9eMToVsI2OFQc1 +xnFZEV7NZVymh5HjKM1jwFy6es+5TFoMtRu6vDxKS6Y13lIlZ4oQSh8aXtG5Ylt2 +CqsKNHyDbZUpvKe/k19TBmVXQBCYFuN733jI9/4JBtpygnxwt1aXCvq/PFFGsTS4 +p1JOQvr/jaD7b4JO6IMXH1kSVxiMXKXNG7wPUNr6OWJvc7OqdclsZa7ibEx4L52x +DuFmsxQo4a3iibhbcjr436OmR5Uw2UAstB5qxWfMhkt+e7rRhCOh/3O7SAYEpt+f +Zr2VFXdGme4kR6uMCzgGiSh0qCseQXpJUZVufn/Go9r+601OJTJIQ9a2VoqlMR8o +Dd14D0gBXXaZkY60Mh8iXR/MjKDuv0KBUyBzfcpk3fLmv0PhGSkbn6j+q1jZbogm +EhI0AL5s2EoofuBdvgdusBhCrrwCMonprqR7BuaKPD0GEw5utnT5ovcUg/sjMJox +10100QwAzQScU4iG/xic/TsN+ZMumhUcYs003MsZkRLvCEFxZurEMx7819CqfhIc +NGd7ETTBSwoNf5pXRTHaTbW6pPiIeWunLUUVsRcNoBtL/cXmg+mu1zdsD7nD51mJ +vG9A7LPW7XVl2Jv2NgQoKkHYO7cVozmcz6AE2z1q+XN4LGto8JEZktb6E7UIyXXg +Ls4Tv0sn5TLgtaJ31w4+9iybNiGoVYOc4h0s5DoNR4ivcZ6n/Qnf8PTrNzejEJY6 +R/UnDbc24u0palGc1kei99d0BYodnq4OlAj7M7ML0GncftInhgA0Dp81YG5PujMa +irhvwtnD5Xysfh1YrroAEN7Qxc8+2JlpgNSFlFFkMgfibc6jvTX6/C6MaFz8hiOq +W43ZBEzjMIs23ZrJKOJGsuTdHSob+VbvqIMgS2PeGb/6g3/GjdipCbynNhX3zUOM +3j/lpZOiAwE/Bftr5FOSfTFpnyorIIeyWgROEZTTL4eSYvnBjzf+tUdXY7ltxJie +q0rpQ42X7+B4gTo8Qj/xC7LXSCldERK57cCwwITvjcHwxPyOiJ9BMI1HlRQ/Fo3C +lPYIst1xjJ67qrTm6mWkor2hUOZcg4MOOzXWuijWRGJ/Wz0H+GKWtoE2X536D6sy +a4Nwwj09oFY4Fph/SUNwy0MLpTSzikpUx6mxjbs3Odvo6tWWVcicp/dCWYCqLpGU +3axEb/qlsaRNtKJg9O3Fq7hh1BTyLNGB2ET5wSKtlSD0bDeF15bBvkHB3z2/lDls +YQ2hEHMjeSEZZyGTPqEHwtBuUwiWBBXwOIhT8nfYXbHWR0CLBLth2+E/JCaO9hD2 +V277arqNFa8nugZMwS+ragi6vbgIX4BiS/rnfYXgqaxD +-----END ENCRYPTED PRIVATE KEY----- diff --git a/lib/public_key/test/pbe_SUITE_data/pbes2_aes_192_enc_key.pem b/lib/public_key/test/pbe_SUITE_data/pbes2_aes_192_enc_key.pem new file mode 100644 index 0000000000..ee82e9f667 --- /dev/null +++ b/lib/public_key/test/pbe_SUITE_data/pbes2_aes_192_enc_key.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIcqBCM7v+ZlkCAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEWBBD93r4IWBhvry+cdfwIDOKeBIIE +0DXM8S70sMsUmwxRZQtKwGfYddEWIc9lrEdsgEEuonF6NrseRq7QdXnBSPwq5f0O +ofMZ/0OCun3Qg1ls1EdsyKdijSOq27ZhHCnmWi1Rw1ApJIAq5i/jY8U17+lUakvG +VtcsuRzlKmFxbBW44kLK7vK6xiA76HPx0I4ZXcdywR0pbLT1ubbhbQ9djLnBiYkT +odszGTyxNceEse1Hu/RhFK17tnwov0fdioKY2i9F7qfq8lYLPrusEKTY7tOVjFOh +bXeCry1BL0KTt65JVGR9xQCI0qokEU0QrCgD6skq7Vx2C/Ho1sW6h8FBFVIm6ozO +bEUtVk3Xgs5yieetha1GxJAang1VxAPemnXfOmVapoSgSv1BQyDdnk3067Sfkh64 +A5yf44BUjvJsSd/ViCVmCryoXU7KOMAdFkyRSiDDLQus6bZGEhc6f+VEikG+TZ2L +xxY4OucE2Bz67S6ycyOUpXKo0+FW0juE6NTJdlYSXWOvfciZKA83h6yAej6MfUEu +4orIvnCTVO7i3+hHybnSgftj42jrqqZzeXll8rkGHg4syrKRVaDD6qfJjgAHBJkJ +pZT4zZwuJ1puWfBykI25S4mKUnk0erq4N5jpGqdm7U14fWBWCjZN85jY4WgZZOJx +kBNO2NbmZKzZEzRGyMJ563z4l7MNfzZBHv+FeBNkX146J4ZhMbT8IXPGV9peNWqu +mY2B9RhN4hlDrd3Hfz5uiiF3UGrFkDcsPRBHWGqQ20YpuOQNno7iL8N0FWauERw1 +dvxAGVwFfUznR3wc/eyGcnRhqQhlYPspukh0IVIyEbre3yVFSG/41GQYQfg08XYd +LYiiDUu1i515/GeDvYN5VcnZ4nMhPgqfxW4rEUZjI86p++bqwqGy8eOCivkzGV3A +IFWQwlvKKzU7tSdi3uHUq5v7xQsJrALdf67JVjCCGfUZa17O41vmm58L/vKhhL2Y +mLz/H004DPsB+CtWoLwqZ8Jmb1EHwqNbna3tGHn3n63j2cV7gykZFa/zXeuBbbJ/ +t4ZIojIEzwAVKA9Xzcl3wyGCRr62WJPEcOqe4kBYREuKd22juPEm9RQgciIIj0tP +eJVpD0QarGGzERsaq7pheAiWisO+Q4cLjF8Mb3/r89abnd4AQk6meabFJIE2dXWp +LZy3I6FkNQ7L7LxNOILhnaWzWGdOBVwHeAAxfbLOzM22ewj7oUwBCRpsBJ8zl2PL +VhUjX6N26YoiR9gE1RBaVrwRkYLmkyGvrowCDoZVPxvJqbfIESQE42zGB9DbEPNp +WXCnzAg5cIjNC31We274yLE7dpNPVRXPJCRhtp7noorWVzDdKB+dFvg08bIir6Vj +1gxy8DvuZE1Gq9vqx38V7Cy2MrSpsgapw5mli4n5cMafE7Ty3j5pBJFF2f3jUn6B +7MjCrKp1d8v6MEy18J/Ugu1Lytb92LMcNtWBKmqyCSxekrUB9/FC2hWqOpdwRI6q +QMWkwshjyEhmlr2PAkBPM4uVzUFc9lBw1GzOUChkr9jiINdbsUSRJrwZ32Nc3gRY +yKzWbEELPSgRcXwXgH3QqZukvmk2tBMTIxilXqKTLmd7t/AEnIhkbqC0pfnyChyU +YlFkme0RpAXpgbDJgv+Vk+1/1s6gyaNSzT4s2Q340WIO +-----END ENCRYPTED PRIVATE KEY----- diff --git a/lib/public_key/test/pbe_SUITE_data/pbes2_aes_256_enc_key.pem b/lib/public_key/test/pbe_SUITE_data/pbes2_aes_256_enc_key.pem new file mode 100644 index 0000000000..050337aead --- /dev/null +++ b/lib/public_key/test/pbe_SUITE_data/pbes2_aes_256_enc_key.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI4MxgpDiHxQcCAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBA2g/L8XmlK2axDkeYJCltnBIIE +0C3+NQ93DzEK/9qicy1sj0Vag1M7AeJjTGGpatETCxM+eHjk4kNNeDeMV5+EmCSu +Db4P48uvHOBGGCcqdjnQovfQsAh81GWxgF3yqpd4OKn2RubMLO4/Qu+zGtt/XRKz +T0pyHHBu6hyPSOhad2SIjKWuaHepwxGYaejLP83sy6yhm0sEmyBUn4nGSTOROcqR +wd7EbwU2PYUcrRGGxtChU7MUNt48wBO50Xmri1ssPPtZV6MHio4IoIz4hqzCjvAc +VE1BqAvNIJ7icpdnL8Jqq0lfwEmGjFCkAjgov5fNW9I1b44jE2Tv5LM2urMH8InQ +9qNjTHozYQhHAk9nX4cmMgHsIhkOd7Z2M+nz8Hd1tj9DmBNOr5XbfyctgVntaMB4 +GGnThuNlX8d5giOKOcaNPMpLU1jtfDcb73mEhwCYcdo1PM0rjrYZ7qetjXJW/oHs +Nl/hIZIRpMuCRVuXHml4G+ziKbMnXUN8sbtvgkQatYFHFQOhAqZeyzWp8SlDcfqb +Zt0LlZVJEhKUYzZgKoe7SmR1rXTTCfYeB75PddyYwVgf/IkT6HJ/y1apGOP6/UJ8 +7UV6zssQA35gMsYDT36sH2hAQvA/cOFxSxrip0gm0xXOeFF0gbyZWbFqk0aULaeF +rbBoMe28akxdE4eD06b+TP2NguUGP72l3TPOlG4PQVScweMw9L3oPXOVj4Vbbd0y +DenNvRHlWIwOh/y7ADTHSWq9CE45QDBvFaTcn43JQWD8xCmhAhI/9H+fhAQUhABm +P5QoJLE2IGo8A+Gi7rfgYQb3fCgqcn8azsRJzozhE+oXxMvxEESejYTtm26FNmLg +ONTWysF9BiaKHt2IXwRX97691wZqv5wJEaxeeJxfVQ6MlAHoEDXe49VxGN4zFXuq +Yb71JdQDgM94jwc/PoUwFH2ALSkIciiKwU0xfFpptycl4qWpy9m7QTIKw0DjgCfg +MuySPRGM5jn3yVg72ux2Qf9MKNEybWjZ+Se9MJ1IZmZK5eOo6L2JsFCc0nRn908E +vn4gAgUfMxyCZ1ygXfxINVAixR+6KPHsz1QTIxTZkrlnXRsuEu1ZfBSHzmXESvJo +3I9PkP/Iekg1FBpB5xxd7mXwCj17EWqYXWsLnfd8SblMjRYd64q7hfx0oU/MJ1wi +KadkGcyAGVRyleJRBR0LleYj/2sDihrRQY4zu5UtzSMFMH0XWjSWk5+ZQb+z3iDc +Ud4GHcHiuTMH+i03ApZGWLN9v93za/15fsnZogstgJkaHxizTz5JuCkRf15xd8+O +EH77Tsfizjp+h2NF/wcr4OSD0i+H0mwZWajpZ3UmSeJ0BFK6ODEbmVycrInpHo3n +zyMJnEDTJXL3HUwZSLjO5e5cNaB+75tdHrj2yJtRLuaJFr02b0EO1MUYfuUuqlK4 +7mg7FkBsimW+CXkoLRjHYK88ibT3G+rZ/STf4S/jxiRjBi06FAql3H02K5i1umgB +0BaaQei0Z8wQxMeTEnGzL+OcJeqDA1ZRFeXe7DNGsX1jeTYKPHA/Dr2IdZqyiCr2 +xh6e7RJuUe4D2liXW8LlMdwhN/7xSinA031PgBmb8XzSRmfdHhytFkA8PiM5T2ew +NR3qXBJ/G7BuRa/t26RuKI3BMVoBQPhGx80ds10uJjxq +-----END ENCRYPTED PRIVATE KEY----- diff --git a/lib/sasl/src/Makefile b/lib/sasl/src/Makefile index fd62588f5c..a5b9a5e731 100644 --- a/lib/sasl/src/Makefile +++ b/lib/sasl/src/Makefile @@ -63,9 +63,6 @@ TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET) # ---------------------------------------------------- ERL_COMPILE_FLAGS += -I../../stdlib/include -Werror -ifeq ($(USE_ESOCK), yes) -ERL_COMPILE_FLAGS += -DUSE_ESOCK=true -endif # ---------------------------------------------------- diff --git a/lib/sasl/src/systools_make.erl b/lib/sasl/src/systools_make.erl index b5a6b44f93..c5d4b9bef3 100644 --- a/lib/sasl/src/systools_make.erl +++ b/lib/sasl/src/systools_make.erl @@ -47,9 +47,11 @@ -compile({inline,[{badarg,2}]}). -ifdef(USE_ESOCK). --define(ESOCK_MODS, [socket]). +-define(ESOCK_SOCKET_MODS, [socket]). +-define(ESOCK_NET_MODS, [prim_net]). -else. --define(ESOCK_MODS, []). +-define(ESOCK_SOCKET_MODS, []). +-define(ESOCK_NET_MODS, []). -endif. @@ -1573,8 +1575,8 @@ preloaded() -> [atomics,counters,erl_init,erl_prim_loader,erl_tracer,erlang, erts_code_purger,erts_dirty_process_signal_handler, erts_internal,erts_literal_area_collector, - init,net,persistent_term,prim_buffer,prim_eval,prim_file, - prim_inet,prim_zip] ++ ?ESOCK_MODS ++ [zlib]. + init,persistent_term,prim_buffer,prim_eval,prim_file, + prim_inet] ++ ?ESOCK_NET_MODS ++ [prim_zip] ++ ?ESOCK_SOCKET_MODS ++ [zlib]. %%______________________________________________________________________ %% Kernel processes; processes that are specially treated by the init diff --git a/lib/snmp/Makefile b/lib/snmp/Makefile index 879f1b05c5..df321fc2d1 100644 --- a/lib/snmp/Makefile +++ b/lib/snmp/Makefile @@ -2,7 +2,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. @@ -136,11 +136,17 @@ dclean: dialyzer_plt: $(DIA_PLT) -$(DIA_PLT): +$(DIA_PLT): Makefile @echo "Building $(APPLICATION) plt file" @dialyzer --build_plt \ --output_plt $@ \ -r ../$(APPLICATION)/ebin \ + ../../lib/kernel/ebin \ + ../../lib/stdlib/ebin \ + ../../lib/runtime_tools/ebin \ + ../../lib/crypto/ebin \ + ../../lib/mnesia/ebin \ + ../../erts/preloaded/ebin \ --output $(DIA_ANALYSIS) \ --verbose @@ -148,4 +154,4 @@ dialyzer: $(DIA_PLT) @echo "Running dialyzer on $(APPLICATION)" @dialyzer --plt $< \ ../$(APPLICATION)/ebin \ - --verbose
\ No newline at end of file + --verbose diff --git a/lib/snmp/doc/src/snmpa_mib_storage.xml b/lib/snmp/doc/src/snmpa_mib_storage.xml index ee2b009e77..6db2f178a9 100644 --- a/lib/snmp/doc/src/snmpa_mib_storage.xml +++ b/lib/snmp/doc/src/snmpa_mib_storage.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2013</year><year>2016</year> + <year>2013</year><year>2019</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -193,7 +193,7 @@ </func> <func> - <name since="OTP R16B01">Module:match_object(TabId, Pattern) -> {ok, Recs} | {error, Reason}</name> + <name since="OTP R16B01">Module:match_object(TabId, Pattern) -> Recs | {error, Reason}</name> <fsummary>Search the mib-storage table for record matching pattern</fsummary> <type> <v>TabId = term()</v> @@ -210,7 +210,7 @@ </func> <func> - <name since="OTP R16B01">Module:match_delete(TabId, Pattern) -> {ok, Recs} | {error, Reason}</name> + <name since="OTP R16B01">Module:match_delete(TabId, Pattern) -> Recs | {error, Reason}</name> <fsummary>Delete records in the mib-storage table matching pattern</fsummary> <type> <v>TabId = term()</v> diff --git a/lib/snmp/src/agent/snmp_community_mib.erl b/lib/snmp/src/agent/snmp_community_mib.erl index 9fd7b30f9f..4bd30632f5 100644 --- a/lib/snmp/src/agent/snmp_community_mib.erl +++ b/lib/snmp/src/agent/snmp_community_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. @@ -206,10 +206,10 @@ do_add_community(Community) -> {error, create_failed} end catch - {error, Reason} -> - {error, Reason}; - Class:Reason -> - {error, {Class, Reason, erlang:get_stacktrace()}} + throw:{error, _} = ERROR -> + ERROR; + C:E:S -> + {error, {C, E, S}} end. %% FIXME: does not work with mnesia @@ -545,26 +545,18 @@ snmpTargetAddrExtTable(is_set_ok, RowIndex, Cols0) -> end. - get_snmpTargetAddrTDomain(RowIndex, Col) -> - case - get( - snmpTargetAddrTable, RowIndex, - [?snmpTargetAddrRowStatus,?snmpTargetAddrTDomain]) - of - [{value,?snmpTargetAddrRowStatus_active},ValueTDomain] -> - case ValueTDomain of - {value,TDomain} -> - TDomain; - _ -> - ?snmpUDPDomain - end; - _ -> + Cols = [?snmpTargetAddrRowStatus,?snmpTargetAddrTDomain], + case snmp_target_mib:snmpTargetAddrTable(get, RowIndex, Cols) of + [{value, ?snmpTargetAddrRowStatus_active}, {value, TDomain}] -> + TDomain; + [{value, ?snmpTargetAddrRowStatus_active}, _] -> + ?snmpUDPDomain; + _ -> wrongValue(Col) end. - verify_snmpTargetAddrExtTable_cols([], _TDomain, Cols) -> {ok, lists:reverse(Cols)}; verify_snmpTargetAddrExtTable_cols([{Col, Val0}|Cols], TDomain, Acc) -> diff --git a/lib/snmp/src/agent/snmp_framework_mib.erl b/lib/snmp/src/agent/snmp_framework_mib.erl index 7ea4f0ed97..6db6f87a85 100644 --- a/lib/snmp/src/agent/snmp_framework_mib.erl +++ b/lib/snmp/src/agent/snmp_framework_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. @@ -246,6 +246,7 @@ check_agent(X) -> %% Ordering function to sort intAgentTransportDomain first %% hence before intAgentIpAddress. Sort other entries on the key. +-dialyzer({nowarn_function, order_agent/2}). order_agent(EntryA, EntryB) -> snmp_conf:keyorder( 1, EntryA, EntryB, diff --git a/lib/snmp/src/agent/snmp_generic.erl b/lib/snmp/src/agent/snmp_generic.erl index e67a1b3c80..26a0dd0648 100644 --- a/lib/snmp/src/agent/snmp_generic.erl +++ b/lib/snmp/src/agent/snmp_generic.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. @@ -421,12 +421,12 @@ table_check_status(NameDb, Col, ?'RowStatus_createAndGo', RowIndex, Cols) -> _Found -> {inconsistentValue, Col} end catch - _:_Reason -> + _:_E:_S -> ?vtrace( "failed construct row (createAndGo): " - " n Reason: ~p" - " n Stack: ~p", - [_Reason, erlang:get_stacktrace()]), + " n Error: ~p" + " n Stack: ~p", + [_E, _S]), {noCreation, Col} % Bad RowIndex end; true -> {inconsistentValue, Col} @@ -441,12 +441,12 @@ table_check_status(NameDb, Col, ?'RowStatus_createAndWait', RowIndex, Cols) -> _Row -> {noError, 0} catch - _:_Reason -> + _:_E:_S -> ?vtrace( "failed construct row (createAndWait): " - " n Reason: ~p" - " n Stack: ~p", - [_Reason, erlang:get_stacktrace()]), + " n Error: ~p" + " n Stack: ~p", + [_E, _S]), {noCreation, Col} % Bad RowIndex end; true -> {inconsistentValue, Col} diff --git a/lib/snmp/src/agent/snmp_standard_mib.erl b/lib/snmp/src/agent/snmp_standard_mib.erl index bfe471178d..679d2657c6 100644 --- a/lib/snmp/src/agent/snmp_standard_mib.erl +++ b/lib/snmp/src/agent/snmp_standard_mib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2015. 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. @@ -27,6 +27,7 @@ -include("snmp_types.hrl"). -include("STANDARD-MIB.hrl"). +-include("snmpa_internal.hrl"). -define(VMODULE,"STANDARD-MIB"). -include("snmp_verbosity.hrl"). @@ -547,10 +548,12 @@ dummy(_Op) -> ok. %%----------------------------------------------------------------- snmp_set_serial_no(new) -> snmp_generic:variable_func(new, {snmpSetSerialNo, volatile}), - random:seed(erlang:phash2([node()]), - erlang:monotonic_time(), - erlang:unique_integer()), - Val = random:uniform(2147483648) - 1, + ?SNMP_RAND_SEED(), + %% rand:seed(exrop, + %% {erlang:phash2([node()]), + %% erlang:monotonic_time(), + %% erlang:unique_integer()}), + Val = rand:uniform(2147483648) - 1, snmp_generic:variable_func(set, Val, {snmpSetSerialNo, volatile}); snmp_set_serial_no(delete) -> diff --git a/lib/snmp/src/agent/snmp_target_mib.erl b/lib/snmp/src/agent/snmp_target_mib.erl index e65fa7f340..22fd3acb84 100644 --- a/lib/snmp/src/agent/snmp_target_mib.erl +++ b/lib/snmp/src/agent/snmp_target_mib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2015. All Rights Reserved. +%% Copyright Ericsson AB 1998-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. @@ -42,6 +42,7 @@ -define(VMODULE,"TARGET-MIB"). -include("snmp_verbosity.hrl"). +-include("snmpa_internal.hrl"). %% Column not accessible via SNMP - needed when the agent sends informs @@ -673,10 +674,12 @@ snmpTargetSpinLock(print) -> snmpTargetSpinLock(new) -> snmp_generic:variable_func(new, {snmpTargetSpinLock, volatile}), - random:seed(erlang:phash2([node()]), - erlang:monotonic_time(), - erlang:unique_integer()), - Val = random:uniform(2147483648) - 1, + ?SNMP_RAND_SEED(), + %% rand:seed(exrop, + %% {erlang:phash2([node()]), + %% erlang:monotonic_time(), + %% erlang:unique_integer()}), + Val = rand:uniform(2147483648) - 1, snmp_generic:variable_func(set, Val, {snmpTargetSpinLock, volatile}); snmpTargetSpinLock(delete) -> diff --git a/lib/snmp/src/agent/snmp_user_based_sm_mib.erl b/lib/snmp/src/agent/snmp_user_based_sm_mib.erl index f6e4fd3951..4842669fa4 100644 --- a/lib/snmp/src/agent/snmp_user_based_sm_mib.erl +++ b/lib/snmp/src/agent/snmp_user_based_sm_mib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2015. 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. @@ -440,10 +440,12 @@ usmUserSpinLock(print) -> usmUserSpinLock(new) -> snmp_generic:variable_func(new, {usmUserSpinLock, volatile}), - random:seed(erlang:phash2([node()]), - erlang:monotonic_time(), - erlang:unique_integer()), - Val = random:uniform(2147483648) - 1, + ?SNMP_RAND_SEED(), + %% rand:seed(exrop, + %% {erlang:phash2([node()]), + %% erlang:monotonic_time(), + %% erlang:unique_integer()}), + Val = rand:uniform(2147483648) - 1, snmp_generic:variable_func(set, Val, {usmUserSpinLock, volatile}); usmUserSpinLock(delete) -> 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 c6eeb7cea2..a5a65d9326 100644 --- a/lib/snmp/src/agent/snmp_view_based_acm_mib.erl +++ b/lib/snmp/src/agent/snmp_view_based_acm_mib.erl @@ -48,6 +48,7 @@ -include("SNMPv2-TC.hrl"). -include("SNMP-VIEW-BASED-ACM-MIB.hrl"). -include("snmpa_vacm.hrl"). +-include("snmpa_internal.hrl"). -define(VMODULE,"VACM-MIB"). @@ -653,7 +654,7 @@ vacmAccessTable(is_set_ok, RowIndex, Cols0) -> {{Col, ?'RowStatus_createAndWait'}, _} -> %% Row already exists => inconsistentValue {inconsistentValue, Col}; - {value, {_Col, ?'RowStatus_destroy'}} -> + {{_Col, ?'RowStatus_destroy'}, _} -> %% always ok! {noError, 0}; {_, false} -> @@ -860,10 +861,12 @@ vacmViewSpinLock(print) -> vacmViewSpinLock(new) -> snmp_generic:variable_func(new, volatile_db(vacmViewSpinLock)), - random:seed(erlang:phash2([node()]), - erlang:monotonic_time(), - erlang:unique_integer()), - Val = random:uniform(2147483648) - 1, + ?SNMP_RAND_SEED(), + %% rand:seed(exrop, + %% {erlang:phash2([node()]), + %% erlang:monotonic_time(), + %% erlang:unique_integer()}), + Val = rand:uniform(2147483648) - 1, snmp_generic:variable_func(set, Val, volatile_db(vacmViewSpinLock)); vacmViewSpinLock(delete) -> @@ -1112,9 +1115,7 @@ externalize_next(Name, Result) when is_list(Result) -> F = fun({[Col | _] = Idx, Val}) -> {Idx, externalize(Name, Col, Val)}; (Other) -> Other end, - [F(R) || R <- Result]; -externalize_next(_, Result) -> - Result. + [F(R) || R <- Result]. externalize_get(Name, Cols, Result) when is_list(Result) -> @@ -1124,9 +1125,7 @@ externalize_get(Name, Cols, Result) when is_list(Result) -> end, %% Merge column numbers and return values. there must be as much %% return values as there are columns requested. And then patch all values - [F(R) || R <- lists:zip(Cols, Result)]; -externalize_get(_, _, Result) -> - Result. + [F(R) || R <- lists:zip(Cols, Result)]. externalize(vacmViewTreeFamilyTable, ?vacmViewTreeFamilyMask, Val) -> imask2emask(Val); diff --git a/lib/snmp/src/agent/snmpa_agent.erl b/lib/snmp/src/agent/snmpa_agent.erl index f280260f47..7489f74223 100644 --- a/lib/snmp/src/agent/snmpa_agent.erl +++ b/lib/snmp/src/agent/snmpa_agent.erl @@ -525,9 +525,25 @@ unregister_subagent(Agent, SubagentOidOrPid) -> %% These subagent_ functions either return a value, or exits %% with {nodedown, Node} | Reason. %%----------------------------------------------------------------- -subagent_get(SubAgent, Varbinds, IsNotification) -> + +%% A proper spec for this would be something like this: +%% But, there is now way to spec that a process *can* exit. +%% -spec subagent_get(Agent, VBs, IsNotification) -> +%% {noError, 0, NewVBs} | +%% {ErrStatus, ErrIndex, []} | +%% erlang:exit(Reason) when +%% Agent :: pid(), +%% VBs :: [snmp:varbind()], +%% IsNotification :: boolean(), +%% NewVBs :: [snmp:varbind()], +%% ErrStatus :: snmp:error_status(), +%% ErrIndex :: snmp:error_index(), +%% Reason :: {nodedown, Node} | term(), +%% Node :: atom(). + +subagent_get(SubAgent, VBs, IsNotification) -> PduData = get_pdu_data(), - call(SubAgent, {subagent_get, Varbinds, PduData, IsNotification}). + call(SubAgent, {subagent_get, VBs, PduData, IsNotification}). subagent_get_next(SubAgent, MibView, Varbinds) -> PduData = get_pdu_data(), diff --git a/lib/snmp/src/agent/snmpa_authentication_service.erl b/lib/snmp/src/agent/snmpa_authentication_service.erl index e4238a8384..b6b9f5bd96 100644 --- a/lib/snmp/src/agent/snmpa_authentication_service.erl +++ b/lib/snmp/src/agent/snmpa_authentication_service.erl @@ -1,7 +1,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. @@ -19,13 +19,27 @@ %% -module(snmpa_authentication_service). --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{init_check_access, 2}]; -behaviour_info(_) -> - undefined. - +-export_type([ + acm_data/0 + ]). + +-type acm_data() :: {community, + SecModel :: 0 | 1 | 2 | 3, % any | v1 | v2c | v3 + Community :: string(), + %% Oids for either: + %% transportDomainUdpIpv4 | transportDomainUdpIpv6 + TDomain :: snmp:oid(), + TAddress :: [non_neg_integer()]} | + {v3, + MsgID :: integer(), + SecModel :: 0 | 1 | 2 | 3, % any | v1 | v2c | v3 + SecName :: string(), + %% noAuthNoPriv | authNoPriv | authPriv + SecLevel :: 1 | 2 | 3, + ContextEngineID :: string(), + ContextName :: string(), + SecData :: term()}. + %%----------------------------------------------------------------- %% init_check_access(Pdu, ACMData) @@ -46,9 +60,7 @@ behaviour_info(_) -> %% Variable = snmpInBadCommunityNames | %% snmpInBadCommunityUses | %% snmpInASNParseErrs -%% Reason = snmp_message_decoding | -%% {bad_community_name, Address, Community}} | -%% {invalid_access, Access, Op} +%% Reason = {bad_community_name, Address, Community}} %% %% Purpose: Called once for each Pdu. Returns a MibView %% which is later used for each variable in the pdu. @@ -57,3 +69,14 @@ behaviour_info(_) -> %% %% NOTE: This function is executed in the Master agents's context %%----------------------------------------------------------------- + +-callback init_check_access(Pdu, ACMData) -> + {ok, MibView, ContextName} | + {error, Reason} | + {discarded, Variable, Reason} when + Pdu :: snmp:pdu(), + ACMData :: acm_data(), + MibView :: snmp_view_based_acm_mib:mibview(), + ContextName :: string(), + Reason :: term(), + Variable :: snmpInBadCommunityNames. diff --git a/lib/snmp/src/agent/snmpa_conf.erl b/lib/snmp/src/agent/snmpa_conf.erl index fc5116dac9..c2e9d4025a 100644 --- a/lib/snmp/src/agent/snmpa_conf.erl +++ b/lib/snmp/src/agent/snmpa_conf.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2016. All Rights Reserved. +%% Copyright Ericsson AB 2006-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. @@ -83,6 +83,34 @@ +-export_type([ + usm_entry/0 + ]). + +-type usm_entry() :: { + EngineID :: string(), + UserName :: string(), + SecName :: string(), + Clone :: zeroDotZero | [non_neg_integer()], + AuthP :: usmNoAuthProtocol | + usmHMACMD5AuthProtocol | + usmHMACSHAAuthProtocol, + AuthKeyC :: string(), + OwnAuthKeyC :: string(), + PrivP :: usmNoPrivProtocol | + usmDESPrivProtocol | + usmAesCfb128Protocol, + PrivKeyC :: string(), + OwnPrivKeyC :: string(), + Public :: string(), + %% Size 16 for usmHMACMD5AuthProtocol + %% Size 20 for usmHMACSHAAuthProtocol + AuthKey :: [non_neg_integer()], + %% Size 16 for usmDESPrivProtocol | usmAesCfb128Protocol + PrivKey :: [non_neg_integer()] + }. + + %% %% ------ agent.conf ------ %% diff --git a/lib/snmp/src/agent/snmpa_discovery_handler.erl b/lib/snmp/src/agent/snmpa_discovery_handler.erl index ffdd6aca1e..6fb1d1eb72 100644 --- a/lib/snmp/src/agent/snmpa_discovery_handler.erl +++ b/lib/snmp/src/agent/snmpa_discovery_handler.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009-2016. All Rights Reserved. +%% Copyright Ericsson AB 2009-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. @@ -19,12 +19,17 @@ %% -module(snmpa_discovery_handler). --export([behaviour_info/1, verify/1]). - -behaviour_info(callbacks) -> - [{stage1_finish, 3}]; -behaviour_info(_) -> - undefined. +-export([verify/1]). +-callback stage1_finish(TargetName, ManagerEngineID, ExtraInfo) -> + ignore | + {ok, snmpa_conf:usm_entry() | [snmpa_conf:usm_entry()]} | + {ok, snmpa_conf:usm_entry() | [snmpa_conf:usm_entry()], NewExtraInfo} when + TargetName :: string(), + ManagerEngineID :: string(), + ExtraInfo :: term(), + NewExtraInfo :: term(). + verify(Mod) -> snmp_misc:verify_behaviour(?MODULE, Mod). + diff --git a/lib/snmp/src/agent/snmpa_error_report.erl b/lib/snmp/src/agent/snmpa_error_report.erl index 8f28eac653..6b281693e5 100644 --- a/lib/snmp/src/agent/snmpa_error_report.erl +++ b/lib/snmp/src/agent/snmpa_error_report.erl @@ -1,7 +1,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. @@ -19,10 +19,12 @@ %% -module(snmpa_error_report). --export([behaviour_info/1]). +-callback config_err(Format, Args) -> + snmp:void() when + Format :: string(), + Args :: [term()]. -behaviour_info(callbacks) -> - [{user_err, 2}, - {config_err, 2}]; -behaviour_info(_) -> - undefined. +-callback user_err(Format, Args) -> + snmp:void() when + Format :: string(), + Args :: [term()]. diff --git a/lib/snmp/src/agent/snmpa_get.erl b/lib/snmp/src/agent/snmpa_get.erl index e67975a67d..8b16016d84 100644 --- a/lib/snmp/src/agent/snmpa_get.erl +++ b/lib/snmp/src/agent/snmpa_get.erl @@ -75,6 +75,9 @@ %% {ErrorStatus, ErrorIndex, []} %%----------------------------------------------------------------- +%% There is now to properly spec the behaviour of the ?AGENT:subagent_get/3 +%% function (it *can* exit). +-dialyzer({nowarn_function, do_get/3}). do_get(UnsortedVarbinds, IsNotification, _Extra) -> {MyVarbinds, SubagentVarbinds} = ?LIB:agent_sort_vbs(UnsortedVarbinds), case do_get_local(MyVarbinds, IsNotification) of @@ -122,6 +125,7 @@ do_get(MibView, UnsortedVarbinds, IsNotification, Extra) -> do_get_local(VBs, IsNotification) -> do_get_local(VBs, [], IsNotification). +-dialyzer({nowarn_function, do_get_local/3}). do_get_local([Vb | Vbs], Res, IsNotification) -> case try_get(Vb, IsNotification) of NewVb when is_record(NewVb, varbind) -> @@ -144,11 +148,16 @@ do_get_local([], Res, _IsNotification) -> %% Returns: {noError, 0, ListOfNewVarbinds} | %% {ErrorStatus, ErrorIndex, []} %%----------------------------------------------------------------- + +%% There is now to properly spec the behaviour of the ?AGENT:subagent_get/3 +%% function (it *can* exit). +-dialyzer({nowarn_function, do_get_subagents/3}). 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 + case (catch ?AGENT:subagent_get(SubAgentPid, Vbs, IsNotification)) of {noError, 0, NewVbs} -> do_get_subagents(Tail, lists:append(NewVbs, Res), IsNotification); {ErrorStatus, ErrorIndex, _} -> @@ -168,6 +177,8 @@ do_get_subagents([], Res, _IsNotification) -> %% #varbind | %% List of #varbind %%----------------------------------------------------------------- + +-dialyzer({nowarn_function, try_get/2}). try_get(IVb, IsNotification) when is_record(IVb, ivarbind) -> ?vtrace("try_get(ivarbind) -> entry with" "~n IVb: ~p", [IVb]), @@ -186,9 +197,12 @@ try_get({TableOid, TableVbs}, IsNotification) -> NVbs ++ NoAccessVbs end. + %%----------------------------------------------------------------- %% Make sure all requested columns are accessible. %%----------------------------------------------------------------- + +-dialyzer({nowarn_function, check_all_table_vbs/4}). check_all_table_vbs([IVb| IVbs], IsNotification, NoA, A) -> #ivarbind{mibentry = Me, varbind = Vb} = IVb, case Me#me.access of @@ -210,6 +224,7 @@ check_all_table_vbs([], _IsNotification, NoA, A) -> {NoA, A}. %% Returns: {error, ErrorStatus, OrgIndex} | %% #varbind %%----------------------------------------------------------------- +-dialyzer({nowarn_function, get_var_value_from_ivb/2}). get_var_value_from_ivb(IVb, IsNotification) when IVb#ivarbind.status =:= noError -> ?vtrace("get_var_value_from_ivb(noError) -> entry", []), @@ -242,6 +257,7 @@ get_var_value_from_ivb(#ivarbind{status = Status, varbind = Vb}, _) -> %%----------------------------------------------------------------- %% Pre: Oid is a correct instance Oid (lookup checked that). %% Returns: A correct return value (see ?AGENT:make_value_a_correct_value) +-dialyzer({nowarn_function, get_var_value_from_mib/2}). get_var_value_from_mib(#me{entrytype = variable, asn1_type = ASN1Type, mfa = {Mod, Func, Args}}, @@ -280,6 +296,7 @@ get_var_value_from_mib(#me{entrytype = table_column, %% non-existing row). %% Returns: {error, ErrorStatus, OrgIndex} | %% {value, Type, Value} +-dialyzer({nowarn_function, get_tab_value_from_mib/3}). get_tab_value_from_mib(#me{mfa = {Mod, Func, Args}}, TableOid, TableVbs) -> ?vtrace("get_tab_value_from_mib -> entry when" "~n Mod: ~p" @@ -302,12 +319,14 @@ get_tab_value_from_mib(#me{mfa = {Mod, Func, Args}}, TableOid, TableVbs) -> %% #varbind. %% The Values list comes from validate_tab_res. %%----------------------------------------------------------------- +-dialyzer({nowarn_function, merge_varbinds_and_value/2}). 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(_, []) -> []. +-dialyzer({nowarn_function, get_value_all_rows/5}). get_value_all_rows([{[], OrgCols} | Rows], Mod, Func, Args, Res) -> ?vtrace("get_value_all_rows -> entry when" "~n OrgCols: ~p", [OrgCols]), @@ -352,10 +371,13 @@ delete_index([]) -> []. %% the retrieved values to reconstruct the original column list, %% but with the retrieved value for each column. %%----------------------------------------------------------------- + +-dialyzer({nowarn_function, remove_duplicates/1}). remove_duplicates(Cols) -> remove_duplicates(Cols, [], []). +-dialyzer({nowarn_function, remove_duplicates/3}). remove_duplicates([{Col, V1, OrgIdx1}, {Col, V2, OrgIdx2} | T], NCols, Dup) -> remove_duplicates([{Col, V1, OrgIdx1} | T], NCols, [{Col, V2, OrgIdx2} | Dup]); @@ -364,6 +386,7 @@ remove_duplicates([Col | T], NCols, Dup) -> remove_duplicates([], NCols, Dup) -> {lists:reverse(NCols), lists:reverse(Dup)}. +-dialyzer({nowarn_function, restore_duplicates/2}). restore_duplicates([], Cols) -> [{Val, OrgIndex} || {_Col, Val, OrgIndex} <- Cols]; restore_duplicates([{Col, _Val2, OrgIndex2} | Dup], @@ -385,6 +408,7 @@ restore_duplicates(Dup, [{_Col, Val, OrgIndex} | T]) -> %% each element in Values and OrgCols correspond to each %% other. %%----------------------------------------------------------------- +-dialyzer({nowarn_function, validate_tab_res/3}). validate_tab_res(Values, OrgCols, Mfa) when is_list(Values) -> {_Col, _ASN1Type, OneIdx} = hd(OrgCols), validate_tab_res(Values, OrgCols, Mfa, [], OneIdx); @@ -407,6 +431,7 @@ validate_tab_res(Error, [{_Col, _ASN1Type, Index} | _OrgCols], Mfa) -> ?LIB:user_err("Invalid return value ~w from ~w (get)",[Error, Mfa]), {error, genErr, Index}. +-dialyzer({nowarn_function, validate_tab_res/5}). validate_tab_res([Value | Values], [{Col, ASN1Type, Index} | OrgCols], Mfa, Res, I) -> diff --git a/lib/snmp/src/agent/snmpa_local_db.erl b/lib/snmp/src/agent/snmpa_local_db.erl index f481641242..c9093fcdb9 100644 --- a/lib/snmp/src/agent/snmpa_local_db.erl +++ b/lib/snmp/src/agent/snmpa_local_db.erl @@ -733,16 +733,16 @@ dets_backup(close, _Cont, _D, B) -> ok; dets_backup(read, Cont1, D, B) -> case dets:bchunk(D, Cont1) of + {error, _} = ERROR -> + ERROR; + '$end_of_table' -> + dets:close(B), + end_of_input; {Cont2, Data} -> F = fun(Arg) -> dets_backup(Arg, Cont2, D, B) end, - {Data, F}; - '$end_of_table' -> - dets:close(B), - end_of_input; - Error -> - Error + {Data, F} end. diff --git a/lib/snmp/src/agent/snmpa_mib_storage.erl b/lib/snmp/src/agent/snmpa_mib_storage.erl index ed0607fb84..d46dab0be0 100644 --- a/lib/snmp/src/agent/snmpa_mib_storage.erl +++ b/lib/snmp/src/agent/snmpa_mib_storage.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% Copyright Ericsson AB 2013-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. @@ -120,7 +120,7 @@ -callback match_object(TabId :: mib_storage_table_id(), Pattern :: ets:match_pattern()) -> - {ok, Recs :: [tuple()]} | {error, Reason :: term()}. + Recs :: [tuple()] | {error, Reason :: term()}. %% --------------------------------------------------------------- @@ -133,7 +133,7 @@ -callback match_delete(TabId :: mib_storage_table_id(), Pattern :: ets:match_pattern()) -> - {ok, Recs :: [tuple()]} | {error, Reason :: term()}. + Recs :: [tuple()] | {error, Reason :: term()}. %% --------------------------------------------------------------- diff --git a/lib/snmp/src/agent/snmpa_mib_storage_dets.erl b/lib/snmp/src/agent/snmpa_mib_storage_dets.erl index 2459b6bc3e..0fcb8083f5 100644 --- a/lib/snmp/src/agent/snmpa_mib_storage_dets.erl +++ b/lib/snmp/src/agent/snmpa_mib_storage_dets.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% Copyright Ericsson AB 2013-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. @@ -291,16 +291,16 @@ dets_backup(close, _Cont, _ID, B) -> ok; dets_backup(read, Cont1, ID, B) -> case dets:bchunk(ID, Cont1) of + {error, _} = ERROR -> + ERROR; + '$end_of_table' -> + dets:close(B), + end_of_input; {Cont2, Data} -> F = fun(Arg) -> dets_backup(Arg, Cont2, ID, B) end, - {Data, F}; - '$end_of_table' -> - dets:close(B), - end_of_input; - Error -> - Error + {Data, F} end. diff --git a/lib/snmp/src/agent/snmpa_mib_storage_ets.erl b/lib/snmp/src/agent/snmpa_mib_storage_ets.erl index 68dfa83247..173dac276e 100644 --- a/lib/snmp/src/agent/snmpa_mib_storage_ets.erl +++ b/lib/snmp/src/agent/snmpa_mib_storage_ets.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% Copyright Ericsson AB 2013-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. @@ -189,7 +189,8 @@ read(#tab{id = ID}, Key) -> write(#tab{id = ID, rec_name = RecName}, Rec) when (is_tuple(Rec) andalso (element(1, Rec) =:= RecName)) -> ?vtrace("write to table ~p", [ID]), - ets:insert(ID, Rec). + ets:insert(ID, Rec), + ok. %% --------------------------------------------------------------- @@ -213,7 +214,9 @@ delete(#tab{id = ID, file = File}) -> %% --------------------------------------------------------------- delete(#tab{id = ID}, Key) -> ?vtrace("delete from table ~p: ~p", [ID, Key]), - ets:delete(ID, Key). + ets:delete(ID, Key), + ok. + %% --------------------------------------------------------------- diff --git a/lib/snmp/src/agent/snmpa_mpd.erl b/lib/snmp/src/agent/snmpa_mpd.erl index b440d57d03..2ec5dcb5e6 100644 --- a/lib/snmp/src/agent/snmpa_mpd.erl +++ b/lib/snmp/src/agent/snmpa_mpd.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2015. All Rights Reserved. +%% Copyright Ericsson AB 1997-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. @@ -76,11 +76,9 @@ init(Vsns) -> ?vlog("init -> entry with" "~n Vsns: ~p", [Vsns]), - random:seed(erlang:phash2([node()]), - erlang:monotonic_time(), - erlang:unique_integer()), - ets:insert(snmp_agent_table, {msg_id, random:uniform(2147483647)}), - ets:insert(snmp_agent_table, {req_id, random:uniform(2147483647)}), + ?SNMP_RAND_SEED(), + ets:insert(snmp_agent_table, {msg_id, rand:uniform(2147483647)}), + ets:insert(snmp_agent_table, {req_id, rand:uniform(2147483647)}), init_counters(), init_versions(Vsns, #state{}). diff --git a/lib/snmp/src/agent/snmpa_network_interface.erl b/lib/snmp/src/agent/snmpa_network_interface.erl index 699fbde671..24985c113e 100644 --- a/lib/snmp/src/agent/snmpa_network_interface.erl +++ b/lib/snmp/src/agent/snmpa_network_interface.erl @@ -1,7 +1,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. @@ -19,28 +19,56 @@ %% -module(snmpa_network_interface). --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{start_link, 4}, - {get_log_type, 1}, - {set_log_type, 2}, - {get_request_limit, 1}, - {set_request_limit, 2}, - {info, 1}, - {verbosity, 2}]; -behaviour_info(_) -> - undefined. - - -%% behaviour_info(callbacks) -> -%% [{start_link, 4}, -%% {send_pdu, 5}, -%% {send_response_pdu, 6}, -%% {discard_pdu, 6}, -%% {send_pdu_req, 6}, -%% {verbosity, 2}, -%% {change_log_type, 2}]; -%% behaviour_info(_) -> -%% undefined. +%% Note that this behaviour is not enough! +%% There is also a set of mandatory messages which the +%% network interface entity must be able to receive and +%% be able to send. See the documentation for more info. + +-callback start_link(Prio, NoteStore, MasterAgent, Opts) -> + {ok, Pid} | {error, Reason} when + Prio :: low | normal | high, % priority_level(), + NoteStore :: pid(), + MasterAgent :: pid(), + Opts :: [Option], + Option :: {verbosity, snmp:verbosity()} | + {versions, [snmp:version()]} | + term(), + Pid :: pid(), + Reason :: term(). + +-callback info(Pid) -> + Info when + Pid :: pid(), + Info :: [{Key, Value}], + Key :: term(), + Value :: term(). + +-callback verbosity(Pid, Verbosity) -> + snmp:void() when + Pid :: pid(), + Verbosity :: snmp:verbosity(). + +-callback get_log_type(Pid) -> + {ok, LogType} | {error, Reason} when + Pid :: pid(), + LogType :: snmp:atl_type(), + Reason :: term(). + +-callback set_log_type(Pid, NewType) -> + {ok, OldType} | {error, Reason} when + Pid :: pid(), + NewType :: snmp:atl_type(), + OldType :: snmp:atl_type(), + Reason :: term(). + +-callback get_request_limit(Pid) -> + {ok, Limit} when + Pid :: pid(), + Limit :: non_neg_integer() | infinity. + +-callback set_request_limit(Pid, NewLimit) -> + {ok, OldLimit} when + Pid :: pid(), + NewLimit :: non_neg_integer() | infinity, + OldLimit :: non_neg_integer() | infinity. diff --git a/lib/snmp/src/agent/snmpa_set.erl b/lib/snmp/src/agent/snmpa_set.erl index 9833d6fdcc..b3a3bf0ab0 100644 --- a/lib/snmp/src/agent/snmpa_set.erl +++ b/lib/snmp/src/agent/snmpa_set.erl @@ -163,10 +163,14 @@ set_phase_two(MyVarbinds, SubagentVarbinds) -> [MyVarbinds, SubagentVarbinds]), case snmpa_set_lib:try_set(MyVarbinds) of {noError, 0} -> + ?vtrace("set phase two: (local) varbinds set ok", []), set_phase_two_subagents(SubagentVarbinds); - {ErrorStatus, Index} -> + {ErrorStatus, ErrorIndex} -> + ?vlog("set phase two: (local) varbinds set failed" + "~n ErrorStatus: ~p" + "~n ErrorIndex: ~p", [ErrorStatus, ErrorIndex]), set_phase_two_undo_subagents(SubagentVarbinds), - {ErrorStatus, Index} + {ErrorStatus, ErrorIndex} end. %%----------------------------------------------------------------- @@ -188,6 +192,7 @@ set_phase_two_subagents([{SubAgentPid, SAVbs} | SubagentVarbinds]) -> {_SAOids, Vbs} = sa_split(SAVbs), case catch snmpa_agent:subagent_set(SubAgentPid, [phase_two, set, Vbs]) of {noError, 0} -> + ?vtrace("set phase two: subagent ~p varbinds set ok", [SubAgentPid]), set_phase_two_subagents(SubagentVarbinds); {'EXIT', Reason} -> user_err("Lost contact with subagent (set)~n~w. Using genErr", @@ -195,10 +200,14 @@ set_phase_two_subagents([{SubAgentPid, SAVbs} | SubagentVarbinds]) -> set_phase_two_undo_subagents(SubagentVarbinds), {genErr, 0}; {ErrorStatus, ErrorIndex} -> + ?vlog("set phase two: subagent ~p varbinds set failed" + "~n ErrorStatus: ~p" + "~n ErrorIndex: ~p", [SubAgentPid, ErrorStatus, ErrorIndex]), set_phase_two_undo_subagents(SubagentVarbinds), {ErrorStatus, ErrorIndex} end; set_phase_two_subagents([]) -> + ?vtrace("set phase two: subagent(s) set ok", []), {noError, 0}. %%----------------------------------------------------------------- diff --git a/lib/snmp/src/agent/snmpa_set_mechanism.erl b/lib/snmp/src/agent/snmpa_set_mechanism.erl index 2f24f38092..4eee7d7257 100644 --- a/lib/snmp/src/agent/snmpa_set_mechanism.erl +++ b/lib/snmp/src/agent/snmpa_set_mechanism.erl @@ -1,7 +1,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. @@ -19,18 +19,26 @@ %% -module(snmpa_set_mechanism). --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{do_set, 2}, {do_subagent_set, 1}]; -behaviour_info(_) -> - undefined. +%% -export([behaviour_info/1]). +%% behaviour_info(callbacks) -> +%% [{do_set, 2}, {do_subagent_set, 1}]; +%% behaviour_info(_) -> +%% undefined. + %%----------------------------------------------------------------- %% do_set(MibView, UnsortedVarbinds) %%----------------------------------------------------------------- +-callback do_set(MibView, UnsortedVBs) -> + {noError, 0} | {ErrStatus, ErrIndex} when + MibView :: snmp_view_based_acm_mib:mibview(), + UnsortedVBs :: [snmp:varbind()], + ErrStatus :: snmp:error_status(), + ErrIndex :: snmp:error_index(). + + %%----------------------------------------------------------------- %% do_subagent_set(Args) %% @@ -41,3 +49,18 @@ behaviour_info(_) -> %% [phase_two, set, UnsortedVarbinds] %% [phase_two, undo, UnsortedVarbinds] %%----------------------------------------------------------------- + +%% -callback do_subagent_set(Args) -> +%% {noError, 0} | {ErrStatus, ErrIndex} when +%% Args :: [phase_one, UnsortedVBs] | +%% [phase_two, set, UnsortedVBs] | +%% [phase_two, undo, UnsortedVBs], +%% ErrStatus :: snmp:error_status(), +%% ErrIndex :: snmp:error_index(), +%% UnsortedVBs :: [snmp:varbind()]. + +-callback do_subagent_set(Args) -> + {noError, 0} | {ErrStatus, ErrIndex} when + Args :: list(), + ErrStatus :: snmp:error_status(), + ErrIndex :: snmp:error_index(). diff --git a/lib/snmp/src/agent/snmpa_supervisor.erl b/lib/snmp/src/agent/snmpa_supervisor.erl index 2cb0556001..7d5c6da2c8 100644 --- a/lib/snmp/src/agent/snmpa_supervisor.erl +++ b/lib/snmp/src/agent/snmpa_supervisor.erl @@ -22,7 +22,7 @@ -behaviour(supervisor). %% External exports --export([start_link/2]). +-export([start_link/2, stop/0, stop/1]). -export([start_sub_sup/1, start_master_sup/1]). -export([start_sub_agent/3, stop_sub_agent/1]). @@ -91,6 +91,39 @@ start_link(master, Opts, {takeover, Node}) -> Else end. + +stop() -> + stop(0). + +stop(Timeout) -> + case whereis(?SERVER) of + Pid when is_pid(Pid) -> + stop(Pid, Timeout); + _ -> + not_running + end. + +%% For some unfathomable reason there is no "nice" way to stop +%% a supervisor. The "normal" way to do it is: +%% 1) exit(Pid, kill) (kaboom) +%% 2) If the caller is the *parent*: exit(Pid, shutdown) +%% So, here we do it the really ugly way...but since this function is +%% intended for testing (mostly)... +stop(Pid, Timeout) when (Timeout =:= 0) -> + sys:terminate(Pid, shutdown), + ok; +stop(Pid, Timeout) -> + MRef = erlang:monitor(process, Pid), + sys:terminate(Pid, shutdown), + receive + {'DOWN', MRef, process, Pid, _} -> + ok + after Timeout -> + erlang:demonitor(MRef, [flush]), + {error, timeout} + end. + + get_own_loaded_mibs() -> AgentInfo = snmpa:info(snmp_master_agent), [ Name || {Name, _, _} <- loaded_mibs(AgentInfo) ]. diff --git a/lib/snmp/src/agent/snmpa_trap.erl b/lib/snmp/src/agent/snmpa_trap.erl index d04b6a206e..119207c76b 100644 --- a/lib/snmp/src/agent/snmpa_trap.erl +++ b/lib/snmp/src/agent/snmpa_trap.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. @@ -364,13 +364,14 @@ send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, LocalEngineID, LocalEngineID, ExtraInfo, NetIf) end catch - T:E -> - Info = [{args, [TrapRec, NotifyName, ContextName, - Recv, Vbs, LocalEngineID, ExtraInfo, NetIf]}, - {tag, T}, - {err, E}, - {stacktrace, erlang:get_stacktrace()}], - ?vlog("snmpa_trap:send_trap exception: ~p", [Info]), + C:E:S -> + Info = [{args, [TrapRec, NotifyName, ContextName, + Recv, Vbs, LocalEngineID, ExtraInfo, NetIf]}, + {class, C}, + {err, E}, + {stacktrace, S}], + ?vlog("snmpa_trap:send_trap exception: " + "~n ~p", [Info]), {error, {failed_sending_trap, Info}} end. @@ -1114,7 +1115,7 @@ transform_taddrs(TAddrs) -> %% v2 transform_taddr({?snmpUDPDomain, Addr}) -> - transform_taddr(transportDomainIdpIpv4, Addr); + transform_taddr(transportDomainUdpIpv4, Addr); transform_taddr({?transportDomainUdpIpv4, Addr}) -> transform_taddr(transportDomainUdpIpv4, Addr); transform_taddr({?transportDomainUdpIpv6, Addr}) -> diff --git a/lib/snmp/src/agent/snmpa_usm.erl b/lib/snmp/src/agent/snmpa_usm.erl index fb616cd9ef..1debceae98 100644 --- a/lib/snmp/src/agent/snmpa_usm.erl +++ b/lib/snmp/src/agent/snmpa_usm.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2015. 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. @@ -646,10 +646,12 @@ get_des_salt() -> ets:insert(snmp_agent_table, {usm_des_salt, 0}), 0; _ -> % it doesn't exist, initialize - random:seed(erlang:phash2([node()]), - erlang:monotonic_time(), - erlang:unique_integer()), - R = random:uniform(4294967295), + ?SNMP_RAND_SEED(), + %% rand:seed(exrop, + %% {erlang:phash2([node()]), + %% erlang:monotonic_time(), + %% erlang:unique_integer()}), + R = rand:uniform(4294967295), ets:insert(snmp_agent_table, {usm_des_salt, R}), R end, @@ -679,10 +681,12 @@ get_aes_salt() -> ets:insert(snmp_agent_table, {usm_aes_salt, 0}), 0; _ -> % it doesn't exist, initialize - random:seed(erlang:phash2([node()]), - erlang:monotonic_time(), - erlang:unique_integer()), - R = random:uniform(36893488147419103231), + ?SNMP_RAND_SEED(), + %% rand:seed(exrop, + %% {erlang:phash2([node()]), + %% erlang:monotonic_time(), + %% erlang:unique_integer()}), + R = rand:uniform(36893488147419103231), ets:insert(snmp_agent_table, {usm_aes_salt, R}), R end, diff --git a/lib/snmp/src/app/snmp.erl b/lib/snmp/src/app/snmp.erl index 216452afdd..1e6a93deff 100644 --- a/lib/snmp/src/app/snmp.erl +++ b/lib/snmp/src/app/snmp.erl @@ -95,6 +95,9 @@ dir/0, snmp_timer/0, + atl_type/0, + verbosity/0, + engine_id/0, tdomain/0, community/0, @@ -188,6 +191,9 @@ -type dir() :: string(). -type snmp_timer() :: #snmp_incr_timer{}. +-type atl_type() :: read | write | read_write. +-type verbosity() :: info | log | debug | trace | silence. + -type engine_id() :: string(). -type tdomain() :: transportDomainUdpIpv4 | transportDomainUdpIpv6. -type community() :: string(). @@ -590,15 +596,6 @@ print_mod_info(Prefix, {Module, Info}) -> _ -> "Not found" end, - CompDate = - case key1search(compile_time, Info) of - {value, {Year, Month, Day, Hour, Min, Sec}} -> - io_lib:format( - "~w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", - [Year, Month, Day, Hour, Min, Sec]); - _ -> - "Not found" - end, Digest = case key1search(md5, Info) of {value, MD5} when is_binary(MD5) -> @@ -610,13 +607,11 @@ print_mod_info(Prefix, {Module, Info}) -> "~s Vsn: ~s~n" "~s App vsn: ~s~n" "~s Compiler ver: ~s~n" - "~s Compile time: ~s~n" "~s MD5 digest: ~s~n", [Prefix, Module, Prefix, Vsn, Prefix, AppVsn, Prefix, CompVer, - Prefix, CompDate, Prefix, Digest]), ok. @@ -711,13 +706,8 @@ sys_info() -> [{arch, SysArch}, {ver, SysVer}]. os_info() -> - V = os:version(), - case os:type() of - {OsFam, OsName} -> - [{fam, OsFam}, {name, OsName}, {ver, V}]; - OsFam -> - [{fam, OsFam}, {ver, V}] - end. + {OsFam, OsName} = os:type(), + [{fam, OsFam}, {name, OsName}, {ver, os:version()}]. ms1() -> App = ?APPLICATION, diff --git a/lib/snmp/src/app/snmp_internal.hrl b/lib/snmp/src/app/snmp_internal.hrl index 374767df15..f9a758ab7b 100644 --- a/lib/snmp/src/app/snmp_internal.hrl +++ b/lib/snmp/src/app/snmp_internal.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2016. All Rights Reserved. +%% Copyright Ericsson AB 2006-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. @@ -25,7 +25,12 @@ -define(APPLICATION, snmp). -endif. --define(STACK(), erlang:get_stacktrace()). + +-define(SNMP_RAND_SEED_ALG, exrop). +-define(SNMP_RAND_SEED(), rand:seed(?SNMP_RAND_SEED_ALG, + {erlang:phash2([node()]), + erlang:monotonic_time(), + erlang:unique_integer()})). -define(snmp_info(C, F, A), ?snmp_msg(info_msg, C, F, A)). -define(snmp_warning(C, F, A), ?snmp_msg(warning_msg, C, F, A)). @@ -39,5 +44,3 @@ -endif. % -ifdef(snmp_internal). - - diff --git a/lib/snmp/src/compile/Makefile b/lib/snmp/src/compile/Makefile index 4093ffa9ca..d9678669a5 100644 --- a/lib/snmp/src/compile/Makefile +++ b/lib/snmp/src/compile/Makefile @@ -2,7 +2,7 @@ # %CopyrightBegin% # -# Copyright Ericsson AB 1997-2016. All Rights Reserved. +# Copyright Ericsson AB 1997-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. @@ -59,6 +59,8 @@ PARSER_TARGET = $(PARSER_MODULE).$(EMULATOR) # ---------------------------------------------------- # FLAGS # ---------------------------------------------------- +ERL_COMPILE_FLAGS += -pa $(ERL_TOP)/lib/snmp/ebin + ifeq ($(WARN_UNUSED_VARS),true) ERL_COMPILE_FLAGS += +warn_unused_vars endif diff --git a/lib/snmp/src/compile/snmpc.erl b/lib/snmp/src/compile/snmpc.erl index c810bfcd41..4249799195 100644 --- a/lib/snmp/src/compile/snmpc.erl +++ b/lib/snmp/src/compile/snmpc.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2018. All Rights Reserved. +%% Copyright Ericsson AB 1997-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. @@ -31,6 +31,7 @@ -export([init/3]). -include_lib("stdlib/include/erl_compile.hrl"). +-include_lib("snmp/src/app/snmp_internal.hrl"). -include("snmp_types.hrl"). -include("snmpc.hrl"). -include("snmpc_lib.hrl"). @@ -413,9 +414,11 @@ get_verbosity(Options) -> %%---------------------------------------------------------------------- init(From, MibFileName, Options) -> - random:seed(erlang:phash2([node()]), - erlang:monotonic_time(), - erlang:unique_integer()), + ?SNMP_RAND_SEED(), + %% rand:seed(exrop, + %% {erlang:phash2([node()]), + %% erlang:monotonic_time(), + %% erlang:unique_integer()}), put(options, Options), put(verbosity, get_verbosity(Options)), put(description, get_description(Options)), diff --git a/lib/snmp/src/manager/snmpm.erl b/lib/snmp/src/manager/snmpm.erl index cf8c95d69f..8e60cecaf9 100644 --- a/lib/snmp/src/manager/snmpm.erl +++ b/lib/snmp/src/manager/snmpm.erl @@ -1,7 +1,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. @@ -32,7 +32,7 @@ %% Management API start/0, start/1, start_link/0, start_link/1, - stop/0, + stop/0, stop/1, monitor/0, demonitor/1, notify_started/1, cancel_notify_started/1, @@ -196,7 +196,12 @@ start(Opts) -> ok. stop() -> - snmpm_supervisor:stop(). + stop(0). + +stop(Timeout) when (Timeout =:= infinity) orelse + (is_integer(Timeout) andalso (Timeout >= 0)) -> + snmpm_supervisor:stop(Timeout). + monitor() -> diff --git a/lib/snmp/src/manager/snmpm_conf.erl b/lib/snmp/src/manager/snmpm_conf.erl index 0421f49c88..d097b40438 100644 --- a/lib/snmp/src/manager/snmpm_conf.erl +++ b/lib/snmp/src/manager/snmpm_conf.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2016. All Rights Reserved. +%% Copyright Ericsson AB 2006-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. @@ -356,6 +356,7 @@ read_config_file(Dir, File, Order, Check) -> %% ---- config file utility functions ---- +-dialyzer({nowarn_function, check_ok/1}). % Future compat check_ok(ok) -> ok; check_ok({ok, _}) -> diff --git a/lib/snmp/src/manager/snmpm_config.erl b/lib/snmp/src/manager/snmpm_config.erl index 118cdcd1df..4653d9822d 100644 --- a/lib/snmp/src/manager/snmpm_config.erl +++ b/lib/snmp/src/manager/snmpm_config.erl @@ -1,7 +1,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. @@ -479,10 +479,7 @@ agent_info(Domain, Address, Item) when is_atom(Domain) -> NAddress -> do_agent_info(Domain, NAddress, Item) catch - _Thrown -> - %% p(?MODULE_STRING":agent_info(~p, ~p, ~p) throwed ~p at.~n" - %% " ~p", - %% [Domain, Address, Item, _Thrown, erlang:get_stacktrace()]), + _C:_E:_S -> {error, not_found} end; agent_info(Ip, Port, Item) when is_integer(Port) -> @@ -493,10 +490,7 @@ agent_info(Ip, Port, Item) when is_integer(Port) -> Address -> do_agent_info(Domain, Address, Item) catch - _Thrown -> - %% p(?MODULE_STRING":agent_info(~p, ~p, ~p) throwed ~p at.~n" - %% " ~p", - %% [Ip, Port, Item, _Thrown, erlang:get_stacktrace()]), + _C:_E:_S -> {error, not_found} end. @@ -1688,9 +1682,10 @@ read_agents_config_file(Dir) -> Check = fun check_agent_config/2, try read_file(Dir, "agents.conf", Order, Check, []) catch - throw:Error -> - ?vlog("agent config error: ~p", [Error]), - erlang:raise(throw, Error, erlang:get_stacktrace()) + throw:E:S -> + ?vlog("agent config error: " + "~n ~p", [E]), + erlang:raise(throw, E, S) end. check_agent_config(Agent, State) -> @@ -1935,9 +1930,10 @@ read_users_config_file(Dir) -> Check = fun (User, State) -> {check_user_config(User), State} end, try read_file(Dir, "users.conf", Order, Check, []) catch - throw:Error -> - ?vlog("failure reading users config file: ~n ~p", [Error]), - erlang:raise(throw, Error, erlang:get_stacktrace()) + throw:E:S -> + ?vlog("failure reading users config file: " + "~n ~p", [E]), + erlang:raise(throw, E, S) end. check_user_config({Id, Mod, Data}) -> @@ -2351,10 +2347,11 @@ read_file(Dir, FileName, Order, Check, Default) -> read_file(Dir, FileName, Order, Check) -> try snmp_conf:read(filename:join(Dir, FileName), Order, Check) catch - throw:{error, Reason} = Error + throw:{error, Reason} = E:S when element(1, Reason) =:= failed_open -> - error_msg("failed reading config from ~s: ~p", [FileName, Reason]), - erlang:raise(throw, Error, erlang:get_stacktrace()) + error_msg("failed reading config from ~s: " + "~n ~p", [FileName, Reason]), + erlang:raise(throw, E, S) end. %%-------------------------------------------------------------------- @@ -2741,16 +2738,16 @@ dets_backup(close, _Cont, _D, B) -> ok; dets_backup(read, Cont1, D, B) -> case dets:bchunk(D, Cont1) of + {error, _} = ERROR -> + ERROR; + '$end_of_table' -> + dets:close(B), + end_of_input; {Cont2, Data} -> F = fun(Arg) -> dets_backup(Arg, Cont2, D, B) end, - {Data, F}; - '$end_of_table' -> - dets:close(B), - end_of_input; - Error -> - Error + {Data, F} end. diff --git a/lib/snmp/src/manager/snmpm_mpd.erl b/lib/snmp/src/manager/snmpm_mpd.erl index 191dc2c281..8d0a7918a6 100644 --- a/lib/snmp/src/manager/snmpm_mpd.erl +++ b/lib/snmp/src/manager/snmpm_mpd.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2015. 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. @@ -68,11 +68,13 @@ %%%----------------------------------------------------------------- init(Vsns) -> ?vdebug("init -> entry with ~p", [Vsns]), - random:seed(erlang:phash2([node()]), - erlang:monotonic_time(), - erlang:unique_integer()), - snmpm_config:cre_counter(msg_id, random:uniform(2147483647)), - snmpm_config:cre_counter(req_id, random:uniform(2147483647)), + ?SNMP_RAND_SEED(), + %% rand:seed(exrop, + %% {erlang:phash2([node()]), + %% erlang:monotonic_time(), + %% erlang:unique_integer()}), + snmpm_config:cre_counter(msg_id, rand:uniform(2147483647)), + snmpm_config:cre_counter(req_id, rand:uniform(2147483647)), init_counters(), State = init_versions(Vsns, #state{}), init_usm(State#state.v3), diff --git a/lib/snmp/src/manager/snmpm_net_if.erl b/lib/snmp/src/manager/snmpm_net_if.erl index 29216f9d6a..184f782860 100644 --- a/lib/snmp/src/manager/snmpm_net_if.erl +++ b/lib/snmp/src/manager/snmpm_net_if.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2017. 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. @@ -182,11 +182,9 @@ worker(Worker, Failer, #state{log = Log} = State) -> %% Winds up in handle_info {'DOWN', ...} erlang:exit({net_if_worker, Result}) catch - Class:Reason -> + C:E:S -> %% Winds up in handle_info {'DOWN', ...} - erlang:exit( - {net_if_worker, Failer, - Class, Reason, erlang:get_stacktrace()}) + erlang:exit({net_if_worker, Failer, C, E, S}) end end, [monitor]). @@ -983,11 +981,10 @@ udp_send(Sock, To, Msg) -> error_msg("failed sending message to ~p:~p:~n" " ~p",[IpAddr, IpPort, Reason]) catch - error:Error -> - error_msg("failed sending message to ~p:~p:~n" - " error:~p~n" - " ~p", - [IpAddr, IpPort, Error, erlang:get_stacktrace()]) + error:E:S -> + error_msg("failed sending message to ~p:~p:" + "~n ~p" + "~n ~p", [IpAddr, IpPort, E, S]) end. sz(B) when is_binary(B) -> diff --git a/lib/snmp/src/manager/snmpm_network_interface.erl b/lib/snmp/src/manager/snmpm_network_interface.erl index d0f6f709d3..7123bb94f0 100644 --- a/lib/snmp/src/manager/snmpm_network_interface.erl +++ b/lib/snmp/src/manager/snmpm_network_interface.erl @@ -1,7 +1,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. @@ -20,19 +20,61 @@ -module(snmpm_network_interface). --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{start_link, 2}, - {stop, 1}, - {send_pdu, 7}, - {inform_response, 4}, - {note_store, 2}, - {info, 1}, - {verbosity, 2}, - %% {system_info_updated, 2}, - {get_log_type, 1}, - {set_log_type, 2}]; -behaviour_info(_) -> - undefined. +-callback start_link(Server, NoteStore) -> + {ok, Pid} | {error, Reason} when + Server :: pid(), + NoteStore :: pid(), + Pid :: pid(), + Reason :: term(). + +-callback stop(Pid) -> + snmp:void() when + Pid :: pid(). + +-callback send_pdu(Pid, Pdu, Vsn, MsgData, Domain, Addr, ExtraInfo) -> + snmp:void() when + Pid :: pid(), + Pdu :: snmp:pdu(), + Vsn :: 'version-1' | 'version-2' | 'version-3', + MsgData :: term(), + Domain :: snmp:tdomain(), + Addr :: {inet:ip_address(), inet:port_number()}, + ExtraInfo :: term(). + +-callback inform_response(Pid, Ref, Addr, Port) -> + snmp:void() when + Pid :: pid(), + Ref :: term(), + Addr :: inet:ip_address(), + Port :: inet:port_number(). + +-callback note_store(Pid, NoteStore) -> + snmp:void() when + Pid :: pid(), + NoteStore :: pid(). + +-callback info(Pid) -> + Info when + Pid :: pid(), + Info :: [{Key, Value}], + Key :: term(), + Value :: term(). + +-callback verbosity(Pid, Verbosity) -> + snmp:void() when + Pid :: pid(), + Verbosity :: snmp:verbosity(). + +-callback get_log_type(Pid) -> + {ok, LogType} | {error, Reason} when + Pid :: pid(), + LogType :: snmp:atl_type(), + Reason :: term(). + +-callback set_log_type(Pid, NewType) -> + {ok, OldType} | {error, Reason} when + Pid :: pid(), + NewType :: snmp:atl_type(), + OldType :: snmp:atl_type(), + Reason :: term(). diff --git a/lib/snmp/src/manager/snmpm_server.erl b/lib/snmp/src/manager/snmpm_server.erl index c8d7fa1e8b..a6ca2b2b14 100644 --- a/lib/snmp/src/manager/snmpm_server.erl +++ b/lib/snmp/src/manager/snmpm_server.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2015. 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. @@ -1755,9 +1755,10 @@ handle_error(_UserId, Mod, Reason, ReqId, Data, _State) -> Mod:handle_error(ReqId, Reason, Data) end catch - T:E -> + C:E:S -> CallbackArgs = [ReqId, Reason, Data], - handle_invalid_result(handle_error, CallbackArgs, T, E) + handle_invalid_result(handle_error, CallbackArgs, + C, E, S) end end, handle_callback(F), @@ -1948,9 +1949,10 @@ handle_pdu( Mod:handle_pdu(TargetName, ReqId, SnmpResponse, Data) end catch - T:E -> + C:E:S -> CallbackArgs = [TargetName, ReqId, SnmpResponse, Data], - handle_invalid_result(handle_pdu, CallbackArgs, T, E) + handle_invalid_result(handle_pdu, CallbackArgs, + C, E, S) end end, handle_callback(F), @@ -2119,10 +2121,10 @@ do_handle_agent(DefUserId, DefMod, "<~p,~p>: ~n~w", [Type, Domain, Addr, SnmpInfo]) end; - T:E -> + C:E:S -> CallbackArgs = [Domain_or_Ip, Addr_or_Port, Type, SnmpInfo, DefData], - handle_invalid_result(handle_agent, CallbackArgs, T, E) + handle_invalid_result(handle_agent, CallbackArgs, C, E, S) end. @@ -2331,8 +2333,8 @@ do_handle_trap( handle_invalid_result(handle_trap, CallbackArgs, InvalidResult) catch - T:E -> - handle_invalid_result(handle_trap, CallbackArgs, T, E) + C:E:S -> + handle_invalid_result(handle_trap, CallbackArgs, C, E, S) end. @@ -2523,8 +2525,8 @@ do_handle_inform( reply catch - T:E -> - handle_invalid_result(handle_inform, CallbackArgs, T, E), + C:E:S -> + handle_invalid_result(handle_inform, CallbackArgs, C, E, S), reply end, @@ -2837,8 +2839,8 @@ do_handle_report( reply catch - T:E -> - handle_invalid_result(handle_report, CallbackArgs, T, E), + C:E:S -> + handle_invalid_result(handle_report, CallbackArgs, C, E, S), reply end. @@ -2855,15 +2857,14 @@ handle_callback(F) -> -handle_invalid_result(Func, Args, T, E) -> - Stacktrace = ?STACK(), +handle_invalid_result(Func, Args, C, E, S) -> error_msg("Callback function failed: " "~n Function: ~p" "~n Args: ~p" - "~n Error Type: ~p" + "~n Class: ~p" "~n Error: ~p" "~n Stacktrace: ~p", - [Func, Args, T, E, Stacktrace]). + [Func, Args, C, E, S]). handle_invalid_result(Func, Args, InvalidResult) -> error_msg("Callback function returned invalid result: " diff --git a/lib/snmp/src/manager/snmpm_supervisor.erl b/lib/snmp/src/manager/snmpm_supervisor.erl index c36bbe1bdd..bc66025c6f 100644 --- a/lib/snmp/src/manager/snmpm_supervisor.erl +++ b/lib/snmp/src/manager/snmpm_supervisor.erl @@ -1,7 +1,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,7 +24,7 @@ %% External exports --export([start_link/2, stop/0]). +-export([start_link/2, stop/0, stop/1]). %% supervisor callbacks -export([init/1]). @@ -38,26 +38,54 @@ %%%------------------------------------------------------------------- %%% API %%%------------------------------------------------------------------- + start_link(Type, Opts) -> ?d("start_link -> entry with" "~n Opts: ~p", [Opts]), SupName = {local, ?MODULE}, supervisor:start_link(SupName, ?MODULE, [Type, Opts]). + stop() -> + stop(0). + +stop(Timeout) -> ?d("stop -> entry", []), case whereis(?SERVER) of Pid when is_pid(Pid) -> - ?d("stop -> Pid: ~p", [Pid]), - exit(Pid, shutdown), - ?d("stop -> stopped", []), - ok; + stop(Pid, Timeout); _ -> ?d("stop -> not running", []), not_running end. +%% For some unfathomable reason there is no "nice" way to stop +%% a supervisor. The "normal" way to do it is: +%% 1) exit(Pid, kill) (kaboom) +%% 2) If the caller is the *parent*: exit(Pid, shutdown) +%% So, here we do it the really ugly way...but since this function is +%% intended for testing (mostly)... +stop(Pid, Timeout) when (Timeout =:= 0) -> + ?d("stop -> Pid: ~p", [Pid]), + sys:terminate(Pid, shutdown), + ?d("stop -> stopped", []), + ok; +stop(Pid, Timeout) -> + ?d("stop -> Pid: ~p", [Pid]), + MRef = erlang:monitor(process, Pid), + sys:terminate(Pid, shutdown), + receive + {'DOWN', MRef, process, Pid, _} -> + ?d("stop -> stopped", []), + ok + after Timeout -> + ?d("stop -> timeout", []), + erlang:demonitor(MRef, [flush]), + {error, timeout} + end. + + %%%------------------------------------------------------------------- %%% Callback functions from supervisor %%%------------------------------------------------------------------- diff --git a/lib/snmp/src/misc/snmp_conf.erl b/lib/snmp/src/misc/snmp_conf.erl index 513616a285..20b7af0373 100644 --- a/lib/snmp/src/misc/snmp_conf.erl +++ b/lib/snmp/src/misc/snmp_conf.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. @@ -164,6 +164,14 @@ no_filter(X) -> X. %% An ordering function (A, B) shall return true iff %% A is less than or equal to B i.e shall return %% false iff A is to be ordered after B. + +-spec keyorder(N, A, B, Keys) -> + boolean() when + N :: integer(), + A :: tuple(), + B :: tuple(), + Keys :: maybe_improper_list(). + keyorder(N, A, B, _) when element(N, A) == element(N, B) -> true; keyorder(N, A, B, [Key | _]) @@ -236,15 +244,16 @@ read_check(File, Check, [{StartLine, Row, EndLine}|Lines], State, Res) -> " NewRow: ~p~n", [NewRow]), read_check(File, Check, Lines, NewState, [NewRow | Res]) catch - {error, Reason} -> - ?vtrace("read_check -> error:~n" - " Reason: ~p", [Reason]), + throw:{error, Reason} -> + ?vtrace("read_check -> error:" + "~n Reason: ~p", [Reason]), error({failed_check, File, StartLine, EndLine, Reason}); - Class:Reason -> - Error = {Class,Reason,erlang:get_stacktrace()}, - ?vtrace("read_check -> failure:~n" - " Error: ~p", [Error]), - error({failed_check, File, StartLine, EndLine, Error}) + C:E:S -> + ?vtrace("read_check -> failure:" + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + error({failed_check, File, StartLine, EndLine, {C, E, S}}) end. open_file(File) -> diff --git a/lib/snmp/src/misc/snmp_config.erl b/lib/snmp/src/misc/snmp_config.erl index 45661b71a7..3104f2a096 100644 --- a/lib/snmp/src/misc/snmp_config.erl +++ b/lib/snmp/src/misc/snmp_config.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. @@ -96,17 +96,15 @@ ]). --export_type([void/0, - order_config_entry_function/0, +-export_type([ + order_config_entry_function/0, check_config_entry_function/0, - write_config_function/0]). + write_config_function/0 + ]). %%---------------------------------------------------------------------- --type void() :: term(). % Any value - ignored - - %%---------------------------------------------------------------------- %% Handy SNMP configuration %%---------------------------------------------------------------------- @@ -1106,6 +1104,7 @@ verify_sec_type(ST) -> {error, "invalid security type: " ++ ST}. verify_address(A) -> verify_address(A, snmpUDPDomain). +-dialyzer({nowarn_function, verify_address/2}). % Future compat verify_address(A, snmpUDPDomain = _Domain) -> do_verify_address(A, inet); verify_address(A, transportDomainUdpIpv4 = _Domain) -> @@ -2573,15 +2572,17 @@ write_config_file(Dir, FileName, Order, Check, Write, Entries) Error end catch - Error -> - S = erlang:get_stacktrace(), - d("File write of ~s throwed: ~p~n ~p~n", - [FileName, Error, S]), - Error; - C:E -> - S = erlang:get_stacktrace(), - d("File write of ~s exception: ~p:~p~n ~p~n", - [FileName,C,E,S]), + throw:E:S -> + d("File write of ~s throwed: " + "~n ~p" + "~n ~p" + "~n", [FileName, E, S]), + E; + C:E:S -> + d("File write of ~s exception: " + "~n ~p:~p" + "~n ~p" + "~n", [FileName, C, E, S]), {error, {failed_write, Dir, FileName, {C, E, S}}} end. @@ -2590,16 +2591,18 @@ write_config_file(Dir, FileName, Write, Entries, Fd) -> ok -> close_config_file(Dir, FileName, Fd) catch - Error -> - S = erlang:get_stacktrace(), - d("File write of ~s throwed: ~p~n ~p~n", - [FileName, Error, S]), + throw:E:S -> + d("File write of ~s throwed: " + "~n ~p" + "~n ~p" + "~n", [FileName, E, S]), close_config_file(Dir, FileName, Fd), - Error; - C:E -> - S = erlang:get_stacktrace(), - d("File write of ~s exception: ~p:~p~n ~p~n", - [FileName,C,E,S]), + E; + C:E:S -> + d("File write of ~s exception: " + "~n ~p:~p" + "~n ~p" + "~n", [FileName, C, E, S]), close_config_file(Dir, FileName, Fd), {error, {failed_write, Dir, FileName, {C, E, S}}} end. @@ -2661,16 +2664,18 @@ append_config_file(Dir, FileName, Order, Check, Write, Entries, Fd) -> ok -> close_config_file(Dir, FileName, Fd) catch - Error -> - S = erlang:get_stacktrace(), - d("File append of ~s throwed: ~p~n ~p~n", - [FileName, Error, S]), + throw:E:S -> + d("File append of ~s throwed: " + "~n ~p" + "~n ~p" + "~n", [FileName, E, S]), close_config_file(Dir, FileName, Fd), - Error; - C:E -> - S = erlang:get_stacktrace(), - d("File append of ~s exception: ~p:~p~n ~p~n", - [FileName,C,E,S]), + E; + C:E:S -> + d("File append of ~s exception: " + "~n ~p:~p" + "~n ~p" + "~n", [FileName, C, E, S]), close_config_file(Dir, FileName, Fd), {error, {failed_append, Dir, FileName, {C, E, S}}} end. @@ -2702,16 +2707,18 @@ read_config_file(Dir, FileName, Order, Check) SortedLines = sort_lines(Lines, Order), {ok, verify_lines(SortedLines, Check, undefined, [])} catch - Error -> - S = erlang:get_stacktrace(), - d("File read of ~s throwed: ~p~n ~p~n", - [FileName, Error, S]), - {error, Error}; - T:E -> - S = erlang:get_stacktrace(), - d("File read of ~s exception: ~p:~p~n ~p~n", - [FileName,T,E,S]), - {error, {failed_read, Dir, FileName, {T, E, S}}} + throw:E:S -> + d("File read of ~s throwed: " + "~n ~p" + "~n ~p" + "~n", [FileName, E, S]), + {error, E}; + C:E:S -> + d("File read of ~s exception: " + "~n ~p:~p" + "~n ~p" + "~n", [FileName, C, E, S]), + {error, {failed_read, Dir, FileName, {C, E, S}}} after file:close(Fd) end; @@ -2760,11 +2767,10 @@ verify_lines( {{ok, NewTerm}, NewState} -> verify_lines(Lines, Check, NewState, [NewTerm|Acc]) catch - {error, Reason} -> + throw:{error, Reason}:_ -> throw({failed_check, StartLine, EndLine, Reason}); - C:R -> - S = erlang:get_stacktrace(), - throw({failed_check, StartLine, EndLine, {C, R, S}}) + C:E:S -> + throw({failed_check, StartLine, EndLine, {C, E, S}}) end. diff --git a/lib/snmp/src/misc/snmp_log.erl b/lib/snmp/src/misc/snmp_log.erl index 5713c14912..8a4dfa621b 100644 --- a/lib/snmp/src/misc/snmp_log.erl +++ b/lib/snmp/src/misc/snmp_log.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2018. All Rights Reserved. +%% Copyright Ericsson AB 1997-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. @@ -342,9 +342,9 @@ validate_loop({Cont, Terms, BadBytes}, Log, Validator, PrevTS, PrevSN) -> ?vtrace("validate_loop -> " "~n NextTS: ~p" "~n NextSN: ~p", [NextTS, NextSN]), - validate_loop(disk_log:chunk(Log, Cont), Log, Validator, NextTS, NextSN); -validate_loop(Error, _Log, _Write, _PrevTS, _PrevSN) -> - Error. + validate_loop(disk_log:chunk(Log, Cont), Log, Validator, NextTS, NextSN). +%% validate_loop(Error, _Log, _Write, _PrevTS, _PrevSN) -> +%% Error. %% -- log --- @@ -924,14 +924,7 @@ f(TimeStamp, SeqNo, end, format_tab( "~w ~s - ~s [~s]~s ~w\n~s", - [Class, AddrStr, HdrStr, TimeStamp, SeqNo, Vsn, Str]); -f(TimeStamp, SeqNo, Msg, AddrStr, _Mib) -> - io:format("<ERROR> Unexpected data: " - "~n TimeStamp: ~s~s" - "~n Msg: ~p" - "~n AddrStr: ~p" - "~n", [TimeStamp, SeqNo, Msg, AddrStr]), - throw({error, 'invalid-message'}). + [Class, AddrStr, HdrStr, TimeStamp, SeqNo, Vsn, Str]). f(F, A) -> lists:flatten(io_lib:format(F, A)). diff --git a/lib/snmp/test/Makefile b/lib/snmp/test/Makefile index a9142d911d..d9b01536ea 100644 --- a/lib/snmp/test/Makefile +++ b/lib/snmp/test/Makefile @@ -180,7 +180,7 @@ emakebuild: $(EMAKEFILE) targets: mib $(EMAKEFILE) erl -make -old_targets: $(TARGET_FILES) $(TEST_SERVER_TARGETS) +old_targets: mib $(TARGET_FILES) $(TEST_SERVER_TARGETS) $(EMAKEFILE): Makefile $(MAKE_EMAKE) $(ERL_COMPILE_FLAGS) -o$(EBIN) '*_SUITE_make' > $(EMAKEFILE) diff --git a/lib/snmp/test/modules.mk b/lib/snmp/test/modules.mk index 8b6547f9a9..ec3870dbd8 100644 --- a/lib/snmp/test/modules.mk +++ b/lib/snmp/test/modules.mk @@ -42,6 +42,8 @@ SUITE_MODULES = \ snmp_manager_test TEST_UTIL_MODULES = \ + snmp_test_global_sys_monitor \ + snmp_test_sys_monitor \ snmp_test_lib \ snmp_test_manager \ snmp_test_mgr \ diff --git a/lib/snmp/test/snmp_agent_test.erl b/lib/snmp/test/snmp_agent_test.erl index 71e3fa3b9a..a45cfa9e98 100644 --- a/lib/snmp/test/snmp_agent_test.erl +++ b/lib/snmp/test/snmp_agent_test.erl @@ -557,6 +557,8 @@ init_per_suite(Config0) when is_list(Config0) -> Config3 = [{mib_dir, MibDir}, {std_mib_dir, StdMibDir} | Config2], + snmp_test_global_sys_monitor:start(), + snmp_test_sys_monitor:start(), % We need one on this node also snmp_test_mgr_counter_server:start(), p("init_per_suite -> end when" @@ -580,6 +582,8 @@ end_per_suite(Config) when is_list(Config) -> p("end_per_suite -> failed stopping counter server" "~n Reason: ~p", [Reason]) end, + snmp_test_sys_monitor:stop(), + snmp_test_global_sys_monitor:stop(), p("end_per_suite -> end when" "~n Nodes: ~p", [erlang:nodes()]), @@ -667,22 +671,39 @@ init_per_group(GroupName, Config) -> snmp_test_lib:init_group_top_dir(GroupName, Config). init_per_group_ipv6(GroupName, Config, Init) -> - {ok, Hostname0} = inet:gethostname(), - case ct:require(ipv6_hosts) of - ok -> - case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts)) of - true -> - Init( - snmp_test_lib:init_group_top_dir( - GroupName, - [{ipfamily, inet6}, - {ip, ?LOCALHOST(inet6)} - | lists:keydelete(ip, 1, Config)])); - false -> - {skip, "Host does not support IPV6"} - end; - _ -> - {skip, "Test config ipv6_hosts is missing"} + %% <OS-CONDITIONAL-SKIP> + %% This is a higly questionable test. + %% But until we have time to figure out what IPv6 issues + %% are actually causing the failures... + OSSkipable = [{unix, + [ + {darwin, fun(V) when (V > {9, 8, 0}) -> + %% This version is OK: No Skip + false; + (_) -> + %% This version is *not* ok: Skip + true + end} + ] + }], + %% </OS-CONDITIONAL-SKIP> + case ?OS_BASED_SKIP(OSSkipable) of + true -> + {skip, "Host *may* not *properly* support IPV6"}; + false -> + %% Even if this host supports IPv6 we don't use it unless its + %% one of the configured/supported IPv6 hosts... + case (?HAS_SUPPORT_IPV6() andalso ?IS_IPV6_HOST()) of + true -> + Init( + snmp_test_lib:init_group_top_dir( + GroupName, + [{ipfamily, inet6}, + {ip, ?LOCALHOST(inet6)} + | lists:keydelete(ip, 1, Config)])); + false -> + {skip, "Host does not support IPv6"} + end end. end_per_group(all_tcs, Config) -> @@ -751,6 +772,8 @@ init_per_testcase(Case, Config) when is_list(Config) -> Result = init_per_testcase1(Case, Config), + snmp_test_global_sys_monitor:reset_events(), + p("init_per_testcase -> done when" "~n Result: ~p" "~n Nodes: ~p", [Result, erlang:nodes()]), @@ -800,6 +823,9 @@ end_per_testcase(Case, Config) when is_list(Config) -> "~n Nodes: ~p", [Config, erlang:nodes()]), display_log(Config), + + p("system events during test: " + "~n ~p", [snmp_test_global_sys_monitor:events()]), Result = end_per_testcase1(Case, Config), @@ -1637,7 +1663,7 @@ create_local_db_dir(Config) when is_list(Config) -> Name = list_to_atom(atom_to_list(create_local_db_dir) ++"-"++As++"-"++Bs++"-"++Cs), Pa = filename:dirname(code:which(?MODULE)), - {ok,Node} = ?t:start_node(Name, slave, [{args, "-pa "++Pa}]), + {ok,Node} = ?t:start_node(Name, slave, [{args, "-pa " ++ Pa}]), %% first start with a nonexisting DbDir Fun1 = fun() -> @@ -6567,7 +6593,6 @@ otp_4394_test() -> gn([[1,1]]), Res = case snmp_test_mgr:expect(1, [{[sysDescr,0], "Erlang SNMP agent"}]) of - %% {error, 1, {"?",[]}, {"~w",[timeout]}} {error, 1, _, {_, [timeout]}} -> ?DBG("otp_4394_test -> expected result: timeout", []), ok; diff --git a/lib/snmp/test/snmp_agent_test_lib.erl b/lib/snmp/test/snmp_agent_test_lib.erl index 6defdadb5a..c0da47dc4c 100644 --- a/lib/snmp/test/snmp_agent_test_lib.erl +++ b/lib/snmp/test/snmp_agent_test_lib.erl @@ -66,7 +66,7 @@ ]). %% Internal exports --export([wait/5, run/4]). +-export([tc_wait/5, tc_run/4]). -include_lib("kernel/include/file.hrl"). -include_lib("common_test/include/ct.hrl"). @@ -276,87 +276,197 @@ init_case(Config) when is_list(Config) -> %%% configuration. %%%-------------------------------------------------- -try_test(Mod, Func) -> - call(get(mgr_node), ?MODULE, run, [Mod, Func, [], []]). - -try_test(Mod, Func, A) -> - call(get(mgr_node), ?MODULE, run, [Mod, Func, A, []]). - -try_test(Mod, Func, A, Opts) -> - call(get(mgr_node), ?MODULE, run, [Mod, Func, A, Opts]). - -call(N,M,F,A) -> - ?DBG("call -> entry with~n" - " N: ~p~n" - " M: ~p~n" - " F: ~p~n" - " A: ~p~n" - " when~n" - " get(): ~p", - [N,M,F,A,get()]), - spawn(N, ?MODULE, wait, [self(),get(),M,F,A]), +try_test(TcRunMod, TcRunFunc) -> + try_test(TcRunMod, TcRunFunc, []). + +try_test(TcRunMod, TcRunFunc, TcRunArgs) -> + try_test(TcRunMod, TcRunFunc, TcRunArgs, []). + +try_test(TcRunMod, TcRunFunc, TcRunArgs, TcRunOpts) -> + Node = get(mgr_node), + Mod = ?MODULE, + Func = tc_run, + Args = [TcRunMod, TcRunFunc, TcRunArgs, TcRunOpts], + tc_try(Node, Mod, Func, Args). + +%% We spawn a test case runner process on the manager node. +%% The assumption is that the manager shall do something, but +%% not all test cases have the manager perform actions. +%% In some cases we make a rpc call back to the agent node directly +%% and call something in the agent... (for example the info_test +%% test case). +%% We should use link (instead of monitor) in order for the test case +%% timeout cleanup (kills) should have effect on the test case runner +%% process as well. + +tc_try(N, M, F, A) -> + ?PRINT2("tc_try -> entry with" + "~n N: ~p" + "~n M: ~p" + "~n F: ~p" + "~n A: ~p" + "~n when" + "~n get(): ~p" + "~n", [N, + M, F, A, + get()]), + case net_adm:ping(N) of + pong -> + ?PRINT2("tc_try -> ~p still running - start runner~n", [N]), + OldFlag = trap_exit(true), % Make sure we catch it + Runner = spawn_link(N, ?MODULE, tc_wait, [self(), get(), M, F, A]), + await_tc_runner_started(Runner, OldFlag), + await_tc_runner_done(Runner, OldFlag); + pang -> + ?EPRINT2("tc_try -> ~p *not* running~n", [N]), + skip({node_not_running, N}) + end. + +await_tc_runner_started(Runner, OldFlag) -> + ?PRINT2("await tc-runner (~p) start ack~n", [Runner]), receive - {done, {'EXIT', Rn}, Loc} -> - ?DBG("call -> done with exit: " - "~n Rn: ~p" - "~n Loc: ~p", [Rn, Loc]), + {'EXIT', Runner, Reason} -> + ?EPRINT2("TC runner start failed: " + "~n ~p~n", [Reason]), + exit({tx_runner_start_failed, Reason}); + {tc_runner_started, Runner} -> + ?PRINT2("TC runner start acknowledged~n"), + ok + after 10000 -> %% We should *really* not have to wait this long, but... + trap_exit(OldFlag), + unlink_and_flush_exit(Runner), + RunnerInfo = process_info(Runner), + ?EPRINT2("TC runner start timeout: " + "~n ~p", [RunnerInfo]), + %% If we don't get a start ack within 10 seconds, we are f*ed + exit(Runner, kill), + exit({tc_runner_start, timeout, RunnerInfo}) + end. + +await_tc_runner_done(Runner, OldFlag) -> + receive + {'EXIT', Runner, Reason} -> + %% This is not a normal (tc) failure (that is the clause below). + %% Instead the tc runner process crashed, for some reason. So + %% check if have got any system events, and if so, skip. + SysEvs = snmp_test_global_sys_monitor:events(), + if + (SysEvs =:= []) -> + ?EPRINT2("TC runner failed: " + "~n ~p~n", [Reason]), + exit({tx_runner_failed, Reason}); + true -> + ?EPRINT2("TC runner failed when we got system events: " + "~n Reason: ~p" + "~n Sys Events: ~p" + "~n", [Reason, SysEvs]), + skip([{reason, Reason}, {system_events, SysEvs}]) + end; + {tc_runner_done, Runner, {'EXIT', {skip, Reason}}, Loc} -> + ?PRINT2("call -> done with skip: " + "~n Reason: ~p" + "~n Loc: ~p" + "~n", [Reason, Loc]), + trap_exit(OldFlag), + unlink_and_flush_exit(Runner), + put(test_server_loc, Loc), + skip(Reason); + {tc_runner_done, Runner, {'EXIT', Rn}, Loc} -> + ?PRINT2("call -> done with exit: " + "~n Rn: ~p" + "~n Loc: ~p" + "~n", [Rn, Loc]), + trap_exit(OldFlag), + unlink_and_flush_exit(Runner), put(test_server_loc, Loc), exit(Rn); - {done, Ret, _Zed} -> + {tc_runner_done, Runner, Ret, _Zed} -> ?DBG("call -> done:" "~n Ret: ~p" "~n Zed: ~p", [Ret, _Zed]), + trap_exit(OldFlag), + unlink_and_flush_exit(Runner), case Ret of {error, Reason} -> exit(Reason); + {skip, Reason} -> + skip(Reason); OK -> OK end end. -wait(From, Env, M, F, A) -> - ?DBG("wait -> entry with" - "~n From: ~p" - "~n Env: ~p" - "~n M: ~p" - "~n F: ~p" - "~n A: ~p", [From, Env, M, F, A]), +trap_exit(Flag) when is_boolean(Flag) -> + erlang:process_flag(trap_exit, Flag). + +unlink_and_flush_exit(Pid) -> + unlink(Pid), + receive + {'EXIT', Pid, _} -> + ok + after 0 -> + ok + end. + +tc_wait(From, Env, M, F, A) -> + ?PRINT2("tc_wait -> entry with" + "~n From: ~p" + "~n Env: ~p" + "~n M: ~p" + "~n F: ~p" + "~n A: ~p", [From, Env, M, F, A]), + From ! {tc_runner_started, self()}, lists:foreach(fun({K,V}) -> put(K,V) end, Env), - Rn = (catch apply(M, F, A)), - ?DBG("wait -> Rn: ~n~p", [Rn]), - From ! {done, Rn, get(test_server_loc)}, - exit(Rn). - -run(Mod, Func, Args, Opts) -> - ?DBG("run -> entry with" - "~n Mod: ~p" - "~n Func: ~p" - "~n Args: ~p" - "~n Opts: ~p", [Mod, Func, Args, Opts]), - M = get(mib_dir), - Dir = get(mgr_dir), - User = snmp_misc:get_option(user, Opts, "all-rights"), - SecLevel = snmp_misc:get_option(sec_level, Opts, noAuthNoPriv), - EngineID = snmp_misc:get_option(engine_id, Opts, "agentEngine"), + ?PRINT2("tc_wait -> env set - now run tc~n"), + Res = (catch apply(M, F, A)), + ?PRINT2("tc_wait -> tc run done: " + "~n ~p" + "~n", [Res]), + From ! {tc_runner_done, self(), Res, get(test_server_loc)}, + %% The point of this is that in some cases we have seen that the + %% exit signal having been "passed on" to the CT, which consider any + %% exit a fail (even if its {'EXIT', ok}). + %% So, just to be on the safe side, convert an 'ok' to a 'normal'. + case Res of + ok -> + exit(normal); + {ok, _} -> + exit(normal); + _ -> + exit(Res) + end. + +tc_run(Mod, Func, Args, Opts) -> + ?PRINT2("tc_run -> entry with" + "~n Mod: ~p" + "~n Func: ~p" + "~n Args: ~p" + "~n Opts: ~p" + "~n", [Mod, Func, Args, Opts]), + (catch snmp_test_mgr:stop()), % If we had a running mgr from a failed case + M = get(mib_dir), + Dir = get(mgr_dir), + User = snmp_misc:get_option(user, Opts, "all-rights"), + SecLevel = snmp_misc:get_option(sec_level, Opts, noAuthNoPriv), + EngineID = snmp_misc:get_option(engine_id, Opts, "agentEngine"), CtxEngineID = snmp_misc:get_option(context_engine_id, Opts, EngineID), - Community = snmp_misc:get_option(community, Opts, "all-rights"), - ?DBG("run -> start crypto app",[]), - _CryptoRes = ?CRYPTO_START(), - ?DBG("run -> Crypto: ~p", [_CryptoRes]), - catch snmp_test_mgr:stop(), % If we had a running mgr from a failed case - StdM = join(code:priv_dir(snmp), "mibs") ++ "/", - Vsn = get(vsn), - ?DBG("run -> config:" - "~n M: ~p" - "~n Vsn: ~p" - "~n Dir: ~p" - "~n User: ~p" - "~n SecLevel: ~p" - "~n EngineID: ~p" - "~n CtxEngineID: ~p" - "~n Community: ~p" - "~n StdM: ~p", - [M,Vsn,Dir,User,SecLevel,EngineID,CtxEngineID,Community,StdM]), + Community = snmp_misc:get_option(community, Opts, "all-rights"), + ?DBG("tc_run -> start crypto app",[]), + _CryptoRes = ?CRYPTO_START(), + ?DBG("tc_run -> Crypto: ~p", [_CryptoRes]), + StdM = join(code:priv_dir(snmp), "mibs") ++ "/", + Vsn = get(vsn), + ?PRINT2("tc_run -> config:" + "~n M: ~p" + "~n Vsn: ~p" + "~n Dir: ~p" + "~n User: ~p" + "~n SecLevel: ~p" + "~n EngineID: ~p" + "~n CtxEngineID: ~p" + "~n Community: ~p" + "~n StdM: ~p" + "~n", [M,Vsn,Dir,User,SecLevel,EngineID,CtxEngineID,Community,StdM]), case snmp_test_mgr:start([%% {agent, snmp_test_lib:hostname()}, {packet_server_debug, true}, {debug, true}, @@ -376,24 +486,45 @@ run(Mod, Func, Args, Opts) -> {mibs, mibs(StdM, M)}]) of {ok, _Pid} -> case (catch apply(Mod, Func, Args)) of + {'EXIT', {skip, Reason}} -> + ?EPRINT2("apply skip detected: " + "~n ~p", [Reason]), + (catch snmp_test_mgr:stop()), + ?SKIP(Reason); {'EXIT', Reason} -> - catch snmp_test_mgr:stop(), - ?FAIL({apply_failed, {Mod, Func, Args}, Reason}); + %% We have hosts (mostly *very* slooow VMs) that + %% can timeout anything. Since we are basically + %% testing communication, we therefor must check + %% for system events at every failure. Grrr! + SysEvs = snmp_test_global_sys_monitor:events(), + (catch snmp_test_mgr:stop()), + if + (SysEvs =:= []) -> + ?EPRINT2("TC runner failed: " + "~n ~p~n", [Reason]), + ?FAIL({apply_failed, {Mod, Func, Args}, Reason}); + true -> + ?EPRINT2("apply exit catched when we got system events: " + "~n Reason: ~p" + "~n Sys Events: ~p" + "~n", [Reason, SysEvs]), + ?SKIP([{reason, Reason}, {system_events, SysEvs}]) + end; Res -> - catch snmp_test_mgr:stop(), + (catch snmp_test_mgr:stop()), Res end; {error, Reason} -> ?EPRINT2("Failed starting (test) manager: " "~n ~p", [Reason]), - catch snmp_test_mgr:stop(), + (catch snmp_test_mgr:stop()), ?line ?FAIL({mgr_start_error, Reason}); Err -> ?EPRINT2("Failed starting (test) manager: " "~n ~p", [Err]), - catch snmp_test_mgr:stop(), + (catch snmp_test_mgr:stop()), ?line ?FAIL({mgr_start_failure, Err}) end. @@ -907,10 +1038,22 @@ expect2(Mod, Line, F) -> %% ---------------------------------------------------------------------- -get_timeout() -> - get_timeout(os:type()). +-define(BASE_REQ_TIMEOUT, 3500). -get_timeout(_) -> 3500. +get_timeout() -> + %% Try to figure out how "fast" a machine is. + %% We assume that the number of schedulers + %% (which depends on the number of core:s) + %% effect the performance of the host... + %% This is obviously not enough. The network + %% also matterns, clock freq or the CPU, ... + %% But its better than what we had before... + case erlang:system_info(schedulers) of + N when is_integer(N) -> + ?BASE_REQ_TIMEOUT + timer:seconds(10 div N); + _ -> + ?BASE_REQ_TIMEOUT + end. receive_pdu(To) -> receive @@ -1083,6 +1226,18 @@ do_expect(trap, Enterp, Generic, Specific, ExpVBs, To) -> {PureE, Generic, Specific, ExpVBs}, {Ent2, G2, Spec2, VBs}}}; + {error, timeout} = Error -> + SysEvs = snmp_test_global_sys_monitor:events(), + io_format_expect("[expecting trap] got timeout when system events:" + "~n ~p", [SysEvs]), + if + (SysEvs =:= []) -> + Error; + true -> + skip({system_events, SysEvs}) + end; + + Error -> Error end. @@ -1184,7 +1339,7 @@ do_expect2(Check, Type, Err, Idx, ExpVBs, To) io_format_expect("received unexpected pdu with (11) " "~n Type: ~p" "~n ReqId: ~p" - "~n Errot status: ~p" + "~n Error status: ~p" "~n Error index: ~p", [Type2, ReqId, Err2, Idx2]), {error, @@ -1247,7 +1402,7 @@ do_expect2(Check, Type, Err, Idx, ExpVBs, To) io_format_expect("received unexpected pdu with (15) " "~n Type: ~p" "~n ReqId: ~p" - "~n Errot status: ~p" + "~n Error status: ~p" "~n Error index: ~p" "~n Varbinds: ~p", [Type2, ReqId, Err2, Idx2, VBs2]), @@ -1257,10 +1412,23 @@ do_expect2(Check, Type, Err, Idx, ExpVBs, To) {Type2, Err2, Idx2, VBs2}, ReqId}}; - Error -> - io_format_expect("received error (16): " + + {error, timeout} = Error -> + SysEvs = snmp_test_global_sys_monitor:events(), + io_format_expect("got timeout (16) when system events:" + "~n ~p", [SysEvs]), + if + (SysEvs =:= []) -> + Error; + true -> + skip({system_events, SysEvs}) + end; + + + Error -> + io_format_expect("received error (17): " "~n Error: ~p", [Error]), - Error + Error end. @@ -1378,12 +1546,15 @@ start_node(Name) -> "" end, %% Do not use start_link!!! (the proc that calls this one is tmp) - ?DBG("start_node -> Args: ~p~n",[Args]), - A = Args ++ " -pa " ++ Pa, + ?DBG("start_node -> Args: ~p~n", [Args]), + A = Args ++ " -pa " ++ Pa ++ + " -s " ++ atom_to_list(snmp_test_sys_monitor) ++ " start" ++ + " -s global sync", case (catch ?START_NODE(Name, A)) of {ok, Node} -> %% Tell the test_server to not clean up things it never started. ?DBG("start_node -> Node: ~p",[Node]), + global:sync(), {ok, Node}; Else -> ?ERR("start_node -> failed with(other): Else: ~p",[Else]), @@ -1701,6 +1872,10 @@ rpc(Node, F, A) -> join(Dir, File) -> filename:join(Dir, File). + +skip(R) -> + exit({skip, R}). + %% await_pdu(To) -> %% await_response(To, pdu). %% diff --git a/lib/snmp/test/snmp_manager_config_test.erl b/lib/snmp/test/snmp_manager_config_test.erl index 64d3134055..ccbdd77629 100644 --- a/lib/snmp/test/snmp_manager_config_test.erl +++ b/lib/snmp/test/snmp_manager_config_test.erl @@ -1,7 +1,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. @@ -35,6 +35,7 @@ -include_lib("common_test/include/ct.hrl"). -include("snmp_test_lib.hrl"). -include_lib("snmp/src/manager/snmpm_usm.hrl"). +-include_lib("snmp/src/app/snmp_internal.hrl"). %%---------------------------------------------------------------------- @@ -2259,11 +2260,13 @@ create_and_increment(Conf) when is_list(Conf) -> ?line {ok, _Pid} = snmpm_config:start_link(Opts), %% Random init - random:seed(erlang:phash2([node()]), - erlang:monotonic_time(), - erlang:unique_integer()), + ?SNMP_RAND_SEED(), + %% rand:seed(exrop, + %% {erlang:phash2([node()]), + %% erlang:monotonic_time(), + %% erlang:unique_integer()}), - StartVal = random:uniform(2147483647), + StartVal = rand:uniform(2147483647), IncVal = 42, EndVal = StartVal + IncVal, diff --git a/lib/snmp/test/snmp_manager_test.erl b/lib/snmp/test/snmp_manager_test.erl index 5b0ebf8647..c31bb92e1f 100644 --- a/lib/snmp/test/snmp_manager_test.erl +++ b/lib/snmp/test/snmp_manager_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2017. All Rights Reserved. +%% Copyright Ericsson AB 2003-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. @@ -62,7 +62,7 @@ register_user1/1, - register_agent1/1, + register_agent_old/1, register_agent2/1, register_agent3/1, @@ -185,7 +185,6 @@ end_per_suite(Config) when is_list(Config) -> init_per_testcase(Case, Config) when is_list(Config) -> - io:format(user, "~n~n*** INIT ~w:~w ***~n~n", [?MODULE, Case]), p(Case, "init_per_testcase begin when" "~n Nodes: ~p~n~n", [erlang:nodes()]), %% This version of the API, based on Addr and Port, has been deprecated @@ -204,13 +203,15 @@ init_per_testcase(Case, Config) when is_list(Config) -> Result = case lists:member(Case, DeprecatedApiCases) of true -> - {skip, api_no_longer_supported}; + {skip, "API no longer supported"}; false -> try init_per_testcase2(Case, Config) catch - C:{skip, _} = E:_ when ((C =:= throw) orelse (C =:= exit)) -> + C:{skip, _} = E:_ when ((C =:= throw) orelse + (C =:= exit)) -> E; - C:E:_ when ((C =:= throw) orelse (C =:= exit)) -> + C:E:_ when ((C =:= throw) orelse + (C =:= exit)) -> {skip, {catched, C, E}} end end, @@ -479,7 +480,7 @@ groups() -> }, {agent_tests, [], [ - register_agent1, + register_agent_old, register_agent2, register_agent3 ] @@ -595,7 +596,7 @@ groups() -> ipv6_tests() -> [ - register_agent1, + register_agent_old, simple_sync_get_next3, simple_async_get2, simple_sync_get3, @@ -619,38 +620,47 @@ init_per_group(event_tests_mt = GroupName, Config) -> GroupName, [{manager_net_if_module, snmpm_net_if_mt} | Config]); init_per_group(ipv6_mt = GroupName, Config) -> - {ok, Hostname0} = inet:gethostname(), - case ct:require(ipv6_hosts) of - ok -> - case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts)) of - true -> - ipv6_init( - snmp_test_lib:init_group_top_dir( - GroupName, - [{manager_net_if_module, snmpm_net_if_mt} - | Config])); - false -> - {skip, "Host does not support IPv6"} - end; - _ -> - {skip, "Test config ipv6_hosts is missing"} - end; + init_per_group_ipv6(GroupName, + [{manager_net_if_module, snmpm_net_if_mt} | Config]); init_per_group(ipv6 = GroupName, Config) -> - {ok, Hostname0} = inet:gethostname(), - case ct:require(ipv6_hosts) of - ok -> - case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts)) of - true -> - ipv6_init(snmp_test_lib:init_group_top_dir(GroupName, Config)); - false -> - {skip, "Host does not support IPv6"} - end; - _ -> - {skip, "Test config ipv6_hosts is missing"} - end; + init_per_group_ipv6(GroupName, Config); init_per_group(GroupName, Config) -> snmp_test_lib:init_group_top_dir(GroupName, Config). + +init_per_group_ipv6(GroupName, Config) -> + %% <OS-CONDITIONAL-SKIP> + OSSkipable = [{unix, + [ + {darwin, fun(V) when (V > {9, 8, 0}) -> + %% This version is OK: No Skip + false; + (_) -> + %% This version is *not* ok: Skip + %% We need a fully qualified hostname + %% to get a proper IPv6 address (in this + %% version), but its just to messy, so + %% instead we skip this **OLD** darwin... + true + end} + ] + }], + %% </OS-CONDITIONAL-SKIP> + case ?OS_BASED_SKIP(OSSkipable) of + true -> + {skip, "Host *may* not *properly* support IPV6"}; + false -> + %% Even if this host supports IPv6 we don't use it unless its + %% one of the configures/supported IPv6 hosts... + case (?HAS_SUPPORT_IPV6() andalso ?IS_IPV6_HOST()) of + true -> + ipv6_init(snmp_test_lib:init_group_top_dir(GroupName, Config)); + false -> + {skip, "Host does not support IPv6"} + end + end. + + end_per_group(_GroupName, Config) -> %% Do we really need to do this? lists:keydelete(snmp_group_top_dir, 1, Config). @@ -662,11 +672,11 @@ end_per_group(_GroupName, Config) -> simple_start_and_stop1(suite) -> []; simple_start_and_stop1(Config) when is_list(Config) -> - %% ?SKIP(not_yet_implemented), - process_flag(trap_exit, true), - put(tname,ssas1), - p("starting with Config: ~n~p", [Config]), + ?TC_TRY(simple_start_and_stop1, + fun() -> do_simple_start_and_stop1(Config) end). +do_simple_start_and_stop1(Config) -> + p("starting with Config: ~n~p", [Config]), ConfDir = ?config(manager_conf_dir, Config), DbDir = ?config(manager_db_dir, Config), @@ -687,7 +697,6 @@ simple_start_and_stop1(Config) when is_list(Config) -> ?SLEEP(1000), - p("end"), ok. @@ -695,9 +704,10 @@ simple_start_and_stop1(Config) when is_list(Config) -> simple_start_and_stop2(suite) -> []; simple_start_and_stop2(Config) when is_list(Config) -> - %% ?SKIP(not_yet_implemented), - process_flag(trap_exit, true), - put(tname,ssas2), + ?TC_TRY(simple_start_and_stop2, + fun() -> do_simple_start_and_stop2(Config) end). + +do_simple_start_and_stop2(Config) -> p("starting with Config: ~p~n", [Config]), ManagerNode = start_manager_node(), @@ -735,7 +745,6 @@ simple_start_and_stop2(Config) when is_list(Config) -> ?SLEEP(1000), - p("end"), ok. @@ -743,8 +752,10 @@ simple_start_and_stop2(Config) when is_list(Config) -> simple_start_and_monitor_crash1(suite) -> []; simple_start_and_monitor_crash1(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,ssamc1), + ?TC_TRY(simple_start_and_monitor_crash1, + fun() -> do_simple_start_and_monitor_crash1(Config) end). + +do_simple_start_and_monitor_crash1(Config) -> p("starting with Config: ~n~p", [Config]), ConfDir = ?config(manager_conf_dir, Config), @@ -788,7 +799,6 @@ simple_start_and_monitor_crash1(Config) when is_list(Config) -> ?FAIL(timeout) end, - p("end"), ok. @@ -796,8 +806,10 @@ simple_start_and_monitor_crash1(Config) when is_list(Config) -> simple_start_and_monitor_crash2(suite) -> []; simple_start_and_monitor_crash2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,ssamc2), + ?TC_TRY(simple_start_and_monitor_crash2, + fun() -> do_simple_start_and_monitor_crash2(Config) end). + +do_simple_start_and_monitor_crash2(Config) -> p("starting with Config: ~n~p", [Config]), ConfDir = ?config(manager_conf_dir, Config), @@ -842,7 +854,6 @@ simple_start_and_monitor_crash2(Config) when is_list(Config) -> ?FAIL(timeout) end, - p("end"), ok. @@ -888,8 +899,10 @@ simulate_crash(NumKills, _) -> notify_started01(suite) -> []; notify_started01(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,ns01), + ?TC_TRY(notify_started01, + fun() -> do_notify_started01(Config) end). + +do_notify_started01(Config) -> p("starting with Config: ~n~p", [Config]), ConfDir = ?config(manager_conf_dir, Config), @@ -976,37 +989,34 @@ snmpm_starter(Opts, To) -> notify_started02(suite) -> []; notify_started02(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,ns02), + ?TC_TRY(notify_started02, + fun() -> notify_started02_cond(Config) end, + fun() -> do_notify_started02(Config) end). - %% <CONDITIONAL-SKIP> - %% The point of this is to catch machines running - %% SLES9 (2.6.5) +notify_started02_cond(Config) -> LinuxVersionVerify = fun() -> case os:cmd("uname -m") of "i686" ++ _ -> -%% io:format("found an i686 machine, " -%% "now check version~n", []), case os:version() of {2, 6, Rev} when Rev >= 16 -> - true; + false; {2, Min, _} when Min > 6 -> - true; + false; {Maj, _, _} when Maj > 2 -> - true; + false; _ -> - false + true end; _ -> - true + false end end, Skippable = [{unix, [{linux, LinuxVersionVerify}]}], Condition = fun() -> ?OS_BASED_SKIP(Skippable) end, - ?NON_PC_TC_MAYBE_SKIP(Config, Condition), - %% </CONDITIONAL-SKIP> - + ?NON_PC_TC_MAYBE_SKIP(Config, Condition). + +do_notify_started02(Config) -> p("starting with Config: ~n~p", [Config]), ConfDir = ?config(manager_conf_dir, Config), @@ -1014,10 +1024,10 @@ notify_started02(Config) when is_list(Config) -> write_manager_conf(ConfDir), - Opts = [{server, [{verbosity, log}]}, - {net_if, [{verbosity, silence}]}, + Opts = [{server, [{verbosity, log}]}, + {net_if, [{verbosity, silence}]}, {note_store, [{verbosity, silence}]}, - {config, [{verbosity, log}, {dir, ConfDir}, {db_dir, DbDir}]}], + {config, [{verbosity, debug}, {dir, ConfDir}, {db_dir, DbDir}]}], p("start snmpm client process"), NumIterations = 5, @@ -1047,8 +1057,14 @@ notify_started02(Config) when is_list(Config) -> p("await snmpm client process exit (max ~p+10000 msec)", [ApproxStartTime]), receive + %% We take this opportunity to check if we got a skip from + %% the ctrl process. + {'EXIT', Pid2, {skip, SkipReason1}} -> + ?SKIP(SkipReason1); {'EXIT', Pid1, normal} -> ok; + {'EXIT', Pid1, {suite_failed, Reason1}} -> + ?FAIL({client, Reason1}); {'EXIT', Pid1, Reason1} -> ?FAIL({client, Reason1}) after ApproxStartTime + 10000 -> @@ -1061,6 +1077,9 @@ notify_started02(Config) when is_list(Config) -> receive {'EXIT', Pid2, normal} -> ok; + {'EXIT', Pid2, {skip, SkipReason2}} -> + %% In case of a race + ?SKIP(SkipReason2); {'EXIT', Pid2, Reason2} -> ?FAIL({ctrl, Reason2}) after 5000 -> @@ -1085,7 +1104,7 @@ ns02_client_await_approx_runtime(Pid) -> "~n ~p", [Pid, Reason]), {error, Reason} - after 15000 -> + after 30000 -> %% Either something is *really* wrong or this machine %% is dog slow. Either way, this is a skip-reason... {skip, approx_runtime_timeout} @@ -1150,6 +1169,12 @@ ns02_ctrl(Opts, N) -> p("starting"), ns02_ctrl_loop(Opts, N). + +%% We have seen that some times it takes unreasonably long time to +%% start the manager (it got "stuck" in snmpm_config). But since +%% we did not have enough verbosity, we do not know how far it got. +%% So, we try to monitor each start attempt. We allow 5 sec (just +%% to give slow boxes a chance). ns02_ctrl_loop(_Opts, 0) -> p("done"), exit(normal); @@ -1157,19 +1182,45 @@ ns02_ctrl_loop(Opts, N) -> p("entry when N: ~p", [N]), ?SLEEP(2000), p("start manager"), - snmpm:start(Opts), + TS1 = erlang:system_time(millisecond), + {StarterPid, StarterMRef} = + erlang:spawn_monitor(fun() -> exit(snmpm:start(Opts)) end), + receive + {'DOWN', StarterMRef, process, StarterPid, ok} -> + TS2 = erlang:system_time(millisecond), + p("manager started: ~w ms", [TS2-TS1]), + ok + after 5000 -> + p("manager (~p) start timeout - kill", [StarterPid]), + exit(StarterPid, kill), + exit({skip, start_timeout}) + end, ?SLEEP(2000), p("stop manager"), - snmpm:stop(), + ?SLEEP(100), % Give the verbosity to take effect... + TS3 = erlang:system_time(millisecond), + case snmpm:stop(5000) of + ok -> + TS4 = erlang:system_time(millisecond), + p("manager stopped: ~p ms", [TS4-TS3]), + ok; + {error, timeout} -> + p("manager stop timeout - kill (cleanup) and skip"), + exit(whereis(snmpm_supervisor), kill), + exit({skip, stop_timeout}) + end, ns02_ctrl_loop(Opts, N-1). + %%====================================================================== info(suite) -> []; info(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,info), + ?TC_TRY(info, + fun() -> do_info(Config) end). + +do_info(Config) -> p("starting with Config: ~n~p", [Config]), ConfDir = ?config(manager_conf_dir, Config), @@ -1197,7 +1248,6 @@ info(Config) when is_list(Config) -> ?SLEEP(1000), - p("end"), ok. verify_info(Info) when is_list(Info) -> @@ -1237,9 +1287,10 @@ verify_info([{Key, SubKeys}|Keys], Info) -> register_user1(suite) -> []; register_user1(Config) when is_list(Config) -> - %% ?SKIP(not_yet_implemented). - process_flag(trap_exit, true), - put(tname,ru1), + ?TC_TRY(register_user1, + fun() -> do_register_user1(Config) end). + +do_register_user1(Config) -> p("starting with Config: ~p~n", [Config]), ManagerNode = start_manager_node(), @@ -1313,7 +1364,6 @@ register_user1(Config) when is_list(Config) -> ?SLEEP(1000), - p("end"), ok. verify_users([], []) -> @@ -1331,14 +1381,15 @@ verify_users(ActualUsers0, [User|RegUsers]) -> %%====================================================================== -register_agent1(doc) -> +register_agent_old(doc) -> ["Test registration of agents with the OLD interface functions"]; -register_agent1(suite) -> +register_agent_old(suite) -> []; -register_agent1(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,ra1), - +register_agent_old(Config) when is_list(Config) -> + ?TC_TRY(register_agent_old, + fun() -> do_register_agent_old(Config) end). + +do_register_agent_old(Config) -> p("starting with Config: ~p~n", [Config]), ManagerNode = start_manager_node(), @@ -1453,7 +1504,6 @@ register_agent1(Config) when is_list(Config) -> ?SLEEP(1000), - p("end"), ok. @@ -1464,8 +1514,10 @@ register_agent2(doc) -> register_agent2(suite) -> []; register_agent2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ra2), + ?TC_TRY(register_agent2, + fun() -> do_register_agent2(Config) end). + +do_register_agent2(Config) -> p("starting with Config: ~p~n", [Config]), ManagerNode = start_manager_node(), @@ -1598,7 +1650,6 @@ register_agent2(Config) when is_list(Config) -> ?SLEEP(1000), - p("end"), ok. @@ -1610,8 +1661,10 @@ register_agent3(doc) -> register_agent3(suite) -> []; register_agent3(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ra3), + ?TC_TRY(register_agent3, + fun() -> do_register_agent3(Config) end). + +do_register_agent3(Config) -> p("starting with Config: ~p~n", [Config]), ManagerNode = start_manager_node(), @@ -1748,7 +1801,6 @@ register_agent3(Config) when is_list(Config) -> ?SLEEP(1000), - p("end"), ok. @@ -1757,10 +1809,11 @@ register_agent3(Config) when is_list(Config) -> simple_sync_get1(doc) -> ["Simple sync get-request - Old style (Addr & Port)"]; simple_sync_get1(suite) -> []; simple_sync_get1(Config) when is_list(Config) -> - ?SKIP(api_no_longer_supported), + ?TC_TRY(simple_sync_get1, + fun() -> {skip, "API no longer supported"} end, + fun() -> do_simple_sync_get1(Config) end). - process_flag(trap_exit, true), - put(tname, ssg1), +do_simple_sync_get1(Config) -> p("starting with Config: ~p~n", [Config]), Node = ?config(manager_node, Config), @@ -1819,18 +1872,18 @@ simple_sync_get2(doc) -> ["Simple sync get-request - Version 2 API (TargetName)"]; simple_sync_get2(suite) -> []; simple_sync_get2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ssg2), - do_simple_sync_get2(Config), - display_log(Config), - ok. + ?TC_TRY(simple_sync_get2, + fun() -> do_simple_sync_get2(Config) end). do_simple_sync_get2(Config) -> + p("starting with Config: ~n~p", [Config]), Get = fun(Node, TargetName, Oids) -> mgr_user_sync_get(Node, TargetName, Oids) end, PostVerify = fun() -> ok end, - do_simple_sync_get2(Config, Get, PostVerify). + do_simple_sync_get2(Config, Get, PostVerify), + display_log(Config), + ok. do_simple_sync_get2(Config, Get, PostVerify) -> p("starting with Config: ~p~n", [Config]), @@ -1886,13 +1939,11 @@ simple_sync_get3(doc) -> ["Simple sync get-request - Version 3 API (TargetName and send-opts)"]; simple_sync_get3(suite) -> []; simple_sync_get3(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ssg3), - do_simple_sync_get3(Config), - display_log(Config), - ok. + ?TC_TRY(simple_sync_get3, + fun() -> do_simple_sync_get3(Config) end). do_simple_sync_get3(Config) -> + p("starting with Config: ~n~p", [Config]), Self = self(), Msg = simple_sync_get3, Fun = fun() -> Self ! Msg end, @@ -1911,7 +1962,9 @@ do_simple_sync_get3(Config) -> ok end end, - do_simple_sync_get2(Config, Get, PostVerify). + do_simple_sync_get2(Config, Get, PostVerify), + display_log(Config), + ok. %%====================================================================== @@ -1920,10 +1973,11 @@ simple_async_get1(doc) -> ["Simple (async) get-request - Old style (Addr & Port)"]; simple_async_get1(suite) -> []; simple_async_get1(Config) when is_list(Config) -> - ?SKIP(api_no_longer_supported), + ?TC_TRY(simple_async_get1, + fun() -> {skip, "API no longer supported"} end, + fun() -> do_simple_async_get1(Config) end). - process_flag(trap_exit, true), - put(tname, sag1), +do_simple_async_get1(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -2024,8 +2078,10 @@ simple_async_get2(doc) -> ["Simple (async) get-request - Version 2 API (TargetName)"]; simple_async_get2(suite) -> []; simple_async_get2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, sag2), + ?TC_TRY(simple_async_get2, + fun() -> do_simple_async_get2(Config) end). + +do_simple_async_get2(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), AgentNode = ?config(agent_node, Config), @@ -2105,8 +2161,10 @@ simple_async_get3(doc) -> ["Simple (async) get-request - Version 3 API (TargetName and send-opts)"]; simple_async_get3(suite) -> []; simple_async_get3(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, sag3), + ?TC_TRY(simple_async_get3, + fun() -> do_simple_async_get3(Config) end). + +do_simple_async_get3(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), AgentNode = ?config(agent_node, Config), @@ -2137,10 +2195,11 @@ simple_sync_get_next1(doc) -> ["Simple (sync) get_next-request - " "Old style (Addr & Port)"]; simple_sync_get_next1(suite) -> []; simple_sync_get_next1(Config) when is_list(Config) -> - ?SKIP(api_no_longer_supported), + ?TC_TRY(simple_sync_get_next1, + fun() -> {skip, "API no longer supported"} end, + fun() -> do_simple_sync_get_next1(Config) end). - process_flag(trap_exit, true), - put(tname, ssgn1), +do_simple_sync_get_next1(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -2278,8 +2337,10 @@ simple_sync_get_next2(doc) -> ["Simple (sync) get_next-request - Version 2 API (TargetName)"]; simple_sync_get_next2(suite) -> []; simple_sync_get_next2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ssgn2), + ?TC_TRY(simple_sync_get_next2, + fun() -> do_simple_sync_get_next2(Config) end). + +do_simple_sync_get_next2(Config) -> p("starting with Config: ~p~n", [Config]), GetNext = fun(Node, TargetName, Oids) -> @@ -2431,10 +2492,11 @@ simple_async_get_next1(doc) -> ["Simple (async) get_next-request - " "Old style (Addr & Port)"]; simple_async_get_next1(suite) -> []; simple_async_get_next1(Config) when is_list(Config) -> - ?SKIP(api_no_longer_supported), + ?TC_TRY(simple_async_get_next1, + fun() -> {skip, "API no longer supported"} end, + fun() -> do_simple_async_get_next1(Config) end). - process_flag(trap_exit, true), - put(tname, ssgn1), +do_simple_async_get_next1(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -2531,8 +2593,10 @@ simple_async_get_next2(doc) -> ["Simple (async) get_next-request - Version 2 API (TargetName)"]; simple_async_get_next2(suite) -> []; simple_async_get_next2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ssgn2), + ?TC_TRY(simple_async_get_next2, + fun() -> do_simple_async_get_next2(Config) end). + +do_simple_async_get_next2(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -2642,8 +2706,10 @@ simple_async_get_next3(doc) -> "Version 3 API (TargetName with send-opts)"]; simple_async_get_next3(suite) -> []; simple_async_get_next3(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ssgn2), + ?TC_TRY(simple_async_get_next3, + fun() -> do_simple_async_get_next3(Config) end). + +do_simple_async_get_next3(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -2685,10 +2751,11 @@ simple_sync_set1(doc) -> ["Simple (sync) set-request - " "Old style (Addr & Port)"]; simple_sync_set1(suite) -> []; simple_sync_set1(Config) when is_list(Config) -> - ?SKIP(api_no_longer_supported), + ?TC_TRY(simple_sync_set1, + fun() -> {skip, "API no longer supported"} end, + fun() -> do_simple_sync_set1(Config) end). - process_flag(trap_exit, true), - put(tname, sss1), +do_simple_sync_set1(Config) -> p("starting with Config: ~p~n", [Config]), Node = ?config(manager_node, Config), @@ -2758,8 +2825,10 @@ simple_sync_set2(doc) -> ["Simple (sync) set-request - Version 2 API (TargetName)"]; simple_sync_set2(suite) -> []; simple_sync_set2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, sss2), + ?TC_TRY(simple_sync_set2, + fun() -> do_simple_sync_set2(Config) end). + +do_simple_sync_set2(Config) -> p("starting with Config: ~p~n", [Config]), Set = fun(Node, TargetName, VAVs) -> @@ -2828,8 +2897,10 @@ simple_sync_set3(doc) -> ["Simple (sync) set-request - Version 3 API (TargetName with send-opts)"]; simple_sync_set3(suite) -> []; simple_sync_set3(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, sss3), + ?TC_TRY(simple_sync_set3, + fun() -> do_simple_sync_set3(Config) end). + +do_simple_sync_set3(Config) -> p("starting with Config: ~p~n", [Config]), Self = self(), @@ -2857,10 +2928,11 @@ simple_async_set1(doc) -> ["Simple (async) set-request - " "Old style (Addr & Port)"]; simple_async_set1(suite) -> []; simple_async_set1(Config) when is_list(Config) -> - ?SKIP(api_no_longer_supported), + ?TC_TRY(simple_async_set1, + fun() -> {skip, "API no longer supported"} end, + fun() -> do_simple_async_set1(Config) end). - process_flag(trap_exit, true), - put(tname, sas1), +do_simple_async_set1(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -2952,8 +3024,10 @@ simple_async_set2(doc) -> ["Simple (async) set-request - Version 2 API (TargetName)"]; simple_async_set2(suite) -> []; simple_async_set2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, sas2), + ?TC_TRY(simple_async_set2, + fun() -> do_simple_async_set2(Config) end). + +do_simple_async_set2(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -3025,8 +3099,10 @@ simple_async_set3(doc) -> ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"]; simple_async_set3(suite) -> []; simple_async_set3(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, sas3), + ?TC_TRY(simple_async_set3, + fun() -> do_simple_async_set3(Config) end). + +do_simple_async_set3(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -3069,10 +3145,11 @@ simple_sync_get_bulk1(doc) -> ["Simple (sync) get_bulk-request - " "Old style (Addr & Port)"]; simple_sync_get_bulk1(suite) -> []; simple_sync_get_bulk1(Config) when is_list(Config) -> - ?SKIP(api_no_longer_supported), + ?TC_TRY(simple_sync_get_bulk1, + fun() -> {skip, "API no longer supported"} end, + fun() -> do_simple_sync_get_bulk1(Config) end). - process_flag(trap_exit, true), - put(tname, ssgb1), +do_simple_sync_get_bulk1(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -3243,8 +3320,10 @@ simple_sync_get_bulk2(doc) -> ["Simple (sync) get_bulk-request - Version 2 API (TargetName)"]; simple_sync_get_bulk2(suite) -> []; simple_sync_get_bulk2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ssgb2), + ?TC_TRY(simple_sync_get_bulk2, + fun() -> do_simple_sync_get_bulk2(Config) end). + +do_simple_sync_get_bulk2(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -3399,8 +3478,10 @@ simple_sync_get_bulk3(doc) -> "Version 3 API (TargetName with send-opts)"]; simple_sync_get_bulk3(suite) -> []; simple_sync_get_bulk3(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ssgb3), + ?TC_TRY(simple_sync_get_bulk3, + fun() -> do_simple_sync_get_bulk3(Config) end). + +do_simple_sync_get_bulk3(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -3436,10 +3517,11 @@ simple_async_get_bulk1(doc) -> ["Simple (async) get_bulk-request - " "Old style (Addr & Port)"]; simple_async_get_bulk1(suite) -> []; simple_async_get_bulk1(Config) when is_list(Config) -> - ?SKIP(api_no_longer_supported), + ?TC_TRY(simple_async_get_bulk1, + fun() -> {skip, "API no longer supported"} end, + fun() -> do_simple_async_get_bulk1(Config) end). - process_flag(trap_exit, true), - put(tname, sagb1), +do_simple_async_get_bulk1(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -3582,8 +3664,10 @@ simple_async_get_bulk2(doc) -> ["Simple (async) get_bulk-request - Version 2 API (TargetName)"]; simple_async_get_bulk2(suite) -> []; simple_async_get_bulk2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, sagb2), + ?TC_TRY(simple_async_get_bulk2, + fun() -> do_simple_async_get_bulk2(Config) end). + +do_simple_async_get_bulk2(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -3738,8 +3822,10 @@ simple_async_get_bulk3(doc) -> "Version 3 API (TargetName with send-opts)"]; simple_async_get_bulk3(suite) -> []; simple_async_get_bulk3(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, sagb3), + ?TC_TRY(simple_async_get_bulk3, + fun() -> do_simple_async_get_bulk3(Config) end). + +do_simple_async_get_bulk3(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -3782,10 +3868,11 @@ misc_async1(doc) -> ["Misc (async) request(s) - " "Old style (Addr & Port)"]; misc_async1(suite) -> []; misc_async1(Config) when is_list(Config) -> - ?SKIP(api_no_longer_supported), + ?TC_TRY(misc_async1, + fun() -> {skip, "API no longer supported"} end, + fun() -> do_misc_async1(Config) end). - process_flag(trap_exit, true), - put(tname, ms1), +do_misc_async1(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -3974,8 +4061,10 @@ misc_async2(doc) -> ["Misc (async) request(s) - Version 2 API (TargetName)"]; misc_async2(suite) -> []; misc_async2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ms2), + ?TC_TRY(misc_async2, + fun() -> do_misc_async2(Config) end). + +do_misc_async2(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -4224,8 +4313,10 @@ verify_trap(Trap, [{Id, Verifier}|Verifiers]) -> trap1(suite) -> []; trap1(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,t1), + ?TC_TRY(trap1, + fun() -> do_trap1(Config) end). + +do_trap1(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -4377,8 +4468,10 @@ trap1(Config) when is_list(Config) -> trap2(suite) -> []; trap2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,t2), + ?TC_TRY(trap2, + fun() -> do_trap2(Config) end). + +do_trap2(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -4570,8 +4663,10 @@ trap2(Config) when is_list(Config) -> inform1(suite) -> []; inform1(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,i1), + ?TC_TRY(inform1, + fun() -> do_inform1(Config) end). + +do_inform1(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -4697,8 +4792,10 @@ inform1(Config) when is_list(Config) -> inform2(suite) -> []; inform2(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, i2), + ?TC_TRY(inform2, + fun() -> do_inform2(Config) end). + +do_inform2(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -4832,7 +4929,7 @@ inform2(Config) when is_list(Config) -> "~n ~p", [Addr]), ok; {snmp_notification, inform2_tag1, {no_response, Addr}} -> - p("<ERROR> received expected \"no response\" " + e("Received unexpected \"no response\" " "notification from: " "~n ~p", [Addr]), {error, no_response} @@ -4869,8 +4966,10 @@ inform2(Config) when is_list(Config) -> inform3(suite) -> []; inform3(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,i3), + ?TC_TRY(inform3, + fun() -> do_inform3(Config) end). + +do_inform3(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -4966,7 +5065,7 @@ inform3(Config) when is_list(Config) -> "~n ~p", [Addr]), ok; {snmp_notification, inform3_tag1, {got_response, Addr}} -> - p("<ERROR> received unexpected \"got response\" " + e("Received unexpected \"got response\" " "notification from: " "~n ~p", [Addr]), @@ -5005,8 +5104,10 @@ inform3(Config) when is_list(Config) -> inform4(suite) -> []; inform4(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname,i4), + ?TC_TRY(inform4, + fun() -> do_inform4(Config) end). + +do_inform4(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -5125,8 +5226,10 @@ inform4(Config) when is_list(Config) -> inform_swarm(suite) -> []; inform_swarm(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, is), + ?TC_TRY(inform_swarm, + fun() -> do_inform_swarm(Config) end). + +do_inform_swarm(Config) -> p("starting with Config: ~p~n", [Config]), MgrNode = ?config(manager_node, Config), @@ -5253,7 +5356,7 @@ inform_swarm_collector(N, SentAckCnt, RecvCnt, RespCnt, Timeout) -> inform_swarm_collector(N, SentAckCnt, RecvCnt+1, RespCnt, Timeout); {Err, Idx, VBs} -> - p("<ERROR> unexpected error status: " + e("Unexpected error status: " "~n Err: ~p" "~n Idx: ~p" "~n VBs: ~p", [Err, Idx, VBs]), @@ -5272,7 +5375,7 @@ inform_swarm_collector(N, SentAckCnt, RecvCnt, RespCnt, Timeout) -> %% The agent did not received ack from the manager in time {snmp_notification, inform2_tag1, {no_response, Addr}} -> - p("<ERROR> received expected \"no response\" notification " + e("Received expected \"no response\" notification " "from: " "~n ~p", [Addr]), Reason = {no_response, Addr, {N, SentAckCnt, RecvCnt, RespCnt}}, @@ -5297,8 +5400,10 @@ report(Config) when is_list(Config) -> otp8015_1(doc) -> ["OTP-8015:1 - testing the new api-function."]; otp8015_1(suite) -> []; otp8015_1(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, otp8015_1), + ?TC_TRY(otp8015_1, + fun() -> do_otp8015_1(Config) end). + +do_otp8015_1(Config) -> p("starting with Config: ~p~n", [Config]), ConfDir = ?config(manager_conf_dir, Config), @@ -5345,8 +5450,10 @@ otp8015_1(Config) when is_list(Config) -> otp8395_1(doc) -> ["OTP-8395:1 - simple get with ATL sequence numbering."]; otp8395_1(suite) -> []; otp8395_1(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, otp8395_1), + ?TC_TRY(otp8395_1, + fun() -> do_otp8395_1(Config) end). + +do_otp8395_1(Config) -> do_simple_sync_get2(Config). @@ -5449,10 +5556,10 @@ command_handler([{No, Desc, Cmd}|Cmds]) -> p("command_handler -> ~w: ok",[No]), command_handler(Cmds); {error, Reason} -> - p("<ERROR> command_handler -> ~w error: ~n~p",[No, Reason]), + e("Command_handler -> ~w error: ~n~p",[No, Reason]), ?line ?FAIL({command_failed, No, Reason}); Error -> - p("<ERROR> command_handler -> ~w unexpected: ~n~p",[No, Error]), + e("Command_handler -> ~w unexpected: ~n~p",[No, Error]), ?line ?FAIL({unexpected_command_result, No, Error}) end. @@ -6318,6 +6425,8 @@ start_manager_node() -> start_node(snmp_manager). start_node(Name) -> + start_node(Name, true). +start_node(Name, Retry) -> Pa = filename:dirname(code:which(?MODULE)), Args = case init:get_argument('CC_TEST') of {ok, [[]]} -> @@ -6328,30 +6437,47 @@ start_node(Name) -> "" end, A = Args ++ " -pa " ++ Pa, - case (catch ?START_NODE(Name, A)) of + try ?START_NODE(Name, A) of {ok, Node} -> Node; - Else -> - ?line ?FAIL(Else) + {error, timeout} -> + e("Failed starting node ~p: timeout", [Name]), + ?line ?FAIL({error_starting_node, Name, timeout}); + {error, {already_running, Node}} when (Retry =:= true) -> + %% Ouch + %% Either we previously failed to (properly) stop the node + %% or it was a failed start, that reported failure (for instance + %% timeout) but actually succeeded. Regardless, we don't know + %% the state of this node, so (try) stop it and then (re-) try + %% start again. + e("Failed starting node ~p: Already Running - try stop", [Node]), + case ?STOP_NODE(Node) of + true -> + p("Successfully stopped old node ~p", [Node]), + start_node(Name, false); + false -> + e("Failed stop old node ~p", [Node]), + ?line ?FAIL({error_starting_node, Node, Retry, already_running}) + end; + {error, {already_running, Node}} -> + e("Failed starting node ~p: Already Running", [Node]), + ?line ?FAIL({error_starting_node, Node, Retry, already_running}); + {error, Reason} -> + e("Failed starting node ~p: ~p", [Name, Reason]), + ?line ?FAIL({error_starting_node, Name, Reason}) + catch + exit:{suite_failed, Reason} -> + e("(suite) Failed starting node ~p: ~p", [Name, Reason]), + ?line ?FAIL({failed_starting_node, Name, Reason}) end. -stop_node(Node) -> - rpc:cast(Node, erlang, halt, []), - await_stopped(Node, 5). -await_stopped(Node, 0) -> - p("await_stopped -> ~p still exist: giving up", [Node]), - ok; -await_stopped(Node, N) -> - Nodes = erlang:nodes(), - case lists:member(Node, Nodes) of - true -> - p("await_stopped -> ~p still exist: ~w", [Node, N]), - ?SLEEP(1000), - await_stopped(Node, N-1); - false -> - p("await_stopped -> ~p gone: ~w", [Node, N]), - ok +stop_node(Node) -> + case ?STOP_NODE(Node) of + true -> + ok; + false -> + ?line ?FAIL({failed_stop_node, Node}) end. @@ -6596,12 +6722,15 @@ rcall(Node, Mod, Func, Args) -> %% ------ +e(F, A) -> + p("<ERROR> " ++ F, A). + p(F) -> p(F, []). p(F, A) -> p(get(tname), F, A). - + p(TName, F, A) -> io:format("*** [~w][~s] ***" "~n " ++ F ++ "~n", [TName, formated_timestamp()|A]). diff --git a/lib/snmp/test/snmp_test_global_sys_monitor.erl b/lib/snmp/test/snmp_test_global_sys_monitor.erl new file mode 100644 index 0000000000..eafb96621a --- /dev/null +++ b/lib/snmp/test/snmp_test_global_sys_monitor.erl @@ -0,0 +1,214 @@ +%% +%% %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_test_global_sys_monitor). + +-export([start/0, stop/0, + reset_events/0, + events/0, + log/1]). +-export([init/1]). + +-define(NAME, ?MODULE). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start() -> + Parent = self(), + proc_lib:start(?MODULE, init, [Parent]). + +stop() -> + cast(stop). + +%% This does not reset the global counter but the "collector" +%% See events for more info. +reset_events() -> + cast(reset_events). + +events() -> + call(events). + +log(Event) -> + cast({node(), Event}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init(Parent) -> + process_flag(priority, high), + case global:register_name(?NAME, self()) of + yes -> + info_msg("Starting", []), + proc_lib:init_ack(Parent, {ok, self()}), + loop(#{parent => Parent, ev_cnt => 0, evs => []}); + no -> + warning_msg("Already started", []), + proc_lib:init_ack(Parent, {error, already_started}), + exit(normal) + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +loop(State) -> + receive + {?MODULE, stop} -> + warning_msg("Stopping with ~w events counted", + [maps:get(ev_cnt, State)]), + exit(normal); + + {?MODULE, reset_events} -> + loop(State#{evs => []}); + + {?MODULE, Ref, From, events} -> + Evs = maps:get(evs, State), + From ! {?MODULE, Ref, lists:reverse(Evs)}, + loop(State); + + {?MODULE, {Node, Event}} -> + State2 = process_event(State, Node, Event), + loop(State2); + + {nodedown = Event, Node} -> + State2 = process_event(State, Node, Event), + loop(State2); + + _ -> + loop(State) + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +process_event(State, Node, {Pid, TS, Tag, Info}) -> + process_system_event(State, Node, Pid, TS, Tag, Info); + +process_event(State, Node, {TS, starting}) -> + FTS = snmp_misc:format_timestamp(TS), + info_msg("System Monitor on node ~p starting at ~s", [Node, FTS]), + if + (Node =/= node()) -> + erlang:monitor_node(Node, true); + true -> + ok + end, + State; + +process_event(State, Node, {TS, already_started}) -> + FTS = snmp_misc:format_timestamp(TS), + info_msg("System Monitor on node ~p already started", [Node, FTS]), + State; + +process_event(State, Node, nodedown) -> + info_msg("Node ~p down", [Node]), + State; + +process_event(State, Node, Event) -> + warning_msg("Received unknown event from node ~p:" + "~n ~p", [Node, Event]), + State. + + +%% System Monitor events +%% We only *count* system events +process_system_event(#{ev_cnt := Cnt, evs := Evs} = State, + Node, Pid, TS, long_gc = Ev, Info) -> + print_system_event("Long GC", Node, Pid, TS, Info), + State#{ev_cnt => Cnt + 1, evs => [{Node, Ev} | Evs]}; +process_system_event(#{ev_cnt := Cnt, evs := Evs} = State, + Node, Pid, TS, long_schedule = Ev, Info) -> + print_system_event("Long Schedule", Node, Pid, TS, Info), + State#{ev_cnt => Cnt + 1, evs => [{Node, Ev} | Evs]}; +process_system_event(#{ev_cnt := Cnt, evs := Evs} = State, + Node, Pid, TS, large_heap = Ev, Info) -> + print_system_event("Large Heap", Node, Pid, TS, Info), + State#{ev_cnt => Cnt + 1, evs => [{Node, Ev} | Evs]}; +process_system_event(#{ev_cnt := Cnt, evs := Evs} = State, + Node, Pid, TS, busy_port = Ev, Info) -> + print_system_event("Busy port", Node, Pid, TS, Info), + State#{ev_cnt => Cnt + 1, evs => [{Node, Ev} | Evs]}; +process_system_event(#{ev_cnt := Cnt, evs := Evs} = State, + Node, Pid, TS, busy_dist_port = Ev, Info) -> + print_system_event("Busy dist port", Node, Pid, TS, Info), + State#{ev_cnt => Cnt + 1, evs => [{Node, Ev} | Evs]}; + +%% And everything else +process_system_event(State, Node, Pid, TS, Tag, Info) -> + Pre = f("Unknown Event '~p'", [Tag]), + print_system_event(Pre, Node, Pid, TS, Info), + State. + + +print_system_event(Pre, Node, Pid, TS, Info) -> + FTS = snmp_misc:format_timestamp(TS), + warning_msg("~s from ~p (~p) at ~s:" + "~n ~p", [Pre, Node, Pid, FTS, Info]). + +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +cast(Msg) -> + try global:send(?NAME, {?MODULE, Msg}) of + Pid when is_pid(Pid) -> + ok + catch + C:E:_ -> + {error, {catched, C, E}} + end. + +call(Req) -> + call(Req, infinity). + +call(Req, Timeout) -> + Ref = make_ref(), + try global:send(?NAME, {?MODULE, Ref, self(), Req}) of + Pid when is_pid(Pid) -> + receive + {?MODULE, Ref, Rep} -> + Rep + after Timeout -> + {error, timeout} + end + catch + C:E:_ -> + {error, {catched, C, E}} + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +info_msg(F, A) -> + error_logger:info_msg(format_msg(F, A), []). + +warning_msg(F, A) -> + error_logger:warning_msg(format_msg(F, A), []). + + +format_msg(F, A) -> + f("~n" ++ + "****** SNMP TEST GLOBAL SYSTEM MONITOR ******~n~n" ++ + F ++ + "~n~n", + A). + diff --git a/lib/snmp/test/snmp_test_lib.erl b/lib/snmp/test/snmp_test_lib.erl index a483690653..26b68501e2 100644 --- a/lib/snmp/test/snmp_test_lib.erl +++ b/lib/snmp/test/snmp_test_lib.erl @@ -23,9 +23,12 @@ -include_lib("kernel/include/file.hrl"). +-export([tc_try/2, tc_try/3]). -export([hostname/0, hostname/1, localhost/0, localhost/1, os_type/0, sz/1, display_suite_info/1]). --export([non_pc_tc_maybe_skip/4, os_based_skip/1]). +-export([non_pc_tc_maybe_skip/4, os_based_skip/1, + has_support_ipv6/0, has_support_ipv6/1, + is_ipv6_host/0, is_ipv6_host/1]). -export([fix_data_dir/1, init_suite_top_dir/2, init_group_top_dir/2, init_testcase_top_dir/2, lookup/2, @@ -34,17 +37,114 @@ -export([hours/1, minutes/1, seconds/1, sleep/1]). -export([flush_mqueue/0, trap_exit/0, trap_exit/1]). -export([ping/1, local_nodes/0, nodes_on/1]). --export([start_node/2]). +-export([start_node/2, stop_node/1]). -export([is_app_running/1, is_crypto_running/0, is_mnesia_running/0, is_snmp_running/0]). -export([crypto_start/0, crypto_support/0]). -export([watchdog/3, watchdog_start/1, watchdog_start/2, watchdog_stop/1]). -export([del_dir/1]). -export([cover/1]). --export([p/2, print1/2, print2/2, print/5, formated_timestamp/0]). +-export([f/2, p/2, print1/2, print2/2, print/5, formated_timestamp/0]). %% ---------------------------------------------------------------------- +%% Run test-case +%% + +%% *** tc_try/2,3 *** +%% Case: Basically the test case name +%% TCCondFun: A fun that is evaluated before the actual test case +%% The point of this is that it can performs checks to +%% see if we shall run the test case at all. +%% For instance, the test case may only work in specific +%% conditions. +%% FCFun: The test case fun +tc_try(Case, TCFun) -> + tc_try(Case, fun() -> ok end, TCFun). + +tc_try(Case, TCCondFun, TCFun) + when is_atom(Case) andalso + is_function(TCCondFun, 0) andalso + is_function(TCFun, 0) -> + tc_begin(Case), + try TCCondFun() of + ok -> + try + begin + TCFun(), + sleep(seconds(1)), + tc_end("ok") + end + catch + C:{skip, _} = SKIP when ((C =:= throw) orelse (C =:= exit)) -> + tc_end( f("skipping(catched,~w,tc)", [C]) ), + SKIP; + C:E:S -> + tc_end( f("failed(catched,~w,tc)", [C]) ), + erlang:raise(C, E, S) + end; + {skip, _} = SKIP -> + tc_end("skipping(tc)"), + SKIP; + {error, Reason} -> + tc_end("failed(tc)"), + exit({tc_cond_failed, Reason}) + catch + C:{skip, _} = SKIP when ((C =:= throw) orelse (C =:= exit)) -> + tc_end( f("skipping(catched,~w,cond)", [C]) ), + SKIP; + C:E:S -> + tc_end( f("failed(catched,~w,cond)", [C]) ), + erlang:raise(C, E, S) + end. + + +tc_set_name(N) when is_atom(N) -> + tc_set_name(atom_to_list(N)); +tc_set_name(N) when is_list(N) -> + put(tc_name, N). + +tc_get_name() -> + get(tc_name). + +tc_begin(TC) -> + OldVal = process_flag(trap_exit, true), + put(old_trap_exit, OldVal), + tc_set_name(TC), + tc_print("begin ***", + "~n----------------------------------------------------~n", ""). + +tc_end(Result) when is_list(Result) -> + OldVal = erase(old_trap_exit), + process_flag(trap_exit, OldVal), + tc_print("done: ~s", [Result], + "", "----------------------------------------------------~n~n"), + ok. + +tc_print(F, Before, After) -> + tc_print(F, [], Before, After). + +tc_print(F, A, Before, After) -> + Name = tc_which_name(), + FStr = f("*** [~s][~s][~p] " ++ F ++ "~n", + [formated_timestamp(),Name,self()|A]), + io:format(user, Before ++ FStr ++ After, []). + +tc_which_name() -> + case tc_get_name() of + undefined -> + case get(sname) of + undefined -> + ""; + SName when is_list(SName) -> + SName + end; + Name when is_list(Name) -> + Name + end. + + +%% ---------------------------------------------------------------------- %% Misc functions %% @@ -52,11 +152,12 @@ hostname() -> hostname(node()). hostname(Node) -> - from($@, atom_to_list(Node)). - -from(H, [H | T]) -> T; -from(H, [_ | T]) -> from(H, T); -from(_H, []) -> []. + case string:tokens(atom_to_list(Node), [$@]) of + [_, Host] -> + Host; + _ -> + [] + end. %% localhost() -> %% {ok, Ip} = snmp_misc:ip(net_adm:localhost()), @@ -78,7 +179,9 @@ localhost(Family) -> {error, Reason1} -> fail({getifaddrs, Reason1}, ?MODULE, ?LINE) end; - {ok, {0, _, _, _, _, _, _, _}} when (Family =:= inet6) -> + {ok, {A1, _, _, _, _, _, _, _}} when (Family =:= inet6) andalso + ((A1 =:= 0) orelse + (A1 =:= 16#fe80)) -> %% Ouch, we need to use something else case inet:getifaddrs() of {ok, IfList} -> @@ -197,63 +300,121 @@ non_pc_tc_maybe_skip(Config, Condition, File, Line) %% test-server... ok; _ -> - case Condition() of + try Condition() of true -> skip(non_pc_testcase, File, Line); false -> ok + catch + C:E:S -> + skip({condition, C, E, S}, File, Line) end end end. +%% The type and spec'ing is just to increase readability +-type os_family() :: win32 | unix. +-type os_name() :: atom(). +-type os_version() :: string() | {non_neg_integer(), + non_neg_integer(), + non_neg_integer()}. +-type os_skip_check() :: fun(() -> boolean()) | + fun((os_version()) -> boolean()). +-type skippable() :: any | [os_family() | + {os_family(), os_name() | + [os_name() | {os_name(), + os_skip_check()}]}]. + +-spec os_based_skip(skippable()) -> boolean(). + os_based_skip(any) -> - io:format("os_based_skip(any) -> entry" - "~n", []), true; os_based_skip(Skippable) when is_list(Skippable) -> - io:format("os_based_skip -> entry with" - "~n Skippable: ~p" - "~n", [Skippable]), - {OsFam, OsName} = - case os:type() of - {_Fam, _Name} = FamAndName -> - FamAndName; - Fam -> - {Fam, undefined} - end, - io:format("os_based_skip -> os-type: " - "~n OsFam: ~p" - "~n OsName: ~p" - "~n", [OsFam, OsName]), + os_base_skip(Skippable, os:type()); +os_based_skip(_Crap) -> + false. + +os_base_skip(Skippable, {OsFam, OsName}) -> + os_base_skip(Skippable, OsFam, OsName); +os_base_skip(Skippable, OsFam) -> + os_base_skip(Skippable, OsFam, undefined). + +os_base_skip(Skippable, OsFam, OsName) -> + %% Check if the entire family is to be skipped + %% Example: [win32, unix] case lists:member(OsFam, Skippable) of true -> true; false -> - case lists:keysearch(OsFam, 1, Skippable) of - {value, {OsFam, OsName}} -> - true; - {value, {OsFam, OsNames}} when is_list(OsNames) -> + %% Example: [{unix, freebsd}] | [{unix, [freebsd, darwin]}] + case lists:keysearch(OsFam, 1, Skippable) of + {value, {OsFam, OsName}} -> + true; + {value, {OsFam, OsNames}} when is_list(OsNames) -> + %% OsNames is a list of: + %% [atom()|{atom(), function/0 | function/1}] case lists:member(OsName, OsNames) of true -> true; false -> - case lists:keymember(OsName, 1, OsNames) of - {value, {OsName, Check}} when is_function(Check) -> - Check(); - _ -> - false - end + os_based_skip_check(OsName, OsNames) end; - _ -> - false - end - end; -os_based_skip(_Crap) -> - io:format("os_based_skip -> entry with" - "~n _Crap: ~p" - "~n", [_Crap]), - false. + _ -> + false + end + end. + +%% Performs a check via a provided fun with arity 0 or 1. +%% The argument is the result of os:version(). +os_based_skip_check(OsName, OsNames) -> + case lists:keysearch(OsName, 1, OsNames) of + {value, {OsName, Check}} when is_function(Check, 0) -> + Check(); + {value, {OsName, Check}} when is_function(Check, 1) -> + Check(os:version()); + _ -> + false + end. + + +%% A basic test to check if current host supports IPv6 +has_support_ipv6() -> + case inet:gethostname() of + {ok, Hostname} -> + has_support_ipv6(Hostname); + _ -> + false + end. + +has_support_ipv6(Hostname) -> + case inet:getaddr(Hostname, inet6) of + {ok, Addr} when (size(Addr) =:= 8) andalso + (element(1, Addr) =/= 0) andalso + (element(1, Addr) =/= 16#fe80) -> + true; + {ok, _} -> + false; + {error, _} -> + false + end. + + +is_ipv6_host() -> + case inet:gethostname() of + {ok, Hostname} -> + is_ipv6_host(Hostname); + {error, _} -> + false + end. + +is_ipv6_host(Hostname) -> + case ct:require(ipv6_hosts) of + ok -> + lists:member(list_to_atom(Hostname), ct:get_config(ipv6_hosts)); + _ -> + false + end. %% ---------------------------------------------------------------- @@ -452,10 +613,14 @@ nodes_on(Host) when is_list(Host) -> start_node(Name, Args) -> - Opts = [{cleanup,false}, {args,Args}], + Opts = [{cleanup, false}, {args, Args}], test_server:start_node(Name, slave, Opts). +stop_node(Node) -> + test_server:stop_node(Node). + + %% ---------------------------------------------------------------- %% Application and Crypto utility functions %% @@ -648,6 +813,9 @@ cover([Suite, Case] = Args) when is_atom(Suite) andalso is_atom(Case) -> %% (debug) Print functions %% +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + p(Mod, Case) when is_atom(Mod) andalso is_atom(Case) -> case get(test_case) of undefined -> diff --git a/lib/snmp/test/snmp_test_lib.hrl b/lib/snmp/test/snmp_test_lib.hrl index 335f3fff3c..f077f15d3e 100644 --- a/lib/snmp/test/snmp_test_lib.hrl +++ b/lib/snmp/test/snmp_test_lib.hrl @@ -18,15 +18,11 @@ %% The Initial Developer of the Original Code is Ericsson AB. %% %CopyrightEnd% %% + %%---------------------------------------------------------------------- -%% Purpose: Define common macros for testing +%% Purpose: Define common macros for (the snmp) testing %%---------------------------------------------------------------------- -%% - (some of the) Macros stolen from the test server - - -%% -define(line,put(test_server_loc,{?MODULE,?LINE}),). - - %% - Misc macros - -ifndef(APPLICATION). @@ -45,12 +41,18 @@ %% - Test case macros - +-define(TC_TRY(C, TC), snmp_test_lib:tc_try(C, TC)). +-define(TC_TRY(C, TCCond, TC), snmp_test_lib:tc_try(C, TCCond, TC)). -define(OS_BASED_SKIP(Skippable), snmp_test_lib:os_based_skip(Skippable)). -define(NON_PC_TC_MAYBE_SKIP(Config, Condition), snmp_test_lib:non_pc_tc_maybe_skip(Config, Condition, ?MODULE, ?LINE)). --define(SKIP(Reason), snmp_test_lib:skip(Reason, ?MODULE, ?LINE)). --define(FAIL(Reason), snmp_test_lib:fail(Reason, ?MODULE, ?LINE)). +-define(SKIP(Reason), snmp_test_lib:skip(Reason, ?MODULE, ?LINE)). +-define(FAIL(Reason), snmp_test_lib:fail(Reason, ?MODULE, ?LINE)). +-define(IS_IPV6_HOST(), snmp_test_lib:is_ipv6_host()). +-define(IS_IPV6_HOST(H), snmp_test_lib:is_ipv6_host(H)). +-define(HAS_SUPPORT_IPV6(), snmp_test_lib:has_support_ipv6()). +-define(HAS_SUPPORT_IPV6(H), snmp_test_lib:has_support_ipv6(H)). %% - Time macros - @@ -88,6 +90,7 @@ -define(LNODES(), snmp_test_lib:local_nodes()). -define(NODES(H), snmp_test_lib:nodes_on(H)). -define(START_NODE(N,A), snmp_test_lib:start_node(N,A)). +-define(STOP_NODE(N), snmp_test_lib:stop_node(N)). %% - Application and Crypto utility macros - @@ -150,8 +153,10 @@ snmp_test_lib:print(P, ?MODULE, ?LINE, F, A)). -define(PRINT1(F, A), snmp_test_lib:print1(F, A)). +-define(PRINT1(F), ?PRINT1(F, [])). -define(EPRINT1(F, A), ?PRINT1("<ERROR> " ++ F, A)). -define(PRINT2(F, A), snmp_test_lib:print2(F, A)). +-define(PRINT2(F), ?PRINT2(F, [])). -define(EPRINT2(F, A), ?PRINT2("<ERROR> " ++ F, A)). diff --git a/lib/snmp/test/snmp_test_mgr.erl b/lib/snmp/test/snmp_test_mgr.erl index 73a4d56084..9d6be65088 100644 --- a/lib/snmp/test/snmp_test_mgr.erl +++ b/lib/snmp/test/snmp_test_mgr.erl @@ -52,6 +52,7 @@ -include_lib("snmp/include/snmp_types.hrl"). -include_lib("snmp/include/STANDARD-MIB.hrl"). -include("snmp_test_lib.hrl"). +-include_lib("snmp/src/app/snmp_internal.hrl"). -record(state, {dbg = true, quiet, @@ -192,9 +193,11 @@ receive_trap(Timeout) -> init({Options, CallerPid}) -> put(sname, mgr), put(verbosity, debug), - random:seed(erlang:phash2([node()]), - erlang:monotonic_time(), - erlang:unique_integer()), + ?SNMP_RAND_SEED(), + %% rand:seed(exrop, + %% {erlang:phash2([node()]), + %% erlang:monotonic_time(), + %% erlang:unique_integer()}), case (catch is_options_ok(Options)) of true -> put(debug, get_value(debug, Options, false)), @@ -244,10 +247,21 @@ init({Options, CallerPid}) -> IpFamily = get_value(ipfamily, Options, inet), print("[~w] ~p -> IpFamily: ~p~n", [?MODULE, self(), IpFamily]), AgIp = case snmp_misc:assq(agent, Options) of - {value, Tuple4} when is_tuple(Tuple4) andalso - (size(Tuple4) =:= 4) -> - Tuple4; + {value, Addr} when is_tuple(Addr) andalso + (size(Addr) =:= 4) andalso + (IpFamily =:= inet) -> + print("[~w] ~p -> Addr: ~p~n", + [?MODULE, self(), Addr]), + Addr; + {value, Addr} when is_tuple(Addr) andalso + (size(Addr) =:= 8) andalso + (IpFamily =:= inet6) -> + print("[~w] ~p -> Addr: ~p~n", + [?MODULE, self(), Addr]), + Addr; {value, Host} when is_list(Host) -> + print("[~w] ~p -> Host: ~p~n", + [?MODULE, self(), Host]), {ok, Ip} = snmp_misc:ip(Host, IpFamily), Ip end, @@ -668,7 +682,6 @@ make_vb(Oid) -> #varbind{oid = Oid, variabletype = 'NULL', value = 'NULL'}. make_request_id() -> - %% random:uniform(16#FFFFFFF-1). snmp_test_mgr_counter_server:increment(mgr_request_id, 1, 1, 2147483647). echo_pdu(PDU, MiniMIB) -> @@ -1141,5 +1154,5 @@ d(_,_F,_A) -> print(F, A) -> ?PRINT2("MGR " ++ F, A). -formated_timestamp() -> - snmp_test_lib:formated_timestamp(). +%% formated_timestamp() -> +%% snmp_test_lib:formated_timestamp(). diff --git a/lib/snmp/test/snmp_test_mgr_misc.erl b/lib/snmp/test/snmp_test_mgr_misc.erl index 315e3ebd9e..6608a88c00 100644 --- a/lib/snmp/test/snmp_test_mgr_misc.erl +++ b/lib/snmp/test/snmp_test_mgr_misc.erl @@ -576,6 +576,7 @@ vsn('version-2') -> v2c. udp_send(UdpId, AgentIp, UdpPort, B) -> + ?vlog("attempt send message (~w bytes) to ~p", [sz(B), {AgentIp, UdpPort}]), case (catch gen_udp:send(UdpId, AgentIp, UdpPort, B)) of {error,ErrorReason} -> error("failed (error) sending message to ~p:~p: " @@ -880,7 +881,7 @@ display_prop_hdr(S) -> %%---------------------------------------------------------------------- sz(L) when is_list(L) -> - length(lists:flatten(L)); + iolist_size(L); sz(B) when is_binary(B) -> size(B); sz(O) -> diff --git a/lib/snmp/test/snmp_test_server.erl b/lib/snmp/test/snmp_test_server.erl index a77bdc142c..ab7dbbbaa0 100644 --- a/lib/snmp/test/snmp_test_server.erl +++ b/lib/snmp/test/snmp_test_server.erl @@ -207,7 +207,7 @@ do_subcases(Mod, Fun, [{conf, Init, Cases, Finish}|SubCases], Config, Acc) [{failed, {Mod, Fun}, Error}] end, do_subcases(Mod, Fun, SubCases, Config, [R|Acc]); -do_subcases(Mod, Fun, [SubCase|SubCases], Config, Acc) when atom(SubCase) -> +do_subcases(Mod, Fun, [SubCase|SubCases], Config, Acc) when is_atom(SubCase) -> R = do_case(Mod, SubCase, Config), do_subcases(Mod, Fun, SubCases,Config, [R|Acc]). @@ -407,7 +407,7 @@ d(_, _, _, _) -> ok. timestamp() -> - {Date, Time} = calendar:now_to_datetime( now() ), + {Date, Time} = calendar:now_to_datetime( erlang:timestamp() ), {YYYY, MM, DD} = Date, {Hour, Min, Sec} = Time, FormatDate = diff --git a/lib/snmp/test/snmp_test_sys_monitor.erl b/lib/snmp/test/snmp_test_sys_monitor.erl new file mode 100644 index 0000000000..2291c6ca97 --- /dev/null +++ b/lib/snmp/test/snmp_test_sys_monitor.erl @@ -0,0 +1,86 @@ +%% +%% %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_test_sys_monitor). + +-export([start/0, stop/0, + init/1]). + +-define(NAME, ?MODULE). +-define(GSM, snmp_test_global_sys_monitor). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start() -> + Parent = self(), + proc_lib:start(?MODULE, init, [Parent]). + +stop() -> + case whereis(?NAME) of + Pid when is_pid(Pid) -> + Pid ! {?MODULE, stop}, + ok; + _ -> + ok + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init(Parent) -> + process_flag(priority, high), + try register(?NAME, self()) of + true -> + global:sync(), + MonSettings = [ + busy_port, + busy_dist_port, + {long_gc, 1000}, + {long_schedule, 1000}, + {large_heap, 8*1024*1024} % 8 MB + ], + erlang:system_monitor(self(), MonSettings), + ?GSM:log({erlang:timestamp(), starting}), + proc_lib:init_ack(Parent, {ok, self()}), + loop(#{parent => Parent}) + catch + _:_:_ -> + ?GSM:log({erlang:timestamp(), already_started}), + proc_lib:init_ack(Parent, {error, already_started}), + exit(normal) + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +loop(State) -> + receive + {monitor, Pid, Tag, Info} -> + ?GSM:log({Pid, erlang:timestamp(), Tag, Info}), + loop(State); + + _ -> + loop(State) + end. + + + diff --git a/lib/snmp/test/snmp_to_snmpnet_SUITE.erl b/lib/snmp/test/snmp_to_snmpnet_SUITE.erl index b83c7461d1..b8bdb30271 100644 --- a/lib/snmp/test/snmp_to_snmpnet_SUITE.erl +++ b/lib/snmp/test/snmp_to_snmpnet_SUITE.erl @@ -247,9 +247,17 @@ erlang_agent_netsnmp_get(Config) when is_list(Config) -> start_agent(Config), Oid = ?sysDescr_instance, Expected = expected(Oid, get), - [Expected = snmpget(Oid, Transport, Config) - || Transport <- Transports], - ok. + try + begin + [Expected = snmpget(Oid, Transport, Config) + || Transport <- Transports], + ok + end + catch + throw:{skip, _} = SKIP -> + SKIP + end. + %%-------------------------------------------------------------------- erlang_manager_netsnmp_get() -> @@ -260,29 +268,34 @@ erlang_manager_netsnmp_get(Config) when is_list(Config) -> SysDescr = "Net-SNMP agent", TargetName = "Target Net-SNMP agent", Transports = ?config(transports, Config), - ProgHandle = start_snmpd(Community, SysDescr, Config), - start_manager(Config), - snmp_manager_user:start_link(self(), test_user), - [snmp_manager_user:register_agent( - TargetName++domain_suffix(Domain), - [{reg_type, target_name}, - {tdomain, Domain}, {taddress, Addr}, - {community, Community}, {engine_id, "EngineId"}, - {version, v2}, {sec_model, v2c}, {sec_level, noAuthNoPriv}]) - || {Domain, Addr} <- Transports], - Results = - [snmp_manager_user:sync_get( - TargetName++domain_suffix(Domain), - [?sysDescr_instance]) - || {Domain, _} <- Transports], - ct:pal("sync_get -> ~p", [Results]), - snmp_manager_user:stop(), - stop_program(ProgHandle), - [{ok, - {noError, 0, - [{varbind, ?sysDescr_instance, 'OCTET STRING', SysDescr,1}] }, - _} = R || R <- Results], - ok. + case start_snmpd(Community, SysDescr, Config) of + {skip, _} = SKIP -> + SKIP; + ProgHandle -> + start_manager(Config), + snmp_manager_user:start_link(self(), test_user), + [snmp_manager_user:register_agent( + TargetName++domain_suffix(Domain), + [{reg_type, target_name}, + {tdomain, Domain}, {taddress, Addr}, + {community, Community}, {engine_id, "EngineId"}, + {version, v2}, {sec_model, v2c}, {sec_level, noAuthNoPriv}]) + || {Domain, Addr} <- Transports], + Results = + [snmp_manager_user:sync_get( + TargetName++domain_suffix(Domain), + [?sysDescr_instance]) + || {Domain, _} <- Transports], + ct:pal("sync_get -> ~p", [Results]), + snmp_manager_user:stop(), + stop_program(ProgHandle), + [{ok, + {noError, 0, + [{varbind, ?sysDescr_instance, 'OCTET STRING', SysDescr,1}] }, + _} = R || R <- Results], + ok + end. + %%-------------------------------------------------------------------- erlang_agent_netsnmp_inform(Config) when is_list(Config) -> @@ -292,17 +305,19 @@ erlang_agent_netsnmp_inform(Config) when is_list(Config) -> start_agent(Config), ok = snmpa:load_mib(snmp_master_agent, filename:join(DataDir, Mib)), - ProgHandle = start_snmptrapd(Mib, Config), - - snmpa:send_notification( - snmp_master_agent, testTrapv22, {erlang_agent_test, self()}), - - receive - {snmp_targets, erlang_agent_test, Addresses} -> - ct:pal("Notification sent to: ~p~n", [Addresses]), - erlang_agent_netsnmp_inform_responses(Addresses) - end, - stop_program(ProgHandle). + case start_snmptrapd(Mib, Config) of + {skip, _} = SKIP -> + SKIP; + ProgHandle -> + snmpa:send_notification( + snmp_master_agent, testTrapv22, {erlang_agent_test, self()}), + receive + {snmp_targets, erlang_agent_test, Addresses} -> + ct:pal("Notification sent to: ~p~n", [Addresses]), + erlang_agent_netsnmp_inform_responses(Addresses) + end, + stop_program(ProgHandle) + end. erlang_agent_netsnmp_inform_responses([]) -> receive @@ -326,6 +341,7 @@ erlang_agent_netsnmp_inform_responses([Address | Addresses]) -> %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- + snmpget(Oid, Transport, Config) -> Versions = ?config(snmp_versions, Config), @@ -335,10 +351,14 @@ snmpget(Oid, Transport, Config) -> "-Cf", net_snmp_addr_str(Transport), oid_str(Oid)], - ProgHandle = start_program(snmpget, Args, none, Config), - {_, line, Line} = get_program_output(ProgHandle), - stop_program(ProgHandle), - Line. + case start_program(snmpget, Args, none, Config) of + {skip, _} = SKIP -> + throw(SKIP); + ProgHandle -> + {_, line, Line} = get_program_output(ProgHandle), + stop_program(ProgHandle), + Line + end. start_snmptrapd(Mibs, Config) -> DataDir = ?config(data_dir, Config), @@ -382,12 +402,13 @@ start_program(Prog, Args, StartCheckMP, Config) -> DataDir = ?config(data_dir, Config), StartWrapper = filename:join(DataDir, "start_stop_wrapper"), Parent = self(), - Pid = - spawn_link( + %% process_flag(trap_exit, true), + {Pid, Mon} = + spawn_monitor( fun () -> run_program(Parent, StartWrapper, [Path | Args]) end), - start_check(Pid, erlang:monitor(process, Pid), StartCheckMP). + start_check(Pid, Mon, StartCheckMP). start_check(Pid, Mon, none) -> {Pid, Mon}; @@ -400,6 +421,10 @@ start_check(Pid, Mon, StartCheckMP) -> nomatch -> start_check(Pid, Mon, StartCheckMP) end; + {'DOWN', Mon, _, _Pid, {skip, Reason} = SKIP} -> + ct:pal("Received DOWN from ~p" + "~n Skip Reason: ~p", [_Pid, Reason]), + SKIP; {'DOWN', Mon, _, _, Reason} -> ct:fail("Prog ~p start failed: ~p", [Pid, Reason]) end. @@ -446,14 +471,34 @@ run_program_loop(Parent, Port, Buf) -> {Port, {data, {Flag, Data}}} -> case Flag of eol -> - Line = iolist_to_binary(lists:reverse(Buf, Data)), - ct:pal("Prog ~p output: ~s", [Port, Line]), - Parent ! {self(), line, Line}, - run_program_loop(Parent, Port, []); + Line = iolist_to_binary(lists:reverse(Buf, Data)), + ct:pal("Prog ~p output: ~s", [Port, Line]), + %% There are potentially many different fail outputs, + %% but for now we test for just this one: illegal option + IOpt = "illegal option", + case string:find(binary_to_list(Line), IOpt) of + nomatch -> + Parent ! {self(), line, Line}, + run_program_loop(Parent, Port, []); + Line2 -> + %% Try to extract the actual illegal option string + IOpt2 = + case string:take( + string:prefix(Line2, IOpt), [$-, $ ]) of + {_, Str} when length(Str) > 0 -> + Str; + _X -> + Line2 + end, + ct:pal("Force program ~p stop", [Port]), + true = port_command(Port, <<"stop\n">>), + (catch port_close(Port)), + exit({skip, {illegal_option, IOpt2}}) + end; noeol -> run_program_loop(Parent, Port, [Data | Buf]) end; - {Port, {exit_status,ExitStatus}} -> + {Port, {exit_status, ExitStatus}} -> ct:pal("Prog ~p exit: ~p", [Port, ExitStatus]), catch port_close(Port), Parent ! {self(), exit, ExitStatus}; diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index 3fd6eae423..8b7cb4dcd4 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -347,6 +347,7 @@ <datatype> <name name="subsystem_daemon_option"/> + <name name="subsystem_specs"/> <name name="subsystem_spec"/> <desc> <p>Defines a subsystem in the daemon.</p> diff --git a/lib/ssh/doc/src/ssh_client_channel.xml b/lib/ssh/doc/src/ssh_client_channel.xml index cd28b95fd3..e6683dbd0b 100644 --- a/lib/ssh/doc/src/ssh_client_channel.xml +++ b/lib/ssh/doc/src/ssh_client_channel.xml @@ -150,12 +150,12 @@ <tag><c>{init_args(), list()}</c></tag> <item><p>The list of arguments to the <c>init</c> function of the callback module.</p></item> - <tag><c>{cm, ssh:connection_ref()}</c></tag> + <tag><c>{cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>}</c></tag> <item><p>Reference to the <c>ssh</c> connection as returned by <seealso marker="ssh#connect-3">ssh:connect/3</seealso>. </p></item> - <tag><c>{channel_id, ssh:channel_id()}</c></tag> + <tag><c>{channel_id, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>}</c></tag> <item><p>Id of the <c>ssh</c> channel as returned by <seealso marker="ssh_connection#session_channel/2">ssh_connection:session_channel/2,4</seealso>. </p></item> @@ -198,7 +198,7 @@ {ok, ChannelRef} | {error, Reason}</name> <fsummary>Starts a process that handles an SSH channel.</fsummary> <type> - <v>SshConnection = ssh:connection_ref()</v> + <v>SshConnection = <seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso></v> <d>As returned by <seealso marker="ssh#connect-3">ssh:connect/3</seealso></d> <v>ChannelId = <seealso marker="ssh#type-channel_id">ssh:channel_id()</seealso></v> @@ -374,7 +374,7 @@ function and all channels are to handle the following message.</p> <taglist> - <tag><c>{ssh_channel_up, ssh:channel_id(), ssh:connection_ref()}</c></tag> + <tag><c>{ssh_channel_up, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>}</c></tag> <item><p>This is the first message that the channel receives. It is sent just before the <seealso marker="#init-1">init/1</seealso> function @@ -393,21 +393,21 @@ ChannelId, State}</name> <fsummary>Handles <c>ssh</c> connection protocol messages.</fsummary> <type> - <v>Msg = ssh_connection:event()</v> + <v>Msg = <seealso marker="ssh_connection#type-event">ssh_connection:event()</seealso></v> <v>ChannelId = <seealso marker="ssh#type-channel_id">ssh:channel_id()</seealso></v> <v>State = term()</v> </type> <desc> <p>Handles SSH Connection Protocol messages that may need service-specific attention. For details, - see <seealso marker="ssh_connection"> ssh_connection:event()</seealso>. + see <seealso marker="ssh_connection#type-event">ssh_connection:event()</seealso>. </p> <p>The following message is taken care of by the <c>ssh_client_channel</c> behavior.</p> <taglist> - <tag><c>{closed, ssh:channel_id()}</c></tag> + <tag><c>{closed, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>}</c></tag> <item><p>The channel behavior sends a close message to the other side, if such a message has not already been sent. Then it terminates the channel with reason <c>normal</c>.</p></item> diff --git a/lib/ssh/doc/src/ssh_connection.xml b/lib/ssh/doc/src/ssh_connection.xml index 2a701929f6..9fa1da659c 100644 --- a/lib/ssh/doc/src/ssh_connection.xml +++ b/lib/ssh/doc/src/ssh_connection.xml @@ -40,128 +40,119 @@ <p>The <url href="http://www.ietf.org/rfc/rfc4254.txt">SSH Connection Protocol</url> is used by clients and servers, that is, SSH channels, to communicate over the SSH connection. The API functions in this module send SSH Connection Protocol events, - which are received as messages by the remote channel. - If the receiving channel is an Erlang process, the - messages have the format - <c><![CDATA[{ssh_cm, connection_ref(), ssh_event_msg()}]]></c>. + which are received as messages by the remote channel handling the remote channel. + The Erlang format of thoose messages is + (see also <seealso marker="#type-event">below</seealso>): + </p> + <p><c>{ssh_cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>, </c><seealso marker="#type-channel_msg"><c>channel_msg()</c></seealso><c>}</c> + </p> + <p> If the <seealso marker="ssh_client_channel">ssh_client_channel</seealso> behavior is used to implement the channel process, these messages are handled by <seealso marker="ssh_client_channel#Module:handle_ssh_msg-2">handle_ssh_msg/2</seealso>.</p> </description> - <section> - <title>DATA TYPES</title> - - <p>Type definitions that are used more than once in this module, - or abstractions to indicate the intended use of the data - type, or both:</p> - - <taglist> - <tag><c>boolean() =</c></tag> - <item><p><c>true | false </c></p></item> - <tag><c>string() =</c></tag> - <item><p>list of ASCII characters</p></item> - <tag><c>timeout() =</c></tag> - <item><p><c>infinity | integer()</c> in milliseconds</p></item> - <tag><c>connection_ref() =</c></tag> - <item><p>opaque() -as returned by - <c>ssh:connect/3</c> or sent to an SSH channel processes</p></item> - <tag><c>channel_id() =</c></tag> - <item><p><c>integer()</c></p></item> - <tag><c>ssh_data_type_code() =</c></tag> - <item><p><c>1</c> ("stderr") | <c>0</c> ("normal") are - valid values, see - <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url> Section 5.2.</p></item> - <tag><c>ssh_request_status() =</c></tag> - <item><p> <c>success | failure</c></p></item> - <tag><c>event() =</c></tag> - <item><p><c>{ssh_cm, connection_ref(), ssh_event_msg()}</c></p></item> - <tag><c>ssh_event_msg() =</c></tag> - <item><p><c>data_events() | status_events() | terminal_events()</c></p></item> - <tag><c>reason() =</c></tag> - <item><p><c>timeout | closed</c></p></item> - </taglist> - - <taglist> - <tag><em>data_events()</em></tag> - <item> - <taglist> - <tag><c><![CDATA[{data, channel_id(), ssh_data_type_code(), Data :: binary()}]]></c></tag> - <item><p>Data has arrived on the channel. This event is sent as a - result of calling <seealso marker="ssh_connection#send-3"> - ssh_connection:send/[3,4,5]</seealso>.</p></item> - - <tag><c><![CDATA[{eof, channel_id()}]]></c></tag> - <item><p>Indicates that the other side sends no more data. - This event is sent as a result of calling <seealso - marker="ssh_connection#send_eof-2"> ssh_connection:send_eof/2</seealso>. - </p></item> - </taglist> - </item> + <datatypes> + <datatype> + <name name="ssh_data_type_code"/> + <desc> + <p>The valid values are <c>0</c> ("normal") and <c>1</c> ("stderr"), see + <url href="https://tools.ietf.org/html/rfc4254#page-8">RFC 4254, Section 5.2</url>.</p> + </desc> + </datatype> - <tag><em>status_events()</em></tag> - <item> + <datatype> + <name name="result"/> + <name name="reason"/> + <desc> + <p>The result of a call.</p> + <p>If the request reached the peer, was handled and the response + reached the requesting node the <seealso marker="#type-req_status">req_status()</seealso> + is the status reported from the peer.</p> + <p>If not, the <seealso marker="#type-reason">reason()</seealso> indicates what went wrong:</p> + <taglist> + <tag><c>closed</c></tag> + <item>indicates that the channel or connection was closed when trying to send the request + </item> + <tag><c>timeout</c></tag> + <item>indicates that the operation exceeded a time limit + </item> + </taglist> + </desc> + </datatype> - <taglist> - <tag><c><![CDATA[{signal, channel_id(), ssh_signal()}]]></c></tag> - <item><p>A signal can be delivered to the remote process/service - using the following message. Some systems do not support - signals, in which case they are to ignore this message. There is - currently no function to generate this event as the signals - referred to are on OS-level and not something generated by an - Erlang program.</p></item> + <datatype> + <name name="req_status"/> + <desc> + <p>The status of a request. + Coresponds to the <c>SSH_MSG_CHANNEL_SUCCESS</c> and <c>SSH_MSG_CHANNEL_FAILURE</c> values in + <url href="https://tools.ietf.org/html/rfc4254#section-5.4">RFC 4254, Section 5.4</url>. + </p> + </desc> + </datatype> - <tag><c><![CDATA[{exit_signal, channel_id(), ExitSignal :: string(), ErrorMsg ::string(), - LanguageString :: string()}]]></c></tag> + <datatype_title>SSH Connection Protocol: General</datatype_title> + <datatype> + <name name="event"/> + <name name="channel_msg"/> + <desc> + <p>As mentioned in the introduction, the + <url href="https://tools.ietf.org/html/rfc4254">SSH Connection Protocol</url> + events are handled as messages. When writing a channel handling process without using + the support by the <seealso marker="ssh_client_channel">ssh_client_channel</seealso> + behavior the process must handle thoose messages. + </p> + </desc> + </datatype> - <item><p>A remote execution can terminate violently because of a signal. - Then this message can be received. For details on valid string - values, see <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url> - Section 6.10, which shows a special case of these signals.</p></item> + <datatype> + <name name="want_reply"/> + <desc> + <p>Messages that include a <c>WantReply</c> expect the channel handling + process to call <seealso marker="ssh_connection#reply_request-4"> + ssh_connection:reply_request/4</seealso> + with the boolean value of <c>WantReply</c> as the second argument.</p> + </desc> + </datatype> - <tag><c><![CDATA[{exit_status, channel_id(), ExitStatus :: integer()}]]></c></tag> - <item><p>When the command running at the other end terminates, the - following message can be sent to return the exit status of the - command. A zero <c>exit_status</c> usually means that the command - terminated successfully. This event is sent as a result of calling - <seealso marker="ssh_connection#exit_status-3"> - ssh_connection:exit_status/3</seealso>.</p></item> - <tag><c><![CDATA[{closed, channel_id()}]]></c></tag> - <item><p>This event is sent as a result of calling - <seealso marker="ssh_connection#close-2">ssh_connection:close/2</seealso>. - Both the handling of this event and sending it are taken care of by the - <seealso marker="ssh_client_channel">ssh_client_channel</seealso> behavior.</p></item> - - </taglist> - </item> + <datatype_title>Data Transfer (RFC 4254, section 5.2)</datatype_title> + <datatype> + <name name="data_ch_msg"/> + <desc> + <p>Data has arrived on the channel. This event is sent as a result of calling + <seealso marker="ssh_connection#send-3"> ssh_connection:send/[3,4,5]</seealso>. + </p> + </desc> + </datatype> - <tag><em>terminal_events()</em></tag> - <item> - <p>Channels implementing a shell and command execution on the - server side are to handle the following messages that can be sent by client- - channel processes.</p> + <datatype_title>Closing a Channel (RFC 4254, section 5.3)</datatype_title> + <datatype> + <name name="eof_ch_msg"/> + <desc> + <p>Indicates that the other side sends no more data. This event is sent as a result of calling + <seealso marker="ssh_connection#send_eof-2"> ssh_connection:send_eof/2</seealso>. + </p> + </desc> + </datatype> + <datatype> + <name name="closed_ch_msg"/> + <desc> + <p>This event is sent as a result of calling + <seealso marker="ssh_connection#close-2">ssh_connection:close/2</seealso>. + Both the handling of this event and sending it are taken care of by the + <seealso marker="ssh_client_channel">ssh_client_channel</seealso> behavior.</p> + </desc> + </datatype> - <p>Events that include a <c>WantReply</c> expect the event handling - process to call <seealso marker="ssh_connection#reply_request-4"> - ssh_connection:reply_request/4</seealso> - with the boolean value of <c>WantReply</c> as the second argument.</p> - <taglist> - <tag><c><![CDATA[{env, channel_id(), WantReply :: boolean(), - Var ::string(), Value :: string()}]]></c></tag> - <item><p>Environment variables can be passed to the shell/command - to be started later. This event is sent as a result of calling <seealso - marker="ssh_connection#setenv-5"> ssh_connection:setenv/5</seealso>. - </p></item> - - <tag><c><![CDATA[{pty, channel_id(), - WantReply :: boolean(), {Terminal :: string(), CharWidth :: integer(), - RowHeight :: integer(), PixelWidth :: integer(), PixelHeight :: integer(), - TerminalModes :: [{Opcode :: atom() | integer(), - Value :: integer()}]}}]]></c></tag> - <item><p>A pseudo-terminal has been requested for the + <datatype_title>Requesting a Pseudo-Terminal (RFC 4254, section 6.2)</datatype_title> + <datatype> + <name name="pty_ch_msg"/> + <name name="term_mode"/> + <desc> + <p>A pseudo-terminal has been requested for the session. <c>Terminal</c> is the value of the TERM environment variable value, that is, <c>vt100</c>. Zero dimension parameters must be ignored. The character/row dimensions override the pixel @@ -169,46 +160,103 @@ drawable area of the window. <c>Opcode</c> in the <c>TerminalModes</c> list is the mnemonic name, represented as a lowercase Erlang atom, defined in - <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url>, Section 8. + <url href="https://tools.ietf.org/html/rfc4254#section-8">RFC 4254</url>, Section 8. It can also be an <c>Opcode</c> if the mnemonic name is not listed in the RFC. Example: <c>OP code: 53, mnemonic name ECHO erlang atom: echo</c>. This event is sent as a result of calling <seealso - marker="ssh_connection#ptty_alloc/4">ssh_connection:ptty_alloc/4</seealso>.</p></item> + marker="ssh_connection#ptty_alloc/4">ssh_connection:ptty_alloc/4</seealso>.</p> + </desc> + </datatype> + - <tag><c><![CDATA[{shell, WantReply :: boolean()}]]></c></tag> - <item><p>This message requests that the user default shell + <datatype_title>Environment Variable Passing (RFC 4254, section 6.4)</datatype_title> + <datatype> + <name name="env_ch_msg"/> + <desc> + <p>Environment variables can be passed to the shell/command + to be started later. This event is sent as a result of calling <seealso + marker="ssh_connection#setenv-5"> ssh_connection:setenv/5</seealso>. + </p> + </desc> + </datatype> + + + <datatype_title>Starting a Shell or Command (RFC 4254, section 6.5)</datatype_title> + <datatype> + <name name="shell_ch_msg"/> + <desc> + <p>This message requests that the user default shell is started at the other end. This event is sent as a result of calling <seealso marker="ssh_connection#shell-2"> ssh_connection:shell/2</seealso>. - </p></item> + </p> + </desc> + </datatype> + <datatype> + <name name="exec_ch_msg"/> + <desc> + <p>This message requests that the server starts + execution of the given command. This event is sent as a result of calling <seealso + marker="ssh_connection#exec-4">ssh_connection:exec/4 </seealso>. + </p> + </desc> + </datatype> + - <tag><c><![CDATA[{window_change, channel_id(), CharWidth() :: integer(), - RowHeight :: integer(), PixWidth :: integer(), PixHeight :: integer()}]]></c></tag> - <item><p>When the window (terminal) size changes on the client + <datatype_title>Window Dimension Change Message (RFC 4254, section 6.7)</datatype_title> + <datatype> + <name name="window_change_ch_msg"/> + <desc> + <p>When the window (terminal) size changes on the client side, it <em>can</em> send a message to the server side to inform it of - the new dimensions. No API function generates this event.</p></item> + the new dimensions. No API function generates this event.</p> + </desc> + </datatype> - <tag><c><![CDATA[{exec, channel_id(), - WantReply :: boolean(), Cmd :: string()}]]></c></tag> - <item><p>This message requests that the server starts - execution of the given command. This event is sent as a result of calling <seealso - marker="ssh_connection#exec-4">ssh_connection:exec/4 </seealso>. - </p></item> - </taglist> - </item> - </taglist> - </section> + <datatype_title>Signals (RFC 4254, section 6.9)</datatype_title> + <datatype> + <name name="signal_ch_msg"/> + <desc> + <p>A signal can be delivered to the remote process/service + using the following message. Some systems do not support + signals, in which case they are to ignore this message. There is + currently no function to generate this event as the signals + referred to are on OS-level and not something generated by an + Erlang program.</p> + </desc> + </datatype> + + + <datatype_title>Returning Exit Status (RFC 4254, section 6.10)</datatype_title> + <datatype> + <name name="exit_status_ch_msg"/> + <desc> + <p>When the command running at the other end terminates, the + following message can be sent to return the exit status of the + command. A zero <c>exit_status</c> usually means that the command + terminated successfully. This event is sent as a result of calling + <seealso marker="ssh_connection#exit_status-3"> + ssh_connection:exit_status/3</seealso>.</p> + </desc> + </datatype> + <datatype> + <name name="exit_signal_ch_msg"/> + <desc> + <p>A remote execution can terminate violently because of a signal. + Then this message can be received. For details on valid string + values, see <url href="https://tools.ietf.org/html/rfc4254#section-6.10">RFC 4254</url> + Section 6.10, which shows a special case of these signals.</p> + </desc> + </datatype> + + </datatypes> + <funcs> <func> - <name since="">adjust_window(ConnectionRef, ChannelId, NumOfBytes) -> ok</name> + <name since="" name="adjust_window" arity="3"/> <fsummary>Adjusts the SSH flow control window.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - <v>NumOfBytes = integer()</v> - </type> - <desc> + <desc> <p>Adjusts the SSH flow control window. This is to be done by both the client- and server-side channel processes.</p> @@ -221,17 +269,12 @@ </func> <func> - <name since="">close(ConnectionRef, ChannelId) -> ok</name> + <name since="" name="close" arity="2"/> <fsummary>Sends a close message on the channel <c>ChannelId</c>.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - </type> <desc> <p>A server- or client-channel process can choose to close their session by sending a close event. </p> - <note><p>This function is called by the <c>ssh_client_channel</c> behavior when the channel is terminated, see <seealso marker="ssh_client_channel"> ssh_client_channel(3)</seealso>. Thus, channels implemented @@ -240,57 +283,61 @@ </func> <func> - <name since="">exec(ConnectionRef, ChannelId, Command, TimeOut) -> ssh_request_status() | - {error, reason()}</name> + <name since="" name="exec" arity="4"/> <fsummary>Requests that the server starts the execution of the given command.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - <v>Command = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Is to be called by a client-channel process to request that the server starts executing the given command. The result is several messages according to the following pattern. The last message is a channel close message, as the <c>exec</c> request is a one-time execution that closes the channel when it is done.</p> - <taglist> - <tag><c>N x {ssh_cm, connection_ref(), - {data, channel_id(), ssh_data_type_code(), Data :: binary()}}</c></tag> + <!--taglist> + <tag><c>N x {ssh_cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>, {data, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>, </c><seealso marker="#type-ssh_data_type_code">ssh_data_type_code()</seealso><c>, Data :: binary()}}</c></tag> <item><p>The result of executing the command can be only one line or thousands of lines depending on the command.</p></item> - <tag><c>0 or 1 x {ssh_cm, connection_ref(), {eof, channel_id()}}</c></tag> + <tag><c>0 or 1 x {ssh_cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>, {eof, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>}}</c></tag> <item><p>Indicates that no more data is to be sent.</p></item> - <tag><c>0 or 1 x {ssh_cm, - connection_ref(), {exit_signal, - channel_id(), ExitSignal :: string(), ErrorMsg :: string(), LanguageString :: string()}}</c></tag> + <tag><c>0 or 1 x {ssh_cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>, {exit_signal, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>, ExitSignal :: string(), ErrorMsg :: string(), LanguageString :: string()}}</c></tag> <item><p>Not all systems send signals. For details on valid string values, see RFC 4254, Section 6.10</p></item> - <tag><c>0 or 1 x {ssh_cm, connection_ref(), {exit_status, - channel_id(), ExitStatus :: integer()}}</c></tag> + <tag><c>0 or 1 x {ssh_cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>, {exit_status, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>, ExitStatus :: integer()}}</c></tag> <item><p>It is recommended by the SSH Connection Protocol to send this message, but that is not always the case.</p></item> - <tag><c>1 x {ssh_cm, connection_ref(), - {closed, channel_id()}}</c></tag> + <tag><c>1 x {ssh_cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>, {closed, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>}}</c></tag> <item><p>Indicates that the <c>ssh_client_channel</c> started for the execution of the command has now been shut down.</p></item> + </taglist--> + + <taglist> + <tag>N x <seealso marker="#type-data_ch_msg">data message(s)</seealso></tag> + <item><p>The result of executing the command can be only one line + or thousands of lines depending on the command.</p></item> + + <tag>0 or 1 x <seealso marker="#type-eof_ch_msg">eof message</seealso></tag> + <item><p>Indicates that no more data is to be sent.</p></item> + + <tag>0 or 1 x <seealso marker="#type-exit_signal_ch_msg">exit signal message</seealso></tag> + <item><p>Not all systems send signals. For details on valid string + values, see RFC 4254, Section 6.10</p></item> + + <tag>0 or 1 x <seealso marker="#type-exit_status_ch_msg">exit status message</seealso></tag> + <item><p>It is recommended by the SSH Connection Protocol to send this + message, but that is not always the case.</p></item> + + <tag>1 x <seealso marker="#type-closed_ch_msg">closed status message</seealso></tag> + <item><p>Indicates that the <c>ssh_client_channel</c> started for the + execution of the command has now been shut down.</p></item> </taglist> </desc> </func> <func> - <name since="">exit_status(ConnectionRef, ChannelId, Status) -> ok</name> + <name since="" name="exit_status" arity="3"/> <fsummary>Sends the exit status of a command to the client.</fsummary> - <type> - <v>ConnectionRef = connection_ref() </v> - <v>ChannelId = channel_id()</v> - <v>Status = integer()</v> - </type> <desc> <p>Is to be called by a server-channel process to send the exit status of a command to the client.</p> @@ -298,16 +345,10 @@ </func> <func> - <name since="OTP 17.5">ptty_alloc(ConnectionRef, ChannelId, Options) -></name> - <name since="OTP 17.4">ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> > ssh_request_status() | - {error, reason()}</name> + <name since="OTP 17.5" name="ptty_alloc" arity="3"/> + <name since="OTP 17.4" name="ptty_alloc" arity="4"/> <fsummary>Sends an SSH Connection Protocol <c>pty_req</c>, to allocate a pseudo-terminal.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - <v>Options = proplists:proplist()</v> - </type> <desc> <p>Sends an SSH Connection Protocol <c>pty_req</c>, to allocate a pseudo-terminal. Is to be called by an SSH client process.</p> @@ -339,14 +380,8 @@ </func> <func> - <name since="">reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok</name> + <name since="" name="reply_request" arity="4"/> <fsummary>Sends status replies to requests that want such replies.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>WantReply = boolean()</v> - <v>Status = ssh_request_status()</v> - <v>ChannelId = channel_id()</v> - </type> <desc> <p>Sends status replies to requests where the requester has stated that it wants a status report, that is, <c>WantReply = true</c>. @@ -361,14 +396,15 @@ <name since="">send(ConnectionRef, ChannelId, Data, Timeout) -></name> <name since="">send(ConnectionRef, ChannelId, Type, Data) -></name> <name since="">send(ConnectionRef, ChannelId, Type, Data, TimeOut) -> - ok | {error, timeout} | {error, closed}</name> + ok | Error</name> <fsummary>Sends channel data.</fsummary> <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> + <v>ConnectionRef = <seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso></v> + <v>ChannelId = <seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso></v> <v>Data = binary()</v> - <v>Type = ssh_data_type_code()</v> + <v>Type = <seealso marker="#type-ssh_data_type_code">ssh_data_type_code()</seealso></v> <v>Timeout = timeout()</v> + <v>Error = {error, <seealso marker="#type-reason">reason()</seealso>}</v> </type> <desc> <p>Is to be called by client- and server-channel processes to send data to each other. @@ -380,29 +416,17 @@ </func> <func> - <name since="">send_eof(ConnectionRef, ChannelId) -> ok | {error, closed}</name> + <name since="" name="send_eof" arity="2"/> <fsummary>Sends EOF on channel <c>ChannelId</c>.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - </type> <desc> <p>Sends EOF on channel <c>ChannelId</c>.</p> </desc> </func> <func> - <name since="">session_channel(ConnectionRef, Timeout) -></name> - <name since="">session_channel(ConnectionRef, InitialWindowSize, - MaxPacketSize, Timeout) -> {ok, channel_id()} | {error, reason()}</name> + <name since="" name="session_channel" arity="2"/> + <name since="" name="session_channel" arity="4"/> <fsummary>Opens a channel for an SSH session.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>InitialWindowSize = integer()</v> - <v>MaxPacketSize = integer()</v> - <v>Timeout = timeout()</v> - <v>Reason = term()</v> - </type> <desc> <p>Opens a channel for an SSH session. The channel id returned from this function is the id used as input to the other functions in this module.</p> @@ -410,17 +434,9 @@ </func> <func> - <name since="">setenv(ConnectionRef, ChannelId, Var, Value, TimeOut) -> ssh_request_status() | - {error, reason()}</name> + <name since="" name="setenv" arity="5"/> <fsummary>Environment variables can be passed to the shell/command to be started later.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - <v>Var = string()</v> - <v>Value = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Environment variables can be passed before starting the shell/command. Is to be called by a client channel processes.</p> @@ -428,14 +444,9 @@ </func> <func> - <name since="">shell(ConnectionRef, ChannelId) -> ok | failure | {error, closed} - </name> + <name since="" name="shell" arity="2"/> <fsummary>Requests that the user default shell (typically defined in /etc/passwd in Unix systems) is to be executed at the server end.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - </type> <desc> <p>Is to be called by a client channel process to request that the user default shell (typically defined in /etc/passwd in Unix systems) is executed @@ -448,15 +459,8 @@ </func> <func> - <name since="">subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> ssh_request_status() | - {error, reason()}</name> + <name since="" name="subsystem" arity="4"/> <fsummary>Requests to execute a predefined subsystem on the server.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - <v>Subsystem = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Is to be called by a client-channel process for requesting to execute a predefined subsystem on the server. diff --git a/lib/ssh/doc/src/ssh_server_channel.xml b/lib/ssh/doc/src/ssh_server_channel.xml index a4e18bbfbf..87c745c9fb 100644 --- a/lib/ssh/doc/src/ssh_server_channel.xml +++ b/lib/ssh/doc/src/ssh_server_channel.xml @@ -112,7 +112,7 @@ function and all channels are to handle the following message.</p> <taglist> - <tag><c>{ssh_channel_up, ssh:channel_id(), ssh:connection_ref()}</c></tag> + <tag><c>{ssh_channel_up, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>}</c></tag> <item><p>This is the first message that the channel receives. This is especially useful if the server wants to send a message to the client without first @@ -129,21 +129,21 @@ ChannelId, State}</name> <fsummary>Handles <c>ssh</c> connection protocol messages.</fsummary> <type> - <v>Msg = ssh_connection:event()</v> + <v>Msg = <seealso marker="ssh_connection#type-event">ssh_connection:event()</seealso></v> <v>ChannelId = <seealso marker="ssh#type-channel_id">ssh:channel_id()</seealso></v> <v>State = term()</v> </type> <desc> <p>Handles SSH Connection Protocol messages that may need service-specific attention. For details, - see <seealso marker="ssh_connection"> ssh_connection:event()</seealso>. + see <seealso marker="ssh_connection#type-event">ssh_connection:event()</seealso>. </p> <p>The following message is taken care of by the <c>ssh_server_channel</c> behavior.</p> <taglist> - <tag><c>{closed, ssh:channel_id()}</c></tag> + <tag><c>{closed, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>}</c></tag> <item><p>The channel behavior sends a close message to the other side, if such a message has not already been sent. Then it terminates the channel with reason <c>normal</c>.</p></item> diff --git a/lib/ssh/doc/src/ssh_sftp.xml b/lib/ssh/doc/src/ssh_sftp.xml index c89092798d..f9f1e0953b 100644 --- a/lib/ssh/doc/src/ssh_sftp.xml +++ b/lib/ssh/doc/src/ssh_sftp.xml @@ -37,60 +37,112 @@ SSH.</p> </description> - <section> - <title>DATA TYPES</title> - <p>Type definitions that are used more than once in this module, - or abstractions to indicate the intended use of the data type, or both: - </p> - - <taglist> - <tag><c>reason()</c></tag> - <item> - <p>= <c>atom() | string() | tuple() </c>A description of the reason why an operation failed.</p> - <p> - 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> - <p> + <datatypes> + <datatype> + <name name="sftp_option"/> + <desc> + </desc> + </datatype> + + <datatype_title>Error cause</datatype_title> + <datatype> + <name name="reason"/> + <desc> + <p>A description of the reason why an operation failed.</p> + <p>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/html/draft-ietf-secsh-filexfer-13#page-49">draft-ietf-secsh-filexfer-13</url> + section 9.1. The codes are named as <c>SSH_FX_*</c> which are transformed into lowercase of the star-part. 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>The <c>tuple()</c> reason are other errors like for example <c>{exit_status,1}</c>. </p> - </item> + </desc> + </datatype> - <tag><c>connection_ref() =</c></tag> - <item><p><c>opaque()</c> - as returned by - <seealso marker="ssh#connect-3"><c>ssh:connect/3</c></seealso></p></item> + <datatype_title>Crypto operations for open_tar</datatype_title> + <datatype> + <name name="tar_crypto_spec"/> + <name name="encrypt_spec"/> + <name name="decrypt_spec"/> + <desc> + <p>Specifies the encryption or decryption applied to tar files when using + <seealso marker="#open_tar/3">open_tar/3</seealso> or + <seealso marker="#open_tar/4">open_tar/4</seealso>. + </p> + <p>The encryption or decryption is applied to the generated stream of + bytes prior to sending the resulting stream to the SFTP server. + </p> + <p>For code examples see Section + <seealso marker="using_ssh#example-with-encryption">Example with encryption</seealso> + in the ssh Users Guide. + </p> + </desc> + </datatype> - <tag><c>timeout()</c></tag> - <item><p>= <c>infinity | integer()</c> in milliseconds. Default infinity.</p></item> - </taglist> - </section> + <datatype> + <name name="init_fun"/> + <name name="chunk_size"/> + <name name="crypto_state"/> + <desc> + <p>The <c>init_fun()</c> in the + <seealso marker="#type-tar_crypto_spec">tar_crypto_spec</seealso> + is applied once prior to any other <c>crypto</c> + operation. The intention is that this function initiates the encryption or + decryption for example by calling + <seealso marker="crypto:crypto#crypto_init/4">crypto:crypto_init/4</seealso> + or similar. The <c>crypto_state()</c> is the state such a function may return. + </p> + <p>If the selected cipher needs to have the input data partioned into + blocks of a certain size, the <c>init_fun()</c> should return the second + form of return value with the <c>chunk_size()</c> set to the block size. + If the <c>chunk_size()</c> is <c>undefined</c>, the size of the <c>PlainBin</c>s varies, + because this is intended for stream crypto, whereas a fixed <c>chunk_size()</c> is intended for block crypto. + A <c>chunk_size()</c> can be changed in the return from the <c>crypto_fun()</c>. + The value can be changed between <c>pos_integer()</c> and <c>undefined</c>. + </p> + </desc> + </datatype> - <section> - <title>Time-outs</title> - <p>If the request functions for the SFTP channel return <c>{error, timeout}</c>, - no answer was received from the server within the expected time.</p> - <p>The request may have reached the server and may have been performed. - However, no answer was received from the server within the expected time.</p> - </section> + <datatype> + <name name="crypto_fun"/> + <name name="crypto_result"/> + <desc> + <p>The initial <c>crypto_state()</c> returned from the + <seealso marker="#type-init_fun">init_fun()</seealso> + is folded into repeated applications of the <c>crypto_fun()</c> in the + <seealso marker="#type-tar_crypto_spec">tar_crypto_spec</seealso>. + The binary returned from that fun is sent to the remote SFTP server and + the new <c>crypto_state()</c> is used in the next call of the + <c>crypto_fun()</c>. + </p> + <p>If the <c>crypto_fun()</c> reurns a <c>chunk_size()</c>, that value + is as block size for further blocks in calls to <c>crypto_fun()</c>. + </p> + </desc> + </datatype> + + <datatype> + <name name="final_fun"/> + <desc> + <p>If doing encryption, + the <c>final_fun()</c> in the + <seealso marker="#type-tar_crypto_spec">tar_crypto_spec</seealso> + is applied to the last piece of data. + The <c>final_fun()</c> is responsible for padding (if needed) and + encryption of that last piece. + </p> + </desc> + </datatype> + </datatypes> <funcs> <func> - <name since="">apread(ChannelPid, Handle, Position, Len) -> {async, N} | {error, reason()}</name> + <name name="apread" arity="4" since=""/> <fsummary>Reads asynchronously from an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Len = integer()</v> - <v>N = term()</v> - </type> <desc><p>The <c><![CDATA[apread/4]]></c> function reads from a specified position, combining the <seealso marker="#position-3"><c>position/3</c></seealso> and <seealso marker="#aread-3"><c>aread/3</c></seealso> functions.</p> @@ -98,17 +150,8 @@ </func> <func> - <name since="">apwrite(ChannelPid, Handle, Position, Data) -> {async, N} | {error, reason()}</name> + <name name="apwrite" arity="4" since=""/> <fsummary>Writes asynchronously to an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Len = integer()</v> - <v>Data = binary()</v> - <v>Timeout = timeout()</v> - <v>N = term()</v> - </type> <desc><p>The <c><![CDATA[apwrite/4]]></c> function writes to a specified position, combining the <seealso marker="#position-3"><c>position/3</c></seealso> and <seealso marker="#awrite-3"><c>awrite/3</c></seealso> functions.</p> @@ -116,15 +159,8 @@ </func> <func> - <name since="">aread(ChannelPid, Handle, Len) -> {async, N} | {error, reason()}</name> + <name name="aread" arity="3" since=""/> <fsummary>Reads asynchronously from an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Len = integer()</v> - <v>N = term()</v> - </type> <desc> <p>Reads from an open file, without waiting for the result. If the handle is valid, the function returns <c><![CDATA[{async, N}]]></c>, where <c>N</c> @@ -137,16 +173,8 @@ </func> <func> - <name since="">awrite(ChannelPid, Handle, Data) -> {async, N} | {error, reason()}</name> + <name name="awrite" arity="3" since=""/> <fsummary>Writes asynchronously to an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Len = integer()</v> - <v>Data = binary()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Writes to an open file, without waiting for the result. If the handle is valid, the function returns <c><![CDATA[{async, N}]]></c>, where <c>N</c> @@ -159,28 +187,18 @@ </func> <func> - <name since="">close(ChannelPid, Handle) -></name> - <name since="">close(ChannelPid, Handle, Timeout) -> ok | {error, reason()}</name> + <name name="close" arity="2" since=""/> + <name name="close" arity="3" since=""/> <fsummary>Closes an open handle.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Closes a handle to an open file or directory on the server.</p> </desc> </func> <func> - <name since="">delete(ChannelPid, Name) -></name> - <name since="">delete(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name> + <name name="delete" arity="2" since=""/> + <name name="delete" arity="3" since=""/> <fsummary>Deletes a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Deletes the file specified by <c><![CDATA[Name]]></c>. </p> @@ -188,14 +206,9 @@ </func> <func> - <name since="">del_dir(ChannelPid, Name) -></name> - <name since="">del_dir(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name> + <name name="del_dir" arity="2" since=""/> + <name name="del_dir" arity="3" since=""/> <fsummary>Deletes an empty directory.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Deletes a directory specified by <c><![CDATA[Name]]></c>. The directory must be empty before it can be successfully deleted. @@ -204,16 +217,9 @@ </func> <func> - <name since="">list_dir(ChannelPid, Path) -></name> - <name since="">list_dir(ChannelPid, Path, Timeout) -> {ok, Filenames} | {error, reason()}</name> + <name name="list_dir" arity="2" since=""/> + <name name="list_dir" arity="3" since=""/> <fsummary>Lists the directory.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Path = string()</v> - <v>Filenames = [Filename]</v> - <v>Filename = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Lists the given directory on the server, returning the filenames as a list of strings.</p> @@ -221,14 +227,9 @@ </func> <func> - <name since="">make_dir(ChannelPid, Name) -></name> - <name since="">make_dir(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name> + <name name="make_dir" arity="2" since=""/> + <name name="make_dir" arity="3" since=""/> <fsummary>Creates a directory.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Creates a directory specified by <c><![CDATA[Name]]></c>. <c><![CDATA[Name]]></c> must be a full path to a new directory. The directory can only be @@ -237,14 +238,9 @@ </func> <func> - <name since="">make_symlink(ChannelPid, Name, Target) -></name> - <name since="">make_symlink(ChannelPid, Name, Target, Timeout) -> ok | {error, reason()}</name> + <name name="make_symlink" arity="3" since=""/> + <name name="make_symlink" arity="4" since=""/> <fsummary>Creates a symbolic link.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Target = string()</v> - </type> <desc> <p>Creates a symbolic link pointing to <c><![CDATA[Target]]></c> with the name <c><![CDATA[Name]]></c>. @@ -252,32 +248,19 @@ </desc> </func> - <func> - <name since="">open(ChannelPid, File, Mode) -></name> - <name since="">open(ChannelPid, File, Mode, Timeout) -> {ok, Handle} | {error, reason()}</name> + <func> + <name name="open" arity="3" since=""/> + <name name="open" arity="4" since=""/> <fsummary>Opens a file and returns a handle.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>File = string()</v> - <v>Mode = [Modeflag]</v> - <v>Modeflag = read | write | creat | trunc | append | binary</v> - <v>Timeout = timeout()</v> - <v>Handle = term()</v> - </type> <desc> <p>Opens a file on the server and returns a handle, which can be used for reading or writing.</p> </desc> </func> <func> - <name since="">opendir(ChannelPid, Path) -></name> - <name since="">opendir(ChannelPid, Path, Timeout) -> {ok, Handle} | {error, reason()}</name> + <name name="opendir" arity="2" since=""/> + <name name="opendir" arity="3" since=""/> <fsummary>Opens a directory and returns a handle.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Path = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Opens a handle to a directory on the server. The handle can be used for reading directory contents.</p> @@ -285,72 +268,36 @@ </func> <func> - <name since="OTP 17.4">open_tar(ChannelPid, Path, Mode) -></name> - <name since="OTP 17.4">open_tar(ChannelPid, Path, Mode, Timeout) -> {ok, Handle} | {error, reason()}</name> + <name name="open_tar" arity="3" since="OTP 17.4"/> + <name name="open_tar" arity="4" since="OTP 17.4"/> <fsummary>Opens a tar file on the server to which <c>ChannelPid</c> is connected and returns a handle.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Path = string()</v> - <v>Mode = [read] | [write] | [read,EncryptOpt] | [write,DecryptOpt]</v> - <v>EncryptOpt = {crypto,{InitFun,EncryptFun,CloseFun}}</v> - <v>DecryptOpt = {crypto,{InitFun,DecryptFun}}</v> - <v>InitFun = (fun() -> {ok,CryptoState}) | (fun() -> {ok,CryptoState,ChunkSize})</v> - <v>CryptoState = any()</v> - <v>ChunkSize = undefined | pos_integer()</v> - <v>EncryptFun = (fun(PlainBin,CryptoState) -> EncryptResult)</v> - <v>EncryptResult = {ok,EncryptedBin,CryptoState} | {ok,EncryptedBin,CryptoState,ChunkSize}</v> - <v>PlainBin = binary()</v> - <v>EncryptedBin = binary()</v> - <v>DecryptFun = (fun(EncryptedBin,CryptoState) -> DecryptResult)</v> - <v>DecryptResult = {ok,PlainBin,CryptoState} | {ok,PlainBin,CryptoState,ChunkSize}</v> - <v>CloseFun = (fun(PlainBin,CryptoState) -> {ok,EncryptedBin})</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Opens a handle to a tar file on the server, associated with <c>ChannelPid</c>. - The handle can be used for remote tar creation and extraction, as defined by the - <seealso marker="stdlib:erl_tar#init-3">erl_tar:init/3</seealso> function. - </p> - - <p> For code exampel see Section - <seealso marker="using_ssh">SFTP Client with TAR Compression and Encryption</seealso> in - the ssh Users Guide. </p> - - <p>The <c>crypto</c> mode option is applied to the generated stream of bytes prior to sending - them to the SFTP server. This is intended for encryption but can be used for other - purposes. + The handle can be used for remote tar creation and extraction. The actual writing + and reading is performed by calls to + <seealso marker="stdlib:erl_tar#add-3">erl_tar:add/3,4</seealso> and + <seealso marker="stdlib:erl_tar#extract-2">erl_tar:extract/2</seealso>. + Note: The + <seealso marker="stdlib:erl_tar#init-3">erl_tar:init/3</seealso> function should not + be called, that one is called by this open_tar function. </p> - <p>The <c>InitFun</c> is applied once - prior to any other <c>crypto</c> operation. The returned <c>CryptoState</c> is then folded into - repeated applications of the <c>EncryptFun</c> or <c>DecryptFun</c>. The binary returned - from those funs are sent further to the remote SFTP server. Finally, if doing encryption, - the <c>CloseFun</c> is applied to the last piece of data. The <c>CloseFun</c> is - responsible for padding (if needed) and encryption of that last piece. + <p>For code examples see Section + <seealso marker="using_ssh#sftp-client-with-tar-compression">SFTP Client with TAR Compression</seealso> + in the ssh Users Guide. </p> - <p>The <c>ChunkSize</c> defines the size of the <c>PlainBin</c>s that <c>EncodeFun</c> is applied - to. If the <c>ChunkSize</c> is <c>undefined</c>, the size of the <c>PlainBin</c>s varies, - because this is intended for stream crypto, whereas a fixed <c>ChunkSize</c> is intended for block crypto. - <c>ChunkSize</c>s can be changed in the return from the <c>EncryptFun</c> or - <c>DecryptFun</c>. The value can be changed between <c>pos_integer()</c> and <c>undefined</c>. + <p>The <c>crypto</c> mode option is explained in the data types section above, see + <seealso marker="#Crypto operations for open_tar">Crypto operations for open_tar</seealso>. + Encryption is assumed if the <c>Mode</c> contains <c>write</c>, and + decryption if the <c>Mode</c> contains <c>read</c>. </p> - </desc> </func> <func> - <name since="">position(ChannelPid, Handle, Location) -></name> - <name since="">position(ChannelPid, Handle, Location, Timeout) -> {ok, NewPosition | {error, reason()}</name> + <name name="position" arity="3" since=""/> + <name name="position" arity="4" since=""/> <fsummary>Sets the file position of a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Location = Offset - | {bof, Offset} | {cur, Offset} | {eof, Offset} | bof | cur | eof</v> - <v>Offset = integer()</v> - <v>Timeout = timeout()</v> - <v>NewPosition = integer()</v> - </type> <desc> <p>Sets the file position of the file referenced by <c><![CDATA[Handle]]></c>. Returns <c><![CDATA[{ok, NewPosition}]]></c> (as an absolute offset) if @@ -384,17 +331,9 @@ </func> <func> - <name since="">pread(ChannelPid, Handle, Position, Len) -></name> - <name since="">pread(ChannelPid, Handle, Position, Len, Timeout) -> {ok, Data} | eof | {error, reason()}</name> + <name name="pread" arity="4" since=""/> + <name name="pread" arity="5" since=""/> <fsummary>Reads from an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Len = integer()</v> - <v>Timeout = timeout()</v> - <v>Data = string() | binary()</v> - </type> <desc><p>The <c><![CDATA[pread/3,4]]></c> function reads from a specified position, combining the <seealso marker="#position-3"><c>position/3</c></seealso> and <seealso marker="#read-3"><c>read/3,4</c></seealso> functions.</p> @@ -402,16 +341,9 @@ </func> <func> - <name since="">pwrite(ChannelPid, Handle, Position, Data) -> ok</name> - <name since="">pwrite(ChannelPid, Handle, Position, Data, Timeout) -> ok | {error, reason()}</name> + <name name="pwrite" arity="4" since=""/> + <name name="pwrite" arity="5" since=""/> <fsummary>Writes to an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Data = iolist()</v> - <v>Timeout = timeout()</v> - </type> <desc><p>The <c><![CDATA[pwrite/3,4]]></c> function writes to a specified position, combining the <seealso marker="#position-3"><c>position/3</c></seealso> and <seealso marker="#write-3"><c>write/3,4</c></seealso> functions.</p> @@ -419,16 +351,9 @@ </func> <func> - <name since="">read(ChannelPid, Handle, Len) -></name> - <name since="">read(ChannelPid, Handle, Len, Timeout) -> {ok, Data} | eof | {error, reason()}</name> + <name name="read" arity="3" since=""/> + <name name="read" arity="4" since=""/> <fsummary>Reads from an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Len = integer()</v> - <v>Timeout = timeout()</v> - <v>Data = string() | binary()</v> - </type> <desc> <p>Reads <c><![CDATA[Len]]></c> bytes from the file referenced by <c><![CDATA[Handle]]></c>. Returns <c><![CDATA[{ok, Data}]]></c>, <c><![CDATA[eof]]></c>, or @@ -440,32 +365,19 @@ </desc> </func> - <func> - <name since="">read_file(ChannelPid, File) -></name> - <name since="">read_file(ChannelPid, File, Timeout) -> {ok, Data} | {error, reason()}</name> + <func> + <name name="read_file" arity="2" since=""/> + <name name="read_file" arity="3" since=""/> <fsummary>Reads a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>File = string()</v> - <v>Data = binary()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Reads a file from the server, and returns the data in a binary.</p> </desc> </func> - <func> - <name since="">read_file_info(ChannelPid, Name) -></name> - <name since="">read_file_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, reason()}</name> + <func> + <name name="read_file_info" arity="2" since=""/> + <name name="read_file_info" arity="3" since=""/> <fsummary>Gets information about a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Handle = term()</v> - <v>Timeout = timeout()</v> - <v>FileInfo = record()</v> - </type> <desc> <p>Returns a <c><![CDATA[file_info]]></c> record from the file system object specified by <c><![CDATA[Name]]></c> or <c><![CDATA[Handle]]></c>. See @@ -474,38 +386,26 @@ </p> <p> Depending on the underlying OS:es links might be followed and info on the final file, directory - etc is returned. See <seealso marker="#read_link_info-2">ssh_sftp::read_link_info/2</seealso> + etc is returned. See <seealso marker="#read_link_info-2">read_link_info/2</seealso> on how to get information on links instead. </p> </desc> </func> <func> - <name since="">read_link(ChannelPid, Name) -></name> - <name since="">read_link(ChannelPid, Name, Timeout) -> {ok, Target} | {error, reason()}</name> + <name name="read_link" arity="2" since=""/> + <name name="read_link" arity="3" since=""/> <fsummary>Reads symbolic link.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Target = string()</v> - </type> <desc> <p>Reads the link target from the symbolic link specified by <c><![CDATA[name]]></c>. </p> </desc> </func> - <func> - <name since="">read_link_info(ChannelPid, Name) -> {ok, FileInfo} | {error, reason()}</name> - <name since="">read_link_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, reason()}</name> + <func> + <name since="" name="read_link_info" arity="2"/> + <name since="" name="read_link_info" arity="3"/> <fsummary>Gets information about a symbolic link.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Handle = term()</v> - <v>Timeout = timeout()</v> - <v>FileInfo = record()</v> - </type> <desc> <p>Returns a <c><![CDATA[file_info]]></c> record from the symbolic link specified by <c><![CDATA[Name]]></c> or <c><![CDATA[Handle]]></c>. @@ -517,15 +417,9 @@ </func> <func> - <name since="">rename(ChannelPid, OldName, NewName) -> </name> - <name since="">rename(ChannelPid, OldName, NewName, Timeout) -> ok | {error, reason()}</name> + <name since="" name="rename" arity="3"/> + <name since="" name="rename" arity="4"/> <fsummary>Renames a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>OldName = string()</v> - <v>NewName = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Renames a file named <c><![CDATA[OldName]]></c> and gives it the name <c><![CDATA[NewName]]></c>. @@ -535,25 +429,27 @@ <func> <name since="">start_channel(ConnectionRef) -></name> - <name since="">start_channel(ConnectionRef, Options) -> - {ok, Pid} | {error, reason()|term()}</name> + <name since="">start_channel(ConnectionRef, SftpOptions) -> + {ok, ChannelPid} | Error</name> + <name since="">start_channel(Host) -></name> <name since="">start_channel(Host, Options) -></name> - <name since="">start_channel(Host, Port, Options) -> - {ok, Pid, ConnectionRef} | {error, reason()|term()}</name> - + <name since="">start_channel(Host, Port, Options) -></name> <name since="">start_channel(TcpSocket) -></name> <name since="">start_channel(TcpSocket, Options) -> - {ok, Pid, ConnectionRef} | {error, reason()|term()}</name> + {ok, ChannelPid, ConnectionRef} | Error</name> <fsummary>Starts an SFTP client.</fsummary> <type> - <v>Host = string()</v> - <v>ConnectionRef = connection_ref()</v> - <v>Port = integer()</v> - <v>TcpSocket = port()</v> - <d>The socket is supposed to be from <seealso marker="kernel:gen_tcp#connect-3">gen_tcp:connect</seealso> or <seealso marker="kernel:gen_tcp#accept-1">gen_tcp:accept</seealso> with option <c>{active,false}</c></d> - <v>Options = [{Option, Value}]</v> + <v>Host = <seealso marker="ssh:ssh#type-host">ssh:host()</seealso></v> + <v>Port = <seealso marker="kernel:inet#type-port_number">inet:port_number()</seealso></v> + <v>TcpSocket = <seealso marker="ssh:ssh#type-open_socket">ssh:open_socket()</seealso></v> + <v>Options = [ <seealso marker="#type-sftp_option">sftp_option()</seealso> + | <seealso marker="ssh:ssh#type-client_option">ssh:client_option()</seealso> ]</v> + <v>SftpOptions = [ <seealso marker="#type-sftp_option">sftp_option()</seealso> ]</v> + <v>ChannelPid = pid()</v> + <v>ConnectionRef = <seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso></v> + <v>Error = {error, <seealso marker="#type-reason">reason()</seealso>}</v> </type> <desc> <p>If no connection reference is provided, a connection is set @@ -594,11 +490,8 @@ </func> <func> - <name since="">stop_channel(ChannelPid) -> ok</name> + <name since="" name="stop_channel" arity="1"/> <fsummary>Stops the SFTP client channel.</fsummary> - <type> - <v>ChannelPid = pid()</v> - </type> <desc> <p>Stops an SFTP channel. Does not close the SSH connection. Use <seealso marker="ssh#close-1">ssh:close/1</seealso> to close it.</p> @@ -606,16 +499,9 @@ </func> <func> - <name since="">write(ChannelPid, Handle, Data) -></name> - <name since="">write(ChannelPid, Handle, Data, Timeout) -> ok | {error, reason()}</name> + <name since="" name="write" arity="3"/> + <name since="" name="write" arity="4"/> <fsummary>Writes to an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Data = iolist()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Writes <c><![CDATA[data]]></c> to the file referenced by <c><![CDATA[Handle]]></c>. The file is to be opened with <c><![CDATA[write]]></c> or <c><![CDATA[append]]></c> @@ -625,15 +511,9 @@ </func> <func> - <name since="">write_file(ChannelPid, File, Iolist) -></name> - <name since="">write_file(ChannelPid, File, Iolist, Timeout) -> ok | {error, reason()}</name> + <name since="" name="write_file" arity="3"/> + <name since="" name="write_file" arity="4"/> <fsummary>Writes a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>File = string()</v> - <v>Iolist = iolist()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Writes a file to the server. The file is created if it does not exist but overwritten if it exists.</p> @@ -641,15 +521,9 @@ </func> <func> - <name since="">write_file_info(ChannelPid, Name, Info) -></name> - <name since="">write_file_info(ChannelPid, Name, Info, Timeout) -> ok | {error, reason()}</name> + <name since="" name="write_file_info" arity="3"/> + <name since="" name="write_file_info" arity="4"/> <fsummary>Writes information for a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Info = record()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Writes file information from a <c><![CDATA[file_info]]></c> record to the file specified by <c><![CDATA[Name]]></c>. See diff --git a/lib/ssh/doc/src/ssh_sftpd.xml b/lib/ssh/doc/src/ssh_sftpd.xml index ee72784add..0d7b340399 100644 --- a/lib/ssh/doc/src/ssh_sftpd.xml +++ b/lib/ssh/doc/src/ssh_sftpd.xml @@ -35,36 +35,23 @@ <p>Specifies a channel process to handle an SFTP subsystem.</p> </description> - <section> - <title>DATA TYPES</title> - <taglist> - <tag><c>subsystem_spec() =</c></tag> - <item><p><c>{subsystem_name(), {channel_callback(), channel_init_args()}}</c></p></item> - <tag><c>subsystem_name() =</c></tag> - <item><p><c>"sftp"</c></p></item> - <tag><c>channel_callback() =</c></tag> - <item><p><c>atom()</c> - Name of the Erlang module implementing the subsystem using the - <seealso marker="ssh_server_channel">ssh_server_channel</seealso> (replaces ssh_daemon_channel) behaviour.</p></item> - <tag><c>channel_init_args() =</c></tag> - <item><p><c>list()</c> - The one given as argument to function <c>subsystem_spec/1</c>.</p></item> - </taglist> - </section> <funcs> <func> - <name since="">subsystem_spec(Options) -> subsystem_spec()</name> + <name name="subsystem_spec" arity="1" since=""/> <fsummary>Returns the subsystem specification that allows an SSH daemon to handle the subsystem "sftp".</fsummary> - <type> - <v>Options = [{Option, Value}]</v> - </type> <desc> <p>Is to be used together with <c>ssh:daemon/[1,2,3]</c></p> + <p>The <c>Name</c> is <c>"sftp"</c> and + <c>CbMod</c> is the name of the Erlang module implementing the subsystem using the + <seealso marker="ssh_server_channel">ssh_server_channel</seealso> (replaces ssh_daemon_channel) behaviour. + </p> <p>Options:</p> <taglist> - <tag><c><![CDATA[{cwd, String}]]></c></tag> + <tag><c>cwd</c></tag> <item> <p>Sets the initial current working directory for the server.</p> </item> - <tag><c><![CDATA[{file_handler, CallbackModule}]]></c></tag> + <tag><c>file_handler</c></tag> <item> <p>Determines which module to call for accessing the file server. The default value is <c>ssh_sftpd_file</c>, which uses the @@ -72,13 +59,13 @@ APIs to access the standard OTP file server. This option can be used to plug in other file servers.</p> </item> - <tag><c><![CDATA[{max_files, Integer}]]></c></tag> + <tag><c>max_files</c></tag> <item> <p>The default value is <c>0</c>, which means that there is no upper limit. If supplied, the number of filenames returned to the SFTP client per <c>READDIR</c> request is limited to at most the given value.</p> </item> - <tag><c><![CDATA[{root, String}]]></c></tag> + <tag><c>root</c></tag> <item> <p>Sets the SFTP root directory. Then the user cannot see any files above this root. If, for example, the root directory is set to <c>/tmp</c>, @@ -86,7 +73,7 @@ <c>cd /etc</c>, the user moves to <c>/tmp/etc</c>. </p> </item> - <tag><c><![CDATA[{sftpd_vsn, integer()}]]></c></tag> + <tag><c>sftpd_vsn</c></tag> <item> <p>Sets the SFTP version to use. Defaults to 5. Version 6 is under development and limited.</p> diff --git a/lib/ssh/doc/src/using_ssh.xml b/lib/ssh/doc/src/using_ssh.xml index 4455d5ecc5..5c56dee81d 100644 --- a/lib/ssh/doc/src/using_ssh.xml +++ b/lib/ssh/doc/src/using_ssh.xml @@ -232,9 +232,10 @@ </section> <section> - <title>SFTP Client with TAR Compression and Encryption</title> - - <p>Example of writing and then reading a tar file follows:</p> + <title>SFTP Client with TAR Compression</title> + <section> + <title>Basic example</title> + <p>This is an example of writing and then reading a tar file:</p> <code type="erl"> {ok,HandleWrite} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [write]), ok = erl_tar:add(HandleWrite, .... ), @@ -248,8 +249,12 @@ {ok,NameValueList} = erl_tar:extract(HandleRead,[memory]), ok = erl_tar:close(HandleRead), </code> + </section> - <p>The previous write and read example can be extended with encryption and decryption as follows:</p> + <section> + <title>Example with encryption</title> + <p>The previous <seealso marker="using_ssh#basic-example">Basic example</seealso> + can be extended with encryption and decryption as follows:</p> <code type="erl"> %% First three parameters depending on which crypto type we select: Key = <<"This is a 256 bit key. abcdefghi">>, @@ -297,6 +302,7 @@ Cr = {InitFun,DecryptFun}, ok = erl_tar:close(HandleRead), </code> </section> + </section> <section> <marker id="usersguide_creating_a_subsystem"/> diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index 6d64a45112..9627b70eeb 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -99,7 +99,7 @@ APP_TARGET= $(EBIN)/$(APP_FILE) APPUP_SRC= $(APPUP_FILE).src APPUP_TARGET= $(EBIN)/$(APPUP_FILE) -INTERNAL_HRL_FILES = ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl ssh.hrl ssh_userauth.hrl ssh_xfer.hrl +INTERNAL_HRL_FILES = ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl ssh.hrl ssh_xfer.hrl # ---------------------------------------------------- # FLAGS diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index ff5aee14d7..32f10c797d 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -66,6 +66,8 @@ cipher_alg/0, mac_alg/0, compression_alg/0, + host/0, + open_socket/0, ip_port/0 ]). diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 04453e6ef0..a991f72cf2 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -68,6 +68,25 @@ -define(string(X), ?string_utf8(X)). -define(binary(X), << ?STRING(X) >>). +-define('2bin'(X), (if is_binary(X) -> X; + is_list(X) -> list_to_binary(X); + X==undefined -> <<>> + end) ). + +%% encoding macros +-define('E...'(X), ?'2bin'(X)/binary ). +-define(Eboolean(X), ?BOOLEAN(case X of + true -> ?TRUE; + false -> ?FALSE + end) ). +-define(Ebyte(X), ?BYTE(X) ). +-define(Euint32(X), ?UINT32(X) ). +-define(Estring(X), ?STRING(?'2bin'(X)) ). +-define(Estring_utf8(X), ?string_utf8(X)/binary ). +-define(Ename_list(X), ?STRING(ssh_bits:name_list(X)) ). +-define(Empint(X), (ssh_bits:mpint(X))/binary ). +-define(Ebinary(X), ?STRING(X) ). + %% Cipher details -define(SSH_CIPHER_NONE, 0). -define(SSH_CIPHER_3DES, 3). @@ -293,7 +312,8 @@ | gen_tcp:listen_option() | ?COMMON_OPTION . --type subsystem_daemon_option() :: {subsystems, subsystem_spec()}. +-type subsystem_daemon_option() :: {subsystems, subsystem_specs()}. +-type subsystem_specs() :: [ subsystem_spec() ]. -type shell_daemon_option() :: {shell, mod_fun_args() | 'shell_fun/1'() | 'shell_fun/2'() }. -type 'shell_fun/1'() :: fun((User::string()) -> pid()) . @@ -459,14 +479,6 @@ recv_ext_info }). --record(ssh_key, - { - type, - public, - private, - comment = "" - }). - -record(ssh_pty, {term = "", % e.g. "xterm" width = 80, height = 25, @@ -474,13 +486,6 @@ pixel_height = 768, modes = <<>>}). -%% assertion macro --define(ssh_assert(Expr, Reason), - case Expr of - true -> ok; - _ -> exit(Reason) - end). - %% dbg help macros -define(wr_record(N,BlackList), diff --git a/lib/ssh/src/ssh_client_channel.erl b/lib/ssh/src/ssh_client_channel.erl index f985d8e273..3bd1e1fdf1 100644 --- a/lib/ssh/src/ssh_client_channel.erl +++ b/lib/ssh/src/ssh_client_channel.erl @@ -52,7 +52,7 @@ -callback handle_msg(Msg ::term(), State :: term()) -> {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. --callback handle_ssh_msg({ssh_cm, ConnectionRef::ssh:connection_ref(), SshMsg::term()}, +-callback handle_ssh_msg(ssh_connection:event(), State::term()) -> {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl index 9a060b8304..d6b50613f9 100644 --- a/lib/ssh/src/ssh_connect.hrl +++ b/lib/ssh/src/ssh_connect.hrl @@ -263,11 +263,8 @@ -record(connection, { requests = [], %% [{ChannelId, Pid}...] awaiting reply on request, channel_cache, - port_bindings, channel_id_seed, cli_spec, - address, - port, options, exec, system_supervisor, diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 83f85b1d8e..c5316bf133 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -60,13 +60,121 @@ request_failure_msg/0, request_success_msg/1, - bind/4, unbind/3, unbind_channel/2, - bound_channel/3, encode_ip/1 + encode_ip/1 ]). -type connection_ref() :: ssh:connection_ref(). -type channel_id() :: ssh:channel_id(). +-type req_status() :: success | failure . +-type reason() :: closed | timeout . + +-type result() :: req_status() | {error, reason()} . + +-type ssh_data_type_code() :: non_neg_integer(). % Only 0 and 1 are used + + +%%% The SSH Connection Protocol + +-export_type([event/0, + channel_msg/0, + want_reply/0, + data_ch_msg/0, + eof_ch_msg/0, + signal_ch_msg/0, + exit_signal_ch_msg/0, + exit_status_ch_msg/0, + closed_ch_msg/0, + env_ch_msg/0, + pty_ch_msg/0, + shell_ch_msg/0, + window_change_ch_msg/0, + exec_ch_msg/0 + ]). + +-type event() :: {ssh_cm, ssh:connection_ref(), channel_msg()}. +-type channel_msg() :: data_ch_msg() + | eof_ch_msg() + | closed_ch_msg() + | pty_ch_msg() + | env_ch_msg() + | shell_ch_msg() + | exec_ch_msg() + | signal_ch_msg() + | window_change_ch_msg() + | exit_status_ch_msg() + | exit_signal_ch_msg() + . + +-type want_reply() :: boolean(). + +-type data_ch_msg() :: {data, + ssh:channel_id(), + ssh_data_type_code(), + Data :: binary() + } . +-type eof_ch_msg() :: {eof, + ssh:channel_id() + } . +-type signal_ch_msg() :: {signal, + ssh:channel_id(), + SignalName :: string() + } . +-type exit_signal_ch_msg() :: {exit_signal, ssh:channel_id(), + ExitSignal :: string(), + ErrorMsg :: string(), + LanguageString :: string()} . +-type exit_status_ch_msg() :: {exit_status, + ssh:channel_id(), + ExitStatus :: non_neg_integer() + } . +-type closed_ch_msg() :: {closed, + ssh:channel_id() + } . +-type env_ch_msg() :: {env, + ssh:channel_id(), + want_reply(), + Var :: string(), + Value :: string() + } . +-type pty_ch_msg() :: {pty, + ssh:channel_id(), + want_reply(), + {Terminal :: string(), + CharWidth :: non_neg_integer(), + RowHeight :: non_neg_integer(), + PixelWidth :: non_neg_integer(), + PixelHeight :: non_neg_integer(), + TerminalModes :: [term_mode()] + } + } . + +-type term_mode() :: {Opcode :: atom() | byte(), + Value :: non_neg_integer()} . + +-type shell_ch_msg() :: {shell, + ssh:channel_id(), + want_reply() + } . +-type window_change_ch_msg() :: {window_change, + ssh:channel_id(), + CharWidth :: non_neg_integer(), + RowHeight :: non_neg_integer(), + PixelWidth :: non_neg_integer(), + PixelHeight :: non_neg_integer() + } . +-type exec_ch_msg() :: {exec, + ssh:channel_id(), + want_reply(), + Command :: string() + } . + +%%% This function is soley to convince all +%%% checks that the type event() exists... +-export([dummy/1]). +-spec dummy(event()) -> false. +dummy(_) -> false. + %%-------------------------------------------------------------------- %%% API %%-------------------------------------------------------------------- @@ -77,14 +185,21 @@ %% application, a system command, or some built-in subsystem. %% -------------------------------------------------------------------- --spec session_channel(connection_ref(), timeout()) -> - {ok, channel_id()} | {error, timeout | closed}. +-spec session_channel(ConnectionRef, Timeout) -> Result when + ConnectionRef :: ssh:connection_ref(), + Timeout :: timeout(), + Result :: {ok, ssh:channel_id()} | {error, reason()} . session_channel(ConnectionHandler, Timeout) -> session_channel(ConnectionHandler, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout). --spec session_channel(connection_ref(), integer(), integer(), timeout()) -> - {ok, channel_id()} | {error, timeout | closed}. + +-spec session_channel(ConnectionRef, InitialWindowSize, MaxPacketSize, Timeout) -> Result when + ConnectionRef :: ssh:connection_ref(), + InitialWindowSize :: pos_integer(), + MaxPacketSize :: pos_integer(), + Timeout :: timeout(), + Result :: {ok, ssh:channel_id()} | {error, reason()} . session_channel(ConnectionHandler, InitialWindowSize, MaxPacketSize, Timeout) -> case ssh_connection_handler:open_channel(ConnectionHandler, "session", <<>>, @@ -100,8 +215,11 @@ session_channel(ConnectionHandler, InitialWindowSize, MaxPacketSize, Timeout) -> %% Description: Will request that the server start the %% execution of the given command. %%-------------------------------------------------------------------- --spec exec(connection_ref(), channel_id(), string(), timeout()) -> - success | failure | {error, timeout | closed}. +-spec exec(ConnectionRef, ChannelId, Command, Timeout) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Command :: string(), + Timeout :: timeout(). exec(ConnectionHandler, ChannelId, Command, TimeOut) -> ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, "exec", @@ -112,8 +230,10 @@ exec(ConnectionHandler, ChannelId, Command, TimeOut) -> %% defined in /etc/passwd in UNIX systems) be started at the other %% end. %%-------------------------------------------------------------------- --spec shell(connection_ref(), channel_id()) -> - ok | success | failure | {error, timeout}. +-spec shell(ConnectionRef, ChannelId) -> Result when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Result :: ok | success | failure | {error, timeout} . shell(ConnectionHandler, ChannelId) -> ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, @@ -122,8 +242,11 @@ shell(ConnectionHandler, ChannelId) -> %% %% Description: Executes a predefined subsystem. %%-------------------------------------------------------------------- --spec subsystem(connection_ref(), channel_id(), string(), timeout()) -> - success | failure | {error, timeout | closed}. +-spec subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Subsystem :: string(), + Timeout :: timeout(). subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) -> ssh_connection_handler:request(ConnectionHandler, self(), @@ -134,12 +257,13 @@ subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) -> %%-------------------------------------------------------------------- -spec send(connection_ref(), channel_id(), iodata()) -> ok | {error, timeout | closed}. + send(ConnectionHandler, ChannelId, Data) -> send(ConnectionHandler, ChannelId, 0, Data, infinity). --spec send(connection_ref(), channel_id(), integer()| iodata(), timeout() | iodata()) -> - ok | {error, timeout | closed}. +-spec send(connection_ref(), channel_id(), iodata(), timeout()) -> ok | {error, reason()}; + (connection_ref(), channel_id(), ssh_data_type_code(), iodata()) -> ok | {error, reason()}. send(ConnectionHandler, ChannelId, Data, TimeOut) when is_integer(TimeOut) -> send(ConnectionHandler, ChannelId, 0, Data, TimeOut); @@ -151,14 +275,15 @@ send(ConnectionHandler, ChannelId, Type, Data) -> send(ConnectionHandler, ChannelId, Type, Data, infinity). --spec send(connection_ref(), channel_id(), integer(), iodata(), timeout()) -> - ok | {error, timeout | closed}. +-spec send(connection_ref(), channel_id(), ssh_data_type_code(), iodata(), timeout()) -> ok | {error, reason()}. send(ConnectionHandler, ChannelId, Type, Data, TimeOut) -> ssh_connection_handler:send(ConnectionHandler, ChannelId, Type, Data, TimeOut). %%-------------------------------------------------------------------- --spec send_eof(connection_ref(), channel_id()) -> ok | {error, closed}. +-spec send_eof(ConnectionRef, ChannelId) -> ok | {error, closed} when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(). %% %% %% Description: Sends eof on the channel <ChannelId>. @@ -167,7 +292,10 @@ send_eof(ConnectionHandler, Channel) -> ssh_connection_handler:send_eof(ConnectionHandler, Channel). %%-------------------------------------------------------------------- --spec adjust_window(connection_ref(), channel_id(), integer()) -> ok. +-spec adjust_window(ConnectionRef, ChannelId, NumOfBytes) -> ok when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + NumOfBytes :: integer(). %% %% %% Description: Adjusts the ssh flowcontrol window. @@ -176,8 +304,12 @@ adjust_window(ConnectionHandler, Channel, Bytes) -> ssh_connection_handler:adjust_window(ConnectionHandler, Channel, Bytes). %%-------------------------------------------------------------------- --spec setenv(connection_ref(), channel_id(), string(), string(), timeout()) -> - success | failure | {error, timeout | closed}. +-spec setenv(ConnectionRef, ChannelId, Var, Value, Timeout) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Var :: string(), + Value :: string(), + Timeout :: timeout(). %% %% %% Description: Environment variables may be passed to the shell/command to be @@ -189,7 +321,9 @@ setenv(ConnectionHandler, ChannelId, Var, Value, TimeOut) -> %%-------------------------------------------------------------------- --spec close(connection_ref(), channel_id()) -> ok. +-spec close(ConnectionRef, ChannelId) -> ok when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(). %% %% %% Description: Sends a close message on the channel <ChannelId>. @@ -198,7 +332,11 @@ close(ConnectionHandler, ChannelId) -> ssh_connection_handler:close(ConnectionHandler, ChannelId). %%-------------------------------------------------------------------- --spec reply_request(connection_ref(), boolean(), success | failure, channel_id()) -> ok. +-spec reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok when + ConnectionRef :: ssh:connection_ref(), + WantReply :: boolean(), + Status :: req_status(), + ChannelId :: ssh:channel_id(). %% %% %% Description: Send status replies to requests that want such replies. @@ -211,15 +349,20 @@ reply_request(_,false, _, _) -> %%-------------------------------------------------------------------- %% Description: Sends a ssh connection protocol pty_req. %%-------------------------------------------------------------------- --spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist()) -> - success | failure | {error, timeout}. +-spec ptty_alloc(ConnectionRef, ChannelId, Options) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Options :: proplists:proplist(). ptty_alloc(ConnectionHandler, Channel, Options) -> ptty_alloc(ConnectionHandler, Channel, Options, infinity). --spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist(), timeout()) -> - success | failure | {error, timeout | closed}. +-spec ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Options :: proplists:proplist(), + Timeout :: timeout(). ptty_alloc(ConnectionHandler, Channel, Options0, TimeOut) -> TermData = backwards_compatible(Options0, []), % FIXME @@ -252,6 +395,10 @@ signal(ConnectionHandler, Channel, Sig) -> "signal", false, [?string(Sig)], 0). +-spec exit_status(ConnectionRef, ChannelId, Status) -> ok when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Status :: integer(). exit_status(ConnectionHandler, Channel, Status) -> ssh_connection_handler:request(ConnectionHandler, Channel, "exit-status", false, [?uint32(Status)], 0). @@ -713,29 +860,6 @@ request_success_msg(Data) -> %%%---------------------------------------------------------------- %%% %%% -bind(IP, Port, ChannelPid, Connection) -> - Binds = [{{IP, Port}, ChannelPid} - | lists:keydelete({IP, Port}, 1, - Connection#connection.port_bindings)], - Connection#connection{port_bindings = Binds}. - -unbind(IP, Port, Connection) -> - Connection#connection{ - port_bindings = - lists:keydelete({IP, Port}, 1, - Connection#connection.port_bindings)}. -unbind_channel(ChannelPid, Connection) -> - Binds = [{Bind, ChannelP} || {Bind, ChannelP} - <- Connection#connection.port_bindings, - ChannelP =/= ChannelPid], - Connection#connection{port_bindings = Binds}. - -bound_channel(IP, Port, Connection) -> - case lists:keysearch({IP, Port}, 1, Connection#connection.port_bindings) of - {value, {{IP, Port}, ChannelPid}} -> ChannelPid; - _ -> undefined - end. - encode_ip(Addr) when is_tuple(Addr) -> case catch inet_parse:ntoa(Addr) of {'EXIT',_} -> false; diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 8f32966a12..e984cbb21b 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -386,16 +386,24 @@ init_connection_handler(Role, Socket, Opts) -> D); {stop, Error} -> - Sups = ?GET_INTERNAL_OPT(supervisors, Opts), - C = #connection{system_supervisor = proplists:get_value(system_sup, Sups), - sub_system_supervisor = proplists:get_value(subsystem_sup, Sups), - connection_supervisor = proplists:get_value(connection_sup, Sups) - }, + D = try + %% Only servers have supervisorts defined in Opts + Sups = ?GET_INTERNAL_OPT(supervisors, Opts), + #connection{system_supervisor = proplists:get_value(system_sup, Sups), + sub_system_supervisor = proplists:get_value(subsystem_sup, Sups), + connection_supervisor = proplists:get_value(connection_sup, Sups) + } + of + C -> + #data{connection_state=C} + catch + _:_ -> + #data{connection_state=#connection{}} + end, gen_statem:enter_loop(?MODULE, [], {init_error,Error}, - #data{connection_state=C, - socket=Socket}) + D#data{socket=Socket}) end. @@ -406,7 +414,6 @@ init([Role,Socket,Opts]) -> {Protocol, Callback, CloseTag} = ?GET_OPT(transport, Opts), C = #connection{channel_cache = ssh_client_channel:cache_create(), channel_id_seed = 0, - port_bindings = [], requests = [], options = Opts}, D0 = #data{starter = ?GET_INTERNAL_OPT(user_pid, Opts), @@ -1550,7 +1557,7 @@ terminate({shutdown,"Connection closed"}, _StateName, D) -> terminate({shutdown,{init,Reason}}, StateName, D) -> %% Error in initiation. "This error should not occur". - log(error, D, io_lib:format("Shutdown in init (StateName=~p): ~p~n",[StateName,Reason])), + log(error, D, "Shutdown in init (StateName=~p): ~p~n", [StateName,Reason]), stop_subsystem(D), close_transport(D); @@ -1952,12 +1959,12 @@ send_disconnect(Code, Reason, DetailedText, Module, Line, StateName, D0) -> call_disconnectfun_and_log_cond(LogMsg, DetailedText, Module, Line, StateName, D) -> case disconnect_fun(LogMsg, D) of void -> - log(info, D, - io_lib:format("~s~n" - "State = ~p~n" - "Module = ~p, Line = ~p.~n" - "Details:~n ~s~n", - [LogMsg, StateName, Module, Line, DetailedText])); + log(info, D, + "~s~n" + "State = ~p~n" + "Module = ~p, Line = ~p.~n" + "Details:~n ~s~n", + [LogMsg, StateName, Module, Line, DetailedText]); _ -> ok end. @@ -2021,6 +2028,9 @@ fold_keys(Keys, Fun, Extra) -> end, [], Keys). %%%---------------------------------------------------------------- +log(Tag, D, Format, Args) -> + log(Tag, D, io_lib:format(Format,Args)). + log(Tag, D, Reason) -> case atom_to_list(Tag) of % Dialyzer-technical reasons... "error" -> do_log(error_msg, Reason, D); @@ -2028,36 +2038,56 @@ log(Tag, D, Reason) -> "info" -> do_log(info_msg, Reason, D) end. -do_log(F, Reason, #data{ssh_params = #ssh{role = Role} = S - }) -> - VSN = - case application:get_key(ssh,vsn) of - {ok,Vsn} -> Vsn; - undefined -> "" - end, - PeerVersion = - case Role of - server -> S#ssh.c_version; - client -> S#ssh.s_version - end, - CryptoInfo = - try - [{_,_,CI}] = crypto:info_lib(), - <<"(",CI/binary,")">> + +do_log(F, Reason0, #data{ssh_params = S}) -> + Reason = + try io_lib:format("~s",[Reason0]) + of _ -> Reason0 catch - _:_ -> "" - end, - Other = - case Role of - server -> "Client"; - client -> "Server" + _:_ -> io_lib:format("~p",[Reason0]) end, - error_logger:F("Erlang SSH ~p ~s ~s.~n" - "~s: ~p~n" - "~s~n", - [Role, VSN, CryptoInfo, - Other, PeerVersion, - Reason]). + case S of + #ssh{role = Role} when Role==server ; + Role==client -> + {PeerRole,PeerVersion} = + case Role of + server -> {"Client", S#ssh.c_version}; + client -> {"Server", S#ssh.s_version} + end, + error_logger:F("Erlang SSH ~p ~s ~s.~n" + "~s: ~p~n" + "~s~n", + [Role, + ssh_log_version(), crypto_log_info(), + PeerRole, PeerVersion, + Reason]); + _ -> + error_logger:F("Erlang SSH ~s ~s.~n" + "~s~n", + [ssh_log_version(), crypto_log_info(), + Reason]) + end. + +crypto_log_info() -> + try + [{_,_,CI}] = crypto:info_lib(), + case crypto:info_fips() of + enabled -> + <<"(",CI/binary,". FIPS enabled)">>; + not_enabled -> + <<"(",CI/binary,". FIPS available but not enabled)">>; + _ -> + <<"(",CI/binary,")">> + end + catch + _:_ -> "" + end. + +ssh_log_version() -> + case application:get_key(ssh,vsn) of + {ok,Vsn} -> Vsn; + undefined -> "" + end. %%%---------------------------------------------------------------- not_connected_filter({connection_reply, _Data}) -> true; diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index d95e58c1bb..7c86a81108 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -34,24 +34,6 @@ -export([dbg_trace/3]). --define('2bin'(X), (if is_binary(X) -> X; - is_list(X) -> list_to_binary(X); - X==undefined -> <<>> - end) ). - --define('E...'(X), ?'2bin'(X)/binary ). --define(Eboolean(X), ?BOOLEAN(case X of - true -> ?TRUE; - false -> ?FALSE - end) ). --define(Ebyte(X), ?BYTE(X) ). --define(Euint32(X), ?UINT32(X) ). --define(Estring(X), ?STRING(?'2bin'(X)) ). --define(Estring_utf8(X), ?string_utf8(X)/binary ). --define(Ename_list(X), ?STRING(ssh_bits:name_list(X)) ). --define(Empint(X), (ssh_bits:mpint(X))/binary ). --define(Ebinary(X), ?STRING(X) ). - ucl(B) -> try unicode:characters_to_list(B) of L when is_list(L) -> L; diff --git a/lib/ssh/src/ssh_server_channel.erl b/lib/ssh/src/ssh_server_channel.erl index 555080e9ee..1905c40c98 100644 --- a/lib/ssh/src/ssh_server_channel.erl +++ b/lib/ssh/src/ssh_server_channel.erl @@ -37,7 +37,7 @@ -callback handle_msg(Msg ::term(), State :: term()) -> {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. --callback handle_ssh_msg({ssh_cm, ConnectionRef::ssh:connection_ref(), SshMsg::term()}, +-callback handle_ssh_msg(ssh_connection:event(), State::term()) -> {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 1b2ba5a50b..4b6e187c3a 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -92,24 +92,63 @@ -define(XF(S), S#state.xf). -define(REQID(S), S#state.req_id). +-type sftp_option() :: {timeout, timeout()} + | {sftp_vsn, pos_integer()} + | {window_size, pos_integer()} + | {packet_size, pos_integer()} . + +-type reason() :: atom() | string() | tuple() . + %%==================================================================== %% API %%==================================================================== + + +%%%================================================================ +%%% + +%%%---------------------------------------------------------------- +%%% start_channel/1 + start_channel(Cm) when is_pid(Cm) -> start_channel(Cm, []); + start_channel(Socket) when is_port(Socket) -> start_channel(Socket, []); -start_channel(Host) when is_list(Host) -> + +start_channel(Host) -> start_channel(Host, []). + +%%%---------------------------------------------------------------- +%%% start_channel/2 + +%%% -spec:s are as if Dialyzer handled signatures for separate +%%% function clauses. + +-spec start_channel(ssh:open_socket(), + [ssh:client_options() | sftp_option()] + ) + -> {ok,pid(),ssh:connection_ref()} | {error,reason()}; + + (ssh:connection_ref(), + [sftp_option()] + ) + -> {ok,pid()} | {ok,pid(),ssh:connection_ref()} | {error,reason()}; + + (ssh:host(), + [ssh:client_options() | sftp_option()] + ) + -> {ok,pid(),ssh:connection_ref()} | {error,reason()} . + start_channel(Socket, UserOptions) when is_port(Socket) -> - {SshOpts, _ChanOpts, SftpOpts} = handle_options(UserOptions), + {SshOpts, ChanOpts, SftpOpts} = handle_options(UserOptions), Timeout = % A mixture of ssh:connect and ssh_sftp:start_channel: proplists:get_value(connect_timeout, SshOpts, proplists:get_value(timeout, SftpOpts, infinity)), case ssh:connect(Socket, SshOpts, Timeout) of {ok,Cm} -> - case start_channel(Cm, UserOptions) of + case start_channel(Cm, ChanOpts ++ SftpOpts) of {ok, Pid} -> {ok, Pid, Cm}; Error -> @@ -144,6 +183,16 @@ start_channel(Cm, UserOptions) when is_pid(Cm) -> start_channel(Host, UserOptions) -> start_channel(Host, 22, UserOptions). + +%%%---------------------------------------------------------------- +%%% start_channel/3 + +-spec start_channel(ssh:host(), + inet:port_number(), + [ssh:client_option() | sftp_option()] + ) + -> {ok,pid(),ssh:connection_ref()} | {error,reason()}. + start_channel(Host, Port, UserOptions) -> {SshOpts, ChanOpts, SftpOpts} = handle_options(UserOptions), Timeout = % A mixture of ssh:connect and ssh_sftp:start_channel: @@ -168,6 +217,15 @@ start_channel(Host, Port, UserOptions) -> Error end. +%%% Helper for start_channel + +wait_for_version_negotiation(Pid, Timeout) -> + call(Pid, wait_for_version_negotiation, Timeout). + +%%%---------------------------------------------------------------- +-spec stop_channel(ChannelPid) -> ok when + ChannelPid :: pid(). + stop_channel(Pid) -> case is_process_alive(Pid) of true -> @@ -185,20 +243,63 @@ stop_channel(Pid) -> ok end. -wait_for_version_negotiation(Pid, Timeout) -> - call(Pid, wait_for_version_negotiation, Timeout). - +%%%---------------------------------------------------------------- +-spec open(ChannelPid, Name, Mode) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Name :: string(), + Mode :: [read | write | append | binary | raw], + Handle :: term(), + Error :: {error, reason()} . open(Pid, File, Mode) -> open(Pid, File, Mode, ?FILEOP_TIMEOUT). +-spec open(ChannelPid, Name, Mode, Timeout) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Name :: string(), + Mode :: [read | write | append | binary | raw], + Timeout :: timeout(), + Handle :: term(), + Error :: {error, reason()} . open(Pid, File, Mode, FileOpTimeout) -> call(Pid, {open, false, File, Mode}, FileOpTimeout). + +-type tar_crypto_spec() :: encrypt_spec() | decrypt_spec() . + +-type encrypt_spec() :: {init_fun(), crypto_fun(), final_fun()} . +-type decrypt_spec() :: {init_fun(), crypto_fun()} . + +-type init_fun() :: fun(() -> {ok,crypto_state()}) + | fun(() -> {ok,crypto_state(),chunk_size()}) . + +-type crypto_fun() :: fun((TextIn::binary(), crypto_state()) -> crypto_result()) . +-type crypto_result() :: {ok,TextOut::binary(),crypto_state()} + | {ok,TextOut::binary(),crypto_state(),chunk_size()} . + +-type final_fun() :: fun((FinalTextIn::binary(),crypto_state()) -> {ok,FinalTextOut::binary()}) . + +-type chunk_size() :: undefined | pos_integer(). +-type crypto_state() :: any() . + + +-spec open_tar(ChannelPid, Path, Mode) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Path :: string(), + Mode :: [read | write | {crypto, tar_crypto_spec()} ], + Handle :: term(), + Error :: {error, reason()} . open_tar(Pid, File, Mode) -> open_tar(Pid, File, Mode, ?FILEOP_TIMEOUT). +-spec open_tar(ChannelPid, Path, Mode, Timeout) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Path :: string(), + Mode :: [read | write | {crypto, tar_crypto_spec()} ], + Timeout :: timeout(), + Handle :: term(), + Error :: {error, reason()} . open_tar(Pid, File, Mode, FileOpTimeout) -> case {lists:member(write,Mode), lists:member(read,Mode), - Mode -- [read,write]} of + Mode -- [write,read]} of {true,false,[]} -> {ok,Handle} = open(Pid, File, [write], FileOpTimeout), erl_tar:init(Pid, write, @@ -264,13 +365,33 @@ open_tar(Pid, File, Mode, FileOpTimeout) -> end. +-spec opendir(ChannelPid, Path) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Path :: string(), + Handle :: term(), + Error :: {error, reason()} . opendir(Pid, Path) -> opendir(Pid, Path, ?FILEOP_TIMEOUT). +-spec opendir(ChannelPid, Path, Timeout) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Path :: string(), + Timeout :: timeout(), + Handle :: term(), + Error :: {error, reason()} . opendir(Pid, Path, FileOpTimeout) -> call(Pid, {opendir, false, Path}, FileOpTimeout). +-spec close(ChannelPid, Handle) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Error :: {error, reason()} . close(Pid, Handle) -> close(Pid, Handle, ?FILEOP_TIMEOUT). +-spec close(ChannelPid, Handle, Timeout) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Timeout :: timeout(), + Error :: {error, reason()} . close(Pid, Handle, FileOpTimeout) -> call(Pid, {close,false,Handle}, FileOpTimeout). @@ -279,47 +400,149 @@ readdir(Pid,Handle) -> readdir(Pid,Handle, FileOpTimeout) -> call(Pid, {readdir,false,Handle}, FileOpTimeout). +-spec pread(ChannelPid, Handle, Position, Len) -> {ok, Data} | eof | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Len :: integer(), + Data :: string() | binary(), + Error :: {error, reason()}. pread(Pid, Handle, Offset, Len) -> pread(Pid, Handle, Offset, Len, ?FILEOP_TIMEOUT). + +-spec pread(ChannelPid, Handle, Position, Len, Timeout) -> {ok, Data} | eof | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Len :: integer(), + Timeout :: timeout(), + Data :: string() | binary(), + Error :: {error, reason()}. pread(Pid, Handle, Offset, Len, FileOpTimeout) -> call(Pid, {pread,false,Handle, Offset, Len}, FileOpTimeout). + +-spec read(ChannelPid, Handle, Len) -> {ok, Data} | eof | Error when + ChannelPid :: pid(), + Handle :: term(), + Len :: integer(), + Data :: string() | binary(), + Error :: {error, reason()}. read(Pid, Handle, Len) -> read(Pid, Handle, Len, ?FILEOP_TIMEOUT). + +-spec read(ChannelPid, Handle, Len, Timeout) -> {ok, Data} | eof | Error when + ChannelPid :: pid(), + Handle :: term(), + Len :: integer(), + Timeout :: timeout(), + Data :: string() | binary(), + Error :: {error, reason()}. read(Pid, Handle, Len, FileOpTimeout) -> call(Pid, {read,false,Handle, Len}, FileOpTimeout). + %% TODO this ought to be a cast! Is so in all practical meaning %% even if it is obscure! +-spec apread(ChannelPid, Handle, Position, Len) -> {async, N} | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Len :: integer(), + Error :: {error, reason()}, + N :: term() . apread(Pid, Handle, Offset, Len) -> call(Pid, {pread,true,Handle, Offset, Len}, infinity). %% TODO this ought to be a cast! +-spec aread(ChannelPid, Handle, Len) -> {async, N} | Error when + ChannelPid :: pid(), + Handle :: term(), + Len :: integer(), + Error :: {error, reason()}, + N :: term() . aread(Pid, Handle, Len) -> call(Pid, {read,true,Handle, Len}, infinity). + +-spec pwrite(ChannelPid, Handle, Position, Data) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Data :: iolist(), + Error :: {error, reason()}. pwrite(Pid, Handle, Offset, Data) -> pwrite(Pid, Handle, Offset, Data, ?FILEOP_TIMEOUT). + +-spec pwrite(ChannelPid, Handle, Position, Data, Timeout) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Data :: iolist(), + Timeout :: timeout(), + Error :: {error, reason()}. pwrite(Pid, Handle, Offset, Data, FileOpTimeout) -> call(Pid, {pwrite,false,Handle,Offset,Data}, FileOpTimeout). + +-spec write(ChannelPid, Handle, Data) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Data :: iodata(), + Error :: {error, reason()}. write(Pid, Handle, Data) -> write(Pid, Handle, Data, ?FILEOP_TIMEOUT). + +-spec write(ChannelPid, Handle, Data, Timeout) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Data :: iodata(), + Timeout :: timeout(), + Error :: {error, reason()}. write(Pid, Handle, Data, FileOpTimeout) -> call(Pid, {write,false,Handle,Data}, FileOpTimeout). %% TODO this ought to be a cast! Is so in all practical meaning %% even if it is obscure! +-spec apwrite(ChannelPid, Handle, Position, Data) -> {async, N} | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Data :: binary(), + Error :: {error, reason()}, + N :: term() . apwrite(Pid, Handle, Offset, Data) -> call(Pid, {pwrite,true,Handle,Offset,Data}, infinity). %% TODO this ought to be a cast! Is so in all practical meaning %% even if it is obscure! +-spec awrite(ChannelPid, Handle, Data) -> {async, N} | Error when + ChannelPid :: pid(), + Handle :: term(), + Data :: binary(), + Error :: {error, reason()}, + N :: term() . awrite(Pid, Handle, Data) -> call(Pid, {write,true,Handle,Data}, infinity). +-spec position(ChannelPid, Handle, Location) -> {ok, NewPosition} | Error when + ChannelPid :: pid(), + Handle :: term(), + Location :: Offset | {bof, Offset} | {cur, Offset} | {eof, Offset} | bof | cur | eof, + Offset :: integer(), + NewPosition :: integer(), + Error :: {error, reason()}. position(Pid, Handle, Pos) -> position(Pid, Handle, Pos, ?FILEOP_TIMEOUT). + +-spec position(ChannelPid, Handle, Location, Timeout) -> {ok, NewPosition} | Error when + ChannelPid :: pid(), + Handle :: term(), + Location :: Offset | {bof, Offset} | {cur, Offset} | {eof, Offset} | bof | cur | eof, + Timeout :: timeout(), + Offset :: integer(), + NewPosition :: integer(), + Error :: {error, reason()}. position(Pid, Handle, Pos, FileOpTimeout) -> call(Pid, {position, Handle, Pos}, FileOpTimeout). @@ -328,8 +551,21 @@ real_path(Pid, Path) -> real_path(Pid, Path, FileOpTimeout) -> call(Pid, {real_path, false, Path}, FileOpTimeout). + +-spec read_file_info(ChannelPid, Name) -> {ok, FileInfo} | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Error :: {error, reason()}. read_file_info(Pid, Name) -> read_file_info(Pid, Name, ?FILEOP_TIMEOUT). + +-spec read_file_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | Error when + ChannelPid :: pid(), + Name :: string(), + Timeout :: timeout(), + FileInfo :: file:file_info(), + Error :: {error, reason()}. read_file_info(Pid, Name, FileOpTimeout) -> call(Pid, {read_file_info,false,Name}, FileOpTimeout). @@ -338,18 +574,57 @@ get_file_info(Pid, Handle) -> get_file_info(Pid, Handle, FileOpTimeout) -> call(Pid, {get_file_info,false,Handle}, FileOpTimeout). + +-spec write_file_info(ChannelPid, Name, FileInfo) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Error :: {error, reason()}. write_file_info(Pid, Name, Info) -> write_file_info(Pid, Name, Info, ?FILEOP_TIMEOUT). + +-spec write_file_info(ChannelPid, Name, FileInfo, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Timeout :: timeout(), + Error :: {error, reason()}. write_file_info(Pid, Name, Info, FileOpTimeout) -> call(Pid, {write_file_info,false,Name, Info}, FileOpTimeout). + +-spec read_link_info(ChannelPid, Name) -> {ok, FileInfo} | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Error :: {error, reason()}. read_link_info(Pid, Name) -> read_link_info(Pid, Name, ?FILEOP_TIMEOUT). + +-spec read_link_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Timeout :: timeout(), + Error :: {error, reason()}. read_link_info(Pid, Name, FileOpTimeout) -> call(Pid, {read_link_info,false,Name}, FileOpTimeout). + +-spec read_link(ChannelPid, Name) -> {ok, Target} | Error when + ChannelPid :: pid(), + Name :: string(), + Target :: string(), + Error :: {error, reason()}. read_link(Pid, LinkName) -> read_link(Pid, LinkName, ?FILEOP_TIMEOUT). + +-spec read_link(ChannelPid, Name, Timeout) -> {ok, Target} | Error when + ChannelPid :: pid(), + Name :: string(), + Target :: string(), + Timeout :: timeout(), + Error :: {error, reason()}. read_link(Pid, LinkName, FileOpTimeout) -> case call(Pid, {read_link,false,LinkName}, FileOpTimeout) of {ok, [{Name, _Attrs}]} -> @@ -358,28 +633,79 @@ read_link(Pid, LinkName, FileOpTimeout) -> ErrMsg end. +-spec make_symlink(ChannelPid, Name, Target) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Target :: string(), + Error :: {error, reason()} . make_symlink(Pid, Name, Target) -> make_symlink(Pid, Name, Target, ?FILEOP_TIMEOUT). +-spec make_symlink(ChannelPid, Name, Target, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Target :: string(), + Timeout :: timeout(), + Error :: {error, reason()} . make_symlink(Pid, Name, Target, FileOpTimeout) -> call(Pid, {make_symlink,false, Name, Target}, FileOpTimeout). + +-spec rename(ChannelPid, OldName, NewName) -> ok | Error when + ChannelPid :: pid(), + OldName :: string(), + NewName :: string(), + Error :: {error, reason()}. rename(Pid, FromFile, ToFile) -> rename(Pid, FromFile, ToFile, ?FILEOP_TIMEOUT). + +-spec rename(ChannelPid, OldName, NewName, Timeout) -> ok | Error when + ChannelPid :: pid(), + OldName :: string(), + NewName :: string(), + Timeout :: timeout(), + Error :: {error, reason()}. rename(Pid, FromFile, ToFile, FileOpTimeout) -> call(Pid, {rename,false,FromFile, ToFile}, FileOpTimeout). +-spec delete(ChannelPid, Name) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Error :: {error, reason()} . delete(Pid, Name) -> delete(Pid, Name, ?FILEOP_TIMEOUT). +-spec delete(ChannelPid, Name, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Timeout :: timeout(), + Error :: {error, reason()} . delete(Pid, Name, FileOpTimeout) -> call(Pid, {delete,false,Name}, FileOpTimeout). +-spec make_dir(ChannelPid, Name) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Error :: {error, reason()} . make_dir(Pid, Name) -> make_dir(Pid, Name, ?FILEOP_TIMEOUT). +-spec make_dir(ChannelPid, Name, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Timeout :: timeout(), + Error :: {error, reason()} . make_dir(Pid, Name, FileOpTimeout) -> call(Pid, {make_dir,false,Name}, FileOpTimeout). +-spec del_dir(ChannelPid, Name) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Error :: {error, reason()} . del_dir(Pid, Name) -> del_dir(Pid, Name, ?FILEOP_TIMEOUT). +-spec del_dir(ChannelPid, Name, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Timeout :: timeout(), + Error :: {error, reason()} . del_dir(Pid, Name, FileOpTimeout) -> call(Pid, {del_dir,false,Name}, FileOpTimeout). @@ -396,9 +722,21 @@ recv_window(Pid, FileOpTimeout) -> call(Pid, recv_window, FileOpTimeout). +-spec list_dir(ChannelPid, Path) -> {ok,FileNames} | Error when + ChannelPid :: pid(), + Path :: string(), + FileNames :: [FileName], + FileName :: string(), + Error :: {error, reason()} . list_dir(Pid, Name) -> list_dir(Pid, Name, ?FILEOP_TIMEOUT). - +-spec list_dir(ChannelPid, Path, Timeout) -> {ok,FileNames} | Error when + ChannelPid :: pid(), + Path :: string(), + Timeout :: timeout(), + FileNames :: [FileName], + FileName :: string(), + Error :: {error, reason()} . list_dir(Pid, Name, FileOpTimeout) -> case opendir(Pid, Name, FileOpTimeout) of {ok,Handle} -> @@ -429,9 +767,20 @@ do_list_dir(Pid, Handle, FileOpTimeout, Acc) -> end. +-spec read_file(ChannelPid, File) -> {ok, Data} | Error when + ChannelPid :: pid(), + File :: string(), + Data :: binary(), + Error :: {error, reason()}. read_file(Pid, Name) -> read_file(Pid, Name, ?FILEOP_TIMEOUT). +-spec read_file(ChannelPid, File, Timeout) -> {ok, Data} | Error when + ChannelPid :: pid(), + File :: string(), + Data :: binary(), + Timeout :: timeout(), + Error :: {error, reason()}. read_file(Pid, Name, FileOpTimeout) -> case open(Pid, Name, [read, binary], FileOpTimeout) of {ok, Handle} -> @@ -453,9 +802,20 @@ read_file_loop(Pid, Handle, PacketSz, FileOpTimeout, Acc) -> Error end. +-spec write_file(ChannelPid, File, Data) -> ok | Error when + ChannelPid :: pid(), + File :: string(), + Data :: iodata(), + Error :: {error, reason()}. write_file(Pid, Name, List) -> write_file(Pid, Name, List, ?FILEOP_TIMEOUT). +-spec write_file(ChannelPid, File, Data, Timeout) -> ok | Error when + ChannelPid :: pid(), + File :: string(), + Data :: iodata(), + Timeout :: timeout(), + Error :: {error, reason()}. write_file(Pid, Name, List, FileOpTimeout) when is_list(List) -> write_file(Pid, Name, list_to_binary(List), FileOpTimeout); write_file(Pid, Name, Bin, FileOpTimeout) -> diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index 5ec12e2d04..bf921f0ff3 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -58,7 +58,17 @@ %%==================================================================== %% API %%==================================================================== --spec subsystem_spec(list()) -> subsystem_spec(). +-spec subsystem_spec(Options) -> Spec when + Options :: [ {cwd, string()} | + {file_handler, CallbackModule::string()} | + {max_files, integer()} | + {root, string()} | + {sftpd_vsn, integer()} + ], + Spec :: {Name, {CbMod,Options}}, + Name :: string(), + CbMod :: atom() . + subsystem_spec(Options) -> {"sftp", {?MODULE, Options}}. diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index eaab13433a..a85926354e 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -61,14 +61,6 @@ -export([pack/3, adjust_algs_for_peer_version/2]). -export([decompress/2, decrypt_blocks/3, is_valid_mac/3 ]). % FIXME: remove --define(Estring(X), ?STRING((if is_binary(X) -> X; - is_list(X) -> list_to_binary(X); - X==undefined -> <<>> - end))). --define(Empint(X), (ssh_bits:mpint(X))/binary ). --define(Ebinary(X), ?STRING(X) ). --define(Euint32(X), ?UINT32(X) ). - %%%---------------------------------------------------------------------------- %%% %%% There is a difference between supported and default algorithms. The diff --git a/lib/ssh/src/ssh_userauth.hrl b/lib/ssh/src/ssh_userauth.hrl deleted file mode 100644 index 2cfc1f0f83..0000000000 --- a/lib/ssh/src/ssh_userauth.hrl +++ /dev/null @@ -1,78 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2005-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% -%% - -%% - -%%% Description: user authentication protocol - --define(SSH_MSG_USERAUTH_REQUEST, 50). --define(SSH_MSG_USERAUTH_FAILURE, 51). --define(SSH_MSG_USERAUTH_SUCCESS, 52). --define(SSH_MSG_USERAUTH_BANNER, 53). --define(SSH_MSG_USERAUTH_PK_OK, 60). --define(SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, 60). --define(SSH_MSG_USERAUTH_INFO_REQUEST, 60). --define(SSH_MSG_USERAUTH_INFO_RESPONSE, 61). - --record(ssh_msg_userauth_request, - { - user, %% string - service, %% string - method, %% string "publickey", "password" - data %% opaque - }). - --record(ssh_msg_userauth_failure, - { - authentications, %% string - partial_success %% boolean - }). - --record(ssh_msg_userauth_success, - { - }). - --record(ssh_msg_userauth_banner, - { - message, %% string - language %% string - }). - --record(ssh_msg_userauth_passwd_changereq, - { - prompt, %% string - languge %% string - }). - --record(ssh_msg_userauth_pk_ok, - { - algorithm_name, % string - key_blob % string - }). - --record(ssh_msg_userauth_info_request, - {name, - instruction, - language_tag, - num_prompts, - data}). --record(ssh_msg_userauth_info_response, - {num_responses, - data}). diff --git a/lib/ssh/test/ssh_bench_SUITE.erl b/lib/ssh/test/ssh_bench_SUITE.erl index 880c519a5e..5ff7a71c45 100644 --- a/lib/ssh/test/ssh_bench_SUITE.erl +++ b/lib/ssh/test/ssh_bench_SUITE.erl @@ -26,7 +26,7 @@ -include_lib("ssh/src/ssh.hrl"). -include_lib("ssh/src/ssh_transport.hrl"). -include_lib("ssh/src/ssh_connect.hrl"). --include_lib("ssh/src/ssh_userauth.hrl"). +-include_lib("ssh/src/ssh_auth.hrl"). %%%================================================================ %%% diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index 5fdcf15b5f..335896c60a 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -27,6 +27,130 @@ </header> <p>This document describes the changes made to the SSL application.</p> +<section><title>SSL 9.3.5</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Enhance error handling for erroneous alerts from the + peer.</p> + <p> + Own Id: OTP-15943</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 9.3.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix handling of certificate decoding problems in TLS 1.3 + similarly as in TLS 1.2.</p> + <p> + Own Id: OTP-15900</p> + </item> + <item> + <p> + Hibernation now works as expected in all cases, was + accidently broken by optimization efforts.</p> + <p> + Own Id: OTP-15910</p> + </item> + <item> + <p> + Fix interoperability problems with openssl when the TLS + 1.3 server is configured wirh the option + signature_algs_cert.</p> + <p> + Own Id: OTP-15913</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 9.3.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Correct handshake handling, might cause strange symptoms + such as ASN.1 certificate decoding issues.</p> + <p> + Own Id: OTP-15879 Aux Id: ERL-968 </p> + </item> + <item> + <p> + Fix handling of the signature_algorithms_cert extension + in the ClientHello handshake message.</p> + <p> + Own Id: OTP-15887 Aux Id: ERL-973 </p> + </item> + <item> + <p> + Handle new ClientHello extensions when handshake is + paused by the {handshake, hello} ssl option.</p> + <p> + Own Id: OTP-15888 Aux Id: ERL-975 </p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 9.3.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Returned "alert error string" is now same as logged alert + string</p> + <p> + Own Id: OTP-15844</p> + </item> + <item> + <p> + Fix returned extension map fields to follow the + documentation.</p> + <p> + Own Id: OTP-15862 Aux Id: ERL-951 </p> + </item> + <item> + <p> + Avoid DTLS crash due to missing gen_server return value + in DTLS packet demux process.</p> + <p> + Own Id: OTP-15864 Aux Id: ERL-962 </p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 9.3.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Missing check of size of user_data_buffer made internal + socket behave as an active socket instead of active N. + This could cause memory problems.</p> + <p> + Own Id: OTP-15825 Aux Id: ERL-934, OTP-15823 </p> + </item> + </list> + </section> + +</section> + <section><title>SSL 9.3</title> <section><title>Fixed Bugs and Malfunctions</title> @@ -102,6 +226,87 @@ </section> +<section><title>SSL 9.2.3.5</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Handling of zero size fragments in TLS could cause an + infinite loop. This has now been corrected.</p> + <p> + Own Id: OTP-15328 Aux Id: ERIERL-379 </p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 9.2.3.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Hibernation now works as expected in all cases, was + accidently broken by optimization efforts.</p> + <p> + Own Id: OTP-15910</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 9.2.3.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Correct handshake handling, might cause strange symptoms + such as ASN.1 certificate decoding issues.</p> + <p> + Own Id: OTP-15879 Aux Id: ERL-968 </p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 9.2.3.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Returned "alert error string" is now same as logged alert + string</p> + <p> + Own Id: OTP-15844</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 9.2.3.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Correct solution for retaining tcp flow control OTP-15802 + (ERL-934) as to not break ssl:recv as reported in + (ERL-938)</p> + <p> + Own Id: OTP-15823 Aux Id: ERL-934, ERL-938 </p> + </item> + </list> + </section> + +</section> + <section><title>SSL 9.2.3</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index 3aa6e09c2c..05590666da 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -207,6 +207,10 @@ <datatype> <name name="sign_scheme"/> </datatype> + + <datatype> + <name name="group"/> + </datatype> <datatype> <name name="kex_algo"/> @@ -363,7 +367,20 @@ </p> </desc> </datatype> - + + <datatype> + <name name="supported_groups"/> + <desc> + <p>TLS 1.3 introduces the "supported_groups" extension that is used for negotiating + the Diffie-Hellman parameters in a TLS 1.3 handshake. Both client and server + can specify a list of parameters that they are willing to use. + </p> + <p> If it is not specified it will use a default list ([x25519, x448, secp256r1, secp384r1]) that + is filtered based on the installed crypto library version. + </p> + </desc> + </datatype> + <datatype> <name name="secure_renegotiation"/> <desc><p>Specifies if to reject renegotiation attempt that does @@ -919,6 +936,8 @@ fun(srp, Username :: string(), UserState :: term()) -> <name name="dh_der"/> <desc><p>The DER-encoded Diffie-Hellman parameters. If specified, it overrides option <c>dhfile</c>.</p> + <warning><p>The <c>dh_der</c> option is not supported by TLS 1.3. Use the + <c>supported_groups</c> option instead.</p></warning> </desc> </datatype> @@ -928,6 +947,8 @@ fun(srp, Username :: string(), UserState :: term()) -> parameters to be used by the server if a cipher suite using Diffie Hellman key exchange is negotiated. If not specified, default parameters are used.</p> + <warning><p>The <c>dh_file</c> option is not supported by TLS 1.3. Use the + <c>supported_groups</c> option instead.</p></warning> </desc> </datatype> diff --git a/lib/ssl/doc/src/standards_compliance.xml b/lib/ssl/doc/src/standards_compliance.xml index ca98385f85..9df48b99d3 100644 --- a/lib/ssl/doc/src/standards_compliance.xml +++ b/lib/ssl/doc/src/standards_compliance.xml @@ -126,29 +126,28 @@ <section> <title>TLS 1.3</title> - <p>OTP-22 introduces basic support for TLS 1.3 on the server side. Basic functionality + <p>OTP-22 introduces basic support for TLS 1.3. Basic functionality covers a simple TLS 1.3 handshake with support of the mandatory extensions (supported_groups, signature_algorithms, key_share, supported_versions and - signature_algorithms_cert). The server supports a selective set of cryptographic algorithms:</p> + signature_algorithms_cert). The current implementation supports a selective set of cryptographic algorithms:</p> <list type="bulleted"> <item>Key Exchange: ECDHE</item> <item>Groups: all standard groups supported for the Diffie-Hellman key exchange</item> <item>Ciphers: TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256 and TLS_AES_128_CCM_SHA256</item> - <item>Signature Algorithms: RSA and RSA PSS</item> - <item>Certificates: currently only certificates with RSA keys are supported</item> + <item>Signature Algorithms: rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pkcs1_sha512, + ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, ecdsa_secp521r1_sha512, rsa_pss_rsae_sha256, + rsa_pss_rsae_sha384, rsa_pss_rsae_sha512, rsa_pkcs1_sha1 and ecdsa_sha1</item> + <item>Certificates: RSA (it MUST use the rsaEncryption OID) and ECDSA keys</item> </list> <p>Other notable features:</p> <list type="bulleted"> - <item>The server supports the HelloRetryRequest mechanism</item> <item>PSK and session resumption not supported</item> <item>Early data and 0-RTT not supported</item> <item>Key and Initialization Vector Update not supported</item> </list> <p>For more detailed information see the <seealso marker="#soc_table">Standards Compliance</seealso> below.</p> - <warning><p>Note that the client side is not yet functional. It is planned to be released - later in OTP-22.</p></warning> <p> The following table describes the current state of standards compliance for TLS 1.3.</p> <p>(<em>C</em> = Compliant, <em>NC</em> = Non-Compliant, <em>PC</em> = Partially-Compliant, @@ -176,25 +175,25 @@ <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">Version downgrade protection mechanism</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">RSASSA-PSS signature schemes</cell> <cell align="left" valign="middle"><em>PC</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">supported_versions (ClientHello) extension</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">signature_algorithms_cert extension</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> @@ -211,7 +210,7 @@ <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">(EC)DHE</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -295,8 +294,8 @@ </url> </cell> <cell align="left" valign="middle"><em>Client</em></cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>PC</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -319,14 +318,14 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">supported_groups (RFC7919)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">signature_algorithms (RFC8446)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -343,8 +342,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">application_layer_protocol_negotiation (RFC7301)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -373,8 +372,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">key_share (RFC8446)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -403,8 +402,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">supported_versions (RFC8446)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -427,8 +426,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">signature_algorithms_cert (RFC8446)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> @@ -459,13 +458,13 @@ <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">supported_groups (RFC7919)</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">signature_algorithms (RFC8446)</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -482,8 +481,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">application_layer_protocol_negotiation (RFC7301)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -513,7 +512,7 @@ <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">key_share (RFC8446)</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -543,7 +542,7 @@ <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">supported_versions (RFC8446)</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -567,7 +566,7 @@ <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">signature_algorithms_cert (RFC8446)</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> @@ -577,20 +576,20 @@ </url> </cell> <cell align="left" valign="middle"><em>Client</em></cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>PC</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">Version downgrade protection</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">key_share (RFC8446)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -601,8 +600,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">supported_versions (RFC8446)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> @@ -615,13 +614,13 @@ <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">Version downgrade protection</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">key_share (RFC8446)</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -633,7 +632,7 @@ <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">supported_versions (RFC8446)</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> @@ -650,7 +649,7 @@ <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">key_share (RFC8446)</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -662,7 +661,7 @@ <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">supported_versions (RFC8446)</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> @@ -672,8 +671,8 @@ </url> </cell> <cell align="left" valign="middle"><em>Client</em></cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -706,62 +705,62 @@ </url> </cell> <cell align="left" valign="middle"><em>Client</em></cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>PC</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pkcs1_sha256</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pkcs1_sha384</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pkcs1_sha512</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">ecdsa_secp256r1_sha256</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">ecdsa_secp384r1_sha384</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">ecdsa_secp521r1_sha512</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pss_rsae_sha256</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pss_rsae_sha384</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pss_rsae_sha512</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -796,14 +795,14 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pkcs1_sha1</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">ecdsa_sha1</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> @@ -816,55 +815,55 @@ <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pkcs1_sha256</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pkcs1_sha384</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pkcs1_sha512</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">ecdsa_secp256r1_sha256</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">ecdsa_secp384r1_sha384</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">ecdsa_secp521r1_sha512</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pss_rsae_sha256</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pss_rsae_sha384</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pss_rsae_sha512</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -900,13 +899,13 @@ <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pkcs1_sha1</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">ecdsa_sha1</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> @@ -967,68 +966,68 @@ </url> </cell> <cell align="left" valign="middle"><em>Client</em></cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">secp256r1</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">secp384r1</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">secp521r1</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">x25519</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">x448</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">ffdhe2048</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">ffdhe3072</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">ffdhe4096</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">ffdhe6144</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">ffdhe8192</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> @@ -1105,8 +1104,8 @@ </url> </cell> <cell align="left" valign="middle"><em>Client</em></cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -1224,8 +1223,8 @@ </url> </cell> <cell align="left" valign="middle"><em>Client</em></cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>PC</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -1362,8 +1361,8 @@ </url> </cell> <cell align="left" valign="middle"><em>Client</em></cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>PC</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -1374,8 +1373,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">signature_algorithms (RFC8446)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -1398,8 +1397,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">signature_algorithms_cert (RFC8446)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> @@ -1417,8 +1416,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">signature_algorithms (RFC8446)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -1441,8 +1440,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">signature_algorithms_cert (RFC8446)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> @@ -1463,8 +1462,8 @@ </url> </cell> <cell align="left" valign="middle"><em>Client</em></cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>PC</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -1521,73 +1520,82 @@ 4.4.2.2. Server Certificate Selection </url> </cell> - <cell align="left" valign="middle"><em>Client</em></cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"><em></em></cell> + <cell align="left" valign="middle"><em></em></cell> + <cell align="left" valign="middle"><em>PC</em></cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> - <cell align="left" valign="middle">certificate type MUST be X.509v3</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"><em></em></cell> + <cell align="left" valign="middle">The certificate type MUST be X.509v3, unless explicitly + negotiated otherwise</cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> - <cell align="left" valign="middle">certificate's public key is compatible</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"><em></em></cell> + <cell align="left" valign="middle">The server's end-entity certificate's public key (and associated + restrictions) MUST be compatible with the selected authentication + algorithm from the client's "signature_algorithms" extension + (currently RSA, ECDSA, or EdDSA).</cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> - <cell align="left" valign="middle">The certificate MUST allow the key to be used for signing</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"><em></em></cell> + <cell align="left" valign="middle">The certificate MUST allow the key to be used for signing + with a signature scheme indicated in the client's "signature_algorithms"/"signature_algorithms_cert" + extensions</cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> - <cell align="left" valign="middle">server_name and certificate_authorities are used</cell> + <cell align="left" valign="middle">The "server_name" and "certificate_authorities" + extensions are used to guide certificate selection. As servers + MAY require the presence of the "server_name" extension, clients + SHOULD send this extension, when applicable.</cell> <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"><em></em></cell> + <cell align="left" valign="middle"></cell> </row> <row> - <cell align="left" valign="middle"></cell> - <cell align="left" valign="middle"><em>Server</em></cell> + <cell align="left" valign="middle"> + <url href="https://tools.ietf.org/html/rfc8446#section-4.4.2.3"> + 4.4.2.3. Client Certificate Selection + </url> + </cell> + <cell align="left" valign="middle"><em></em></cell> <cell align="left" valign="middle"><em>PC</em></cell> - <cell align="left" valign="middle"><em></em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> - <cell align="left" valign="middle">certificate type MUST be X.509v3</cell> + <cell align="left" valign="middle">The certificate type MUST be X.509v3, unless explicitly + negotiated otherwise</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle"><em>22</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> - <cell align="left" valign="middle">certificate's public key is compatible</cell> - <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle"><em>22</em></cell> + <cell align="left" valign="middle">If the "certificate_authorities" extension in the + CertificateRequest message was present, at least one of the + certificates in the certificate chain SHOULD be issued by one of + the listed CAs.</cell> + <cell align="left" valign="middle"><em>NC</em></cell> + <cell align="left" valign="middle"><em></em></cell> </row> <row> <cell align="left" valign="middle"></cell> - <cell align="left" valign="middle">The certificate MUST allow the key to be used for signing</cell> + <cell align="left" valign="middle">The certificates MUST be signed using an acceptable signature + algorithm</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle"><em>22</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> - <cell align="left" valign="middle">server_name and certificate_authorities are used</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> - </row> - - <row> - <cell align="left" valign="middle"> - <url href="https://tools.ietf.org/html/rfc8446#section-4.4.2.3"> - 4.4.2.3. Client Certificate Selection - </url> - </cell> - <cell align="left" valign="middle"><em></em></cell> + <cell align="left" valign="middle">If the CertificateRequest message contained a non-empty + "oid_filters" extension, the end-entity certificate MUST match the + extension OIDs that are recognized by the client</cell> <cell align="left" valign="middle"><em>NC</em></cell> <cell align="left" valign="middle"><em></em></cell> </row> @@ -1599,8 +1607,8 @@ </url> </cell> <cell align="left" valign="middle"><em>Client</em></cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -1616,8 +1624,8 @@ </url> </cell> <cell align="left" valign="middle"><em>Client</em></cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -1633,8 +1641,8 @@ </url> </cell> <cell align="left" valign="middle"><em>Client</em></cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -1738,25 +1746,25 @@ <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">MUST NOT be interleaved with other record types</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">MUST NOT span key changes</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">MUST NOT send zero-length fragments</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">Alert messages MUST NOT be fragmented</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> @@ -1807,7 +1815,7 @@ <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">The padding sent is automatically verified</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> @@ -1950,51 +1958,51 @@ </url> </cell> <cell align="left" valign="middle"><em></em></cell> - <cell align="left" valign="middle"><em>PC</em></cell> - <cell align="left" valign="middle"><em>22</em></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">MUST implement the TLS_AES_128_GCM_SHA256</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">SHOULD implement the TLS_AES_256_GCM_SHA384</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">SHOULD implement the TLS_CHACHA20_POLY1305_SHA256</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle"><em>Digital signatures</em></cell> - <cell align="left" valign="middle"><em>PC</em></cell> - <cell align="left" valign="middle"><em>22</em></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">MUST support rsa_pkcs1_sha256 (for certificates)</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">MUST support rsa_pss_rsae_sha256 (for CertificateVerify and certificates)</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">MUST support ecdsa_secp256r1_sha256</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> @@ -2007,13 +2015,13 @@ <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">MUST support key exchange with secp256r1</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">SHOULD support key exchange with X25519</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> @@ -2030,7 +2038,7 @@ <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">Supported Versions</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -2042,25 +2050,25 @@ <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">Signature Algorithms</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">Signature Algorithms Certificate</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">Negotiated Groups</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">Key Share</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -2072,32 +2080,32 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle"><em>MUST send and use these extensions</em></cell> - <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>PC</em></cell> <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">"supported_versions" is REQUIRED for ClientHello, ServerHello and HelloRetryRequest</cell> - <cell align="left" valign="middle"><em>PC</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">"signature_algorithms" is REQUIRED for certificate authentication</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">"supported_groups" is REQUIRED for ClientHello messages using (EC)DHE key exchange</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">"key_share" is REQUIRED for (EC)DHE key exchange</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -2115,20 +2123,20 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle"><em>TLS 1.3 ClientHello</em></cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"><em></em></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">If not containing a "pre_shared_key" extension, it MUST contain both a "signature_algorithms" extension and a "supported_groups" extension.</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">If containing a "supported_groups" extension, it MUST also contain a "key_share" extension, and vice versa. An empty KeyShare.client_shares vector is permitted.</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> @@ -2151,30 +2159,44 @@ </url> </cell> <cell align="left" valign="middle"><em></em></cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"><em></em></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle"><em>MUST correctly handle extensible fields</em></cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"><em></em></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> - <cell align="left" valign="middle">A client sending a ClientHello MUST support all parameters advertised in it.</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle">A client sending a ClientHello MUST support all parameters + advertised in it. Otherwise, the server may fail to interoperate by selecting one of those parameters.</cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> </row> <row> <cell align="left" valign="middle"></cell> - <cell align="left" valign="middle">A middlebox which terminates a TLS connection MUST behave as a compliant TLS server</cell> + <cell align="left" valign="middle">A server receiving a ClientHello MUST correctly ignore all + unrecognized cipher suites, extensions, and other parameters. Otherwise, it may fail to + interoperate with newer clients. In TLS 1.3, a client receiving a CertificateRequest or + NewSessionTicket MUST also ignore all unrecognized extensions.</cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>22.1</em></cell> + </row> + + <row> + <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle">A middlebox which terminates a TLS connection MUST behave as a + compliant TLS server</cell> <cell align="left" valign="middle"><em>NA</em></cell> <cell align="left" valign="middle"></cell> </row> <row> <cell align="left" valign="middle"></cell> - <cell align="left" valign="middle">A middlebox which forwards ClientHello parameters it does not understand MUST NOT process any messages beyond that ClientHello.</cell> + <cell align="left" valign="middle">A middlebox which forwards ClientHello parameters it does not + understand MUST NOT process any messages beyond that ClientHello. It MUST forward all subsequent + traffic unmodified. Otherwise, it may fail to interoperate with newer clients and servers.</cell> <cell align="left" valign="middle"><em>NA</em></cell> <cell align="left" valign="middle"></cell> </row> @@ -2193,25 +2215,25 @@ <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">TLS_AES_128_GCM_SHA256</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">TLS_AES_256_GCM_SHA384</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">TLS_CHACHA20_POLY1305_SHA256</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">TLS_AES_128_CCM_SHA256</cell> <cell align="left" valign="middle"><em>C</em></cell> - <cell align="left" valign="middle">22</cell> + <cell align="left" valign="middle"><em>22</em></cell> </row> <row> <cell align="left" valign="middle"></cell> diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index e070006900..b220691e79 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -51,7 +51,7 @@ -export([encode_alert/3, send_alert/2, send_alert_in_connection/2, close/5, protocol_name/0]). %% Data handling --export([next_record/1, socket/4, setopts/3, getopts/3]). +-export([socket/4, setopts/3, getopts/3]). %% gen_statem state functions -export([init/3, error/3, downgrade/3, %% Initiation and take down states @@ -451,11 +451,11 @@ init({call, From}, {start, Timeout}, HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.versions), State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}}), {State2, Actions} = send_handshake(Hello, State1#state{connection_env = CEnv#connection_env{negotiated_version = HelloVersion}}), - State3 = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, %% RequestedVersion + State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, %% RequestedVersion session = Session0#session{session_id = Hello#client_hello.session_id}, start_or_recv_from = From}, - next_event(hello, no_record, State3, [{{timeout, handshake}, Timeout, close} | Actions]); + next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close} | Actions]); init({call, _} = Type, Event, #state{static_env = #static_env{role = server}, protocol_specific = PS} = State) -> Result = gen_handshake(?FUNCTION_NAME, Type, Event, @@ -514,7 +514,7 @@ hello(internal, #client_hello{cookie = <<>>, VerifyRequest = dtls_handshake:hello_verify_request(Cookie, ?HELLO_VERIFY_REQUEST_VERSION), State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}}), {State, Actions} = send_handshake(VerifyRequest, State1), - next_event(?FUNCTION_NAME, no_record, + next_event(?FUNCTION_NAME, no_record, State#state{handshake_env = HsEnv#handshake_env{ tls_handshake_history = ssl_handshake:init_handshake_history()}}, @@ -836,9 +836,12 @@ initial_flight_state(_) -> next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{ dtls_record_buffer = Buf0, dtls_cipher_texts = CT0} = Buffers, + connection_env = #connection_env{negotiated_version = Version}, + static_env = #static_env{data_tag = DataTag}, ssl_options = SslOpts} = State0) -> case dtls_record:get_dtls_records(Data, - acceptable_record_versions(StateName, State0), + {DataTag, StateName, Version, + [dtls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_DATAGRAM_VERSIONS]}, Buf0, SslOpts) of {Records, Buf1} -> CT1 = CT0 ++ Records, @@ -849,10 +852,6 @@ next_dtls_record(Data, StateName, #state{protocol_buffers = #protocol_buffers{ Alert end. -acceptable_record_versions(hello, _) -> - [dtls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_DATAGRAM_VERSIONS]; -acceptable_record_versions(_, #state{connection_env = #connection_env{negotiated_version = Version}}) -> - [Version]. dtls_handshake_events(Packets) -> lists:map(fun(Packet) -> diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index d8c0e30973..4a381745d4 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -255,7 +255,8 @@ enc_handshake(#client_hello{client_version = {Major, Minor}, CmLength = byte_size(BinCompMethods), BinCipherSuites = list_to_binary(CipherSuites), CsLength = byte_size(BinCipherSuites), - ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions), + ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions, + dtls_v1:corresponding_tls_version({Major, Minor})), {?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SIDLength), SessionID/binary, diff --git a/lib/ssl/src/dtls_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl index c6431b55a9..94b350eaa5 100644 --- a/lib/ssl/src/dtls_packet_demux.erl +++ b/lib/ssl/src/dtls_packet_demux.erl @@ -154,9 +154,9 @@ handle_info({Transport, Socket, IP, InPortNo, _} = Msg, #state{listener = Socket handle_info({PassiveTag, Socket}, #state{active_n = N, listener = Socket, - transport = {_,_,_, udp_error, PassiveTag}}) -> - next_datagram(Socket, N); - + transport = {_, _, _, _, PassiveTag}} = State) -> + next_datagram(Socket, N), + {noreply, State}; %% UDP socket does not have a connection and should not receive an econnreset %% This does however happens on some windows versions. Just ignoring it %% appears to make things work as expected! diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl index a4846f42c5..8b8db7b2de 100644 --- a/lib/ssl/src/dtls_record.erl +++ b/lib/ssl/src/dtls_record.erl @@ -162,26 +162,16 @@ current_connection_state_epoch(#{current_write := #{epoch := Epoch}}, Epoch. %%-------------------------------------------------------------------- --spec get_dtls_records(binary(), [ssl_record:ssl_version()], binary(), +-spec get_dtls_records(binary(), {atom(), atom(), ssl_record:ssl_version(), [ssl_record:ssl_version()]}, binary(), #ssl_options{}) -> {[binary()], binary()} | #alert{}. %% %% Description: Given old buffer and new data from UDP/SCTP, packs up a records %% and returns it as a list of tls_compressed binaries also returns leftover %% data %%-------------------------------------------------------------------- -get_dtls_records(Data, Versions, Buffer, SslOpts) -> +get_dtls_records(Data, Vinfo, Buffer, SslOpts) -> BinData = list_to_binary([Buffer, Data]), - case erlang:byte_size(BinData) of - N when N >= 3 -> - case assert_version(BinData, Versions) of - true -> - get_dtls_records_aux(BinData, [], SslOpts); - false -> - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) - end; - _ -> - get_dtls_records_aux(BinData, [], SslOpts) - end. + get_dtls_records_aux(Vinfo, BinData, [], SslOpts). %%==================================================================== %% Encoding DTLS records @@ -405,52 +395,49 @@ initial_connection_state(ConnectionEnd, BeastMitigation) -> client_verify_data => undefined, server_verify_data => undefined }. -assert_version(<<?BYTE(_), ?BYTE(MajVer), ?BYTE(MinVer), _/binary>>, Versions) -> - is_acceptable_version({MajVer, MinVer}, Versions). -get_dtls_records_aux(<<?BYTE(?APPLICATION_DATA),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Epoch), ?UINT48(SequenceNumber), - ?UINT16(Length), Data:Length/binary, Rest/binary>> = RawDTLSRecord, - Acc, SslOpts) -> - ssl_logger:debug(SslOpts#ssl_options.log_level, inbound, 'record', [RawDTLSRecord]), - get_dtls_records_aux(Rest, [#ssl_tls{type = ?APPLICATION_DATA, - version = {MajVer, MinVer}, - epoch = Epoch, sequence_number = SequenceNumber, - fragment = Data} | Acc], SslOpts); -get_dtls_records_aux(<<?BYTE(?HANDSHAKE),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Epoch), ?UINT48(SequenceNumber), - ?UINT16(Length), - Data:Length/binary, Rest/binary>> = RawDTLSRecord, - Acc, SslOpts) when MajVer >= 128 -> - ssl_logger:debug(SslOpts#ssl_options.log_level, inbound, 'record', [RawDTLSRecord]), - get_dtls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE, - version = {MajVer, MinVer}, - epoch = Epoch, sequence_number = SequenceNumber, - fragment = Data} | Acc], SslOpts); -get_dtls_records_aux(<<?BYTE(?ALERT),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Epoch), ?UINT48(SequenceNumber), - ?UINT16(Length), Data:Length/binary, - Rest/binary>> = RawDTLSRecord, Acc, SslOpts) -> +get_dtls_records_aux({DataTag, StateName, _, Versions} = Vinfo, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), + ?UINT16(Epoch), ?UINT48(SequenceNumber), + ?UINT16(Length), Data:Length/binary, Rest/binary>> = RawDTLSRecord, + Acc, SslOpts) when ((StateName == hello) orelse + ((StateName == certify) andalso (DataTag == udp)) orelse + ((StateName == abbreviated) andalso(DataTag == udp))) + andalso + ((Type == ?HANDSHAKE) orelse + (Type == ?ALERT)) -> ssl_logger:debug(SslOpts#ssl_options.log_level, inbound, 'record', [RawDTLSRecord]), - get_dtls_records_aux(Rest, [#ssl_tls{type = ?ALERT, - version = {MajVer, MinVer}, - epoch = Epoch, sequence_number = SequenceNumber, - fragment = Data} | Acc], SslOpts); -get_dtls_records_aux(<<?BYTE(?CHANGE_CIPHER_SPEC),?BYTE(MajVer),?BYTE(MinVer), + case is_acceptable_version({MajVer, MinVer}, Versions) of + true -> + get_dtls_records_aux(Vinfo, Rest, [#ssl_tls{type = Type, + version = {MajVer, MinVer}, + epoch = Epoch, sequence_number = SequenceNumber, + fragment = Data} | Acc], SslOpts); + false -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + end; +get_dtls_records_aux({_, _, Version, _} = Vinfo, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Epoch), ?UINT48(SequenceNumber), ?UINT16(Length), Data:Length/binary, Rest/binary>> = RawDTLSRecord, - Acc, SslOpts) -> + Acc, SslOpts) when (Type == ?APPLICATION_DATA) orelse + (Type == ?HANDSHAKE) orelse + (Type == ?ALERT) orelse + (Type == ?CHANGE_CIPHER_SPEC) -> ssl_logger:debug(SslOpts#ssl_options.log_level, inbound, 'record', [RawDTLSRecord]), - get_dtls_records_aux(Rest, [#ssl_tls{type = ?CHANGE_CIPHER_SPEC, - version = {MajVer, MinVer}, - epoch = Epoch, sequence_number = SequenceNumber, - fragment = Data} | Acc], SslOpts); -get_dtls_records_aux(<<?BYTE(_), ?BYTE(_MajVer), ?BYTE(_MinVer), + case {MajVer, MinVer} of + Version -> + get_dtls_records_aux(Vinfo, Rest, [#ssl_tls{type = Type, + version = {MajVer, MinVer}, + epoch = Epoch, sequence_number = SequenceNumber, + fragment = Data} | Acc], SslOpts); + _ -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + end; +get_dtls_records_aux(_, <<?BYTE(_), ?BYTE(_MajVer), ?BYTE(_MinVer), ?UINT16(Length), _/binary>>, _Acc, _) when Length > ?MAX_CIPHER_TEXT_LENGTH -> ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW); -get_dtls_records_aux(Data, Acc, _) -> +get_dtls_records_aux(_, Data, Acc, _) -> case size(Data) =< ?MAX_CIPHER_TEXT_LENGTH + ?INITIAL_BYTES of true -> {lists:reverse(Acc), Data}; diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 6af65e09f2..7ff9aed8ea 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -125,7 +125,11 @@ protocol_extensions/0, session_id/0, error_alert/0, - srp_param_type/0]). + tls_alert/0, + srp_param_type/0, + named_curve/0, + sign_scheme/0, + group/0]). %% ------------------------------------------------------------------------------------------------------- @@ -191,7 +195,8 @@ | rsa_pss_pss_sha384 | rsa_pss_pss_sha512 | rsa_pkcs1_sha1 - | ecdsa_sha1. + | ecdsa_sha1. % exported + -type kex_algo() :: rsa | dhe_rsa | dhe_dss | ecdhe_ecdsa | ecdh_ecdsa | ecdh_rsa | @@ -236,10 +241,10 @@ sect163r2 | secp160k1 | secp160r1 | - secp160r2. + secp160r2. % exported -type group() :: secp256r1 | secp384r1 | secp521r1 | ffdhe2048 | - ffdhe3072 | ffdhe4096 | ffdhe6144 | ffdhe8192. + ffdhe3072 | ffdhe4096 | ffdhe6144 | ffdhe8192. % exported -type srp_param_type() :: srp_1024 | srp_1536 | @@ -279,7 +284,7 @@ bad_certificate_status_response | bad_certificate_hash_value | unknown_psk_identity | - no_application_protocol. + no_application_protocol. % exported %% ------------------------------------------------------------------------------------------------------- -type common_option() :: {protocol, protocol()} | @@ -292,6 +297,7 @@ {ciphers, cipher_suites()} | {eccs, [named_curve()]} | {signature_algs_cert, signature_schemes()} | + {supported_groups, supported_groups()} | {secure_renegotiate, secure_renegotiation()} | {depth, allowed_cert_chain_length()} | {verify_fun, custom_verify()} | @@ -338,6 +344,7 @@ -type protocol_versions() :: [protocol_version()]. -type signature_algs() :: [{hash(), sign_algo()}]. -type signature_schemes() :: [sign_scheme()]. +-type supported_groups() :: [group()]. -type custom_user_lookup() :: {Lookupfun :: fun(), UserState :: any()}. -type padding_check() :: boolean(). -type beast_mitigation() :: one_n_minus_one | zero_n | disabled. @@ -975,7 +982,8 @@ cipher_suites(all) -> %% Description: Returns all default and all supported cipher suites for a %% TLS/DTLS version %%-------------------------------------------------------------------- -cipher_suites(Base, Version) when Version == 'tlsv1.2'; +cipher_suites(Base, Version) when Version == 'tlsv1.3'; + Version == 'tlsv1.2'; Version == 'tlsv1.1'; Version == tlsv1; Version == sslv3 -> @@ -1909,7 +1917,7 @@ validate_option(Opt, Value) -> throw({error, {options, {Opt, Value}}}). handle_cb_info({V1, V2, V3, V4}, {_,_,_,_,_}) -> - {V1,V2,V3,V4, list_to_atom(atom_to_list(V2) ++ "passive")}; + {V1,V2,V3,V4, list_to_atom(atom_to_list(V2) ++ "_passive")}; handle_cb_info(CbInfo, _) -> CbInfo. diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl index 06b1b005a5..2d57b72f7b 100644 --- a/lib/ssl/src/ssl_alert.erl +++ b/lib/ssl/src/ssl_alert.erl @@ -32,7 +32,11 @@ -include("ssl_record.hrl"). -include("ssl_internal.hrl"). --export([decode/1, own_alert_txt/1, alert_txt/1, reason_code/2]). +-export([decode/1, + own_alert_txt/1, + alert_txt/1, + alert_txt/4, + reason_code/4]). %%==================================================================== %% Internal application API @@ -48,20 +52,29 @@ decode(Bin) -> decode(Bin, [], 0). %%-------------------------------------------------------------------- -%% -spec reason_code(#alert{}, client | server) -> -%% {tls_alert, unicode:chardata()} | closed. -%-spec reason_code(#alert{}, client | server) -> closed | {essl, string()}. +-spec reason_code(#alert{}, client | server, ProtocolName::string(), StateName::atom()) -> + {tls_alert, {atom(), unicode:chardata()}} | closed. %% %% Description: Returns the error reason that will be returned to the %% user. %%-------------------------------------------------------------------- -reason_code(#alert{description = ?CLOSE_NOTIFY}, _) -> +reason_code(#alert{description = ?CLOSE_NOTIFY}, _, _, _) -> closed; -reason_code(#alert{description = Description, role = Role} = Alert, Role) -> - {tls_alert, {description_atom(Description), own_alert_txt(Alert)}}; -reason_code(#alert{description = Description} = Alert, Role) -> - {tls_alert, {description_atom(Description), alert_txt(Alert#alert{role = Role})}}. +reason_code(#alert{description = Description, role = Role} = Alert, Role, ProtocolName, StateName) -> + Txt = lists:flatten(alert_txt(ProtocolName, Role, StateName, own_alert_txt(Alert))), + {tls_alert, {description_atom(Description), Txt}}; +reason_code(#alert{description = Description} = Alert, Role, ProtocolName, StateName) -> + Txt = lists:flatten(alert_txt(ProtocolName, Role, StateName, alert_txt(Alert))), + {tls_alert, {description_atom(Description), Txt}}. + +%%-------------------------------------------------------------------- +-spec alert_txt(string(), server | client, StateNam::atom(), string()) -> string(). +%% +%% Description: Generates alert text for log or string part of error return. +%%-------------------------------------------------------------------- +alert_txt(ProtocolName, Role, StateName, Txt) -> + io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt]). %%-------------------------------------------------------------------- -spec own_alert_txt(#alert{}) -> string(). diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index 21db887bb5..c16e2331ff 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -38,7 +38,7 @@ cipher_init/3, nonce_seed/2, decipher/6, cipher/5, aead_encrypt/6, aead_decrypt/6, suites/1, all_suites/1, crypto_support_filters/0, chacha_suites/1, anonymous_suites/1, psk_suites/1, psk_suites_anon/1, - srp_suites/0, srp_suites_anon/0, + srp_suites/1, srp_suites_anon/1, rc4_suites/1, des_suites/1, rsa_suites/1, filter/3, filter_suites/1, filter_suites/2, hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1, @@ -284,7 +284,7 @@ all_suites({3, _} = Version) -> suites(Version) ++ chacha_suites(Version) ++ psk_suites(Version) - ++ srp_suites() + ++ srp_suites(Version) ++ rc4_suites(Version) ++ des_suites(Version) ++ rsa_suites(Version); @@ -313,8 +313,8 @@ chacha_suites(_) -> %% Description: Returns a list of the anonymous cipher suites, only supported %% if explicitly set by user. Intended only for testing. %%-------------------------------------------------------------------- -anonymous_suites({3, N}) -> - srp_suites_anon() ++ anonymous_suites(N); +anonymous_suites({3, N} = Version) -> + srp_suites_anon(Version) ++ anonymous_suites(N); anonymous_suites({254, _} = Version) -> dtls_v1:anonymous_suites(Version); anonymous_suites(4) -> @@ -375,7 +375,7 @@ psk_suites(_) -> %%-------------------------------------------------------------------- psk_suites_anon({3, N}) -> psk_suites_anon(N); -psk_suites_anon(3) -> +psk_suites_anon(3 = N) -> [ ?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384, ?TLS_PSK_WITH_AES_256_GCM_SHA384, @@ -401,8 +401,8 @@ psk_suites_anon(3) -> ?TLS_PSK_WITH_AES_128_CCM, ?TLS_PSK_WITH_AES_128_CCM_8, ?TLS_ECDHE_PSK_WITH_RC4_128_SHA - ] ++ psk_suites_anon(0); -psk_suites_anon(_) -> + ] ++ psk_suites_anon(N-1); +psk_suites_anon(N) when N > 0 -> [?TLS_DHE_PSK_WITH_AES_256_CBC_SHA, ?TLS_PSK_WITH_AES_256_CBC_SHA, ?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, @@ -413,14 +413,18 @@ psk_suites_anon(_) -> ?TLS_PSK_WITH_3DES_EDE_CBC_SHA, ?TLS_ECDHE_PSK_WITH_RC4_128_SHA, ?TLS_DHE_PSK_WITH_RC4_128_SHA, - ?TLS_PSK_WITH_RC4_128_SHA]. + ?TLS_PSK_WITH_RC4_128_SHA]; +psk_suites_anon(0) -> + []. %%-------------------------------------------------------------------- --spec srp_suites() -> [ssl_cipher_format:cipher_suite()]. +-spec srp_suites(tls_record:tls_version()) -> [ssl_cipher_format:cipher_suite()]. %% %% Description: Returns a list of the SRP cipher suites, only supported %% if explicitly set by user. %%-------------------------------------------------------------------- -srp_suites() -> +srp_suites({3,0}) -> + []; +srp_suites(_) -> [?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA, ?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA, ?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, @@ -429,12 +433,14 @@ srp_suites() -> ?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA]. %%-------------------------------------------------------------------- --spec srp_suites_anon() -> [ssl_cipher_format:cipher_suite()]. +-spec srp_suites_anon(tls_record:tls_version()) -> [ssl_cipher_format:cipher_suite()]. %% %% Description: Returns a list of the SRP anonymous cipher suites, only supported %% if explicitly set by user. %%-------------------------------------------------------------------- -srp_suites_anon() -> +srp_suites_anon({3,0}) -> + []; +srp_suites_anon(_) -> [?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA, ?TLS_SRP_SHA_WITH_AES_128_CBC_SHA, ?TLS_SRP_SHA_WITH_AES_256_CBC_SHA]. @@ -923,6 +929,12 @@ signature_scheme(rsa_pss_pss_sha384) -> ?RSA_PSS_PSS_SHA384; signature_scheme(rsa_pss_pss_sha512) -> ?RSA_PSS_PSS_SHA512; signature_scheme(rsa_pkcs1_sha1) -> ?RSA_PKCS1_SHA1; signature_scheme(ecdsa_sha1) -> ?ECDSA_SHA1; +%% Handling legacy signature algorithms +signature_scheme({Hash0, Sign0}) -> + Hash = hash_algorithm(Hash0), + Sign = sign_algorithm(Sign0), + <<?UINT16(SigAlg)>> = <<?BYTE(Hash),?BYTE(Sign)>>, + SigAlg; signature_scheme(?RSA_PKCS1_SHA256) -> rsa_pkcs1_sha256; signature_scheme(?RSA_PKCS1_SHA384) -> rsa_pkcs1_sha384; signature_scheme(?RSA_PKCS1_SHA512) -> rsa_pkcs1_sha512; @@ -962,18 +974,30 @@ scheme_to_components(rsa_pss_pss_sha256) -> {sha256, rsa_pss_pss, undefined}; scheme_to_components(rsa_pss_pss_sha384) -> {sha384, rsa_pss_pss, undefined}; scheme_to_components(rsa_pss_pss_sha512) -> {sha512, rsa_pss_pss, undefined}; scheme_to_components(rsa_pkcs1_sha1) -> {sha1, rsa_pkcs1, undefined}; -scheme_to_components(ecdsa_sha1) -> {sha1, ecdsa, undefined}. +scheme_to_components(ecdsa_sha1) -> {sha1, ecdsa, undefined}; +%% Handling legacy signature algorithms +scheme_to_components({Hash,Sign}) -> {Hash, Sign, undefined}. -%% TODO: Add support for EC and RSA-SSA signatures -signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha1WithRSAEncryption}) -> - rsa_pkcs1_sha1; +%% TODO: Add support for ed25519, ed448, rsa_pss* signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha256WithRSAEncryption}) -> rsa_pkcs1_sha256; signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha384WithRSAEncryption}) -> rsa_pkcs1_sha384; signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha512WithRSAEncryption}) -> - rsa_pkcs1_sha512. + rsa_pkcs1_sha512; +signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'ecdsa-with-SHA256'}) -> + ecdsa_secp256r1_sha256; +signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'ecdsa-with-SHA384'}) -> + ecdsa_secp384r1_sha384; +signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'ecdsa-with-SHA512'}) -> + ecdsa_secp512r1_sha512; +signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'sha-1WithRSAEncryption'}) -> + rsa_pkcs1_sha1; +signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha1WithRSAEncryption}) -> + rsa_pkcs1_sha1; +signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'ecdsa-with-SHA1'}) -> + ecdsa_sha1. %% RFC 5246: 6.2.3.2. CBC Block Cipher diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index a5f754d2e3..2483509228 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -124,7 +124,7 @@ handshake(#sslsocket{pid = [Pid|_]} = Socket, Timeout) -> connected -> {ok, Socket}; {ok, Ext} -> - {ok, Socket, Ext}; + {ok, Socket, no_records(Ext)}; Error -> Error end. @@ -328,34 +328,33 @@ prf(ConnectionPid, Secret, Label, Seed, WantedLength) -> %%==================================================================== %% Alert and close handling %%==================================================================== -handle_own_alert(Alert, _, StateName, +handle_own_alert(Alert0, _, StateName, #state{static_env = #static_env{role = Role, protocol_cb = Connection}, ssl_options = SslOpts} = State) -> try %% Try to tell the other side - send_alert(Alert, StateName, State) + send_alert(Alert0, StateName, State) catch _:_ -> %% Can crash if we are in a uninitialized state ignore end, try %% Try to tell the local user - log_alert(SslOpts#ssl_options.log_level, Role, - Connection:protocol_name(), StateName, - Alert#alert{role = Role}), + Alert = Alert0#alert{role = Role}, + log_alert(SslOpts#ssl_options.log_level, Role, Connection:protocol_name(), StateName, Alert), handle_normal_shutdown(Alert,StateName, State) catch _:_ -> ok end, {stop, {shutdown, own_alert}, State}. -handle_normal_shutdown(Alert, _, #state{static_env = #static_env{role = Role, - socket = Socket, - transport_cb = Transport, - protocol_cb = Connection, - tracker = Tracker}, - handshake_env = #handshake_env{renegotiation = {false, first}}, - start_or_recv_from = StartFrom} = State) -> +handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role, + socket = Socket, + transport_cb = Transport, + protocol_cb = Connection, + tracker = Tracker}, + handshake_env = #handshake_env{renegotiation = {false, first}}, + start_or_recv_from = StartFrom} = State) -> Pids = Connection:pids(State), - alert_user(Pids, Transport, Tracker,Socket, StartFrom, Alert, Role, Connection); + alert_user(Pids, Transport, Tracker,Socket, StartFrom, Alert, Role, StateName, Connection); handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role, socket = Socket, @@ -366,9 +365,9 @@ handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = socket_options = Opts, start_or_recv_from = RecvFrom} = State) -> Pids = Connection:pids(State), - alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, Connection). + alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, StateName, Connection). -handle_alert(#alert{level = ?FATAL} = Alert, StateName, +handle_alert(#alert{level = ?FATAL} = Alert0, StateName, #state{static_env = #static_env{role = Role, socket = Socket, host = Host, @@ -382,11 +381,11 @@ handle_alert(#alert{level = ?FATAL} = Alert, StateName, session = Session, socket_options = Opts} = State) -> invalidate_session(Role, Host, Port, Session), + Alert = Alert0#alert{role = opposite_role(Role)}, log_alert(SslOpts#ssl_options.log_level, Role, Connection:protocol_name(), - StateName, Alert#alert{role = opposite_role(Role)}), + StateName, Alert), Pids = Connection:pids(State), - alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, - opposite_role(Role), Connection), + alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role, StateName, Connection), {stop, {shutdown, normal}, State}; handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, @@ -396,13 +395,14 @@ handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, StateName, State) -> handle_normal_shutdown(Alert, StateName, State), {stop,{shutdown, peer_close}, State}; -handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, +handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert0, StateName, #state{static_env = #static_env{role = Role, protocol_cb = Connection}, handshake_env = #handshake_env{renegotiation = {true, internal}}, ssl_options = SslOpts} = State) -> + Alert = Alert0#alert{role = opposite_role(Role)}, log_alert(SslOpts#ssl_options.log_level, Role, - Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), + Connection:protocol_name(), StateName, Alert), handle_normal_shutdown(Alert, StateName, State), {stop,{shutdown, peer_close}, State}; @@ -614,7 +614,8 @@ read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) -> <<SizeA:32, DataA:SizeA/binary, SizeB:32, DataB:SizeB/binary, SizeC:32, DataC:SizeC/binary, - SizeD:32, DataD:SizeD/binary, Rest/binary>> -> + SizeD:32, DataD:SizeD/binary, Rest/binary>> + when 0 < SizeA, 0 < SizeB, 0 < SizeC, 0 < SizeD -> %% We have 4 complete packets in the first binary erlang:dist_ctrl_put_data(DHandle, DataA), erlang:dist_ctrl_put_data(DHandle, DataB), @@ -624,7 +625,8 @@ read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) -> DHandle, Front0, BufferSize - (4*4+SizeA+SizeB+SizeC+SizeD), Rear0, Rest); <<SizeA:32, DataA:SizeA/binary, SizeB:32, DataB:SizeB/binary, - SizeC:32, DataC:SizeC/binary, Rest/binary>> -> + SizeC:32, DataC:SizeC/binary, Rest/binary>> + when 0 < SizeA, 0 < SizeB, 0 < SizeC -> %% We have 3 complete packets in the first binary erlang:dist_ctrl_put_data(DHandle, DataA), erlang:dist_ctrl_put_data(DHandle, DataB), @@ -632,7 +634,8 @@ read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) -> read_application_dist_data( DHandle, Front0, BufferSize - (3*4+SizeA+SizeB+SizeC), Rear0, Rest); <<SizeA:32, DataA:SizeA/binary, - SizeB:32, DataB:SizeB/binary, Rest/binary>> -> + SizeB:32, DataB:SizeB/binary, Rest/binary>> + when 0 < SizeA, 0 < SizeB -> %% We have 2 complete packets in the first binary erlang:dist_ctrl_put_data(DHandle, DataA), erlang:dist_ctrl_put_data(DHandle, DataB), @@ -643,13 +646,13 @@ read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) -> %% Basic one packet code path <<Size:32, Data:Size/binary, Rest/binary>> -> %% We have a complete packet in the first binary - erlang:dist_ctrl_put_data(DHandle, Data), + 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data), read_application_dist_data(DHandle, Front0, BufferSize - (4+Size), Rear0, Rest); <<Size:32, FirstData/binary>> when 4+Size =< BufferSize -> %% We have a complete packet in the buffer %% - fetch the missing content from the buffer front {Data,Front,Rear} = iovec_from_front(Size - byte_size(FirstData), Front0, Rear0, [FirstData]), - erlang:dist_ctrl_put_data(DHandle, Data), + 0 < Size andalso erlang:dist_ctrl_put_data(DHandle, Data), read_application_dist_data(DHandle, Front, BufferSize - (4+Size), Rear); <<Bin/binary>> -> %% In OTP-21 the match context reuse optimization fails if we use Bin0 in recursion, so here we @@ -665,23 +668,61 @@ read_application_dist_data(DHandle, Front0, BufferSize, Rear0, Bin0) -> %% contains enough data to maybe form a packet %% - fetch a tiny binary from the buffer front to complete the length field {LengthField,Front,Rear} = - iovec_from_front(4 - byte_size(IncompleteLengthField), Front0, Rear0, [IncompleteLengthField]), + case IncompleteLengthField of + <<>> -> + iovec_from_front(4, Front0, Rear0, []); + _ -> + iovec_from_front( + 4 - byte_size(IncompleteLengthField), Front0, Rear0, [IncompleteLengthField]) + end, LengthBin = iolist_to_binary(LengthField), read_application_dist_data(DHandle, Front, BufferSize, Rear, LengthBin); <<IncompleteLengthField/binary>> -> %% We do not have enough data in the buffer to even form a length field - await more data - {[IncompleteLengthField|Front0],BufferSize,Rear0} + case IncompleteLengthField of + <<>> -> + {Front0,BufferSize,Rear0}; + _ -> + {[IncompleteLengthField|Front0],BufferSize,Rear0} + end end end. +iovec_from_front(0, Front, Rear, Acc) -> + {lists:reverse(Acc),Front,Rear}; iovec_from_front(Size, [], Rear, Acc) -> - iovec_from_front(Size, lists:reverse(Rear), [], Acc); + case Rear of + %% Avoid lists:reverse/1 for simple cases. + %% Case clause for [] to avoid infinite loop. + [_] -> + iovec_from_front(Size, Rear, [], Acc); + [Bin2,Bin1] -> + iovec_from_front(Size, [Bin1,Bin2], [], Acc); + [Bin3,Bin2,Bin1] -> + iovec_from_front(Size, [Bin1,Bin2,Bin3], [], Acc); + [_,_,_|_] = Rear -> + iovec_from_front(Size, lists:reverse(Rear), [], Acc) + end; +iovec_from_front(Size, [Bin|Front], Rear, []) -> + case Bin of + <<Last:Size/binary>> -> % Just enough + {[Last],Front,Rear}; + <<Last:Size/binary, Rest/binary>> -> % More than enough, split here + {[Last],[Rest|Front],Rear}; + <<>> -> % Not enough, skip empty binaries + iovec_from_front(Size, Front, Rear, []); + <<_/binary>> -> % Not enough + BinSize = byte_size(Bin), + iovec_from_front(Size - BinSize, Front, Rear, [Bin]) + end; iovec_from_front(Size, [Bin|Front], Rear, Acc) -> case Bin of <<Last:Size/binary>> -> % Just enough {lists:reverse(Acc, [Last]),Front,Rear}; <<Last:Size/binary, Rest/binary>> -> % More than enough, split here {lists:reverse(Acc, [Last]),[Rest|Front],Rear}; + <<>> -> % Not enough, skip empty binaries + iovec_from_front(Size, Front, Rear, Acc); <<_/binary>> -> % Not enough BinSize = byte_size(Bin), iovec_from_front(Size - BinSize, Front, Rear, [Bin|Acc]) @@ -709,6 +750,7 @@ handle_session(#server_hello{cipher_suite = CipherSuite, {ExpectNPN, Protocol} = case Protocol0 of undefined -> + {false, CurrentProtocol}; _ -> {ProtoExt =:= npn, Protocol0} @@ -1194,7 +1236,7 @@ cipher(internal, #next_protocol{selected_protocol = SelectedProtocol}, #state{static_env = #static_env{role = server}, handshake_env = #handshake_env{expecting_finished = true, expecting_next_protocol_negotiation = true} = HsEnv} = State, Connection) -> - Connection:next_event(?FUNCTION_NAME, no_record, + Connection:next_event(?FUNCTION_NAME, no_record, State#state{handshake_env = HsEnv#handshake_env{negotiated_protocol = SelectedProtocol, expecting_next_protocol_negotiation = false}}); cipher(internal, #change_cipher_spec{type = <<1>>}, #state{handshake_env = HsEnv, connection_states = ConnectionStates0} = @@ -1233,10 +1275,17 @@ connection({call, From}, {connection_information, false}, State, _) -> Info = connection_info(State), hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Info}}]); connection({call, From}, negotiated_protocol, - #state{handshake_env = #handshake_env{negotiated_protocol = undefined}} = State, _) -> + #state{handshake_env = #handshake_env{alpn = undefined, + negotiated_protocol = undefined}} = State, _) -> hibernate_after(?FUNCTION_NAME, State, [{reply, From, {error, protocol_not_negotiated}}]); connection({call, From}, negotiated_protocol, - #state{handshake_env = #handshake_env{negotiated_protocol = SelectedProtocol}} = State, _) -> + #state{handshake_env = #handshake_env{alpn = undefined, + negotiated_protocol = SelectedProtocol}} = State, _) -> + hibernate_after(?FUNCTION_NAME, State, + [{reply, From, {ok, SelectedProtocol}}]); +connection({call, From}, negotiated_protocol, + #state{handshake_env = #handshake_env{alpn = SelectedProtocol, + negotiated_protocol = undefined}} = State, _) -> hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, SelectedProtocol}}]); connection({call, From}, Msg, State, Connection) -> @@ -1445,7 +1494,7 @@ handle_info({ErrorTag, Socket, econnaborted}, StateName, } = State) when StateName =/= connection -> Pids = Connection:pids(State), alert_user(Pids, Transport, Tracker,Socket, - StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, Connection), + StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, StateName, Connection), {stop, {shutdown, normal}, State}; handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{socket = Socket, @@ -2907,22 +2956,22 @@ send_user(Pid, Msg) -> Pid ! Msg, ok. -alert_user(Pids, Transport, Tracker, Socket, connection, Opts, Pid, From, Alert, Role, Connection) -> - alert_user(Pids, Transport, Tracker, Socket, Opts#socket_options.active, Pid, From, Alert, Role, Connection); -alert_user(Pids, Transport, Tracker, Socket,_, _, _, From, Alert, Role, Connection) -> - alert_user(Pids, Transport, Tracker, Socket, From, Alert, Role, Connection). +alert_user(Pids, Transport, Tracker, Socket, connection, Opts, Pid, From, Alert, Role, StateName, Connection) -> + alert_user(Pids, Transport, Tracker, Socket, Opts#socket_options.active, Pid, From, Alert, Role, StateName, Connection); +alert_user(Pids, Transport, Tracker, Socket,_, _, _, From, Alert, Role, StateName, Connection) -> + alert_user(Pids, Transport, Tracker, Socket, From, Alert, Role, StateName, Connection). -alert_user(Pids, Transport, Tracker, Socket, From, Alert, Role, Connection) -> - alert_user(Pids, Transport, Tracker, Socket, false, no_pid, From, Alert, Role, Connection). +alert_user(Pids, Transport, Tracker, Socket, From, Alert, Role, StateName, Connection) -> + alert_user(Pids, Transport, Tracker, Socket, false, no_pid, From, Alert, Role, StateName, Connection). -alert_user(_, _, _, _, false = Active, Pid, From, Alert, Role, _) when From =/= undefined -> +alert_user(_, _, _, _, false = Active, Pid, From, Alert, Role, StateName, Connection) when From =/= undefined -> %% If there is an outstanding ssl_accept | recv %% From will be defined and send_or_reply will %% send the appropriate error message. - ReasonCode = ssl_alert:reason_code(Alert, Role), + ReasonCode = ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName), send_or_reply(Active, Pid, From, {error, ReasonCode}); -alert_user(Pids, Transport, Tracker, Socket, Active, Pid, From, Alert, Role, Connection) -> - case ssl_alert:reason_code(Alert, Role) of +alert_user(Pids, Transport, Tracker, Socket, Active, Pid, From, Alert, Role, StateName, Connection) -> + case ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName) of closed -> send_or_reply(Active, Pid, From, {ssl_closed, Connection:socket(Pids, Transport, Socket, Tracker)}); @@ -2933,11 +2982,11 @@ alert_user(Pids, Transport, Tracker, Socket, Active, Pid, From, Alert, Role, Con log_alert(Level, Role, ProtocolName, StateName, #alert{role = Role} = Alert) -> Txt = ssl_alert:own_alert_txt(Alert), - Report = io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt]), + Report = ssl_alert:alert_txt(ProtocolName, Role, StateName, Txt), ssl_logger:notice(Level, Report); log_alert(Level, Role, ProtocolName, StateName, Alert) -> Txt = ssl_alert:alert_txt(Alert), - Report = io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt]), + Report = ssl_alert:alert_txt(ProtocolName, Role, StateName, Txt), ssl_logger:notice(Level, Report). invalidate_session(client, Host, Port, Session) -> @@ -3000,3 +3049,8 @@ new_emulated([], EmOpts) -> EmOpts; new_emulated(NewEmOpts, _) -> NewEmOpts. + +no_records(Extensions) -> + maps:map(fun(_, Value) -> + ssl_handshake:extension_value(Value) + end, Extensions). diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index ff7207a8ce..844368c761 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -66,6 +66,7 @@ sni_hostname = undefined, expecting_next_protocol_negotiation = false ::boolean(), next_protocol = undefined :: undefined | binary(), + alpn = undefined, %% Used in TLS 1.3 negotiated_protocol, hashsign_algorithm = {undefined, undefined}, cert_hashsign_algorithm = {undefined, undefined}, @@ -76,7 +77,7 @@ srp_params :: #srp_user{} | secret_printout() | 'undefined', public_key_info :: ssl_handshake:public_key_info() | 'undefined', premaster_secret :: binary() | secret_printout() | 'undefined', - server_psk_identity :: binary() | 'undefined' % server psk identity hint + server_psk_identity :: binary() | 'undefined' % server psk identity hint }). -record(connection_env, { diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 7b34991f4f..bd2efa9fbb 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -58,7 +58,7 @@ ]). %% Encode --export([encode_handshake/2, encode_hello_extensions/1, encode_extensions/1, encode_extensions/2, +-export([encode_handshake/2, encode_hello_extensions/2, encode_extensions/1, encode_extensions/2, encode_client_protocol_negotiation/2, encode_protocols_advertised_on_server/1]). %% Decode -export([decode_handshake/3, decode_vector/1, decode_hello_extensions/4, decode_extensions/3, @@ -76,7 +76,8 @@ handle_client_hello_extensions/9, %% Returns server hello extensions handle_server_hello_extensions/9, select_curve/2, select_curve/3, select_hashsign/4, select_hashsign/5, - select_hashsign_algs/3, empty_extensions/2, add_server_share/3 + select_hashsign_algs/3, empty_extensions/2, add_server_share/3, + add_alpn/2, add_selected_version/1, decode_alpn/1 ]). -export([get_cert_params/1, @@ -98,8 +99,8 @@ hello_request() -> #hello_request{}. %%-------------------------------------------------------------------- --spec server_hello(binary(), ssl_record:ssl_version(), ssl_record:connection_states(), - Extension::map()) -> #server_hello{}. +%%-spec server_hello(binary(), ssl_record:ssl_version(), ssl_record:connection_states(), +%% Extension::map()) -> #server_hello{}. %% %% Description: Creates a server hello message. %%-------------------------------------------------------------------- @@ -363,7 +364,7 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, CertDbHandle, CertDbRef) end catch - error:{badmatch,{asn1, Asn1Reason}} -> + error:{badmatch,{error, {asn1, Asn1Reason}}} -> %% ASN-1 decode of certificate somehow failed ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, {failed_to_decode_certificate, Asn1Reason}); error:OtherReason -> @@ -533,14 +534,14 @@ encode_handshake(#next_protocol{selected_protocol = SelectedProtocol}, _Version) PaddingLength = 32 - ((byte_size(SelectedProtocol) + 2) rem 32), {?NEXT_PROTOCOL, <<?BYTE((byte_size(SelectedProtocol))), SelectedProtocol/binary, ?BYTE(PaddingLength), 0:(PaddingLength * 8)>>}; -encode_handshake(#server_hello{server_version = {Major, Minor}, +encode_handshake(#server_hello{server_version = {Major, Minor} = Version, random = Random, session_id = Session_ID, cipher_suite = CipherSuite, compression_method = Comp_method, extensions = Extensions}, _Version) -> SID_length = byte_size(Session_ID), - ExtensionsBin = encode_hello_extensions(Extensions), + ExtensionsBin = encode_hello_extensions(Extensions, Version), {?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SID_length), Session_ID/binary, CipherSuite/binary, ?BYTE(Comp_method), ExtensionsBin/binary>>}; @@ -588,7 +589,9 @@ encode_handshake(#certificate_verify{signature = BinSig, hashsign_algorithm = Ha encode_handshake(#finished{verify_data = VerifyData}, _Version) -> {?FINISHED, VerifyData}. -encode_hello_extensions(Extensions) -> +encode_hello_extensions(_, {3, 0}) -> + <<>>; +encode_hello_extensions(Extensions, _) -> encode_extensions(hello_extensions_list(Extensions), <<>>). encode_extensions(Exts) -> @@ -706,7 +709,25 @@ encode_extensions([#key_share_server_hello{server_share = ServerShare0} | Rest], encode_extensions([#key_share_hello_retry_request{selected_group = Group0} | Rest], Acc) -> Group = tls_v1:group_to_enum(Group0), encode_extensions(Rest, <<?UINT16(?KEY_SHARE_EXT), - ?UINT16(2), ?UINT16(Group), Acc/binary>>). + ?UINT16(2), ?UINT16(Group), Acc/binary>>); +encode_extensions([#psk_key_exchange_modes{ke_modes = KEModes0} | Rest], Acc) -> + KEModes = encode_psk_key_exchange_modes(KEModes0), + KEModesLen = byte_size(KEModes), + ExtLen = KEModesLen + 1, + encode_extensions(Rest, <<?UINT16(?PSK_KEY_EXCHANGE_MODES_EXT), + ?UINT16(ExtLen), ?BYTE(KEModesLen), KEModes/binary, Acc/binary>>); +encode_extensions([#pre_shared_key_client_hello{ + offered_psks = #offered_psks{ + identities = Identities0, + binders = Binders0} = PSKs} | Rest], Acc) -> + Identities = encode_psk_identities(Identities0), + Binders = encode_psk_binders(Binders0), + Len = byte_size(Identities) + byte_size(Binders), + encode_extensions(Rest, <<?UINT16(?PRE_SHARED_KEY_EXT), + ?UINT16(Len), Identities/binary, Binders/binary, Acc/binary>>); +encode_extensions([#pre_shared_key_server_hello{selected_identity = Identity} | Rest], Acc) -> + encode_extensions(Rest, <<?UINT16(?PRE_SHARED_KEY_EXT), + ?UINT16(2), ?UINT16(Identity), Acc/binary>>). encode_client_protocol_negotiation(undefined, _) -> @@ -1165,6 +1186,13 @@ add_server_share(hello_retry_request, Extensions, Extensions#{key_share => #key_share_hello_retry_request{ selected_group = Group}}. +add_alpn(Extensions, ALPN0) -> + ALPN = encode_alpn([ALPN0], false), + Extensions#{alpn => ALPN}. + +add_selected_version(Extensions) -> + SupportedVersions = #server_hello_selected_version{selected_version = {3,4}}, + Extensions#{server_hello_selected_version => SupportedVersions}. kse_remove_private_key(#key_share_entry{ group = Group, @@ -1186,10 +1214,7 @@ signature_algs_ext(undefined) -> signature_algs_ext(SignatureSchemes0) -> %% The SSL option signature_algs contains both hash-sign algorithms (tuples) and %% signature schemes (atoms) if TLS 1.3 is configured. - %% Filter out all hash-sign tuples when creating the signature_algs extension. - %% (TLS 1.3 specific record type) - SignatureSchemes = lists:filter(fun is_atom/1, SignatureSchemes0), - #signature_algorithms{signature_scheme_list = SignatureSchemes}. + #signature_algorithms{signature_scheme_list = SignatureSchemes0}. signature_algs_cert(undefined) -> undefined; @@ -1251,6 +1276,8 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression, %% We also ignore the ALPN extension during renegotiation (see encode_alpn/2). [Protocol] when not Renegotiation -> {ConnectionStates, alpn, Protocol}; + [_] when Renegotiation -> + {ConnectionStates, alpn, undefined}; undefined -> NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined), Protocol = handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation), @@ -1474,7 +1501,20 @@ extension_value(#next_protocol_negotiation{extension_data = Data}) -> extension_value(#srp{username = Name}) -> Name; extension_value(#renegotiation_info{renegotiated_connection = Data}) -> - Data. + Data; +extension_value(#signature_algorithms{signature_scheme_list = Schemes}) -> + Schemes; +extension_value(#signature_algorithms_cert{signature_scheme_list = Schemes}) -> + Schemes; +extension_value(#key_share_client_hello{client_shares = ClientShares}) -> + ClientShares; +extension_value(#key_share_server_hello{server_share = ServerShare}) -> + ServerShare; +extension_value(#client_hello_versions{versions = Versions}) -> + Versions; +extension_value(#server_hello_selected_version{selected_version = SelectedVersion}) -> + SelectedVersion. + %%-------------------------------------------------------------------- %%% Internal functions @@ -2075,6 +2115,41 @@ encode_key_share_entry(#key_share_entry{ Len = byte_size(KeyExchange), <<?UINT16((tls_v1:group_to_enum(Group))),?UINT16(Len),KeyExchange/binary>>. +encode_psk_key_exchange_modes(KEModes) -> + encode_psk_key_exchange_modes(lists:reverse(KEModes), <<>>). +%% +encode_psk_key_exchange_modes([], Acc) -> + Acc; +encode_psk_key_exchange_modes([psk_ke|T], Acc) -> + encode_psk_key_exchange_modes(T, <<?BYTE(?PSK_KE),Acc/binary>>); +encode_psk_key_exchange_modes([psk_dhe_ke|T], Acc) -> + encode_psk_key_exchange_modes(T, <<?BYTE(?PSK_DHE_KE),Acc/binary>>). + + +encode_psk_identities(Identities) -> + encode_psk_identities(Identities, <<>>). +%% +encode_psk_identities([], Acc) -> + Len = byte_size(Acc), + <<?UINT16(Len), Acc/binary>>; +encode_psk_identities([#psk_identity{ + identity = Identity, + obfuscated_ticket_age = Age}|T], Acc) -> + IdLen = byte_size(Identity), + encode_psk_identities(T, <<Acc/binary,?UINT16(IdLen),Identity/binary,Age/binary>>). + + +encode_psk_binders(Binders) -> + encode_psk_binders(Binders, <<>>). +%% +encode_psk_binders([], Acc) -> + Len = byte_size(Acc), + <<?UINT16(Len), Acc/binary>>; +encode_psk_binders([Binder|T], Acc) -> + Len = byte_size(Binder), + encode_psk_binders(T, <<Acc/binary,?BYTE(Len),Binder/binary>>). + + hello_extensions_list(HelloExtensions) -> [Ext || {_, Ext} <- maps:to_list(HelloExtensions), Ext =/= undefined]. @@ -2427,6 +2502,33 @@ decode_extensions(<<?UINT16(?KEY_SHARE_EXT), ?UINT16(Len), #key_share_hello_retry_request{ selected_group = tls_v1:enum_to_group(Group)}}); +decode_extensions(<<?UINT16(?PSK_KEY_EXCHANGE_MODES_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) -> + <<?BYTE(PLen),KEModes:PLen/binary>> = ExtData, + decode_extensions(Rest, Version, MessageType, + Acc#{psk_key_exchange_modes => + #psk_key_exchange_modes{ + ke_modes = decode_psk_key_exchange_modes(KEModes)}}); + +decode_extensions(<<?UINT16(?PRE_SHARED_KEY_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, + Version, MessageType = client_hello, Acc) -> + <<?UINT16(IdLen),Identities:IdLen/binary,?UINT16(BLen),Binders:BLen/binary>> = ExtData, + decode_extensions(Rest, Version, MessageType, + Acc#{pre_shared_key => + #pre_shared_key_client_hello{ + offered_psks = #offered_psks{ + identities = decode_psk_identities(Identities), + binders = decode_psk_binders(Binders)}}}); + +decode_extensions(<<?UINT16(?PRE_SHARED_KEY_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, + Version, MessageType = server_hello, Acc) -> + <<?UINT16(Identity)>> = ExtData, + decode_extensions(Rest, Version, MessageType, + Acc#{pre_shared_key => + #pre_shared_key_server_hello{ + selected_identity = Identity}}); %% Ignore data following the ClientHello (i.e., %% extensions) if not understood. @@ -2486,6 +2588,38 @@ decode_protocols(<<?BYTE(Len), Protocol:Len/binary, Rest/binary>>, Acc) -> decode_protocols(_Bytes, _Acc) -> {error, invalid_protocols}. + +decode_psk_key_exchange_modes(KEModes) -> + decode_psk_key_exchange_modes(KEModes, []). +%% +decode_psk_key_exchange_modes(<<>>, Acc) -> + lists:reverse(Acc); +decode_psk_key_exchange_modes(<<?BYTE(?PSK_KE), Rest/binary>>, Acc) -> + decode_psk_key_exchange_modes(Rest, [psk_ke|Acc]); +decode_psk_key_exchange_modes(<<?BYTE(?PSK_DHE_KE), Rest/binary>>, Acc) -> + decode_psk_key_exchange_modes(Rest, [psk_dhe_ke|Acc]). + + +decode_psk_identities(Identities) -> + decode_psk_identities(Identities, []). +%% +decode_psk_identities(<<>>, Acc) -> + lists:reverse(Acc); +decode_psk_identities(<<?UINT16(Len), Identity:Len/binary, Age:4/binary, Rest/binary>>, Acc) -> + decode_psk_identities(Rest, [#psk_identity{ + identity = Identity, + obfuscated_ticket_age = Age}|Acc]). + + +decode_psk_binders(Binders) -> + decode_psk_binders(Binders, []). +%% +decode_psk_binders(<<>>, Acc) -> + lists:reverse(Acc); +decode_psk_binders(<<?BYTE(Len), Binder:Len/binary, Rest/binary>>, Acc) -> + decode_psk_binders(Rest, [Binder|Acc]). + + %% encode/decode stream of certificate data to/from list of certificate data certs_to_list(ASN1Certs) -> certs_to_list(ASN1Certs, []). @@ -2652,7 +2786,7 @@ filter_unavailable_ecc_suites(_, Suites) -> handle_renegotiation_extension(Role, RecordCB, Version, Info, Random, NegotiatedCipherSuite, ClientCipherSuites, Compression, ConnectionStates0, Renegotiation, SecureRenegotation) -> - {ok, ConnectionStates} = handle_renegotiation_info(RecordCB, Role, Info, ConnectionStates0, + {ok, ConnectionStates} = handle_renegotiation_info(Version, RecordCB, Role, Info, ConnectionStates0, Renegotiation, SecureRenegotation, ClientCipherSuites), hello_pending_connection_states(RecordCB, Role, @@ -2922,11 +3056,11 @@ renegotiation_info(_RecordCB, server, ConnectionStates, true) -> #renegotiation_info{renegotiated_connection = undefined} end. -handle_renegotiation_info(_RecordCB, _, #renegotiation_info{renegotiated_connection = ?byte(0)}, +handle_renegotiation_info(_, _RecordCB, _, #renegotiation_info{renegotiated_connection = ?byte(0)}, ConnectionStates, false, _, _) -> {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; -handle_renegotiation_info(_RecordCB, server, undefined, ConnectionStates, _, _, CipherSuites) -> +handle_renegotiation_info(_, _RecordCB, server, undefined, ConnectionStates, _, _, CipherSuites) -> case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of true -> {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; @@ -2934,10 +3068,10 @@ handle_renegotiation_info(_RecordCB, server, undefined, ConnectionStates, _, _, {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)} end; -handle_renegotiation_info(_RecordCB, _, undefined, ConnectionStates, false, _, _) -> +handle_renegotiation_info(_, _RecordCB, _, undefined, ConnectionStates, false, _, _) -> {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)}; -handle_renegotiation_info(_RecordCB, client, #renegotiation_info{renegotiated_connection = ClientServerVerify}, +handle_renegotiation_info(_, _RecordCB, client, #renegotiation_info{renegotiated_connection = ClientServerVerify}, ConnectionStates, true, _, _) -> ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), CData = maps:get(client_verify_data, ConnectionState), @@ -2948,7 +3082,7 @@ handle_renegotiation_info(_RecordCB, client, #renegotiation_info{renegotiated_co false -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, client_renegotiation)) end; -handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify}, +handle_renegotiation_info(_, _RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify}, ConnectionStates, true, _, CipherSuites) -> case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of @@ -2964,11 +3098,13 @@ handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_co throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation)) end end; +handle_renegotiation_info({3,0}, _RecordCB, client, undefined, ConnectionStates, true, _SecureRenegotation, _) -> + {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; -handle_renegotiation_info(RecordCB, client, undefined, ConnectionStates, true, SecureRenegotation, _) -> +handle_renegotiation_info(_, RecordCB, client, undefined, ConnectionStates, true, SecureRenegotation, _) -> handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation); -handle_renegotiation_info(RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) -> +handle_renegotiation_info(_, RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) -> case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of true -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv})); @@ -3022,7 +3158,7 @@ empty_extensions({3,4}, client_hello) -> %% padding => undefined, key_share => undefined, pre_shared_key => undefined, - %% psk_key_exhange_modes => undefined, + psk_key_exchange_modes => undefined, %% early_data => undefined, %% cookie => undefined, client_hello_versions => undefined, @@ -3046,6 +3182,13 @@ empty_extensions({3,4}, server_hello) -> key_share => undefined, pre_shared_key => undefined }; +empty_extensions({3,4}, hello_retry_request) -> + #{server_hello_selected_version => undefined, + key_share => undefined, + pre_shared_key => undefined + }; +empty_extensions({3,0}, _) -> + empty_extensions(); empty_extensions(_, server_hello) -> #{renegotiation_info => undefined, alpn => undefined, diff --git a/lib/ssl/src/ssl_logger.erl b/lib/ssl/src/ssl_logger.erl index 987693b96b..514a4464bc 100644 --- a/lib/ssl/src/ssl_logger.erl +++ b/lib/ssl/src/ssl_logger.erl @@ -200,6 +200,11 @@ parse_handshake(Direction, #encrypted_extensions{} = EncryptedExtensions) -> Header = io_lib:format("~s Handshake, EncryptedExtensions", [header_prefix(Direction)]), Message = io_lib:format("~p", [?rec_info(encrypted_extensions, EncryptedExtensions)]), + {Header, Message}; +parse_handshake(Direction, #new_session_ticket{} = NewSessionTicket) -> + Header = io_lib:format("~s Post-Handshake, NewSessionTicket", + [header_prefix(Direction)]), + Message = io_lib:format("~p", [?rec_info(new_session_ticket, NewSessionTicket)]), {Header, Message}. diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index a05858221a..3998f03519 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -31,6 +31,7 @@ -include("tls_connection.hrl"). -include("tls_handshake.hrl"). +-include("tls_handshake_1_3.hrl"). -include("ssl_alert.hrl"). -include("tls_record.hrl"). -include("ssl_cipher.hrl"). @@ -62,7 +63,7 @@ close/5, protocol_name/0]). %% Data handling --export([next_record/1, socket/4, setopts/3, getopts/3]). +-export([socket/4, setopts/3, getopts/3]). %% gen_statem state functions -export([init/3, error/3, downgrade/3, %% Initiation and take down states @@ -161,32 +162,60 @@ pids(#state{protocol_specific = #{sender := Sender}}) -> %%==================================================================== %% State transition handling %%==================================================================== -next_record(#state{handshake_env = +next_record(_, #state{handshake_env = #handshake_env{unprocessed_handshake_events = N} = HsEnv} = State) when N > 0 -> {no_record, State#state{handshake_env = HsEnv#handshake_env{unprocessed_handshake_events = N-1}}}; -next_record(#state{protocol_buffers = - #protocol_buffers{tls_cipher_texts = [_|_] = CipherTexts}, - connection_states = ConnectionStates, - ssl_options = #ssl_options{padding_check = Check}} = State) -> +next_record(_, #state{protocol_buffers = + #protocol_buffers{tls_cipher_texts = [_|_] = CipherTexts}, + connection_states = ConnectionStates, + ssl_options = #ssl_options{padding_check = Check}} = State) -> next_record(State, CipherTexts, ConnectionStates, Check); -next_record(#state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []}, - protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec, - static_env = #static_env{socket = Socket, - close_tag = CloseTag, - transport_cb = Transport} - } = State) -> - case tls_socket:setopts(Transport, Socket, [{active, N}]) of - ok -> - {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}}; - _ -> - self() ! {CloseTag, Socket}, - {no_record, State} - end; -next_record(State) -> +next_record(connection, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []}, + protocol_specific = #{active_n_toggle := true} + } = State) -> + %% If ssl application user is not reading data wait to activate socket + flow_ctrl(State); + +next_record(_, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []}, + protocol_specific = #{active_n_toggle := true} + } = State) -> + activate_socket(State); +next_record(_, State) -> {no_record, State}. + +flow_ctrl(#state{user_data_buffer = {_,Size,_}, + socket_options = #socket_options{active = false}, + bytes_to_read = undefined} = State) when Size =/= 0 -> + {no_record, State}; +flow_ctrl(#state{user_data_buffer = {_,Size,_}, + socket_options = #socket_options{active = false}, + bytes_to_read = 0} = State) when Size =/= 0 -> + {no_record, State}; +flow_ctrl(#state{user_data_buffer = {_,Size,_}, + socket_options = #socket_options{active = false}, + bytes_to_read = BytesToRead} = State) when (Size >= BytesToRead) andalso + (BytesToRead > 0) -> + {no_record, State}; +flow_ctrl(State) -> + activate_socket(State). + + +activate_socket(#state{protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec, + static_env = #static_env{socket = Socket, + close_tag = CloseTag, + transport_cb = Transport} + } = State) -> + case tls_socket:setopts(Transport, Socket, [{active, N}]) of + ok -> + {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}}; + _ -> + self() ! {CloseTag, Socket}, + {no_record, State} + end. + %% Decipher next record and concatenate consecutive ?APPLICATION_DATA records into one %% next_record(State, CipherTexts, ConnectionStates, Check) -> @@ -224,31 +253,20 @@ next_record_done(#state{protocol_buffers = Buffers} = State, CipherTexts, Connec State#state{protocol_buffers = Buffers#protocol_buffers{tls_cipher_texts = CipherTexts}, connection_states = ConnectionStates}}. - next_event(StateName, Record, State) -> next_event(StateName, Record, State, []). %% next_event(StateName, no_record, State0, Actions) -> - case next_record(State0) of + case next_record(StateName, State0) of {no_record, State} -> - {next_state, StateName, State, Actions}; - {#ssl_tls{} = Record, State} -> - {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; - #alert{} = Alert -> - Version = State0#state.connection_env#connection_env.negotiated_version, - ssl_connection:handle_own_alert(Alert, Version, StateName, State0) + ssl_connection:hibernate_after(StateName, State, Actions); + {Record, State} -> + next_event(StateName, Record, State, Actions) end; -next_event(StateName, Record, State, Actions) -> - case Record of - no_record -> - {next_state, StateName, State, Actions}; - #ssl_tls{} = Record -> - {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; - #alert{} = Alert -> - Version = State#state.connection_env#connection_env.negotiated_version, - ssl_connection:handle_own_alert(Alert, Version, StateName, State) - end. - +next_event(StateName, #ssl_tls{} = Record, State, Actions) -> + {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; +next_event(StateName, #alert{} = Alert, State, Actions) -> + {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}. %%% TLS record protocol level application data messages handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, @@ -272,12 +290,8 @@ handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, Stat {stop, _, _} = Stop-> Stop; {Record, State1} -> - case next_event(StateName, Record, State1) of - {next_state, StateName, State, Actions} -> - ssl_connection:hibernate_after(StateName, State, Actions); - {stop, _, _} = Stop -> - Stop - end + {next_state, StateName, State, Actions} = next_event(StateName, Record, State1), + ssl_connection:hibernate_after(StateName, State, Actions) end; %%% TLS record protocol level handshake messages handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data}, @@ -303,8 +317,7 @@ handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data}, _ -> HsEnv = State#state.handshake_env, {next_state, StateName, - State#state{protocol_buffers = Buffers, - handshake_env = + State#state{handshake_env = HsEnv#handshake_env{unprocessed_handshake_events = unprocessed_events(Events)}}, Events} end @@ -323,7 +336,9 @@ handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName, handle_alerts(Alerts, {next_state, StateName, State}); [] -> ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, empty_alert), - Version, StateName, State) + Version, StateName, State); + #alert{} = Alert -> + ssl_connection:handle_own_alert(Alert, Version, StateName, State) catch _:_ -> ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, alert_decode_error), @@ -381,6 +396,7 @@ queue_handshake(Handshake, #state{handshake_env = #handshake_env{tls_handshake_h handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}, flight_buffer = Flight0 ++ [BinHandshake]}. + send_handshake_flight(#state{static_env = #static_env{socket = Socket, transport_cb = Transport}, flight_buffer = Flight} = State0) -> @@ -646,10 +662,16 @@ hello(internal, #server_hello{} = Hello, case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of #alert{} = Alert -> %%TODO ssl_connection:handle_own_alert(Alert, ReqVersion, hello, - State#state{connection_env = CEnv#connection_env{negotiated_version = ReqVersion}}); + State#state{connection_env = + CEnv#connection_env{negotiated_version = ReqVersion}}); + %% Legacy TLS 1.2 and older {Version, NewId, ConnectionStates, ProtoExt, Protocol} -> ssl_connection:handle_session(Hello, - Version, NewId, ConnectionStates, ProtoExt, Protocol, State) + Version, NewId, ConnectionStates, ProtoExt, Protocol, State); + %% TLS 1.3 + {next_state, wait_sh} -> + %% Continue in TLS 1.3 'wait_sh' state + {next_state, wait_sh, State, [{next_event, internal, Hello}]} end; hello(info, Event, State) -> gen_info(Event, ?FUNCTION_NAME, State); @@ -790,6 +812,11 @@ connection(internal, #client_hello{}, State = reinit_handshake_data(State0), next_event(?FUNCTION_NAME, no_record, State); +connection(internal, #new_session_ticket{}, State) -> + %% TLS 1.3 + %% Drop NewSessionTicket (currently not supported) + next_event(?FUNCTION_NAME, no_record, State); + connection(Type, Event, State) -> ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). @@ -1049,7 +1076,7 @@ next_tls_record(Data, StateName, case tls_record:get_tls_records(Data, Versions, Buf0, SslOpts) of {Records, Buf1} -> CT1 = CT0 ++ Records, - next_record(State0#state{protocol_buffers = + next_record(StateName, State0#state{protocol_buffers = Buffers#protocol_buffers{tls_record_buffer = Buf1, tls_cipher_texts = CT1}}); #alert{} = Alert -> @@ -1150,7 +1177,6 @@ encode_handshake(Handshake, Version, ConnectionStates0, Hist0) -> encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) -> tls_record:encode_change_cipher_spec(Version, ConnectionStates). --spec decode_alerts(binary()) -> list(). decode_alerts(Bin) -> ssl_alert:decode(Bin). @@ -1273,9 +1299,10 @@ maybe_generate_client_shares(#ssl_options{ versions = [Version|_], supported_groups = #supported_groups{ - supported_groups = Groups}}) + supported_groups = [Group|_]}}) when Version =:= {3,4} -> - ssl_cipher:generate_client_shares(Groups); + %% Generate only key_share entry for the most preferred group + ssl_cipher:generate_client_shares([Group]); maybe_generate_client_shares(_) -> undefined. diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl index 701a5860c2..117e4f059d 100644 --- a/lib/ssl/src/tls_connection_1_3.erl +++ b/lib/ssl/src/tls_connection_1_3.erl @@ -112,13 +112,15 @@ negotiated/4, wait_cert/4, wait_cv/4, - wait_finished/4 + wait_finished/4, + wait_sh/4, + wait_ee/4, + wait_cert_cr/4 ]). -start(internal, #change_cipher_spec{}, State0, _Module) -> - {Record, State} = tls_connection:next_record(State0), - tls_connection:next_event(?FUNCTION_NAME, Record, State); +start(internal, #change_cipher_spec{}, State, _Module) -> + tls_connection:next_event(?FUNCTION_NAME, no_record, State); start(internal, #client_hello{} = Hello, State0, _Module) -> case tls_handshake_1_3:do_start(Hello, State0) of #alert{} = Alert -> @@ -128,13 +130,19 @@ start(internal, #client_hello{} = Hello, State0, _Module) -> {State, negotiated} -> {next_state, negotiated, State, [{next_event, internal, start_handshake}]} end; +start(internal, #server_hello{} = ServerHello, State0, _Module) -> + case tls_handshake_1_3:do_start(ServerHello, State0) of + #alert{} = Alert -> + ssl_connection:handle_own_alert(Alert, {3,4}, start, State0); + {State, NextState} -> + {next_state, NextState, State, []} + end; start(Type, Msg, State, Connection) -> ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). -negotiated(internal, #change_cipher_spec{}, State0, _Module) -> - {Record, State} = tls_connection:next_record(State0), - tls_connection:next_event(?FUNCTION_NAME, Record, State); +negotiated(internal, #change_cipher_spec{}, State, _Module) -> + tls_connection:next_event(?FUNCTION_NAME, no_record, State); negotiated(internal, Message, State0, _Module) -> case tls_handshake_1_3:do_negotiated(Message, State0) of #alert{} = Alert -> @@ -144,41 +152,36 @@ negotiated(internal, Message, State0, _Module) -> end. -wait_cert(internal, #change_cipher_spec{}, State0, _Module) -> - {Record, State} = tls_connection:next_record(State0), - tls_connection:next_event(?FUNCTION_NAME, Record, State); +wait_cert(internal, #change_cipher_spec{}, State, _Module) -> + tls_connection:next_event(?FUNCTION_NAME, no_record, State); wait_cert(internal, #certificate_1_3{} = Certificate, State0, _Module) -> case tls_handshake_1_3:do_wait_cert(Certificate, State0) of {#alert{} = Alert, State} -> ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert, State); - {State1, NextState} -> - {Record, State} = tls_connection:next_record(State1), - tls_connection:next_event(NextState, Record, State) + {State, NextState} -> + tls_connection:next_event(NextState, no_record, State) end; wait_cert(Type, Msg, State, Connection) -> ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). -wait_cv(internal, #change_cipher_spec{}, State0, _Module) -> - {Record, State} = tls_connection:next_record(State0), - tls_connection:next_event(?FUNCTION_NAME, Record, State); +wait_cv(internal, #change_cipher_spec{}, State, _Module) -> + tls_connection:next_event(?FUNCTION_NAME, no_record, State); wait_cv(internal, #certificate_verify_1_3{} = CertificateVerify, State0, _Module) -> case tls_handshake_1_3:do_wait_cv(CertificateVerify, State0) of {#alert{} = Alert, State} -> ssl_connection:handle_own_alert(Alert, {3,4}, wait_cv, State); - {State1, NextState} -> - {Record, State} = tls_connection:next_record(State1), - tls_connection:next_event(NextState, Record, State) + {State, NextState} -> + tls_connection:next_event(NextState, no_record, State) end; wait_cv(Type, Msg, State, Connection) -> ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). -wait_finished(internal, #change_cipher_spec{}, State0, _Module) -> - {Record, State} = tls_connection:next_record(State0), - tls_connection:next_event(?FUNCTION_NAME, Record, State); +wait_finished(internal, #change_cipher_spec{}, State, _Module) -> + tls_connection:next_event(?FUNCTION_NAME, no_record, State); wait_finished(internal, #finished{} = Finished, State0, Module) -> case tls_handshake_1_3:do_wait_finished(Finished, State0) of @@ -190,3 +193,52 @@ wait_finished(internal, end; wait_finished(Type, Msg, State, Connection) -> ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). + + +wait_sh(internal, #change_cipher_spec{}, State, _Module) -> + tls_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_sh(internal, #server_hello{} = Hello, State0, _Module) -> + case tls_handshake_1_3:do_wait_sh(Hello, State0) of + #alert{} = Alert -> + ssl_connection:handle_own_alert(Alert, {3,4}, wait_sh, State0); + {State1, start, ServerHello} -> + %% hello_retry_request: go to start + {next_state, start, State1, [{next_event, internal, ServerHello}]}; + {State1, wait_ee} -> + tls_connection:next_event(wait_ee, no_record, State1) + end; +wait_sh(Type, Msg, State, Connection) -> + ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). + + +wait_ee(internal, #change_cipher_spec{}, State, _Module) -> + tls_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_ee(internal, #encrypted_extensions{} = EE, State0, _Module) -> + case tls_handshake_1_3:do_wait_ee(EE, State0) of + #alert{} = Alert -> + ssl_connection:handle_own_alert(Alert, {3,4}, wait_ee, State0); + {State1, NextState} -> + tls_connection:next_event(NextState, no_record, State1) + end; +wait_ee(Type, Msg, State, Connection) -> + ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). + + +wait_cert_cr(internal, #change_cipher_spec{}, State, _Module) -> + tls_connection:next_event(?FUNCTION_NAME, no_record, State); +wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0, _Module) -> + case tls_handshake_1_3:do_wait_cert_cr(Certificate, State0) of + {#alert{} = Alert, State} -> + ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert_cr, State); + {State1, NextState} -> + tls_connection:next_event(NextState, no_record, State1) + end; +wait_cert_cr(internal, #certificate_request_1_3{} = CertificateRequest, State0, _Module) -> + case tls_handshake_1_3:do_wait_cert_cr(CertificateRequest, State0) of + #alert{} = Alert -> + ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert_cr, State0); + {State1, NextState} -> + tls_connection:next_event(NextState, no_record, State1) + end; +wait_cert_cr(Type, Msg, State, Connection) -> + ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index 2480e05097..37265e0759 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -105,7 +105,7 @@ client_hello(Host, Port, ConnectionStates, {tls_record:tls_version(), {resumed | new, #session{}}, ssl_record:connection_states(), binary() | undefined, HelloExt::map(), {ssl:hash(), ssl:sign_algo()} | - undefined} | #alert{}. + undefined} | {atom(), atom()} |#alert{}. %% %% Description: Handles a received hello message %%-------------------------------------------------------------------- @@ -148,29 +148,48 @@ hello(#server_hello{server_version = {Major, Minor}, %% %% - If "supported_version" is present (ServerHello): %% - Abort handshake with an "illegal_parameter" alert -hello(#server_hello{server_version = Version, +hello(#server_hello{server_version = LegacyVersion, + random = Random, + cipher_suite = CipherSuite, + compression_method = Compression, + session_id = SessionId, extensions = #{server_hello_selected_version := - #server_hello_selected_version{selected_version = Version}} + #server_hello_selected_version{selected_version = Version} = HelloExt} }, - #ssl_options{versions = SupportedVersions}, - _ConnectionStates0, _Renegotiation) -> - case tls_record:is_higher({3,4}, Version) of + #ssl_options{versions = SupportedVersions} = SslOpt, + ConnectionStates0, Renegotiation) -> + %% In TLS 1.3, the TLS server indicates its version using the "supported_versions" extension + %% (Section 4.2.1), and the legacy_version field MUST be set to 0x0303, which is the version + %% number for TLS 1.2. + %% The "supported_versions" extension is supported from TLS 1.2. + case LegacyVersion > {3,3} orelse + LegacyVersion =:= {3,3} andalso Version < {3,3} of true -> ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); false -> case tls_record:is_acceptable_version(Version, SupportedVersions) of true -> - %% Implement TLS 1.3 statem ??? - ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION); + case Version of + {3,3} -> + %% TLS 1.2 ServerHello with "supported_versions" (special case) + handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, + Compression, HelloExt, SslOpt, + ConnectionStates0, Renegotiation); + {3,4} -> + %% TLS 1.3 + {next_state, wait_sh} + end; false -> ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) end end; -hello(#server_hello{server_version = Version, random = Random, +hello(#server_hello{server_version = Version, + random = Random, cipher_suite = CipherSuite, compression_method = Compression, - session_id = SessionId, extensions = HelloExt}, + session_id = SessionId, + extensions = HelloExt}, #ssl_options{versions = SupportedVersions} = SslOpt, ConnectionStates0, Renegotiation) -> case tls_record:is_acceptable_version(Version, SupportedVersions) of @@ -360,7 +379,7 @@ do_hello(Version, Versions, CipherSuites, Hello, SslOpts, Info, Renegotiation) - %%-------------------------------------------------------------------- enc_handshake(#hello_request{}, {3, N}) when N < 4 -> {?HELLO_REQUEST, <<>>}; -enc_handshake(#client_hello{client_version = {Major, Minor}, +enc_handshake(#client_hello{client_version = {Major, Minor} = Version, random = Random, session_id = SessionID, cipher_suites = CipherSuites, @@ -371,7 +390,7 @@ enc_handshake(#client_hello{client_version = {Major, Minor}, CmLength = byte_size(BinCompMethods), BinCipherSuites = list_to_binary(CipherSuites), CsLength = byte_size(BinCipherSuites), - ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions), + ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions, Version), {?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SIDLength), SessionID/binary, diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index 8a4ad922e1..c29366e717 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -39,14 +39,23 @@ %% Create handshake messages -export([certificate/5, certificate_verify/4, - encrypted_extensions/0, - server_hello/4]). + encrypted_extensions/1]). -export([do_start/2, do_negotiated/2, do_wait_cert/2, do_wait_cv/2, - do_wait_finished/2]). + do_wait_finished/2, + do_wait_sh/2, + do_wait_ee/2, + do_wait_cert_cr/2]). + + +%% crypto:hash(sha256, "HelloRetryRequest"). +-define(HELLO_RETRY_REQUEST_RANDOM, <<207,33,173,116,229,154,97,17, + 190,29,140,2,30,101,184,145, + 194,162,17,22,122,187,140,94, + 7,158,9,226,200,168,51,156>>). %%==================================================================== %% Create handshake messages @@ -64,11 +73,24 @@ server_hello(MsgType, SessionId, KeyShare, ConnectionStates) -> extensions = Extensions }. + +%% The server's extensions MUST contain "supported_versions". +%% Additionally, it SHOULD contain the minimal set of extensions +%% necessary for the client to generate a correct ClientHello pair. As +%% with the ServerHello, a HelloRetryRequest MUST NOT contain any +%% extensions that were not first offered by the client in its +%% ClientHello, with the exception of optionally the "cookie" (see +%% Section 4.2.2) extension. +server_hello_extensions(hello_retry_request = MsgType, KeyShare) -> + SupportedVersions = #server_hello_selected_version{selected_version = {3,4}}, + Extensions = #{server_hello_selected_version => SupportedVersions}, + ssl_handshake:add_server_share(MsgType, Extensions, KeyShare); server_hello_extensions(MsgType, KeyShare) -> SupportedVersions = #server_hello_selected_version{selected_version = {3,4}}, Extensions = #{server_hello_selected_version => SupportedVersions}, ssl_handshake:add_server_share(MsgType, Extensions, KeyShare). + server_hello_random(server_hello, #security_parameters{server_random = Random}) -> Random; %% For reasons of backward compatibility with middleboxes (see @@ -79,13 +101,17 @@ server_hello_random(server_hello, #security_parameters{server_random = Random}) %% CF 21 AD 74 E5 9A 61 11 BE 1D 8C 02 1E 65 B8 91 %% C2 A2 11 16 7A BB 8C 5E 07 9E 09 E2 C8 A8 33 9C server_hello_random(hello_retry_request, _) -> - crypto:hash(sha256, "HelloRetryRequest"). + ?HELLO_RETRY_REQUEST_RANDOM. -%% TODO: implement support for encrypted_extensions -encrypted_extensions() -> +encrypted_extensions(#state{handshake_env = #handshake_env{alpn = undefined}}) -> #encrypted_extensions{ extensions = #{} + }; +encrypted_extensions(#state{handshake_env = #handshake_env{alpn = ALPNProtocol}}) -> + Extensions = ssl_handshake:add_alpn(#{}, ALPNProtocol), + #encrypted_extensions{ + extensions = Extensions }. @@ -111,7 +137,7 @@ add_signature_algorithms_cert(Extensions, undefined) -> Extensions; add_signature_algorithms_cert(Extensions, SignAlgsCert) -> Extensions#{signature_algorithms_cert => - #signature_algorithms{signature_scheme_list = SignAlgsCert}}. + #signature_algorithms_cert{signature_scheme_list = SignAlgsCert}}. filter_tls13_algs(undefined) -> undefined; @@ -119,7 +145,6 @@ filter_tls13_algs(Algo) -> lists:filter(fun is_atom/1, Algo). -%% TODO: use maybe monad for error handling! %% enum { %% X509(0), %% RawPublicKey(2), @@ -142,18 +167,28 @@ filter_tls13_algs(Algo) -> %% opaque certificate_request_context<0..2^8-1>; %% CertificateEntry certificate_list<0..2^24-1>; %% } Certificate; -certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, server) -> +certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, Role) -> case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of {ok, _, Chain} -> CertList = chain_to_cert_list(Chain), %% If this message is in response to a CertificateRequest, the value of %% certificate_request_context in that message. Otherwise (in the case %%of server authentication), this field SHALL be zero length. - #certificate_1_3{ - certificate_request_context = <<>>, - certificate_list = CertList}; - {error, Error} -> - ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {server_has_no_suitable_certificates, Error}) + {ok, #certificate_1_3{ + certificate_request_context = <<>>, + certificate_list = CertList}}; + {error, Error} when Role =:= server -> + {error, {no_suitable_certificates, Error}}; + {error, _Error} when Role =:= client -> + %% The client MUST send a Certificate message if and only if the server + %% has requested client authentication via a CertificateRequest message + %% (Section 4.3.2). If the server requests client authentication but no + %% suitable certificate is available, the client MUST send a Certificate + %% message containing no certificates (i.e., with the "certificate_list" + %% field having length 0). + {ok, #certificate_1_3{ + certificate_request_context = <<>>, + certificate_list = []}} end. @@ -161,7 +196,7 @@ certificate_verify(PrivateKey, SignatureScheme, #state{connection_states = ConnectionStates, handshake_env = #handshake_env{ - tls_handshake_history = {Messages, _}}}, server) -> + tls_handshake_history = {Messages, _}}}, Role) -> #{security_parameters := SecParamsR} = ssl_record:pending_connection_state(ConnectionStates, write), #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR, @@ -173,11 +208,11 @@ certificate_verify(PrivateKey, SignatureScheme, %% Transcript-Hash uses the HKDF hash function defined by the cipher suite. THash = tls_v1:transcript_hash(Context, HKDFAlgo), + ContextString = context_string(Role), %% Digital signatures use the hash function defined by the selected signature %% scheme. - case sign(THash, <<"TLS 1.3, server CertificateVerify">>, - HashAlgo, PrivateKey) of + case sign(THash, ContextString, HashAlgo, PrivateKey) of {ok, Signature} -> {ok, #certificate_verify_1_3{ algorithm = SignatureScheme, @@ -252,6 +287,21 @@ encode_handshake(HandshakeMsg) -> %% Decode handshake %%==================================================================== + +decode_handshake(?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, + ?BYTE(SID_length), Session_ID:SID_length/binary, + Cipher_suite:2/binary, ?BYTE(Comp_method), + ?UINT16(ExtLen), Extensions:ExtLen/binary>>) + when Random =:= ?HELLO_RETRY_REQUEST_RANDOM -> + HelloExtensions = ssl_handshake:decode_hello_extensions(Extensions, {3,4}, {Major, Minor}, + hello_retry_request), + #server_hello{ + server_version = {Major,Minor}, + random = Random, + session_id = Session_ID, + cipher_suite = Cipher_suite, + compression_method = Comp_method, + extensions = HelloExtensions}; decode_handshake(?CERTIFICATE_REQUEST, <<?BYTE(0), ?UINT16(Size), EncExts:Size/binary>>) -> Exts = decode_extensions(EncExts, certificate_request), #certificate_request_1_3{ @@ -384,6 +434,15 @@ certificate_entry(DER) -> %% 79 %% 00 %% 0101010101010101010101010101010101010101010101010101010101010101 +sign(THash, Context, HashAlgo, #'ECPrivateKey'{} = PrivateKey) -> + Content = build_content(Context, THash), + try public_key:sign(Content, HashAlgo, PrivateKey) of + Signature -> + {ok, Signature} + catch + error:badarg -> + {error, badarg} + end; sign(THash, Context, HashAlgo, PrivateKey) -> Content = build_content(Context, THash), @@ -401,7 +460,16 @@ sign(THash, Context, HashAlgo, PrivateKey) -> end. -verify(THash, Context, HashAlgo, Signature, PublicKey) -> +verify(THash, Context, HashAlgo, Signature, {?'id-ecPublicKey', PublicKey, PublicKeyParams}) -> + Content = build_content(Context, THash), + try public_key:verify(Content, HashAlgo, Signature, {PublicKey, PublicKeyParams}) of + Result -> + {ok, Result} + catch + error:badarg -> + {error, badarg} + end; +verify(THash, Context, HashAlgo, Signature, {?rsaEncryption, PublicKey, _PubKeyParams}) -> Content = build_content(Context, THash), %% The length of the Salt MUST be equal to the length of the output @@ -428,15 +496,17 @@ build_content(Context, THash) -> %%==================================================================== +%% TLS Server do_start(#client_hello{cipher_suites = ClientCiphers, session_id = SessionId, extensions = Extensions} = _Hello, #state{connection_states = _ConnectionStates0, ssl_options = #ssl_options{ciphers = ServerCiphers, signature_algs = ServerSignAlgs, - supported_groups = ServerGroups0}, + supported_groups = ServerGroups0, + alpn_preferred_protocols = ALPNPreferredProtocols, + honor_cipher_order = HonorCipherOrder}, session = #session{own_certificate = Cert}} = State0) -> - ClientGroups0 = maps:get(elliptic_curves, Extensions, undefined), ClientGroups = get_supported_groups(ClientGroups0), ServerGroups = get_supported_groups(ServerGroups0), @@ -444,23 +514,27 @@ do_start(#client_hello{cipher_suites = ClientCiphers, ClientShares0 = maps:get(key_share, Extensions, undefined), ClientShares = get_key_shares(ClientShares0), + ClientALPN0 = maps:get(alpn, Extensions, undefined), + ClientALPN = ssl_handshake:decode_alpn(ClientALPN0), + ClientSignAlgs = get_signature_scheme_list( maps:get(signature_algs, Extensions, undefined)), ClientSignAlgsCert = get_signature_scheme_list( maps:get(signature_algs_cert, Extensions, undefined)), - %% TODO: use library function if it exists - %% Init the maybe "monad" {Ref,Maybe} = maybe(), try + %% Handle ALPN extension if ALPN is configured + ALPNProtocol = Maybe(handle_alpn(ALPNPreferredProtocols, ClientALPN)), + %% If the server does not select a PSK, then the server independently selects a %% cipher suite, an (EC)DHE group and key share for key establishment, %% and a signature algorithm/certificate pair to authenticate itself to %% the client. - Cipher = Maybe(select_cipher_suite(ClientCiphers, ServerCiphers)), + Cipher = Maybe(select_cipher_suite(HonorCipherOrder, ClientCiphers, ServerCiphers)), Groups = Maybe(select_common_groups(ServerGroups, ClientGroups)), - Maybe(validate_key_share(ClientGroups, ClientShares)), + Maybe(validate_client_key_share(ClientGroups, ClientShares)), {PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert), @@ -479,8 +553,14 @@ do_start(#client_hello{cipher_suites = ClientCiphers, %% Generate server_share KeyShare = ssl_cipher:generate_server_share(Group), - State1 = update_start_state(State0, Cipher, KeyShare, SessionId, - Group, SelectedSignAlg, ClientPubKey), + State1 = update_start_state(State0, + #{cipher => Cipher, + key_share => KeyShare, + session_id => SessionId, + group => Group, + sign_alg => SelectedSignAlg, + peer_public_key => ClientPubKey, + alpn => ALPNProtocol}), %% 4.1.4. Hello Retry Request %% @@ -490,10 +570,7 @@ do_start(#client_hello{cipher_suites = ClientCiphers, %% the handshake. Maybe(send_hello_retry_request(State1, ClientPubKey, KeyShare, SessionId)) - %% TODO: - %% - session handling - %% - handle extensions: ALPN - %% (do not handle: NPN, srp, renegotiation_info, ec_point_formats) + %% TODO: session handling catch {Ref, {insufficient_security, no_suitable_groups}} -> @@ -505,7 +582,87 @@ do_start(#client_hello{cipher_suites = ClientCiphers, {Ref, {insufficient_security, no_suitable_signature_algorithm}} -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, "No suitable signature algorithm"); {Ref, {insufficient_security, no_suitable_public_key}} -> - ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key) + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key); + {Ref, no_application_protocol} -> + ?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL) + end; +%% TLS Client +do_start(#server_hello{cipher_suite = SelectedCipherSuite, + session_id = SessionId, + extensions = Extensions} = _ServerHello, + #state{static_env = #static_env{role = client, + host = Host, + port = Port, + transport_cb = Transport, + socket = Socket, + session_cache = Cache, + session_cache_cb = CacheCb}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}, + tls_handshake_history = _HHistory} = HsEnv, + connection_env = CEnv, + ssl_options = #ssl_options{ciphers = ClientCiphers, + supported_groups = ClientGroups0} = SslOpts, + session = #session{own_certificate = Cert} = Session0, + connection_states = ConnectionStates0 + } = State0) -> + ClientGroups = get_supported_groups(ClientGroups0), + + {Ref,Maybe} = maybe(), + try + ServerKeyShare = maps:get(key_share, Extensions, undefined), + SelectedGroup = get_selected_group(ServerKeyShare), + + %% Upon receipt of this extension in a HelloRetryRequest, the client + %% MUST verify that (1) the selected_group field corresponds to a group + %% which was provided in the "supported_groups" extension in the + %% original ClientHello and (2) the selected_group field does not + %% correspond to a group which was provided in the "key_share" extension + %% in the original ClientHello. If either of these checks fails, then + %% the client MUST abort the handshake with an "illegal_parameter" + %% alert. + Maybe(validate_selected_group(SelectedGroup, ClientGroups)), + + Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)), + + %% Otherwise, when sending the new ClientHello, the client MUST + %% replace the original "key_share" extension with one containing only a + %% new KeyShareEntry for the group indicated in the selected_group field + %% of the triggering HelloRetryRequest. + ClientKeyShare = ssl_cipher:generate_client_shares([SelectedGroup]), + Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, + Cache, CacheCb, Renegotiation, Cert, ClientKeyShare), + + HelloVersion = tls_record:hello_version(SslOpts#ssl_options.versions), + + %% Update state + State1 = update_start_state(State0, + #{cipher => SelectedCipherSuite, + key_share => ClientKeyShare, + session_id => SessionId, + group => SelectedGroup}), + + %% Replace ClientHello1 with a special synthetic handshake message + State2 = replace_ch1_with_message_hash(State1), + #state{handshake_env = #handshake_env{tls_handshake_history = HHistory}} = State2, + + {BinMsg, ConnectionStates, Handshake} = + tls_connection:encode_handshake(Hello, HelloVersion, ConnectionStates0, HHistory), + tls_socket:send(Transport, Socket, BinMsg), + ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'handshake', Hello), + ssl_logger:debug(SslOpts#ssl_options.log_level, outbound, 'record', BinMsg), + + State = State2#state{ + connection_states = ConnectionStates, + connection_env = CEnv#connection_env{negotiated_version = HelloVersion}, %% Requested version + session = Session0#session{session_id = Hello#client_hello.session_id}, + handshake_env = HsEnv#handshake_env{tls_handshake_history = Handshake}, + key_share = ClientKeyShare}, + + {State, wait_sh} + + catch + {Ref, {illegal_parameter, Reason}} -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, Reason) end. @@ -515,7 +672,7 @@ do_negotiated(start_handshake, own_certificate = OwnCert, ecc = SelectedGroup, sign_alg = SignatureScheme, - dh_public_value = ClientKey}, + dh_public_value = ClientPublicKey}, ssl_options = #ssl_options{} = SslOpts, key_share = KeyShare, handshake_env = #handshake_env{tls_handshake_history = _HHistory0}, @@ -526,6 +683,8 @@ do_negotiated(start_handshake, socket = _Socket, transport_cb = _Transport} } = State0) -> + ServerPrivateKey = get_server_private_key(KeyShare), + {Ref,Maybe} = maybe(), try @@ -536,12 +695,12 @@ do_negotiated(start_handshake, {State1, _} = tls_connection:send_handshake(ServerHello, State0), State2 = - calculate_handshake_secrets(ClientKey, SelectedGroup, KeyShare, State1), + calculate_handshake_secrets(ClientPublicKey, ServerPrivateKey, SelectedGroup, State1), State3 = ssl_record:step_encryption_state(State2), %% Create EncryptedExtensions - EncryptedExtensions = encrypted_extensions(), + EncryptedExtensions = encrypted_extensions(State2), %% Encode EncryptedExtensions State4 = tls_connection:queue_handshake(EncryptedExtensions, State3), @@ -550,7 +709,7 @@ do_negotiated(start_handshake, {State5, NextState} = maybe_send_certificate_request(State4, SslOpts), %% Create Certificate - Certificate = certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server), + Certificate = Maybe(certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server)), %% Encode Certificate State6 = tls_connection:queue_handshake(Certificate, State5), @@ -574,14 +733,16 @@ do_negotiated(start_handshake, catch {Ref, badarg} -> - ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {digitally_sign, badarg}) + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {digitally_sign, badarg}); + {Ref, {no_suitable_certificates, Reason}} -> + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {no_suitable_certificates, Reason}) end. do_wait_cert(#certificate_1_3{} = Certificate, State0) -> {Ref,Maybe} = maybe(), try - Maybe(process_client_certificate(Certificate, State0)) + Maybe(process_certificate(Certificate, State0)) catch {Ref, {certificate_required, State}} -> {?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required), State}; @@ -591,6 +752,8 @@ do_wait_cert(#certificate_1_3{} = Certificate, State0) -> {?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason), State}; {Ref, {{handshake_failure, Reason}, State}} -> {?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason), State}; + {Ref, {#alert{} = Alert, State}} -> + {Alert, State}; {#alert{} = Alert, State} -> {Alert, State} end. @@ -599,8 +762,8 @@ do_wait_cert(#certificate_1_3{} = Certificate, State0) -> do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, State0) -> {Ref,Maybe} = maybe(), try - Maybe(verify_signature_algorithm(State0, CertificateVerify)), - Maybe(verify_certificate_verify(State0, CertificateVerify)) + State1 = Maybe(verify_signature_algorithm(State0, CertificateVerify)), + Maybe(verify_certificate_verify(State1, CertificateVerify)) catch {Ref, {{bad_certificate, Reason}, State}} -> {?ALERT_REC(?FATAL, ?BAD_CERTIFICATE, {bad_certificate, Reason}), State}; @@ -610,20 +773,9 @@ do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, State0) -> {?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {handshake_failure, Reason}), State} end. - +%% TLS Server do_wait_finished(#finished{verify_data = VerifyData}, - #state{connection_states = _ConnectionStates0, - session = #session{session_id = _SessionId, - own_certificate = _OwnCert}, - ssl_options = #ssl_options{} = _SslOpts, - key_share = _KeyShare, - handshake_env = #handshake_env{tls_handshake_history = _HHistory0}, - static_env = #static_env{ - cert_db = _CertDbHandle, - cert_db_ref = _CertDbRef, - socket = _Socket, - transport_cb = _Transport} - } = State0) -> + #state{static_env = #static_env{role = server}} = State0) -> {Ref,Maybe} = maybe(), @@ -639,19 +791,237 @@ do_wait_finished(#finished{verify_data = VerifyData}, catch {Ref, decrypt_error} -> ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error) + end; +%% TLS Client +do_wait_finished(#finished{verify_data = _VerifyData}, + #state{static_env = #static_env{role = client}} = State0) -> + + {Ref,Maybe} = maybe(), + + try + %% Maybe(validate_client_finished(State0, VerifyData)), + + %% Maybe send Certificate + CertificateVerify + State1 = Maybe(maybe_queue_cert_cert_cv(State0)), + + Finished = finished(State1), + + %% Encode Finished + State2 = tls_connection:queue_handshake(Finished, State1), + + %% Send first flight + {State3, _} = tls_connection:send_handshake_flight(State2), + + State4 = calculate_traffic_secrets(State3), + + %% Configure traffic keys + ssl_record:step_encryption_state(State4) + + catch + {Ref, decrypt_error} -> + ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error); + {Ref, badarg} -> + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {digitally_sign, badarg}); + {Ref, {no_suitable_certificates, Reason}} -> + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {no_suitable_certificates, Reason}) + end. + + +do_wait_sh(#server_hello{cipher_suite = SelectedCipherSuite, + session_id = SessionId, + extensions = Extensions} = ServerHello, + #state{key_share = ClientKeyShare0, + ssl_options = #ssl_options{ciphers = ClientCiphers, + supported_groups = ClientGroups0}} = State0) -> + ClientGroups = get_supported_groups(ClientGroups0), + ServerKeyShare0 = maps:get(key_share, Extensions, undefined), + ClientKeyShare = get_key_shares(ClientKeyShare0), + + {Ref,Maybe} = maybe(), + try + %% Go to state 'start' if server replies with 'HelloRetryRequest'. + Maybe(maybe_hello_retry_request(ServerHello, State0)), + + ServerKeyShare = get_key_shares(ServerKeyShare0), + + Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)), + Maybe(validate_server_key_share(ClientGroups, ServerKeyShare)), + + %% Get server public key + {SelectedGroup, ServerPublicKey} = get_server_public_key(ServerKeyShare), + + {_, ClientPrivateKey} = get_client_private_key([SelectedGroup], ClientKeyShare), + + %% Update state + State1 = update_start_state(State0, + #{cipher => SelectedCipherSuite, + key_share => ClientKeyShare0, + session_id => SessionId, + group => SelectedGroup, + peer_public_key => ServerPublicKey}), + + State2 = calculate_handshake_secrets(ServerPublicKey, ClientPrivateKey, SelectedGroup, State1), + + State3 = ssl_record:step_encryption_state(State2), + + {State3, wait_ee} + + catch + {Ref, {State, StateName, ServerHello}} -> + {State, StateName, ServerHello}; + {Ref, {insufficient_security, no_suitable_groups}} -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups); + {Ref, illegal_parameter} -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); + {Ref, no_suitable_cipher} -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher); + {Ref, {insufficient_security, no_suitable_signature_algorithm}} -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, "No suitable signature algorithm"); + {Ref, {insufficient_security, no_suitable_public_key}} -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key) + end. + + +do_wait_ee(#encrypted_extensions{extensions = Extensions}, State0) -> + + ALPNProtocol0 = maps:get(alpn, Extensions, undefined), + ALPNProtocol = get_alpn(ALPNProtocol0), + + {Ref,_Maybe} = maybe(), + + try + %% Update state + #state{handshake_env = HsEnv} = State0, + State1 = State0#state{handshake_env = HsEnv#handshake_env{alpn = ALPNProtocol}}, + + {State1, wait_cert_cr} + catch + {Ref, {insufficient_security, no_suitable_groups}} -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups); + {Ref, illegal_parameter} -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); + {Ref, no_suitable_cipher} -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher); + {Ref, {insufficient_security, no_suitable_signature_algorithm}} -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, "No suitable signature algorithm"); + {Ref, {insufficient_security, no_suitable_public_key}} -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key) end. +do_wait_cert_cr(#certificate_1_3{} = Certificate, State0) -> + {Ref,Maybe} = maybe(), + try + Maybe(process_certificate(Certificate, State0)) + catch + {Ref, {certificate_required, _State}} -> + ?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required); + {Ref, {{certificate_unknown, Reason}, _State}} -> + ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, Reason); + {Ref, {{internal_error, Reason}, _State}} -> + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason); + {Ref, {{handshake_failure, Reason}, _State}} -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason); + {Ref, {#alert{} = Alert, State}} -> + {Alert, State} + end; +do_wait_cert_cr(#certificate_request_1_3{} = CertificateRequest, State0) -> + {Ref,Maybe} = maybe(), + try + Maybe(process_certificate_request(CertificateRequest, State0)) + catch + {Ref, {certificate_required, _State}} -> + ?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required); + {Ref, {{certificate_unknown, Reason}, _State}} -> + ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, Reason); + {Ref, {illegal_parameter, Reason}} -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, Reason); + {Ref, {{internal_error, Reason}, _State}} -> + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason); + {Ref, {{handshake_failure, Reason}, _State}} -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason) + end. + + + %% TODO: Remove this function! %% not_implemented(State, Reason) -> %% {error, {not_implemented, State, Reason}}. -%% + %% not_implemented(update_secrets, State0, Reason) -> %% State1 = calculate_traffic_secrets(State0), %% State = ssl_record:step_encryption_state(State1), %% {error, {not_implemented, State, Reason}}. +%% For reasons of backward compatibility with middleboxes (see +%% Appendix D.4), the HelloRetryRequest message uses the same structure +%% as the ServerHello, but with Random set to the special value of the +%% SHA-256 of "HelloRetryRequest": +%% +%% CF 21 AD 74 E5 9A 61 11 BE 1D 8C 02 1E 65 B8 91 +%% C2 A2 11 16 7A BB 8C 5E 07 9E 09 E2 C8 A8 33 9C +%% +%% Upon receiving a message with type server_hello, implementations MUST +%% first examine the Random value and, if it matches this value, process +%% it as described in Section 4.1.4). +maybe_hello_retry_request(#server_hello{random = ?HELLO_RETRY_REQUEST_RANDOM} = ServerHello, State0) -> + {error, {State0, start, ServerHello}}; +maybe_hello_retry_request(_, _) -> + ok. + + +maybe_queue_cert_cert_cv(#state{client_certificate_requested = false} = State) -> + {ok, State}; +maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0, + session = #session{session_id = _SessionId, + own_certificate = OwnCert}, + ssl_options = #ssl_options{} = _SslOpts, + key_share = _KeyShare, + handshake_env = #handshake_env{tls_handshake_history = _HHistory0}, + static_env = #static_env{ + role = client, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef, + socket = _Socket, + transport_cb = _Transport} + } = State0) -> + {Ref,Maybe} = maybe(), + try + %% Create Certificate + Certificate = Maybe(certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, client)), + + %% Encode Certificate + State1 = tls_connection:queue_handshake(Certificate, State0), + + %% Maybe create and queue CertificateVerify + State = Maybe(maybe_queue_cert_verify(Certificate, State1)), + {ok, State} + catch + {Ref, badarg} -> + {error, badarg} + end. + + +%% Clients MUST send this message whenever authenticating via a certificate +%% (i.e., when the Certificate message is non-empty). +maybe_queue_cert_verify(#certificate_1_3{certificate_list = []}, State) -> + {ok, State}; +maybe_queue_cert_verify(_Certificate, + #state{connection_states = _ConnectionStates0, + session = #session{sign_alg = SignatureScheme}, + connection_env = #connection_env{private_key = CertPrivateKey}, + static_env = #static_env{role = client} + } = State) -> + {Ref,Maybe} = maybe(), + try + CertificateVerify = Maybe(certificate_verify(CertPrivateKey, SignatureScheme, State, client)), + {ok, tls_connection:queue_handshake(CertificateVerify, State)} + catch + {Ref, badarg} -> + {error, badarg} + end. + %% Recipients of Finished messages MUST verify that the contents are %% correct and if incorrect MUST terminate the connection with a @@ -684,7 +1054,7 @@ send_hello_retry_request(#state{connection_states = ConnectionStates0} = State0, ServerHello = server_hello(hello_retry_request, SessionId, KeyShare, ConnectionStates0), {State1, _} = tls_connection:send_handshake(ServerHello, State0), - %% TODO: Fix handshake history! + %% Update handshake history State2 = replace_ch1_with_message_hash(State1), {ok, {State2, start}}; @@ -703,19 +1073,44 @@ maybe_send_certificate_request(State, #ssl_options{ {tls_connection:queue_handshake(CertificateRequest, State), wait_cert}. -process_client_certificate(#certificate_1_3{ - certificate_request_context = <<>>, - certificate_list = []}, - #state{ssl_options = - #ssl_options{ - fail_if_no_peer_cert = false}} = State) -> +process_certificate_request(#certificate_request_1_3{}, + #state{session = #session{own_certificate = undefined}} = State) -> + {ok, {State#state{client_certificate_requested = true}, wait_cert}}; + +process_certificate_request(#certificate_request_1_3{ + extensions = Extensions}, + #state{session = #session{own_certificate = Cert} = Session} = State) -> + ServerSignAlgs = get_signature_scheme_list( + maps:get(signature_algs, Extensions, undefined)), + ServerSignAlgsCert = get_signature_scheme_list( + maps:get(signature_algs_cert, Extensions, undefined)), + + {_PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert), + + %% Check if server supports signature algorithm of client certificate + case check_cert_sign_algo(SignAlgo, SignHash, ServerSignAlgs, ServerSignAlgsCert) of + ok -> + {ok, {State#state{client_certificate_requested = true}, wait_cert}}; + {error, _} -> + %% Certificate not supported: send empty certificate in state 'wait_finished' + {ok, {State#state{client_certificate_requested = true, + session = Session#session{own_certificate = undefined}}, wait_cert}} + end. + + +process_certificate(#certificate_1_3{ + certificate_request_context = <<>>, + certificate_list = []}, + #state{ssl_options = + #ssl_options{ + fail_if_no_peer_cert = false}} = State) -> {ok, {State, wait_finished}}; -process_client_certificate(#certificate_1_3{ - certificate_request_context = <<>>, - certificate_list = []}, - #state{ssl_options = - #ssl_options{ - fail_if_no_peer_cert = true}} = State0) -> +process_certificate(#certificate_1_3{ + certificate_request_context = <<>>, + certificate_list = []}, + #state{ssl_options = + #ssl_options{ + fail_if_no_peer_cert = true}} = State0) -> %% At this point the client believes that the connection is up and starts using %% its traffic secrets. In order to be able send an proper Alert to the client @@ -724,19 +1119,18 @@ process_client_certificate(#certificate_1_3{ State1 = calculate_traffic_secrets(State0), State = ssl_record:step_encryption_state(State1), {error, {certificate_required, State}}; -process_client_certificate(#certificate_1_3{certificate_list = Certs0}, - #state{ssl_options = - #ssl_options{signature_algs = SignAlgs, - signature_algs_cert = SignAlgsCert} = SslOptions, - static_env = - #static_env{ - role = Role, - host = Host, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, - crl_db = CRLDbHandle}} = State0) -> +process_certificate(#certificate_1_3{certificate_list = Certs0}, + #state{ssl_options = + #ssl_options{signature_algs = SignAlgs, + signature_algs_cert = SignAlgsCert} = SslOptions, + static_env = + #static_env{ + role = Role, + host = Host, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef, + crl_db = CRLDbHandle}} = State0) -> %% TODO: handle extensions! - %% Remove extensions from list of certificates! Certs = convert_certificate_chain(Certs0), case is_supported_signature_algorithm(Certs, SignAlgs, SignAlgsCert) of @@ -747,13 +1141,11 @@ process_client_certificate(#certificate_1_3{certificate_list = Certs0}, State = store_peer_cert(State0, PeerCert, PublicKeyInfo), {ok, {State, wait_cv}}; {error, Reason} -> - State1 = calculate_traffic_secrets(State0), - State = ssl_record:step_encryption_state(State1), + State = update_encryption_state(Role, State0), {error, {Reason, State}}; - #alert{} = Alert -> - State1 = calculate_traffic_secrets(State0), - State = ssl_record:step_encryption_state(State1), - {Alert, State} + {ok, #alert{} = Alert} -> + State = update_encryption_state(Role, State0), + {error, {Alert, State}} end; false -> State1 = calculate_traffic_secrets(State0), @@ -777,6 +1169,17 @@ is_supported_signature_algorithm([BinCert|_], SignAlgs0) -> lists:member(Scheme, SignAlgs). +%% Sets correct encryption state when sending Alerts in shared states that use different secrets. +%% - If client: use handshake secrets. +%% - If server: use traffic secrets as by this time the client's state machine +%% already stepped into the 'connection' state. +update_encryption_state(server, State0) -> + State1 = calculate_traffic_secrets(State0), + ssl_record:step_encryption_state(State1); +update_encryption_state(client, State) -> + State. + + validate_certificate_chain(Certs, CertDbHandle, CertDbRef, SslOptions, CRLDbHandle, Role, Host) -> ServerName = ssl_handshake:server_name(SslOptions#ssl_options.server_name_indication, Host, Role), [PeerCert | ChainCerts ] = Certs, @@ -797,12 +1200,12 @@ validate_certificate_chain(Certs, CertDbHandle, CertDbRef, SslOptions, CRLDbHand {ok, {PublicKeyInfo,_}} -> {ok, {PeerCert, PublicKeyInfo}}; {error, Reason} -> - ssl_handshake:handle_path_validation_error(Reason, PeerCert, ChainCerts, - SslOptions, Options, - CertDbHandle, CertDbRef) + {ok, ssl_handshake:handle_path_validation_error(Reason, PeerCert, ChainCerts, + SslOptions, Options, + CertDbHandle, CertDbRef)} end catch - error:{badmatch,{asn1, Asn1Reason}} -> + error:{badmatch,{error, {asn1, Asn1Reason}}} -> %% ASN-1 decode of certificate somehow failed {error, {certificate_unknown, {failed_to_decode_certificate, Asn1Reason}}}; error:OtherReason -> @@ -861,7 +1264,7 @@ message_hash(ClientHello1, HKDFAlgo) -> crypto:hash(HKDFAlgo, ClientHello1)]. -calculate_handshake_secrets(ClientKey, SelectedGroup, KeyShare, +calculate_handshake_secrets(PublicKey, PrivateKey, SelectedGroup, #state{connection_states = ConnectionStates, handshake_env = #handshake_env{ @@ -874,13 +1277,13 @@ calculate_handshake_secrets(ClientKey, SelectedGroup, KeyShare, %% Calculate handshake_secret PSK = binary:copy(<<0>>, ssl_cipher:hash_size(HKDFAlgo)), EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, PSK}), - PrivateKey = get_server_private_key(KeyShare), %% #'ECPrivateKey'{} - IKM = calculate_shared_secret(ClientKey, PrivateKey, SelectedGroup), + IKM = calculate_shared_secret(PublicKey, PrivateKey, SelectedGroup), HandshakeSecret = tls_v1:key_schedule(handshake_secret, HKDFAlgo, IKM, EarlySecret), %% Calculate [sender]_handshake_traffic_secret {Messages, _} = HHistory, + ClientHSTrafficSecret = tls_v1:client_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages)), ServerHSTrafficSecret = @@ -899,10 +1302,13 @@ calculate_handshake_secrets(ClientKey, SelectedGroup, KeyShare, ReadKey, ReadIV, ReadFinishedKey, WriteKey, WriteIV, WriteFinishedKey). -calculate_traffic_secrets(#state{connection_states = ConnectionStates, - handshake_env = - #handshake_env{ - tls_handshake_history = HHistory}} = State0) -> + +calculate_traffic_secrets(#state{ + static_env = #static_env{role = Role}, + connection_states = ConnectionStates, + handshake_env = + #handshake_env{ + tls_handshake_history = HHistory}} = State0) -> #{security_parameters := SecParamsR} = ssl_record:pending_connection_state(ConnectionStates, read), #security_parameters{prf_algorithm = HKDFAlgo, @@ -913,7 +1319,7 @@ calculate_traffic_secrets(#state{connection_states = ConnectionStates, tls_v1:key_schedule(master_secret, HKDFAlgo, HandshakeSecret), %% Get the correct list messages for the handshake context. - Messages = get_handshake_context(HHistory), + Messages = get_handshake_context(Role, HHistory), %% Calculate [sender]_application_traffic_secret_0 ClientAppTrafficSecret0 = @@ -942,11 +1348,6 @@ get_private_key(#key_share_entry{ {_, PrivateKey}}) -> PrivateKey. -%% TODO: implement EC keys -get_public_key({?'rsaEncryption', PublicKey, _}) -> - PublicKey. - - %% X25519, X448 calculate_shared_secret(OthersKey, MyKey, Group) when is_binary(OthersKey) andalso is_binary(MyKey) andalso @@ -966,9 +1367,11 @@ calculate_shared_secret(OthersKey, MyKey = #'ECPrivateKey'{}, _Group) public_key:compute_key(Point, MyKey). -update_pending_connection_states(#state{connection_states = - CS = #{pending_read := PendingRead0, - pending_write := PendingWrite0}} = State, +update_pending_connection_states(#state{ + static_env = #static_env{role = server}, + connection_states = + CS = #{pending_read := PendingRead0, + pending_write := PendingWrite0}} = State, HandshakeSecret, ReadKey, ReadIV, ReadFinishedKey, WriteKey, WriteIV, WriteFinishedKey) -> @@ -977,8 +1380,23 @@ update_pending_connection_states(#state{connection_states = PendingWrite = update_connection_state(PendingWrite0, HandshakeSecret, WriteKey, WriteIV, WriteFinishedKey), State#state{connection_states = CS#{pending_read => PendingRead, + pending_write => PendingWrite}}; +update_pending_connection_states(#state{ + static_env = #static_env{role = client}, + connection_states = + CS = #{pending_read := PendingRead0, + pending_write := PendingWrite0}} = State, + HandshakeSecret, + ReadKey, ReadIV, ReadFinishedKey, + WriteKey, WriteIV, WriteFinishedKey) -> + PendingRead = update_connection_state(PendingRead0, HandshakeSecret, + WriteKey, WriteIV, WriteFinishedKey), + PendingWrite = update_connection_state(PendingWrite0, HandshakeSecret, + ReadKey, ReadIV, ReadFinishedKey), + State#state{connection_states = CS#{pending_read => PendingRead, pending_write => PendingWrite}}. + update_connection_state(ConnectionState = #{security_parameters := SecurityParameters0}, HandshakeSecret, Key, IV, FinishedKey) -> %% Store secret @@ -988,11 +1406,24 @@ update_connection_state(ConnectionState = #{security_parameters := SecurityParam cipher_state => cipher_init(Key, IV, FinishedKey)}. +update_start_state(State, Map) -> + Cipher = maps:get(cipher, Map, undefined), + KeyShare = maps:get(key_share, Map, undefined), + SessionId = maps:get(session_id, Map, undefined), + Group = maps:get(group, Map, undefined), + SelectedSignAlg = maps:get(sign_alg, Map, undefined), + PeerPublicKey = maps:get(peer_public_key, Map, undefined), + ALPNProtocol = maps:get(alpn, Map, undefined), + update_start_state(State, Cipher, KeyShare, SessionId, + Group, SelectedSignAlg, PeerPublicKey, + ALPNProtocol). +%% update_start_state(#state{connection_states = ConnectionStates0, + handshake_env = #handshake_env{} = HsEnv, connection_env = CEnv, session = Session} = State, Cipher, KeyShare, SessionId, - Group, SelectedSignAlg, ClientPubKey) -> + Group, SelectedSignAlg, PeerPublicKey, ALPNProtocol) -> #{security_parameters := SecParamsR0} = PendingRead = maps:get(pending_read, ConnectionStates0), #{security_parameters := SecParamsW0} = PendingWrite = @@ -1003,11 +1434,12 @@ update_start_state(#state{connection_states = ConnectionStates0, ConnectionStates0#{pending_read => PendingRead#{security_parameters => SecParamsR}, pending_write => PendingWrite#{security_parameters => SecParamsW}}, State#state{connection_states = ConnectionStates, + handshake_env = HsEnv#handshake_env{alpn = ALPNProtocol}, key_share = KeyShare, session = Session#session{session_id = SessionId, ecc = Group, sign_alg = SelectedSignAlg, - dh_public_value = ClientPubKey, + dh_public_value = PeerPublicKey, cipher_suite = Cipher}, connection_env = CEnv#connection_env{negotiated_version = {3,4}}}. @@ -1071,25 +1503,41 @@ get_handshake_context_cv({[<<15,_/binary>>|Messages], _}) -> %% %% Drop all client messages from the front of the iolist using the property that %% incoming messages are binaries. -get_handshake_context({Messages, _}) -> - get_handshake_context(Messages); -get_handshake_context([H|T]) when is_binary(H) -> - get_handshake_context(T); -get_handshake_context(L) -> +get_handshake_context(server, {Messages, _}) -> + get_handshake_context_server(Messages); +get_handshake_context(client, {Messages, _}) -> + get_handshake_context_client(Messages). + +get_handshake_context_server([H|T]) when is_binary(H) -> + get_handshake_context_server(T); +get_handshake_context_server(L) -> + L. + + +get_handshake_context_client([H|T]) when is_list(H) -> + get_handshake_context_client(T); +get_handshake_context_client(L) -> L. +%% If the CertificateVerify message is sent by a server, the signature +%% algorithm MUST be one offered in the client's "signature_algorithms" +%% extension unless no valid certificate chain can be produced without +%% unsupported algorithms +%% %% If sent by a client, the signature algorithm used in the signature %% MUST be one of those present in the supported_signature_algorithms %% field of the "signature_algorithms" extension in the %% CertificateRequest message. -verify_signature_algorithm(#state{ssl_options = - #ssl_options{ - signature_algs = ServerSignAlgs}} = State0, - #certificate_verify_1_3{algorithm = ClientSignAlg}) -> - case lists:member(ClientSignAlg, ServerSignAlgs) of +verify_signature_algorithm(#state{ + static_env = #static_env{role = Role}, + ssl_options = + #ssl_options{ + signature_algs = LocalSignAlgs}} = State0, + #certificate_verify_1_3{algorithm = PeerSignAlg}) -> + case lists:member(PeerSignAlg, LocalSignAlgs) of true -> - ok; + {ok, maybe_update_selected_sign_alg(State0, PeerSignAlg, Role)}; false -> State1 = calculate_traffic_secrets(State0), State = ssl_record:step_encryption_state(State1), @@ -1098,11 +1546,19 @@ verify_signature_algorithm(#state{ssl_options = end. -verify_certificate_verify(#state{connection_states = ConnectionStates, - handshake_env = - #handshake_env{ - public_key_info = PublicKeyInfo, - tls_handshake_history = HHistory}} = State0, +maybe_update_selected_sign_alg(#state{session = Session} = State, SignAlg, client) -> + State#state{session = Session#session{sign_alg = SignAlg}}; +maybe_update_selected_sign_alg(State, _, _) -> + State. + + +verify_certificate_verify(#state{ + static_env = #static_env{role = Role}, + connection_states = ConnectionStates, + handshake_env = + #handshake_env{ + public_key_info = PublicKeyInfo, + tls_handshake_history = HHistory}} = State0, #certificate_verify_1_3{ algorithm = SignatureScheme, signature = Signature}) -> @@ -1120,12 +1576,11 @@ verify_certificate_verify(#state{connection_states = ConnectionStates, %% Transcript-Hash uses the HKDF hash function defined by the cipher suite. THash = tls_v1:transcript_hash(Context, HKDFAlgo), - PublicKey = get_public_key(PublicKeyInfo), + ContextString = peer_context_string(Role), %% Digital signatures use the hash function defined by the selected signature %% scheme. - case verify(THash, <<"TLS 1.3, client CertificateVerify">>, - HashAlgo, Signature, PublicKey) of + case verify(THash, ContextString, HashAlgo, Signature, PublicKeyInfo) of {ok, true} -> {ok, {State0, wait_finished}}; {ok, false} -> @@ -1139,6 +1594,19 @@ verify_certificate_verify(#state{connection_states = ConnectionStates, end. +context_string(server) -> + <<"TLS 1.3, server CertificateVerify">>; +context_string(client) -> + <<"TLS 1.3, client CertificateVerify">>. + + +%% Return context string for verifing peer signature +peer_context_string(server) -> + <<"TLS 1.3, client CertificateVerify">>; +peer_context_string(client) -> + <<"TLS 1.3, server CertificateVerify">>. + + %% If there is no overlap between the received %% "supported_groups" and the groups supported by the server, then the %% server MUST abort the handshake with a "handshake_failure" or an @@ -1172,14 +1640,36 @@ select_common_groups(ServerGroups, ClientGroups) -> %% for groups not listed in the client's "supported_groups" extension. %% Servers MAY check for violations of these rules and abort the %% handshake with an "illegal_parameter" alert if one is violated. -validate_key_share(_ ,[]) -> +validate_client_key_share(_ ,[]) -> ok; -validate_key_share([], _) -> +validate_client_key_share([], _) -> {error, illegal_parameter}; -validate_key_share([G|ClientGroups], [{_, G, _}|ClientShares]) -> - validate_key_share(ClientGroups, ClientShares); -validate_key_share([_|ClientGroups], [_|_] = ClientShares) -> - validate_key_share(ClientGroups, ClientShares). +validate_client_key_share([G|ClientGroups], [{_, G, _}|ClientShares]) -> + validate_client_key_share(ClientGroups, ClientShares); +validate_client_key_share([_|ClientGroups], [_|_] = ClientShares) -> + validate_client_key_share(ClientGroups, ClientShares). + + +%% Verify that selected group is offered by the client. +validate_server_key_share([G|_ClientGroups], {_, G, _}) -> + ok; +validate_server_key_share([_|ClientGroups], {_, _, _} = ServerKeyShare) -> + validate_server_key_share(ClientGroups, ServerKeyShare). + + +validate_selected_group(SelectedGroup, [SelectedGroup|_]) -> + {error, {illegal_parameter, + "Selected group sent by the server shall not correspond to a group" + " which was provided in the key_share extension"}}; +validate_selected_group(SelectedGroup, ClientGroups) -> + case lists:member(SelectedGroup, ClientGroups) of + true -> + ok; + false -> + {error, {illegal_parameter, + "Selected group sent by the server shall correspond to a group" + " which was provided in the supported_groups extension"}} + end. get_client_public_key([Group|_] = Groups, ClientShares) -> @@ -1197,32 +1687,79 @@ get_client_public_key([Group|Groups], ClientShares, PreferredGroup) -> get_client_public_key(Groups, ClientShares, PreferredGroup) end. +get_client_private_key([Group|_] = Groups, ClientShares) -> + get_client_private_key(Groups, ClientShares, Group). +%% +get_client_private_key(_, [], PreferredGroup) -> + {PreferredGroup, no_suitable_key}; +get_client_private_key([], _, PreferredGroup) -> + {PreferredGroup, no_suitable_key}; +get_client_private_key([Group|Groups], ClientShares, PreferredGroup) -> + case lists:keysearch(Group, 2, ClientShares) of + {value, {_, _, {_, ClientPrivateKey}}} -> + {Group, ClientPrivateKey}; + {value, {_, _, #'ECPrivateKey'{} = ClientPrivateKey}} -> + {Group, ClientPrivateKey}; + false -> + get_client_private_key(Groups, ClientShares, PreferredGroup) + end. + -%% get_client_public_key(Group, ClientShares) -> -%% case lists:keysearch(Group, 2, ClientShares) of -%% {value, {_, _, ClientPublicKey}} -> -%% ClientPublicKey; -%% false -> -%% %% 4.1.4. Hello Retry Request -%% %% -%% %% The server will send this message in response to a ClientHello -%% %% message if it is able to find an acceptable set of parameters but the -%% %% ClientHello does not contain sufficient information to proceed with -%% %% the handshake. -%% no_suitable_key -%% end. - -select_cipher_suite([], _) -> +get_server_public_key({key_share_entry, Group, PublicKey}) -> + {Group, PublicKey}. + + +%% RFC 7301 - Application-Layer Protocol Negotiation Extension +%% It is expected that a server will have a list of protocols that it +%% supports, in preference order, and will only select a protocol if the +%% client supports it. In that case, the server SHOULD select the most +%% highly preferred protocol that it supports and that is also +%% advertised by the client. In the event that the server supports no +%% protocols that the client advertises, then the server SHALL respond +%% with a fatal "no_application_protocol" alert. +handle_alpn(undefined, _) -> + {ok, undefined}; +handle_alpn([], _) -> + {error, no_application_protocol}; +handle_alpn([_|_], undefined) -> + {ok, undefined}; +handle_alpn([ServerProtocol|T], ClientProtocols) -> + case lists:member(ServerProtocol, ClientProtocols) of + true -> + {ok, ServerProtocol}; + false -> + handle_alpn(T, ClientProtocols) + end. + + +select_cipher_suite(_, [], _) -> {error, no_suitable_cipher}; -select_cipher_suite([Cipher|ClientCiphers], ServerCiphers) -> +%% If honor_cipher_order is set to true, use the server's preference for +%% cipher suite selection. +select_cipher_suite(true, ClientCiphers, ServerCiphers) -> + select_cipher_suite(false, ServerCiphers, ClientCiphers); +select_cipher_suite(false, [Cipher|ClientCiphers], ServerCiphers) -> case lists:member(Cipher, tls_v1:suites('TLS_v1.3')) andalso lists:member(Cipher, ServerCiphers) of true -> {ok, Cipher}; false -> - select_cipher_suite(ClientCiphers, ServerCiphers) + select_cipher_suite(false, ClientCiphers, ServerCiphers) end. + +%% RFC 8446 4.1.3 ServerHello +%% A client which receives a cipher suite that was not offered MUST abort the +%% handshake with an "illegal_parameter" alert. +validate_cipher_suite(Cipher, ClientCiphers) -> + case lists:member(Cipher, ClientCiphers) of + true -> + ok; + false -> + {error, illegal_parameter} + end. + + %% RFC 8446 (TLS 1.3) %% TLS 1.3 provides two extensions for indicating which signature %% algorithms may be used in digital signatures. The @@ -1246,15 +1783,20 @@ check_cert_sign_algo(SignAlgo, SignHash, _, ClientSignAlgsCert) -> %% DSA keys are not supported by TLS 1.3 select_sign_algo(dsa, _ClientSignAlgs, _ServerSignAlgs) -> {error, {insufficient_security, no_suitable_public_key}}; -%% TODO: Implement support for ECDSA keys! select_sign_algo(_, [], _) -> {error, {insufficient_security, no_suitable_signature_algorithm}}; select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) -> {_, S, _} = ssl_cipher:scheme_to_components(C), %% RSASSA-PKCS1-v1_5 and Legacy algorithms are not defined for use in signed %% TLS handshake messages: filter sha-1 and rsa_pkcs1. + %% + %% RSASSA-PSS RSAE algorithms: If the public key is carried in an X.509 + %% certificate, it MUST use the rsaEncryption OID. + %% RSASSA-PSS PSS algorithms: If the public key is carried in an X.509 certificate, + %% it MUST use the RSASSA-PSS OID. case ((PublicKeyAlgo =:= rsa andalso S =:= rsa_pss_rsae) - orelse (PublicKeyAlgo =:= rsa_pss andalso S =:= rsa_pss_rsae)) + orelse (PublicKeyAlgo =:= rsa_pss andalso S =:= rsa_pss_pss) + orelse (PublicKeyAlgo =:= ecdsa andalso S =:= ecdsa)) andalso lists:member(C, ServerSignAlgs) of true -> @@ -1331,7 +1873,20 @@ get_supported_groups(#supported_groups{supported_groups = Groups}) -> Groups. get_key_shares(#key_share_client_hello{client_shares = ClientShares}) -> - ClientShares. + ClientShares; +get_key_shares(#key_share_server_hello{server_share = ServerShare}) -> + ServerShare. + +get_selected_group(#key_share_hello_retry_request{selected_group = SelectedGroup}) -> + SelectedGroup. + +get_alpn(ALPNProtocol0) -> + case ssl_handshake:decode_alpn(ALPNProtocol0) of + undefined -> + undefined; + [ALPNProtocol] -> + ALPNProtocol + end. maybe() -> Ref = erlang:make_ref(), diff --git a/lib/ssl/src/tls_handshake_1_3.hrl b/lib/ssl/src/tls_handshake_1_3.hrl index 7ae1b93e1c..eb85f216c8 100644 --- a/lib/ssl/src/tls_handshake_1_3.hrl +++ b/lib/ssl/src/tls_handshake_1_3.hrl @@ -74,29 +74,52 @@ y % opaque Y[coordinate_length]; }). +%% RFC 8446 4.2.9. Pre-Shared Key Exchange Modes + +%% enum { psk_ke(0), psk_dhe_ke(1), (255) } PskKeyExchangeMode; -define(PSK_KE, 0). -define(PSK_DHE_KE, 1). --record(psk_keyexchange_modes, { +-record(psk_key_exchange_modes, { ke_modes % ke_modes<1..255> }). + +%% RFC 8446 4.2.10. Early Data Indication -record(empty, { }). -record(early_data_indication, { indication % uint32 max_early_data_size (new_session_ticket) | %% #empty{} (client_hello, encrypted_extensions) }). --record(psk_identity, { - identity, % opaque identity<1..2^16-1> - obfuscated_ticket_age % uint32 - }). --record(offered_psks, { - psk_identity, %identities<7..2^16-1>; - psk_binder_entry %binders<33..2^16-1>, opaque PskBinderEntry<32..255> - }). --record(pre_shared_keyextension,{ - extension %OfferedPsks (client_hello) | uint16 selected_identity (server_hello) - }). + +%% RFC 8446 4.2.11. Pre-Shared Key Extension +-record(psk_identity, + { + identity, % opaque identity<1..2^16-1> + obfuscated_ticket_age % uint32 + }). + +-record(offered_psks, + { + identities, % PskIdentity identities<7..2^16-1>; + binders % PskBinderEntry binders<33..2^16-1>; opaque PskBinderEntry<32..255> + }). + +%% struct { +%% select (Handshake.msg_type) { +%% case client_hello: OfferedPsks; +%% case server_hello: uint16 selected_identity; +%% }; +%% } PreSharedKeyExtension; +-record(pre_shared_key_client_hello, + { + offered_psks + }). + +-record(pre_shared_key_server_hello, + { + selected_identity + }). %% RFC 8446 B.3.1.2. -record(cookie, { diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl index a5c550a429..2aeab98929 100644 --- a/lib/ssl/src/tls_record.erl +++ b/lib/ssl/src/tls_record.erl @@ -514,16 +514,27 @@ validate_tls_record_length(Versions, {_,Size0,_} = Q0, SslOpts, Acc, Type, Versi end. -binary_from_front(SplitSize, {Front,Size,Rear}) -> +binary_from_front(0, Q) -> + {<<>>, Q}; +binary_from_front(SplitSize, {Front,Size,Rear}) when SplitSize =< Size -> binary_from_front(SplitSize, Front, Size, Rear, []). %% -binary_from_front(SplitSize, [], Size, [_] = Rear, Acc) -> - %% Optimize a simple case - binary_from_front(SplitSize, Rear, Size, [], Acc); +%% SplitSize > 0 and there is at least SplitSize bytes buffered in Front and Rear binary_from_front(SplitSize, [], Size, Rear, Acc) -> - binary_from_front(SplitSize, lists:reverse(Rear), Size, [], Acc); + case Rear of + %% Avoid lists:reverse/1 for simple cases. + %% Case clause for [] to avoid infinite loop. + [_] -> + binary_from_front(SplitSize, Rear, Size, [], Acc); + [Bin2,Bin1] -> + binary_from_front(SplitSize, [Bin1,Bin2], Size, [], Acc); + [Bin3,Bin2,Bin1] -> + binary_from_front(SplitSize, [Bin1,Bin2,Bin3], Size, [], Acc); + [_,_,_|_] -> + binary_from_front(SplitSize, lists:reverse(Rear), Size, [], Acc) + end; binary_from_front(SplitSize, [Bin|Front], Size, Rear, []) -> - %% Optimize a frequent case + %% Optimize the frequent case when the accumulator is empty BinSize = byte_size(Bin), if SplitSize < BinSize -> diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl index 74321a1ae2..d713062284 100644 --- a/lib/ssl/src/tls_record_1_3.erl +++ b/lib/ssl/src/tls_record_1_3.erl @@ -138,6 +138,15 @@ decode_cipher_text(#ssl_tls{type = ?ALERT, {#ssl_tls{type = ?ALERT, version = {3,4}, %% Internally use real version fragment = <<2,47>>}, ConnectionStates0}; +%% TLS 1.3 server can receive a User Cancelled Alert when handshake is +%% paused and then cancelled on the client side. +decode_cipher_text(#ssl_tls{type = ?ALERT, + version = ?LEGACY_VERSION, + fragment = <<2,90>>}, + ConnectionStates0) -> + {#ssl_tls{type = ?ALERT, + version = {3,4}, %% Internally use real version + fragment = <<2,90>>}, ConnectionStates0}; %% RFC8446 - TLS 1.3 %% D.4. Middlebox Compatibility Mode %% - If not offering early data, the client sends a dummy diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl index 27cd5765e5..f7c8c770ae 100644 --- a/lib/ssl/src/tls_v1.erl +++ b/lib/ssl/src/tls_v1.erl @@ -606,8 +606,26 @@ signature_schemes(Version, SignatureSchemes) when is_tuple(Version) Acc end; %% Special clause for filtering out the legacy hash-sign tuples. - (_ , Acc) -> - Acc + ({Hash, dsa = Sign} = Alg, Acc) -> + case proplists:get_bool(dss, PubKeys) + andalso proplists:get_bool(Hash, Hashes) + andalso is_pair(Hash, Sign, Hashes) + of + true -> + [Alg | Acc]; + false -> + Acc + end; + ({Hash, Sign} = Alg, Acc) -> + case proplists:get_bool(Sign, PubKeys) + andalso proplists:get_bool(Hash, Hashes) + andalso is_pair(Hash, Sign, Hashes) + of + true -> + [Alg | Acc]; + false -> + Acc + end end, Supported = lists:foldl(Fun, [], SignatureSchemes), lists:reverse(Supported); diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile index dba90aaff0..0925c0facc 100644 --- a/lib/ssl/test/Makefile +++ b/lib/ssl/test/Makefile @@ -37,14 +37,29 @@ VSN=$(SSL_VSN) MODULES = \ ssl_test_lib \ + ssl_app_env_SUITE\ + ssl_alert_SUITE\ ssl_bench_test_lib \ ssl_dist_test_lib \ - ssl_alpn_handshake_SUITE \ + ssl_api_SUITE\ + tls_api_SUITE\ ssl_basic_SUITE \ ssl_bench_SUITE \ ssl_cipher_SUITE \ ssl_cipher_suite_SUITE \ - openssl_server_cipher_suite_SUITE\ + openssl_cipher_suite_SUITE\ + ssl_alpn_SUITE \ + openssl_alpn_SUITE\ + ssl_npn_SUITE \ + openssl_npn_SUITE\ + openssl_sni_SUITE\ + ssl_renegotiate_SUITE\ + openssl_renegotiate_SUITE\ + openssl_reject_SUITE\ + ssl_cert_tests\ + ssl_cert_SUITE\ + openssl_server_cert_SUITE\ + openssl_client_cert_SUITE\ ssl_certificate_verify_SUITE\ ssl_crl_SUITE\ ssl_dist_SUITE \ @@ -52,12 +67,12 @@ MODULES = \ ssl_engine_SUITE\ ssl_handshake_SUITE \ ssl_npn_hello_SUITE \ - ssl_npn_handshake_SUITE \ ssl_packet_SUITE \ ssl_payload_SUITE \ ssl_pem_cache_SUITE \ + ssl_session_SUITE \ ssl_session_cache_SUITE \ - ssl_to_openssl_SUITE \ + openssl_session_SUITE \ ssl_ECC_SUITE \ ssl_ECC_openssl_SUITE \ ssl_ECC\ @@ -65,6 +80,10 @@ MODULES = \ ssl_sni_SUITE \ ssl_eqc_SUITE \ ssl_rfc_5869_SUITE \ + tls_1_3_record_SUITE\ + openssl_tls_1_3_version_SUITE\ + tls_1_3_version_SUITE\ + ssl_socket_SUITE\ make_certs \ x509_test \ inet_crypto_dist diff --git a/lib/ssl/test/openssl_alpn_SUITE.erl b/lib/ssl/test/openssl_alpn_SUITE.erl new file mode 100644 index 0000000000..5008dba922 --- /dev/null +++ b/lib/ssl/test/openssl_alpn_SUITE.erl @@ -0,0 +1,421 @@ +%% +%% %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(openssl_alpn_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-define(OPENSSL_QUIT, "Q\n"). +-define(OPENSSL_RENEGOTIATE, "R\n"). +-define(SLEEP, 1000). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + %% Note: ALPN not supported in sslv3 + case ssl_test_lib:openssl_sane_dtls_alpn() of + true -> + [ + {group, 'tlsv1.3'}, + {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_alpn() of + true -> + [ + {'tlsv1.3', [], alpn_tests()}, + {'tlsv1.2', [], alpn_tests() ++ alpn_npn_coexist() ++ rengotiation_tests()}, + {'tlsv1.1', [], alpn_tests() ++ alpn_npn_coexist() ++ rengotiation_tests()}, + {'tlsv1', [], alpn_tests() ++ alpn_npn_coexist() ++ rengotiation_tests()}, + {'dtlsv1.2', [], alpn_tests()}, + {'dtlsv1', [], alpn_tests()} + ]; + false -> + [ + {'tlsv1.3', [], alpn_tests()}, + {'tlsv1.2', [], alpn_tests() ++ alpn_npn_coexist() ++ rengotiation_tests()}, + {'tlsv1.1', [], alpn_tests() ++ alpn_npn_coexist() ++ rengotiation_tests()}, + {'tlsv1', [], alpn_tests() ++ alpn_npn_coexist() ++ rengotiation_tests()} + ] + end. + +alpn_tests() -> + [erlang_client_alpn_openssl_server_alpn, + erlang_server_alpn_openssl_client_alpn, + erlang_client_alpn_openssl_server, + erlang_client_openssl_server_alpn, + erlang_server_alpn_openssl_client, + erlang_server_openssl_client_alpn]. + +alpn_npn_coexist() -> + [ + erlang_client_alpn_npn_openssl_server_alpn_npn, + erlang_server_alpn_npn_openssl_client_alpn_npn + ]. +rengotiation_tests() -> + case ssl_test_lib:sane_openssl_alpn_npn_renegotiate() of + true -> + [erlang_client_alpn_openssl_server_alpn_renegotiate, + erlang_server_alpn_openssl_client_alpn_renegotiate]; + false -> + [] + end. +init_per_suite(Config0) -> + case os:find_executable("openssl") of + false -> + {skip, "Openssl not found"}; + _ -> + case check_openssl_alpn_support(Config0) of + {skip, _} = Skip -> + Skip; + _ -> + ct:pal("Version: ~p", [os:cmd("openssl version")]), + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end + end + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto), + ssl_test_lib:kill_openssl(). + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(TestCase, Config) -> + ct:timetrap({seconds, 10}), + special_init(TestCase, Config). + +special_init(TestCase, Config) + when TestCase == erlang_client_alpn_openssl_server_alpn_renegotiate; + TestCase == erlang_server_alpn_openssl_client_alpn_renegotiate -> + {ok, Version} = application:get_env(ssl, protocol_version), + ssl_test_lib:check_sane_openssl_renegotaite(Config, Version); +special_init(TestCase, Config) + when TestCase == erlang_client_alpn_npn_openssl_server_alpn_npn; + TestCase == erlang_server_alpn_npn_openssl_client_alpn_npn -> + ssl_test_lib:check_openssl_npn_support(Config); +special_init(_, Config) -> + Config. + +end_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +erlang_client_alpn_openssl_server_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------- + +erlang_server_alpn_openssl_client_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------------- + +erlang_client_alpn_openssl_server(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_client_and_openssl_server_with_opts(Config, + [{alpn_advertised_protocols, [<<"spdy/2">>]}], + [], + Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------------- + +erlang_client_openssl_server_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_client_and_openssl_server_with_opts(Config, + [], + ["-alpn", "spdy/2"], + Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------------- + +erlang_server_alpn_openssl_client(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_server_and_openssl_client_with_opts(Config, + [{alpn_preferred_protocols, [<<"spdy/2">>]}], + [], + Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------------- + +erlang_server_openssl_client_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_server_and_openssl_client_with_opts(Config, + [], + ["-alpn", "spdy/2"], + Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------- + +erlang_client_alpn_openssl_server_alpn_renegotiate(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------- + +erlang_server_alpn_openssl_client_alpn_renegotiate(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------- + +erlang_client_alpn_npn_openssl_server_alpn_npn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------- + +erlang_server_alpn_npn_openssl_client_alpn_npn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + + +%%-------------------------------------------------------------------- +%% Internal functions ----------------------------------------------- +%%-------------------------------------------------------------------- +check_openssl_alpn_support(Config) -> + HelpText = os:cmd("openssl s_client --help"), + case string:str(HelpText, "alpn") of + 0 -> + {skip, "Openssl not compiled with alpn support"}; + _ -> + Config + end. + +start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]} | ClientOpts0], + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-accept", + integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile, "-key", KeyFile], + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + Callback(Client, OpensslPort), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false). + +start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>]} | ServerOpts0], + + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_client", "-alpn", "http/1.0,spdy/2", "-msg", "-port", + integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-host", "localhost"], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + + Callback(Server, OpenSslPort), + + ssl_test_lib:close(Server), + + ssl_test_lib:close_port(OpenSslPort), + process_flag(trap_exit, false). + +start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]}, + {client_preferred_next_protocols, {client, [<<"spdy/3">>, <<"http/1.1">>]}} | ClientOpts0], + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-nextprotoneg", + "spdy/3", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-cert", CertFile, "-key", KeyFile], + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + Callback(Client, OpensslPort), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false). + +start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>]}, + {next_protocols_advertised, [<<"spdy/3">>, <<"http/1.1">>]} | ServerOpts0], + + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_client", "-alpn", "http/1.1,spdy/2", "-nextprotoneg", "spdy/3", + "-msg", "-port", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-host", "localhost"], + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + + Callback(Server, OpenSslPort), + + ssl_test_lib:close(Server), + ssl_test_lib:close_port(OpenSslPort), + process_flag(trap_exit, false). + diff --git a/lib/ssl/test/openssl_server_cipher_suite_SUITE.erl b/lib/ssl/test/openssl_cipher_suite_SUITE.erl index 6ce34ce7fa..955eb914c0 100644 --- a/lib/ssl/test/openssl_server_cipher_suite_SUITE.erl +++ b/lib/ssl/test/openssl_cipher_suite_SUITE.erl @@ -20,7 +20,7 @@ %% --module(openssl_server_cipher_suite_SUITE). +-module(openssl_cipher_suite_SUITE). %% Note: This directive should only be used in test suites. -compile(export_all). @@ -31,19 +31,26 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- all() -> - [ - {group, 'tlsv1.2'}, + [ + {group, openssl_server}, + {group, openssl_client} + ]. + +all_protocol_groups() -> + [{group, 'tlsv1.2'}, {group, 'tlsv1.1'}, {group, 'tlsv1'}, {group, 'sslv3'}, {group, 'dtlsv1.2'}, {group, 'dtlsv1'} - ]. + ]. groups() -> %% TODO: Enable SRP, PSK suites (needs OpenSSL s_server conf) %% TODO: Enable all "kex" on DTLS [ + {openssl_server, all_protocol_groups()}, + {openssl_client, all_protocol_groups()}, {'tlsv1.2', [], kex()}, {'tlsv1.1', [], kex()}, {'tlsv1', [], kex()}, @@ -135,6 +142,9 @@ kex() -> dtls_kex() -> %% Should be all kex in the future dtls_rsa() ++ dss() ++ anonymous(). +ssl3_kex() -> + ssl3_rsa() ++ ssl3_dss() ++ ssl3_anonymous(). + rsa() -> [{group, dhe_rsa}, {group, ecdhe_rsa}, @@ -148,6 +158,11 @@ dtls_rsa() -> %%,{group, rsa_psk} ]. +ssl3_rsa() -> + [{group, dhe_rsa}, + {group, rsa} + ]. + ecdsa() -> [{group, ecdhe_ecdsa}]. @@ -156,6 +171,10 @@ dss() -> %%{group, srp_dss} ]. +ssl3_dss() -> + [{group, dhe_dss} + ]. + anonymous() -> [{group, dh_anon}, {group, ecdh_anon} @@ -165,6 +184,9 @@ anonymous() -> %%{group, srp_anon} ]. +ssl3_anonymous() -> + [{group, dh_anon}]. + init_per_suite(Config) -> catch crypto:stop(), try crypto:start() of @@ -177,7 +199,8 @@ init_per_suite(Config) -> end_per_suite(_Config) -> ssl:stop(), - application:stop(crypto). + application:stop(crypto), + ssl_test_lib:kill_openssl(). %%-------------------------------------------------------------------- init_per_group(GroupName, Config) -> @@ -198,7 +221,12 @@ init_per_group(GroupName, Config) -> false -> do_init_per_group(GroupName, Config) end. - +do_init_per_group(openssl_client, Config0) -> + Config = proplists:delete(server_type, proplists:delete(client_type, Config0)), + [{client_type, openssl}, {server_type, erlang} | Config]; +do_init_per_group(openssl_server, Config0) -> + Config = proplists:delete(server_type, proplists:delete(client_type, Config0)), + [{client_type, erlang}, {server_type, openssl} | Config]; do_init_per_group(GroupName, Config) when GroupName == ecdh_anon; GroupName == ecdhe_rsa; GroupName == ecdhe_psk -> @@ -746,8 +774,7 @@ cipher_suite_test(CipherSuite, _Version, Config) -> ct:log("Testing CipherSuite ~p~n", [CipherSuite]), ct:log("Server Opts ~p~n", [ServerOpts]), ct:log("Client Opts ~p~n", [ClientOpts]), - ssl_test_lib:basic_test([{ciphers, [CipherSuite]} | COpts], SOpts, [{client_type, erlang}, - {server_type, openssl} | Config]). + ssl_test_lib:basic_test([{ciphers, [CipherSuite]} | COpts], SOpts, Config). test_ciphers(Kex, Cipher, Version) -> diff --git a/lib/ssl/test/openssl_client_cert_SUITE.erl b/lib/ssl/test/openssl_client_cert_SUITE.erl new file mode 100644 index 0000000000..b327988744 --- /dev/null +++ b/lib/ssl/test/openssl_client_cert_SUITE.erl @@ -0,0 +1,350 @@ +%% +%% %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(openssl_client_cert_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() -> + [ + {group, openssl_client} + ]. + +groups() -> + [ + {openssl_client, [], protocol_groups()}, + {'tlsv1.3', [], tls_1_3_protocol_groups()}, + {'tlsv1.2', [], pre_tls_1_3_protocol_groups()}, + {'tlsv1.1', [], pre_tls_1_3_protocol_groups()}, + {'tlsv1', [], pre_tls_1_3_protocol_groups()}, + {'sslv3', [], ssl_protocol_groups()}, + {'dtlsv1.2', [], pre_tls_1_3_protocol_groups()}, + {'dtlsv1', [], pre_tls_1_3_protocol_groups()}, + {rsa, [], all_version_tests()}, + {ecdsa, [], all_version_tests()}, + {dsa, [], all_version_tests()}, + {rsa_1_3, [], all_version_tests() ++ tls_1_3_tests() ++ [unsupported_sign_algo_client_auth, + unsupported_sign_algo_cert_client_auth]}, + {ecdsa_1_3, [], all_version_tests() ++ tls_1_3_tests()} + ]. + +protocol_groups() -> + [{group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +ssl_protocol_groups() -> + [{group, rsa}, + {group, dsa}]. + +pre_tls_1_3_protocol_groups() -> + [{group, rsa}, + {group, ecdsa}, + {group, dsa}]. + +tls_1_3_protocol_groups() -> + [{group, rsa_1_3}, + {group, ecdsa_1_3}]. + +tls_1_3_tests() -> + [ + hello_retry_request, + custom_groups, + hello_retry_client_auth, + hello_retry_client_auth_empty_cert_accepted, + hello_retry_client_auth_empty_cert_rejected + ]. + +all_version_tests() -> + [ + no_auth, + auth, + client_auth_empty_cert_accepted, + client_auth_empty_cert_rejected, + client_auth_partial_chain, + client_auth_allow_partial_chain, + client_auth_do_not_allow_partial_chain, + client_auth_partial_chain_fun_fail, + missing_root_cert_no_auth + %%invalid_signature_client + ]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + Config + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + +init_per_group(openssl_client, Config0) -> + Config = proplists:delete(server_type, proplists:delete(client_type, Config0)), + [{client_type, openssl}, {server_type, erlang} | Config]; +init_per_group(Group, Config0) when Group == rsa; + Group == rsa_1_3 -> + Config = ssl_test_lib:make_rsa_cert(Config0), + COpts = proplists:get_value(client_rsa_opts, Config), + SOpts = proplists:get_value(server_rsa_opts, Config), + %% Make sure _rsa* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(fun(dhe_rsa) -> + true; + (ecdhe_rsa) -> + true; + (_) -> + false + end, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, rsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + [] -> + {skip, {no_sup, Group, Version}} + end; +init_per_group(Group, Config0) when Group == ecdsa; + Group == ecdsa_1_3 -> + PKAlg = crypto:supports(public_keys), + case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse + lists:member(dh, PKAlg)) of + true -> + Config = ssl_test_lib:make_ecdsa_cert(Config0), + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + %% Make sure ecdh* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(fun(ecdh_ecdsa) -> + true; + (ecdhe_ecdsa) -> + true; + (_) -> + false + end, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, ecdsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))] + )]; + [] -> + {skip, {no_sup, Group, Version}} + end; + false -> + {skip, "Missing EC crypto support"} + end; +init_per_group(Group, Config0) when Group == dsa -> + PKAlg = crypto:supports(public_keys), + case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of + true -> + Config = ssl_test_lib:make_dsa_cert(Config0), + COpts = proplists:get_value(client_dsa_opts, Config), + SOpts = proplists:get_value(server_dsa_opts, Config), + %% Make sure dhe_dss* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(fun(dh_dss) -> + true; + (dhe_dss) -> + true; + (_) -> + false + end, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, dsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + [] -> + {skip, {no_sup, Group, Version}} + end; + false -> + {skip, "Missing DSS crypto support"} + end; +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 -> + [{version, GroupName} + | ssl_test_lib:init_tls_version(GroupName, Config)]; + false -> + {skip, "Missing openssl support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. +init_per_testcase(TestCase, Config) when + TestCase == client_auth_empty_cert_accepted; + TestCase == client_auth_empty_cert_rejected -> + Version = proplists:get_value(version,Config), + case Version of + sslv3 -> + %% Openssl client sends "No Certificate Reserved" warning ALERT + %% instead of sending EMPTY cert message in SSL-3.0 so empty cert test are not + %% relevant + {skip, openssl_behaves_differently}; + _ -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config + end; +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +no_auth() -> + ssl_cert_tests:no_auth(). + +no_auth(Config) -> + ssl_cert_tests:no_auth(Config). +%%-------------------------------------------------------------------- +auth() -> + ssl_cert_tests:auth(). +auth(Config) -> + ssl_cert_tests:auth(Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_accepted() -> + ssl_cert_tests:client_auth_empty_cert_accepted(). +client_auth_empty_cert_accepted(Config) -> + ssl_cert_tests:client_auth_empty_cert_accepted(Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_rejected() -> + ssl_cert_tests:client_auth_empty_cert_rejected(). +client_auth_empty_cert_rejected(Config) -> + ssl_cert_tests:client_auth_empty_cert_rejected(Config). +%%-------------------------------------------------------------------- +client_auth_partial_chain() -> + ssl_cert_tests:client_auth_partial_chain(). +client_auth_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_partial_chain(Config). + +%%-------------------------------------------------------------------- +client_auth_allow_partial_chain() -> + ssl_cert_tests:client_auth_allow_partial_chain(). +client_auth_allow_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_allow_partial_chain(Config). +%%-------------------------------------------------------------------- +client_auth_do_not_allow_partial_chain() -> + ssl_cert_tests:client_auth_do_not_allow_partial_chain(). +client_auth_do_not_allow_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_do_not_allow_partial_chain(Config). + +%%-------------------------------------------------------------------- +client_auth_partial_chain_fun_fail() -> + ssl_cert_tests:client_auth_partial_chain_fun_fail(). +client_auth_partial_chain_fun_fail(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_partial_chain_fun_fail(Config). + +%%-------------------------------------------------------------------- +missing_root_cert_no_auth() -> + ssl_cert_tests:missing_root_cert_no_auth(). +missing_root_cert_no_auth(Config) when is_list(Config) -> + ssl_cert_tests:missing_root_cert_no_auth(Config). + +%%-------------------------------------------------------------------- +invalid_signature_client() -> + ssl_cert_tests:invalid_signature_client(). +invalid_signature_client(Config) when is_list(Config) -> + ssl_cert_tests:invalid_signature_client(Config). +%%-------------------------------------------------------------------- +invalid_signature_server() -> + ssl_cert_tests:invalid_signature_client(). +invalid_signature_server(Config) when is_list(Config) -> + ssl_cert_tests:invalid_signature_client(Config). + +%%-------------------------------------------------------------------- +%% TLS 1.3 Test Cases ------------------------------------------------ +%%-------------------------------------------------------------------- +hello_retry_request() -> + ssl_cert_tests:hello_retry_request(). +hello_retry_request(Config) -> + ssl_cert_tests:hello_retry_request(Config). +%%-------------------------------------------------------------------- +custom_groups() -> + ssl_cert_tests:custom_groups(). +custom_groups(Config) -> + ssl_cert_tests:custom_groups(Config). +unsupported_sign_algo_cert_client_auth() -> + ssl_cert_tests:unsupported_sign_algo_cert_client_auth(). +unsupported_sign_algo_cert_client_auth(Config) -> + ssl_cert_tests:unsupported_sign_algo_cert_client_auth(Config). +unsupported_sign_algo_client_auth() -> + ssl_cert_tests:unsupported_sign_algo_client_auth(). +unsupported_sign_algo_client_auth(Config) -> + ssl_cert_tests:unsupported_sign_algo_client_auth(Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth() -> + ssl_cert_tests:hello_retry_client_auth(). +hello_retry_client_auth(Config) -> + ssl_cert_tests:hello_retry_client_auth(Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_accepted() -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_accepted(). +hello_retry_client_auth_empty_cert_accepted(Config) -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_accepted(Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_rejected() -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_rejected(). +hello_retry_client_auth_empty_cert_rejected(Config) -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_rejected(Config). diff --git a/lib/ssl/test/openssl_npn_SUITE.erl b/lib/ssl/test/openssl_npn_SUITE.erl new file mode 100644 index 0000000000..0294f4997f --- /dev/null +++ b/lib/ssl/test/openssl_npn_SUITE.erl @@ -0,0 +1,311 @@ +%% +%% %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(openssl_npn_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-define(OPENSSL_QUIT, "Q\n"). +-define(OPENSSL_RENEGOTIATE, "R\n"). +-define(SLEEP, 1000). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + %% NPN is not supported in TLS-1.3 (replaced by ALPN and deprecated in TLS 1.2) + %% OpenSSL DTLS support for NPN is either not there or broken. + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}]. + +groups() -> + [{'tlsv1.2', [], npn_tests() ++ npn_renegotiate_tests()}, + {'tlsv1.1', [], npn_tests() ++ npn_renegotiate_tests()}, + {'tlsv1', [], npn_tests() ++ npn_renegotiate_tests()} + ]. + +npn_tests() -> + [erlang_client_openssl_server_npn, + erlang_server_openssl_client_npn, + erlang_server_openssl_client_npn_only_client, + erlang_server_openssl_client_npn_only_server, + erlang_client_openssl_server_npn_only_client, + erlang_client_openssl_server_npn_only_server]. + +npn_renegotiate_tests() -> + case ssl_test_lib:sane_openssl_alpn_npn_renegotiate() of + true -> + [erlang_server_openssl_client_npn_renegotiate, + erlang_client_openssl_server_npn_renegotiate]; + false -> + [] + end. + +init_per_suite(Config0) -> + case os:find_executable("openssl") of + false -> + {skip, "Openssl not found"}; + _ -> + case check_openssl_npn_support(Config0) of + {skip, _} = Skip -> + Skip; + _ -> + ct:pal("Version: ~p", [os:cmd("openssl version")]), + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end + end + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto), + ssl_test_lib:kill_openssl(). + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(TestCase, Config) -> + ct:timetrap({seconds, 10}), + special_init(TestCase, Config). + +special_init(TestCase, Config) + when TestCase == erlang_client_npn_openssl_server_npn_renegotiate; + TestCase == erlang_server_npn_openssl_client_npn_renegotiate -> + {ok, Version} = application:get_env(ssl, protocol_version), + ssl_test_lib:check_sane_openssl_renegotaite(Config, Version); +special_init(_, Config) -> + Config. + +end_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +erlang_client_openssl_server_npn() -> + [{doc,"Test erlang client with openssl server doing npn negotiation"}]. + +erlang_client_openssl_server_npn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, + fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). + +%%-------------------------------------------------------------------- +erlang_client_openssl_server_npn_renegotiate() -> + [{doc,"Test erlang client with openssl server doing npn negotiation and renegotiate"}]. + +erlang_client_openssl_server_npn_renegotiate(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, + fun(Client, OpensslPort) -> + true = port_command(OpensslPort, + ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Client, Data) + end). +%%-------------------------------------------------------------------------- +erlang_server_openssl_client_npn() -> + [{doc,"Test erlang server with openssl client and npn negotiation"}]. + +erlang_server_openssl_client_npn(Config) when is_list(Config) -> + + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, + fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------------- +erlang_server_openssl_client_npn_renegotiate() -> + [{doc,"Test erlang server with openssl client and npn negotiation with renegotiation"}]. + +erlang_server_openssl_client_npn_renegotiate(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, + fun(Server, OpensslPort) -> + true = port_command(OpensslPort, + ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). +%%-------------------------------------------------------------------------- +erlang_client_openssl_server_npn_only_server(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_client_and_openssl_server_with_opts(Config, [], + ["-nextprotoneg", "spdy/2"], Data, + fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------------- + +erlang_client_openssl_server_npn_only_client(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_client_and_openssl_server_with_opts(Config, + [{client_preferred_next_protocols, + {client, [<<"spdy/2">>], <<"http/1.1">>}}], [], + Data, + fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------------- +erlang_server_openssl_client_npn_only_server(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_server_and_openssl_client_with_opts(Config, + [{next_protocols_advertised, [<<"spdy/2">>]}], [], + Data, + fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +erlang_server_openssl_client_npn_only_client(Config) when is_list(Config) -> + Data = "From openssl to erlang", + ssl_test_lib:start_erlang_server_and_openssl_client_with_opts(Config, [], ["-nextprotoneg", "spdy/2"], + Data, + fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, Data) + end). + +%%-------------------------------------------------------------------- +%% Internal functions ----------------------------------------------- +%%-------------------------------------------------------------------- + +start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ClientOpts = [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}} | ClientOpts0], + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_server", "-msg", "-nextprotoneg", "http/1.1,spdy/2", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile, "-key", KeyFile], + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, ClientOpts}]), + + Callback(Client, OpensslPort), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false). + +start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ServerOpts = [{next_protocols_advertised, [<<"spdy/2">>]} | ServerOpts0], + + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_client", "-nextprotoneg", "http/1.0,spdy/2", "-msg", "-connect", + ssl_test_lib:hostname_format(Hostname) ++ ":" + ++ integer_to_list(Port), ssl_test_lib:version_flag(Version)], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + + Callback(Server, OpenSslPort), + + ssl_test_lib:close(Server), + + ssl_test_lib:close_port(OpenSslPort), + process_flag(trap_exit, false). + +check_openssl_npn_support(Config) -> + HelpText = os:cmd("openssl s_client --help"), + case string:str(HelpText, "nextprotoneg") of + 0 -> + {skip, "Openssl not compiled with nextprotoneg support"}; + _ -> + Config + end. diff --git a/lib/ssl/test/openssl_reject_SUITE.erl b/lib/ssl/test/openssl_reject_SUITE.erl new file mode 100644 index 0000000000..deefd11823 --- /dev/null +++ b/lib/ssl/test/openssl_reject_SUITE.erl @@ -0,0 +1,209 @@ +%% +%% %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(openssl_reject_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-define(SLEEP, 1000). +-define(OPENSSL_GARBAGE, "P\n"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}]. + +groups() -> + [{'tlsv1.2', [], all_versions_tests()}, + {'tlsv1.1', [], all_versions_tests()}, + {'tlsv1', [], all_versions_tests()}, + {'sslv3', [], all_versions_tests()} + ]. + +all_versions_tests() -> + [ + erlang_client_bad_openssl_server, + ssl2_erlang_server_openssl_client + ]. + +init_per_suite(Config0) -> + case os:find_executable("openssl") of + false -> + {skip, "Openssl not found"}; + _ -> + ct:pal("Version: ~p", [os:cmd("openssl version")]), + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto), + ssl_test_lib:kill_openssl(). + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(TestCase, Config) -> + ct:timetrap({seconds, 10}), + special_init(TestCase, Config). + +special_init(ssl2_erlang_server_openssl_client, Config) -> + case ssl_test_lib:supports_ssl_tls_version(sslv2) of + true -> + Config; + false -> + {skip, "sslv2 not supported by openssl"} + end; +special_init(_, Config) -> + Config. + +end_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +erlang_client_bad_openssl_server() -> + [{doc,"Test what happens if openssl server sends garbage to erlang ssl client"}]. +erlang_client_bad_openssl_server(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-cert", CertFile, "-key", KeyFile], + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, server_sent_garbage, []}}, + {options, + [{versions, [Version]} | ClientOpts]}]), + + %% Send garbage + true = port_command(OpensslPort, ?OPENSSL_GARBAGE), + + ct:sleep(?SLEEP), + + Client0 ! server_sent_garbage, + + ssl_test_lib:check_result(Client0, true), + + ssl_test_lib:close(Client0), + + %% Make sure openssl does not hang and leave zombie process + Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, + [{versions, [Version]} | ClientOpts]}]), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + ssl_test_lib:close(Client1), + process_flag(trap_exit, false). + +%%-------------------------------------------------------------------- +ssl2_erlang_server_openssl_client() -> + [{doc,"Test that ssl v2 clients are rejected"}]. + +ssl2_erlang_server_openssl_client(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Exe = "openssl", + Args = ["s_client", "-connect", ssl_test_lib:hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), + "-ssl2", "-msg"], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ct:log("Ports ~p~n", [[erlang:port_info(P) || P <- erlang:ports()]]), + ssl_test_lib:consume_port_exit(OpenSslPort), + ssl_test_lib:check_server_alert(Server, unexpected_message), + process_flag(trap_exit, false). + + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- + +server_sent_garbage(Socket) -> + receive + server_sent_garbage -> + {error, closed} == ssl:send(Socket, "data") + + end. diff --git a/lib/ssl/test/openssl_renegotiate_SUITE.erl b/lib/ssl/test/openssl_renegotiate_SUITE.erl new file mode 100644 index 0000000000..787b5208b8 --- /dev/null +++ b/lib/ssl/test/openssl_renegotiate_SUITE.erl @@ -0,0 +1,342 @@ +%% +%% %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(openssl_renegotiate_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-define(SLEEP, 1000). +-define(OPENSSL_RENEGOTIATE, "R\n"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'}]; + false -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}] + end. + +groups() -> + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{'tlsv1.2', [], all_versions_tests()}, + {'tlsv1.1', [], all_versions_tests()}, + {'tlsv1', [], all_versions_tests()}, + {'sslv3', [], all_versions_tests()}, + {'dtlsv1.2', [], all_versions_tests()}, + {'dtlsv1', [], all_versions_tests()} + ]; + false -> + [{'tlsv1.2', [], all_versions_tests()}, + {'tlsv1.1', [], all_versions_tests()}, + {'tlsv1', [], all_versions_tests()}, + {'sslv3', [], all_versions_tests()} + ] + end. + +all_versions_tests() -> + [ + erlang_client_openssl_server_renegotiate, + erlang_client_openssl_server_renegotiate_after_client_data, + erlang_client_openssl_server_nowrap_seqnum, + erlang_server_openssl_client_nowrap_seqnum + ]. + + +init_per_suite(Config0) -> + case os:find_executable("openssl") of + false -> + {skip, "Openssl not found"}; + _ -> + ct:pal("Version: ~p", [os:cmd("openssl version")]), + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto), + ssl_test_lib:kill_openssl(). + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:check_sane_openssl_renegotaite(ssl_test_lib:init_tls_version(GroupName, Config), + GroupName); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(TestCase, Config) -> + ct:timetrap({seconds, 10}), + special_init(TestCase, Config). + +special_init(TestCase, Config) + when TestCase == erlang_client_openssl_server_renegotiate; + TestCase == erlang_client_openssl_server_nowrap_seqnum; + TestCase == erlang_server_openssl_client_nowrap_seqnum; + TestCase == erlang_client_openssl_server_renegotiate_after_client_data + -> + {ok, Version} = application:get_env(ssl, protocol_version), + ssl_test_lib:check_sane_openssl_renegotaite(Config, Version); +special_init(_, Config) -> + Config. + +end_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +erlang_client_openssl_server_renegotiate() -> + [{doc,"Test erlang client when openssl server issuses a renegotiate"}]. +erlang_client_openssl_server_renegotiate(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + ErlData = "From erlang to openssl", + OpenSslData = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile, "-key", KeyFile, "-msg"], + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + delayed_send, [[ErlData, OpenSslData]]}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, OpenSslData), + + ssl_test_lib:check_result(Client, OpenSslData), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + ssl_test_lib:close(Client), + process_flag(trap_exit, false), + ok. +%%-------------------------------------------------------------------- +erlang_client_openssl_server_renegotiate_after_client_data() -> + [{doc,"Test erlang client when openssl server issuses a renegotiate after reading client data"}]. +erlang_client_openssl_server_renegotiate_after_client_data(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + ErlData = "From erlang to openssl", + OpenSslData = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile, "-key", KeyFile, "-msg"], + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + send_wait_send, [[ErlData, OpenSslData]]}}, + {options, [{reuse_sessions, false} |ClientOpts]}]), + + true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, OpenSslData), + + ssl_test_lib:check_result(Client, OpenSslData), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + ssl_test_lib:close(Client), + process_flag(trap_exit, false), + ok. + +%%-------------------------------------------------------------------- +erlang_client_openssl_server_nowrap_seqnum() -> + [{doc, "Test that erlang client will renegotiate session when", + "max sequence number celing is about to be reached. Although" + "in the testcase we use the test option renegotiate_at" + " to lower treashold substantially."}]. +erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + ErlData = "From erlang to openssl\n", + N = 10, + + Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile, "-key", KeyFile, "-msg"], + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + trigger_renegotiate, [[ErlData, N+2]]}}, + {options, [{reuse_sessions, false}, + {renegotiate_at, N} | ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + ssl_test_lib:close(Client), + process_flag(trap_exit, false). +%%-------------------------------------------------------------------- +erlang_server_openssl_client_nowrap_seqnum() -> + [{doc, "Test that erlang client will renegotiate session when", + "max sequence number celing is about to be reached. Although" + "in the testcase we use the test option renegotiate_at" + " to lower treashold substantially."}]. +erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + N = 10, + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + trigger_renegotiate, [[Data, N+2]]}}, + {options, [{renegotiate_at, N}, {reuse_sessions, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_client","-connect", ssl_test_lib:hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-msg"], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + + true = port_command(OpenSslPort, Data), + + ssl_test_lib:check_result(Server, ok), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close(Server), + ssl_test_lib:close_port(OpenSslPort), + process_flag(trap_exit, false). + + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- +delayed_send(Socket, [ErlData, OpenSslData]) -> + ct:sleep(?SLEEP), + ssl:send(Socket, ErlData), + ssl_test_lib:active_recv(Socket, length(OpenSslData)). + + +send_wait_send(Socket, [ErlData, OpenSslData]) -> + ssl:send(Socket, ErlData), + ct:sleep(?SLEEP), + ssl:send(Socket, ErlData), + ssl_test_lib:active_recv(Socket, length(OpenSslData)). + diff --git a/lib/ssl/test/openssl_server_cert_SUITE.erl b/lib/ssl/test/openssl_server_cert_SUITE.erl new file mode 100644 index 0000000000..c2af864a92 --- /dev/null +++ b/lib/ssl/test/openssl_server_cert_SUITE.erl @@ -0,0 +1,373 @@ +%% +%% %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(openssl_server_cert_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() -> + [ + {group, openssl_server}]. + +groups() -> + [ + {openssl_server, [], protocol_groups()}, + {'tlsv1.3', [], tls_1_3_protocol_groups()}, + {'tlsv1.2', [], pre_tls_1_3_protocol_groups()}, + {'tlsv1.1', [], pre_tls_1_3_protocol_groups()}, + {'tlsv1', [], pre_tls_1_3_protocol_groups()}, + {'sslv3', [], ssl_protocol_groups()}, + {'dtlsv1.2', [], pre_tls_1_3_protocol_groups()}, + {'dtlsv1', [], pre_tls_1_3_protocol_groups()}, + {rsa, [], all_version_tests()}, + {ecdsa, [], all_version_tests()}, + {dsa, [], all_version_tests()}, + {rsa_1_3, [], all_version_tests() ++ tls_1_3_tests()}, + %% TODO: Create proper conf of openssl server + %%++ [unsupported_sign_algo_client_auth, + %% unsupported_sign_algo_cert_client_auth]}, + {ecdsa_1_3, [], all_version_tests() ++ tls_1_3_tests()} + ]. + +protocol_groups() -> + [{group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +ssl_protocol_groups() -> + [{group, rsa}, + {group, dsa}]. + +pre_tls_1_3_protocol_groups() -> + [{group, rsa}, + {group, ecdsa}, + {group, dsa}]. + +tls_1_3_protocol_groups() -> + [{group, rsa_1_3}, + {group, ecdsa_1_3}]. + +tls_1_3_tests() -> + [ + hello_retry_request, + custom_groups, + hello_retry_client_auth, + hello_retry_client_auth_empty_cert_accepted, + hello_retry_client_auth_empty_cert_rejected + ]. + +all_version_tests() -> + [ + no_auth, + auth, + missing_root_cert_no_auth + %%invalid_signature_client + ]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + Config + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + +init_per_group(openssl_server, Config0) -> + Config = proplists:delete(server_type, proplists:delete(client_type, Config0)), + [{client_type, erlang}, {server_type, openssl} | Config]; +init_per_group(rsa = Group, Config0) -> + Config = ssl_test_lib:make_rsa_cert(Config0), + COpts = proplists:get_value(client_rsa_opts, Config), + SOpts = proplists:get_value(server_rsa_opts, Config), + %% Make sure _rsa* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(fun(dhe_rsa) -> + true; + (ecdhe_rsa) -> + true; + (_) -> + false + end, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, rsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + [] -> + {skip, {no_sup, Group, Version}} + end; +init_per_group(rsa_1_3 = Group, Config0) -> + Config = ssl_test_lib:make_rsa_cert(Config0), + COpts = proplists:get_value(client_rsa_opts, Config), + SOpts = proplists:get_value(server_rsa_opts, Config), + %% Make sure _rsa* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(undefined, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, rsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + [] -> + {skip, {no_sup, Group, Version}} + end; +init_per_group(ecdsa = Group, Config0) -> + PKAlg = crypto:supports(public_keys), + case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse + lists:member(dh, PKAlg)) of + true -> + Config = ssl_test_lib:make_ecdsa_cert(Config0), + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + %% Make sure ecdh* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(fun(ecdh_ecdsa) -> + true; + (ecdhe_ecdsa) -> + true; + (_) -> + false + end, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, ecdsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))] + )]; + [] -> + {skip, {no_sup, Group, Version}} + end; + false -> + {skip, "Missing EC crypto support"} + end; +init_per_group(ecdsa_1_3 = Group, Config0) -> + PKAlg = crypto:supports(public_keys), + case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse + lists:member(dh, PKAlg)) of + true -> + Config = ssl_test_lib:make_ecdsa_cert(Config0), + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + %% Make sure ecdh* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(undefined, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, ecdsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))] + )]; + [] -> + {skip, {no_sup, Group, Version}} + end; + false -> + {skip, "Missing EC crypto support"} + end; +init_per_group(Group, Config0) when Group == dsa -> + PKAlg = crypto:supports(public_keys), + case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of + true -> + Config = ssl_test_lib:make_dsa_cert(Config0), + COpts = proplists:get_value(client_dsa_opts, Config), + SOpts = proplists:get_value(server_dsa_opts, Config), + %% Make sure dhe_dss* suite is choosen by ssl_test_lib:start_server + Version = proplists:get_value(version,Config), + Ciphers = ssl_cert_tests:test_ciphers(fun(dh_dss) -> + true; + (dhe_dss) -> + true; + (_) -> + false + end, Version), + case Ciphers of + [_|_] -> + [{cert_key_alg, dsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, [{ciphers, Ciphers} | COpts]}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + [] -> + {skip, {no_sup, Group, Version}} + end; + false -> + {skip, "Missing DSS crypto support"} + end; +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 -> + [{version, GroupName} + | ssl_test_lib:init_tls_version(GroupName, Config)]; + false -> + {skip, "Missing openssl support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +no_auth() -> + ssl_cert_tests:no_auth(). + +no_auth(Config) -> + ssl_cert_tests:no_auth(Config). +%%-------------------------------------------------------------------- +auth() -> + ssl_cert_tests:auth(). +auth(Config) -> + ssl_cert_tests:auth(Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_accepted() -> + ssl_cert_tests:client_auth_empty_cert_accepted(). +client_auth_empty_cert_accepted(Config) -> + ssl_cert_tests:client_auth_empty_cert_accepted(Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_rejected() -> + ssl_cert_tests:client_auth_empty_cert_rejected(). +client_auth_empty_cert_rejected(Config) -> + ssl_cert_tests:client_auth_empty_cert_rejected(Config). +%%-------------------------------------------------------------------- +client_auth_partial_chain() -> + ssl_cert_tests:client_auth_partial_chain(). +client_auth_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_partial_chain(Config). + +%%-------------------------------------------------------------------- +client_auth_allow_partial_chain() -> + ssl_cert_tests:client_auth_allow_partial_chain(). +client_auth_allow_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_allow_partial_chain(Config). +%%-------------------------------------------------------------------- +client_auth_do_not_allow_partial_chain() -> + ssl_cert_tests:client_auth_do_not_allow_partial_chain(). +client_auth_do_not_allow_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_do_not_allow_partial_chain(Config). + +%%-------------------------------------------------------------------- +client_auth_partial_chain_fun_fail() -> + ssl_cert_tests:client_auth_partial_chain_fun_fail(). +client_auth_partial_chain_fun_fail(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_partial_chain_fun_fail(Config). + +%%-------------------------------------------------------------------- +missing_root_cert_no_auth() -> + ssl_cert_tests:missing_root_cert_no_auth(). +missing_root_cert_no_auth(Config) when is_list(Config) -> + ssl_cert_tests:missing_root_cert_no_auth(Config). + +%%-------------------------------------------------------------------- +invalid_signature_client() -> + ssl_cert_tests:invalid_signature_client(). +invalid_signature_client(Config) when is_list(Config) -> + ssl_cert_tests:invalid_signature_client(Config). +%%-------------------------------------------------------------------- +invalid_signature_server() -> + ssl_cert_tests:invalid_signature_client(). +invalid_signature_server(Config) when is_list(Config) -> + ssl_cert_tests:invalid_signature_client(Config). + +%%-------------------------------------------------------------------- +%% TLS 1.3 Test Cases ------------------------------------------------ +%%-------------------------------------------------------------------- +hello_retry_request() -> + ssl_cert_tests:hello_retry_request(). +hello_retry_request(Config) -> + ssl_cert_tests:hello_retry_request(Config). +%%-------------------------------------------------------------------- +custom_groups() -> + ssl_cert_tests:custom_groups(). +custom_groups(Config) -> + ssl_cert_tests:custom_groups(Config). +unsupported_sign_algo_cert_client_auth() -> + ssl_cert_tests:unsupported_sign_algo_cert_client_auth(). +unsupported_sign_algo_cert_client_auth(Config) -> + ssl_cert_tests:unsupported_sign_algo_cert_client_auth(Config). +unsupported_sign_algo_client_auth() -> + ssl_cert_tests:unsupported_sign_algo_client_auth(). +unsupported_sign_algo_client_auth(Config) -> + ssl_cert_tests:unsupported_sign_algo_client_auth(Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth() -> + ssl_cert_tests:hello_retry_client_auth(). +hello_retry_client_auth(Config) -> + ssl_cert_tests:hello_retry_client_auth(Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_accepted() -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_accepted(). +hello_retry_client_auth_empty_cert_accepted(Config) -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_accepted(Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_rejected() -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_rejected(). +hello_retry_client_auth_empty_cert_rejected(Config) -> + ssl_cert_tests:hello_retry_client_auth_empty_cert_rejected(Config). diff --git a/lib/ssl/test/openssl_session_SUITE.erl b/lib/ssl/test/openssl_session_SUITE.erl new file mode 100644 index 0000000000..97d83b98c3 --- /dev/null +++ b/lib/ssl/test/openssl_session_SUITE.erl @@ -0,0 +1,258 @@ +%% +%% %CopyrightBegin% +%% +%% 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 +%% +%% 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(openssl_session_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-define(SLEEP, 1000). +-define(EXPIRE, 10). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'}]; + false -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}] + end. + +groups() -> + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{'tlsv1.2', [], tests()}, + {'tlsv1.1', [], tests()}, + {'tlsv1', [], tests()}, + {'sslv3', [], tests()}, + {'dtlsv1.2', [], tests()}, + {'dtlsv1', [], tests()} + ]; + false -> + [{'tlsv1.2', [], tests()}, + {'tlsv1.1', [], tests()}, + {'tlsv1', [], tests()}, + {'sslv3', [], tests()} + ] + end. + +tests() -> + [ + reuse_session_erlang_server, + reuse_session_erlang_client + ]. + + +init_per_suite(Config0) -> + case os:find_executable("openssl") of + false -> + {skip, "Openssl not found"}; + _ -> + ct:pal("Version: ~p", [os:cmd("openssl version")]), + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto), + ssl_test_lib:kill_openssl(). + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(reuse_session_erlang_client, Config) -> + ct:timetrap(?EXPIRE * 1000 * 5), + ssl:stop(), + application:load(ssl), + application:set_env(ssl, session_lifetime, ?EXPIRE), + ssl:start(), + Config; + +init_per_testcase(TestCase, Config) -> + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(reuse_session_erlang_client, Config) -> + application:unset_env(ssl, session_lifetime), + Config; +end_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +reuse_session_erlang_server() -> + [{doc, "Test erlang server with openssl client that reconnects with the" + "same session id, to test reusing of sessions."}]. +reuse_session_erlang_server(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, active_recv, [length(Data)]}}, + {reconnect_times, 5}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_client", "-connect", ssl_test_lib:hostname_format(Hostname) + ++ ":" ++ integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-reconnect"], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + + true = port_command(OpenSslPort, Data), + + ssl_test_lib:check_result(Server, Data), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close(Server), + ssl_test_lib:close_port(OpenSslPort). + +%%-------------------------------------------------------------------- + +reuse_session_erlang_client() -> + [{doc, "Test erlang ssl client that wants to reuse sessions"}]. +reuse_session_erlang_client(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Version = ssl_test_lib:protocol_version(Config), + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + CACertFile = proplists:get_value(cacertfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-cert", CertFile,"-key", KeyFile, "-CAfile", CACertFile], + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client0 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_sessions, save}, {verify, verify_peer}| ClientOpts]}]), + + SID = receive + {Client0, Id0} -> + Id0 + end, + + ssl_test_lib:close(Client0), + + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_session, SID} | ClientOpts]}]), + receive + {Client1, SID} -> + ok + after ?SLEEP -> + ct:fail(session_not_reused) + end, + + + ssl_test_lib:close(Client1), + %% Make sure session is unregistered due to expiration + ct:sleep(20000), + + Client2 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, ClientOpts}]), + receive + {Client2, ID} -> + case ID of + SID -> + ct:fail(expired_session_reused); + _ -> + ok + end + end, + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + ssl_test_lib:close(Client2). + + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- diff --git a/lib/ssl/test/openssl_sni_SUITE.erl b/lib/ssl/test/openssl_sni_SUITE.erl new file mode 100644 index 0000000000..26f08e36c0 --- /dev/null +++ b/lib/ssl/test/openssl_sni_SUITE.erl @@ -0,0 +1,251 @@ +%% +%% %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(openssl_sni_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-define(OPENSSL_QUIT, "Q\n"). +-define(OPENSSL_RENEGOTIATE, "R\n"). +-define(SLEEP, 1000). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + %% Note: SNI not supported in sslv3 + case ssl_test_lib:openssl_sane_dtls() of + true -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'} + %% Seems broken in openssl + %%{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', [], sni_tests()}, + {'tlsv1.1', [], sni_tests()}, + {'tlsv1', [], sni_tests()} + %% Seems broken in openssl + %%{'dtlsv1.2', [], sni_tests()}, + %%{'dtlsv1', [], sni_tests()} + ]; + false -> + [{'tlsv1.2', [], sni_tests()}, + {'tlsv1.1', [], sni_tests()}, + {'tlsv1', [], sni_tests()} + ] + end. + +sni_tests() -> + [erlang_server_openssl_client_sni_match, + erlang_server_openssl_client_sni_match_fun, + erlang_server_openssl_client_sni_no_match, + erlang_server_openssl_client_sni_no_match_fun, + erlang_server_openssl_client_sni_no_header, + erlang_server_openssl_client_sni_no_header_fun]. + +init_per_suite(Config0) -> + case os:find_executable("openssl") of + false -> + {skip, "Openssl not found"}; + _ -> + case check_openssl_sni_support(Config0) of + {skip, _} = Skip -> + Skip; + _ -> + ct:pal("Version: ~p", [os:cmd("openssl version")]), + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + Config = ssl_test_lib:make_rsa_cert(Config0), + RsaOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + [{sni_server_opts, [{sni_hosts, + [{"a.server", [ + {certfile, proplists:get_value(certfile, RsaOpts)}, + {keyfile, proplists:get_value(keyfile, RsaOpts)} + ]}, + {"b.server", [ + {certfile, proplists:get_value(certfile, RsaOpts)}, + {keyfile, proplists:get_value(keyfile, RsaOpts)} + ]} + ]}]} | Config] + catch _:_ -> + {skip, "Crypto did not start"} + end + end + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto), + ssl_test_lib:kill_openssl(). + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:supports_ssl_tls_version(GroupName) of + true -> + case ssl_test_lib:check_sane_openssl_version(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, openssl_does_not_support_version} + end; + false -> + {skip, openssl_does_not_support_version} + end; + _ -> + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, 10}), + Config. + + +end_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +erlang_server_openssl_client_sni_no_header(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test(Config, undefined, undefined, "server Peer cert"). + +erlang_server_openssl_client_sni_no_header_fun(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test_sni_fun(Config, undefined, undefined, "server Peer cert"). + +erlang_server_openssl_client_sni_match(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test(Config, "a.server", "a.server", "server Peer cert"). + +erlang_server_openssl_client_sni_match_fun(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test_sni_fun(Config, "a.server", "a.server", "server Peer cert"). + +erlang_server_openssl_client_sni_no_match(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test(Config, "c.server", undefined, "server Peer cert"). + +erlang_server_openssl_client_sni_no_match_fun(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test_sni_fun(Config, "c.server", undefined, "server Peer cert"). + + +erlang_server_openssl_client_sni_test(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> + Version = ssl_test_lib:protocol_version(Config), + ct:log("Start running handshake, Config: ~p, SNIHostname: ~p, ExpectedSNIHostname: ~p, ExpectedCN: ~p", + [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), + ServerOptions = proplists:get_value(sni_server_opts, Config) ++ proplists:get_value(server_rsa_opts, Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, + {options, ServerOptions}]), + Port = ssl_test_lib:inet_port(Server), + Exe = "openssl", + ClientArgs = case SNIHostname of + undefined -> + openssl_client_args(Version, Hostname,Port); + _ -> + openssl_client_args(Version, Hostname, Port, SNIHostname) + end, + ClientPort = ssl_test_lib:portable_open_port(Exe, ClientArgs), + + ssl_test_lib:check_result(Server, ExpectedSNIHostname), + ssl_test_lib:close_port(ClientPort), + ssl_test_lib:close(Server), + ok. + + +erlang_server_openssl_client_sni_test_sni_fun(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> + Version = ssl_test_lib:protocol_version(Config), + ct:log("Start running handshake for sni_fun, Config: ~p, SNIHostname: ~p, ExpectedSNIHostname: ~p, ExpectedCN: ~p", + [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), + [{sni_hosts, ServerSNIConf}] = proplists:get_value(sni_server_opts, Config), + SNIFun = fun(Domain) -> proplists:get_value(Domain, ServerSNIConf, undefined) end, + ServerOptions = proplists:get_value(server_rsa_opts, Config) ++ [{sni_fun, SNIFun}], + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, + {options, ServerOptions}]), + Port = ssl_test_lib:inet_port(Server), + Exe = "openssl", + ClientArgs = case SNIHostname of + undefined -> + openssl_client_args(Version, Hostname,Port); + _ -> + openssl_client_args(Version, Hostname, Port, SNIHostname) + end, + + ClientPort = ssl_test_lib:portable_open_port(Exe, ClientArgs), + + ssl_test_lib:check_result(Server, ExpectedSNIHostname), + ssl_test_lib:close_port(ClientPort), + ssl_test_lib:close(Server). + +send_and_hostname(SSLSocket) -> + ssl:send(SSLSocket, "OK"), + case ssl:connection_information(SSLSocket, [sni_hostname]) of + {ok, []} -> + undefined; + {ok, [{sni_hostname, Hostname}]} -> + Hostname + end. + +openssl_client_args(Version, Hostname, Port) -> + ["s_client", "-connect", Hostname ++ ":" ++ integer_to_list(Port), ssl_test_lib:version_flag(Version)]. + +openssl_client_args(Version, Hostname, Port, ServerName) -> + ["s_client", "-connect", Hostname ++ ":" ++ + integer_to_list(Port), ssl_test_lib:version_flag(Version), "-servername", ServerName]. + +check_openssl_sni_support(Config) -> + HelpText = os:cmd("openssl s_client --help"), + case ssl_test_lib:is_sane_oppenssl_client() of + true -> + case string:str(HelpText, "-servername") of + 0 -> + {skip, "Current openssl doesn't support SNI"}; + _ -> + Config + end; + false -> + {skip, "Current openssl doesn't support SNI or extension handling is flawed"} + end. diff --git a/lib/ssl/test/openssl_tls_1_3_version_SUITE.erl b/lib/ssl/test/openssl_tls_1_3_version_SUITE.erl new file mode 100644 index 0000000000..8a2692ec1d --- /dev/null +++ b/lib/ssl/test/openssl_tls_1_3_version_SUITE.erl @@ -0,0 +1,172 @@ +%% +%% %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(openssl_tls_1_3_version_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() -> + [ + %%{group, openssl_server}, + {group, openssl_client} + ]. + +groups() -> + [ + %%{openssl_server, [{group, 'tlsv1.3'}]}, + {openssl_client, [{group, 'tlsv1.3'}]}, + {'tlsv1.3', [], cert_groups()}, + {rsa, [], tests()}, + {ecdsa, [], tests()} + ]. + +cert_groups() -> + [{group, rsa}, + {group, ecdsa}]. + +tests() -> + [tls13_client_tls12_server, + %%tls13_client_with_ext_tls12_server, + tls12_client_tls13_server]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + case ssl_test_lib:check_sane_openssl_version('tlsv1.3') of + true -> + ssl_test_lib:clean_start(), + Config; + false -> + {skip, openssl_does_not_support_version} + end + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto). + +init_per_group(openssl_client, Config0) -> + Config = proplists:delete(server_type, proplists:delete(client_type, Config0)), + [{client_type, openssl}, {server_type, erlang} | Config]; +init_per_group(openssl_server, Config0) -> + Config = proplists:delete(server_type, proplists:delete(client_type, Config0)), + [{client_type, erlang}, {server_type, openssl} | Config]; +init_per_group(rsa, Config0) -> + Config = ssl_test_lib:make_rsa_cert(Config0), + COpts = proplists:get_value(client_rsa_opts, Config), + SOpts = proplists:get_value(server_rsa_opts, Config), + [{client_cert_opts, COpts}, {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))]; +init_per_group(ecdsa, Config0) -> + PKAlg = crypto:supports(public_keys), + case lists:member(ecdsa, PKAlg) andalso + (lists:member(ecdh, PKAlg) orelse lists:member(dh, PKAlg)) of + true -> + Config = ssl_test_lib:make_ecdsa_cert(Config0), + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + [{client_cert_opts, COpts}, {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))]; + false -> + {skip, "Missing EC crypto support"} + end; +init_per_group(GroupName, Config) -> + ssl_test_lib:clean_tls_version(Config), + case ssl_test_lib:is_tls_version(GroupName) andalso + ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + _ -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl:start(), + Config; + false -> + {skip, "Missing crypto support"} + end + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +tls13_client_tls12_server() -> + [{doc,"Test that a TLS 1.3 client can connect to a TLS 1.2 server."}]. + +tls13_client_tls12_server(Config) when is_list(Config) -> + ClientOpts = [{versions, + ['tlsv1.3', 'tlsv1.2']} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{versions, + ['tlsv1.1', 'tlsv1.2']} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%% tls13_client_with_ext_tls12_server() -> +%% [{doc,"Test basic connection between TLS 1.2 server and TLS 1.3 client when " +%% "client has TLS 1.3 specsific extensions"}]. + +%% tls13_client_with_ext_tls12_server(Config) -> +%% ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), +%% ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + +%% {ServerOpts, ClientOpts} = +%% case proplists:get_value(client_type) of +%% erlang -> +%% {[{versions, ['tlsv1.2']}|ServerOpts0], +%% [{versions, ['tlsv1.2','tlsv1.3']}, +%% {signature_algs_cert, [ecdsa_secp384r1_sha384, +%% ecdsa_secp256r1_sha256, +%% rsa_pss_rsae_sha256, +%% rsa_pkcs1_sha256, +%% {sha256,rsa},{sha256,dsa}]}|ClientOpts0]}; +%% openssl -> + + +%% ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +tls12_client_tls13_server() -> + [{doc,"Test that a TLS 1.2 client can connect to a TLS 1.3 server."}]. + +tls12_client_tls13_server(Config) when is_list(Config) -> + ClientOpts = [{versions, + ['tlsv1.1', 'tlsv1.2']} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{versions, + ['tlsv1.3', 'tlsv1.2']} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + diff --git a/lib/ssl/test/property_test/ssl_eqc_handshake.erl b/lib/ssl/test/property_test/ssl_eqc_handshake.erl index 38a4b7fb11..2ceb540e15 100644 --- a/lib/ssl/test/property_test/ssl_eqc_handshake.erl +++ b/lib/ssl/test/property_test/ssl_eqc_handshake.erl @@ -119,19 +119,27 @@ tls_msg(Version) -> %% client_hello(?'TLS_v1.3' = Version) -> #client_hello{session_id = session_id(), - client_version = ?'TLS_v1.2', - cipher_suites = cipher_suites(Version), + client_version = ?'TLS_v1.2', + cipher_suites = cipher_suites(Version), + compression_methods = compressions(Version), + random = client_random(Version), + extensions = client_hello_extensions(Version) + }; +client_hello(Version) -> + #client_hello{session_id = session_id(), + client_version = Version, + cipher_suites = cipher_suites(Version), compression_methods = compressions(Version), random = client_random(Version), extensions = client_hello_extensions(Version) }; -client_hello(Version) -> +client_hello(?'SSL_v3' = Version) -> #client_hello{session_id = session_id(), client_version = Version, cipher_suites = cipher_suites(Version), compression_methods = compressions(Version), random = client_random(Version), - extensions = client_hello_extensions(Version) + extensions = ssl_handshake:empty_extensions(Version, client_hello) }. server_hello(?'TLS_v1.3' = Version) -> @@ -142,6 +150,14 @@ server_hello(?'TLS_v1.3' = Version) -> compression_method = compression(Version), extensions = server_hello_extensions(Version) }; +server_hello(?'SSL_v3' = Version) -> + #server_hello{server_version = Version, + session_id = session_id(), + random = server_random(Version), + cipher_suite = cipher_suite(Version), + compression_method = compression(Version), + extensions = ssl_handshake:empty_extensions(Version, server_hello) + }; server_hello(Version) -> #server_hello{server_version = Version, session_id = session_id(), @@ -291,7 +307,7 @@ pre_shared_keyextension() -> %% | | | %% | signature_algorithms_cert (RFC 8446) | CH, CR | %% +--------------------------------------------------+-------------+ -extensions(?'TLS_v1.3' = Version, client_hello) -> +extensions(?'TLS_v1.3' = Version, MsgType = client_hello) -> ?LET({ ServerName, %% MaxFragmentLength, @@ -306,8 +322,8 @@ extensions(?'TLS_v1.3' = Version, client_hello) -> %% ServerCertificateType, %% Padding, KeyShare, - %% PreSharedKey, - %% PSKKeyExchangeModes, + PreSharedKey, + PSKKeyExchangeModes, %% EarlyData, %% Cookie, SupportedVersions, @@ -328,9 +344,9 @@ extensions(?'TLS_v1.3' = Version, client_hello) -> %% oneof([client_cert_type(), undefined]), %% oneof([server_cert_type(), undefined]), %% oneof([padding(), undefined]), - oneof([key_share(client_hello), undefined]), - %% oneof([pre_shared_key(), undefined]), - %% oneof([psk_key_exchange_modes(), undefined]), + oneof([key_share(MsgType), undefined]), + oneof([pre_shared_key(MsgType), undefined]), + oneof([psk_key_exchange_modes(), undefined]), %% oneof([early_data(), undefined]), %% oneof([cookie(), undefined]), oneof([client_hello_versions(Version)]), @@ -357,8 +373,8 @@ extensions(?'TLS_v1.3' = Version, client_hello) -> %% server_cert_type => ServerCertificateType, %% padding => Padding, key_share => KeyShare, - %% pre_shared_key => PreSharedKey, - %% psk_key_exhange_modes => PSKKeyExchangeModes, + pre_shared_key => PreSharedKey, + psk_key_exchange_modes => PSKKeyExchangeModes, %% early_data => EarlyData, %% cookie => Cookie, client_hello_versions => SupportedVersions, @@ -401,15 +417,15 @@ extensions(Version, client_hello) -> srp => SRP %% renegotiation_info => RenegotiationInfo })); -extensions(?'TLS_v1.3' = Version, server_hello) -> +extensions(?'TLS_v1.3' = Version, MsgType = server_hello) -> ?LET({ KeyShare, - %% PreSharedKeys, + PreSharedKey, SupportedVersions }, { - oneof([key_share(server_hello), undefined]), - %% oneof([pre_shared_keys(), undefined]), + oneof([key_share(MsgType), undefined]), + oneof([pre_shared_key(MsgType), undefined]), oneof([server_hello_selected_version()]) }, maps:filter(fun(_, undefined) -> @@ -419,7 +435,7 @@ extensions(?'TLS_v1.3' = Version, server_hello) -> end, #{ key_share => KeyShare, - %% pre_shared_keys => PreSharedKeys, + pre_shared_key => PreSharedKey, server_hello_selected_version => SupportedVersions })); extensions(Version, server_hello) -> @@ -810,3 +826,58 @@ group_list(N, Pool, Acc) -> R = rand:uniform(length(Pool)), G = lists:nth(R, Pool), group_list(N - 1, Pool -- [G], [G|Acc]). + + +ke_modes() -> + oneof([[psk_ke],[psk_dhe_ke],[psk_ke,psk_dhe_ke]]). + +psk_key_exchange_modes() -> + ?LET(KEModes, ke_modes(), + #psk_key_exchange_modes{ + ke_modes = KEModes}). + +pre_shared_key(client_hello) -> + ?LET(OfferedPsks, offered_psks(), + #pre_shared_key_client_hello{ + offered_psks = OfferedPsks}); +pre_shared_key(server_hello) -> + ?LET(SelectedIdentity, selected_identity(), + #pre_shared_key_server_hello{ + selected_identity = SelectedIdentity}). + +selected_identity() -> + rand:uniform(32). + +offered_psks() -> + ?LET(Size, choose(1,5), + #offered_psks{ + identities = psk_identities(Size), + binders = psk_binders(Size)}). + +psk_identities(Size) -> + psk_identities(Size, []). +%% +psk_identities(0, Acc) -> + Acc; +psk_identities(N, Acc) -> + psk_identities(N - 1, [psk_identity()|Acc]). + +psk_identity() -> + Len = rand:uniform(32), + Identity = crypto:strong_rand_bytes(Len), + Age = crypto:strong_rand_bytes(4), + #psk_identity{ + identity = Identity, + obfuscated_ticket_age = Age}. + +psk_binders(Size) -> + psk_binders(Size, []). +%% +psk_binders(0, Acc) -> + Acc; +psk_binders(N, Acc) -> + psk_binders(N - 1, [psk_binder()|Acc]). + +psk_binder() -> + Len = rand:uniform(224) + 31, + crypto:strong_rand_bytes(Len). diff --git a/lib/ssl/test/ssl_ECC_openssl_SUITE.erl b/lib/ssl/test/ssl_ECC_openssl_SUITE.erl index 68d4e910fd..787c08a517 100644 --- a/lib/ssl/test/ssl_ECC_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_ECC_openssl_SUITE.erl @@ -67,7 +67,8 @@ init_per_suite(Config0) -> end_per_suite(_Config) -> application:stop(ssl), - application:stop(crypto). + application:stop(crypto), + ssl_test_lib:kill_openssl(). %%-------------------------------------------------------------------- init_per_group(GroupName, Config) -> diff --git a/lib/ssl/test/ssl_alert_SUITE.erl b/lib/ssl/test/ssl_alert_SUITE.erl new file mode 100644 index 0000000000..cc0b636580 --- /dev/null +++ b/lib/ssl/test/ssl_alert_SUITE.erl @@ -0,0 +1,100 @@ +%% +%% 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(ssl_alert_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"). + +-include_lib("ssl/src/ssl_alert.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + alerts, + alert_details, + alert_details_not_too_big + ]. + +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, 5}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +alerts() -> + [{doc, "Test ssl_alert:alert_txt/1"}]. +alerts(Config) when is_list(Config) -> + Descriptions = [?CLOSE_NOTIFY, ?UNEXPECTED_MESSAGE, ?BAD_RECORD_MAC, + ?DECRYPTION_FAILED_RESERVED, ?RECORD_OVERFLOW, ?DECOMPRESSION_FAILURE, + ?HANDSHAKE_FAILURE, ?BAD_CERTIFICATE, ?UNSUPPORTED_CERTIFICATE, + ?CERTIFICATE_REVOKED,?CERTIFICATE_EXPIRED, ?CERTIFICATE_UNKNOWN, + ?ILLEGAL_PARAMETER, ?UNKNOWN_CA, ?ACCESS_DENIED, ?DECODE_ERROR, + ?DECRYPT_ERROR, ?EXPORT_RESTRICTION, ?PROTOCOL_VERSION, + ?INSUFFICIENT_SECURITY, ?INTERNAL_ERROR, ?USER_CANCELED, + ?NO_RENEGOTIATION, ?UNSUPPORTED_EXTENSION, ?CERTIFICATE_UNOBTAINABLE, + ?UNRECOGNISED_NAME, ?BAD_CERTIFICATE_STATUS_RESPONSE, + ?BAD_CERTIFICATE_HASH_VALUE, ?UNKNOWN_PSK_IDENTITY, + 255 %% Unsupported/unknow alert will result in a description too + ], + Alerts = [?ALERT_REC(?WARNING, ?CLOSE_NOTIFY) | + [?ALERT_REC(?FATAL, Desc) || Desc <- Descriptions]], + lists:foreach(fun(Alert) -> + try ssl_alert:alert_txt(Alert) + catch + C:E:T -> + ct:fail({unexpected, {C, E, T}}) + end + end, Alerts). +%%-------------------------------------------------------------------- +alert_details() -> + [{doc, "Test that ssl_alert:alert_txt/1 result contains extendend error description"}]. +alert_details(Config) when is_list(Config) -> + Unique = make_ref(), + UniqueStr = lists:flatten(io_lib:format("~w", [Unique])), + Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY, Unique), + case string:str(ssl_alert:alert_txt(Alert), UniqueStr) of + 0 -> + ct:fail(error_details_missing); + _ -> + ok + end. + +%%-------------------------------------------------------------------- +alert_details_not_too_big() -> + [{doc, "Test that ssl_alert:alert_txt/1 limits printed depth of extended error description"}]. +alert_details_not_too_big(Config) when is_list(Config) -> + Reason = lists:duplicate(10, lists:duplicate(10, lists:duplicate(10, {some, data}))), + Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY, Reason), + case length(ssl_alert:alert_txt(Alert)) < 1000 of + true -> + ok; + false -> + ct:fail(ssl_alert_text_too_big) + end. diff --git a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl b/lib/ssl/test/ssl_alpn_SUITE.erl index dfc780479e..82a49e1469 100644 --- a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_alpn_SUITE.erl @@ -19,7 +19,7 @@ %% %% --module(ssl_alpn_handshake_SUITE). +-module(ssl_alpn_SUITE). %% Note: This directive should only be used in test suites. -compile(export_all). @@ -32,7 +32,9 @@ %%-------------------------------------------------------------------- all() -> - [{group, 'tlsv1.2'}, + [ + {group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, {group, 'tlsv1.1'}, {group, 'tlsv1'}, {group, 'sslv3'}, @@ -42,12 +44,13 @@ all() -> groups() -> [ - {'tlsv1.2', [], alpn_tests()}, - {'tlsv1.1', [], alpn_tests()}, - {'tlsv1', [], alpn_tests()}, + {'tlsv1.3', [], alpn_tests() -- [client_renegotiate, session_reused]}, + {'tlsv1.2', [], alpn_tests() ++ alpn_npn_coexist()}, + {'tlsv1.1', [], alpn_tests() ++ alpn_npn_coexist()}, + {'tlsv1', [], alpn_tests() ++ alpn_npn_coexist()}, {'sslv3', [], alpn_not_supported()}, - {'dtlsv1.2', [], alpn_tests() -- [client_renegotiate]}, - {'dtlsv1', [], alpn_tests() -- [client_renegotiate]} + {'dtlsv1.2', [], alpn_tests() ++ alpn_npn_coexist()}, + {'dtlsv1', [], alpn_tests() ++ alpn_npn_coexist()} ]. alpn_tests() -> @@ -61,12 +64,16 @@ alpn_tests() -> client_alpn_and_server_no_support, client_no_support_and_server_alpn, client_alpn_npn_and_server_alpn, - client_alpn_npn_and_server_alpn_npn, - client_alpn_and_server_alpn_npn, client_renegotiate, session_reused ]. +alpn_npn_coexist() -> + [ + client_alpn_npn_and_server_alpn_npn, + client_alpn_and_server_alpn_npn + ]. + alpn_not_supported() -> [alpn_not_supported_client, alpn_not_supported_server @@ -77,8 +84,7 @@ init_per_suite(Config0) -> try crypto:start() of ok -> ssl_test_lib:clean_start(), - Config = ssl_test_lib:make_rsa_cert(Config0), - ssl_test_lib:cert_options(Config) + ssl_test_lib:make_rsa_cert(Config0) catch _:_ -> {skip, "Crypto did not start"} end. diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl new file mode 100644 index 0000000000..fefecc0b65 --- /dev/null +++ b/lib/ssl/test/ssl_api_SUITE.erl @@ -0,0 +1,1976 @@ +%% +%% %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(ssl_api_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("ssl/src/ssl_api.hrl"). + +-define(SLEEP, 500). +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + [ + {group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +groups() -> + [ + {'tlsv1.3', [], ((gen_api_tests() ++ tls13_group() ++ handshake_paus_tests()) -- [dh_params, honor_server_cipher_order, honor_client_cipher_order, + new_options_in_handshake]) + ++ (since_1_2() -- [conf_signature_algs])}, + {'tlsv1.2', [], gen_api_tests() ++ since_1_2() ++ handshake_paus_tests() ++ pre_1_3()}, + {'tlsv1.1', [], gen_api_tests() ++ handshake_paus_tests() ++ pre_1_3()}, + {'tlsv1', [], gen_api_tests() ++ handshake_paus_tests() ++ pre_1_3() ++ beast_mitigation_test()}, + {'sslv3', [], (gen_api_tests() -- [new_options_in_handshake]) ++ beast_mitigation_test() ++ pre_1_3()}, + {'dtlsv1.2', [], (gen_api_tests() -- [invalid_keyfile, invalid_certfile, invalid_cacertfile, + invalid_options, new_options_in_handshake]) ++ handshake_paus_tests() ++ pre_1_3()}, + {'dtlsv1', [], (gen_api_tests() -- [invalid_keyfile, invalid_certfile, invalid_cacertfile, + invalid_options, new_options_in_handshake]) ++ handshake_paus_tests() ++ pre_1_3()} + ]. + +since_1_2() -> + [ + conf_signature_algs, + no_common_signature_algs + ]. + +pre_1_3() -> + [ + default_reject_anonymous + ]. +gen_api_tests() -> + [ + peercert, + peercert_with_client_cert, + connection_information, + secret_connection_info, + versions, + active_n, + dh_params, + hibernate, + hibernate_right_away, + listen_socket, + recv_active, + recv_active_once, + recv_active_n, + recv_timeout, + recv_close, + controlling_process, + controller_dies, + controlling_process_transport_accept_socket, + close_with_timeout, + close_in_error_state, + call_in_error_state, + close_transport_accept, + abuse_transport_accept_socket, + honor_server_cipher_order, + honor_client_cipher_order, + ipv6, + der_input, + new_options_in_handshake, + max_handshake_size, + invalid_certfile, + invalid_cacertfile, + invalid_keyfile, + options_not_proplist, + invalid_options + ]. + +handshake_paus_tests() -> + [ + handshake_continue, + handshake_continue_timeout, + hello_client_cancel, + hello_server_cancel + ]. + +%% Only relevant for SSL 3.0 and TLS 1.1 +beast_mitigation_test() -> + [%% Original option + rizzo_disabled, + %% Same effect as disable + rizzo_zero_n, + %% Same as default + rizzo_one_n_minus_one + ]. + +tls13_group() -> + [ + supported_groups, + honor_server_cipher_order_tls13, + honor_client_cipher_order_tls13 + ]. + + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + [{client_type, erlang}, + {server_type, erlang} | ssl_test_lib:init_tls_version(GroupName, Config)]; + false -> + {skip, "Missing crypto support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(prf, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Version = ssl_test_lib:protocol_version(Config), + PRFS = [md5, sha, sha256, sha384, sha512], + %% All are the result of running tls_v1:prf(PrfAlgo, <<>>, <<>>, <<>>, 16) + %% with the specified PRF algorithm + ExpectedPrfResults = + [{md5, <<96,139,180,171,236,210,13,10,28,32,2,23,88,224,235,199>>}, + {sha, <<95,3,183,114,33,169,197,187,231,243,19,242,220,228,70,151>>}, + {sha256, <<166,249,145,171,43,95,158,232,6,60,17,90,183,180,0,155>>}, + {sha384, <<153,182,217,96,186,130,105,85,65,103,123,247,146,91,47,106>>}, + {sha512, <<145,8,98,38,243,96,42,94,163,33,53,49,241,4,127,28>>}, + %% TLS 1.0 and 1.1 PRF: + {md5sha, <<63,136,3,217,205,123,200,177,251,211,17,229,132,4,173,80>>}], + TestPlan = prf_create_plan([Version], PRFS, ExpectedPrfResults), + [{prf_test_plan, TestPlan} | Config]; +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(internal_active_n, _Config) -> + application:unset_env(ssl, internal_active_n); +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +peercert() -> + [{doc,"Test API function peercert/1"}]. +peercert(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, peercert_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, peercert_result, []}}, + {options, ClientOpts}]), + + CertFile = proplists:get_value(certfile, ServerOpts), + [{'Certificate', BinCert, _}]= ssl_test_lib:pem_to_der(CertFile), + + ServerMsg = {error, no_peercert}, + ClientMsg = {ok, BinCert}, + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- + +peercert_with_client_cert() -> + [{doc,"Test API function peercert/1"}]. +peercert_with_client_cert(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, peercert_result, []}}, + {options, [{verify, verify_peer} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, peercert_result, []}}, + {options, ClientOpts}]), + + ServerCertFile = proplists:get_value(certfile, ServerOpts), + [{'Certificate', ServerBinCert, _}]= ssl_test_lib:pem_to_der(ServerCertFile), + ClientCertFile = proplists:get_value(certfile, ClientOpts), + [{'Certificate', ClientBinCert, _}]= ssl_test_lib:pem_to_der(ClientCertFile), + + ServerMsg = {ok, ClientBinCert}, + ClientMsg = {ok, ServerBinCert}, + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +connection_information() -> + [{doc,"Test the API function ssl:connection_information/1"}]. +connection_information(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, 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_information_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_information_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- + +secret_connection_info() -> + [{doc,"Test the API function ssl:connection_information/2"}]. +secret_connection_info(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, secret_connection_info_result, []}}, + {options, [{verify, verify_peer} | ServerOpts]}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, secret_connection_info_result, []}}, + {options, [{verify, verify_peer} |ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, true, Client, true), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +prf() -> + [{doc,"Test that ssl:prf/5 uses the negotiated PRF."}]. +prf(Config) when is_list(Config) -> + TestPlan = proplists:get_value(prf_test_plan, Config), + case TestPlan of + [] -> ct:fail({error, empty_prf_test_plan}); + _ -> lists:foreach(fun(Suite) -> + lists:foreach( + fun(Test) -> + V = proplists:get_value(tls_ver, Test), + C = proplists:get_value(ciphers, Test), + E = proplists:get_value(expected, Test), + P = proplists:get_value(prf, Test), + prf_run_test(Config, V, C, E, P) + end, Suite) + end, TestPlan) + end. + +%%-------------------------------------------------------------------- +dh_params() -> + [{doc,"Test to specify DH-params file in server."}]. + +dh_params(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + DataDir = proplists:get_value(data_dir, Config), + DHParamFile = filename:join(DataDir, "dHParam.pem"), + + {ClientNode, ServerNode, Hostname} = 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, [{dhfile, DHParamFile} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, + [{ciphers,[{dhe_rsa,aes_256_cbc,sha}]} | + ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +conf_signature_algs() -> + [{doc,"Test to set the signature_algs option on both client and server"}]. +conf_signature_algs(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = 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, []}}, + {options, [{active, false}, {signature_algs, [{sha256, rsa}]} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false}, {signature_algs, [{sha256, rsa}]} | ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +%%-------------------------------------------------------------------- +no_common_signature_algs() -> + [{doc,"Set the signature_algs option so that there client and server does not share any hash sign algorithms"}]. +no_common_signature_algs(Config) when is_list(Config) -> + + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, [{signature_algs, [{sha256, rsa}]} + | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, [{signature_algs, [{sha384, rsa}]} + | ClientOpts]}]), + + ssl_test_lib:check_server_alert(Server, Client, insufficient_security). + +%%-------------------------------------------------------------------- +handshake_continue() -> + [{doc, "Test API function ssl:handshake_continue/3"}]. +handshake_continue(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = 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, ssl_test_lib:ssl_options([{reuseaddr, true}, + {verify, verify_peer}, + {handshake, hello} | ServerOpts + ], + Config)}, + {continue_options, proplists:delete(reuseaddr, ServerOpts)} + ]), + + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ssl_test_lib:ssl_options([{handshake, hello}, + {verify, verify_peer} | ClientOpts + ], + Config)}, + {continue_options, proplists:delete(reuseaddr, ClientOpts)}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +%%------------------------------------------------------------------ +handshake_continue_timeout() -> + [{doc, "Test API function ssl:handshake_continue/3 with short timeout"}]. +handshake_continue_timeout(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {timeout, 1}, + {options, ssl_test_lib:ssl_options([{reuseaddr, true}, {handshake, hello}, + {verify, verify_peer} | ServerOpts], + Config)}, + {continue_options, proplists:delete(reuseaddr, ServerOpts)} + ]), + + Port = ssl_test_lib:inet_port(Server), + + + ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, [{verify, verify_peer} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, {error,timeout}), + ssl_test_lib:close(Server). + + +%%-------------------------------------------------------------------- +hello_client_cancel() -> + [{doc, "Test API function ssl:handshake_cancel/1 on the client side"}]. +hello_client_cancel(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ssl_test_lib:ssl_options([{handshake, hello}, + {verify, verify_peer} | ServerOpts], Config)}, + {continue_options, proplists:delete(reuseaddr, ServerOpts)}]), + + Port = ssl_test_lib:inet_port(Server), + + %% That is ssl:handshake_cancel returns ok + {connect_failed, ok} = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, ssl_test_lib:ssl_options([{handshake, hello}, + {verify, verify_peer} | ClientOpts], Config)}, + {continue_options, cancel}]), + ssl_test_lib:check_server_alert(Server, user_canceled). +%%-------------------------------------------------------------------- +hello_server_cancel() -> + [{doc, "Test API function ssl:handshake_cancel/1 on the server side"}]. +hello_server_cancel(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ssl_test_lib:ssl_options([{handshake, hello}, + {verify, verify_peer} | ServerOpts + ], Config)}, + {continue_options, cancel}]), + + Port = ssl_test_lib:inet_port(Server), + + ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, ssl_test_lib:ssl_options([{handshake, hello}, + {verify, verify_peer} | ClientOpts + ], Config)}, + {continue_options, proplists:delete(reuseaddr, ClientOpts)}]), + + ssl_test_lib:check_result(Server, ok). + +%%-------------------------------------------------------------------- +versions() -> + [{doc,"Test API function versions/0"}]. + +versions(Config) when is_list(Config) -> + [_|_] = Versions = ssl:versions(), + ct:log("~p~n", [Versions]). + +%%-------------------------------------------------------------------- +%% Test case adapted from gen_tcp_misc_SUITE. +active_n() -> + [{doc,"Test {active,N} option"}]. + +active_n(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + Port = ssl_test_lib:inet_port(node()), + N = 3, + LS = ok(ssl:listen(Port, [{active,N}|ServerOpts])), + [{active,N}] = ok(ssl:getopts(LS, [active])), + active_n_common(LS, N), + Self = self(), + spawn_link(fun() -> + S0 = ok(ssl:transport_accept(LS)), + {ok, S} = ssl:handshake(S0), + ok = ssl:setopts(S, [{active,N}]), + [{active,N}] = ok(ssl:getopts(S, [active])), + ssl:controlling_process(S, Self), + Self ! {server, S} + end), + C = ok(ssl:connect("localhost", Port, [{active,N}|ClientOpts])), + [{active,N}] = ok(ssl:getopts(C, [active])), + S = receive + {server, S0} -> S0 + after + 1000 -> + exit({error, connect}) + end, + active_n_common(C, N), + active_n_common(S, N), + ok = ssl:setopts(C, [{active,N}]), + ok = ssl:setopts(S, [{active,N}]), + ReceiveMsg = fun(Socket, Msg) -> + receive + {ssl,Socket,Msg} -> + ok; + {ssl,Socket,Begin} -> + receive + {ssl,Socket,End} -> + Msg = Begin ++ End, + ok + after 1000 -> + exit(timeout) + end + after 1000 -> + exit(timeout) + end + end, + repeat(3, fun(I) -> + Msg = "message "++integer_to_list(I), + ok = ssl:send(C, Msg), + ReceiveMsg(S, Msg), + ok = ssl:send(S, Msg), + ReceiveMsg(C, Msg) + end), + receive + {ssl_passive,S} -> + [{active,false}] = ok(ssl:getopts(S, [active])) + after + 1000 -> + exit({error,ssl_passive}) + end, + receive + {ssl_passive,C} -> + [{active,false}] = ok(ssl:getopts(C, [active])) + after + 1000 -> + exit({error,ssl_passive}) + end, + LS2 = ok(ssl:listen(0, [{active,0}])), + receive + {ssl_passive,LS2} -> + [{active,false}] = ok(ssl:getopts(LS2, [active])) + after + 1000 -> + exit({error,ssl_passive}) + end, + ok = ssl:close(LS2), + ok = ssl:close(C), + ok = ssl:close(S), + ok = ssl:close(LS), + ok. + +hibernate() -> + [{doc,"Check that an SSL connection that is started with option " + "{hibernate_after, 1000} indeed hibernates after 1000ms of " + "inactivity"}]. + +hibernate(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = 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, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + {Client, #sslsocket{pid=[Pid|_]}} = ssl_test_lib:start_client([return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{hibernate_after, 1000}|ClientOpts]}]), + {current_function, _} = + process_info(Pid, current_function), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ct:sleep(1500), + {current_function, {erlang, hibernate, 3}} = + process_info(Pid, current_function), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- + +hibernate_right_away() -> + [{doc,"Check that an SSL connection that is configured to hibernate " + "after 0 or 1 milliseconds hibernates as soon as possible and not " + "crashes"}]. + +hibernate_right_away(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + StartServerOpts = [{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}], + StartClientOpts = [return_socket, + {node, ClientNode}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}], + + Server1 = ssl_test_lib:start_server(StartServerOpts), + Port1 = ssl_test_lib:inet_port(Server1), + {Client1, #sslsocket{pid = [Pid1|_]}} = ssl_test_lib:start_client(StartClientOpts ++ + [{port, Port1}, {options, [{hibernate_after, 0}|ClientOpts]}]), + + ssl_test_lib:check_result(Server1, ok, Client1, ok), + + ct:sleep(1000), %% Schedule out + + {current_function, {erlang, hibernate, 3}} = + process_info(Pid1, current_function), + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client1), + + Server2 = ssl_test_lib:start_server(StartServerOpts), + Port2 = ssl_test_lib:inet_port(Server2), + {Client2, #sslsocket{pid = [Pid2|_]}} = ssl_test_lib:start_client(StartClientOpts ++ + [{port, Port2}, {options, [{hibernate_after, 1}|ClientOpts]}]), + + ssl_test_lib:check_result(Server2, ok, Client2, ok), + + ct:sleep(1000), %% Schedule out + + {current_function, {erlang, hibernate, 3}} = + process_info(Pid2, current_function), + + ssl_test_lib:close(Server2), + ssl_test_lib:close(Client2). + +listen_socket() -> + [{doc,"Check error handling and inet compliance when calling API functions with listen sockets."}]. + +listen_socket(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ok, ListenSocket} = ssl:listen(0, ServerOpts), + + %% This can be a valid thing to do as + %% options are inherited by the accept socket + ok = ssl:controlling_process(ListenSocket, self()), + + {ok, _} = ssl:sockname(ListenSocket), + + {error, enotconn} = ssl:send(ListenSocket, <<"data">>), + {error, enotconn} = ssl:recv(ListenSocket, 0), + {error, enotconn} = ssl:connection_information(ListenSocket), + {error, enotconn} = ssl:peername(ListenSocket), + {error, enotconn} = ssl:peercert(ListenSocket), + {error, enotconn} = ssl:renegotiate(ListenSocket), + {error, enotconn} = ssl:prf(ListenSocket, 'master_secret', <<"Label">>, [client_random], 256), + {error, enotconn} = ssl:shutdown(ListenSocket, read_write), + + ok = ssl:close(ListenSocket). + +%%-------------------------------------------------------------------- +recv_active() -> + [{doc,"Test recv on active socket"}]. + +recv_active(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, try_recv_active, []}}, + {options, [{active, true} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, try_recv_active, []}}, + {options, [{active, true} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +recv_active_once() -> + [{doc,"Test recv on active (once) socket"}]. + +recv_active_once(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, try_recv_active_once, []}}, + {options, [{active, once} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, try_recv_active_once, []}}, + {options, [{active, once} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +recv_active_n() -> + [{doc,"Test recv on active (n) socket"}]. + +recv_active_n(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, try_recv_active_once, []}}, + {options, [{active, 1} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, try_recv_active_once, []}}, + {options, [{active, 1} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +recv_timeout() -> + [{doc,"Test ssl:ssl_accept timeout"}]. + +recv_timeout(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, send_recv_result_timeout_server, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + send_recv_result_timeout_client, []}}, + {options, [{active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +recv_close() -> + [{doc,"Special case of call error handling"}]. +recv_close(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, do_recv_close, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + {_Client, #sslsocket{} = SslSocket} = ssl_test_lib:start_client([return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + ssl:close(SslSocket), + ssl_test_lib:check_result(Server, ok). + + + +%%-------------------------------------------------------------------- +controlling_process() -> + [{doc,"Test API function controlling_process/2"}]. + +controlling_process(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ClientMsg = "Server hello", + ServerMsg = "Client hello", + + Server = ssl_test_lib:start_server([ + {node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, + controlling_process_result, [self(), + ServerMsg]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + {Client, CSocket} = ssl_test_lib:start_client([return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + controlling_process_result, [self(), + ClientMsg]}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ServerMsg = ssl_test_lib:active_recv(CSocket, length(ServerMsg)), + %% We do not have the TLS server socket but all messages form the client + %% socket are now read, so ramining are form the server socket + ClientMsg = ssl_active_recv(length(ClientMsg)), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +controller_dies() -> + [{doc,"Test that the socket is closed after controlling process dies"}]. +controller_dies(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ClientMsg = "Hello server", + ServerMsg = "Hello client", + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, + controller_dies_result, [self(), + ServerMsg]}}, + {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, + controller_dies_result, [self(), + ClientMsg]}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", [self(), Client, Server]), + ct:sleep(?SLEEP), %% so that they are connected + + process_flag(trap_exit, true), + + %% Test that clients die + exit(Client, killed), + get_close(Client, ?LINE), + + %% Test that clients die when process disappear + Server ! listen, + Tester = self(), + Connect = fun(Pid) -> + {ok, Socket} = ssl:connect(Hostname, Port, ClientOpts), + %% Make sure server finishes and verification + %% and is in coonection state before + %% killing client + ct:sleep(?SLEEP), + Pid ! {self(), connected, Socket}, + receive die_nice -> normal end + end, + Client2 = spawn_link(fun() -> Connect(Tester) end), + receive {Client2, connected, _Socket} -> Client2 ! die_nice end, + + get_close(Client2, ?LINE), + + %% Test that clients die when the controlling process have changed + Server ! listen, + + Client3 = spawn_link(fun() -> Connect(Tester) end), + Controller = spawn_link(fun() -> receive die_nice -> normal end end), + receive + {Client3, connected, Socket} -> + ok = ssl:controlling_process(Socket, Controller), + Client3 ! die_nice + end, + + ct:log("Wating on exit ~p~n",[Client3]), + receive {'EXIT', Client3, normal} -> ok end, + + receive %% Client3 is dead but that doesn't matter, socket should not be closed. + Unexpected -> + ct:log("Unexpected ~p~n",[Unexpected]), + ct:fail({line, ?LINE-1}) + after 1000 -> + ok + end, + Controller ! die_nice, + get_close(Controller, ?LINE), + + %% Test that servers die + Server ! listen, + LastClient = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + controller_dies_result, [self(), + ClientMsg]}}, + {options, ClientOpts}]), + ct:sleep(?SLEEP), %% so that they are connected + + exit(Server, killed), + get_close(Server, ?LINE), + process_flag(trap_exit, false), + ssl_test_lib:close(LastClient). +%%-------------------------------------------------------------------- +controlling_process_transport_accept_socket() -> + [{doc,"Only ssl:handshake and ssl:controlling_process is allowed for transport_accept:sockets"}]. +controlling_process_transport_accept_socket(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server_transport_control([{node, ServerNode}, + {port, 0}, + {from, self()}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + _Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, ClientOpts}]), + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server). + +%%-------------------------------------------------------------------- +close_with_timeout() -> + [{doc,"Test normal (not downgrade) ssl:close/2"}]. +close_with_timeout(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_close, []}}, + {options,[{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, tls_close, []}}, + {options, [{active, false} |ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok). + +%%-------------------------------------------------------------------- +close_in_error_state() -> + [{doc,"Special case of closing socket in error state"}]. +close_in_error_state(Config) when is_list(Config) -> + ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), + ServerOpts = [{cacertfile, "foo.pem"} | proplists:delete(cacertfile, ServerOpts0)], + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + _ = spawn_link(?MODULE, run_error_server_close, [[self() | ServerOpts]]), + receive + {_Pid, Port} -> + spawn_link(?MODULE, run_client_error, [[Port, ClientOpts]]) + end, + receive + ok -> + ok; + Other -> + ct:fail(Other) + end. + +%%-------------------------------------------------------------------- +call_in_error_state() -> + [{doc,"Special case of call error handling"}]. +call_in_error_state(Config) when is_list(Config) -> + ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = [{cacertfile, "foo.pem"} | proplists:delete(cacertfile, ServerOpts0)], + Pid = spawn_link(?MODULE, run_error_server, [[self() | ServerOpts]]), + receive + {Pid, Port} -> + spawn_link(?MODULE, run_client_error, [[Port, ClientOpts]]) + end, + receive + {error, closed} -> + ok; + Other -> + ct:fail(Other) + end. +%%-------------------------------------------------------------------- +close_transport_accept() -> + [{doc,"Tests closing ssl socket when waiting on ssl:transport_accept/1"}]. + +close_transport_accept(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), + + Port = 0, + Opts = [{active, false} | ServerOpts], + {ok, ListenSocket} = rpc:call(ServerNode, ssl, listen, [Port, Opts]), + spawn_link(fun() -> + ct:sleep(?SLEEP), + rpc:call(ServerNode, ssl, close, [ListenSocket]) + end), + case rpc:call(ServerNode, ssl, transport_accept, [ListenSocket]) of + {error, closed} -> + ok; + Other -> + exit({?LINE, Other}) + end. +%%-------------------------------------------------------------------- +abuse_transport_accept_socket() -> + [{doc,"Only ssl:handshake and ssl:controlling_process is allowed for transport_accept:sockets"}]. +abuse_transport_accept_socket(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server_transport_abuse_socket([{node, ServerNode}, + {port, 0}, + {from, self()}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- + +invalid_keyfile() -> + [{doc,"Test what happens with an invalid key file"}]. +invalid_keyfile(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + BadKeyFile = filename:join([proplists:get_value(priv_dir, Config), + "badkey.pem"]), + BadOpts = [{keyfile, BadKeyFile}| proplists:delete(keyfile, ServerOpts)], + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, BadOpts}]), + + Port = ssl_test_lib:inet_port(Server), + + Client = + ssl_test_lib:start_client_error([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {from, self()}, {options, ClientOpts}]), + + File = proplists:get_value(keyfile,BadOpts), + ssl_test_lib:check_result(Server, {error,{options, {keyfile, File, {error,enoent}}}}, Client, + {error, closed}). + +%%-------------------------------------------------------------------- +honor_server_cipher_order() -> + [{doc,"Test API honor server cipher order."}]. +honor_server_cipher_order(Config) when is_list(Config) -> + ClientCiphers = [#{key_exchange => dhe_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}, + #{key_exchange => dhe_rsa, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}], + ServerCiphers = [#{key_exchange => dhe_rsa, + cipher => aes_256_cbc, + mac =>sha, + prf => default_prf}, + #{key_exchange => dhe_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}], + honor_cipher_order(Config, true, ServerCiphers, ClientCiphers, #{key_exchange => dhe_rsa, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}). + +%%-------------------------------------------------------------------- +honor_client_cipher_order() -> + [{doc,"Test API honor server cipher order."}]. +honor_client_cipher_order(Config) when is_list(Config) -> + ClientCiphers = [#{key_exchange => dhe_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}, + #{key_exchange => dhe_rsa, + cipher => aes_256_cbc, + mac => sha, + prf => default_prf}], + ServerCiphers = [#{key_exchange => dhe_rsa, + cipher => aes_256_cbc, + mac =>sha, + prf => default_prf}, + #{key_exchange => dhe_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}], + honor_cipher_order(Config, false, ServerCiphers, ClientCiphers, #{key_exchange => dhe_rsa, + cipher => aes_128_cbc, + mac => sha, + prf => default_prf}). + +%%-------------------------------------------------------------------- +ipv6() -> + [{require, ipv6_hosts}, + {doc,"Test ipv6."}]. +ipv6(Config) when is_list(Config) -> + {ok, Hostname0} = inet:gethostname(), + + case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts)) of + true -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = + ssl_test_lib:run_where(Config, ipv6), + Server = ssl_test_lib:start_server([{node, ServerNode}, + {port, 0}, {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, + [inet6, {active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, + [inet6, {active, false} | ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client); + false -> + {skip, "Host does not support IPv6"} + end. + +%%-------------------------------------------------------------------- +der_input() -> + [{doc,"Test to input certs and key as der"}]. + +der_input(Config) when is_list(Config) -> + DataDir = proplists:get_value(data_dir, Config), + DHParamFile = filename:join(DataDir, "dHParam.pem"), + + {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + [CADb | _] = element(6, State), + + Size = ets:info(CADb, size), + + SeverVerifyOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ServerCert, ServerKey, ServerCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} | + SeverVerifyOpts]), + ClientVerifyOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + {ClientCert, ClientKey, ClientCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} | + ClientVerifyOpts]), + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, + {dh, DHParams}, + {cert, ServerCert}, {key, ServerKey}, {cacerts, ServerCaCerts}], + ClientOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, + {dh, DHParams}, + {cert, ClientCert}, {key, ClientKey}, {cacerts, ClientCaCerts}], + {ClientNode, ServerNode, Hostname} = 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, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + Size = ets:info(CADb, size). + +%%-------------------------------------------------------------------- +invalid_certfile() -> + [{doc,"Test what happens with an invalid cert file"}]. + +invalid_certfile(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + BadCertFile = filename:join([proplists:get_value(priv_dir, Config), + "badcert.pem"]), + ServerBadOpts = [{certfile, BadCertFile}| proplists:delete(certfile, ServerOpts)], + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ServerBadOpts}]), + + Port = ssl_test_lib:inet_port(Server), + + Client = + ssl_test_lib:start_client_error([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {from, self()}, + {options, ClientOpts}]), + File = proplists:get_value(certfile, ServerBadOpts), + ssl_test_lib:check_result(Server, {error,{options, {certfile, File, {error,enoent}}}}, + Client, {error, closed}). + + +%%-------------------------------------------------------------------- +invalid_cacertfile() -> + [{doc,"Test what happens with an invalid cacert file"}]. + +invalid_cacertfile(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + BadCACertFile = filename:join([proplists:get_value(priv_dir, Config), + "badcacert.pem"]), + ServerBadOpts = [{cacertfile, BadCACertFile}| proplists:delete(cacertfile, ServerOpts)], + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server0 = + ssl_test_lib:start_server_error([{node, ServerNode}, + {port, 0}, {from, self()}, + {options, ServerBadOpts}]), + + Port0 = ssl_test_lib:inet_port(Server0), + + + Client0 = + ssl_test_lib:start_client_error([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {from, self()}, + {options, ClientOpts}]), + + File0 = proplists:get_value(cacertfile, ServerBadOpts), + + ssl_test_lib:check_result(Server0, {error, {options, {cacertfile, File0,{error,enoent}}}}, + Client0, {error, closed}), + + File = File0 ++ "do_not_exit.pem", + ServerBadOpts1 = [{cacertfile, File}|proplists:delete(cacertfile, ServerBadOpts)], + + Server1 = + ssl_test_lib:start_server_error([{node, ServerNode}, + {port, 0}, {from, self()}, + {options, ServerBadOpts1}]), + + Port1 = ssl_test_lib:inet_port(Server1), + + Client1 = + ssl_test_lib:start_client_error([{node, ClientNode}, + {port, Port1}, {host, Hostname}, + {from, self()}, + {options, ClientOpts}]), + + + ssl_test_lib:check_result(Server1, {error, {options, {cacertfile, File,{error,enoent}}}}, + Client1, {error, closed}), + ok. + +%%-------------------------------------------------------------------- +new_options_in_handshake() -> + [{doc,"Test that you can set ssl options in handshake/3 and not only in tcp upgrade"}]. +new_options_in_handshake(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + Version = ssl_test_lib:protocol_version(Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + [_, Cipher | _] = ssl:filter_cipher_suites(ssl:cipher_suites(all, Version), + [{key_exchange, + fun(dhe_rsa) -> + true; + (ecdhe_rsa) -> + true; + (ecdh_rsa) -> + true; + (rsa) -> + true; + (_) -> + false + end + }]), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {ssl_extra_opts, [{versions, [Version]}, + {ciphers,[Cipher]}]}, %% To be set in ssl_accept/3 + {mfa, {?MODULE, connection_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, []}}, + {options, [{ciphers, [Cipher]} | ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ServerMsg = ClientMsg = {ok, {Version, Cipher}}, + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%------------------------------------------------------------------- +max_handshake_size() -> + [{doc,"Test that we can set max_handshake_size to max value."}]. + +max_handshake_size(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = 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, [{max_handshake_size, 8388607} |ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{max_handshake_size, 8388607} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok). + + +%%------------------------------------------------------------------- +options_not_proplist() -> + [{doc,"Test what happens if an option is not a key value tuple"}]. + +options_not_proplist(Config) when is_list(Config) -> + BadOption = {client_preferred_next_protocols, + client, [<<"spdy/3">>,<<"http/1.1">>], <<"http/1.1">>}, + {option_not_a_key_value_tuple, BadOption} = + ssl:connect("twitter.com", 443, [binary, {active, false}, + BadOption]). + +%%------------------------------------------------------------------- +invalid_options() -> + [{doc,"Test what happens when we give invalid options"}]. + +invalid_options(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Check = fun(Client, Server, {versions, [sslv2, sslv3]} = Option) -> + ssl_test_lib:check_result(Server, + {error, {options, {sslv2, Option}}}, + Client, + {error, {options, {sslv2, Option}}}); + (Client, Server, Option) -> + ssl_test_lib:check_result(Server, + {error, {options, Option}}, + Client, + {error, {options, Option}}) + end, + + TestOpts = + [{versions, [sslv2, sslv3]}, + {verify, 4}, + {verify_fun, function}, + {fail_if_no_peer_cert, 0}, + {verify_client_once, 1}, + {depth, four}, + {certfile, 'cert.pem'}, + {keyfile,'key.pem' }, + {password, foo}, + {cacertfile, ""}, + {dhfile,'dh.pem' }, + {ciphers, [{foo, bar, sha, ignore}]}, + {reuse_session, foo}, + {reuse_sessions, 0}, + {renegotiate_at, "10"}, + {mode, depech}, + {packet, 8.0}, + {packet_size, "2"}, + {header, a}, + {active, trice}, + {key, 'key.pem' }], + + [begin + Server = + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, [TestOpt | ServerOpts]}]), + %% Will never reach a point where port is used. + Client = + ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, + {host, Hostname}, {from, self()}, + {options, [TestOpt | ClientOpts]}]), + Check(Client, Server, TestOpt), + ok + end || TestOpt <- TestOpts], + ok. +%%------------------------------------------------------------------- + +default_reject_anonymous()-> + [{doc,"Test that by default anonymous cipher suites are rejected "}]. +default_reject_anonymous(Config) when is_list(Config) -> + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + Version = ssl_test_lib:protocol_version(Config), + TLSVersion = ssl_test_lib:tls_version(Version), + + [CipherSuite | _] = ssl_test_lib:ecdh_dh_anonymous_suites(TLSVersion), + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, + [{ciphers,[CipherSuite]} | + ClientOpts]}]), + + ssl_test_lib:check_server_alert(Server, Client, insufficient_security). + +%%------------------------------------------------------------------- +%% Note that these test only test that the options are valid to set. As application data +%% is a stream you can not test that the send acctually splits it up as when it arrives +%% again at the user layer it may be concatenated. But COVER can show that the split up +%% code has been run. + +rizzo_disabled() -> + [{doc, "Test original beast mitigation disable option for SSL 3.0 and TLS 1.0"}]. + +rizzo_disabled(Config) -> + ClientOpts = [{beast_mitigation, disabled} | ssl_test_lib:ssl_options(client_rsa_opts, Config)], + ServerOpts = [{beast_mitigation, disabled} | ssl_test_lib:ssl_options(server_rsa_opts, Config)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%------------------------------------------------------------------- +rizzo_zero_n() -> + [{doc, "Test zero_n beast mitigation option (same affect as original disable option) for SSL 3.0 and TLS 1.0"}]. + +rizzo_zero_n(Config) -> + ClientOpts = [{beast_mitigation, zero_n} | ssl_test_lib:ssl_options(client_rsa_opts, Config)], + ServerOpts = [{beast_mitigation, zero_n} | ssl_test_lib:ssl_options(server_rsa_opts, Config)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%------------------------------------------------------------------- +rizzo_one_n_minus_one () -> + [{doc, "Test beast_mitigation option one_n_minus_one (same affect as default) for SSL 3.0 and TLS 1.0"}]. + +rizzo_one_n_minus_one (Config) -> + ClientOpts = [{beast_mitigation, one_n_minus_one } | ssl_test_lib:ssl_options(client_rsa_opts, Config)], + ServerOpts = [{beast_mitigation, one_n_minus_one} | ssl_test_lib:ssl_options(server_rsa_opts, Config)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +supported_groups() -> + [{doc,"Test the supported_groups option in TLS 1.3."}]. + +supported_groups(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = 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, [{supported_groups, [x448, x25519]} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{supported_groups,[x448]} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +honor_client_cipher_order_tls13() -> + [{doc,"Test API honor server cipher order in TLS 1.3."}]. +honor_client_cipher_order_tls13(Config) when is_list(Config) -> + ClientCiphers = [#{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}, + #{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}], + ServerCiphers = [#{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}, + #{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}], + honor_cipher_order(Config, false, ServerCiphers, ClientCiphers, #{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}). + +%%-------------------------------------------------------------------- +honor_server_cipher_order_tls13() -> + [{doc,"Test API honor server cipher order in TLS 1.3."}]. +honor_server_cipher_order_tls13(Config) when is_list(Config) -> + ClientCiphers = [#{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}, + #{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}], + ServerCiphers = [#{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}, + #{key_exchange => any, + cipher => aes_256_gcm, + mac => aead, + prf => sha384}], + honor_cipher_order(Config, true, ServerCiphers, ClientCiphers, #{key_exchange => any, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}). + + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- + +peercert_result(Socket) -> + ssl:peercert(Socket). + +connection_information_result(Socket) -> + {ok, Info = [_ | _]} = ssl:connection_information(Socket), + case length(Info) > 3 of + true -> + %% Atleast one ssl_option() is set + ct:log("Info ~p", [Info]), + ok; + false -> + ct:fail(no_ssl_options_returned) + end. +secret_connection_info_result(Socket) -> + {ok, [{protocol, Protocol}]} = ssl:connection_information(Socket, [protocol]), + {ok, ConnInfo} = ssl:connection_information(Socket, [client_random, server_random, master_secret]), + check_connection_info(Protocol, ConnInfo). + + +%% In TLS 1.3 the master_secret field is used to store multiple secrets from the key schedule and it is a tuple. +%% client_random and server_random are not used in the TLS 1.3 key schedule. +check_connection_info('tlsv1.3', [{client_random, ClientRand}, {master_secret, {master_secret, MasterSecret}}]) -> + is_binary(ClientRand) andalso is_binary(MasterSecret); +check_connection_info('tlsv1.3', [{server_random, ServerRand}, {master_secret, {master_secret, MasterSecret}}]) -> + is_binary(ServerRand) andalso is_binary(MasterSecret); +check_connection_info(_, [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}]) -> + is_binary(ClientRand) andalso is_binary(ServerRand) andalso is_binary(MasterSecret); +check_connection_info(_, _) -> + false. + + +prf_create_plan(TlsVersions, PRFs, Results) -> + lists:foldl(fun(Ver, Acc) -> + A = prf_ciphers_and_expected(Ver, PRFs, Results), + [A|Acc] + end, [], TlsVersions). + +prf_ciphers_and_expected(TlsVer, PRFs, Results) -> + case TlsVer of + TlsVer when TlsVer == sslv3 orelse TlsVer == tlsv1 + orelse TlsVer == 'tlsv1.1' orelse TlsVer == 'dtlsv1' -> + Ciphers = ssl:cipher_suites(), + {_, Expected} = lists:keyfind(md5sha, 1, Results), + [[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected}, {prf, md5sha}]]; + TlsVer when TlsVer == 'tlsv1.2' orelse TlsVer == 'dtlsv1.2'-> + lists:foldl( + fun(PRF, Acc) -> + Ciphers = prf_get_ciphers(TlsVer, PRF), + case Ciphers of + [] -> + ct:log("No ciphers for PRF algorithm ~p. Skipping.", [PRF]), + Acc; + Ciphers -> + {_, Expected} = lists:keyfind(PRF, 1, Results), + [[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected}, + {prf, PRF}] | Acc] + end + end, [], PRFs) + end. + +prf_get_ciphers(_, PRF) -> + lists:filter( + fun(C) when tuple_size(C) == 4 andalso + element(4, C) == PRF -> + true; + (_) -> + false + end, + ssl:cipher_suites()). + +prf_run_test(_, TlsVer, [], _, Prf) -> + ct:fail({error, cipher_list_empty, TlsVer, Prf}); +prf_run_test(Config, TlsVer, Ciphers, Expected, Prf) -> + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + BaseOpts = [{active, true}, {versions, [TlsVer]}, {ciphers, Ciphers}, {protocol, tls_or_dtls(TlsVer)}], + ServerOpts = BaseOpts ++ proplists:get_value(server_opts, Config), + ClientOpts = BaseOpts ++ proplists:get_value(client_opts, Config), + Server = ssl_test_lib:start_server( + [{node, ServerNode}, {port, 0}, {from, self()}, + {mfa, {?MODULE, prf_verify_value, [TlsVer, Expected, Prf]}}, + {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, prf_verify_value, [TlsVer, Expected, Prf]}}, + {options, ClientOpts}]), + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +prf_verify_value(Socket, TlsVer, Expected, Algo) -> + Ret = ssl:prf(Socket, <<>>, <<>>, [<<>>], 16), + case TlsVer of + sslv3 -> + case Ret of + {error, undefined} -> ok; + _ -> + {error, {expected, {error, undefined}, + got, Ret, tls_ver, TlsVer, prf_algorithm, Algo}} + end; + _ -> + case Ret of + {ok, Expected} -> ok; + {ok, Val} -> {error, {expected, Expected, got, Val, tls_ver, TlsVer, + prf_algorithm, Algo}} + end + end. + +tls_or_dtls('dtlsv1') -> + dtls; +tls_or_dtls('dtlsv1.2') -> + dtls; +tls_or_dtls(_) -> + tls. + +active_n_common(S, N) -> + ok = ssl:setopts(S, [{active,-N}]), + receive + {ssl_passive, S} -> ok + after + 1000 -> + error({error,ssl_passive_failure}) + end, + [{active,false}] = ok(ssl:getopts(S, [active])), + ok = ssl:setopts(S, [{active,0}]), + receive + {ssl_passive, S} -> ok + after + 1000 -> + error({error,ssl_passive_failure}) + end, + ok = ssl:setopts(S, [{active,32767}]), + {error,{options,_}} = ssl:setopts(S, [{active,1}]), + {error,{options,_}} = ssl:setopts(S, [{active,-32769}]), + ok = ssl:setopts(S, [{active,-32768}]), + receive + {ssl_passive, S} -> ok + after + 1000 -> + error({error,ssl_passive_failure}) + end, + [{active,false}] = ok(ssl:getopts(S, [active])), + ok = ssl:setopts(S, [{active,N}]), + ok = ssl:setopts(S, [{active,true}]), + [{active,true}] = ok(ssl:getopts(S, [active])), + receive + _ -> error({error,active_n}) + after + 0 -> + ok + end, + ok = ssl:setopts(S, [{active,N}]), + ok = ssl:setopts(S, [{active,once}]), + [{active,once}] = ok(ssl:getopts(S, [active])), + receive + _ -> error({error,active_n}) + after + 0 -> + ok + end, + {error,{options,_}} = ssl:setopts(S, [{active,32768}]), + ok = ssl:setopts(S, [{active,false}]), + [{active,false}] = ok(ssl:getopts(S, [active])), + ok. + +ok({ok,V}) -> V. + +repeat(N, Fun) -> + repeat(N, N, Fun). + +repeat(N, T, Fun) when is_integer(N), N > 0 -> + Fun(T-N), + repeat(N-1, T, Fun); +repeat(_, _, _) -> + ok. + +try_recv_active(Socket) -> + ssl:send(Socket, "Hello world"), + {error, einval} = ssl:recv(Socket, 11), + ok. +try_recv_active_once(Socket) -> + {error, einval} = ssl:recv(Socket, 11), + ok. + +controlling_process_result(Socket, Pid, Msg) -> + ok = ssl:controlling_process(Socket, Pid), + %% Make sure other side has evaluated controlling_process + %% before message is sent + ct:sleep(?SLEEP), + ssl:send(Socket, Msg), + no_result_msg. + +controller_dies_result(_Socket, _Pid, _Msg) -> + receive Result -> Result end. +get_close(Pid, Where) -> + receive + {'EXIT', Pid, _Reason} -> + receive + {_, {ssl_closed, Socket}} -> + ct:log("Socket closed ~p~n",[Socket]); + Unexpected -> + ct:log("Unexpected ~p~n",[Unexpected]), + ct:fail({line, ?LINE-1}) + after 5000 -> + ct:fail({timeout, {line, ?LINE, Where}}) + end; + Unexpected -> + ct:log("Unexpected ~p~n",[Unexpected]), + ct:fail({line, ?LINE-1}) + after 5000 -> + ct:fail({timeout, {line, ?LINE, Where}}) + end. + +ssl_active_recv(N) -> + ssl_active_recv(N, []). + +ssl_active_recv(0, Acc) -> + Acc; +ssl_active_recv(N, Acc) -> + receive + {ssl, _, Bytes} -> + ssl_active_recv(N-length(Bytes), Acc ++ Bytes) + end. + +send_recv_result_timeout_client(Socket) -> + {error, timeout} = ssl:recv(Socket, 11, 500), + {error, timeout} = ssl:recv(Socket, 11, 0), + ssl:send(Socket, "Hello world"), + receive + Msg -> + io:format("Msg ~p~n",[Msg]) + after 500 -> + ok + end, + {ok, "Hello world"} = ssl:recv(Socket, 11, 500), + ok. +send_recv_result_timeout_server(Socket) -> + ssl:send(Socket, "Hello"), + {ok, "Hello world"} = ssl:recv(Socket, 11), + ssl:send(Socket, " world"), + ok. + +do_recv_close(Socket) -> + {error, closed} = ssl:recv(Socket, 11), + receive + {_,{error,closed}} -> + error_extra_close_sent_to_user_process + after 500 -> + ok + end. + +tls_close(Socket) -> + ok = ssl_test_lib:send_recv_result(Socket), + case ssl:close(Socket, 5000) of + ok -> + ok; + {error, closed} -> + ok; + Other -> + ct:fail(Other) + end. + +run_error_server_close([Pid | Opts]) -> + {ok, Listen} = ssl:listen(0, Opts), + {ok,{_, Port}} = ssl:sockname(Listen), + Pid ! {self(), Port}, + {ok, Socket} = ssl:transport_accept(Listen), + Pid ! ssl:close(Socket). + +run_error_server([ Pid | Opts]) -> + {ok, Listen} = ssl:listen(0, Opts), + {ok,{_, Port}} = ssl:sockname(Listen), + Pid ! {self(), Port}, + {ok, Socket} = ssl:transport_accept(Listen), + Pid ! ssl:controlling_process(Socket, self()). + +run_client_error([Port, Opts]) -> + ssl:connect("localhost", Port, Opts). + +honor_cipher_order(Config, Honor, ServerCiphers, ClientCiphers, Expected) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, 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, []}}, + {options, [{ciphers, ServerCiphers}, {honor_cipher_order, Honor} + | 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, []}}, + {options, [{ciphers, ClientCiphers}, {honor_cipher_order, Honor} + | ClientOpts]}]), + + Version = ssl_test_lib:protocol_version(Config), + + ServerMsg = ClientMsg = {ok, {Version, Expected}}, + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +connection_info_result(Socket) -> + {ok, Info} = ssl:connection_information(Socket, [protocol, selected_cipher_suite]), + {ok, {proplists:get_value(protocol, Info), proplists:get_value(selected_cipher_suite, Info)}}. + +der_input_opts(Opts) -> + Certfile = proplists:get_value(certfile, Opts), + CaCertsfile = proplists:get_value(cacertfile, Opts), + Keyfile = proplists:get_value(keyfile, Opts), + Dhfile = proplists:get_value(dhfile, Opts), + [{_, Cert, _}] = ssl_test_lib:pem_to_der(Certfile), + [{Asn1Type, Key, _}] = ssl_test_lib:pem_to_der(Keyfile), + [{_, DHParams, _}] = ssl_test_lib:pem_to_der(Dhfile), + CaCerts = + lists:map(fun(Entry) -> + {_, CaCert, _} = Entry, + CaCert + end, ssl_test_lib:pem_to_der(CaCertsfile)), + {Cert, {Asn1Type, Key}, CaCerts, DHParams}. diff --git a/lib/ssl/test/ssl_basic_SUITE_data/dHParam.pem b/lib/ssl/test/ssl_api_SUITE_data/dHParam.pem index feb581da30..feb581da30 100644 --- a/lib/ssl/test/ssl_basic_SUITE_data/dHParam.pem +++ b/lib/ssl/test/ssl_api_SUITE_data/dHParam.pem diff --git a/lib/ssl/test/ssl_app_env_SUITE.erl b/lib/ssl/test/ssl_app_env_SUITE.erl new file mode 100644 index 0000000000..27fbcb8e47 --- /dev/null +++ b/lib/ssl/test/ssl_app_env_SUITE.erl @@ -0,0 +1,171 @@ +%% +%% %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(ssl_app_env_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("ssl/src/ssl_api.hrl"). + +-define(SLEEP, 500). +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + [ + {group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +groups() -> + [ + {'tlsv1.3', [], tests()}, + {'tlsv1.2', [], tests()}, + {'tlsv1.1', [], tests()}, + {'tlsv1', [], tests()}, + {'sslv3', [], tests()}, + {'dtlsv1.2', [], tests()}, + {'dtlsv1', [], tests()} + ]. + +tests() -> + [ + internal_active_1, + protocol_versions, + empty_protocol_versions + ]. + + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + [{client_type, erlang}, + {server_type, erlang} | ssl_test_lib:init_tls_version(GroupName, Config)]; + false -> + {skip, "Missing crypto support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(internal_active_1, Config) -> + ssl:stop(), + application:load(ssl), + application:set_env(ssl, internal_active_n, 1), + ssl:start(), + ct:timetrap({seconds, 5}), + Config; +init_per_testcase(protocol_versions, Config) -> + Version = ssl_test_lib:protocol_version(Config), + case atom_to_list(Version) of + "d" ++ _ -> + ssl:stop(), + application:load(ssl), + application:set_env(ssl, dtls_protocol_version, [Version]), + ssl:start(); + _ -> + ssl:stop(), + application:load(ssl), + application:set_env(ssl, protocol_version, [Version]), + ssl:start() + end, + ct:timetrap({seconds, 5}), + Config; +init_per_testcase(empty_protocol_versions, Config) -> + ssl:stop(), + application:load(ssl), + ssl_test_lib:clean_env(), + application:set_env(ssl, protocol_version, []), + application:set_env(ssl, dtls_protocol_version, []), + ssl:start(), + ct:timetrap({seconds, 5}), + Config; +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, 5}), + Config. + +end_per_testcase(_, _Config) -> + ssl_test_lib:clean_start(). + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +%%-------------------------------------------------------------------- +internal_active_1() -> + [{doc,"Test internal active 1 (behave as internal active once)"}]. + +internal_active_1(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +protocol_versions() -> + [{doc,"Test to set a list of protocol versions in app environment."}]. + +protocol_versions(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +empty_protocol_versions() -> + [{doc,"Test to set an empty list of protocol versions in app environment."}]. + +empty_protocol_versions(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 20d9f28512..355cd31070 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -26,15 +26,8 @@ -compile(export_all). -include_lib("common_test/include/ct.hrl"). --include_lib("public_key/include/public_key.hrl"). +-include_lib("ssl/src/ssl_api.hrl"). --include("ssl_api.hrl"). --include("ssl_cipher.hrl"). --include("ssl_internal.hrl"). --include("ssl_alert.hrl"). --include("ssl_internal.hrl"). --include("tls_record.hrl"). --include("tls_handshake.hrl"). -define(TIMEOUT, 20000). -define(EXPIRE, 10). @@ -49,219 +42,35 @@ all() -> [ {group, basic}, - {group, basic_tls}, - {group, options}, - {group, options_tls}, - {group, session}, - {group, 'dtlsv1.2'}, - {group, 'dtlsv1'}, - {group, 'tlsv1.3'}, - {group, 'tlsv1.2'}, - {group, 'tlsv1.1'}, - {group, 'tlsv1'}, - {group, 'sslv3'} + {group, options} ]. groups() -> [{basic, [], basic_tests()}, - {basic_tls, [], basic_tests_tls()}, - {options, [], options_tests()}, - {options_tls, [], options_tests_tls()}, - {'dtlsv1.2', [], all_versions_groups()}, - {'dtlsv1', [], all_versions_groups()}, - {'tlsv1.3', [], tls13_test_group()}, - {'tlsv1.2', [], all_versions_groups() ++ tls_versions_groups() ++ [conf_signature_algs, no_common_signature_algs]}, - {'tlsv1.1', [], all_versions_groups() ++ tls_versions_groups()}, - {'tlsv1', [], all_versions_groups() ++ tls_versions_groups() ++ rizzo_tests()}, - {'sslv3', [], all_versions_groups() ++ tls_versions_groups() ++ rizzo_tests() ++ [tls_ciphersuite_vs_version]}, - {api,[], api_tests()}, - {api_tls,[], api_tests_tls()}, - {session, [], session_tests()}, - {renegotiate, [], renegotiate_tests()}, - {ciphers, [], cipher_tests()}, - {error_handling_tests, [], error_handling_tests()}, - {error_handling_tests_tls, [], error_handling_tests_tls()} + {options, [], options_tests()} ]. -tls_versions_groups ()-> - [ - {group, api_tls}, - {group, error_handling_tests_tls}]. - -all_versions_groups ()-> - [{group, api}, - {group, renegotiate}, - {group, ciphers}, - {group, error_handling_tests}]. - - basic_tests() -> [app, - appup, - alerts, - alert_details, - alert_details_not_too_big, + appup, version_option, connect_twice, connect_dist, - clear_pem_cache, defaults, fallback, cipher_format, - suite_to_str - ]. - -basic_tests_tls() -> - [tls_send_close - ]. - -options_tests() -> - [der_input, - ssl_options_not_proplist, - raw_ssl_option, - invalid_inet_get_option, - invalid_inet_get_option_not_list, - invalid_inet_get_option_improper_list, - invalid_inet_set_option, - invalid_inet_set_option_not_list, - invalid_inet_set_option_improper_list, - dh_params, - invalid_certfile, - invalid_cacertfile, - invalid_keyfile, - invalid_options, - protocol_versions, - empty_protocol_versions, - ipv6, - reuseaddr, - honor_server_cipher_order, - honor_client_cipher_order, - unordered_protocol_versions_server, - unordered_protocol_versions_client, - max_handshake_size -]. - -options_tests_tls() -> - [tls_misc_ssl_options, - tls_tcp_reuseaddr]. - -api_tests() -> - [secret_connection_info, - connection_information, - peercert, - peercert_with_client_cert, - versions, + tls_versions_option, eccs, - controlling_process, - getstat, - close_with_timeout, - hibernate, - hibernate_right_away, - listen_socket, - ssl_recv_timeout, - server_name_indication_option, - accept_pool, - prf, - socket_options, - active_n, cipher_suites, - handshake_continue, - handshake_continue_timeout, - hello_client_cancel, - hello_server_cancel - ]. - -api_tests_tls() -> - [tls_versions_option, - tls_upgrade, - tls_upgrade_with_timeout, - tls_ssl_accept_timeout, - tls_downgrade, - tls_shutdown, - tls_shutdown_write, - tls_shutdown_both, - tls_shutdown_error, - peername, - sockname, - tls_socket_options, - new_options_in_accept - ]. - -session_tests() -> - [reuse_session, - reuse_session_expired, - server_does_not_want_to_reuse_session, - no_reuses_session_server_restart_new_cert, - no_reuses_session_server_restart_new_cert_file]. - -renegotiate_tests() -> - [client_renegotiate, - server_renegotiate, - client_secure_renegotiate, - client_secure_renegotiate_fallback, - client_renegotiate_reused_session, - server_renegotiate_reused_session, - client_no_wrap_sequence_number, - server_no_wrap_sequence_number, - renegotiate_dos_mitigate_active, - renegotiate_dos_mitigate_passive, - renegotiate_dos_mitigate_absolute]. - -cipher_tests() -> - [old_cipher_suites, - cipher_suites_mix, - default_reject_anonymous]. - -error_handling_tests()-> - [close_transport_accept, - recv_active, - recv_active_once, - recv_active_n, - recv_error_handling, - call_in_error_state, - close_in_error_state, - abuse_transport_accept_socket, - controlling_process_transport_accept_socket + old_cipher_suites, + cipher_suites_mix ]. -error_handling_tests_tls()-> - [controller_dies, - tls_client_closes_socket, - tls_closed_in_active_once, - tls_tcp_error_propagation_in_active_mode, - tls_tcp_connect, - tls_tcp_connect_big, - tls_dont_crash_on_handshake_garbage - ]. - -rizzo_tests() -> - [rizzo, - no_rizzo_rc4, - rizzo_one_n_minus_one, - rizzo_zero_n, - rizzo_disabled]. - -%% For testing TLS 1.3 features and possible regressions -tls13_test_group() -> - [tls13_enable_client_side, - tls13_enable_server_side, - tls_record_1_3_encode_decode, - tls13_finished_verify_data, - tls13_1_RTT_handshake, - tls13_basic_ssl_server_openssl_client, - tls13_custom_groups_ssl_server_openssl_client, - tls13_hello_retry_request_ssl_server_openssl_client, - tls13_client_auth_empty_cert_alert_ssl_server_openssl_client, - tls13_client_auth_empty_cert_ssl_server_openssl_client, - tls13_client_auth_ssl_server_openssl_client, - tls13_hrr_client_auth_empty_cert_alert_ssl_server_openssl_client, - tls13_hrr_client_auth_empty_cert_ssl_server_openssl_client, - tls13_hrr_client_auth_ssl_server_openssl_client, - tls13_unsupported_sign_algo_client_auth_ssl_server_openssl_client, - tls13_unsupported_sign_algo_cert_client_auth_ssl_server_openssl_client, - tls13_connection_information]. +options_tests() -> + [ + unordered_protocol_versions_server, + unordered_protocol_versions_client]. -%%-------------------------------------------------------------------- init_per_suite(Config0) -> catch crypto:stop(), try crypto:start() of @@ -284,213 +93,6 @@ end_per_suite(_Config) -> application:stop(crypto). %%-------------------------------------------------------------------- - -init_per_group(GroupName, Config) when GroupName == basic_tls; - GroupName == options_tls; - GroupName == options; - GroupName == basic; - GroupName == session; - GroupName == error_handling_tests_tls; - GroupName == tls13_test_group - -> - ssl_test_lib:clean_tls_version(Config); -init_per_group(GroupName, Config) -> - ssl_test_lib:clean_tls_version(Config), - case ssl_test_lib:is_tls_version(GroupName) andalso ssl_test_lib:sufficient_crypto_support(GroupName) of - true -> - ssl_test_lib:init_tls_version(GroupName, Config); - _ -> - case ssl_test_lib:sufficient_crypto_support(GroupName) of - true -> - ssl:start(), - Config; - false -> - {skip, "Missing crypto support"} - end - end. - -end_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of - true -> - ssl_test_lib:clean_tls_version(Config); - false -> - Config - end. - -%%-------------------------------------------------------------------- -init_per_testcase(Case, Config) when Case == unordered_protocol_versions_client; - Case == unordered_protocol_versions_server-> - case proplists:get_value(supported, ssl:versions()) of - ['tlsv1.2' | _] -> - ct:timetrap({seconds, 5}), - Config; - _ -> - {skip, "TLS 1.2 need but not supported on this platform"} - end; - -init_per_testcase(protocol_versions, Config) -> - ssl:stop(), - application:load(ssl), - %% For backwards compatibility sslv2 should be filtered out. - application:set_env(ssl, protocol_version, [sslv2, sslv3, tlsv1]), - ssl:start(), - ct:timetrap({seconds, 5}), - Config; - -init_per_testcase(reuse_session_expired, Config) -> - ssl:stop(), - application:load(ssl), - ssl_test_lib:clean_env(), - application:set_env(ssl, session_lifetime, ?EXPIRE), - application:set_env(ssl, session_delay_cleanup_time, 500), - ssl:start(), - ct:timetrap({seconds, 30}), - Config; - -init_per_testcase(empty_protocol_versions, Config) -> - ssl:stop(), - application:load(ssl), - ssl_test_lib:clean_env(), - application:set_env(ssl, protocol_version, []), - ssl:start(), - ct:timetrap({seconds, 5}), - Config; - -init_per_testcase(fallback, Config) -> - case tls_record:highest_protocol_version([]) of - {3, N} when N > 1 -> - ct:timetrap({seconds, 5}), - Config; - _ -> - {skip, "Not relevant if highest supported version is less than 3.2"} - end; - -init_per_testcase(TestCase, Config) when TestCase == client_renegotiate; - TestCase == server_renegotiate; - TestCase == client_secure_renegotiate; - TestCase == client_renegotiate_reused_session; - TestCase == server_renegotiate_reused_session; - TestCase == client_no_wrap_sequence_number; - TestCase == server_no_wrap_sequence_number; - TestCase == renegotiate_dos_mitigate_active; - TestCase == renegotiate_dos_mitigate_passive; - TestCase == renegotiate_dos_mitigate_absolute -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, ?SEC_RENEGOTIATION_TIMEOUT + 5}), - Config; - -init_per_testcase(TestCase, Config) when TestCase == versions_option; - TestCase == tls_tcp_connect_big -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 60}), - Config; - -init_per_testcase(version_option, Config) -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 10}), - Config; - -init_per_testcase(reuse_session, Config) -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 10}), - Config; - -init_per_testcase(rizzo, Config) -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 60}), - Config; - -init_per_testcase(no_rizzo_rc4, Config) -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 60}), - Config; - -init_per_testcase(rizzo_one_n_minus_one, Config) -> - ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - ct:timetrap({seconds, 60}), - rizzo_add_mitigation_option(one_n_minus_one, Config); - -init_per_testcase(rizzo_zero_n, Config) -> - ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - ct:timetrap({seconds, 60}), - rizzo_add_mitigation_option(zero_n, Config); - -init_per_testcase(rizzo_disabled, Config) -> - ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - ct:timetrap({seconds, 60}), - rizzo_add_mitigation_option(disabled, Config); - -init_per_testcase(TestCase, Config) when TestCase == no_reuses_session_server_restart_new_cert_file; - TestCase == no_reuses_session_server_restart_new_cert -> - ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - ct:timetrap({seconds, 15}), - Config; - -init_per_testcase(prf, Config) -> - ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - ct:timetrap({seconds, 40}), - case proplists:get_value(tc_group_path, Config) of - [] -> Prop = []; - [Prop] -> Prop - end, - case proplists:get_value(name, Prop) of - undefined -> TlsVersions = [sslv3, tlsv1, 'tlsv1.1', 'tlsv1.2']; - TlsVersion when is_atom(TlsVersion) -> - TlsVersions = [TlsVersion] - end, - PRFS=[md5, sha, sha256, sha384, sha512], - %All are the result of running tls_v1:prf(PrfAlgo, <<>>, <<>>, <<>>, 16) - %with the specified PRF algorithm - ExpectedPrfResults= - [{md5, <<96,139,180,171,236,210,13,10,28,32,2,23,88,224,235,199>>}, - {sha, <<95,3,183,114,33,169,197,187,231,243,19,242,220,228,70,151>>}, - {sha256, <<166,249,145,171,43,95,158,232,6,60,17,90,183,180,0,155>>}, - {sha384, <<153,182,217,96,186,130,105,85,65,103,123,247,146,91,47,106>>}, - {sha512, <<145,8,98,38,243,96,42,94,163,33,53,49,241,4,127,28>>}, - %TLS 1.0 and 1.1 PRF: - {md5sha, <<63,136,3,217,205,123,200,177,251,211,17,229,132,4,173,80>>}], - TestPlan = prf_create_plan(TlsVersions, PRFS, ExpectedPrfResults), - [{prf_test_plan, TestPlan} | Config]; - -init_per_testcase(TestCase, Config) when TestCase == tls_ssl_accept_timeout; - TestCase == tls_client_closes_socket; - TestCase == tls_closed_in_active_once; - TestCase == tls_downgrade -> - ssl:stop(), - ssl:start(), - ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 15}), - Config; -init_per_testcase(TestCase, Config) when TestCase == clear_pem_cache; - TestCase == der_input; - TestCase == defaults -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - %% White box test need clean start - ssl:stop(), - ssl:start(), - ct:timetrap({seconds, 20}), - Config; -init_per_testcase(raw_ssl_option, Config) -> - ct:timetrap({seconds, 5}), - case os:type() of - {unix,linux} -> - Config; - _ -> - {skip, "Raw options are platform-specific"} - end; - -init_per_testcase(accept_pool, Config) -> - ct:timetrap({seconds, 5}), - case proplists:get_value(protocol, Config) of - dtls -> - {skip, "Not yet supported on DTLS sockets"}; - _ -> - ssl_test_lib:ct_log_supported_protocol_versions(Config), - Config - end; -init_per_testcase(controller_dies, Config) -> - ct:timetrap({seconds, 10}), - Config; init_per_testcase(eccs, Config) -> case ssl:eccs() of [] -> @@ -505,19 +107,8 @@ init_per_testcase(_TestCase, Config) -> ct:timetrap({seconds, 5}), Config. -end_per_testcase(reuse_session_expired, Config) -> - application:unset_env(ssl, session_lifetime), - application:unset_env(ssl, session_delay_cleanup_time), - end_per_testcase(default_action, Config); - -end_per_testcase(Case, Config) when Case == protocol_versions; - Case == empty_protocol_versions-> - application:unset_env(ssl, protocol_versions), - end_per_testcase(default_action, Config); - end_per_testcase(_TestCase, Config) -> Config. - %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- @@ -531,632 +122,76 @@ appup() -> appup(Config) when is_list(Config) -> ok = ?t:appup_test(ssl). %%-------------------------------------------------------------------- -alerts() -> - [{doc, "Test ssl_alert:alert_txt/1"}]. -alerts(Config) when is_list(Config) -> - Descriptions = [?CLOSE_NOTIFY, ?UNEXPECTED_MESSAGE, ?BAD_RECORD_MAC, - ?DECRYPTION_FAILED_RESERVED, ?RECORD_OVERFLOW, ?DECOMPRESSION_FAILURE, - ?HANDSHAKE_FAILURE, ?BAD_CERTIFICATE, ?UNSUPPORTED_CERTIFICATE, - ?CERTIFICATE_REVOKED,?CERTIFICATE_EXPIRED, ?CERTIFICATE_UNKNOWN, - ?ILLEGAL_PARAMETER, ?UNKNOWN_CA, ?ACCESS_DENIED, ?DECODE_ERROR, - ?DECRYPT_ERROR, ?EXPORT_RESTRICTION, ?PROTOCOL_VERSION, - ?INSUFFICIENT_SECURITY, ?INTERNAL_ERROR, ?USER_CANCELED, - ?NO_RENEGOTIATION, ?UNSUPPORTED_EXTENSION, ?CERTIFICATE_UNOBTAINABLE, - ?UNRECOGNISED_NAME, ?BAD_CERTIFICATE_STATUS_RESPONSE, - ?BAD_CERTIFICATE_HASH_VALUE, ?UNKNOWN_PSK_IDENTITY, - 255 %% Unsupported/unknow alert will result in a description too - ], - Alerts = [?ALERT_REC(?WARNING, ?CLOSE_NOTIFY) | - [?ALERT_REC(?FATAL, Desc) || Desc <- Descriptions]], - lists:foreach(fun(Alert) -> - try ssl_alert:alert_txt(Alert) - catch - C:E:T -> - ct:fail({unexpected, {C, E, T}}) - end - end, Alerts). -%%-------------------------------------------------------------------- -alert_details() -> - [{doc, "Test that ssl_alert:alert_txt/1 result contains extendend error description"}]. -alert_details(Config) when is_list(Config) -> - Unique = make_ref(), - UniqueStr = lists:flatten(io_lib:format("~w", [Unique])), - Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY, Unique), - case string:str(ssl_alert:alert_txt(Alert), UniqueStr) of - 0 -> - ct:fail(error_details_missing); - _ -> - ok - end. - -%%-------------------------------------------------------------------- -alert_details_not_too_big() -> - [{doc, "Test that ssl_alert:alert_txt/1 limits printed depth of extended error description"}]. -alert_details_not_too_big(Config) when is_list(Config) -> - Reason = lists:duplicate(10, lists:duplicate(10, lists:duplicate(10, {some, data}))), - Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY, Reason), - case length(ssl_alert:alert_txt(Alert)) < 1000 of - true -> - ok; - false -> - ct:fail(ssl_alert_text_too_big) - end. - -%%-------------------------------------------------------------------- -new_options_in_accept() -> - [{doc,"Test that you can set ssl options in ssl_accept/3 and not only in tcp upgrade"}]. -new_options_in_accept(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_dsa_opts, Config), - [_ , _ | ServerSslOpts] = ssl_test_lib:ssl_options(server_opts, Config), %% Remove non ssl opts - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Version = ssl_test_lib:protocol_options(Config, [{tls, sslv3}, {dtls, dtlsv1}]), - Cipher = ssl_test_lib:protocol_options(Config, [{tls, #{key_exchange =>rsa, - cipher => rc4_128, - mac => sha, - prf => default_prf - }}, - {dtls, #{key_exchange =>rsa, - cipher => aes_128_cbc, - mac => sha, - prf => default_prf - }}]), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {ssl_extra_opts, [{versions, [Version]}, - {ciphers,[Cipher]} | ServerSslOpts]}, %% To be set in ssl_accept/3 - {mfa, {?MODULE, connection_info_result, []}}, - {options, proplists:delete(cacertfile, ServerOpts0)}]), - - 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, []}}, - {options, [{versions, [Version]}, - {ciphers,[Cipher]} | ClientOpts]}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ServerMsg = ClientMsg = {ok, {Version, Cipher}}, +version_option() -> + [{doc, "Use version option and do no specify ciphers list. Bug specified incorrect ciphers"}]. +version_option(Config) when is_list(Config) -> + Versions = proplists:get_value(supported, ssl:versions()), + [version_option_test(Config, Version) || Version <- Versions]. - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -handshake_continue() -> - [{doc, "Test API function ssl:handshake_continue/3"}]. -handshake_continue(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = 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, ssl_test_lib:ssl_options([{reuseaddr, true}, {handshake, hello}], - Config)}, - {continue_options, proplists:delete(reuseaddr, ServerOpts)} - ]), - - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ssl_test_lib:ssl_options([{handshake, hello}], - Config)}, - {continue_options, proplists:delete(reuseaddr, ClientOpts)}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%------------------------------------------------------------------ -handshake_continue_timeout() -> - [{doc, "Test API function ssl:handshake_continue/3 with short timeout"}]. -handshake_continue_timeout(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {timeout, 1}, - {options, ssl_test_lib:ssl_options([{reuseaddr, true}, {handshake, hello}], - Config)}, - {continue_options, proplists:delete(reuseaddr, ServerOpts)} - ]), - - Port = ssl_test_lib:inet_port(Server), - - - {connect_failed, _} = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, {error,timeout}), - ssl_test_lib:close(Server). - - -%%-------------------------------------------------------------------- -hello_client_cancel() -> - [{doc, "Test API function ssl:handshake_cancel/1 on the client side"}]. -hello_client_cancel(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, ssl_test_lib:ssl_options([{handshake, hello}], Config)}, - {continue_options, proplists:delete(reuseaddr, ServerOpts)}]), - - Port = ssl_test_lib:inet_port(Server), - - %% That is ssl:handshake_cancel returns ok - {connect_failed, ok} = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, ssl_test_lib:ssl_options([{handshake, hello}], Config)}, - {continue_options, cancel}]), - ssl_test_lib:check_server_alert(Server, user_canceled). %%-------------------------------------------------------------------- -hello_server_cancel() -> - [{doc, "Test API function ssl:handshake_cancel/1 on the server side"}]. -hello_server_cancel(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, ssl_test_lib:ssl_options([{handshake, hello}], Config)}, - {continue_options, cancel}]), - - Port = ssl_test_lib:inet_port(Server), - - {connect_failed, _} = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, ssl_test_lib:ssl_options([{handshake, hello}], Config)}, - {continue_options, proplists:delete(reuseaddr, ClientOpts)}]), - - ssl_test_lib:check_result(Server, ok). - -%%-------------------------------------------------------------------- -prf() -> - [{doc,"Test that ssl:prf/5 uses the negotiated PRF."}]. -prf(Config) when is_list(Config) -> - TestPlan = proplists:get_value(prf_test_plan, Config), - case TestPlan of - [] -> ct:fail({error, empty_prf_test_plan}); - _ -> lists:foreach(fun(Suite) -> - lists:foreach( - fun(Test) -> - V = proplists:get_value(tls_ver, Test), - C = proplists:get_value(ciphers, Test), - E = proplists:get_value(expected, Test), - P = proplists:get_value(prf, Test), - prf_run_test(Config, V, C, E, P) - end, Suite) - end, TestPlan) - end. - -%%-------------------------------------------------------------------- - -secret_connection_info() -> - [{doc,"Test the API function ssl:connection_information/2"}]. -secret_connection_info(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, secret_connection_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, secret_connection_info_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, true, Client, true), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -%%-------------------------------------------------------------------- - -connection_information() -> - [{doc,"Test the API function ssl:connection_information/1"}]. -connection_information(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, 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_information_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_information_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ServerMsg = ClientMsg = ok, - - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -%%-------------------------------------------------------------------- -protocol_versions() -> - [{doc,"Test to set a list of protocol versions in app environment."}]. - -protocol_versions(Config) when is_list(Config) -> - basic_test(Config). - -%%-------------------------------------------------------------------- -empty_protocol_versions() -> - [{doc,"Test to set an empty list of protocol versions in app environment."}]. - -empty_protocol_versions(Config) when is_list(Config) -> - basic_test(Config). - -%%-------------------------------------------------------------------- - -controlling_process() -> - [{doc,"Test API function controlling_process/2"}]. - -controlling_process(Config) when is_list(Config) -> +connect_twice() -> + [{doc,""}]. +connect_twice(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - ClientMsg = "Server hello", - ServerMsg = "Client hello", - - Server = ssl_test_lib:start_server([ - {node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, - controlling_process_result, [self(), - ServerMsg]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - {Client, CSocket} = ssl_test_lib:start_client([return_socket, - {node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - controlling_process_result, [self(), - ClientMsg]}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ServerMsg = ssl_test_lib:active_recv(CSocket, length(ServerMsg)), - %% We do not have the TLS server socket but all messages form the client - %% socket are now read, so ramining are form the server socket - ClientMsg = ssl_active_recv(length(ClientMsg)), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -getstat() -> - [{doc,"Test API function getstat/2"}]. - -getstat(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server1 = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Port1 = ssl_test_lib:inet_port(Server1), - Server2 = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Port2 = ssl_test_lib:inet_port(Server2), - {ok, ActiveC} = rpc:call(ClientNode, ssl, connect, - [Hostname,Port1,[{active, once}|ClientOpts]]), - {ok, PassiveC} = rpc:call(ClientNode, ssl, connect, - [Hostname,Port2,[{active, false}|ClientOpts]]), - - ct:log("Testcase ~p, Client ~p Servers ~p, ~p ~n", - [self(), self(), Server1, Server2]), - - %% We only check that the values are non-zero initially - %% (due to the handshake), and that sending more changes the values. - - %% Passive socket. - - {ok, InitialStats} = ssl:getstat(PassiveC), - ct:pal("InitialStats ~p~n", [InitialStats]), - [true] = lists:usort([0 =/= proplists:get_value(Name, InitialStats) - || Name <- [recv_cnt, recv_oct, recv_avg, recv_max, send_cnt, send_oct, send_avg, send_max]]), - - ok = ssl:send(PassiveC, "Hello world"), - wait_for_send(PassiveC), - {ok, SStats} = ssl:getstat(PassiveC, [send_cnt, send_oct]), - ct:pal("SStats ~p~n", [SStats]), - [true] = lists:usort([proplists:get_value(Name, SStats) =/= proplists:get_value(Name, InitialStats) - || Name <- [send_cnt, send_oct]]), - - %% Active socket. - - {ok, InitialAStats} = ssl:getstat(ActiveC), - ct:pal("InitialAStats ~p~n", [InitialAStats]), - [true] = lists:usort([0 =/= proplists:get_value(Name, InitialAStats) - || Name <- [recv_cnt, recv_oct, recv_avg, recv_max, send_cnt, send_oct, send_avg, send_max]]), - _ = receive - {ssl, ActiveC, _} -> - ok - after - ?SLEEP -> - exit(timeout) - end, - - ok = ssl:send(ActiveC, "Hello world"), - wait_for_send(ActiveC), - {ok, ASStats} = ssl:getstat(ActiveC, [send_cnt, send_oct]), - ct:pal("ASStats ~p~n", [ASStats]), - [true] = lists:usort([proplists:get_value(Name, ASStats) =/= proplists:get_value(Name, InitialAStats) - || Name <- [send_cnt, send_oct]]), - - ok. - -%%-------------------------------------------------------------------- -controller_dies() -> - [{doc,"Test that the socket is closed after controlling process dies"}]. -controller_dies(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - ClientMsg = "Hello server", - ServerMsg = "Hello client", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, - controller_dies_result, [self(), - ServerMsg]}}, - {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, - controller_dies_result, [self(), - ClientMsg]}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", [self(), Client, Server]), - ct:sleep(?SLEEP), %% so that they are connected - - process_flag(trap_exit, true), - - %% Test that clients die - exit(Client, killed), - get_close(Client, ?LINE), - - %% Test that clients die when process disappear - Server ! listen, - Tester = self(), - Connect = fun(Pid) -> - {ok, Socket} = ssl:connect(Hostname, Port, ClientOpts), - %% Make sure server finishes and verification - %% and is in coonection state before - %% killing client - ct:sleep(?SLEEP), - Pid ! {self(), connected, Socket}, - receive die_nice -> normal end - end, - Client2 = spawn_link(fun() -> Connect(Tester) end), - receive {Client2, connected, _Socket} -> Client2 ! die_nice end, - - get_close(Client2, ?LINE), - - %% Test that clients die when the controlling process have changed - Server ! listen, - Client3 = spawn_link(fun() -> Connect(Tester) end), - Controller = spawn_link(fun() -> receive die_nice -> normal end end), - receive - {Client3, connected, Socket} -> - ok = ssl:controlling_process(Socket, Controller), - Client3 ! die_nice - end, - - ct:log("Wating on exit ~p~n",[Client3]), - receive {'EXIT', Client3, normal} -> ok end, - - receive %% Client3 is dead but that doesn't matter, socket should not be closed. - Unexpected -> - ct:log("Unexpected ~p~n",[Unexpected]), - ct:fail({line, ?LINE-1}) - after 1000 -> - ok - end, - Controller ! die_nice, - get_close(Controller, ?LINE), - - %% Test that servers die - Server ! listen, - LastClient = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - controller_dies_result, [self(), - ClientMsg]}}, - {options, ClientOpts}]), - ct:sleep(?SLEEP), %% so that they are connected - - exit(Server, killed), - get_close(Server, ?LINE), - process_flag(trap_exit, false), - ssl_test_lib:close(LastClient). - -%%-------------------------------------------------------------------- -tls_client_closes_socket() -> - [{doc,"Test what happens when client closes socket before handshake is compleated"}]. - -tls_client_closes_socket(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - TcpOpts = [binary, {reuseaddr, true}], - - Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {tcp_options, TcpOpts}, - {ssl_options, ServerOpts}]), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{keepalive, true},{active, false} + | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{keepalive, true},{active, false} + | ClientOpts]}]), + Server ! listen, - Connect = fun() -> - {ok, _Socket} = rpc:call(ClientNode, gen_tcp, connect, - [Hostname, Port, [binary]]), - %% Make sure that ssl_accept is called before - %% client process ends and closes socket. - ct:sleep(?SLEEP) - end, - - _Client = spawn_link(Connect), - - ssl_test_lib:check_result(Server, {error,closed}). - -%%-------------------------------------------------------------------- -tls_closed_in_active_once() -> - [{doc, "Test that ssl_closed is delivered in active once with non-empty buffer, check ERL-420."}]. - -tls_closed_in_active_once(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config), - TcpOpts = [binary, {reuseaddr, true}], - Port = ssl_test_lib:inet_port(node()), - Server = fun() -> - {ok, Listen} = gen_tcp:listen(Port, TcpOpts), - {ok, TcpServerSocket} = gen_tcp:accept(Listen), - {ok, ServerSocket} = ssl:ssl_accept(TcpServerSocket, ServerOpts), - lists:foreach( - fun(_) -> - ssl:send(ServerSocket, "some random message\r\n") - end, lists:seq(1, 20)), - %% Close TCP instead of SSL socket to trigger the bug: - gen_tcp:close(TcpServerSocket), - gen_tcp:close(Listen) - end, - spawn_link(Server), - {ok, Socket} = ssl:connect(Hostname, Port, [{active, false} | ClientOpts]), - Result = tls_closed_in_active_once_loop(Socket), - ssl:close(Socket), - case Result of - ok -> ok; - _ -> ct:fail(Result) - end. - -tls_closed_in_active_once_loop(Socket) -> - case ssl:setopts(Socket, [{active, once}]) of - ok -> - receive - {ssl, Socket, _} -> - tls_closed_in_active_once_loop(Socket); - {ssl_closed, Socket} -> - ok - after 5000 -> - no_ssl_closed_received - end; - {error, closed} -> - ok - end. -%%-------------------------------------------------------------------- -connect_dist() -> - [{doc,"Test a simple connect as is used by distribution"}]. - -connect_dist(Config) when is_list(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_kc_opts, Config), - ClientOpts = [{ssl_imp, new},{active, false}, {packet,4}|ClientOpts0], - ServerOpts0 = ssl_test_lib:ssl_options(server_kc_opts, Config), - ServerOpts = [{ssl_imp, new},{active, false}, {packet,4}|ServerOpts0], + {Client1, #sslsocket{}} = + ssl_test_lib:start_client([return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{keepalive, true},{active, false} + | ClientOpts]}]), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, connect_dist_s, []}}, - {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, connect_dist_c, []}}, - {options, ClientOpts}]), - ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:check_result(Server, ok, Client1, ok), ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- - -clear_pem_cache() -> - [{doc,"Test that internal reference tabel is cleaned properly even when " - " the PEM cache is cleared" }]. -clear_pem_cache(Config) when is_list(Config) -> - {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), - [_, _,_, _, Prop] = StatusInfo, - State = ssl_test_lib:state(Prop), - [_,{FilRefDb, _} |_] = element(6, State), - {Server, Client} = basic_verify_test_no_close(Config), - CountReferencedFiles = fun({_, -1}, Acc) -> - Acc; - ({_, N}, Acc) -> - N + Acc - end, - - 2 = ets:foldl(CountReferencedFiles, 0, FilRefDb), - ssl:clear_pem_cache(), - _ = sys:get_status(whereis(ssl_manager)), - {Server1, Client1} = basic_verify_test_no_close(Config), - 4 = ets:foldl(CountReferencedFiles, 0, FilRefDb), - ssl_test_lib:close(Server), ssl_test_lib:close(Client), - ct:sleep(2000), - _ = sys:get_status(whereis(ssl_manager)), - 2 = ets:foldl(CountReferencedFiles, 0, FilRefDb), - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client1), - ct:sleep(2000), - _ = sys:get_status(whereis(ssl_manager)), - 0 = ets:foldl(CountReferencedFiles, 0, FilRefDb). + ssl_test_lib:close(Client1). +defaults(Config) when is_list(Config)-> + Versions = ssl:versions(), + true = lists:member(sslv3, proplists:get_value(available, Versions)), + false = lists:member(sslv3, proplists:get_value(supported, Versions)), + true = lists:member('tlsv1', proplists:get_value(available, Versions)), + false = lists:member('tlsv1', proplists:get_value(supported, Versions)), + true = lists:member('tlsv1.1', proplists:get_value(available, Versions)), + false = lists:member('tlsv1.1', proplists:get_value(supported, Versions)), + true = lists:member('tlsv1.2', proplists:get_value(available, Versions)), + true = lists:member('tlsv1.2', proplists:get_value(supported, Versions)), + false = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites()), + true = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites(all)), + false = lists:member({rsa,des_cbc,sha}, ssl:cipher_suites()), + true = lists:member({rsa,des_cbc,sha}, ssl:cipher_suites(all)), + false = lists:member({dhe_rsa,des_cbc,sha}, ssl:cipher_suites()), + true = lists:member({dhe_rsa,des_cbc,sha}, ssl:cipher_suites(all)), + true = lists:member('dtlsv1.2', proplists:get_value(available_dtls, Versions)), + true = lists:member('dtlsv1', proplists:get_value(available_dtls, Versions)), + true = lists:member('dtlsv1.2', proplists:get_value(supported_dtls, Versions)), + false = lists:member('dtlsv1', proplists:get_value(supported_dtls, Versions)). -%%-------------------------------------------------------------------- fallback() -> [{doc, "Test TLS_FALLBACK_SCSV downgrade prevention"}]. @@ -1182,7 +217,6 @@ fallback(Config) when is_list(Config) -> | ClientOpts]}]), ssl_test_lib:check_server_alert(Server, Client, inappropriate_fallback). - %%-------------------------------------------------------------------- cipher_format() -> [{doc, "Test that cipher conversion from maps | tuples | stings to binarys works"}]. @@ -1196,175 +230,6 @@ cipher_format(Config) when is_list(Config) -> ssl:close(Socket2). %%-------------------------------------------------------------------- -suite_to_str() -> - [{doc, "Test that the suite_to_str API works"}]. -suite_to_str(Config) when is_list(Config) -> - "TLS_EMPTY_RENEGOTIATION_INFO_SCSV" = - ssl:suite_to_str(#{key_exchange => null, - cipher => null, - mac => null, - prf => null}), - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" = - ssl:suite_to_str(#{key_exchange => ecdhe_ecdsa, - cipher => aes_128_gcm, - mac => aead, - prf => sha256}), - "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256" = - ssl:suite_to_str(#{key_exchange => ecdh_rsa, - cipher => aes_128_cbc, - mac => sha256, - prf => sha256}). - -%%-------------------------------------------------------------------- - -peername() -> - [{doc,"Test API function peername/1"}]. - -peername(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, peername_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, peername_result, []}}, - {options, [{port, 0} | ClientOpts]}]), - - ClientPort = ssl_test_lib:inet_port(Client), - ServerIp = ssl_test_lib:node_to_hostip(ServerNode), - ClientIp = ssl_test_lib:node_to_hostip(ClientNode), - ServerMsg = {ok, {ClientIp, ClientPort}}, - ClientMsg = {ok, {ServerIp, Port}}, - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -peercert() -> - [{doc,"Test API function peercert/1"}]. -peercert(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, peercert_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, peercert_result, []}}, - {options, ClientOpts}]), - - CertFile = proplists:get_value(certfile, ServerOpts), - [{'Certificate', BinCert, _}]= ssl_test_lib:pem_to_der(CertFile), - - ServerMsg = {error, no_peercert}, - ClientMsg = {ok, BinCert}, - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -peercert_result(Socket) -> - ssl:peercert(Socket). -%%-------------------------------------------------------------------- - -peercert_with_client_cert() -> - [{doc,"Test API function peercert/1"}]. -peercert_with_client_cert(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_dsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, peercert_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, peercert_result, []}}, - {options, ClientOpts}]), - - ServerCertFile = proplists:get_value(certfile, ServerOpts), - [{'Certificate', ServerBinCert, _}]= ssl_test_lib:pem_to_der(ServerCertFile), - ClientCertFile = proplists:get_value(certfile, ClientOpts), - [{'Certificate', ClientBinCert, _}]= ssl_test_lib:pem_to_der(ClientCertFile), - - ServerMsg = {ok, ClientBinCert}, - ClientMsg = {ok, ServerBinCert}, - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -sockname() -> - [{doc,"Test API function sockname/1"}]. -sockname(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, sockname_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, sockname_result, []}}, - {options, [{port, 0} | ClientOpts]}]), - - ClientPort = ssl_test_lib:inet_port(Client), - ServerIp = - case proplists:get_value(protocol, Config) of - dtls -> - %% DTLS sockets are not connected on the server side, - %% so we can only get a ClientIP, ServerIP will always be 0.0.0.0 - {0,0,0,0}; - _ -> - ssl_test_lib:node_to_hostip(ServerNode) - end, - - ClientIp = ssl_test_lib:node_to_hostip(ClientNode), - ServerMsg = {ok, {ServerIp, Port}}, - ClientMsg = {ok, {ClientIp, ClientPort}}, - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -sockname_result(S) -> - ssl:sockname(S). - -%%-------------------------------------------------------------------- cipher_suites() -> [{doc,"Test API function cipher_suites/2, filter_cipher_suites/2" @@ -1461,2716 +326,95 @@ cipher_suites_mix(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, ok, Client, ok), ssl_test_lib:close(Server), ssl_test_lib:close(Client). -%%-------------------------------------------------------------------- -tls_socket_options() -> - [{doc,"Test API function getopts/2 and setopts/2"}]. +unordered_protocol_versions_server() -> + [{doc,"Test that the highest protocol is selected even" + " when it is not first in the versions list."}]. -tls_socket_options(Config) when is_list(Config) -> +unordered_protocol_versions_server(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Values = [{mode, list}, {packet, 0}, {header, 0}, - {active, true}], - %% Shall be the reverse order of Values! - Options = [active, header, packet, mode], - - NewValues = [{mode, binary}, {active, once}], - %% Shall be the reverse order of NewValues! - NewOptions = [active, mode], - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, tls_socket_options_result, - [Options, Values, NewOptions, NewValues]}}, - {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, tls_socket_options_result, - [Options, Values, NewOptions, NewValues]}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - - {ok, Listen} = ssl:listen(0, ServerOpts), - {ok,[{mode,list}]} = ssl:getopts(Listen, [mode]), - ok = ssl:setopts(Listen, [{mode, binary}]), - {ok,[{mode, binary}]} = ssl:getopts(Listen, [mode]), - {ok,[{recbuf, _}]} = ssl:getopts(Listen, [recbuf]), - ssl:close(Listen). - -tls_socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) -> - %% Test get/set emulated opts - {ok, DefaultValues} = ssl:getopts(Socket, Options), - ssl:setopts(Socket, NewValues), - {ok, NewValues} = ssl:getopts(Socket, NewOptions), - %% Test get/set inet opts - {ok,[{nodelay,false}]} = ssl:getopts(Socket, [nodelay]), - ssl:setopts(Socket, [{nodelay, true}]), - {ok,[{nodelay, true}]} = ssl:getopts(Socket, [nodelay]), - {ok, All} = ssl:getopts(Socket, []), - ct:log("All opts ~p~n", [All]), - ok. - - -%%-------------------------------------------------------------------- -socket_options() -> - [{doc,"Test API function getopts/2 and setopts/2"}]. + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), -socket_options(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Values = [{mode, list}, {active, true}], - %% Shall be the reverse order of Values! - Options = [active, mode], - - NewValues = [{mode, binary}, {active, once}], - %% Shall be the reverse order of NewValues! - NewOptions = [active, mode], - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, socket_options_result, - [Options, Values, NewOptions, NewValues]}}, - {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, socket_options_result, - [Options, Values, NewOptions, NewValues]}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - - {ok, Listen} = ssl:listen(0, ServerOpts), - {ok,[{mode,list}]} = ssl:getopts(Listen, [mode]), - ok = ssl:setopts(Listen, [{mode, binary}]), - {ok,[{mode, binary}]} = ssl:getopts(Listen, [mode]), - {ok,[{recbuf, _}]} = ssl:getopts(Listen, [recbuf]), - ssl:close(Listen). - - -socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) -> - %% Test get/set emulated opts - {ok, DefaultValues} = ssl:getopts(Socket, Options), - ssl:setopts(Socket, NewValues), - {ok, NewValues} = ssl:getopts(Socket, NewOptions), - %% Test get/set inet opts - {ok,[{reuseaddr, _}]} = ssl:getopts(Socket, [reuseaddr]), - {ok, All} = ssl:getopts(Socket, []), - ct:log("All opts ~p~n", [All]), - ok. - - -%%-------------------------------------------------------------------- -invalid_inet_get_option() -> - [{doc,"Test handling of invalid inet options in getopts"}]. - -invalid_inet_get_option(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, get_invalid_inet_option, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -invalid_inet_get_option_not_list() -> - [{doc,"Test handling of invalid type in getopts"}]. - -invalid_inet_get_option_not_list(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, get_invalid_inet_option_not_list, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -get_invalid_inet_option_not_list(Socket) -> - {error, {options, {socket_options, some_invalid_atom_here}}} - = ssl:getopts(Socket, some_invalid_atom_here), - ok. - -%%-------------------------------------------------------------------- -invalid_inet_get_option_improper_list() -> - [{doc,"Test handling of invalid type in getopts"}]. - -invalid_inet_get_option_improper_list(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, get_invalid_inet_option_improper_list, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -get_invalid_inet_option_improper_list(Socket) -> - {error, {options, {socket_options, foo,_}}} = ssl:getopts(Socket, [packet | foo]), - ok. - -%%-------------------------------------------------------------------- -invalid_inet_set_option() -> - [{doc,"Test handling of invalid inet options in setopts"}]. - -invalid_inet_set_option(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, set_invalid_inet_option, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -set_invalid_inet_option(Socket) -> - {error, {options, {socket_options, {packet, foo}}}} = ssl:setopts(Socket, [{packet, foo}]), - {error, {options, {socket_options, {header, foo}}}} = ssl:setopts(Socket, [{header, foo}]), - {error, {options, {socket_options, {active, foo}}}} = ssl:setopts(Socket, [{active, foo}]), - {error, {options, {socket_options, {mode, foo}}}} = ssl:setopts(Socket, [{mode, foo}]), - ok. -%%-------------------------------------------------------------------- -invalid_inet_set_option_not_list() -> - [{doc,"Test handling of invalid type in setopts"}]. - -invalid_inet_set_option_not_list(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, set_invalid_inet_option_not_list, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -set_invalid_inet_option_not_list(Socket) -> - {error, {options, {not_a_proplist, some_invalid_atom_here}}} - = ssl:setopts(Socket, some_invalid_atom_here), - ok. - -%%-------------------------------------------------------------------- -invalid_inet_set_option_improper_list() -> - [{doc,"Test handling of invalid tye in setopts"}]. - -invalid_inet_set_option_improper_list(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, set_invalid_inet_option_improper_list, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -set_invalid_inet_option_improper_list(Socket) -> - {error, {options, {not_a_proplist, [{packet, 0} | {foo, 2}]}}} = - ssl:setopts(Socket, [{packet, 0} | {foo, 2}]), - ok. - -%%-------------------------------------------------------------------- -tls_misc_ssl_options() -> - [{doc,"Test what happens when we give valid options"}]. - -tls_misc_ssl_options(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - %% Check that ssl options not tested elsewhere are filtered away e.i. not passed to inet. - TestOpts = [{depth, 1}, - {key, undefined}, - {password, []}, - {reuse_session, fun(_,_,_,_) -> true end}, - {cb_info, {gen_tcp, tcp, tcp_closed, tcp_error}}], - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, TestOpts ++ ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, TestOpts ++ ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -ssl_options_not_proplist() -> - [{doc,"Test what happens if an option is not a key value tuple"}]. - -ssl_options_not_proplist(Config) when is_list(Config) -> - BadOption = {client_preferred_next_protocols, - client, [<<"spdy/3">>,<<"http/1.1">>], <<"http/1.1">>}, - {option_not_a_key_value_tuple, BadOption} = - ssl:connect("twitter.com", 443, [binary, {active, false}, - BadOption]). - -%%-------------------------------------------------------------------- -raw_ssl_option() -> - [{doc,"Ensure that a single 'raw' option is passed to ssl:listen correctly."}]. - -raw_ssl_option(Config) when is_list(Config) -> - % 'raw' option values are platform-specific; these are the Linux values: - IpProtoTcp = 6, - % Use TCP_KEEPIDLE, because (e.g.) TCP_MAXSEG can't be read back reliably. - TcpKeepIdle = 4, - KeepAliveTimeSecs = 55, - LOptions = [{raw, IpProtoTcp, TcpKeepIdle, <<KeepAliveTimeSecs:32/native>>}], - {ok, LSocket} = ssl:listen(0, LOptions), - % Per http://www.erlang.org/doc/man/inet.html#getopts-2, we have to specify - % exactly which raw option we want, and the size of the buffer. - {ok, [{raw, IpProtoTcp, TcpKeepIdle, <<KeepAliveTimeSecs:32/native>>}]} = ssl:getopts(LSocket, [{raw, IpProtoTcp, TcpKeepIdle, 4}]). - - -%%-------------------------------------------------------------------- -versions() -> - [{doc,"Test API function versions/0"}]. - -versions(Config) when is_list(Config) -> - [_|_] = Versions = ssl:versions(), - ct:log("~p~n", [Versions]). - - -%%-------------------------------------------------------------------- -eccs() -> - [{doc, "Test API functions eccs/0 and eccs/1"}]. - -eccs(Config) when is_list(Config) -> - [_|_] = All = ssl:eccs(), - [] = SSL3 = ssl:eccs(sslv3), - [_|_] = Tls = ssl:eccs(tlsv1), - [_|_] = Tls1 = ssl:eccs('tlsv1.1'), - [_|_] = Tls2 = ssl:eccs('tlsv1.2'), - [_|_] = Tls1 = ssl:eccs('dtlsv1'), - [_|_] = Tls2 = ssl:eccs('dtlsv1.2'), - %% ordering is currently unverified by the test - true = lists:sort(All) =:= lists:usort(SSL3 ++ Tls ++ Tls1 ++ Tls2), - ok. - -%%-------------------------------------------------------------------- -send_recv() -> - [{doc,""}]. -send_recv(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = 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, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ClientOpts]}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -tls_send_close() -> - [{doc,""}]. -tls_send_close(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = 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, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - {ok, TcpS} = rpc:call(ClientNode, gen_tcp, connect, - [Hostname,Port,[binary, {active, false}]]), - {ok, SslS} = rpc:call(ClientNode, ssl, connect, - [TcpS,[{active, false}|ClientOpts]]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), self(), Server]), - ok = ssl:send(SslS, "Hello world"), - {ok,<<"Hello world">>} = ssl:recv(SslS, 11), - gen_tcp:close(TcpS), - {error, _} = ssl:send(SslS, "Hello world"). - -%%-------------------------------------------------------------------- -version_option() -> - [{doc, "Use version option and do no specify ciphers list. Bug specified incorrect ciphers"}]. -version_option(Config) when is_list(Config) -> - Versions = proplists:get_value(supported, ssl:versions()), - [version_option_test(Config, Version) || Version <- Versions]. - -%%-------------------------------------------------------------------- -close_transport_accept() -> - [{doc,"Tests closing ssl socket when waiting on ssl:transport_accept/1"}]. - -close_transport_accept(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), - - Port = 0, - Opts = [{active, false} | ServerOpts], - {ok, ListenSocket} = rpc:call(ServerNode, ssl, listen, [Port, Opts]), - spawn_link(fun() -> - ct:sleep(?SLEEP), - rpc:call(ServerNode, ssl, close, [ListenSocket]) - end), - case rpc:call(ServerNode, ssl, transport_accept, [ListenSocket]) of - {error, closed} -> - ok; - Other -> - exit({?LINE, Other}) - end. -%%-------------------------------------------------------------------- -recv_active() -> - [{doc,"Test recv on active socket"}]. - -recv_active(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, try_recv_active, []}}, - {options, [{active, true} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, try_recv_active, []}}, - {options, [{active, true} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -recv_active_once() -> - [{doc,"Test recv on active (once) socket"}]. - -recv_active_once(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, try_recv_active_once, []}}, - {options, [{active, once} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, try_recv_active_once, []}}, - {options, [{active, once} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -recv_active_n() -> - [{doc,"Test recv on active (n) socket"}]. - -recv_active_n(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, try_recv_active_once, []}}, - {options, [{active, 1} | ServerOpts]}]), + {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, try_recv_active_once, []}}, - {options, [{active, 1} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -%% Test case adapted from gen_tcp_misc_SUITE. -active_n() -> - [{doc,"Test {active,N} option"}]. - -active_n(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - Port = ssl_test_lib:inet_port(node()), - N = 3, - LS = ok(ssl:listen(Port, [{active,N}|ServerOpts])), - [{active,N}] = ok(ssl:getopts(LS, [active])), - active_n_common(LS, N), - Self = self(), - spawn_link(fun() -> - S0 = ok(ssl:transport_accept(LS)), - {ok, S} = ssl:handshake(S0), - ok = ssl:setopts(S, [{active,N}]), - [{active,N}] = ok(ssl:getopts(S, [active])), - ssl:controlling_process(S, Self), - Self ! {server, S} - end), - C = ok(ssl:connect("localhost", Port, [{active,N}|ClientOpts])), - [{active,N}] = ok(ssl:getopts(C, [active])), - S = receive - {server, S0} -> S0 - after - 1000 -> - exit({error, connect}) - end, - active_n_common(C, N), - active_n_common(S, N), - ok = ssl:setopts(C, [{active,N}]), - ok = ssl:setopts(S, [{active,N}]), - ReceiveMsg = fun(Socket, Msg) -> - receive - {ssl,Socket,Msg} -> - ok; - {ssl,Socket,Begin} -> - receive - {ssl,Socket,End} -> - Msg = Begin ++ End, - ok - after 1000 -> - exit(timeout) - end - after 1000 -> - exit(timeout) - end - end, - repeat(3, fun(I) -> - Msg = "message "++integer_to_list(I), - ok = ssl:send(C, Msg), - ReceiveMsg(S, Msg), - ok = ssl:send(S, Msg), - ReceiveMsg(C, Msg) - end), - receive - {ssl_passive,S} -> - [{active,false}] = ok(ssl:getopts(S, [active])) - after - 1000 -> - exit({error,ssl_passive}) - end, - receive - {ssl_passive,C} -> - [{active,false}] = ok(ssl:getopts(C, [active])) - after - 1000 -> - exit({error,ssl_passive}) - end, - LS2 = ok(ssl:listen(0, [{active,0}])), - receive - {ssl_passive,LS2} -> - [{active,false}] = ok(ssl:getopts(LS2, [active])) - after - 1000 -> - exit({error,ssl_passive}) - end, - ok = ssl:close(LS2), - ok = ssl:close(C), - ok = ssl:close(S), - ok = ssl:close(LS), - ok. - -active_n_common(S, N) -> - ok = ssl:setopts(S, [{active,-N}]), - receive - {ssl_passive, S} -> ok - after - 1000 -> - error({error,ssl_passive_failure}) - end, - [{active,false}] = ok(ssl:getopts(S, [active])), - ok = ssl:setopts(S, [{active,0}]), - receive - {ssl_passive, S} -> ok - after - 1000 -> - error({error,ssl_passive_failure}) - end, - ok = ssl:setopts(S, [{active,32767}]), - {error,{options,_}} = ssl:setopts(S, [{active,1}]), - {error,{options,_}} = ssl:setopts(S, [{active,-32769}]), - ok = ssl:setopts(S, [{active,-32768}]), - receive - {ssl_passive, S} -> ok - after - 1000 -> - error({error,ssl_passive_failure}) - end, - [{active,false}] = ok(ssl:getopts(S, [active])), - ok = ssl:setopts(S, [{active,N}]), - ok = ssl:setopts(S, [{active,true}]), - [{active,true}] = ok(ssl:getopts(S, [active])), - receive - _ -> error({error,active_n}) - after - 0 -> - ok - end, - ok = ssl:setopts(S, [{active,N}]), - ok = ssl:setopts(S, [{active,once}]), - [{active,once}] = ok(ssl:getopts(S, [active])), - receive - _ -> error({error,active_n}) - after - 0 -> - ok - end, - {error,{options,_}} = ssl:setopts(S, [{active,32768}]), - ok = ssl:setopts(S, [{active,false}]), - [{active,false}] = ok(ssl:getopts(S, [active])), - ok. - -ok({ok,V}) -> V. - -repeat(N, Fun) -> - repeat(N, N, Fun). - -repeat(N, T, Fun) when is_integer(N), N > 0 -> - Fun(T-N), - repeat(N-1, T, Fun); -repeat(_, _, _) -> - ok. - -%%-------------------------------------------------------------------- -dh_params() -> - [{doc,"Test to specify DH-params file in server."}]. - -dh_params(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - DataDir = proplists:get_value(data_dir, Config), - DHParamFile = filename:join(DataDir, "dHParam.pem"), - - {ClientNode, ServerNode, Hostname} = 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, [{dhfile, DHParamFile} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, - [{ciphers,[{dhe_rsa,aes_256_cbc,sha}]} | - ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -tls_upgrade() -> - [{doc,"Test that you can upgrade an tcp connection to an ssl connection"}]. - -tls_upgrade(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - TcpOpts = [binary, {reuseaddr, true}], - - Server = ssl_test_lib:start_upgrade_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, - upgrade_result, []}}, - {tcp_options, - [{active, false} | TcpOpts]}, - {ssl_options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_upgrade_client([{node, ClientNode}, - {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, upgrade_result, []}}, - {tcp_options, [binary]}, - {ssl_options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -upgrade_result(Socket) -> - ssl:setopts(Socket, [{active, true}]), - ok = ssl:send(Socket, "Hello world"), - %% Make sure binary is inherited from tcp socket and that we do - %% not get the list default! - receive - {ssl, _, <<"H">>} -> - receive - {ssl, _, <<"ello world">>} -> - ok - end; - {ssl, _, <<"Hello world">>} -> - ok - end. - -%%-------------------------------------------------------------------- -tls_upgrade_with_timeout() -> - [{doc,"Test ssl_accept/3"}]. - -tls_upgrade_with_timeout(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - TcpOpts = [binary, {reuseaddr, true}], - - Server = ssl_test_lib:start_upgrade_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {timeout, 5000}, - {mfa, {?MODULE, - upgrade_result, []}}, - {tcp_options, - [{active, false} | TcpOpts]}, - {ssl_options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_upgrade_client([{node, ClientNode}, - {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, upgrade_result, []}}, - {tcp_options, TcpOpts}, - {ssl_options, ClientOpts}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -tls_downgrade() -> - [{doc,"Test that you can downgarde an ssl connection to an tcp connection"}]. -tls_downgrade(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, tls_downgrade_result, [self()]}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, tls_downgrade_result, [self()]}}, - {options, [{active, false} |ClientOpts]}]), - - - ssl_test_lib:check_result(Server, ready, Client, ready), - - Server ! go, - Client ! go, - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -close_with_timeout() -> - [{doc,"Test normal (not downgrade) ssl:close/2"}]. -close_with_timeout(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, tls_close, []}}, - {options,[{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, tls_close, []}}, - {options, [{active, false} |ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok). - - -%%-------------------------------------------------------------------- -tls_tcp_connect() -> - [{doc,"Test what happens when a tcp tries to connect, i,e. a bad (ssl) packet is sent first"}]. - -tls_tcp_connect(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - TcpOpts = [binary, {reuseaddr, true}, {active, false}], - - Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {timeout, 5000}, - {mfa, {?MODULE, dummy, []}}, - {tcp_options, TcpOpts}, - {ssl_options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {packet, 0}]), - ct:log("Testcase ~p connected to Server ~p ~n", [self(), Server]), - gen_tcp:send(Socket, "<SOME GARBLED NON SSL MESSAGE>"), - - receive - {tcp_closed, Socket} -> - receive - {Server, {error, Error}} -> - ct:log("Error ~p", [Error]) - end - end. -%%-------------------------------------------------------------------- -tls_tcp_connect_big() -> - [{doc,"Test what happens when a tcp tries to connect, i,e. a bad big (ssl) packet is sent first"}]. - -tls_tcp_connect_big(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - TcpOpts = [binary, {reuseaddr, true}], - - Rand = crypto:strong_rand_bytes(?MAX_CIPHER_TEXT_LENGTH+1), - Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {timeout, 5000}, - {mfa, {?MODULE, dummy, []}}, - {tcp_options, TcpOpts}, - {ssl_options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {packet, 0}]), - ct:log("Testcase ~p connected to Server ~p ~n", [self(), Server]), - - gen_tcp:send(Socket, <<?BYTE(0), - ?BYTE(3), ?BYTE(1), ?UINT16(?MAX_CIPHER_TEXT_LENGTH), Rand/binary>>), - - receive - {tcp_closed, Socket} -> - receive - {Server, {error, timeout}} -> - ct:fail("hangs"); - {Server, {error, Error}} -> - ct:log("Error ~p", [Error]); - {'EXIT', Server, _} -> - ok - end - end. - -%%-------------------------------------------------------------------- -ipv6() -> - [{require, ipv6_hosts}, - {doc,"Test ipv6."}]. -ipv6(Config) when is_list(Config) -> - {ok, Hostname0} = inet:gethostname(), - - case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts)) of - true -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = - ssl_test_lib:run_where(Config, ipv6), - Server = ssl_test_lib:start_server([{node, ServerNode}, - {port, 0}, {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, - [inet6, {active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, - [inet6, {active, false} | ClientOpts]}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client); - false -> - {skip, "Host does not support IPv6"} - end. - -%%-------------------------------------------------------------------- - -invalid_keyfile() -> - [{doc,"Test what happens with an invalid key file"}]. -invalid_keyfile(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - BadOpts = ssl_test_lib:ssl_options(server_bad_key, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, BadOpts}]), - - Port = ssl_test_lib:inet_port(Server), - - Client = - ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {from, self()}, {options, ClientOpts}]), + {from, self()}, + {mfa, {?MODULE, protocol_info_result, []}}, + {options, ClientOpts}]), - File = proplists:get_value(keyfile,BadOpts), - ssl_test_lib:check_result(Server, {error,{options, {keyfile, File, {error,enoent}}}}, Client, - {error, closed}). + ServerMsg = ClientMsg = {ok,'tlsv1.2'}, + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). %%-------------------------------------------------------------------- +unordered_protocol_versions_client() -> + [{doc,"Test that the highest protocol is selected even" + " when it is not first in the versions list."}]. -invalid_certfile() -> - [{doc,"Test what happens with an invalid cert file"}]. - -invalid_certfile(Config) when is_list(Config) -> +unordered_protocol_versions_client(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerBadOpts = ssl_test_lib:ssl_options(server_bad_cert, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, ServerBadOpts}]), - - Port = ssl_test_lib:inet_port(Server), - - Client = - ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {from, self()}, - {options, ClientOpts}]), - File = proplists:get_value(certfile, ServerBadOpts), - ssl_test_lib:check_result(Server, {error,{options, {certfile, File, {error,enoent}}}}, - Client, {error, closed}). - - -%%-------------------------------------------------------------------- -invalid_cacertfile() -> - [{doc,"Test what happens with an invalid cacert file"}]. - -invalid_cacertfile(Config) when is_list(Config) -> - ClientOpts = [{reuseaddr, true}|ssl_test_lib:ssl_options(client_opts, Config)], - ServerBadOpts = [{reuseaddr, true}|ssl_test_lib:ssl_options(server_bad_ca, Config)], - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server0 = - ssl_test_lib:start_server_error([{node, ServerNode}, - {port, 0}, {from, self()}, - {options, ServerBadOpts}]), - - Port0 = ssl_test_lib:inet_port(Server0), - - - Client0 = - ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port0}, {host, Hostname}, - {from, self()}, - {options, ClientOpts}]), - - File0 = proplists:get_value(cacertfile, ServerBadOpts), - - ssl_test_lib:check_result(Server0, {error, {options, {cacertfile, File0,{error,enoent}}}}, - Client0, {error, closed}), - - File = File0 ++ "do_not_exit.pem", - ServerBadOpts1 = [{cacertfile, File}|proplists:delete(cacertfile, ServerBadOpts)], - - Server1 = - ssl_test_lib:start_server_error([{node, ServerNode}, - {port, 0}, {from, self()}, - {options, ServerBadOpts1}]), - - Port1 = ssl_test_lib:inet_port(Server1), - - Client1 = - ssl_test_lib:start_client_error([{node, ClientNode}, - {port, Port1}, {host, Hostname}, - {from, self()}, - {options, ClientOpts}]), - - - ssl_test_lib:check_result(Server1, {error, {options, {cacertfile, File,{error,enoent}}}}, - Client1, {error, closed}), - ok. - - - -%%-------------------------------------------------------------------- -invalid_options() -> - [{doc,"Test what happens when we give invalid options"}]. - -invalid_options(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Check = fun(Client, Server, {versions, [sslv2, sslv3]} = Option) -> - ssl_test_lib:check_result(Server, - {error, {options, {sslv2, Option}}}, - Client, - {error, {options, {sslv2, Option}}}); - (Client, Server, Option) -> - ssl_test_lib:check_result(Server, - {error, {options, Option}}, - Client, - {error, {options, Option}}) - end, - - TestOpts = - [{versions, [sslv2, sslv3]}, - {verify, 4}, - {verify_fun, function}, - {fail_if_no_peer_cert, 0}, - {verify_client_once, 1}, - {depth, four}, - {certfile, 'cert.pem'}, - {keyfile,'key.pem' }, - {password, foo}, - {cacertfile, ""}, - {dhfile,'dh.pem' }, - {ciphers, [{foo, bar, sha, ignore}]}, - {reuse_session, foo}, - {reuse_sessions, 0}, - {renegotiate_at, "10"}, - {mode, depech}, - {packet, 8.0}, - {packet_size, "2"}, - {header, a}, - {active, trice}, - {key, 'key.pem' }], - - [begin - Server = - ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, [TestOpt | ServerOpts]}]), - %% Will never reach a point where port is used. - Client = - ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, - {host, Hostname}, {from, self()}, - {options, [TestOpt | ClientOpts]}]), - Check(Client, Server, TestOpt), - ok - end || TestOpt <- TestOpts], - ok. + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), -%%-------------------------------------------------------------------- -tls_shutdown() -> - [{doc,"Test API function ssl:shutdown/2"}]. -tls_shutdown(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, tls_shutdown_result, [server]}}, - {options, [{exit_on_close, false}, - {active, false} | ServerOpts]}]), + {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, tls_shutdown_result, [client]}}, - {options, - [{exit_on_close, false}, - {active, false} | ClientOpts]}]), - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -tls_shutdown_write() -> - [{doc,"Test API function ssl:shutdown/2 with option write."}]. -tls_shutdown_write(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, tls_shutdown_write_result, [server]}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, tls_shutdown_write_result, [client]}}, - {options, [{active, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, {error, closed}). - -%%-------------------------------------------------------------------- -tls_shutdown_both() -> - [{doc,"Test API function ssl:shutdown/2 with option both."}]. -tls_shutdown_both(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, tls_shutdown_both_result, [server]}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, tls_shutdown_both_result, [client]}}, - {options, [{active, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, {error, closed}). - -%%-------------------------------------------------------------------- -tls_shutdown_error() -> - [{doc,"Test ssl:shutdown/2 error handling"}]. -tls_shutdown_error(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - Port = ssl_test_lib:inet_port(node()), - {ok, Listen} = ssl:listen(Port, ServerOpts), - {error, enotconn} = ssl:shutdown(Listen, read_write), - ok = ssl:close(Listen), - {error, closed} = ssl:shutdown(Listen, read_write). - -%%-------------------------------------------------------------------- -default_reject_anonymous()-> - [{doc,"Test that by default anonymous cipher suites are rejected "}]. -default_reject_anonymous(Config) when is_list(Config) -> - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - Version = ssl_test_lib:protocol_version(Config), - TLSVersion = ssl_test_lib:tls_version(Version), - - [CipherSuite | _] = ssl_test_lib:ecdh_dh_anonymous_suites(TLSVersion), - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, - [{ciphers,[CipherSuite]} | - ClientOpts]}]), - - ssl_test_lib:check_server_alert(Server, Client, insufficient_security). - -%%-------------------------------------------------------------------- -reuse_session() -> - [{doc,"Test reuse of sessions (short handshake)"}]. -reuse_session(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - - ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config). -%%-------------------------------------------------------------------- -reuse_session_expired() -> - [{doc,"Test sessions is not reused when it has expired"}]. -reuse_session_expired(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server0 = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {tcp_options, [{active, false}]}, - {options, ServerOpts}]), - Port0 = ssl_test_lib:inet_port(Server0), - - Client0 = ssl_test_lib:start_client([{node, ClientNode}, - {port, Port0}, {host, Hostname}, - {mfa, {ssl_test_lib, session_id, []}}, - {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]), - Server0 ! listen, - - Client1 = ssl_test_lib:start_client([{node, ClientNode}, - {port, Port0}, {host, Hostname}, - {mfa, {ssl_test_lib, session_id, []}}, - {from, self()}, {options, ClientOpts}]), - - SID = receive - {Client0, Id0} -> - Id0 - end, - - receive - {Client1, SID} -> - ok - after ?SLEEP -> - ct:fail(session_not_reused) - end, - - Server0 ! listen, - - %% Make sure session is unregistered due to expiration - ct:sleep((?EXPIRE*2)), - - make_sure_expired(Hostname, Port0, SID), - - Client2 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port0}, {host, Hostname}, - {mfa, {ssl_test_lib, session_id, []}}, - {from, self()}, {options, ClientOpts}]), - receive - {Client2, SID} -> - ct:fail(session_reused_when_session_expired); - {Client2, _} -> - ok - end, - process_flag(trap_exit, false), - ssl_test_lib:close(Server0), - ssl_test_lib:close(Client0), - ssl_test_lib:close(Client1), - ssl_test_lib:close(Client2). - -make_sure_expired(Host, Port, Id) -> - {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), - [_, _,_, _, Prop] = StatusInfo, - State = ssl_test_lib:state(Prop), - ClientCache = element(2, State), - - case ssl_session_cache:lookup(ClientCache, {{Host, Port}, Id}) of - undefined -> - ok; - #session{is_resumable = false} -> - ok; - _ -> - ct:sleep(?SLEEP), - make_sure_expired(Host, Port, Id) - end. - -%%-------------------------------------------------------------------- -server_does_not_want_to_reuse_session() -> - [{doc,"Test reuse of sessions (short handshake)"}]. -server_does_not_want_to_reuse_session(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {options, [{reuse_session, fun(_,_,_,_) -> - false - end} | - ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client0 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - SessionInfo = - receive - {Server, Info} -> - Info - end, - - Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, - - %% Make sure session is registered - ct:sleep(?SLEEP), - ssl_test_lib:close(Client0), - - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), - receive - {Client1, SessionInfo} -> - ct:fail(session_reused_when_server_does_not_want_to); - {Client1, _Other} -> - ok - end, - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- -client_renegotiate() -> - [{doc,"Test ssl:renegotiate/1 on client."}]. -client_renegotiate(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {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, - renegotiate, [Data]}}, - {options, [{reuse_sessions, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -client_secure_renegotiate() -> - [{doc,"Test ssl:renegotiate/1 on client."}]. -client_secure_renegotiate(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, [{secure_renegotiate, true} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - renegotiate, [Data]}}, - {options, [{reuse_sessions, false}, - {secure_renegotiate, true}| ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -client_secure_renegotiate_fallback() -> - [{doc,"Test that we can set secure_renegotiate to false that is " - "fallback option, we however do not have a insecure server to test against!"}]. -client_secure_renegotiate_fallback(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, [{secure_renegotiate, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), + {mfa, {?MODULE, protocol_info_result, []}}, + {options, [{versions, ['tlsv1.1', 'tlsv1.2']} | ClientOpts]}]), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - renegotiate, [Data]}}, - {options, [{reuse_sessions, false}, - {secure_renegotiate, false}| ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). + ServerMsg = ClientMsg = {ok, 'tlsv1.2'}, + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). + +connect_dist() -> + [{doc,"Test a simple connect as is used by distribution"}]. -%%-------------------------------------------------------------------- -server_renegotiate() -> - [{doc,"Test ssl:renegotiate/1 on server."}]. -server_renegotiate(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), +connect_dist(Config) when is_list(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_kc_opts, Config), + ClientOpts = [{ssl_imp, new},{active, false}, {packet,4}|ClientOpts0], + ServerOpts0 = ssl_test_lib:ssl_options(server_kc_opts, Config), + ServerOpts = [{ssl_imp, new},{active, false}, {packet,4}|ServerOpts0], {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Data = "From erlang to erlang", - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, - renegotiate, [Data]}}, + {mfa, {?MODULE, connect_dist_s, []}}, {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, erlang_ssl_receive, [Data]}}, - {options, [{reuse_sessions, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -client_renegotiate_reused_session() -> - [{doc,"Test ssl:renegotiate/1 on client when the ssl session will be reused."}]. -client_renegotiate_reused_session(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {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, - renegotiate_reuse_session, [Data]}}, - {options, [{reuse_sessions, true} | ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). -%%-------------------------------------------------------------------- -server_renegotiate_reused_session() -> - [{doc,"Test ssl:renegotiate/1 on server when the ssl session will be reused."}]. -server_renegotiate_reused_session(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, - renegotiate_reuse_session, [Data]}}, - {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, erlang_ssl_receive, [Data]}}, - {options, [{reuse_sessions, true} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). -%%-------------------------------------------------------------------- -client_no_wrap_sequence_number() -> - [{doc,"Test that erlang client will renegotiate session when", - "max sequence number celing is about to be reached. Although" - "in the testcase we use the test option renegotiate_at" - " to lower treashold substantially."}]. - -client_no_wrap_sequence_number(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - ErlData = "From erlang to erlang", - N = 12, - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Version = ssl_test_lib:protocol_version(Config, tuple), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - trigger_renegotiate, [[ErlData, treashold(N, Version)]]}}, - {options, [{reuse_sessions, false}, - {renegotiate_at, N} | ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -server_no_wrap_sequence_number() -> - [{doc, "Test that erlang server will renegotiate session when", - "max sequence number celing is about to be reached. Although" - "in the testcase we use the test option renegotiate_at" - " to lower treashold substantially."}]. - -server_no_wrap_sequence_number(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - N = 12, - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - trigger_renegotiate, [[Data, N+2]]}}, - {options, [{renegotiate_at, N} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{reuse_sessions, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -der_input() -> - [{doc,"Test to input certs and key as der"}]. - -der_input(Config) when is_list(Config) -> - DataDir = proplists:get_value(data_dir, Config), - DHParamFile = filename:join(DataDir, "dHParam.pem"), - - {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), - [_, _,_, _, Prop] = StatusInfo, - State = ssl_test_lib:state(Prop), - [CADb | _] = element(6, State), - - Size = ets:info(CADb, size), - - SeverVerifyOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ServerCert, ServerKey, ServerCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} | - SeverVerifyOpts]), - ClientVerifyOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - {ClientCert, ClientKey, ClientCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} | - ClientVerifyOpts]), - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, - {dh, DHParams}, - {cert, ServerCert}, {key, ServerKey}, {cacerts, ServerCaCerts}], - ClientOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, - {dh, DHParams}, - {cert, ClientCert}, {key, ClientKey}, {cacerts, ClientCaCerts}], - {ClientNode, ServerNode, Hostname} = 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, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - Size = ets:info(CADb, size). - -%%-------------------------------------------------------------------- -der_input_opts(Opts) -> - Certfile = proplists:get_value(certfile, Opts), - CaCertsfile = proplists:get_value(cacertfile, Opts), - Keyfile = proplists:get_value(keyfile, Opts), - Dhfile = proplists:get_value(dhfile, Opts), - [{_, Cert, _}] = ssl_test_lib:pem_to_der(Certfile), - [{Asn1Type, Key, _}] = ssl_test_lib:pem_to_der(Keyfile), - [{_, DHParams, _}] = ssl_test_lib:pem_to_der(Dhfile), - CaCerts = - lists:map(fun(Entry) -> - {_, CaCert, _} = Entry, - CaCert - end, ssl_test_lib:pem_to_der(CaCertsfile)), - {Cert, {Asn1Type, Key}, CaCerts, DHParams}. - -%%-------------------------------------------------------------------- -no_reuses_session_server_restart_new_cert() -> - [{doc,"Check that a session is not reused if the server is restarted with a new cert."}]. -no_reuses_session_server_restart_new_cert(Config) when is_list(Config) -> - - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - DsaServerOpts = ssl_test_lib:ssl_options(server_dsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client0 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - SessionInfo = - receive - {Server, Info} -> - Info - end, - - %% Make sure session is registered - ct:sleep(?SLEEP), - Monitor = erlang:monitor(process, Server), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client0), - receive - {'DOWN', Monitor, _, _, _} -> - ok - end, - - Server1 = - ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{reuseaddr, true} | DsaServerOpts]}]), - - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), - receive - {Client1, SessionInfo} -> - ct:fail(session_reused_when_server_has_new_cert); - {Client1, _Other} -> - ok - end, - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- -no_reuses_session_server_restart_new_cert_file() -> - [{doc,"Check that a session is not reused if a server is restarted with a new " - "cert contained in a file with the same name as the old cert."}]. - -no_reuses_session_server_restart_new_cert_file(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), - DsaServerOpts = ssl_test_lib:ssl_options(server_dsa_opts, Config), - PrivDir = proplists:get_value(priv_dir, Config), - - NewServerOpts0 = new_config(PrivDir, ServerOpts), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {options, NewServerOpts0}]), - Port = ssl_test_lib:inet_port(Server), - Client0 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - SessionInfo = - receive - {Server, Info} -> - Info - end, - - %% Make sure session is registered and we get - %% new file time stamp when calling new_config! - ct:sleep(?SLEEP* 2), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client0), - - ssl:clear_pem_cache(), - - NewServerOpts1 = new_config(PrivDir, DsaServerOpts), - - Server1 = - ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{reuseaddr, true} | NewServerOpts1]}]), - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), - receive - {Client1, SessionInfo} -> - ct:fail(session_reused_when_server_has_new_cert); - {Client1, _Other} -> - ok - end, - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- -defaults(Config) when is_list(Config)-> - Versions = ssl:versions(), - true = lists:member(sslv3, proplists:get_value(available, Versions)), - false = lists:member(sslv3, proplists:get_value(supported, Versions)), - true = lists:member('tlsv1', proplists:get_value(available, Versions)), - false = lists:member('tlsv1', proplists:get_value(supported, Versions)), - true = lists:member('tlsv1.1', proplists:get_value(available, Versions)), - false = lists:member('tlsv1.1', proplists:get_value(supported, Versions)), - true = lists:member('tlsv1.2', proplists:get_value(available, Versions)), - true = lists:member('tlsv1.2', proplists:get_value(supported, Versions)), - false = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites()), - true = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites(all)), - false = lists:member({rsa,des_cbc,sha}, ssl:cipher_suites()), - true = lists:member({rsa,des_cbc,sha}, ssl:cipher_suites(all)), - false = lists:member({dhe_rsa,des_cbc,sha}, ssl:cipher_suites()), - true = lists:member({dhe_rsa,des_cbc,sha}, ssl:cipher_suites(all)), - true = lists:member('dtlsv1.2', proplists:get_value(available_dtls, Versions)), - true = lists:member('dtlsv1', proplists:get_value(available_dtls, Versions)), - true = lists:member('dtlsv1.2', proplists:get_value(supported_dtls, Versions)), - false = lists:member('dtlsv1', proplists:get_value(supported_dtls, Versions)). - -%%-------------------------------------------------------------------- -reuseaddr() -> - [{doc,"Test reuseaddr option"}]. - -reuseaddr(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, false} | ClientOpts]}]), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - - Server1 = - ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ServerOpts]}]), - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Server1, ok, Client1, ok), - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- -tls_tcp_reuseaddr() -> - [{doc, "Reference test case."}]. -tls_tcp_reuseaddr(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()}, - {transport, gen_tcp}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, false}, {reuseaddr, true}]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {transport, gen_tcp}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, false}]}]), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - - Server1 = - ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, - {from, self()}, - {transport, gen_tcp}, - {mfa, {?MODULE, tcp_send_recv_result, []}}, - {options, [{active, false}, {reuseaddr, true}]}]), - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {transport, gen_tcp}, - {mfa, {?MODULE, tcp_send_recv_result, []}}, - {options, [{active, false}]}]), - - ssl_test_lib:check_result(Server1, ok, Client1, ok), - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- - -honor_server_cipher_order() -> - [{doc,"Test API honor server cipher order."}]. -honor_server_cipher_order(Config) when is_list(Config) -> - ClientCiphers = [#{key_exchange => dhe_rsa, - cipher => aes_128_cbc, - mac => sha, - prf => default_prf}, - #{key_exchange => dhe_rsa, - cipher => aes_256_cbc, - mac => sha, - prf => default_prf}], - ServerCiphers = [#{key_exchange => dhe_rsa, - cipher => aes_256_cbc, - mac =>sha, - prf => default_prf}, - #{key_exchange => dhe_rsa, - cipher => aes_128_cbc, - mac => sha, - prf => default_prf}], - honor_cipher_order(Config, true, ServerCiphers, ClientCiphers, #{key_exchange => dhe_rsa, - cipher => aes_256_cbc, - mac => sha, - prf => default_prf}). - -honor_client_cipher_order() -> - [{doc,"Test API honor server cipher order."}]. -honor_client_cipher_order(Config) when is_list(Config) -> - ClientCiphers = [#{key_exchange => dhe_rsa, - cipher => aes_128_cbc, - mac => sha, - prf => default_prf}, - #{key_exchange => dhe_rsa, - cipher => aes_256_cbc, - mac => sha, - prf => default_prf}], - ServerCiphers = [#{key_exchange => dhe_rsa, - cipher => aes_256_cbc, - mac =>sha, - prf => default_prf}, - #{key_exchange => dhe_rsa, - cipher => aes_128_cbc, - mac => sha, - prf => default_prf}], -honor_cipher_order(Config, false, ServerCiphers, ClientCiphers, #{key_exchange => dhe_rsa, - cipher => aes_128_cbc, - mac => sha, - prf => default_prf}). - -honor_cipher_order(Config, Honor, ServerCiphers, ClientCiphers, Expected) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, 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, []}}, - {options, [{ciphers, ServerCiphers}, {honor_cipher_order, Honor} - | 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, []}}, - {options, [{ciphers, ClientCiphers}, {honor_cipher_order, Honor} - | ClientOpts]}]), - - Version = ssl_test_lib:protocol_version(Config), - - ServerMsg = ClientMsg = {ok, {Version, Expected}}, - - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -tls_ciphersuite_vs_version() -> - [{doc,"Test a SSLv3 client cannot negotiate a TLSv* cipher suite."}]. -tls_ciphersuite_vs_version(Config) when is_list(Config) -> - - {_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]), - ok = gen_tcp:send(Socket, - <<22, 3,0, 49:16, % handshake, SSL 3.0, length - 1, 45:24, % client_hello, length - 3,0, % SSL 3.0 - 16#deadbeef:256, % 32 'random' bytes = 256 bits - 0, % no session ID - %% three cipher suites -- null, one with sha256 hash and one with sha hash - 6:16, 0,255, 0,61, 0,57, - 1, 0 % no compression - >>), - {ok, <<22, RecMajor:8, RecMinor:8, _RecLen:16, 2, HelloLen:24>>} = gen_tcp:recv(Socket, 9, 10000), - {ok, <<HelloBin:HelloLen/binary>>} = gen_tcp:recv(Socket, HelloLen, 5000), - ServerHello = tls_handshake:decode_handshake({RecMajor, RecMinor}, 2, HelloBin), - case ServerHello of - #server_hello{server_version = {3,0}, cipher_suite = <<0,57>>} -> - ok; - _ -> - ct:fail({unexpected_server_hello, ServerHello}) - end. - -%%-------------------------------------------------------------------- -conf_signature_algs() -> - [{doc,"Test to set the signature_algs option on both client and server"}]. -conf_signature_algs(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ClientNode, ServerNode, Hostname} = 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, []}}, - {options, [{active, false}, {signature_algs, [{sha, rsa}]} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false}, {signature_algs, [{sha, rsa}]} | ClientOpts]}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - -%%-------------------------------------------------------------------- -no_common_signature_algs() -> - [{doc,"Set the signature_algs option so that there client and server does not share any hash sign algorithms"}]. -no_common_signature_algs(Config) when is_list(Config) -> - - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, [{signature_algs, [{sha256, rsa}]} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, [{signature_algs, [{sha384, rsa}]} - | ClientOpts]}]), - - ssl_test_lib:check_server_alert(Server, Client, insufficient_security). - -%%-------------------------------------------------------------------- - -tls_dont_crash_on_handshake_garbage() -> - [{doc, "Ensure SSL server worker thows an alert on garbage during handshake " - "instead of crashing and exposing state to user code"}]. - -tls_dont_crash_on_handshake_garbage(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {_ClientNode, ServerNode, Hostname} = 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, ServerOpts}]), - unlink(Server), monitor(process, Server), - Port = ssl_test_lib:inet_port(Server), - - {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]), - - % Send hello and garbage record - ok = gen_tcp:send(Socket, - [<<22, 3,3, 49:16, 1, 45:24, 3,3, % client_hello - 16#deadbeef:256, % 32 'random' bytes = 256 bits - 0, 6:16, 0,255, 0,61, 0,57, 1, 0 >>, % some hello values - - <<22, 3,3, 5:16, 92,64,37,228,209>> % garbage - ]), - % Send unexpected change_cipher_spec - ok = gen_tcp:send(Socket, <<20, 3,3, 12:16, 111,40,244,7,137,224,16,109,197,110,249,152>>), - - % Ensure we receive an alert, not sudden disconnect - {ok, <<21, _/binary>>} = drop_handshakes(Socket, 1000). - -drop_handshakes(Socket, Timeout) -> - {ok, <<RecType:8, _RecMajor:8, _RecMinor:8, RecLen:16>> = Header} = gen_tcp:recv(Socket, 5, Timeout), - {ok, <<Frag:RecLen/binary>>} = gen_tcp:recv(Socket, RecLen, Timeout), - case RecType of - 22 -> drop_handshakes(Socket, Timeout); - _ -> {ok, <<Header/binary, Frag/binary>>} - end. - - -%%-------------------------------------------------------------------- - -hibernate() -> - [{doc,"Check that an SSL connection that is started with option " - "{hibernate_after, 1000} indeed hibernates after 1000ms of " - "inactivity"}]. - -hibernate(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - {Client, #sslsocket{pid=[Pid|_]}} = ssl_test_lib:start_client([return_socket, - {node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{hibernate_after, 1000}|ClientOpts]}]), - {current_function, _} = - process_info(Pid, current_function), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - timer:sleep(1500), - {current_function, {erlang, hibernate, 3}} = - process_info(Pid, current_function), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- - -hibernate_right_away() -> - [{doc,"Check that an SSL connection that is configured to hibernate " - "after 0 or 1 milliseconds hibernates as soon as possible and not " - "crashes"}]. - -hibernate_right_away(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - StartServerOpts = [{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}], - StartClientOpts = [return_socket, - {node, ClientNode}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}], - - Server1 = ssl_test_lib:start_server(StartServerOpts), - Port1 = ssl_test_lib:inet_port(Server1), - {Client1, #sslsocket{pid = [Pid1|_]}} = ssl_test_lib:start_client(StartClientOpts ++ - [{port, Port1}, {options, [{hibernate_after, 0}|ClientOpts]}]), - - ssl_test_lib:check_result(Server1, ok, Client1, ok), - - {current_function, {erlang, hibernate, 3}} = - process_info(Pid1, current_function), - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client1), - - Server2 = ssl_test_lib:start_server(StartServerOpts), - Port2 = ssl_test_lib:inet_port(Server2), - {Client2, #sslsocket{pid = [Pid2|_]}} = ssl_test_lib:start_client(StartClientOpts ++ - [{port, Port2}, {options, [{hibernate_after, 1}|ClientOpts]}]), - - ssl_test_lib:check_result(Server2, ok, Client2, ok), - - ct:sleep(1000), %% Schedule out - - {current_function, {erlang, hibernate, 3}} = - process_info(Pid2, current_function), - - ssl_test_lib:close(Server2), - ssl_test_lib:close(Client2). - -%%-------------------------------------------------------------------- -listen_socket() -> - [{doc,"Check error handling and inet compliance when calling API functions with listen sockets."}]. - -listen_socket(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {ok, ListenSocket} = ssl:listen(0, ServerOpts), - - %% This can be a valid thing to do as - %% options are inherited by the accept socket - ok = ssl:controlling_process(ListenSocket, self()), - - {ok, _} = ssl:sockname(ListenSocket), - - {error, enotconn} = ssl:send(ListenSocket, <<"data">>), - {error, enotconn} = ssl:recv(ListenSocket, 0), - {error, enotconn} = ssl:connection_information(ListenSocket), - {error, enotconn} = ssl:peername(ListenSocket), - {error, enotconn} = ssl:peercert(ListenSocket), - {error, enotconn} = ssl:renegotiate(ListenSocket), - {error, enotconn} = ssl:prf(ListenSocket, 'master_secret', <<"Label">>, [client_random], 256), - {error, enotconn} = ssl:shutdown(ListenSocket, read_write), - - ok = ssl:close(ListenSocket). -%%-------------------------------------------------------------------- -tls_ssl_accept_timeout() -> - [{doc,"Test ssl:ssl_accept timeout"}]. - -tls_ssl_accept_timeout(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {timeout, 5000}, - {mfa, {ssl_test_lib, - no_result_msg, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - {ok, CSocket} = gen_tcp:connect(Hostname, Port, [binary, {active, true}]), - - receive - {tcp_closed, CSocket} -> - ssl_test_lib:check_result(Server, {error, timeout}), - receive - {'EXIT', Server, _} -> - %% Make sure supervisor had time to react on process exit - %% Could we come up with a better solution to this? - ct:sleep(500), - [] = supervisor:which_children(tls_connection_sup) - end - end. - -%%-------------------------------------------------------------------- -ssl_recv_timeout() -> - [{doc,"Test ssl:ssl_accept timeout"}]. - -ssl_recv_timeout(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, send_recv_result_timeout_server, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - send_recv_result_timeout_client, []}}, - {options, [{active, false} | ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -connect_twice() -> - [{doc,""}]. -connect_twice(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = 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, []}}, - {options, [{keepalive, true},{active, false} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{keepalive, true},{active, false} - | ClientOpts]}]), - Server ! listen, - - {Client1, #sslsocket{}} = - ssl_test_lib:start_client([return_socket, - {node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{keepalive, true},{active, false} - | ClientOpts]}]), - - ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:check_result(Server, ok, Client1, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- -renegotiate_dos_mitigate_active() -> - [{doc, "Mitigate DOS computational attack by not allowing client to renegotiate many times in a row", - "immediately after each other"}]. -renegotiate_dos_mitigate_active(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - renegotiate_immediately, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -renegotiate_dos_mitigate_passive() -> - [{doc, "Mitigate DOS computational attack by not allowing client to renegotiate many times in a row", - "immediately after each other"}]. -renegotiate_dos_mitigate_passive(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = 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, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, {from, self()}, - {mfa, {?MODULE, - renegotiate_immediately, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -renegotiate_dos_mitigate_absolute() -> - [{doc, "Mitigate DOS computational attack by not allowing client to initiate renegotiation"}]. -renegotiate_dos_mitigate_absolute(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - - {ClientNode, ServerNode, Hostname} = 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, [{client_renegotiation, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - renegotiate_rejected, - []}}, + {mfa, {?MODULE, connect_dist_c, []}}, {options, ClientOpts}]), - - ssl_test_lib:check_result(Client, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -tls_tcp_error_propagation_in_active_mode() -> - [{doc,"Test that process recives {ssl_error, Socket, closed} when tcp error ocurres"}]. -tls_tcp_error_propagation_in_active_mode(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - {Client, #sslsocket{pid=[Pid|_]} = SslSocket} = ssl_test_lib:start_client([return_socket, - {node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, receive_msg, []}}, - {options, ClientOpts}]), - - {status, _, _, StatusInfo} = sys:get_status(Pid), - [_, _,_, _, Prop] = StatusInfo, - State = ssl_test_lib:state(Prop), - StaticEnv = element(2, State), - Socket = element(11, StaticEnv), - %% Fake tcp error - Pid ! {tcp_error, Socket, etimedout}, - - ssl_test_lib:check_result(Client, {ssl_closed, SslSocket}). - -%%-------------------------------------------------------------------- -recv_error_handling() -> - [{doc,"Special case of call error handling"}]. -recv_error_handling(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, recv_close, []}}, - {options, [{active, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - {_Client, #sslsocket{} = SslSocket} = ssl_test_lib:start_client([return_socket, - {node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - ssl:close(SslSocket), - ssl_test_lib:check_result(Server, ok). - - - -%%-------------------------------------------------------------------- -call_in_error_state() -> - [{doc,"Special case of call error handling"}]. -call_in_error_state(Config) when is_list(Config) -> - ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = [{cacertfile, "foo.pem"} | proplists:delete(cacertfile, ServerOpts0)], - Pid = spawn_link(?MODULE, run_error_server, [[self() | ServerOpts]]), - receive - {Pid, Port} -> - spawn_link(?MODULE, run_client_error, [[Port, ClientOpts]]) - end, - receive - {error, closed} -> - ok; - Other -> - ct:fail(Other) - end. - -run_client_error([Port, Opts]) -> - ssl:connect("localhost", Port, Opts). -run_error_server([ Pid | Opts]) -> - {ok, Listen} = ssl:listen(0, Opts), - {ok,{_, Port}} = ssl:sockname(Listen), - Pid ! {self(), Port}, - {ok, Socket} = ssl:transport_accept(Listen), - Pid ! ssl:controlling_process(Socket, self()). - -%%-------------------------------------------------------------------- - -close_in_error_state() -> - [{doc,"Special case of closing socket in error state"}]. -close_in_error_state(Config) when is_list(Config) -> - ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), - ServerOpts = [{cacertfile, "foo.pem"} | proplists:delete(cacertfile, ServerOpts0)], - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - _ = spawn_link(?MODULE, run_error_server_close, [[self() | ServerOpts]]), - receive - {_Pid, Port} -> - spawn_link(?MODULE, run_client_error, [[Port, ClientOpts]]) - end, - receive - ok -> - ok; - Other -> - ct:fail(Other) - end. -%%-------------------------------------------------------------------- -abuse_transport_accept_socket() -> - [{doc,"Only ssl:handshake and ssl:controlling_process is allowed for transport_accept:sockets"}]. -abuse_transport_accept_socket(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ssl_test_lib:check_result(Server, ok, Client, ok), - Server = ssl_test_lib:start_server_transport_abuse_socket([{node, ServerNode}, - {port, 0}, - {from, self()}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - ssl_test_lib:check_result(Server, ok), ssl_test_lib:close(Server), ssl_test_lib:close(Client). - - -%%-------------------------------------------------------------------- -controlling_process_transport_accept_socket() -> - [{doc,"Only ssl:handshake and ssl:controlling_process is allowed for transport_accept:sockets"}]. -controlling_process_transport_accept_socket(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server_transport_control([{node, ServerNode}, - {port, 0}, - {from, self()}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - _Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, ClientOpts}]), - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server). - -%%-------------------------------------------------------------------- -run_error_server_close([Pid | Opts]) -> - {ok, Listen} = ssl:listen(0, Opts), - {ok,{_, Port}} = ssl:sockname(Listen), - Pid ! {self(), Port}, - {ok, Socket} = ssl:transport_accept(Listen), - Pid ! ssl:close(Socket). - -%%-------------------------------------------------------------------- - -rizzo() -> - [{doc, "Test that there is a 1/n-1-split for non RC4 in 'TLS < 1.1' as it is - vunrable to Rizzo/Dungon attack"}]. - -rizzo(Config) when is_list(Config) -> - Prop = proplists:get_value(tc_group_properties, Config), - Version = proplists:get_value(name, Prop), - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, NVersion), - [{key_exchange, - fun(Alg) when Alg == ecdh_rsa; Alg == ecdhe_rsa-> - true; - (_) -> - false - end}, - {cipher, - fun(rc4_128) -> - false; - (chacha20_poly1305) -> - false; - (_) -> - true - end}]), - - run_send_recv_rizzo(Ciphers, Config, Version, - {?MODULE, send_recv_result_active_rizzo, []}). -%%-------------------------------------------------------------------- -no_rizzo_rc4() -> - [{doc,"Test that there is no 1/n-1-split for RC4 as it is not vunrable to Rizzo/Dungon attack"}]. - -no_rizzo_rc4(Config) when is_list(Config) -> - Prop = proplists:get_value(tc_group_properties, Config), - Version = proplists:get_value(name, Prop), - NVersion = ssl_test_lib:protocol_version(Config, tuple), - %% Test uses RSA certs - Ciphers = ssl:filter_cipher_suites(ssl_test_lib:rc4_suites(NVersion), - [{key_exchange, - fun(Alg) when Alg == ecdh_rsa; Alg == ecdhe_rsa-> - true; - (_) -> - false - end}]), - run_send_recv_rizzo(Ciphers, Config, Version, - {?MODULE, send_recv_result_active_no_rizzo, []}). - -rizzo_one_n_minus_one() -> - [{doc,"Test that the 1/n-1-split mitigation of Rizzo/Dungon attack can be explicitly selected"}]. - -rizzo_one_n_minus_one(Config) when is_list(Config) -> - Prop = proplists:get_value(tc_group_properties, Config), - Version = proplists:get_value(name, Prop), - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(all, NVersion), - [{key_exchange, - fun(Alg) when Alg == ecdh_rsa; Alg == ecdhe_rsa-> - true; - (_) -> - false - end}, - {cipher, - fun(rc4_128) -> - false; - %% TODO: remove this clause when chacha is fixed! - (chacha20_poly1305) -> - false; - (_) -> - true - end}]), - run_send_recv_rizzo(Ciphers, Config, Version, - {?MODULE, send_recv_result_active_rizzo, []}). - -rizzo_zero_n() -> - [{doc,"Test that the 0/n-split mitigation of Rizzo/Dungon attack can be explicitly selected"}]. - -rizzo_zero_n(Config) when is_list(Config) -> - Prop = proplists:get_value(tc_group_properties, Config), - Version = proplists:get_value(name, Prop), - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(default, NVersion), - [{cipher, - fun(rc4_128) -> - false; - (_) -> - true - end}]), - run_send_recv_rizzo(Ciphers, Config, Version, - {?MODULE, send_recv_result_active_no_rizzo, []}). - -rizzo_disabled() -> - [{doc,"Test that the mitigation of Rizzo/Dungon attack can be explicitly disabled"}]. - -rizzo_disabled(Config) when is_list(Config) -> - Prop = proplists:get_value(tc_group_properties, Config), - Version = proplists:get_value(name, Prop), - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(default, NVersion), - [{cipher, - fun(rc4_128) -> - false; - (_) -> - true - end}]), - run_send_recv_rizzo(Ciphers, Config, Version, - {?MODULE, send_recv_result_active_no_rizzo, []}). - -%%-------------------------------------------------------------------- -new_server_wants_peer_cert() -> - [{doc, "Test that server configured to do client certification does" - " not reuse session without a client certificate."}]. -new_server_wants_peer_cert(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - VServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_verification_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, peercert_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, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - - Monitor = erlang:monitor(process, Server), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - receive - {'DOWN', Monitor, _, _, _} -> - ok - end, - - Server1 = ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, - {from, self()}, - {mfa, {?MODULE, peercert_result, []}}, - {options, VServerOpts}]), - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [ClientOpts]}]), - - CertFile = proplists:get_value(certfile, ClientOpts), - [{'Certificate', BinCert, _}]= ssl_test_lib:pem_to_der(CertFile), - - ServerMsg = {error, no_peercert}, - Sever1Msg = {ok, BinCert}, - - ssl_test_lib:check_result(Server, ServerMsg, Server1, Sever1Msg), - - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- -session_cache_process_list() -> - [{doc,"Test reuse of sessions (short handshake)"}]. -session_cache_process_list(Config) when is_list(Config) -> - session_cache_process(list,Config). -%%-------------------------------------------------------------------- -session_cache_process_mnesia() -> - [{doc,"Test reuse of sessions (short handshake)"}]. -session_cache_process_mnesia(Config) when is_list(Config) -> - session_cache_process(mnesia,Config). +eccs() -> + [{doc, "Test API functions eccs/0 and eccs/1"}]. -%%-------------------------------------------------------------------- +eccs(Config) when is_list(Config) -> + [_|_] = All = ssl:eccs(), + [] = ssl:eccs(sslv3), + [_|_] = Tls = ssl:eccs(tlsv1), + [_|_] = Tls1 = ssl:eccs('tlsv1.1'), + [_|_] = Tls2 = ssl:eccs('tlsv1.2'), + [_|_] = Tls1 = ssl:eccs('dtlsv1'), + [_|_] = Tls2 = ssl:eccs('dtlsv1.2'), + %% ordering is currently not verified by the test + true = lists:sort(All) =:= lists:usort(Tls ++ Tls1 ++ Tls2), + ok. tls_versions_option() -> [{doc,"Test API versions option to connect/listen."}]. @@ -4208,1451 +452,6 @@ tls_versions_option(Config) when is_list(Config) -> %%-------------------------------------------------------------------- -unordered_protocol_versions_server() -> - [{doc,"Test that the highest protocol is selected even" - " when it is not first in the versions list."}]. - -unordered_protocol_versions_server(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {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, protocol_info_result, []}}, - {options, ClientOpts}]), - - ServerMsg = ClientMsg = {ok,'tlsv1.2'}, - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). - -%%-------------------------------------------------------------------- -unordered_protocol_versions_client() -> - [{doc,"Test that the highest protocol is selected even" - " when it is not first in the versions list."}]. - -unordered_protocol_versions_client(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {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, protocol_info_result, []}}, - {options, [{versions, ['tlsv1.1', 'tlsv1.2']} | ClientOpts]}]), - - ServerMsg = ClientMsg = {ok, 'tlsv1.2'}, - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). - -%%-------------------------------------------------------------------- -max_handshake_size() -> - [{doc,"Test that we can set max_handshake_size to max value."}]. - -max_handshake_size(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = 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, [{max_handshake_size, 8388607} |ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{max_handshake_size, 8388607} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok). - -%%-------------------------------------------------------------------- - -server_name_indication_option() -> - [{doc,"Test API server_name_indication option to connect."}]. -server_name_indication_option(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, - [{server_name_indication, disable} | - ClientOpts]} - ]), - - ssl_test_lib:check_result(Server, ok, Client0, ok), - Server ! listen, - - Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, - [{server_name_indication, Hostname} | ClientOpts] - }]), - ssl_test_lib:check_result(Server, ok, Client1, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client0), - ssl_test_lib:close(Client1). -%%-------------------------------------------------------------------- - -accept_pool() -> - [{doc,"Test having an accept pool."}]. -accept_pool(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server0 = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {accepters, 3}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server0), - [Server1, Server2] = ssl_test_lib:accepters(2), - - Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts} - ]), - - Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts} - ]), - - Client2 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts} - ]), - - ssl_test_lib:check_ok([Server0, Server1, Server2, Client0, Client1, Client2]), - - ssl_test_lib:close(Server0), - ssl_test_lib:close(Server1), - ssl_test_lib:close(Server2), - ssl_test_lib:close(Client0), - ssl_test_lib:close(Client1), - ssl_test_lib:close(Client2). - -%%-------------------------------------------------------------------- -%% TLS 1.3 -%%-------------------------------------------------------------------- - -tls13_enable_client_side() -> - [{doc,"Test that a TLS 1.3 client can connect to a TLS 1.2 server."}]. - -tls13_enable_client_side(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {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, protocol_info_result, []}}, - {options, [{versions, - ['tlsv1.2', 'tlsv1.3']} | ClientOpts]}]), - - ServerMsg = ClientMsg = {ok, 'tlsv1.2'}, - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). - -tls13_enable_server_side() -> - [{doc,"Test that a TLS 1.2 client can connect to a TLS 1.3 server."}]. - -tls13_enable_server_side(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, protocol_info_result, []}}, - {options, [{versions, - ['tlsv1.2', 'tlsv1.3']} | ServerOpts] }]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, protocol_info_result, []}}, - {options, [{versions, - ['tlsv1.2', 'tlsv1.1']} | ClientOpts]}]), - - ServerMsg = ClientMsg = {ok, 'tlsv1.2'}, - ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). - -tls_record_1_3_encode_decode() -> - [{doc,"Test TLS 1.3 record encode/decode functions"}]. - -tls_record_1_3_encode_decode(_Config) -> - ConnectionStates = - #{current_read => - #{beast_mitigation => one_n_minus_one, - cipher_state => - {cipher_state, - <<14,172,111,243,199,170,242,203,126,205,34,93,122,115,226,14, - 15,117,155,48,24,112,61,15,113,208,127,51,179,227,194,232>>, - <<197,54,168,218,54,91,157,58,30,201,197,142,51,58,53,231,228, - 131,57,122,170,78,82,196,30,48,23,16,95,255,185,236>>, - undefined,undefined,undefined,16}, - client_verify_data => undefined,compression_state => undefined, - mac_secret => undefined,secure_renegotiation => undefined, - security_parameters => - {security_parameters, - <<19,2>>, - 0,8,2,undefined,undefined,undefined,undefined,undefined, - sha384,undefined,undefined, - {handshake_secret, - <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121, - 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218, - 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56, - 157>>}, - undefined, - <<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207, - 147,61,168,145,177,118,160,153,33,53,48,108,191,174>>, - undefined}, - sequence_number => 0,server_verify_data => undefined}, - current_write => - #{beast_mitigation => one_n_minus_one, - cipher_state => - {cipher_state, - <<14,172,111,243,199,170,242,203,126,205,34,93,122,115,226,14, - 15,117,155,48,24,112,61,15,113,208,127,51,179,227,194,232>>, - <<197,54,168,218,54,91,157,58,30,201,197,142,51,58,53,231,228, - 131,57,122,170,78,82,196,30,48,23,16,95,255,185,236>>, - undefined,undefined,undefined,16}, - client_verify_data => undefined,compression_state => undefined, - mac_secret => undefined,secure_renegotiation => undefined, - security_parameters => - {security_parameters, - <<19,2>>, - 0,8,2,undefined,undefined,undefined,undefined,undefined, - sha384,undefined,undefined, - {handshake_secret, - <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121, - 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218, - 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56, - 157>>}, - undefined, - <<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207, - 147,61,168,145,177,118,160,153,33,53,48,108,191,174>>, - undefined}, - sequence_number => 0,server_verify_data => undefined}}, - - PlainText = [11, - <<0,2,175>>, - <<0,0,2,171,0,2,166,48,130,2,162,48,130,1,138,2,9,0,186,57,220,137,88,255, - 191,235,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,48,18,49,16,48,14,6,3,85, - 4,3,12,7,84,101,115,116,32,67,65,48,30,23,13,49,56,48,53,48,52,49,52,49,50, - 51,56,90,23,13,50,56,48,50,48,52,49,52,49,50,51,56,90,48,20,49,18,48,16,6, - 3,85,4,3,12,9,108,111,99,97,108,104,111,115,116,48,130,1,34,48,13,6,9,42, - 134,72,134,247,13,1,1,1,5,0,3,130,1,15,0,48,130,1,10,2,130,1,1,0,169,40, - 144,176,121,63,134,97,144,126,243,183,225,157,37,131,183,225,87,243,23,88, - 230,70,9,134,32,147,7,27,167,98,51,81,224,75,199,12,229,251,195,207,75,179, - 181,78,128,3,255,44,58,39,43,172,142,45,186,58,51,65,187,199,154,153,245, - 70,133,137,1,27,87,42,116,65,251,129,109,145,233,97,171,71,54,213,185,74, - 209,166,11,218,189,119,206,86,170,60,212,213,85,189,30,50,215,23,185,53, - 132,238,132,176,198,250,139,251,198,221,225,128,109,113,23,220,39,143,71, - 30,59,189,51,244,61,158,214,146,180,196,103,169,189,221,136,78,129,216,148, - 2,9,8,65,37,224,215,233,13,209,21,235,20,143,33,74,59,53,208,90,152,94,251, - 54,114,171,39,88,230,227,158,211,135,37,182,67,205,161,59,20,138,58,253,15, - 53,48,8,157,9,95,197,9,177,116,21,54,9,125,78,109,182,83,20,16,234,223,116, - 41,155,123,87,77,17,120,153,246,239,124,130,105,219,166,146,242,151,66,198, - 75,72,63,28,246,86,16,244,223,22,36,50,15,247,222,98,6,152,136,154,72,150, - 73,127,2,3,1,0,1,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,3,130,1,1,0,76, - 33,54,160,229,219,219,193,150,116,245,252,18,39,235,145,86,12,167,171,52, - 117,166,30,83,5,216,245,177,217,247,95,1,136,94,246,212,108,248,230,111, - 225,202,189,6,129,8,70,128,245,18,204,215,87,82,129,253,227,122,66,182,184, - 189,30,193,169,144,218,216,109,105,110,215,144,60,104,162,178,101,164,218, - 122,60,37,41,143,57,150,52,59,51,112,238,113,239,168,114,69,183,143,154,73, - 61,58,80,247,172,95,251,55,28,186,28,200,206,230,118,243,92,202,189,49,76, - 124,252,76,0,247,112,85,194,69,59,222,163,228,103,49,110,104,109,251,155, - 138,9,37,167,49,189,48,134,52,158,185,129,24,96,153,196,251,90,206,76,239, - 175,119,174,165,133,108,222,125,237,125,187,149,152,83,190,16,202,94,202, - 201,40,218,22,254,63,189,41,174,97,140,203,70,18,196,118,237,175,134,79,78, - 246,2,61,54,77,186,112,32,17,193,192,188,217,252,215,200,7,245,180,179,132, - 183,212,229,155,15,152,206,135,56,81,88,3,123,244,149,110,182,72,109,70,62, - 146,152,146,151,107,126,216,210,9,93,0,0>>], - - {[_Header|Encoded], _} = tls_record_1_3:encode_plain_text(22, PlainText, ConnectionStates), - CipherText = #ssl_tls{type = 23, version = {3,3}, fragment = Encoded}, - - {#ssl_tls{type = 22, version = {3,4}, fragment = DecodedText}, _} = - tls_record_1_3:decode_cipher_text(CipherText, ConnectionStates), - - DecodedText = iolist_to_binary(PlainText), - ct:log("Decoded: ~p ~n", [DecodedText]), - ok. - -tls13_1_RTT_handshake() -> - [{doc,"Test TLS 1.3 1-RTT Handshake"}]. - -tls13_1_RTT_handshake(_Config) -> - %% ConnectionStates with NULL cipher - ConnStatesNull = - #{current_write => - #{security_parameters => - #security_parameters{cipher_suite = ?TLS_NULL_WITH_NULL_NULL}, - sequence_number => 0 - } - }, - - %% {client} construct a ClientHello handshake message: - %% - %% ClientHello (196 octets): 01 00 00 c0 03 03 cb 34 ec b1 e7 81 63 - %% ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83 - %% 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b - %% 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 - %% 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23 - %% 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2 - %% 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a - %% af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 - %% 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 - %% 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 - %% - %% {client} send handshake record: - %% - %% payload (196 octets): 01 00 00 c0 03 03 cb 34 ec b1 e7 81 63 ba - %% 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83 02 - %% 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b 00 - %% 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 12 - %% 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23 00 - %% 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2 3d - %% 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af - %% 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 02 - %% 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 02 - %% 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 - %% - %% complete record (201 octets): 16 03 01 00 c4 01 00 00 c0 03 03 cb - %% 34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 - %% ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 - %% 00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 - %% 00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 - %% 01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d - %% e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d - %% 54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e - %% 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 - %% 01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 - ClientHello = - hexstr2bin("01 00 00 c0 03 03 cb 34 ec b1 e7 81 63 - ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83 - 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b - 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 - 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23 - 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2 - 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a - af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 - 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 - 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01"), - - ClientHelloRecord = - %% Current implementation always sets - %% legacy_record_version to Ox0303 - hexstr2bin("16 03 03 00 c4 01 00 00 c0 03 03 cb - 34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 - ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 - 00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 - 00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 - 01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d - e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d - 54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e - 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 - 01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01"), - - {CHEncrypted, _} = - tls_record:encode_handshake(ClientHello, {3,4}, ConnStatesNull), - ClientHelloRecord = iolist_to_binary(CHEncrypted), - - %% {server} extract secret "early": - %% - %% salt: 0 (all zero octets) - %% - %% IKM (32 octets): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - %% - %% secret (32 octets): 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c - %% e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a - HKDFAlgo = sha256, - Salt = binary:copy(<<?BYTE(0)>>, 32), - IKM = binary:copy(<<?BYTE(0)>>, 32), - EarlySecret = - hexstr2bin("33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c - e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a"), - - {early_secret, EarlySecret} = tls_v1:key_schedule(early_secret, HKDFAlgo, {psk, Salt}), - - %% {client} create an ephemeral x25519 key pair: - %% - %% private key (32 octets): 49 af 42 ba 7f 79 94 85 2d 71 3e f2 78 - %% 4b cb ca a7 91 1d e2 6a dc 56 42 cb 63 45 40 e7 ea 50 05 - %% - %% public key (32 octets): 99 38 1d e5 60 e4 bd 43 d2 3d 8e 43 5a 7d - %% ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af 2c - CPublicKey = - hexstr2bin("99 38 1d e5 60 e4 bd 43 d2 3d 8e 43 5a 7d - ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af 2c"), - - %% {server} create an ephemeral x25519 key pair: - %% - %% private key (32 octets): b1 58 0e ea df 6d d5 89 b8 ef 4f 2d 56 - %% 52 57 8c c8 10 e9 98 01 91 ec 8d 05 83 08 ce a2 16 a2 1e - %% - %% public key (32 octets): c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 - %% 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f - SPrivateKey = - hexstr2bin("b1 58 0e ea df 6d d5 89 b8 ef 4f 2d 56 - 52 57 8c c8 10 e9 98 01 91 ec 8d 05 83 08 ce a2 16 a2 1e"), - - SPublicKey = - hexstr2bin("c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 - 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f"), - - %% {server} construct a ServerHello handshake message: - %% - %% ServerHello (90 octets): 02 00 00 56 03 03 a6 af 06 a4 12 18 60 - %% dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e - %% d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88 - %% 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1 - %% dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04 - ServerHello = - hexstr2bin("02 00 00 56 03 03 a6 af 06 a4 12 18 60 - dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e - d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88 - 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1 - dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04"), - - %% {server} derive secret for handshake "tls13 derived": - %% - %% PRK (32 octets): 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c e2 - %% 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a - %% - %% hash (32 octets): e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 - %% 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55 - %% - %% info (49 octets): 00 20 0d 74 6c 73 31 33 20 64 65 72 69 76 65 64 - %% 20 e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4 - %% 64 9b 93 4c a4 95 99 1b 78 52 b8 55 - %% - %% expanded (32 octets): 6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba - %% b6 97 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba - Hash = - hexstr2bin("e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 - 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55"), - - Hash = crypto:hash(HKDFAlgo, <<>>), - - Info = - hexstr2bin("00 20 0d 74 6c 73 31 33 20 64 65 72 69 76 65 64 - 20 e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4 - 64 9b 93 4c a4 95 99 1b 78 52 b8 55"), - - Info = tls_v1:create_info(<<"derived">>, Hash, ssl_cipher:hash_size(HKDFAlgo)), - - Expanded = - hexstr2bin("6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba - b6 97 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba"), - - Expanded = tls_v1:derive_secret(EarlySecret, <<"derived">>, <<>>, HKDFAlgo), - - %% {server} extract secret "handshake": - %% - %% salt (32 octets): 6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba b6 97 - %% 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba - %% - %% IKM (32 octets): 8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d - %% 35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d - %% - %% secret (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b - %% 01 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac - - %% salt = Expanded - HandshakeIKM = - hexstr2bin("8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d - 35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d"), - - HandshakeSecret = - hexstr2bin("1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b - 01 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac"), - - HandshakeIKM = crypto:compute_key(ecdh, CPublicKey, SPrivateKey, x25519), - - {handshake_secret, HandshakeSecret} = - tls_v1:key_schedule(handshake_secret, HKDFAlgo, HandshakeIKM, - {early_secret, EarlySecret}), - - %% {server} derive secret "tls13 c hs traffic": - %% - %% PRK (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01 - %% 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac - %% - %% hash (32 octets): 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed - %% d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 - %% - %% info (54 octets): 00 20 12 74 6c 73 31 33 20 63 20 68 73 20 74 72 - %% 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 - %% ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 - %% - %% expanded (32 octets): b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e - %% 2d 8f 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21 - - %% PRK = HandshakeSecret - CHSTHash = - hexstr2bin("86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed - d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"), - - CHSTInfo = - hexstr2bin("00 20 12 74 6c 73 31 33 20 63 20 68 73 20 74 72 - 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 - ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"), - - CHSTrafficSecret = - hexstr2bin(" b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e - 2d 8f 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21"), - - CHSH = <<ClientHello/binary,ServerHello/binary>>, - CHSTHash = crypto:hash(HKDFAlgo, CHSH), - CHSTInfo = tls_v1:create_info(<<"c hs traffic">>, CHSTHash, ssl_cipher:hash_size(HKDFAlgo)), - - CHSTrafficSecret = - tls_v1:client_handshake_traffic_secret(HKDFAlgo, {handshake_secret, HandshakeSecret}, CHSH), - - %% {server} derive secret "tls13 s hs traffic": - %% - %% PRK (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01 - %% 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac - %% - %% hash (32 octets): 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed - %% d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 - %% - %% info (54 octets): 00 20 12 74 6c 73 31 33 20 73 20 68 73 20 74 72 - %% 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 - %% ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 - %% - %% expanded (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d - %% 37 b4 e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38 - - %% PRK = HandshakeSecret - %% hash = CHSTHash - SHSTInfo = - hexstr2bin("00 20 12 74 6c 73 31 33 20 73 20 68 73 20 74 72 - 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 - ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"), - - SHSTrafficSecret = - hexstr2bin("b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d - 37 b4 e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38"), - - SHSTInfo = tls_v1:create_info(<<"s hs traffic">>, CHSTHash, ssl_cipher:hash_size(HKDFAlgo)), - - SHSTrafficSecret = - tls_v1:server_handshake_traffic_secret(HKDFAlgo, {handshake_secret, HandshakeSecret}, CHSH), - - - %% {server} derive secret for master "tls13 derived": - %% - %% PRK (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01 - %% 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac - %% - %% hash (32 octets): e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 - %% 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55 - %% - %% info (49 octets): 00 20 0d 74 6c 73 31 33 20 64 65 72 69 76 65 64 - %% 20 e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4 - %% 64 9b 93 4c a4 95 99 1b 78 52 b8 55 - %% - %% expanded (32 octets): 43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 - %% 90 b5 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4 - - %% PRK = HandshakeSecret - %% hash = Hash - %% info = Info - MasterDeriveSecret = - hexstr2bin("43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 - 90 b5 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4"), - - MasterDeriveSecret = tls_v1:derive_secret(HandshakeSecret, <<"derived">>, <<>>, HKDFAlgo), - - %% {server} extract secret "master": - %% - %% salt (32 octets): 43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 90 b5 - %% 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4 - %% - %% IKM (32 octets): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - %% - %% secret (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a - %% 47 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 - - %% salt = MasterDeriveSecret - %% IKM = IKM - MasterSecret = - hexstr2bin("18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a - 47 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19"), - - {master_secret, MasterSecret} = - tls_v1:key_schedule(master_secret, HKDFAlgo, {handshake_secret, HandshakeSecret}), - - %% {server} send handshake record: - %% - %% payload (90 octets): 02 00 00 56 03 03 a6 af 06 a4 12 18 60 dc 5e - %% 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e d3 e2 - %% 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88 76 11 - %% 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 - %% b1 b0 4e 75 1f 0f 00 2b 00 02 03 04 - %% - %% complete record (95 octets): 16 03 03 00 5a 02 00 00 56 03 03 a6 - %% af 06 a4 12 18 60 dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 - %% 34 da c1 55 77 2e d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 - %% 1d 00 20 c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 - %% cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04 - - %% payload = ServerHello - ServerHelloRecord = - hexstr2bin("16 03 03 00 5a 02 00 00 56 03 03 a6 - af 06 a4 12 18 60 dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 - 34 da c1 55 77 2e d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 - 1d 00 20 c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 - cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04"), - - {SHEncrypted, _} = - tls_record:encode_handshake(ServerHello, {3,4}, ConnStatesNull), - ServerHelloRecord = iolist_to_binary(SHEncrypted), - - %% {server} derive write traffic keys for handshake data: - %% - %% PRK (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d 37 b4 - %% e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38 - %% - %% key info (13 octets): 00 10 09 74 6c 73 31 33 20 6b 65 79 00 - %% - %% key expanded (16 octets): 3f ce 51 60 09 c2 17 27 d0 f2 e4 e8 6e - %% e4 03 bc - %% - %% iv info (12 octets): 00 0c 08 74 6c 73 31 33 20 69 76 00 - %% - %% iv expanded (12 octets): 5d 31 3e b2 67 12 76 ee 13 00 0b 30 - - %% PRK = SHSTrafficSecret - WriteKeyInfo = - hexstr2bin("00 10 09 74 6c 73 31 33 20 6b 65 79 00"), - - WriteKey = - hexstr2bin("3f ce 51 60 09 c2 17 27 d0 f2 e4 e8 6e e4 03 bc"), - - WriteIVInfo = - hexstr2bin("00 0c 08 74 6c 73 31 33 20 69 76 00"), - - WriteIV = - hexstr2bin(" 5d 31 3e b2 67 12 76 ee 13 00 0b 30"), - - Cipher = aes_128_gcm, %% TODO: get from ServerHello - - WriteKeyInfo = tls_v1:create_info(<<"key">>, <<>>, ssl_cipher:key_material(Cipher)), - %% TODO: remove hardcoded IV size - WriteIVInfo = tls_v1:create_info(<<"iv">>, <<>>, 12), - - {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, SHSTrafficSecret), - - %% {server} construct an EncryptedExtensions handshake message: - %% - %% EncryptedExtensions (40 octets): 08 00 00 24 00 22 00 0a 00 14 00 - %% 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 1c - %% 00 02 40 01 00 00 00 00 - %% - %% {server} construct a Certificate handshake message: - %% - %% Certificate (445 octets): 0b 00 01 b9 00 00 01 b5 00 01 b0 30 82 - %% 01 ac 30 82 01 15 a0 03 02 01 02 02 01 02 30 0d 06 09 2a 86 48 - %% 86 f7 0d 01 01 0b 05 00 30 0e 31 0c 30 0a 06 03 55 04 03 13 03 - %% 72 73 61 30 1e 17 0d 31 36 30 37 33 30 30 31 32 33 35 39 5a 17 - %% 0d 32 36 30 37 33 30 30 31 32 33 35 39 5a 30 0e 31 0c 30 0a 06 - %% 03 55 04 03 13 03 72 73 61 30 81 9f 30 0d 06 09 2a 86 48 86 f7 - %% 0d 01 01 01 05 00 03 81 8d 00 30 81 89 02 81 81 00 b4 bb 49 8f - %% 82 79 30 3d 98 08 36 39 9b 36 c6 98 8c 0c 68 de 55 e1 bd b8 26 - %% d3 90 1a 24 61 ea fd 2d e4 9a 91 d0 15 ab bc 9a 95 13 7a ce 6c - %% 1a f1 9e aa 6a f9 8c 7c ed 43 12 09 98 e1 87 a8 0e e0 cc b0 52 - %% 4b 1b 01 8c 3e 0b 63 26 4d 44 9a 6d 38 e2 2a 5f da 43 08 46 74 - %% 80 30 53 0e f0 46 1c 8c a9 d9 ef bf ae 8e a6 d1 d0 3e 2b d1 93 - %% ef f0 ab 9a 80 02 c4 74 28 a6 d3 5a 8d 88 d7 9f 7f 1e 3f 02 03 - %% 01 00 01 a3 1a 30 18 30 09 06 03 55 1d 13 04 02 30 00 30 0b 06 - %% 03 55 1d 0f 04 04 03 02 05 a0 30 0d 06 09 2a 86 48 86 f7 0d 01 - %% 01 0b 05 00 03 81 81 00 85 aa d2 a0 e5 b9 27 6b 90 8c 65 f7 3a - %% 72 67 17 06 18 a5 4c 5f 8a 7b 33 7d 2d f7 a5 94 36 54 17 f2 ea - %% e8 f8 a5 8c 8f 81 72 f9 31 9c f3 6b 7f d6 c5 5b 80 f2 1a 03 01 - %% 51 56 72 60 96 fd 33 5e 5e 67 f2 db f1 02 70 2e 60 8c ca e6 be - %% c1 fc 63 a4 2a 99 be 5c 3e b7 10 7c 3c 54 e9 b9 eb 2b d5 20 3b - %% 1c 3b 84 e0 a8 b2 f7 59 40 9b a3 ea c9 d9 1d 40 2d cc 0c c8 f8 - %% 96 12 29 ac 91 87 b4 2b 4d e1 00 00 - %% - %% {server} construct a CertificateVerify handshake message: - %% - %% CertificateVerify (136 octets): 0f 00 00 84 08 04 00 80 5a 74 7c - %% 5d 88 fa 9b d2 e5 5a b0 85 a6 10 15 b7 21 1f 82 4c d4 84 14 5a - %% b3 ff 52 f1 fd a8 47 7b 0b 7a bc 90 db 78 e2 d3 3a 5c 14 1a 07 - %% 86 53 fa 6b ef 78 0c 5e a2 48 ee aa a7 85 c4 f3 94 ca b6 d3 0b - %% be 8d 48 59 ee 51 1f 60 29 57 b1 54 11 ac 02 76 71 45 9e 46 44 - %% 5c 9e a5 8c 18 1e 81 8e 95 b8 c3 fb 0b f3 27 84 09 d3 be 15 2a - %% 3d a5 04 3e 06 3d da 65 cd f5 ae a2 0d 53 df ac d4 2f 74 f3 - EncryptedExtensions = - hexstr2bin("08 00 00 24 00 22 00 0a 00 14 00 - 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 1c - 00 02 40 01 00 00 00 00"), - - Certificate = - hexstr2bin("0b 00 01 b9 00 00 01 b5 00 01 b0 30 82 - 01 ac 30 82 01 15 a0 03 02 01 02 02 01 02 30 0d 06 09 2a 86 48 - 86 f7 0d 01 01 0b 05 00 30 0e 31 0c 30 0a 06 03 55 04 03 13 03 - 72 73 61 30 1e 17 0d 31 36 30 37 33 30 30 31 32 33 35 39 5a 17 - 0d 32 36 30 37 33 30 30 31 32 33 35 39 5a 30 0e 31 0c 30 0a 06 - 03 55 04 03 13 03 72 73 61 30 81 9f 30 0d 06 09 2a 86 48 86 f7 - 0d 01 01 01 05 00 03 81 8d 00 30 81 89 02 81 81 00 b4 bb 49 8f - 82 79 30 3d 98 08 36 39 9b 36 c6 98 8c 0c 68 de 55 e1 bd b8 26 - d3 90 1a 24 61 ea fd 2d e4 9a 91 d0 15 ab bc 9a 95 13 7a ce 6c - 1a f1 9e aa 6a f9 8c 7c ed 43 12 09 98 e1 87 a8 0e e0 cc b0 52 - 4b 1b 01 8c 3e 0b 63 26 4d 44 9a 6d 38 e2 2a 5f da 43 08 46 74 - 80 30 53 0e f0 46 1c 8c a9 d9 ef bf ae 8e a6 d1 d0 3e 2b d1 93 - ef f0 ab 9a 80 02 c4 74 28 a6 d3 5a 8d 88 d7 9f 7f 1e 3f 02 03 - 01 00 01 a3 1a 30 18 30 09 06 03 55 1d 13 04 02 30 00 30 0b 06 - 03 55 1d 0f 04 04 03 02 05 a0 30 0d 06 09 2a 86 48 86 f7 0d 01 - 01 0b 05 00 03 81 81 00 85 aa d2 a0 e5 b9 27 6b 90 8c 65 f7 3a - 72 67 17 06 18 a5 4c 5f 8a 7b 33 7d 2d f7 a5 94 36 54 17 f2 ea - e8 f8 a5 8c 8f 81 72 f9 31 9c f3 6b 7f d6 c5 5b 80 f2 1a 03 01 - 51 56 72 60 96 fd 33 5e 5e 67 f2 db f1 02 70 2e 60 8c ca e6 be - c1 fc 63 a4 2a 99 be 5c 3e b7 10 7c 3c 54 e9 b9 eb 2b d5 20 3b - 1c 3b 84 e0 a8 b2 f7 59 40 9b a3 ea c9 d9 1d 40 2d cc 0c c8 f8 - 96 12 29 ac 91 87 b4 2b 4d e1 00 00"), - - CertificateVerify = - hexstr2bin("0f 00 00 84 08 04 00 80 5a 74 7c - 5d 88 fa 9b d2 e5 5a b0 85 a6 10 15 b7 21 1f 82 4c d4 84 14 5a - b3 ff 52 f1 fd a8 47 7b 0b 7a bc 90 db 78 e2 d3 3a 5c 14 1a 07 - 86 53 fa 6b ef 78 0c 5e a2 48 ee aa a7 85 c4 f3 94 ca b6 d3 0b - be 8d 48 59 ee 51 1f 60 29 57 b1 54 11 ac 02 76 71 45 9e 46 44 - 5c 9e a5 8c 18 1e 81 8e 95 b8 c3 fb 0b f3 27 84 09 d3 be 15 2a - 3d a5 04 3e 06 3d da 65 cd f5 ae a2 0d 53 df ac d4 2f 74 f3"), - - %% {server} calculate finished "tls13 finished": - %% - %% PRK (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d 37 b4 - %% e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38 - %% - %% hash (0 octets): (empty) - %% - %% info (18 octets): 00 20 0e 74 6c 73 31 33 20 66 69 6e 69 73 68 65 - %% 64 00 - %% - %% expanded (32 octets): 00 8d 3b 66 f8 16 ea 55 9f 96 b5 37 e8 85 - %% c3 1f c0 68 bf 49 2c 65 2f 01 f2 88 a1 d8 cd c1 9f c8 - %% - %% finished (32 octets): 9b 9b 14 1d 90 63 37 fb d2 cb dc e7 1d f4 - %% de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 18 - - %% PRK = SHSTrafficSecret - FInfo = - hexstr2bin("00 20 0e 74 6c 73 31 33 20 66 69 6e 69 73 68 65 - 64 00"), - - FExpanded = - hexstr2bin("00 8d 3b 66 f8 16 ea 55 9f 96 b5 37 e8 85 - c3 1f c0 68 bf 49 2c 65 2f 01 f2 88 a1 d8 cd c1 9f c8"), - - FinishedVerifyData = - hexstr2bin("9b 9b 14 1d 90 63 37 fb d2 cb dc e7 1d f4 - de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 18"), - - FInfo = tls_v1:create_info(<<"finished">>, <<>>, ssl_cipher:hash_size(HKDFAlgo)), - - FExpanded = tls_v1:finished_key(SHSTrafficSecret, HKDFAlgo), - - MessageHistory0 = [CertificateVerify, - Certificate, - EncryptedExtensions, - ServerHello, - ClientHello], - - FinishedVerifyData = tls_v1:finished_verify_data(FExpanded, HKDFAlgo, MessageHistory0), - - %% {server} construct a Finished handshake message: - %% - %% Finished (36 octets): 14 00 00 20 9b 9b 14 1d 90 63 37 fb d2 cb - %% dc e7 1d f4 de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 - %% 18 - FinishedHSBin = - hexstr2bin("14 00 00 20 9b 9b 14 1d 90 63 37 fb d2 cb - dc e7 1d f4 de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 - 18"), - - FinishedHS = #finished{verify_data = FinishedVerifyData}, - - FinishedIOList = tls_handshake:encode_handshake(FinishedHS, {3,4}), - FinishedHSBin = iolist_to_binary(FinishedIOList), - - %% {server} derive secret "tls13 c ap traffic": - %% - %% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 - %% 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 - %% - %% hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a - %% 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 - %% - %% info (54 octets): 00 20 12 74 6c 73 31 33 20 63 20 61 70 20 74 72 - %% 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b - %% 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 - %% - %% expanded (32 octets): 9e 40 64 6c e7 9a 7f 9d c0 5a f8 88 9b ce - %% 65 52 87 5a fa 0b 06 df 00 87 f7 92 eb b7 c1 75 04 a5 - - %% PRK = MasterSecret - CAPTHash = - hexstr2bin("96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a - 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), - CAPTInfo = - hexstr2bin("00 20 12 74 6c 73 31 33 20 63 20 61 70 20 74 72 - 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b - 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), - - CAPTrafficSecret = - hexstr2bin("9e 40 64 6c e7 9a 7f 9d c0 5a f8 88 9b ce - 65 52 87 5a fa 0b 06 df 00 87 f7 92 eb b7 c1 75 04 a5"), - - CHSF = <<ClientHello/binary, - ServerHello/binary, - EncryptedExtensions/binary, - Certificate/binary, - CertificateVerify/binary, - FinishedHSBin/binary>>, - - CAPTHash = crypto:hash(HKDFAlgo, CHSF), - - CAPTInfo = - tls_v1:create_info(<<"c ap traffic">>, CAPTHash, ssl_cipher:hash_size(HKDFAlgo)), - - CAPTrafficSecret = - tls_v1:client_application_traffic_secret_0(HKDFAlgo, {master_secret, MasterSecret}, CHSF), - - %% {server} derive secret "tls13 s ap traffic": - %% - %% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 - %% 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 - %% - %% hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a - %% 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 - %% - %% info (54 octets): 00 20 12 74 6c 73 31 33 20 73 20 61 70 20 74 72 - %% 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b - %% 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 - %% - %% expanded (32 octets): a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 - %% 50 32 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43 - - %% PRK = MasterSecret - %% hash = CAPTHash - SAPTInfo = - hexstr2bin(" 00 20 12 74 6c 73 31 33 20 73 20 61 70 20 74 72 - 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b - 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), - - SAPTrafficSecret = - hexstr2bin("a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 - 50 32 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43"), - - SAPTInfo = - tls_v1:create_info(<<"s ap traffic">>, CAPTHash, ssl_cipher:hash_size(HKDFAlgo)), - - SAPTrafficSecret = - tls_v1:server_application_traffic_secret_0(HKDFAlgo, {master_secret, MasterSecret}, CHSF), - - %% {server} derive secret "tls13 exp master": - %% - %% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 - %% 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 - %% - %% hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a - %% 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 - %% - %% info (52 octets): 00 20 10 74 6c 73 31 33 20 65 78 70 20 6d 61 73 - %% 74 65 72 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a 00 - %% 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 - %% - %% expanded (32 octets): fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67 - %% 92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50 - - %% PRK = MasterSecret - %% hash = CAPTHash - ExporterInfo = - hexstr2bin("00 20 10 74 6c 73 31 33 20 65 78 70 20 6d 61 73 - 74 65 72 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a 00 - 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), - - ExporterMasterSecret = - hexstr2bin("fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67 - 92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50"), - - ExporterInfo = - tls_v1:create_info(<<"exp master">>, CAPTHash, ssl_cipher:hash_size(HKDFAlgo)), - - ExporterMasterSecret = - tls_v1:exporter_master_secret(HKDFAlgo, {master_secret, MasterSecret}, CHSF), - - %% {server} derive write traffic keys for application data: - %% - %% PRK (32 octets): a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 50 32 - %% 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43 - %% - %% key info (13 octets): 00 10 09 74 6c 73 31 33 20 6b 65 79 00 - %% - %% key expanded (16 octets): 9f 02 28 3b 6c 9c 07 ef c2 6b b9 f2 ac - %% 92 e3 56 - %% - %% iv info (12 octets): 00 0c 08 74 6c 73 31 33 20 69 76 00 - %% - %% iv expanded (12 octets): cf 78 2b 88 dd 83 54 9a ad f1 e9 84 - - %% PRK = SAPTrafficsecret - %% key info = WriteKeyInfo - %% iv info = WrtieIVInfo - SWKey = - hexstr2bin("9f 02 28 3b 6c 9c 07 ef c2 6b b9 f2 ac 92 e3 56"), - - SWIV = - hexstr2bin("cf 78 2b 88 dd 83 54 9a ad f1 e9 84"), - - {SWKey, SWIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, SAPTrafficSecret), - - %% {server} derive read traffic keys for handshake data: - %% - %% PRK (32 octets): b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e 2d 8f - %% 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21 - %% - %% key info (13 octets): 00 10 09 74 6c 73 31 33 20 6b 65 79 00 - %% - %% key expanded (16 octets): db fa a6 93 d1 76 2c 5b 66 6a f5 d9 50 - %% 25 8d 01 - %% - %% iv info (12 octets): 00 0c 08 74 6c 73 31 33 20 69 76 00 - %% - %% iv expanded (12 octets): 5b d3 c7 1b 83 6e 0b 76 bb 73 26 5f - - %% PRK = CHSTrafficsecret - %% key info = WriteKeyInfo - %% iv info = WrtieIVInfo - SRKey = - hexstr2bin("db fa a6 93 d1 76 2c 5b 66 6a f5 d9 50 25 8d 01"), - - SRIV = - hexstr2bin("5b d3 c7 1b 83 6e 0b 76 bb 73 26 5f"), - - {SRKey, SRIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, CHSTrafficSecret). - - -tls13_finished_verify_data() -> - [{doc,"Test TLS 1.3 Finished message handling"}]. - -tls13_finished_verify_data(_Config) -> - ClientHello = - hexstr2bin("01 00 00 c6 03 03 00 01 02 03 04 05 06 07 08 09 - 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 - 1a 1b 1c 1d 1e 1f 20 e0 e1 e2 e3 e4 e5 e6 e7 e8 - e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 - f9 fa fb fc fd fe ff 00 06 13 01 13 02 13 03 01 - 00 00 77 00 00 00 18 00 16 00 00 13 65 78 61 6d - 70 6c 65 2e 75 6c 66 68 65 69 6d 2e 6e 65 74 00 - 0a 00 08 00 06 00 1d 00 17 00 18 00 0d 00 14 00 - 12 04 03 08 04 04 01 05 03 08 05 05 01 08 06 06 - 01 02 01 00 33 00 26 00 24 00 1d 00 20 35 80 72 - d6 36 58 80 d1 ae ea 32 9a df 91 21 38 38 51 ed - 21 a2 8e 3b 75 e9 65 d0 d2 cd 16 62 54 00 2d 00 - 02 01 01 00 2b 00 03 02 03 04"), - - ServerHello = - hexstr2bin("02 00 00 76 03 03 70 71 72 73 74 75 76 77 78 79 - 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 - 8a 8b 8c 8d 8e 8f 20 e0 e1 e2 e3 e4 e5 e6 e7 e8 - e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 - f9 fa fb fc fd fe ff 13 01 00 00 2e 00 33 00 24 - 00 1d 00 20 9f d7 ad 6d cf f4 29 8d d3 f9 6d 5b - 1b 2a f9 10 a0 53 5b 14 88 d7 f8 fa bb 34 9a 98 - 28 80 b6 15 00 2b 00 02 03 04"), - - EncryptedExtensions = - hexstr2bin("08 00 00 02 00 00"), - - Certificate = - hexstr2bin("0b 00 03 2e 00 00 03 2a 00 03 25 30 82 03 21 30 - 82 02 09 a0 03 02 01 02 02 08 15 5a 92 ad c2 04 - 8f 90 30 0d 06 09 2a 86 48 86 f7 0d 01 01 0b 05 - 00 30 22 31 0b 30 09 06 03 55 04 06 13 02 55 53 - 31 13 30 11 06 03 55 04 0a 13 0a 45 78 61 6d 70 - 6c 65 20 43 41 30 1e 17 0d 31 38 31 30 30 35 30 - 31 33 38 31 37 5a 17 0d 31 39 31 30 30 35 30 31 - 33 38 31 37 5a 30 2b 31 0b 30 09 06 03 55 04 06 - 13 02 55 53 31 1c 30 1a 06 03 55 04 03 13 13 65 - 78 61 6d 70 6c 65 2e 75 6c 66 68 65 69 6d 2e 6e - 65 74 30 82 01 22 30 0d 06 09 2a 86 48 86 f7 0d - 01 01 01 05 00 03 82 01 0f 00 30 82 01 0a 02 82 - 01 01 00 c4 80 36 06 ba e7 47 6b 08 94 04 ec a7 - b6 91 04 3f f7 92 bc 19 ee fb 7d 74 d7 a8 0d 00 - 1e 7b 4b 3a 4a e6 0f e8 c0 71 fc 73 e7 02 4c 0d - bc f4 bd d1 1d 39 6b ba 70 46 4a 13 e9 4a f8 3d - f3 e1 09 59 54 7b c9 55 fb 41 2d a3 76 52 11 e1 - f3 dc 77 6c aa 53 37 6e ca 3a ec be c3 aa b7 3b - 31 d5 6c b6 52 9c 80 98 bc c9 e0 28 18 e2 0b f7 - f8 a0 3a fd 17 04 50 9e ce 79 bd 9f 39 f1 ea 69 - ec 47 97 2e 83 0f b5 ca 95 de 95 a1 e6 04 22 d5 - ee be 52 79 54 a1 e7 bf 8a 86 f6 46 6d 0d 9f 16 - 95 1a 4c f7 a0 46 92 59 5c 13 52 f2 54 9e 5a fb - 4e bf d7 7a 37 95 01 44 e4 c0 26 87 4c 65 3e 40 - 7d 7d 23 07 44 01 f4 84 ff d0 8f 7a 1f a0 52 10 - d1 f4 f0 d5 ce 79 70 29 32 e2 ca be 70 1f df ad - 6b 4b b7 11 01 f4 4b ad 66 6a 11 13 0f e2 ee 82 - 9e 4d 02 9d c9 1c dd 67 16 db b9 06 18 86 ed c1 - ba 94 21 02 03 01 00 01 a3 52 30 50 30 0e 06 03 - 55 1d 0f 01 01 ff 04 04 03 02 05 a0 30 1d 06 03 - 55 1d 25 04 16 30 14 06 08 2b 06 01 05 05 07 03 - 02 06 08 2b 06 01 05 05 07 03 01 30 1f 06 03 55 - 1d 23 04 18 30 16 80 14 89 4f de 5b cc 69 e2 52 - cf 3e a3 00 df b1 97 b8 1d e1 c1 46 30 0d 06 09 - 2a 86 48 86 f7 0d 01 01 0b 05 00 03 82 01 01 00 - 59 16 45 a6 9a 2e 37 79 e4 f6 dd 27 1a ba 1c 0b - fd 6c d7 55 99 b5 e7 c3 6e 53 3e ff 36 59 08 43 - 24 c9 e7 a5 04 07 9d 39 e0 d4 29 87 ff e3 eb dd - 09 c1 cf 1d 91 44 55 87 0b 57 1d d1 9b df 1d 24 - f8 bb 9a 11 fe 80 fd 59 2b a0 39 8c de 11 e2 65 - 1e 61 8c e5 98 fa 96 e5 37 2e ef 3d 24 8a fd e1 - 74 63 eb bf ab b8 e4 d1 ab 50 2a 54 ec 00 64 e9 - 2f 78 19 66 0d 3f 27 cf 20 9e 66 7f ce 5a e2 e4 - ac 99 c7 c9 38 18 f8 b2 51 07 22 df ed 97 f3 2e - 3e 93 49 d4 c6 6c 9e a6 39 6d 74 44 62 a0 6b 42 - c6 d5 ba 68 8e ac 3a 01 7b dd fc 8e 2c fc ad 27 - cb 69 d3 cc dc a2 80 41 44 65 d3 ae 34 8c e0 f3 - 4a b2 fb 9c 61 83 71 31 2b 19 10 41 64 1c 23 7f - 11 a5 d6 5c 84 4f 04 04 84 99 38 71 2b 95 9e d6 - 85 bc 5c 5d d6 45 ed 19 90 94 73 40 29 26 dc b4 - 0e 34 69 a1 59 41 e8 e2 cc a8 4b b6 08 46 36 a0 - 00 00"), - - CertificateVerify = - hexstr2bin("0f 00 01 04 08 04 01 00 17 fe b5 33 ca 6d 00 7d - 00 58 25 79 68 42 4b bc 3a a6 90 9e 9d 49 55 75 - 76 a5 20 e0 4a 5e f0 5f 0e 86 d2 4f f4 3f 8e b8 - 61 ee f5 95 22 8d 70 32 aa 36 0f 71 4e 66 74 13 - 92 6e f4 f8 b5 80 3b 69 e3 55 19 e3 b2 3f 43 73 - df ac 67 87 06 6d cb 47 56 b5 45 60 e0 88 6e 9b - 96 2c 4a d2 8d ab 26 ba d1 ab c2 59 16 b0 9a f2 - 86 53 7f 68 4f 80 8a ef ee 73 04 6c b7 df 0a 84 - fb b5 96 7a ca 13 1f 4b 1c f3 89 79 94 03 a3 0c - 02 d2 9c bd ad b7 25 12 db 9c ec 2e 5e 1d 00 e5 - 0c af cf 6f 21 09 1e bc 4f 25 3c 5e ab 01 a6 79 - ba ea be ed b9 c9 61 8f 66 00 6b 82 44 d6 62 2a - aa 56 88 7c cf c6 6a 0f 38 51 df a1 3a 78 cf f7 - 99 1e 03 cb 2c 3a 0e d8 7d 73 67 36 2e b7 80 5b - 00 b2 52 4f f2 98 a4 da 48 7c ac de af 8a 23 36 - c5 63 1b 3e fa 93 5b b4 11 e7 53 ca 13 b0 15 fe - c7 e4 a7 30 f1 36 9f 9e"), - - BaseKey = - hexstr2bin("a2 06 72 65 e7 f0 65 2a 92 3d 5d 72 ab 04 67 c4 - 61 32 ee b9 68 b6 a3 2d 31 1c 80 58 68 54 88 14"), - - VerifyData = - hexstr2bin("ea 6e e1 76 dc cc 4a f1 85 9e 9e 4e 93 f7 97 ea - c9 a7 8c e4 39 30 1e 35 27 5a d4 3f 3c dd bd e3"), - - Messages = [CertificateVerify, - Certificate, - EncryptedExtensions, - ServerHello, - ClientHello], - - FinishedKey = tls_v1:finished_key(BaseKey, sha256), - VerifyData = tls_v1:finished_verify_data(FinishedKey, sha256, Messages). - -tls13_basic_ssl_server_openssl_client() -> - [{doc,"Test TLS 1.3 basic connection between ssl server and openssl s_client"}]. - -tls13_basic_ssl_server_openssl_client(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - -tls13_custom_groups_ssl_server_openssl_client() -> - [{doc,"Test that ssl server can select a common group for key-exchange"}]. - -tls13_custom_groups_ssl_server_openssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {supported_groups, [x448, secp256r1, secp384r1]}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - ClientOpts = [{groups,"P-384:P-256:X25519"}|ClientOpts0], - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - -tls13_hello_retry_request_ssl_server_openssl_client() -> - [{doc,"Test that ssl server can request a new group when the client's first key share" - "is not supported"}]. - -tls13_hello_retry_request_ssl_server_openssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {supported_groups, [x448, x25519]}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - ClientOpts = [{groups,"P-256:X25519"}|ClientOpts0], - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - -tls13_client_auth_empty_cert_alert_ssl_server_openssl_client() -> - [{doc,"TLS 1.3: Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to true."}]. - -tls13_client_auth_empty_cert_alert_ssl_server_openssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - %% Delete Client Cert and Key - ClientOpts1 = proplists:delete(certfile, ClientOpts0), - ClientOpts = proplists:delete(keyfile, ClientOpts1), - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - {fail_if_no_peer_cert, true}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result(Server, - {error, - {tls_alert, - {certificate_required, - "received SERVER ALERT: Fatal - Certificate required - certificate_required"}}}), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - -tls13_client_auth_empty_cert_ssl_server_openssl_client() -> - [{doc,"TLS 1.3: Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to false."}]. - -tls13_client_auth_empty_cert_ssl_server_openssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - %% Delete Client Cert and Key - ClientOpts1 = proplists:delete(certfile, ClientOpts0), - ClientOpts = proplists:delete(keyfile, ClientOpts1), - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - {fail_if_no_peer_cert, false}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_client_auth_ssl_server_openssl_client() -> - [{doc,"TLS 1.3: Test client authentication."}]. - -tls13_client_auth_ssl_server_openssl_client(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - {fail_if_no_peer_cert, true}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_hrr_client_auth_empty_cert_alert_ssl_server_openssl_client() -> - [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to true."}]. - -tls13_hrr_client_auth_empty_cert_alert_ssl_server_openssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - %% Delete Client Cert and Key - ClientOpts1 = proplists:delete(certfile, ClientOpts0), - ClientOpts2 = proplists:delete(keyfile, ClientOpts1), - ClientOpts = [{groups,"P-256:X25519"}|ClientOpts2], - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - {fail_if_no_peer_cert, true}, - {supported_groups, [x448, x25519]}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result(Server, - {error, - {tls_alert, - {certificate_required, - "received SERVER ALERT: Fatal - Certificate required - certificate_required"}}}), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_hrr_client_auth_empty_cert_ssl_server_openssl_client() -> - [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to false."}]. - -tls13_hrr_client_auth_empty_cert_ssl_server_openssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - %% Delete Client Cert and Key - ClientOpts1 = proplists:delete(certfile, ClientOpts0), - ClientOpts2 = proplists:delete(keyfile, ClientOpts1), - ClientOpts = [{groups,"P-256:X25519"}|ClientOpts2], - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - {fail_if_no_peer_cert, false}, - {supported_groups, [x448, x25519]}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_hrr_client_auth_ssl_server_openssl_client() -> - [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication."}]. - -tls13_hrr_client_auth_ssl_server_openssl_client(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ClientOpts = [{groups,"P-256:X25519"}|ClientOpts0], - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - {fail_if_no_peer_cert, true}, - {supported_groups, [x448, x25519]}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_unsupported_sign_algo_client_auth_ssl_server_openssl_client() -> - [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm"}]. - -tls13_unsupported_sign_algo_client_auth_ssl_server_openssl_client(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {verify, verify_peer}, - %% Skip rsa_pkcs1_sha256! - {signature_algs, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, - {fail_if_no_peer_cert, true}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result( - Server, - {error, - {tls_alert, - {insufficient_security, - "received SERVER ALERT: Fatal - Insufficient Security - " - "\"No suitable signature algorithm\""}}}), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -%% Triggers Client Alert as openssl s_client does not have a certificate with a -%% signature algorithm supported by the server (signature_algorithms_cert extension -%% of CertificateRequest does not contain the algorithm of the client certificate). -tls13_unsupported_sign_algo_cert_client_auth_ssl_server_openssl_client() -> - [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm_cert"}]. - -tls13_unsupported_sign_algo_cert_client_auth_ssl_server_openssl_client(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, - {log_level, debug}, - {verify, verify_peer}, - {signature_algs, [rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pss_rsae_sha256]}, - %% Skip rsa_pkcs1_sha256! - {signature_algs_cert, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, - {fail_if_no_peer_cert, true}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result( - Server, - {error, - {tls_alert, - {illegal_parameter, - "received CLIENT ALERT: Fatal - Illegal Parameter"}}}), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -tls13_connection_information() -> - [{doc,"Test the API function ssl:connection_information/1 in a TLS 1.3 connection"}]. - -tls13_connection_information(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - %% Set versions - ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0], - {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, connection_information_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), - - ssl_test_lib:check_result(Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close_port(Client). - - -%%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- send_recv_result(Socket) -> @@ -5664,491 +463,9 @@ tcp_send_recv_result(Socket) -> {ok,"Hello world"} = gen_tcp:recv(Socket, 11), ok. -basic_verify_test_no_close(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - - {ClientNode, ServerNode, Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - {Server, Client}. - -basic_test(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -prf_create_plan(TlsVersions, PRFs, Results) -> - lists:foldl(fun(Ver, Acc) -> - A = prf_ciphers_and_expected(Ver, PRFs, Results), - [A|Acc] - end, [], TlsVersions). -prf_ciphers_and_expected(TlsVer, PRFs, Results) -> - case TlsVer of - TlsVer when TlsVer == sslv3 orelse TlsVer == tlsv1 - orelse TlsVer == 'tlsv1.1' orelse TlsVer == 'dtlsv1' -> - Ciphers = ssl:cipher_suites(), - {_, Expected} = lists:keyfind(md5sha, 1, Results), - [[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected}, {prf, md5sha}]]; - TlsVer when TlsVer == 'tlsv1.2' orelse TlsVer == 'dtlsv1.2'-> - lists:foldl( - fun(PRF, Acc) -> - Ciphers = prf_get_ciphers(TlsVer, PRF), - case Ciphers of - [] -> - ct:log("No ciphers for PRF algorithm ~p. Skipping.", [PRF]), - Acc; - Ciphers -> - {_, Expected} = lists:keyfind(PRF, 1, Results), - [[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected}, - {prf, PRF}] | Acc] - end - end, [], PRFs) - end. -prf_get_ciphers(_, PRF) -> - lists:filter( - fun(C) when tuple_size(C) == 4 andalso - element(4, C) == PRF -> - true; - (_) -> - false - end, - ssl:cipher_suites()). -prf_run_test(_, TlsVer, [], _, Prf) -> - ct:fail({error, cipher_list_empty, TlsVer, Prf}); -prf_run_test(Config, TlsVer, Ciphers, Expected, Prf) -> - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - BaseOpts = [{active, true}, {versions, [TlsVer]}, {ciphers, Ciphers}, {protocol, tls_or_dtls(TlsVer)}], - ServerOpts = BaseOpts ++ proplists:get_value(server_opts, Config), - ClientOpts = BaseOpts ++ proplists:get_value(client_opts, Config), - Server = ssl_test_lib:start_server( - [{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, prf_verify_value, [TlsVer, Expected, Prf]}}, - {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, prf_verify_value, [TlsVer, Expected, Prf]}}, - {options, ClientOpts}]), - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). -prf_verify_value(Socket, TlsVer, Expected, Algo) -> - Ret = ssl:prf(Socket, <<>>, <<>>, [<<>>], 16), - case TlsVer of - sslv3 -> - case Ret of - {error, undefined} -> ok; - _ -> - {error, {expected, {error, undefined}, - got, Ret, tls_ver, TlsVer, prf_algorithm, Algo}} - end; - _ -> - case Ret of - {ok, Expected} -> ok; - {ok, Val} -> {error, {expected, Expected, got, Val, tls_ver, TlsVer, - prf_algorithm, Algo}} - end - end. - -send_recv_result_timeout_client(Socket) -> - {error, timeout} = ssl:recv(Socket, 11, 500), - {error, timeout} = ssl:recv(Socket, 11, 0), - ssl:send(Socket, "Hello world"), - receive - Msg -> - io:format("Msg ~p~n",[Msg]) - after 500 -> - ok - end, - {ok, "Hello world"} = ssl:recv(Socket, 11, 500), - ok. -send_recv_result_timeout_server(Socket) -> - ssl:send(Socket, "Hello"), - {ok, "Hello world"} = ssl:recv(Socket, 11), - ssl:send(Socket, " world"), - ok. - -recv_close(Socket) -> - {error, closed} = ssl:recv(Socket, 11), - receive - {_,{error,closed}} -> - error_extra_close_sent_to_user_process - after 500 -> - ok - end. - - -send_recv_result_active_rizzo(Socket) -> - ssl:send(Socket, "Hello world"), - "Hello world" = ssl_test_lib:active_recv(Socket, 11), - ok. - -send_recv_result_active_no_rizzo(Socket) -> - ssl:send(Socket, "Hello world"), - "Hello world" = ssl_test_lib:active_recv(Socket, 11), - ok. - - -ssl_active_recv(N) -> - ssl_active_recv(N, []). - -ssl_active_recv(0, Acc) -> - Acc; -ssl_active_recv(N, Acc) -> - receive - {ssl, _, Bytes} -> - ssl_active_recv(N-length(Bytes), Acc ++ Bytes) - end. - result_ok(_Socket) -> ok. -renegotiate(Socket, Data) -> - ct:log("Renegotiating ~n", []), - Result = ssl:renegotiate(Socket), - ct:log("Result ~p~n", [Result]), - ssl:send(Socket, Data), - case Result of - ok -> - ok; - Other -> - Other - end. - -renegotiate_reuse_session(Socket, Data) -> - %% Make sure session is registered - ct:sleep(?SLEEP), - renegotiate(Socket, Data). - -renegotiate_immediately(Socket) -> - _ = ssl_test_lib:active_recv(Socket, 11), - ok = ssl:renegotiate(Socket), - {error, renegotiation_rejected} = ssl:renegotiate(Socket), - ct:sleep(?RENEGOTIATION_DISABLE_TIME + ?SLEEP), - ok = ssl:renegotiate(Socket), - ct:log("Renegotiated again"), - ssl:send(Socket, "Hello world"), - ok. - -renegotiate_rejected(Socket) -> - _ = ssl_test_lib:active_recv(Socket, 11), - {error, renegotiation_rejected} = ssl:renegotiate(Socket), - {error, renegotiation_rejected} = ssl:renegotiate(Socket), - ct:sleep(?RENEGOTIATION_DISABLE_TIME +1), - {error, renegotiation_rejected} = ssl:renegotiate(Socket), - ct:log("Failed to renegotiate again"), - ssl:send(Socket, "Hello world"), - ok. - -rizzo_add_mitigation_option(Value, Config) -> - lists:foldl(fun(Opt, Acc) -> - case proplists:get_value(Opt, Acc) of - undefined -> Acc; - C -> - N = lists:keystore(beast_mitigation, 1, C, - {beast_mitigation, Value}), - lists:keystore(Opt, 1, Acc, {Opt, N}) - end - end, Config, - [client_opts, client_dsa_opts, server_opts, server_dsa_opts, - server_ecdsa_opts, server_ecdh_rsa_opts]). - -new_config(PrivDir, ServerOpts0) -> - CaCertFile = proplists:get_value(cacertfile, ServerOpts0), - CertFile = proplists:get_value(certfile, ServerOpts0), - KeyFile = proplists:get_value(keyfile, ServerOpts0), - NewCaCertFile = filename:join(PrivDir, "new_ca.pem"), - NewCertFile = filename:join(PrivDir, "new_cert.pem"), - NewKeyFile = filename:join(PrivDir, "new_key.pem"), - file:copy(CaCertFile, NewCaCertFile), - file:copy(CertFile, NewCertFile), - file:copy(KeyFile, NewKeyFile), - ServerOpts1 = proplists:delete(cacertfile, ServerOpts0), - ServerOpts2 = proplists:delete(certfile, ServerOpts1), - ServerOpts = proplists:delete(keyfile, ServerOpts2), - - {ok, PEM} = file:read_file(NewCaCertFile), - ct:log("CA file content: ~p~n", [public_key:pem_decode(PEM)]), - - [{cacertfile, NewCaCertFile}, {certfile, NewCertFile}, - {keyfile, NewKeyFile} | ServerOpts]. - -session_cache_process(_Type,Config) when is_list(Config) -> - reuse_session(Config). - -init([Type]) -> - ets:new(ssl_test, [named_table, public, set]), - ets:insert(ssl_test, {type, Type}), - case Type of - list -> - spawn(fun() -> session_loop([]) end); - mnesia -> - mnesia:start(), - {atomic,ok} = mnesia:create_table(sess_cache, []), - sess_cache - end. - -session_cb() -> - [{type, Type}] = ets:lookup(ssl_test, type), - Type. - -terminate(Cache) -> - case session_cb() of - list -> - Cache ! terminate; - mnesia -> - catch {atomic,ok} = - mnesia:delete_table(sess_cache) - end. - -lookup(Cache, Key) -> - case session_cb() of - list -> - Cache ! {self(), lookup, Key}, - receive {Cache, Res} -> Res end; - mnesia -> - case mnesia:transaction(fun() -> - mnesia:read(sess_cache, - Key, read) - end) of - {atomic, [{sess_cache, Key, Value}]} -> - Value; - _ -> - undefined - end - end. - -update(Cache, Key, Value) -> - case session_cb() of - list -> - Cache ! {update, Key, Value}; - mnesia -> - {atomic, ok} = - mnesia:transaction(fun() -> - mnesia:write(sess_cache, - {sess_cache, Key, Value}, write) - end) - end. - -delete(Cache, Key) -> - case session_cb() of - list -> - Cache ! {delete, Key}; - mnesia -> - {atomic, ok} = - mnesia:transaction(fun() -> - mnesia:delete(sess_cache, Key) - end) - end. - -foldl(Fun, Acc, Cache) -> - case session_cb() of - list -> - Cache ! {self(),foldl,Fun,Acc}, - receive {Cache, Res} -> Res end; - mnesia -> - Foldl = fun() -> - mnesia:foldl(Fun, Acc, sess_cache) - end, - {atomic, Res} = mnesia:transaction(Foldl), - Res - end. - -select_session(Cache, PartialKey) -> - case session_cb() of - list -> - Cache ! {self(),select_session, PartialKey}, - receive - {Cache, Res} -> - Res - end; - mnesia -> - Sel = fun() -> - mnesia:select(Cache, - [{{sess_cache,{PartialKey,'$1'}, '$2'}, - [],['$$']}]) - end, - {atomic, Res} = mnesia:transaction(Sel), - Res - end. - -session_loop(Sess) -> - receive - terminate -> - ok; - {Pid, lookup, Key} -> - case lists:keysearch(Key,1,Sess) of - {value, {Key,Value}} -> - Pid ! {self(), Value}; - _ -> - Pid ! {self(), undefined} - end, - session_loop(Sess); - {update, Key, Value} -> - NewSess = [{Key,Value}| lists:keydelete(Key,1,Sess)], - session_loop(NewSess); - {delete, Key} -> - session_loop(lists:keydelete(Key,1,Sess)); - {Pid,foldl,Fun,Acc} -> - Res = lists:foldl(Fun, Acc,Sess), - Pid ! {self(), Res}, - session_loop(Sess); - {Pid,select_session,PKey} -> - Sel = fun({{PKey0, Id},Session}, Acc) when PKey == PKey0 -> - [[Id, Session]|Acc]; - (_,Acc) -> - Acc - end, - Sessions = lists:foldl(Sel, [], Sess), - Pid ! {self(), Sessions}, - session_loop(Sess) - end. - - -erlang_ssl_receive(Socket, Data) -> - case ssl_test_lib:active_recv(Socket, length(Data)) of - Data -> - ok; - Other -> - ct:fail({{expected, Data}, {got, Other}}) - end. - -receive_msg(_) -> - receive - Msg -> - Msg - end. - -controlling_process_result(Socket, Pid, Msg) -> - ok = ssl:controlling_process(Socket, Pid), - %% Make sure other side has evaluated controlling_process - %% before message is sent - ct:sleep(?SLEEP), - ssl:send(Socket, Msg), - no_result_msg. - - -controller_dies_result(_Socket, _Pid, _Msg) -> - receive Result -> Result end. - -get_close(Pid, Where) -> - receive - {'EXIT', Pid, _Reason} -> - receive - {_, {ssl_closed, Socket}} -> - ct:log("Socket closed ~p~n",[Socket]); - Unexpected -> - ct:log("Unexpected ~p~n",[Unexpected]), - ct:fail({line, ?LINE-1}) - after 5000 -> - ct:fail({timeout, {line, ?LINE, Where}}) - end; - Unexpected -> - ct:log("Unexpected ~p~n",[Unexpected]), - ct:fail({line, ?LINE-1}) - after 5000 -> - ct:fail({timeout, {line, ?LINE, Where}}) - end. - -run_send_recv_rizzo(Ciphers, Config, Version, Mfa) -> - Result = lists:map(fun(Cipher) -> - rizzo_test(Cipher, Config, Version, Mfa) end, - Ciphers), - case lists:flatten(Result) of - [] -> - ok; - Error -> - ct:log("Cipher suite errors: ~p~n", [Error]), - ct:fail(cipher_suite_failed_see_test_case_log) - end. - -rizzo_test(Cipher, Config, Version, Mfa) -> - {ClientOpts, ServerOpts} = client_server_opts(Cipher, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, Mfa}, - {options, [{active, true}, {ciphers, [Cipher]}, - {versions, [Version]} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, Mfa}, - {options, [{active, true}, {ciphers, [Cipher]}| ClientOpts]}]), - - Result = ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - case Result of - ok -> - []; - Error -> - [{Cipher, Error}] - end. - -client_server_opts(#{key_exchange := KeyAlgo}, Config) - when KeyAlgo == rsa orelse - KeyAlgo == dhe_rsa orelse - KeyAlgo == ecdhe_rsa orelse - KeyAlgo == rsa_psk orelse - KeyAlgo == srp_rsa -> - {ssl_test_lib:ssl_options(client_opts, Config), - ssl_test_lib:ssl_options(server_opts, Config)}; -client_server_opts(#{key_exchange := KeyAlgo}, Config) when KeyAlgo == dss orelse KeyAlgo == dhe_dss -> - {ssl_test_lib:ssl_options(client_dsa_opts, Config), - ssl_test_lib:ssl_options(server_dsa_opts, Config)}; -client_server_opts(#{key_exchange := KeyAlgo}, Config) when KeyAlgo == ecdh_ecdsa orelse KeyAlgo == ecdhe_ecdsa -> - {ssl_test_lib:ssl_options(client_opts, Config), - ssl_test_lib:ssl_options(server_ecdsa_opts, Config)}; -client_server_opts(#{key_exchange := KeyAlgo}, Config) when KeyAlgo == ecdh_rsa -> - {ssl_test_lib:ssl_options(client_opts, Config), - ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)}. - -connection_information_result(Socket) -> - {ok, Info = [_ | _]} = ssl:connection_information(Socket), - case length(Info) > 3 of - true -> - %% Atleast one ssl_option() is set - ct:log("Info ~p", [Info]), - ok; - false -> - ct:fail(no_ssl_options_returned) - end. - -connection_info_result(Socket) -> - {ok, Info} = ssl:connection_information(Socket, [protocol, selected_cipher_suite]), - {ok, {proplists:get_value(protocol, Info), proplists:get_value(selected_cipher_suite, Info)}}. protocol_info_result(Socket) -> {ok, [{protocol, PVersion}]} = ssl:connection_information(Socket, [protocol]), @@ -6158,11 +475,7 @@ version_info_result(Socket) -> {ok, [{version, Version}]} = ssl:connection_information(Socket, [version]), {ok, Version}. -secret_connection_info_result(Socket) -> - {ok, [{client_random, ClientRand}, {server_random, ServerRand}, {master_secret, MasterSecret}]} - = ssl:connection_information(Socket, [client_random, server_random, master_secret]), - is_binary(ClientRand) andalso is_binary(ServerRand) andalso is_binary(MasterSecret). - + connect_dist_s(S) -> Msg = term_to_binary({erlang,term}), ok = ssl:send(S, Msg). @@ -6172,88 +485,11 @@ connect_dist_c(S) -> {ok, Test} = ssl:recv(S, 0, 10000), ok. -tls_downgrade_result(Socket, Pid) -> - ok = ssl_test_lib:send_recv_result(Socket), - Pid ! {self(), ready}, - receive - go -> - ok - end, - case ssl:close(Socket, {self(), 10000}) of - {ok, TCPSocket} -> - inet:setopts(TCPSocket, [{active, true}]), - gen_tcp:send(TCPSocket, "Downgraded"), - receive - {tcp, TCPSocket, <<"Downgraded">>} -> - ok; - {tcp_closed, TCPSocket} -> - ct:fail("Peer timed out, downgrade aborted"), - ok; - Other -> - {error, Other} - end; - {error, timeout} -> - ct:fail("Timed out, downgrade aborted"), - ok; - Fail -> - {error, Fail} - end. - -tls_close(Socket) -> - ok = ssl_test_lib:send_recv_result(Socket), - case ssl:close(Socket, 5000) of - ok -> - ok; - {error, closed} -> - ok; - Other -> - ct:fail(Other) - end. - - %% First two clauses handles 1/n-1 splitting countermeasure Rizzo/Duong-Beast -treashold(N, {3,0}) -> - (N div 2) + 1; -treashold(N, {3,1}) -> - (N div 2) + 1; -treashold(N, _) -> - N + 1. - -get_invalid_inet_option(Socket) -> - {error, {options, {socket_options, foo, _}}} = ssl:getopts(Socket, [foo]), - ok. - -tls_shutdown_result(Socket, server) -> - ssl:send(Socket, "Hej"), - ok = ssl:shutdown(Socket, write), - {ok, "Hej hopp"} = ssl:recv(Socket, 8), - ok; - -tls_shutdown_result(Socket, client) -> - ssl:send(Socket, "Hej hopp"), - ok = ssl:shutdown(Socket, write), - {ok, "Hej"} = ssl:recv(Socket, 3), - ok. - -tls_shutdown_write_result(Socket, server) -> - ct:sleep(?SLEEP), - ssl:shutdown(Socket, write); -tls_shutdown_write_result(Socket, client) -> - ssl:recv(Socket, 0). - dummy(_Socket) -> %% Should not happen as the ssl connection will not be established %% due to fatal handshake failiure exit(kill). -tls_shutdown_both_result(Socket, server) -> - ct:sleep(?SLEEP), - ssl:shutdown(Socket, read_write); -tls_shutdown_both_result(Socket, client) -> - ssl:recv(Socket, 0). - -peername_result(S) -> - ssl:peername(S). - version_option_test(Config, Version) -> ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), @@ -6279,50 +515,3 @@ version_option_test(Config, Version) -> ssl_test_lib:close(Server), ssl_test_lib:close(Client). -try_recv_active(Socket) -> - ssl:send(Socket, "Hello world"), - {error, einval} = ssl:recv(Socket, 11), - ok. -try_recv_active_once(Socket) -> - {error, einval} = ssl:recv(Socket, 11), - ok. - - -wait_for_send(Socket) -> - %% Make sure TLS process processed send message event - _ = ssl:connection_information(Socket). - -tls_or_dtls('dtlsv1') -> - dtls; -tls_or_dtls('dtlsv1.2') -> - dtls; -tls_or_dtls(_) -> - tls. - -hexstr2int(S) -> - B = hexstr2bin(S), - Bits = size(B) * 8, - <<Integer:Bits/integer>> = B, - Integer. - -hexstr2bin(S) when is_binary(S) -> - hexstr2bin(S, <<>>); -hexstr2bin(S) -> - hexstr2bin(list_to_binary(S), <<>>). -%% -hexstr2bin(<<>>, Acc) -> - Acc; -hexstr2bin(<<C,T/binary>>, Acc) when C =:= 32; %% SPACE - C =:= 10; %% LF - C =:= 13 -> %% CR - hexstr2bin(T, Acc); -hexstr2bin(<<X,Y,T/binary>>, Acc) -> - I = hex2int(X) * 16 + hex2int(Y), - hexstr2bin(T, <<Acc/binary,I>>). - -hex2int(C) when $0 =< C, C =< $9 -> - C - $0; -hex2int(C) when $A =< C, C =< $F -> - C - $A + 10; -hex2int(C) when $a =< C, C =< $f -> - C - $a + 10. diff --git a/lib/ssl/test/ssl_bench_SUITE.erl b/lib/ssl/test/ssl_bench_SUITE.erl index 35efa2b8a3..a297539c36 100644 --- a/lib/ssl/test/ssl_bench_SUITE.erl +++ b/lib/ssl/test/ssl_bench_SUITE.erl @@ -174,7 +174,12 @@ do_test(Type, TC, Loop, ParallellConnections, Server) -> end, {TimeInMicro, _} = timer:tc(Run), TotalTests = ParallellConnections * Loop, - TestPerSecond = 1000000 * TotalTests div TimeInMicro, + TestPerSecond = case TimeInMicro of + 0 -> + undefined; + _ -> + 1000000 * TotalTests div TimeInMicro + end, io:format("TC ~p ~p ~p ~p 1/s~n", [TC, Type, ParallellConnections, TestPerSecond]), unlink(SPid), SPid ! quit, diff --git a/lib/ssl/test/ssl_cert_SUITE.erl b/lib/ssl/test/ssl_cert_SUITE.erl new file mode 100644 index 0000000000..fb1695f38a --- /dev/null +++ b/lib/ssl/test/ssl_cert_SUITE.erl @@ -0,0 +1,563 @@ +%% +%% %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(ssl_cert_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() -> + [ + {group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +groups() -> + [ + {'tlsv1.3', [], tls_1_3_protocol_groups()}, + {'tlsv1.2', [], pre_tls_1_3_protocol_groups()}, + {'tlsv1.1', [], pre_tls_1_3_protocol_groups()}, + {'tlsv1', [], pre_tls_1_3_protocol_groups()}, + {'sslv3', [], ssl_protocol_groups()}, + {'dtlsv1.2', [], pre_tls_1_3_protocol_groups()}, + {'dtlsv1', [], pre_tls_1_3_protocol_groups()}, + {rsa, [], all_version_tests()}, + {ecdsa, [], all_version_tests()}, + {dsa, [], all_version_tests()}, + {rsa_1_3, [], all_version_tests() ++ tls_1_3_tests() ++ [unsupported_sign_algo_client_auth, + unsupported_sign_algo_cert_client_auth]}, + {ecdsa_1_3, [], all_version_tests() ++ tls_1_3_tests()} + ]. + +ssl_protocol_groups() -> + [{group, rsa}, + {group, dsa}]. + +pre_tls_1_3_protocol_groups() -> + [{group, rsa}, + {group, ecdsa}, + {group, dsa}]. + +tls_1_3_protocol_groups() -> + [{group, rsa_1_3}, + {group, ecdsa_1_3}]. + +tls_1_3_tests() -> + [ + hello_retry_request, + custom_groups, + hello_retry_client_auth, + hello_retry_client_auth_empty_cert_accepted, + hello_retry_client_auth_empty_cert_rejected + ]. + +all_version_tests() -> + [ + no_auth, + auth, + client_auth_empty_cert_accepted, + client_auth_empty_cert_rejected, + client_auth_partial_chain, + client_auth_allow_partial_chain, + client_auth_do_not_allow_partial_chain, + client_auth_partial_chain_fun_fail, + missing_root_cert_no_auth, + missing_root_cert_auth, + missing_root_cert_auth_user_verify_fun_accept, + missing_root_cert_auth_user_verify_fun_reject, + verify_fun_always_run_client, + verify_fun_always_run_server, + incomplete_chain_auth + %%invalid_signature_client + ]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + Config + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + +init_per_group(Group, Config0) when Group == rsa; + Group == rsa_1_3 -> + Config = ssl_test_lib:make_rsa_cert(Config0), + COpts = proplists:get_value(client_rsa_opts, Config), + SOpts = proplists:get_value(server_rsa_opts, Config), + [{cert_key_alg, rsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, COpts}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; +init_per_group(Group, Config0) when Group == ecdsa; + Group == ecdsa_1_3 -> + + PKAlg = crypto:supports(public_keys), + case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse lists:member(dh, PKAlg)) of + true -> + Config = ssl_test_lib:make_ecdsa_cert(Config0), + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + [{cert_key_alg, ecdsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, COpts}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))] + )]; + false -> + {skip, "Missing EC crypto support"} + end; + +init_per_group(Group, Config0) when Group == dsa -> + PKAlg = crypto:supports(public_keys), + case lists:member(dss, PKAlg) andalso lists:member(dh, PKAlg) of + true -> + Config = ssl_test_lib:make_dsa_cert(Config0), + COpts = proplists:get_value(client_dsa_opts, Config), + SOpts = proplists:get_value(server_dsa_opts, Config), + [{cert_key_alg, dsa} | + lists:delete(cert_key_alg, + [{client_cert_opts, COpts}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + false -> + {skip, "Missing DSS crypto support"} + end; +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + [{client_type, erlang}, + {server_type, erlang}, {version, GroupName} + | ssl_test_lib:init_tls_version(GroupName, Config)]; + false -> + {skip, "Missing crypto support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +no_auth() -> + ssl_cert_tests:no_auth(). + +no_auth(Config) -> + ssl_cert_tests:no_auth(Config). +%%-------------------------------------------------------------------- +auth() -> + ssl_cert_tests:auth(). +auth(Config) -> + ssl_cert_tests:auth(Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_accepted() -> + ssl_cert_tests:client_auth_empty_cert_accepted(). +client_auth_empty_cert_accepted(Config) -> + ssl_cert_tests:client_auth_empty_cert_accepted(Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_rejected() -> + ssl_cert_tests:client_auth_empty_cert_rejected(). +client_auth_empty_cert_rejected(Config) -> + ssl_cert_tests:client_auth_empty_cert_rejected(Config). +%%-------------------------------------------------------------------- +client_auth_partial_chain() -> + ssl_cert_tests:client_auth_partial_chain(). +client_auth_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_partial_chain(Config). + +%%-------------------------------------------------------------------- +client_auth_allow_partial_chain() -> + ssl_cert_tests:client_auth_allow_partial_chain(). +client_auth_allow_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_allow_partial_chain(Config). +%%-------------------------------------------------------------------- +client_auth_do_not_allow_partial_chain() -> + ssl_cert_tests:client_auth_do_not_allow_partial_chain(). +client_auth_do_not_allow_partial_chain(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_do_not_allow_partial_chain(Config). + +%%-------------------------------------------------------------------- +client_auth_partial_chain_fun_fail() -> + ssl_cert_tests:client_auth_partial_chain_fun_fail(). +client_auth_partial_chain_fun_fail(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_partial_chain_fun_fail(Config). + +%%-------------------------------------------------------------------- +missing_root_cert_no_auth() -> + ssl_cert_tests:missing_root_cert_no_auth(). +missing_root_cert_no_auth(Config) when is_list(Config) -> + ssl_cert_tests:missing_root_cert_no_auth(Config). + +%%-------------------------------------------------------------------- +missing_root_cert_auth() -> + [{doc,"Must have ROOT certs to be able to verify verify peer"}]. +missing_root_cert_auth(Config) when is_list(Config) -> + ServerOpts = proplists:delete(cacertfile, ssl_test_lib:ssl_options(server_cert_opts, Config)), + {ClientNode, ServerNode, _} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, [{verify, verify_peer} + | ServerOpts]}]), + + ssl_test_lib:check_result(Server, {error, {options, {cacertfile, ""}}}), + + ClientOpts = proplists:delete(cacertfile, ssl_test_lib:ssl_options(client_cert_opts, Config)), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0}, + {from, self()}, + {options, [{verify, verify_peer} + | ClientOpts]}]), + + ssl_test_lib:check_result(Client, {error, {options, {cacertfile, ""}}}). + +%%-------------------------------------------------------------------- +missing_root_cert_auth_user_verify_fun_accept() -> + [{doc, "Test that the client succeds if the ROOT CA is unknown in verify_peer mode" + " with a verify_fun that accepts the unknown CA error"}]. + +missing_root_cert_auth_user_verify_fun_accept(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config), + FunAndState = {fun(_,{bad_cert, unknown_ca}, UserState) -> + {valid, UserState}; + (_,{bad_cert, _} = Reason, _) -> + {fail, Reason}; + (_,{extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, UserState) -> + {valid, UserState}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, []}, + ClientOpts = ssl_test_lib:ssl_options([{verify, verify_peer}, + {verify_fun, FunAndState}], Config), + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +missing_root_cert_auth_user_backwardscompatibility_verify_fun_accept() -> + [{doc, "Test old style verify fun"}]. + +missing_root_cert_auth_user_backwardscompatibility_verify_fun_accept(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config), + AcceptBadCa = fun({bad_cert,unknown_ca}, Acc) -> Acc; + (Other, Acc) -> [Other | Acc] + end, + VerifyFun = + fun(ErrorList) -> + case lists:foldl(AcceptBadCa, [], ErrorList) of + [] -> true; + [_|_] -> false + end + end, + + ClientOpts = ssl_test_lib:ssl_options([{verify, verify_peer}, + {verify_fun, VerifyFun}], Config), + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +missing_root_cert_auth_user_verify_fun_reject() -> + [{doc, "Test that the client fails if the ROOT CA is unknown in verify_peer mode" + " with a verify_fun that rejects the unknown CA error"}]. + +missing_root_cert_auth_user_verify_fun_reject(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config), + FunAndState = {fun(_,{bad_cert, unknown_ca} = Reason, _UserState) -> + {fail, Reason}; + (_,{bad_cert, _} = Reason, _) -> + {fail, Reason}; + (_,{extension, UserState}, _) -> + {unknown, UserState}; + (_, valid, UserState) -> + {valid, UserState}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, []}, + ClientOpts = ssl_test_lib:ssl_options([{verify, verify_peer}, + {verify_fun, FunAndState}], Config), + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca). +%%-------------------------------------------------------------------- +incomplete_chain_auth() -> + [{doc,"Test that we can verify an incompleat chain when we have the certs to rebuild it"}]. +incomplete_chain_auth(Config) when is_list(Config) -> + DefaultCertConf = ssl_test_lib:default_cert_chain_conf(), + #{client_config := ClientOpts0, + server_config := ServerOpts0} = ssl_test_lib:make_cert_chains_der(proplists:get_value(cert_key_alg, Config), + [{server_chain, DefaultCertConf}, + {client_chain, DefaultCertConf}]), + [ServerRoot| _] = ServerCas = proplists:get_value(cacerts, ServerOpts0), + ClientCas = proplists:get_value(cacerts, ClientOpts0), + ClientOpts = ssl_test_lib:ssl_options([{verify, verify_peer}, + {cacerts, ServerCas ++ ClientCas} | + proplists:delete(cacerts, ClientOpts0)], Config), + ServerOpts = ssl_test_lib:ssl_options([{verify, verify_peer}, + {cacerts, [ServerRoot]} | + proplists:delete(cacerts, ServerOpts0)], Config), + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +verify_fun_always_run_client() -> + [{doc,"Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"}]. + +verify_fun_always_run_client(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + no_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + %% If user verify fun is called correctly we fail the connection. + %% otherwise we cannot tell this case apart form where we miss + %% to call users verify fun + FunAndState = {fun(_,{extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, [ChainLen]) -> + {valid, [ChainLen + 1]}; + (_, valid_peer, [1]) -> + {fail, "verify_fun_was_always_run"}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, [0]}, + + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + no_result, []}}, + {options, + [{verify, verify_peer}, + {verify_fun, FunAndState} + | ClientOpts]}]), + + ssl_test_lib:check_client_alert(Server, Client, handshake_failure). + +%%-------------------------------------------------------------------- +verify_fun_always_run_server() -> + [{doc,"Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"}]. +verify_fun_always_run_server(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + %% If user verify fun is called correctly we fail the connection. + %% otherwise we cannot tell this case apart form where we miss + %% to call users verify fun + FunAndState = {fun(_,{extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, [ChainLen]) -> + {valid, [ChainLen + 1]}; + (_, valid_peer, [1]) -> + {fail, "verify_fun_was_always_run"}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, [0]}, + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + no_result, []}}, + {options, + [{verify, verify_peer}, + {verify_fun, FunAndState} | + ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + no_result, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_client_alert(Server, Client, handshake_failure). + +%%-------------------------------------------------------------------- +invalid_signature_client() -> + ssl_cert_tests:invalid_signature_client(). +invalid_signature_client(Config) when is_list(Config) -> + ssl_cert_tests:invalid_signature_client(Config). +%%-------------------------------------------------------------------- +invalid_signature_server() -> + ssl_cert_tests:invalid_signature_client(). +invalid_signature_server(Config) when is_list(Config) -> + ssl_cert_tests:invalid_signature_client(Config). + +%%-------------------------------------------------------------------- +%% TLS 1.3 Test cases ----------------------------------------------- +%%-------------------------------------------------------------------- +hello_retry_request() -> + [{doc,"Test that ssl server can request a new group when the client's first key share" + "is not supported"}]. + +hello_retry_request(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [x448, x25519]}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [secp256r1, x25519]}|ClientOpts0], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +custom_groups() -> + [{doc,"Test that ssl server can select a common group for key-exchange"}]. + +custom_groups(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [x448, secp256r1, secp384r1]}|ServerOpts0], + ClientOpts1 = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + ClientOpts = [{supported_groups,[secp384r1, secp256r1, x25519]}|ClientOpts1], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +%% Triggers a Server Alert as ssl client does not have a certificate with a +%% signature algorithm supported by the server (signature_algorithms_cert extension +%% of CertificateRequest does not contain the algorithm of the client certificate). +%% ssl client sends an empty certificate. +unsupported_sign_algo_cert_client_auth() -> + [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm_cert"}]. + +unsupported_sign_algo_cert_client_auth(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {signature_algs, [rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pss_rsae_sha256]}, + %% Skip rsa_pkcs1_sha256! + {signature_algs_cert, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, + {fail_if_no_peer_cert, true}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required). + +%%-------------------------------------------------------------------- +unsupported_sign_algo_client_auth() -> + [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm"}]. + +unsupported_sign_algo_client_auth(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + %% Skip rsa_pkcs1_sha256! + {signature_algs, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, + {fail_if_no_peer_cert, true}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, insufficient_security). +%%-------------------------------------------------------------------- +hello_retry_client_auth() -> + [{doc, "TLS 1.3 (HelloRetryRequest): Test client authentication."}]. + +hello_retry_client_auth(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts1 = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [x448, x25519]}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [secp256r1, x25519]}|ClientOpts0], + ServerOpts = [{verify, verify_peer}, + {fail_if_no_peer_cert, true} | ServerOpts1], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_accepted() -> + [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client sends an empty " + "certificate and fail_if_no_peer_cert is set to true."}]. + +hello_retry_client_auth_empty_cert_accepted(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + %% Delete Client Cert and Key + ClientOpts1 = proplists:delete(certfile, ClientOpts0), + ClientOpts2 = proplists:delete(keyfile, ClientOpts1), + + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, false}, + {supported_groups, [x448, x25519]}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [secp256r1, x25519]}|ClientOpts2], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_rejected() -> + [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client " + "sends an empty certificate and fail_if_no_peer_cert is set to true."}]. + +hello_retry_client_auth_empty_cert_rejected(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + %% Delete Client Cert and Key + ClientOpts1 = proplists:delete(certfile, ClientOpts0), + ClientOpts2 = proplists:delete(keyfile, ClientOpts1), + + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, true}, + {supported_groups, [x448, x25519]}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [secp256r1, x25519]}|ClientOpts2], + + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required). diff --git a/lib/ssl/test/ssl_cert_tests.erl b/lib/ssl/test/ssl_cert_tests.erl new file mode 100644 index 0000000000..c88daa2185 --- /dev/null +++ b/lib/ssl/test/ssl_cert_tests.erl @@ -0,0 +1,386 @@ +%% +%% %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(ssl_cert_tests). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("public_key/include/public_key.hrl"). + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +no_auth() -> + [{doc,"Test connection without authentication"}]. + +no_auth(Config) -> + ClientOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +auth() -> + [{doc,"Test connection with mutual authentication"}]. + +auth(Config) -> + ClientOpts = [{verify, verify_peer} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{verify, verify_peer} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +client_auth_empty_cert_accepted() -> + [{doc,"Test client authentication when client sends an empty certificate and " + "fail_if_no_peer_cert is set to false."}]. + +client_auth_empty_cert_accepted(Config) -> + ClientOpts = proplists:delete(keyfile, + proplists:delete(certfile, + ssl_test_lib:ssl_options(client_cert_opts, Config))), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts = [{verify, verify_peer}, + {fail_if_no_peer_cert, false} | ServerOpts0], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +client_auth_empty_cert_rejected() -> + [{doc,"Test client authentication when client sends an empty certificate and " + "fail_if_no_peer_cert is set to true."}]. + +client_auth_empty_cert_rejected(Config) -> + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ClientOpts0 = ssl_test_lib:ssl_options([], Config), + %% Delete Client Cert and Key + ClientOpts1 = proplists:delete(certfile, ClientOpts0), + ClientOpts = proplists:delete(keyfile, ClientOpts1), + + Version = proplists:get_value(version,Config), + case Version of + 'tlsv1.3' -> + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required); + _ -> + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, handshake_failure) + end. +%%-------------------------------------------------------------------- +client_auth_partial_chain() -> + [{doc, "Client sends an incompleate chain, by default not acceptable."}]. + +client_auth_partial_chain(Config) when is_list(Config) -> + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts0)), + [{_,RootCA,_} | _] = public_key:pem_decode(ClientCAs), + ClientOpts = [{cacerts, [RootCA]} | + proplists:delete(cacertfile, ClientOpts0)], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca). + +%%-------------------------------------------------------------------- +client_auth_allow_partial_chain() -> + [{doc, "Server trusts intermediat CA and accepts a partial chain. (partial_chain option)"}]. + +client_auth_allow_partial_chain(Config) when is_list(Config) -> + ServerOpts0 = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config), + {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts)), + [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ClientCAs), + + PartialChain = fun(CertChain) -> + case lists:member(IntermidiateCA, CertChain) of + true -> + {trusted_ca, IntermidiateCA}; + false -> + unknown_ca + end + end, + ServerOpts = [{cacerts, [IntermidiateCA]}, + {partial_chain, PartialChain} | + proplists:delete(cacertfile, ServerOpts0)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + + %%-------------------------------------------------------------------- +client_auth_do_not_allow_partial_chain() -> + [{doc, "Server does not accept the chain sent by the client as ROOT CA is unkown, " + "and we do not choose to trust the intermediate CA. (partial_chain option)"}]. + +client_auth_do_not_allow_partial_chain(Config) when is_list(Config) -> + ServerOpts0 = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config), + {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts0)), + [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ServerCAs), + + PartialChain = fun(_CertChain) -> + unknown_ca + end, + ServerOpts = [{cacerts, [IntermidiateCA]}, + {partial_chain, PartialChain} | + proplists:delete(cacertfile, ServerOpts0)], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca). + + %%-------------------------------------------------------------------- +client_auth_partial_chain_fun_fail() -> + [{doc, "If parial_chain fun crashes, treat it as if it returned unkown_ca"}]. + +client_auth_partial_chain_fun_fail(Config) when is_list(Config) -> + ServerOpts0 = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config), + + {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts0)), + [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ServerCAs), + + PartialChain = fun(_CertChain) -> + true = false %% crash on purpose + end, + ServerOpts = [{cacerts, [IntermidiateCA]}, + {partial_chain, PartialChain} | + proplists:delete(cacertfile, ServerOpts0)], + + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca). + +%%-------------------------------------------------------------------- +missing_root_cert_no_auth() -> + [{doc,"Test that the client succeds if the ROOT CA is unknown in verify_none mode"}]. + +missing_root_cert_no_auth(Config) -> + ClientOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{verify, verify_none} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +invalid_signature_client() -> + [{doc,"Test server with invalid signature"}]. + +invalid_signature_client(Config) when is_list(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), + + KeyFile = proplists:get_value(keyfile, ClientOpts0), + [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), + Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), + + ClientCertFile = proplists:get_value(certfile, ClientOpts0), + NewClientCertFile = filename:join(PrivDir, "client_invalid_cert.pem"), + [{'Certificate', ClientDerCert, _}] = ssl_test_lib:pem_to_der(ClientCertFile), + ClientOTPCert = public_key:pkix_decode_cert(ClientDerCert, otp), + ClientOTPTbsCert = ClientOTPCert#'OTPCertificate'.tbsCertificate, + NewClientDerCert = public_key:pkix_sign(ClientOTPTbsCert, Key), + ssl_test_lib:der_to_pem(NewClientCertFile, [{'Certificate', NewClientDerCert, not_encrypted}]), + ClientOpts = [{certfile, NewClientCertFile} | proplists:delete(certfile, ClientOpts0)], + ServerOpts = [{verify, verify_peer} | ServerOpts0], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca). + +%%-------------------------------------------------------------------- +invalid_signature_server() -> + [{doc,"Test client with invalid signature"}]. + +invalid_signature_server(Config) when is_list(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), + + KeyFile = proplists:get_value(keyfile, ServerOpts0), + [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), + Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), + + ServerCertFile = proplists:get_value(certfile, ServerOpts0), + NewServerCertFile = filename:join(PrivDir, "server_invalid_cert.pem"), + [{'Certificate', ServerDerCert, _}] = ssl_test_lib:pem_to_der(ServerCertFile), + ServerOTPCert = public_key:pkix_decode_cert(ServerDerCert, otp), + ServerOTPTbsCert = ServerOTPCert#'OTPCertificate'.tbsCertificate, + NewServerDerCert = public_key:pkix_sign(ServerOTPTbsCert, Key), + ssl_test_lib:der_to_pem(NewServerCertFile, [{'Certificate', NewServerDerCert, not_encrypted}]), + ServerOpts = [{certfile, NewServerCertFile} | proplists:delete(certfile, ServerOpts0)], + ClientOpts = [{verify, verify_peer} | ClientOpts0], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca). + +%%-------------------------------------------------------------------- +%% TLS 1.3 Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +hello_retry_request() -> + [{doc,"Test that ssl server can request a new group when the client's first key share" + "is not supported"}]. + +hello_retry_request(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + {ServerOpts, ClientOpts} = group_config(Config, + [{versions, ['tlsv1.2','tlsv1.3']} | ServerOpts0], + [{versions, ['tlsv1.2','tlsv1.3']} | ClientOpts0]), + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +custom_groups() -> + [{doc,"Test that ssl server can select a common group for key-exchange"}]. + +custom_groups(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + {ServerOpts, ClientOpts} = group_config_custom(Config, + [{versions, ['tlsv1.2','tlsv1.3']} | ServerOpts0], + [{versions, ['tlsv1.2','tlsv1.3']} | ClientOpts0]), + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- +%% Triggers a Server Alert as ssl client does not have a certificate with a +%% signature algorithm supported by the server (signature_algorithms_cert extension +%% of CertificateRequest does not contain the algorithm of the client certificate). +%% ssl client sends an empty certificate. +unsupported_sign_algo_cert_client_auth() -> + [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm_cert"}]. + +unsupported_sign_algo_cert_client_auth(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {signature_algs, [rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pss_rsae_sha256]}, + %% Skip rsa_pkcs1_sha256! + {signature_algs_cert, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, + {fail_if_no_peer_cert, true}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required). +%%-------------------------------------------------------------------- +unsupported_sign_algo_client_auth() -> + [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm"}]. + +unsupported_sign_algo_client_auth(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + %% Skip rsa_pkcs1_sha256! + {signature_algs, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, + {fail_if_no_peer_cert, true}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, insufficient_security). +%%-------------------------------------------------------------------- +hello_retry_client_auth() -> + [{doc, "TLS 1.3 (HelloRetryRequest): Test client authentication."}]. + +hello_retry_client_auth(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + {ServerOpts, ClientOpts} = group_config(Config, + [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, true} | ServerOpts0], + [{versions, ['tlsv1.2','tlsv1.3']}, {verify, verify_peer} | ClientOpts0]), + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_accepted() -> + [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client sends an empty " + "certificate and fail_if_no_peer_cert is set to false."}]. + +hello_retry_client_auth_empty_cert_accepted(Config) -> + ClientOpts0 = proplists:delete(keyfile, + proplists:delete(certfile, + ssl_test_lib:ssl_options(client_cert_opts, Config))), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + {ServerOpts, ClientOpts} = group_config(Config, + [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, false} | ServerOpts0], + [{versions, ['tlsv1.2','tlsv1.3']}, {verify, verify_peer} | ClientOpts0]), + + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +hello_retry_client_auth_empty_cert_rejected() -> + [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client " + "sends an empty certificate and fail_if_no_peer_cert is set to true."}]. + +hello_retry_client_auth_empty_cert_rejected(Config) -> + ClientOpts0 = proplists:delete(keyfile, + proplists:delete(certfile, + ssl_test_lib:ssl_options(client_cert_opts, Config))), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + {ServerOpts, ClientOpts} = group_config(Config, + [{versions, ['tlsv1.2','tlsv1.3']}, + {verify, verify_peer}, + {fail_if_no_peer_cert, true} | ServerOpts0], + [{versions, ['tlsv1.2','tlsv1.3']}, {verify, verify_peer} | ClientOpts0]), + + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required). + + +%%-------------------------------------------------------------------- +%% Internal functions ----------------------------------------------- +%%-------------------------------------------------------------------- + +group_config_custom(Config, ServerOpts, ClientOpts) -> + case proplists:get_value(client_type, Config) of + erlang -> + {[{groups,"X448:P-256:P-384"} | ServerOpts], + [{supported_groups, [secp384r1, secp256r1, x25519]} | ClientOpts]}; + openssl -> + {[{supported_groups, [x448, secp256r1, secp384r1]} | ServerOpts], + [{groups,"P-384:P-256:X25519"} | ClientOpts]} + end. + +group_config(Config, ServerOpts, ClientOpts) -> + case proplists:get_value(client_type, Config) of + erlang -> + {[{groups,"X448:X25519"} | ServerOpts], + [{supported_groups, [secp256r1, x25519]} | ClientOpts]}; + openssl -> + {[{supported_groups, [x448, x25519]} | ServerOpts], + [{groups,"P-256:X25519"} | ClientOpts]} + end. + +test_ciphers(_, 'tlsv1.3' = Version) -> + Ciphers = ssl:cipher_suites(default, Version), + ct:log("Version ~p Testing ~p~n", [Version, Ciphers]), + OpenSSLCiphers = openssl_ciphers(), + ct:log("OpenSSLCiphers ~p~n", [OpenSSLCiphers]), + lists:filter(fun(C) -> + ct:log("Cipher ~p~n", [C]), + lists:member(ssl_cipher_format:suite_map_to_openssl_str(C), OpenSSLCiphers) + end, Ciphers); +test_ciphers(Kex, Version) -> + Ciphers = ssl:filter_cipher_suites(ssl:cipher_suites(default, Version), + [{key_exchange, Kex}]), + ct:log("Version ~p Testing ~p~n", [Version, Ciphers]), + OpenSSLCiphers = openssl_ciphers(), + ct:log("OpenSSLCiphers ~p~n", [OpenSSLCiphers]), + lists:filter(fun(C) -> + ct:log("Cipher ~p~n", [C]), + lists:member(ssl_cipher_format:suite_map_to_openssl_str(C), OpenSSLCiphers) + end, Ciphers). + + + +openssl_ciphers() -> + Str = os:cmd("openssl ciphers"), + string:split(string:strip(Str, right, $\n), ":", all). diff --git a/lib/ssl/test/ssl_certificate_verify_SUITE.erl b/lib/ssl/test/ssl_certificate_verify_SUITE.erl index 55dee9a48f..f38858e0bf 100644 --- a/lib/ssl/test/ssl_certificate_verify_SUITE.erl +++ b/lib/ssl/test/ssl_certificate_verify_SUITE.erl @@ -40,6 +40,7 @@ %%-------------------------------------------------------------------- all() -> [ + {group, 'tlsv1.3'}, {group, 'tlsv1.2'}, {group, 'tlsv1.1'}, {group, 'tlsv1'}, @@ -50,6 +51,7 @@ all() -> groups() -> [ + {'tlsv1.3', [], all_protocol_groups()}, {'tlsv1.2', [], all_protocol_groups()}, {'tlsv1.1', [], all_protocol_groups()}, {'tlsv1', [], all_protocol_groups()}, @@ -69,36 +71,20 @@ all_protocol_groups() -> {group, error_handling}]. tests() -> - [verify_peer, - verify_none, - server_require_peer_cert_ok, - server_require_peer_cert_fail, - server_require_peer_cert_empty_ok, - server_require_peer_cert_partial_chain, - server_require_peer_cert_allow_partial_chain, - server_require_peer_cert_do_not_allow_partial_chain, - server_require_peer_cert_partial_chain_fun_fail, - verify_fun_always_run_client, - verify_fun_always_run_server, - cert_expired, - invalid_signature_client, - invalid_signature_server, + [cert_expired, + %invalid_signature_client, + %%invalid_signature_server, extended_key_usage_verify_both, extended_key_usage_verify_server, critical_extension_verify_client, critical_extension_verify_server, critical_extension_verify_none, - customize_hostname_check, - incomplete_chain + long_chain ]. error_handling_tests()-> [client_with_cert_cipher_suites_handshake, - server_verify_no_cacerts, - unknown_server_ca_fail, - unknown_server_ca_accept_verify_none, - unknown_server_ca_accept_verify_peer, - unknown_server_ca_accept_backwardscompatibility, + %%unknown_server_ca_accept_backwardscompatibility, no_authority_key_identifier, no_authority_key_identifier_keyEncipherment]. @@ -157,61 +143,6 @@ end_per_testcase(_TestCase, Config) -> %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- - -verify_peer() -> - [{doc,"Test option verify_peer"}]. -verify_peer(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - Active = proplists:get_value(active, Config), - ReceiveFunction = proplists:get_value(receive_function, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active}, {verify, verify_peer} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active}, {verify, verify_peer} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -verify_none() -> - [{doc,"Test option verify_none"}]. - -verify_none(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - Active = proplists:get_value(active, Config), - ReceiveFunction = proplists:get_value(receive_function, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active}, {verify, verify_none} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active}, - {verify, verify_none} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- - server_verify_client_once() -> [{doc,"Test server option verify_client_once"}]. @@ -250,303 +181,6 @@ server_verify_client_once(Config) when is_list(Config) -> %%-------------------------------------------------------------------- -server_require_peer_cert_ok() -> - [{doc,"Test server option fail_if_no_peer_cert when peer sends cert"}]. - -server_require_peer_cert_ok(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - Active = proplists:get_value(active, Config), - ReceiveFunction = proplists:get_value(receive_function, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- - -server_require_peer_cert_fail() -> - [{doc,"Test server option fail_if_no_peer_cert when peer doesn't send cert"}]. - -server_require_peer_cert_fail(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - BadClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), - Active = proplists:get_value(active, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, [{active, Active} | ServerOpts]}]), - - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {options, [{active, Active} | BadClientOpts]}]), - - ssl_test_lib:check_server_alert(Server, Client, handshake_failure). - -%%-------------------------------------------------------------------- -server_require_peer_cert_empty_ok() -> - [{doc,"Test server option fail_if_no_peer_cert when peer sends cert"}]. - -server_require_peer_cert_empty_ok(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, false} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), - Active = proplists:get_value(active, Config), - ReceiveFunction = proplists:get_value(receive_function, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - ClientOpts = proplists:delete(keyfile, proplists:delete(certfile, ClientOpts0)), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- - -server_require_peer_cert_partial_chain() -> - [{doc, "Client sends an incompleate chain, by default not acceptable."}]. - -server_require_peer_cert_partial_chain(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - Active = proplists:get_value(active, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts)), - [{_,RootCA,_} | _] = public_key:pem_decode(ClientCAs), - - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, Active} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{active, Active}, - {cacerts, [RootCA]} | - proplists:delete(cacertfile, ClientOpts)]}]), - ssl_test_lib:check_server_alert(Server, Client, unknown_ca). - -%%-------------------------------------------------------------------- -server_require_peer_cert_allow_partial_chain() -> - [{doc, "Server trusts intermediat CA and accepts a partial chain. (partial_chain option)"}]. - -server_require_peer_cert_allow_partial_chain(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Active = proplists:get_value(active, Config), - ReceiveFunction = proplists:get_value(receive_function, Config), - - {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts)), - [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ClientCAs), - - PartialChain = fun(CertChain) -> - case lists:member(IntermidiateCA, CertChain) of - true -> - {trusted_ca, IntermidiateCA}; - false -> - unknown_ca - end - end, - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active}, - {cacerts, [IntermidiateCA]}, - {partial_chain, PartialChain} | - proplists:delete(cacertfile, ServerOpts)]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{active, Active} | ClientOpts]}]), - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - - %%-------------------------------------------------------------------- -server_require_peer_cert_do_not_allow_partial_chain() -> - [{doc, "Server does not accept the chain sent by the client as ROOT CA is unkown, " - "and we do not choose to trust the intermediate CA. (partial_chain option)"}]. - -server_require_peer_cert_do_not_allow_partial_chain(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), - [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ServerCAs), - - PartialChain = fun(_CertChain) -> - unknown_ca - end, - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{cacerts, [IntermidiateCA]}, - {partial_chain, PartialChain} | - proplists:delete(cacertfile, ServerOpts)]}]), - - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - ssl_test_lib:check_server_alert(Server, Client, unknown_ca). - %%-------------------------------------------------------------------- -server_require_peer_cert_partial_chain_fun_fail() -> - [{doc, "If parial_chain fun crashes, treat it as if it returned unkown_ca"}]. - -server_require_peer_cert_partial_chain_fun_fail(Config) when is_list(Config) -> - ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ssl_test_lib:ssl_options(server_rsa_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), - [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ServerCAs), - - PartialChain = fun(_CertChain) -> - true = false %% crash on purpose - end, - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, [{cacerts, [IntermidiateCA]}, - {partial_chain, PartialChain} | - proplists:delete(cacertfile, ServerOpts)]}]), - - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts}]), - ssl_test_lib:check_server_alert(Server, Client, unknown_ca). - -%%-------------------------------------------------------------------- -verify_fun_always_run_client() -> - [{doc,"Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"}]. - -verify_fun_always_run_client(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - no_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - %% If user verify fun is called correctly we fail the connection. - %% otherwise we cannot tell this case apart form where we miss - %% to call users verify fun - FunAndState = {fun(_,{extension, _}, UserState) -> - {unknown, UserState}; - (_, valid, [ChainLen]) -> - {valid, [ChainLen + 1]}; - (_, valid_peer, [1]) -> - {fail, "verify_fun_was_always_run"}; - (_, valid_peer, UserState) -> - {valid, UserState} - end, [0]}, - - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - no_result, []}}, - {options, - [{verify, verify_peer}, - {verify_fun, FunAndState} - | ClientOpts]}]), - - ssl_test_lib:check_client_alert(Server, Client, handshake_failure). - -%%-------------------------------------------------------------------- -verify_fun_always_run_server() -> - [{doc,"Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"}]. -verify_fun_always_run_server(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - %% If user verify fun is called correctly we fail the connection. - %% otherwise we cannot tell this case apart form where we miss - %% to call users verify fun - FunAndState = {fun(_,{extension, _}, UserState) -> - {unknown, UserState}; - (_, valid, [ChainLen]) -> - {valid, [ChainLen + 1]}; - (_, valid_peer, [1]) -> - {fail, "verify_fun_was_always_run"}; - (_, valid_peer, UserState) -> - {valid, UserState} - end, [0]}, - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - no_result, []}}, - {options, - [{verify, verify_peer}, - {verify_fun, FunAndState} | - ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - no_result, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_client_alert(Server, Client, handshake_failure). -%%-------------------------------------------------------------------- - cert_expired() -> [{doc,"Test server with expired certificate"}]. @@ -853,6 +487,7 @@ invalid_signature_server(Config) when is_list(Config) -> {from, self()}, {options, [{verify, verify_peer} | ClientOpts]}]), ssl_test_lib:check_server_alert(Server, Client, unknown_ca). + %%-------------------------------------------------------------------- invalid_signature_client() -> @@ -927,214 +562,22 @@ client_with_cert_cipher_suites_handshake(Config) when is_list(Config) -> %%-------------------------------------------------------------------- -server_verify_no_cacerts() -> - [{doc,"Test server must have cacerts if it wants to verify client"}]. -server_verify_no_cacerts(Config) when is_list(Config) -> - ServerOpts = proplists:delete(cacertfile, ssl_test_lib:ssl_options(server_rsa_opts, Config)), - {_, ServerNode, _} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, [{verify, verify_peer} - | ServerOpts]}]), - - ssl_test_lib:check_result(Server, {error, {options, {cacertfile, ""}}}). - - -%%-------------------------------------------------------------------- -unknown_server_ca_fail() -> - [{doc,"Test that the client fails if the ca is unknown in verify_peer mode"}]. -unknown_server_ca_fail(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - no_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - FunAndState = {fun(_,{bad_cert, unknown_ca} = Reason, _) -> - {fail, Reason}; - (_,{extension, _}, UserState) -> - {unknown, UserState}; - (_, valid, UserState) -> - {valid, [test_to_update_user_state | UserState]}; - (_, valid_peer, UserState) -> - {valid, UserState} - end, []}, - - Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - no_result, []}}, - {options, - [{verify, verify_peer}, - {verify_fun, FunAndState} - | ClientOpts]}]), - ssl_test_lib:check_client_alert(Server, Client, unknown_ca). -%%-------------------------------------------------------------------- -unknown_server_ca_accept_verify_none() -> - [{doc,"Test that the client succeds if the ca is unknown in verify_none mode"}]. -unknown_server_ca_accept_verify_none(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active, []}}, - {options, - [{verify, verify_none}| ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). -%%-------------------------------------------------------------------- -unknown_server_ca_accept_verify_peer() -> - [{doc, "Test that the client succeds if the ca is unknown in verify_peer mode" - " with a verify_fun that accepts the unknown ca error"}]. -unknown_server_ca_accept_verify_peer(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - FunAndState = {fun(_,{bad_cert, unknown_ca}, UserState) -> - {valid, UserState}; - (_,{bad_cert, _} = Reason, _) -> - {fail, Reason}; - (_,{extension, _}, UserState) -> - {unknown, UserState}; - (_, valid, UserState) -> - {valid, UserState}; - (_, valid_peer, UserState) -> - {valid, UserState} - end, []}, - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active, []}}, - {options, - [{verify, verify_peer}, - {verify_fun, FunAndState}| ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -unknown_server_ca_accept_backwardscompatibility() -> - [{doc,"Test that old style verify_funs will work"}]. -unknown_server_ca_accept_backwardscompatibility(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(empty_client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - {ClientNode, ServerNode, Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - AcceptBadCa = fun({bad_cert,unknown_ca}, Acc) -> Acc; - (Other, Acc) -> [Other | Acc] - end, - VerifyFun = - fun(ErrorList) -> - case lists:foldl(AcceptBadCa, [], ErrorList) of - [] -> true; - [_|_] -> false - end - end, - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active, []}}, - {options, - [{verify, verify_peer}, - {verify_fun, VerifyFun}| ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- - -customize_hostname_check() -> - [{doc,"Test option customize_hostname_check."}]. -customize_hostname_check(Config) when is_list(Config) -> - Ext = [#'Extension'{extnID = ?'id-ce-subjectAltName', - extnValue = [{dNSName, "*.example.org"}], - critical = false} - ], - {ClientOpts0, ServerOpts0} = ssl_test_lib:make_rsa_cert_chains([{server_chain, - [[], - [], - [{extensions, Ext}] - ]}], - Config, "https_hostname_convention"), - ClientOpts = ssl_test_lib:ssl_options(ClientOpts0, Config), - ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config), - - {ClientNode, ServerNode, Hostname} = 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, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - CustomFun = public_key:pkix_verify_hostname_match_fun(https), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, - [{server_name_indication, "other.example.org"}, - {customize_hostname_check, - [{match_fun, CustomFun}]} | ClientOpts] - }]), - ssl_test_lib:check_result(Server, ok, Client, ok), - - Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, - - Client1 = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, ClientOpts} - ]), - ssl_test_lib:check_client_alert(Server, Client1, handshake_failure). - -incomplete_chain() -> +long_chain() -> [{doc,"Test option verify_peer"}]. -incomplete_chain(Config) when is_list(Config) -> - DefConf = ssl_test_lib:default_cert_chain_conf(), - CertChainConf = ssl_test_lib:gen_conf(rsa, rsa, DefConf, DefConf), +long_chain(Config) when is_list(Config) -> #{server_config := ServerConf, - client_config := ClientConf} = public_key:pkix_test_data(CertChainConf), + client_config := ClientConf} = 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)}], + [{key, ssl_test_lib:hardcode_rsa_key(3)}], + [{key, ssl_test_lib:hardcode_rsa_key(4)}]], + peer => [{key, ssl_test_lib:hardcode_rsa_key(5)}]}, + client_chain => #{root => [{key, ssl_test_lib:hardcode_rsa_key(3)}], + intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]], + peer => [{key, ssl_test_lib:hardcode_rsa_key(1)}]}}), [ServerRoot| _] = ServerCas = proplists:get_value(cacerts, ServerConf), ClientCas = proplists:get_value(cacerts, ClientConf), - + Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -1151,6 +594,7 @@ incomplete_chain(Config) when is_list(Config) -> {mfa, {ssl_test_lib, ReceiveFunction, []}}, {options, [{active, Active}, {verify, verify_peer}, + {depth, 5}, {cacerts, ServerCas ++ ClientCas} | proplists:delete(cacerts, ClientConf)]}]), ssl_test_lib:check_result(Server, ok, Client, ok), diff --git a/lib/ssl/test/ssl_cipher_suite_SUITE.erl b/lib/ssl/test/ssl_cipher_suite_SUITE.erl index 51788c29e7..e598d662e9 100644 --- a/lib/ssl/test/ssl_cipher_suite_SUITE.erl +++ b/lib/ssl/test/ssl_cipher_suite_SUITE.erl @@ -45,7 +45,7 @@ groups() -> {'tlsv1.2', [], kex()}, {'tlsv1.1', [], kex()}, {'tlsv1', [], kex()}, - {'sslv3', [], kex()}, + {'sslv3', [], ssl3_kex()}, {'dtlsv1.2', [], kex()}, {'dtlsv1', [], kex()}, {dhe_rsa, [],[dhe_rsa_3des_ede_cbc, @@ -130,6 +130,11 @@ groups() -> kex() -> rsa() ++ ecdsa() ++ dss() ++ anonymous(). + +ssl3_kex() -> + ssl3_rsa() ++ ssl3_dss() ++ ssl3_anonymous(). + + rsa() -> [{group, dhe_rsa}, {group, ecdhe_rsa}, @@ -138,6 +143,11 @@ rsa() -> {group, rsa_psk} ]. +ssl3_rsa() -> + [{group, dhe_rsa}, + {group, rsa} + ]. + ecdsa() -> [{group, ecdhe_ecdsa}]. @@ -145,6 +155,10 @@ dss() -> [{group, dhe_dss}, {group, srp_dss}]. +ssl3_dss() -> + [{group, dhe_dss} + ]. + anonymous() -> [{group, dh_anon}, {group, ecdh_anon}, @@ -154,6 +168,10 @@ anonymous() -> {group, srp_anon} ]. +ssl3_anonymous() -> + [{group, dh_anon}]. + + init_per_suite(Config) -> catch crypto:stop(), try crypto:start() of diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl index 003e1fc448..7cfb2ac0c5 100644 --- a/lib/ssl/test/ssl_dist_SUITE.erl +++ b/lib/ssl/test/ssl_dist_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2018. All Rights Reserved. +%% Copyright Ericsson AB 2007-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. @@ -311,9 +311,11 @@ listen_port_options(Config) when is_list(Config) -> catch _:Reason -> stop_ssl_node(NH2), + stop_ssl_node(NH1), ct:fail(Reason) end, stop_ssl_node(NH2), + stop_ssl_node(NH1), success(Config). %%-------------------------------------------------------------------- diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl index 1b432970b6..2750a4a9dc 100644 --- a/lib/ssl/test/ssl_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_handshake_SUITE.erl @@ -214,7 +214,7 @@ encode_decode_srp(_Config) -> 0,3, % HostNameLength 98,97,114>>, % hostname = "bar" EncodedExts0 = <<?UINT16(_),EncodedExts/binary>> = - ssl_handshake:encode_hello_extensions(Exts), + ssl_handshake:encode_hello_extensions(Exts, {3,3}), Exts = ssl_handshake:decode_hello_extensions(EncodedExts, {3,3}, {3,3}, client). signature_algorithms(Config) -> diff --git a/lib/ssl/test/ssl_npn_handshake_SUITE.erl b/lib/ssl/test/ssl_npn_SUITE.erl index 878e983bb9..8a76a3a82b 100644 --- a/lib/ssl/test/ssl_npn_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_npn_SUITE.erl @@ -19,7 +19,7 @@ %% %% --module(ssl_npn_handshake_SUITE). +-module(ssl_npn_SUITE). %% Note: This directive should only be used in test suites. -compile(export_all). diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl index 46734ba180..7dbc0c5134 100644 --- a/lib/ssl/test/ssl_npn_hello_SUITE.erl +++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl @@ -24,10 +24,11 @@ %% Note: This directive should only be used in test suites. -compile(export_all). --include("ssl_cipher.hrl"). --include("ssl_internal.hrl"). --include("tls_handshake.hrl"). --include("tls_record.hrl"). + +-include_lib("ssl/src/tls_record.hrl"). +-include_lib("ssl/src/tls_handshake.hrl"). +-include_lib("ssl/src/ssl_cipher.hrl"). +-include_lib("ssl/src/ssl_internal.hrl"). -include_lib("common_test/include/ct.hrl"). %%-------------------------------------------------------------------- diff --git a/lib/ssl/test/ssl_payload_SUITE.erl b/lib/ssl/test/ssl_payload_SUITE.erl index 27b9c258a0..2d0ffd03d7 100644 --- a/lib/ssl/test/ssl_payload_SUITE.erl +++ b/lib/ssl/test/ssl_payload_SUITE.erl @@ -48,21 +48,27 @@ groups() -> payload_tests() -> [server_echos_passive_small, + server_echos_passive_chunk_small, server_echos_active_once_small, server_echos_active_small, client_echos_passive_small, + client_echos_passive_chunk_small, client_echos_active_once_small, client_echos_active_small, server_echos_passive_big, + server_echos_passive_chunk_big, server_echos_active_once_big, server_echos_active_big, client_echos_passive_big, + client_echos_passive_chunk_big, client_echos_active_once_big, client_echos_active_big, server_echos_passive_huge, + server_echos_passive_chunk_huge, server_echos_active_once_huge, server_echos_active_huge, client_echos_passive_huge, + client_echos_passive_chunk_huge, client_echos_active_once_huge, client_echos_active_huge, client_active_once_server_close]. @@ -109,9 +115,11 @@ end_per_group(GroupName, Config) -> init_per_testcase(TestCase, Config) when TestCase == server_echos_passive_huge; + TestCase == server_echos_passive_chunk_huge; TestCase == server_echos_active_once_huge; TestCase == server_echos_active_huge; TestCase == client_echos_passive_huge; + TestCase == client_echos_passive_chunk_huge; TestCase == client_echos_active_once_huge; TestCase == client_echos_active_huge -> case erlang:system_info(system_architecture) of @@ -124,9 +132,11 @@ init_per_testcase(TestCase, Config) init_per_testcase(TestCase, Config) when TestCase == server_echos_passive_big; + TestCase == server_echos_passive_chunk_big; TestCase == server_echos_active_once_big; TestCase == server_echos_active_big; TestCase == client_echos_passive_big; + TestCase == client_echos_passive_chunk_big; TestCase == client_echos_active_once_big; TestCase == client_echos_active_big -> ct:timetrap({seconds, 60}), @@ -157,6 +167,22 @@ server_echos_passive_small(Config) when is_list(Config) -> %%-------------------------------------------------------------------- +server_echos_passive_chunk_small() -> + [{doc, "Client sends 1000 bytes in passive mode to server, that receives them in chunks, " + "sends them back, and closes."}]. + +server_echos_passive_chunk_small(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + %% + Data = binary:copy(<<"1234567890">>, 100), + server_echos_passive_chunk( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). + + +%%-------------------------------------------------------------------- + server_echos_active_once_small() -> [{doc, "Client sends 1000 bytes in active once mode to server, that receives " " them, sends them back, and closes."}]. @@ -200,6 +226,21 @@ client_echos_passive_small(Config) when is_list(Config) -> Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- +client_echos_passive_chunk__small() -> + [{doc, "Server sends 1000 bytes in passive mode to client, that receives them in chunks, " + "sends them back, and closes."}]. + +client_echos_passive_chunk_small(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + %% + Data = binary:copy(<<"1234567890">>, 100), + client_echos_passive_chunk( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). + + +%%-------------------------------------------------------------------- client_echos_active_once_small() -> ["Server sends 1000 bytes in active once mode to client, that receives " "them, sends them back, and closes."]. @@ -241,6 +282,19 @@ server_echos_passive_big(Config) when is_list(Config) -> Data = binary:copy(<<"1234567890">>, 5000), server_echos_passive( Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). +%%-------------------------------------------------------------------- +server_echos_passive_chunk_big() -> + [{doc, "Client sends 50000 bytes to server in passive mode, that receives them, " + "sends them back, and closes."}]. + +server_echos_passive_chunk_big(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + %% + Data = binary:copy(<<"1234567890">>, 5000), + server_echos_passive_chunk( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- @@ -286,6 +340,22 @@ client_echos_passive_big(Config) when is_list(Config) -> client_echos_passive( Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). + +%%-------------------------------------------------------------------- +client_echos_passive_chunk_big() -> + [{doc, "Server sends 50000 bytes to client in passive mode, that receives them, " + "sends them back, and closes."}]. + +client_echos_passive_chunk_big(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + %% + Data = binary:copy(<<"1234567890">>, 5000), + client_echos_passive_chunk( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). + + %%-------------------------------------------------------------------- client_echos_active_once_big() -> [{doc, "Server sends 50000 bytes to client in active once mode, that receives" @@ -329,6 +399,20 @@ server_echos_passive_huge(Config) when is_list(Config) -> Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- +server_echos_passive_chunk_huge() -> + [{doc, "Client sends 500000 bytes to server in passive mode, that receives " + " them, sends them back, and closes."}]. + +server_echos_passive_chunk_huge(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + %% + Data = binary:copy(<<"1234567890">>, 50000), + server_echos_passive_chunk( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). + +%%-------------------------------------------------------------------- server_echos_active_once_huge() -> [{doc, "Client sends 500000 bytes to server in active once mode, that receives " "them, sends them back, and closes."}]. @@ -369,7 +453,19 @@ client_echos_passive_huge(Config) when is_list(Config) -> Data = binary:copy(<<"1234567890">>, 50000), client_echos_passive( Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). +%%-------------------------------------------------------------------- +client_echos_passive_chunk_huge() -> + [{doc, "Server sends 500000 bytes to client in passive mode, that receives " + "them, sends them back, and closes."}]. +client_echos_passive_chunk_huge(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + %% + Data = binary:copy(<<"1234567890">>, 50000), + client_echos_passive_chunk( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- client_echos_active_once_huge() -> [{doc, "Server sends 500000 bytes to client in active once mode, that receives " @@ -442,6 +538,28 @@ server_echos_passive( ssl_test_lib:close(Server), ssl_test_lib:close(Client). +server_echos_passive_chunk( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) -> + Length = byte_size(Data), + Server = + ssl_test_lib:start_server( + [{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, echoer_chunk, [Length]}}, + {options, [{active, false}, {mode, binary} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client( + [{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, sender, [Data]}}, + {options, [{active, false}, {mode, binary} | ClientOpts]}]), + %% + ssl_test_lib:check_result(Server, ok, Client, ok), + %% + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). server_echos_active_once( Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) -> @@ -513,6 +631,31 @@ client_echos_passive( ssl_test_lib:close(Server), ssl_test_lib:close(Client). + +client_echos_passive_chunk( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) -> + Length = byte_size(Data), + Server = + ssl_test_lib:start_server( + [{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, sender, [Data]}}, + {options, [{active, false}, {mode, binary} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client( + [{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, echoer_chunk, [Length]}}, + {options, [{active, false}, {mode, binary} | ClientOpts]}]), + %% + ssl_test_lib:check_result(Server, ok, Client, ok), + %% + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + client_echos_active_once( Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) -> Length = byte_size(Data), @@ -615,6 +758,10 @@ echoer(Socket, Size) -> ct:log("Echoer recv: ~p~n", [ssl:getopts(Socket, [active])]), echo_recv(Socket, Size * 100). +echoer_chunk(Socket, Size) -> + ct:log("Echoer recv: ~p~n", [ssl:getopts(Socket, [active])]), + echo_recv_chunk(Socket, Size, Size * 100). + echoer_active_once(Socket, Size) -> ct:log("Echoer active once: ~p~n", [ssl:getopts(Socket, [active])]), echo_active_once(Socket, Size * 100). @@ -632,6 +779,16 @@ echo_recv(Socket, Size) -> ok = ssl:send(Socket, Data), echo_recv(Socket, Size - byte_size(Data)). + +%% Receive Size bytes +echo_recv_chunk(_Socket, _, 0) -> + ok; +echo_recv_chunk(Socket, ChunkSize, Size) -> + {ok, Data} = ssl:recv(Socket, ChunkSize), + ok = ssl:send(Socket, Data), + echo_recv_chunk(Socket, ChunkSize, Size - ChunkSize). + + %% Receive Size bytes echo_active_once(_Socket, 0) -> ok; diff --git a/lib/ssl/test/ssl_pem_cache_SUITE.erl b/lib/ssl/test/ssl_pem_cache_SUITE.erl index 6f11e2bbe8..3c7f6ab20f 100644 --- a/lib/ssl/test/ssl_pem_cache_SUITE.erl +++ b/lib/ssl/test/ssl_pem_cache_SUITE.erl @@ -34,7 +34,10 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- all() -> - [pem_cleanup, invalid_insert]. + [ + pem_cleanup, + clear_pem_cache, + invalid_insert]. groups() -> []. @@ -110,6 +113,37 @@ pem_cleanup(Config)when is_list(Config) -> ssl_test_lib:close(Client), false = Size == Size1. +clear_pem_cache() -> + [{doc,"Test that internal reference tabel is cleaned properly even when " + " the PEM cache is cleared" }]. +clear_pem_cache(Config) when is_list(Config) -> + {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + [_,{FilRefDb, _} |_] = element(6, State), + {Server, Client} = basic_verify_test_no_close(Config), + CountReferencedFiles = fun({_, -1}, Acc) -> + Acc; + ({_, N}, Acc) -> + N + Acc + end, + + 2 = ets:foldl(CountReferencedFiles, 0, FilRefDb), + ssl:clear_pem_cache(), + _ = sys:get_status(whereis(ssl_manager)), + {Server1, Client1} = basic_verify_test_no_close(Config), + 4 = ets:foldl(CountReferencedFiles, 0, FilRefDb), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + ct:sleep(2000), + _ = sys:get_status(whereis(ssl_manager)), + 2 = ets:foldl(CountReferencedFiles, 0, FilRefDb), + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client1), + ct:sleep(2000), + _ = sys:get_status(whereis(ssl_manager)), + 0 = ets:foldl(CountReferencedFiles, 0, FilRefDb). + invalid_insert() -> [{doc, "Test that insert of invalid pem does not cause empty cache entry"}]. invalid_insert(Config)when is_list(Config) -> @@ -163,3 +197,22 @@ later()-> Gregorian = calendar:datetime_to_gregorian_seconds(DateTime), calendar:gregorian_seconds_to_datetime(Gregorian + (2 * ?CLEANUP_INTERVAL)). +basic_verify_test_no_close(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = 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, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + {Server, Client}. diff --git a/lib/ssl/test/ssl_renegotiate_SUITE.erl b/lib/ssl/test/ssl_renegotiate_SUITE.erl new file mode 100644 index 0000000000..ef3f9ebb52 --- /dev/null +++ b/lib/ssl/test/ssl_renegotiate_SUITE.erl @@ -0,0 +1,499 @@ +%% +%% %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(ssl_renegotiate_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"). + +-define(SLEEP, 500). +-define(RENEGOTIATION_DISABLE_TIME, 12000). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +groups() -> + [{'dtlsv1.2', [], renegotiate_tests()}, + {'dtlsv1', [], renegotiate_tests()}, + {'tlsv1.3', [], renegotiate_tests()}, + {'tlsv1.2', [], renegotiate_tests()}, + {'tlsv1.1', [], renegotiate_tests()}, + {'tlsv1', [], renegotiate_tests()}, + {'sslv3', [], ssl3_renegotiate_tests()} + ]. + +renegotiate_tests() -> + [client_renegotiate, + server_renegotiate, + client_secure_renegotiate, + client_secure_renegotiate_fallback, + client_renegotiate_reused_session, + server_renegotiate_reused_session, + client_no_wrap_sequence_number, + server_no_wrap_sequence_number, + renegotiate_dos_mitigate_active, + renegotiate_dos_mitigate_passive, + renegotiate_dos_mitigate_absolute]. + +ssl3_renegotiate_tests() -> + [client_renegotiate, + server_renegotiate, + client_renegotiate_reused_session, + server_renegotiate_reused_session, + client_no_wrap_sequence_number, + server_no_wrap_sequence_number, + renegotiate_dos_mitigate_active, + renegotiate_dos_mitigate_passive, + renegotiate_dos_mitigate_absolute]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto). + +init_per_group(GroupName, Config) -> + ssl_test_lib:clean_tls_version(Config), + case ssl_test_lib:is_tls_version(GroupName) andalso ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + _ -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl:start(), + Config; + false -> + {skip, "Missing crypto support"} + end + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +client_renegotiate() -> + [{doc,"Test ssl:renegotiate/1 on client."}]. +client_renegotiate(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {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, + renegotiate, [Data]}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +client_secure_renegotiate() -> + [{doc,"Test ssl:renegotiate/1 on client."}]. +client_secure_renegotiate(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {options, [{secure_renegotiate, true} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate, [Data]}}, + {options, [{reuse_sessions, false}, + {secure_renegotiate, true}| ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +client_secure_renegotiate_fallback() -> + [{doc,"Test that we can set secure_renegotiate to false that is " + "fallback option, we however do not have a insecure server to test against!"}]. +client_secure_renegotiate_fallback(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {options, [{secure_renegotiate, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate, [Data]}}, + {options, [{reuse_sessions, false}, + {secure_renegotiate, false}| ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +server_renegotiate() -> + [{doc,"Test ssl:renegotiate/1 on server."}]. +server_renegotiate(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, + renegotiate, [Data]}}, + {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, erlang_ssl_receive, [Data]}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +client_renegotiate_reused_session() -> + [{doc,"Test ssl:renegotiate/1 on client when the ssl session will be reused."}]. +client_renegotiate_reused_session(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {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, + renegotiate_reuse_session, [Data]}}, + {options, [{reuse_sessions, true} | ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +server_renegotiate_reused_session() -> + [{doc,"Test ssl:renegotiate/1 on server when the ssl session will be reused."}]. +server_renegotiate_reused_session(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, + renegotiate_reuse_session, [Data]}}, + {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, erlang_ssl_receive, [Data]}}, + {options, [{reuse_sessions, true} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +client_no_wrap_sequence_number() -> + [{doc,"Test that erlang client will renegotiate session when", + "max sequence number celing is about to be reached. Although" + "in the testcase we use the test option renegotiate_at" + " to lower treashold substantially."}]. + +client_no_wrap_sequence_number(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + ErlData = "From erlang to erlang", + N = 12, + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Version = ssl_test_lib:protocol_version(Config, tuple), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + trigger_renegotiate, [[ErlData, treashold(N, Version)]]}}, + {options, [{reuse_sessions, false}, + {renegotiate_at, N} | ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +server_no_wrap_sequence_number() -> + [{doc, "Test that erlang server will renegotiate session when", + "max sequence number celing is about to be reached. Although" + "in the testcase we use the test option renegotiate_at" + " to lower treashold substantially."}]. + +server_no_wrap_sequence_number(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + N = 12, + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + trigger_renegotiate, [[Data, N+2]]}}, + {options, [{renegotiate_at, N} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{reuse_sessions, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +renegotiate_dos_mitigate_active() -> + [{doc, "Mitigate DOS computational attack by not allowing client to renegotiate many times in a row", + "immediately after each other"}]. +renegotiate_dos_mitigate_active(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = 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, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate_immediately, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +renegotiate_dos_mitigate_passive() -> + [{doc, "Mitigate DOS computational attack by not allowing client to renegotiate many times in a row", + "immediately after each other"}]. +renegotiate_dos_mitigate_passive(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = 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, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate_immediately, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +renegotiate_dos_mitigate_absolute() -> + [{doc, "Mitigate DOS computational attack by not allowing client to initiate renegotiation"}]. +renegotiate_dos_mitigate_absolute(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, ServerNode, Hostname} = 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, [{client_renegotiation, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate_rejected, + []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- +renegotiate(Socket, Data) -> + ct:log("Renegotiating ~n", []), + Result = ssl:renegotiate(Socket), + ct:log("Result ~p~n", [Result]), + ssl:send(Socket, Data), + case Result of + ok -> + ok; + Other -> + Other + end. + +renegotiate_reuse_session(Socket, Data) -> + %% Make sure session is registered + ct:sleep(?SLEEP), + renegotiate(Socket, Data). + +renegotiate_immediately(Socket) -> + _ = ssl_test_lib:active_recv(Socket, 11), + ok = ssl:renegotiate(Socket), + {error, renegotiation_rejected} = ssl:renegotiate(Socket), + ct:sleep(?RENEGOTIATION_DISABLE_TIME + ?SLEEP), + ok = ssl:renegotiate(Socket), + ct:log("Renegotiated again"), + ssl:send(Socket, "Hello world"), + ok. + +renegotiate_rejected(Socket) -> + _ = ssl_test_lib:active_recv(Socket, 11), + {error, renegotiation_rejected} = ssl:renegotiate(Socket), + {error, renegotiation_rejected} = ssl:renegotiate(Socket), + ct:sleep(?RENEGOTIATION_DISABLE_TIME +1), + {error, renegotiation_rejected} = ssl:renegotiate(Socket), + ct:log("Failed to renegotiate again"), + ssl:send(Socket, "Hello world"), + ok. + +%% First two clauses handles 1/n-1 splitting countermeasure Rizzo/Duong-Beast +treashold(N, {3,0}) -> + (N div 2) + 1; +treashold(N, {3,1}) -> + (N div 2) + 1; +treashold(N, _) -> + N + 1. + +erlang_ssl_receive(Socket, Data) -> + case ssl_test_lib:active_recv(Socket, length(Data)) of + Data -> + ok; + Other -> + ct:fail({{expected, Data}, {got, Other}}) + end. diff --git a/lib/ssl/test/ssl_session_SUITE.erl b/lib/ssl/test/ssl_session_SUITE.erl new file mode 100644 index 0000000000..aa79698a72 --- /dev/null +++ b/lib/ssl/test/ssl_session_SUITE.erl @@ -0,0 +1,377 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-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(ssl_session_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include("tls_handshake.hrl"). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-define(SLEEP, 500). +-define(EXPIRE, 10). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'} + ]. + +groups() -> + [{'dtlsv1.2', [], session_tests()}, + {'dtlsv1', [], session_tests()}, + {'tlsv1.3', [], session_tests()}, + {'tlsv1.2', [], session_tests()}, + {'tlsv1.1', [], session_tests()}, + {'tlsv1', [], session_tests()}, + {'sslv3', [], session_tests()} + ]. + +session_tests() -> + [reuse_session, + reuse_session_expired, + server_does_not_want_to_reuse_session, + no_reuses_session_server_restart_new_cert, + no_reuses_session_server_restart_new_cert_file]. + + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + Config = ssl_test_lib:make_rsa_cert(Config0), + ssl_test_lib:make_dsa_cert(Config) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto). + +init_per_group(GroupName, Config) -> + ssl_test_lib:clean_tls_version(Config), + case ssl_test_lib:is_tls_version(GroupName) andalso ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + _ -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl:start(), + Config; + false -> + {skip, "Missing crypto support"} + end + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(reuse_session_expired, Config) -> + ssl:stop(), + application:load(ssl), + ssl_test_lib:clean_env(), + application:set_env(ssl, session_lifetime, ?EXPIRE), + application:set_env(ssl, session_delay_cleanup_time, 500), + ssl:start(), + ct:timetrap({seconds, 30}), + Config; +init_per_testcase(_, Config) -> + ct:timetrap({seconds, 15}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +reuse_session() -> + [{doc,"Test reuse of sessions (short handshake)"}]. +reuse_session(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + + ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config). +%%-------------------------------------------------------------------- +reuse_session_expired() -> + [{doc,"Test sessions is not reused when it has expired"}]. +reuse_session_expired(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server0 = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {tcp_options, [{active, false}]}, + {options, ServerOpts}]), + Port0 = ssl_test_lib:inet_port(Server0), + + Client0 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]), + Server0 ! listen, + + Client1 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, ClientOpts}]), + + SID = receive + {Client0, Id0} -> + Id0 + end, + + receive + {Client1, SID} -> + ok + after ?SLEEP -> + ct:fail(session_not_reused) + end, + + Server0 ! listen, + + %% Make sure session is unregistered due to expiration + ct:sleep((?EXPIRE*2)), + + make_sure_expired(Hostname, Port0, SID), + + Client2 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, ClientOpts}]), + receive + {Client2, SID} -> + ct:fail(session_reused_when_session_expired); + {Client2, _} -> + ok + end, + process_flag(trap_exit, false), + ssl_test_lib:close(Server0), + ssl_test_lib:close(Client0), + ssl_test_lib:close(Client1), + ssl_test_lib:close(Client2). + +make_sure_expired(Host, Port, Id) -> + {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + ClientCache = element(2, State), + + case ssl_session_cache:lookup(ClientCache, {{Host, Port}, Id}) of + undefined -> + ok; + #session{is_resumable = false} -> + ok; + _ -> + ct:sleep(?SLEEP), + make_sure_expired(Host, Port, Id) + end. + +%%-------------------------------------------------------------------- +server_does_not_want_to_reuse_session() -> + [{doc,"Test reuse of sessions (short handshake)"}]. +server_does_not_want_to_reuse_session(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {options, [{reuse_session, fun(_,_,_,_) -> + false + end} | + ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client0 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, no_result, []}}, + {from, self()}, {options, ClientOpts}]), + SessionInfo = + receive + {Server, Info} -> + Info + end, + + Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, + + %% Make sure session is registered + ct:sleep(?SLEEP), + + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {from, self()}, {options, ClientOpts}]), + receive + {Client1, SessionInfo} -> + ct:fail(session_reused_when_server_does_not_want_to); + {Client1, _Other} -> + ok + end, + ssl_test_lib:close(Client0), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client1). + +no_reuses_session_server_restart_new_cert() -> + [{doc,"Check that a session is not reused if the server is restarted with a new cert."}]. +no_reuses_session_server_restart_new_cert(Config) when is_list(Config) -> + + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + DsaServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config), + DsaClientOpts = ssl_test_lib:ssl_options(client_dsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client0 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, no_result, []}}, + {from, self()}, {options, ClientOpts}]), + SessionInfo = + receive + {Server, Info} -> + Info + end, + + %% Make sure session is registered + ct:sleep(?SLEEP), + Monitor = erlang:monitor(process, Server), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client0), + receive + {'DOWN', Monitor, _, _, _} -> + ok + end, + + Server1 = + ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{reuseaddr, true} | DsaServerOpts]}]), + + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {from, self()}, {options, DsaClientOpts}]), + receive + {Client1, SessionInfo} -> + ct:fail(session_reused_when_server_has_new_cert); + {Client1, _Other} -> + ok + end, + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client1). + +%%-------------------------------------------------------------------- +no_reuses_session_server_restart_new_cert_file() -> + [{doc,"Check that a session is not reused if a server is restarted with a new " + "cert contained in a file with the same name as the old cert."}]. + +no_reuses_session_server_restart_new_cert_file(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + DsaServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), + + NewServerOpts0 = ssl_test_lib:new_config(PrivDir, ServerOpts), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {options, NewServerOpts0}]), + Port = ssl_test_lib:inet_port(Server), + Client0 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, no_result, []}}, + {from, self()}, {options, ClientOpts}]), + SessionInfo = + receive + {Server, Info} -> + Info + end, + + %% Make sure session is registered and we get + %% new file time stamp when calling new_config! + ct:sleep(?SLEEP* 2), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client0), + + ssl:clear_pem_cache(), + + NewServerOpts1 = ssl_test_lib:new_config(PrivDir, DsaServerOpts), + + Server1 = + ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{reuseaddr, true} | NewServerOpts1]}]), + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {from, self()}, {options, ClientOpts}]), + receive + {Client1, SessionInfo} -> + ct:fail(session_reused_when_server_has_new_cert); + {Client1, _Other} -> + ok + end, + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client1). + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl index 7629d75100..e68ea2c99d 100644 --- a/lib/ssl/test/ssl_sni_SUITE.erl +++ b/lib/ssl/test/ssl_sni_SUITE.erl @@ -36,7 +36,6 @@ all() -> [{group, 'tlsv1.2'}, {group, 'tlsv1.1'}, {group, 'tlsv1'}, - {group, 'sslv3'}, {group, 'dtlsv1.2'}, {group, 'dtlsv1'} ]. @@ -46,7 +45,6 @@ groups() -> {'tlsv1.2', [], sni_tests()}, {'tlsv1.1', [], sni_tests()}, {'tlsv1', [], sni_tests()}, - {'sslv3', [], sni_tests()}, {'dtlsv1.2', [], sni_tests()}, {'dtlsv1', [], sni_tests()} ]. @@ -61,7 +59,8 @@ sni_tests() -> dns_name, ip_fallback, no_ip_fallback, - dns_name_reuse]. + dns_name_reuse, + customize_hostname_check]. init_per_suite(Config0) -> catch crypto:stop(), @@ -88,12 +87,10 @@ end_per_suite(_) -> ssl:stop(), application:stop(crypto). -init_per_testcase(TestCase, Config) when TestCase == ip_fallback; - TestCase == no_ip_fallback; - TestCase == dns_name_reuse -> +init_per_testcase(customize_hostname_check, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]), - ct:timetrap({seconds, 20}), + ssl_test_lib:clean_start(), + ct:timetrap({seconds, 5}), Config; init_per_testcase(_TestCase, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), @@ -236,7 +233,60 @@ dns_name_reuse(Config) -> {mfa, {ssl_test_lib, session_info_result, []}}, {from, self()}, {options, [{verify, verify_peer} | ClientConf]}]), - ssl_test_lib:check_client_alert(Client1, handshake_failure). + ssl_test_lib:check_client_alert(Client1, handshake_failure), + ssl_test_lib:close(Client0). + + +customize_hostname_check() -> + [{doc,"Test option customize_hostname_check."}]. +customize_hostname_check(Config) when is_list(Config) -> + Ext = [#'Extension'{extnID = ?'id-ce-subjectAltName', + extnValue = [{dNSName, "*.example.org"}], + critical = false} + ], + #{server_config := ServerOpts0, + client_config := ClientOpts0} = ssl_test_lib:make_cert_chains_pem(rsa, [{server_chain, + [[], + [], + [{extensions, Ext}] + ]}], + Config, "https_hostname_convention"), + ClientOpts = ssl_test_lib:ssl_options(ClientOpts0, Config), + ServerOpts = ssl_test_lib:ssl_options(ServerOpts0, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + CustomFun = public_key:pkix_verify_hostname_match_fun(https), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, + [{verify, verify_peer}, + {server_name_indication, "other.example.org"}, + {customize_hostname_check, + [{match_fun, CustomFun}]} | ClientOpts] + }]), + ssl_test_lib:check_result(Server, ok, Client, ok), + + Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, + + Client1 = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{verify, verify_peer}, + {server_name_indication, "other.example.org"} | ClientOpts]} + ]), + ssl_test_lib:check_client_alert(Server, Client1, handshake_failure). %%-------------------------------------------------------------------- %% Internal Functions ------------------------------------------------ diff --git a/lib/ssl/test/ssl_socket_SUITE.erl b/lib/ssl/test/ssl_socket_SUITE.erl new file mode 100644 index 0000000000..d648f2f9e1 --- /dev/null +++ b/lib/ssl/test/ssl_socket_SUITE.erl @@ -0,0 +1,437 @@ +%% +%% 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(ssl_socket_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"). + +-define(SLEEP, 500). +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + {group, tls}, + {group, dtls} + ]. + +groups() -> + [ + {tls,[], socket_tests() ++ raw_inet_opt()}, + {dtls,[], socket_tests()} + ]. + +socket_tests() -> + [ + getstat, + socket_options, + invalid_inet_get_option, + invalid_inet_get_option_not_list, + invalid_inet_get_option_improper_list, + invalid_inet_set_option, + invalid_inet_set_option_not_list, + invalid_inet_set_option_improper_list + ]. + +raw_inet_opt() -> + [ + raw_inet_option + ]. + + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + +init_per_group(dtls, Config) -> + [{protocol_opts, [{protocol, dtls}]} | proplists:delete(protocol_opts, Config)]; +init_per_group(tls, Config) -> + [{protocol_opts, [{protocol, tls}]} | proplists:delete(protocol_opts, Config)]; +init_per_group(_GroupName, Config) -> + [{client_type, erlang}, + {server_type, erlang} | Config]. + +end_per_group(_GroupName, Config) -> + Config. + +init_per_testcase(raw_inet_option, Config) -> + ct:timetrap({seconds, 5}), + case os:type() of + {unix,linux} -> + Config; + _ -> + {skip, "Raw options are platform-specific"} + end; +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, 5}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +getstat() -> + [{doc,"Test API function getstat/2"}]. + +getstat(Config) when is_list(Config) -> + ClientOpts = ?config(client_rsa_opts, Config), + ServerOpts = ?config(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server1 = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ServerOpts]}]), + Port1 = ssl_test_lib:inet_port(Server1), + Server2 = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ServerOpts]}]), + Port2 = ssl_test_lib:inet_port(Server2), + {ok, ActiveC} = rpc:call(ClientNode, ssl, connect, + [Hostname,Port1,[{active, once}|ClientOpts]]), + {ok, PassiveC} = rpc:call(ClientNode, ssl, connect, + [Hostname,Port2,[{active, false}|ClientOpts]]), + + ct:log("Testcase ~p, Client ~p Servers ~p, ~p ~n", + [self(), self(), Server1, Server2]), + + %% We only check that the values are non-zero initially + %% (due to the handshake), and that sending more changes the values. + + %% Passive socket. + + {ok, InitialStats} = ssl:getstat(PassiveC), + ct:pal("InitialStats ~p~n", [InitialStats]), + [true] = lists:usort([0 =/= proplists:get_value(Name, InitialStats) + || Name <- [recv_cnt, recv_oct, recv_avg, recv_max, send_cnt, send_oct, send_avg, send_max]]), + + ok = ssl:send(PassiveC, "Hello world"), + wait_for_send(PassiveC), + {ok, SStats} = ssl:getstat(PassiveC, [send_cnt, send_oct]), + ct:pal("SStats ~p~n", [SStats]), + [true] = lists:usort([proplists:get_value(Name, SStats) =/= proplists:get_value(Name, InitialStats) + || Name <- [send_cnt, send_oct]]), + + %% Active socket. + + {ok, InitialAStats} = ssl:getstat(ActiveC), + ct:pal("InitialAStats ~p~n", [InitialAStats]), + [true] = lists:usort([0 =/= proplists:get_value(Name, InitialAStats) + || Name <- [recv_cnt, recv_oct, recv_avg, recv_max, send_cnt, send_oct, send_avg, send_max]]), + + _ = receive + {ssl, ActiveC, _} -> + ok + after + ?SLEEP -> + exit(timeout) + end, + + ok = ssl:send(ActiveC, "Hello world"), + wait_for_send(ActiveC), + {ok, ASStats} = ssl:getstat(ActiveC, [send_cnt, send_oct]), + ct:pal("ASStats ~p~n", [ASStats]), + [true] = lists:usort([proplists:get_value(Name, ASStats) =/= proplists:get_value(Name, InitialAStats) + || Name <- [send_cnt, send_oct]]), + + ok. +%%-------------------------------------------------------------------- +socket_options() -> + [{doc,"Test API function getopts/2 and setopts/2"}]. + +socket_options(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Values = [{mode, list}, {active, true}], + %% Shall be the reverse order of Values! + Options = [active, mode], + + NewValues = [{mode, binary}, {active, once}], + %% Shall be the reverse order of NewValues! + NewOptions = [active, mode], + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, socket_options_result, + [Options, Values, NewOptions, NewValues]}}, + {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, socket_options_result, + [Options, Values, NewOptions, NewValues]}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + + {ok, Listen} = ssl:listen(0, ServerOpts), + {ok,[{mode,list}]} = ssl:getopts(Listen, [mode]), + ok = ssl:setopts(Listen, [{mode, binary}]), + {ok,[{mode, binary}]} = ssl:getopts(Listen, [mode]), + {ok,[{recbuf, _}]} = ssl:getopts(Listen, [recbuf]), + ssl:close(Listen). + +%%-------------------------------------------------------------------- +raw_inet_option() -> + [{doc,"Ensure that a single 'raw' option is passed to ssl:listen correctly."}]. + +raw_inet_option(Config) when is_list(Config) -> + % 'raw' option values are platform-specific; these are the Linux values: + IpProtoTcp = 6, + % Use TCP_KEEPIDLE, because (e.g.) TCP_MAXSEG can't be read back reliably. + TcpKeepIdle = 4, + KeepAliveTimeSecs = 55, + LOptions = [{raw, IpProtoTcp, TcpKeepIdle, <<KeepAliveTimeSecs:32/native>>}], + {ok, LSocket} = ssl:listen(0, LOptions), + % Per http://www.erlang.org/doc/man/inet.html#getopts-2, we have to specify + % exactly which raw option we want, and the size of the buffer. + {ok, [{raw, IpProtoTcp, TcpKeepIdle, <<KeepAliveTimeSecs:32/native>>}]} = + ssl:getopts(LSocket, [{raw, IpProtoTcp, TcpKeepIdle, 4}]). + +%%-------------------------------------------------------------------- + +invalid_inet_get_option() -> + [{doc,"Test handling of invalid inet options in getopts"}]. + +invalid_inet_get_option(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, get_invalid_inet_option, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +invalid_inet_get_option_not_list() -> + [{doc,"Test handling of invalid type in getopts"}]. + +invalid_inet_get_option_not_list(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, get_invalid_inet_option_not_list, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +invalid_inet_get_option_improper_list() -> + [{doc,"Test handling of invalid type in getopts"}]. + +invalid_inet_get_option_improper_list(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, get_invalid_inet_option_improper_list, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +invalid_inet_set_option() -> + [{doc,"Test handling of invalid inet options in setopts"}]. + +invalid_inet_set_option(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, set_invalid_inet_option, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +invalid_inet_set_option_not_list() -> + [{doc,"Test handling of invalid type in setopts"}]. + +invalid_inet_set_option_not_list(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, set_invalid_inet_option_not_list, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +invalid_inet_set_option_improper_list() -> + [{doc,"Test handling of invalid tye in setopts"}]. + +invalid_inet_set_option_improper_list(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, set_invalid_inet_option_improper_list, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- +socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) -> + %% Test get/set emulated opts + {ok, DefaultValues} = ssl:getopts(Socket, Options), + ssl:setopts(Socket, NewValues), + {ok, NewValues} = ssl:getopts(Socket, NewOptions), + %% Test get/set inet opts + {ok,[{reuseaddr, _}]} = ssl:getopts(Socket, [reuseaddr]), + {ok, All} = ssl:getopts(Socket, []), + ct:log("All opts ~p~n", [All]), + ok. + +get_invalid_inet_option(Socket) -> + {error, {options, {socket_options, foo, _}}} = ssl:getopts(Socket, [foo]), + ok. + +get_invalid_inet_option_not_list(Socket) -> + {error, {options, {socket_options, some_invalid_atom_here}}} + = ssl:getopts(Socket, some_invalid_atom_here), + ok. + +get_invalid_inet_option_improper_list(Socket) -> + {error, {options, {socket_options, foo,_}}} = ssl:getopts(Socket, [packet | foo]), + ok. + +set_invalid_inet_option(Socket) -> + {error, {options, {socket_options, {packet, foo}}}} = ssl:setopts(Socket, [{packet, foo}]), + {error, {options, {socket_options, {header, foo}}}} = ssl:setopts(Socket, [{header, foo}]), + {error, {options, {socket_options, {active, foo}}}} = ssl:setopts(Socket, [{active, foo}]), + {error, {options, {socket_options, {mode, foo}}}} = ssl:setopts(Socket, [{mode, foo}]), + ok. + +set_invalid_inet_option_not_list(Socket) -> + {error, {options, {not_a_proplist, some_invalid_atom_here}}} + = ssl:setopts(Socket, some_invalid_atom_here), + ok. + +set_invalid_inet_option_improper_list(Socket) -> + {error, {options, {not_a_proplist, [{packet, 0} | {foo, 2}]}}} = + ssl:setopts(Socket, [{packet, 0} | {foo, 2}]), + ok. + +wait_for_send(Socket) -> + %% Make sure TLS process processed send message event + _ = ssl:connection_information(Socket). + diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 3b161a0c8a..5dd5fc45af 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -45,9 +45,18 @@ run_where(_, ipv6) -> Host = rpc:call(ServerNode, net_adm, localhost, []), {ClientNode, ServerNode, Host}. -node_to_hostip(Node) -> +node_to_hostip(Node, Role) -> [_ , Host] = string:tokens(atom_to_list(Node), "@"), {ok, Address} = inet:getaddr(Host, inet), + %% Convert client addresses in 127.0.0.0/24 subnet to the atom 'localhost'. + %% This is a workaround for testcase problems caused by the fact that + %% inet:peername/1 and inet:getaddr/2 return different addresses when + %% running on localhost. + normalize_loopback(Address, Role). + +normalize_loopback({127,_,_,_}, client) -> + localhost; +normalize_loopback(Address, _) -> Address. start_server(Args) -> @@ -159,6 +168,7 @@ connect(ListenSocket, Node, N, _, Timeout, SslOpts, [_|_] =ContOpts) -> case ssl:handshake(AcceptSocket, SslOpts, Timeout) of {ok, Socket0, Ext} -> + [_|_] = maps:get(sni, Ext), ct:log("Ext ~p:~n", [Ext]), ct:log("~p:~p~nssl:handshake_continue(~p,~p,~p)~n", [?MODULE,?LINE, Socket0, ContOpts,Timeout]), case ssl:handshake_continue(Socket0, ContOpts, Timeout) of @@ -208,6 +218,55 @@ start_server_transport_control(Args) -> Result end. +start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, OpensslServerOpts, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ClientOpts = ErlangClientOpts ++ ClientOpts0, + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = case OpensslServerOpts of + [] -> + ["s_server", "-accept", + integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile,"-key", KeyFile]; + [Opt, Value] -> + ["s_server", Opt, Value, "-accept", + integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile,"-key", KeyFile] + end, + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + active_recv, [length(Data)]}}, + {options, ClientOpts}]), + + Callback(Client, OpensslPort), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false). + transport_accept_abuse(Opts) -> Port = proplists:get_value(port, Opts), @@ -223,6 +282,34 @@ transport_accept_abuse(Opts) -> _ = ssl:handshake(AcceptSocket, infinity), Pid ! {self(), ok}. +start_erlang_server_and_openssl_client_with_opts(Config, ErlangServerOpts, OpenSSLClientOpts, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ServerOpts = ErlangServerOpts ++ ServerOpts0, + + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, active_recv, [length(Data)]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_client"] ++ OpenSSLClientOpts ++ ["-msg", "-connect", + hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), + ssl_test_lib:version_flag(Version)], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + + Callback(Server, OpenSslPort), + + ssl_test_lib:close(Server), + + ssl_test_lib:close_port(OpenSslPort), + process_flag(trap_exit, false). transport_switch_control(Opts) -> Port = proplists:get_value(port, Opts), @@ -392,14 +479,16 @@ close(Pid, Timeout) -> exit(Pid, kill) end. -check_result(Server, ServerMsg, Client, ClientMsg) -> +check_result(Server, ServerMsg, Client, ClientMsg) -> + {ClientIP, ClientPort} = get_ip_port(ServerMsg), receive {Server, ServerMsg} -> check_result(Client, ClientMsg); - + %% Workaround to accept local addresses (127.0.0.0/24) + {Server, {ok, {{127,_,_,_}, ClientPort}}} when ClientIP =:= localhost -> + check_result(Client, ClientMsg); {Client, ClientMsg} -> check_result(Server, ServerMsg); - {Port, {data,Debug}} when is_port(Port) -> ct:log("~p:~p~n Openssl ~s~n",[?MODULE,?LINE, Debug]), check_result(Server, ServerMsg, Client, ClientMsg); @@ -412,10 +501,14 @@ check_result(Server, ServerMsg, Client, ClientMsg) -> ct:fail(Reason) end. -check_result(Pid, Msg) -> +check_result(Pid, Msg) -> + {ClientIP, ClientPort} = get_ip_port(Msg), receive {Pid, Msg} -> ok; + %% Workaround to accept local addresses (127.0.0.0/24) + {Pid, {ok, {{127,_,_,_}, ClientPort}}} when ClientIP =:= localhost -> + ok; {Port, {data,Debug}} when is_port(Port) -> ct:log("~p:~p~n Openssl ~s~n",[?MODULE,?LINE, Debug]), check_result(Pid,Msg); @@ -427,37 +520,63 @@ check_result(Pid, Msg) -> {got, Unexpected}}, ct:fail(Reason) end. + + +get_ip_port({ok,{ClientIP, ClientPort}}) -> + {ClientIP, ClientPort}; +get_ip_port(_) -> + {undefined, undefined}. + + check_server_alert(Pid, Alert) -> receive - {Pid, {error, {tls_alert, {Alert, _}}}} -> + {Pid, {error, {tls_alert, {Alert, STxt}}}} -> + check_server_txt(STxt), + ok; + {Pid, {error, closed}} -> ok end. check_server_alert(Server, Client, Alert) -> receive - {Server, {error, {tls_alert, {Alert, _}}}} -> - receive - {Client, {error, {tls_alert, {Alert, _}}}} -> - ok; - {Client, {error, closed}} -> - ok - end + {Server, {error, {tls_alert, {Alert, STxt}}}} -> + check_server_txt(STxt), + check_client_alert(Client, Alert) end. check_client_alert(Pid, Alert) -> receive - {Pid, {error, {tls_alert, {Alert, _}}}} -> + {Pid, {error, {tls_alert, {Alert, CTxt}}}} -> + check_client_txt(CTxt), + ok; + {Pid, {ssl_error, _, {tls_alert, {Alert, CTxt}}}} -> + check_client_txt(CTxt), + ok; + {Pid, {error, closed}} -> ok end. check_client_alert(Server, Client, Alert) -> receive - {Client, {error, {tls_alert, {Alert, _}}}} -> - receive - {Server, {error, {tls_alert, {Alert, _}}}} -> - ok; - {Server, {error, closed}} -> - ok - end + {Client, {error, {tls_alert, {Alert, CTxt}}}} -> + check_client_txt(CTxt), + check_server_alert(Server, Alert); + {Client, {ssl_error, _, {tls_alert, {Alert, CTxt}}}} -> + check_client_txt(CTxt), + ok; + {Client, {error, closed}} -> + ok end. +check_server_txt("TLS server" ++ _) -> + ok; +check_server_txt("DTLS server" ++ _) -> + ok; +check_server_txt(Txt) -> + ct:fail({expected_server, {got, Txt}}). +check_client_txt("TLS client" ++ _) -> + ok; +check_client_txt("DTLS client" ++ _) -> + ok; +check_client_txt(Txt) -> + ct:fail({expected_server, {got, Txt}}). wait_for_result(Server, ServerMsg, Client, ClientMsg) -> receive @@ -529,8 +648,7 @@ cert_options(Config) -> "badcert.pem"]), BadKeyFile = filename:join([proplists:get_value(priv_dir, Config), "badkey.pem"]), - PskSharedSecret = <<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>>, - + [{client_opts, [{cacertfile, ClientCaCertFile}, {certfile, ClientCertFile}, {keyfile, ClientKeyFile}]}, @@ -544,30 +662,6 @@ cert_options(Config) -> {ssl_imp, new}]}, {server_opts, [{ssl_imp, new},{reuseaddr, true}, {cacertfile, ServerCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, - {client_psk, [{ssl_imp, new}, - {psk_identity, "Test-User"}, - {user_lookup_fun, {fun user_lookup/3, PskSharedSecret}}]}, - {server_psk, [{ssl_imp, new},{reuseaddr, true}, - {certfile, ServerCertFile}, {keyfile, ServerKeyFile}, - {user_lookup_fun, {fun user_lookup/3, PskSharedSecret}}]}, - {server_psk_hint, [{ssl_imp, new},{reuseaddr, true}, - {certfile, ServerCertFile}, {keyfile, ServerKeyFile}, - {psk_identity, "HINT"}, - {user_lookup_fun, {fun user_lookup/3, PskSharedSecret}}]}, - {server_psk_anon, [{ssl_imp, new},{reuseaddr, true}, - {user_lookup_fun, {fun user_lookup/3, PskSharedSecret}}]}, - {server_psk_anon_hint, [{ssl_imp, new},{reuseaddr, true}, - {psk_identity, "HINT"}, - {user_lookup_fun, {fun user_lookup/3, PskSharedSecret}}]}, - {client_srp, [{ssl_imp, new}, - {srp_identity, {"Test-User", "secret"}}]}, - {server_srp, [{ssl_imp, new},{reuseaddr, true}, - {certfile, ServerCertFile}, {keyfile, ServerKeyFile}, - {user_lookup_fun, {fun user_lookup/3, undefined}}, - {ciphers, srp_suites()}]}, - {server_srp_anon, [{ssl_imp, new},{reuseaddr, true}, - {user_lookup_fun, {fun user_lookup/3, undefined}}, - {ciphers, srp_anon_suites()}]}, {server_verification_opts, [{ssl_imp, new},{reuseaddr, true}, {cacertfile, ClientCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, @@ -608,15 +702,32 @@ make_dsa_cert(Config) -> [{server_dsa_opts, ServerConf}, {server_dsa_verify_opts, [{verify, verify_peer} | ServerConf]}, - {client_dsa_opts, ClientConf}, - {server_srp_dsa, [{user_lookup_fun, {fun user_lookup/3, undefined}}, - {ciphers, srp_dss_suites()} | ServerConf]}, - {client_srp_dsa, [{srp_identity, {"Test-User", "secret"}} - | ClientConf]} + {client_dsa_opts, ClientConf} | Config]; false -> Config end. + + +make_cert_chains_der(Alg, UserConf) -> + ClientChain = proplists:get_value(client_chain, UserConf, default_cert_chain_conf()), + ServerChain = proplists:get_value(server_chain, UserConf, default_cert_chain_conf()), + CertChainConf = gen_conf(Alg, Alg, ClientChain, ServerChain), + public_key:pkix_test_data(CertChainConf). + +make_cert_chains_pem(Alg, UserConf, Config, Suffix) -> + ClientChain = proplists:get_value(client_chain, UserConf, default_cert_chain_conf()), + ServerChain = proplists:get_value(server_chain, UserConf, default_cert_chain_conf()), + CertChainConf = gen_conf(Alg, Alg, ClientChain, ServerChain), + ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(Alg) ++ Suffix]), + ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(Alg) ++ Suffix]), + GenCertData = public_key:pkix_test_data(CertChainConf), + Conf = x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase), + CConf = proplists:get_value(client_config, Conf), + SConf = proplists:get_value(server_config, Conf), + #{server_config => SConf, + client_config => CConf}. + make_rsa_cert_chains(UserConf, Config, Suffix) -> ClientChain = proplists:get_value(client_chain, UserConf, default_cert_chain_conf()), ServerChain = proplists:get_value(server_chain, UserConf, default_cert_chain_conf()), @@ -1084,7 +1195,15 @@ run_client_error(Opts) -> Options = proplists:get_value(options, Opts), ct:log("~p:~p~nssl:connect(~p, ~p, ~p)~n", [?MODULE,?LINE, Host, Port, Options]), Error = Transport:connect(Host, Port, Options), - Pid ! {self(), Error}. + case Error of + {error, _} -> + Pid ! {self(), Error}; + {ok, _Socket} -> + receive + {ssl_error, _, {tls_alert, _}} = SslError -> + Pid ! {self(), SslError} + end + end. accepters(N) -> accepters([], N). @@ -1106,6 +1225,52 @@ basic_test(COpts, SOpts, Config) -> gen_check_result(Server, SType, Client, CType), stop(Server, Client). +basic_alert(ClientOpts, ServerOpts, Config, Alert) -> + SType = proplists:get_value(server_type, Config), + CType = proplists:get_value(client_type, Config), + run_basic_alert(SType, CType, ClientOpts, ServerOpts, Config, Alert). + +run_basic_alert(erlang, erlang, ClientOpts, ServerOpts, Config, Alert) -> + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ServerOpts}]), + + Port = inet_port(Server), + + Client = start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + check_server_alert(Server, Client, Alert); +run_basic_alert(openssl = SType, erlang, ClientOpts, ServerOpts, Config, Alert) -> + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + {_Server, Port} = start_server(SType, ClientOpts, ServerOpts, Config), + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + Client = start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + check_client_alert(Client, Alert); +run_basic_alert(erlang, openssl = CType, ClientOpts, ServerOpts, Config, Alert) -> + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = start_server_error([{node, ServerNode}, {port, 0}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ServerOpts}]), + Port = inet_port(Server), + start_client(CType, Port, ClientOpts, Config), + + check_server_alert(Server, Alert). + + 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), @@ -1147,15 +1312,22 @@ start_basic_client(openssl, Version, Port, ClientOpts) -> OpenSslPort. 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", - Args0 = ["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"], + Ciphers = proplists:get_value(ciphers, ClientOpts, ssl:cipher_suites(default,Version)), + Groups0 = proplists:get_value(groups, ClientOpts), + CertArgs = openssl_cert_options(ClientOpts, client), + Exe = "openssl", + Args0 = case Groups0 of + undefined -> + ["s_client", "-verify", "2", "-port", integer_to_list(Port), cipher_flag(Version), + ciphers(Ciphers, Version), + ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"]; + Group -> + ["s_client", "-verify", "2", "-port", integer_to_list(Port), cipher_flag(Version), + ciphers(Ciphers, Version), "-groups", Group, + ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"] + end, Args = maybe_force_ipv4(Args0), OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), true = port_command(OpenSslPort, "Hello world"), @@ -1207,24 +1379,54 @@ start_server(openssl, ClientOpts, ServerOpts, Config) -> Port = inet_port(node()), Version = protocol_version(Config), Exe = "openssl", - CertArgs = openssl_cert_options(ServerOpts), - [Cipher|_] = proplists:get_value(ciphers, ClientOpts, ssl:cipher_suites(default,Version)), - Args = ["s_server", "-accept", integer_to_list(Port), "-cipher", - ssl_cipher_format:suite_map_to_openssl_str(Cipher), - ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"], + CertArgs = openssl_cert_options(ServerOpts, server), + Ciphers = proplists:get_value(ciphers, ClientOpts, ssl:cipher_suites(default,Version)), + Groups0 = proplists:get_value(groups, ServerOpts), + Args = case Groups0 of + undefined -> + ["s_server", "-accept", integer_to_list(Port), cipher_flag(Version), + ciphers(Ciphers, Version), + ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"]; + Group -> + ["s_server", "-accept", integer_to_list(Port), cipher_flag(Version), + ciphers(Ciphers, Version), "-groups", Group, + ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"] + end, 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), + Versions = protocol_versions(Config), Server = start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {ssl_test_lib, check_key_exchange_send_active, [KeyEx]}}, - {options, [{verify, verify_peer} | ServerOpts]}]), + {options, [{verify, verify_peer}, {versions, Versions} | ServerOpts]}]), {Server, inet_port(Server)}. + +cipher_flag('tlsv1.3') -> + "-ciphersuites"; +cipher_flag(_) -> + "-cipher". + +ciphers(Ciphers, Version) -> + Strs = [ssl_cipher_format:suite_map_to_openssl_str(Cipher) || Cipher <- Ciphers], + ciphers_concat(Version, Strs, ""). + +ciphers_concat(_, [], [":" | Acc]) -> + lists:append(lists:reverse(Acc)); +ciphers_concat('tlsv1.3' = Version, [Head| Tail], Acc) -> + case Head of + "TLS" ++ _ -> + ciphers_concat(Version, Tail, [":", Head | Acc]); + _ -> + ciphers_concat(Version, Tail, Acc) + end; +ciphers_concat(Version, [Head| Tail], Acc) -> + ciphers_concat(Version, Tail, [":", Head | Acc]). start_server_with_raw_key(erlang, ServerOpts, Config) -> {_, ServerNode, _} = ssl_test_lib:run_where(Config), @@ -1279,23 +1481,31 @@ stop(Client, Server) -> close(Client). -openssl_cert_options(ServerOpts) -> - Cert = proplists:get_value(certfile, ServerOpts, undefined), - Key = proplists:get_value(keyfile, ServerOpts, undefined), - CA = proplists:get_value(cacertfile, ServerOpts, undefined), +openssl_cert_options(Opts, Role) -> + Cert = proplists:get_value(certfile, Opts, undefined), + Key = proplists:get_value(keyfile, Opts, undefined), + CA = proplists:get_value(cacertfile, Opts, undefined), case CA of undefined -> case cert_option("-cert", Cert) ++ cert_option("-key", Key) of - [] -> + [] when Role == server -> ["-nocert"]; Other -> Other end; _ -> cert_option("-cert", Cert) ++ cert_option("-CAfile", CA) ++ - cert_option("-key", Key) ++ ["-verify", "2"] + cert_option("-key", Key) ++ openssl_verify(Opts) ++ ["2"] end. +openssl_verify(Opts) -> + case proplists:get_value(fail_if_no_peer_cert, Opts, undefined) of + true -> + ["-Verify"]; + _ -> + ["-verify"] + end. + cert_option(_, undefined) -> []; cert_option(Opt, Value) -> @@ -1623,6 +1833,8 @@ is_tls_version('dtlsv1.2') -> true; is_tls_version('dtlsv1') -> true; +is_tls_version('tlsv1.3') -> + true; is_tls_version('tlsv1.2') -> true; is_tls_version('tlsv1.1') -> @@ -1953,12 +2165,68 @@ check_sane_openssl_version(Version) -> false; {'tlsv1.1', "OpenSSL 0" ++ _} -> false; + {'tlsv1', "OpenSSL 0" ++ _} -> + false; {_, _} -> true end; false -> false end. +check_sane_openssl_renegotaite(Config, Version) when Version == 'tlsv1.1'; + Version == 'tlsv1.2' -> + case os:cmd("openssl version") of + "OpenSSL 1.0.1c" ++ _ -> + {skip, "Known renegotiation bug in OpenSSL"}; + "OpenSSL 1.0.1b" ++ _ -> + {skip, "Known renegotiation bug in OpenSSL"}; + "OpenSSL 1.0.1a" ++ _ -> + {skip, "Known renegotiation bug in OpenSSL"}; + "OpenSSL 1.0.1 " ++ _ -> + {skip, "Known renegotiation bug in OpenSSL"}; + _ -> + check_sane_openssl_renegotaite(Config) + end; +check_sane_openssl_renegotaite(Config, 'sslv3') -> + case os:cmd("openssl version") of + "OpenSSL 1" ++ _ -> + {skip, "Known renegotiation bug with sslv3 in OpenSSL"}; + _ -> + check_sane_openssl_renegotaite(Config) + end; +check_sane_openssl_renegotaite(Config, _) -> + check_sane_openssl_renegotaite(Config). + +check_sane_openssl_renegotaite(Config) -> + case os:cmd("openssl version") of + "OpenSSL 1.0.0" ++ _ -> + {skip, "Known renegotiation bug in OpenSSL"}; + "OpenSSL 0.9.8" ++ _ -> + {skip, "Known renegotiation bug in OpenSSL"}; + "OpenSSL 0.9.7" ++ _ -> + {skip, "Known renegotiation bug in OpenSSL"}; + _ -> + Config + end. + +workaround_openssl_s_clinent() -> + %% http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=683159 + %% https://bugs.archlinux.org/task/33919 + %% Bug seems to manifests it self if TLS version is not + %% explicitly specified + case os:cmd("openssl version") of + "OpenSSL 1.0.1c" ++ _ -> + ["-no_tls1_2"]; + "OpenSSL 1.0.1d" ++ _ -> + ["-no_tls1_2"]; + "OpenSSL 1.0.1e" ++ _ -> + ["-no_tls1_2"]; + "OpenSSL 1.0.1f" ++ _ -> + ["-no_tls1_2"]; + _ -> + [] + end. + enough_openssl_crl_support("OpenSSL 0." ++ _) -> false; enough_openssl_crl_support(_) -> true. @@ -2008,8 +2276,8 @@ filter_suites(Ciphers0, AtomVersion) -> ++ ssl_cipher:anonymous_suites(Version) ++ ssl_cipher:psk_suites(Version) ++ ssl_cipher:psk_suites_anon(Version) - ++ ssl_cipher:srp_suites() - ++ ssl_cipher:srp_suites_anon() + ++ ssl_cipher:srp_suites(Version) + ++ ssl_cipher:srp_suites_anon(Version) ++ ssl_cipher:rc4_suites(Version), Supported1 = ssl_cipher:filter_suites(Supported0), Supported2 = [ssl_cipher_format:suite_bin_to_map(S) || S <- Supported1], @@ -2126,6 +2394,14 @@ protocol_version(Config, atom) -> tls_record:protocol_version(protocol_version(Config, tuple)) end. +protocol_versions(Config) -> + Version = protocol_version(Config), + case Version of + 'tlsv1.3' -> %% TLS-1.3 servers shall also support 1.2 + ['tlsv1.3', 'tlsv1.2']; + _ -> + [Version] + end. protocol_options(Config, Options) -> Protocol = proplists:get_value(protocol, Config, tls), {Protocol, Opts} = lists:keyfind(Protocol, 1, Options), @@ -2149,7 +2425,8 @@ clean_env() -> application:unset_env(ssl, session_cache_server_max), application:unset_env(ssl, ssl_pem_cache_clean), application:unset_env(ssl, bypass_pem_cache), - application:unset_env(ssl, alert_timeout). + application:unset_env(ssl, alert_timeout), + application:unset_env(ssl, internal_active_n). clean_start() -> ssl:stop(), @@ -2443,3 +2720,78 @@ digest() -> _ -> {digest, sha1} end. + +kill_openssl() -> + case os:type() of + {unix, _} -> + os:cmd("pkill openssl"); + {win32, _} -> + os:cmd("cmd.exe /C \"taskkill /IM openssl.exe /F\"") + end. + +hostname_format(Hostname) -> + case lists:member($., Hostname) of + true -> + Hostname; + false -> + "localhost" + end. + +erlang_ssl_receive_and_assert_negotiated_protocol(Socket, Protocol, Data) -> + case ssl:negotiated_protocol(Socket) of + {ok, Protocol} -> + active_recv(Socket, length(Data)); + Result -> + {error, {{expected, Protocol}, {got, Result}}} + end. + +check_openssl_npn_support(Config) -> + HelpText = os:cmd("openssl s_client --help"), + case string:str(HelpText, "nextprotoneg") of + 0 -> + {skip, "Openssl not compiled with nextprotoneg support"}; + _ -> + Config + end. + +new_config(PrivDir, ServerOpts0) -> + CaCertFile = proplists:get_value(cacertfile, ServerOpts0), + CertFile = proplists:get_value(certfile, ServerOpts0), + KeyFile = proplists:get_value(keyfile, ServerOpts0), + NewCaCertFile = filename:join(PrivDir, "new_ca.pem"), + NewCertFile = filename:join(PrivDir, "new_cert.pem"), + NewKeyFile = filename:join(PrivDir, "new_key.pem"), + file:copy(CaCertFile, NewCaCertFile), + file:copy(CertFile, NewCertFile), + file:copy(KeyFile, NewKeyFile), + ServerOpts1 = proplists:delete(cacertfile, ServerOpts0), + ServerOpts2 = proplists:delete(certfile, ServerOpts1), + ServerOpts = proplists:delete(keyfile, ServerOpts2), + + {ok, PEM} = file:read_file(NewCaCertFile), + ct:log("CA file content: ~p~n", [public_key:pem_decode(PEM)]), + + [{cacertfile, NewCaCertFile}, {certfile, NewCertFile}, + {keyfile, NewKeyFile} | ServerOpts]. + +sane_openssl_alpn_npn_renegotiate() -> + case os:cmd("openssl version") of + "LibreSSL 2.9.1" ++ _ -> + false; + "LibreSSL 2.6.4" ++ _ -> + false; + "OpenSSL 1.1.1a-freebsd" ++ _ -> + false; + _ -> + true + end. + +openssl_sane_dtls_alpn() -> + case os:cmd("openssl version") of + "OpenSSL 1.1.0g" ++ _ -> + false; + "OpenSSL 1.1.1a" ++ _ -> + false; + _-> + openssl_sane_dtls() + end. diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl deleted file mode 100644 index 07abddbcf7..0000000000 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ /dev/null @@ -1,2021 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% 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 -%% -%% 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_to_openssl_SUITE). - -%% Note: This directive should only be used in test suites. --compile(export_all). - --include_lib("common_test/include/ct.hrl"). - --define(SLEEP, 1000). --define(OPENSSL_RENEGOTIATE, "R\n"). --define(OPENSSL_QUIT, "Q\n"). --define(OPENSSL_GARBAGE, "P\n"). --define(EXPIRE, 10). - -%%-------------------------------------------------------------------- -%% Common Test interface functions ----------------------------------- -%%-------------------------------------------------------------------- - -all() -> - case ssl_test_lib:openssl_sane_dtls() of - true -> - [{group, 'tlsv1.2'}, - {group, 'tlsv1.1'}, - {group, 'tlsv1'}, - {group, 'sslv3'}, - {group, 'dtlsv1.2'}, - {group, 'dtlsv1'}]; - false -> - [{group, 'tlsv1.2'}, - {group, 'tlsv1.1'}, - {group, 'tlsv1'}, - {group, 'sslv3'}] - end. - -groups() -> - case ssl_test_lib:openssl_sane_dtls() of - true -> - [{'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 -> - [{'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. - -all_versions_tests() -> - [ - erlang_client_openssl_server, - erlang_server_openssl_client, - erlang_client_openssl_server_dsa_cert, - erlang_server_openssl_client_dsa_cert, - erlang_client_openssl_server_anon, - erlang_server_openssl_client_anon, - erlang_server_openssl_client_anon_with_cert, - erlang_server_openssl_client_reuse_session, - erlang_client_openssl_server_renegotiate, - erlang_client_openssl_server_renegotiate_after_client_data, - 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, - ssl2_erlang_server_openssl_client - ]. - -dtls_all_versions_tests() -> - 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, - erlang_client_openssl_server_anon, - erlang_server_openssl_client_anon, - erlang_server_openssl_client_anon_with_cert, - erlang_server_openssl_client_reuse_session, - erlang_client_openssl_server_renegotiate, - erlang_client_openssl_server_nowrap_seqnum, - erlang_server_openssl_client_nowrap_seqnum, - ciphers_rsa_signed_certs, - ciphers_dsa_signed_certs - %%expired_session - ]. - -alpn_tests() -> - [erlang_client_alpn_openssl_server_alpn, - erlang_server_alpn_openssl_client_alpn, - erlang_client_alpn_openssl_server, - erlang_client_openssl_server_alpn, - erlang_server_alpn_openssl_client, - erlang_server_openssl_client_alpn, - erlang_client_alpn_openssl_server_alpn_renegotiate, - erlang_server_alpn_openssl_client_alpn_renegotiate, - erlang_client_alpn_npn_openssl_server_alpn_npn, - erlang_server_alpn_npn_openssl_client_alpn_npn]. - -npn_tests() -> - [erlang_client_openssl_server_npn, - erlang_server_openssl_client_npn, - erlang_server_openssl_client_npn_renegotiate, - erlang_client_openssl_server_npn_renegotiate, - erlang_server_openssl_client_npn_only_client, - erlang_server_openssl_client_npn_only_server, - erlang_client_openssl_server_npn_only_client, - erlang_client_openssl_server_npn_only_server]. - -sni_server_tests() -> - [erlang_server_openssl_client_sni_match, - erlang_server_openssl_client_sni_match_fun, - erlang_server_openssl_client_sni_no_match, - erlang_server_openssl_client_sni_no_match_fun, - erlang_server_openssl_client_sni_no_header, - erlang_server_openssl_client_sni_no_header_fun]. - - -init_per_suite(Config0) -> - case os:find_executable("openssl") of - false -> - {skip, "Openssl not found"}; - _ -> - ct:pal("Version: ~p", [os:cmd("openssl version")]), - catch crypto:stop(), - try crypto:start() of - ok -> - ssl_test_lib:clean_start(), - Config = - case ssl_test_lib:openssl_dsa_support() of - true -> - Config1 = ssl_test_lib:make_rsa_cert(Config0), - ssl_test_lib:make_dsa_cert(Config1); - false -> - ssl_test_lib:make_rsa_cert(Config0) - end, - ssl_test_lib:cipher_restriction(Config) - catch _:_ -> - {skip, "Crypto did not start"} - end - end. - -end_per_suite(_Config) -> - ssl:stop(), - application:stop(crypto). - - -init_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of - true -> - case ssl_test_lib:supports_ssl_tls_version(GroupName) of - true -> - case ssl_test_lib:check_sane_openssl_version(GroupName) of - true -> - ssl_test_lib:init_tls_version(GroupName, Config); - false -> - {skip, openssl_does_not_support_version} - end; - false -> - {skip, openssl_does_not_support_version} - end; - _ -> - ssl:start(), - Config - end. - -end_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of - true -> - ssl_test_lib:clean_tls_version(Config); - false -> - Config - end. - -init_per_testcase(expired_session, Config) -> - ct:timetrap(?EXPIRE * 1000 * 5), - ssl:stop(), - application:load(ssl), - application:set_env(ssl, session_lifetime, ?EXPIRE), - ssl:start(), - Config; - -init_per_testcase(TestCase, Config) when - TestCase == ciphers_dsa_signed_certs; - TestCase == erlang_client_openssl_server_dsa_cert; - TestCase == erlang_server_openssl_client_dsa_cert; - TestCase == erlang_client_openssl_server_dsa_cert; - TestCase == erlang_server_openssl_client_dsa_cert -> - case ssl_test_lib:openssl_dsa_support() andalso ssl_test_lib:is_sane_oppenssl_client() of - true -> - special_init(TestCase, Config); - false -> - {skip, "DSA not supported by OpenSSL"} - end; -init_per_testcase(TestCase, Config) -> - ct:timetrap({seconds, 35}), - special_init(TestCase, Config). - -special_init(TestCase, Config) when - TestCase == ciphers_rsa_signed_certs; - TestCase == ciphers_dsa_signed_certs-> - ct:timetrap({seconds, 90}), - Config; -special_init(TestCase, Config) - when TestCase == erlang_client_openssl_server_renegotiate; - TestCase == erlang_client_openssl_server_nowrap_seqnum; - TestCase == erlang_server_openssl_client_nowrap_seqnum; - TestCase == erlang_client_openssl_server_renegotiate_after_client_data - -> - {ok, Version} = application:get_env(ssl, protocol_version), - check_sane_openssl_renegotaite(Config, Version); - -special_init(ssl2_erlang_server_openssl_client, Config) -> - case ssl_test_lib:supports_ssl_tls_version(sslv2) of - true -> - Config; - false -> - {skip, "sslv2 not supported by openssl"} - end; - -special_init(TestCase, Config) - when TestCase == erlang_client_alpn_openssl_server_alpn; - TestCase == erlang_server_alpn_openssl_client_alpn; - TestCase == erlang_client_alpn_openssl_server; - TestCase == erlang_client_openssl_server_alpn; - TestCase == erlang_server_alpn_openssl_client; - TestCase == erlang_server_openssl_client_alpn -> - check_openssl_alpn_support(Config); - -special_init(TestCase, Config) - when TestCase == erlang_client_alpn_openssl_server_alpn_renegotiate; - TestCase == erlang_server_alpn_openssl_client_alpn_renegotiate -> - {ok, Version} = application:get_env(ssl, protocol_version), - case check_sane_openssl_renegotaite(Config, Version) of - {skip, _} = Skip -> - Skip; - _ -> - check_openssl_alpn_support(Config) - end; - -special_init(TestCase, Config) - when TestCase == erlang_client_alpn_npn_openssl_server_alpn_npn; - TestCase == erlang_server_alpn_npn_openssl_client_alpn_npn -> - case check_openssl_alpn_support(Config) of - {skip, _} = Skip -> - Skip; - _ -> - check_openssl_npn_support(Config) - end; - -special_init(TestCase, Config) - when TestCase == erlang_client_openssl_server_npn; - TestCase == erlang_server_openssl_client_npn; - TestCase == erlang_server_openssl_client_npn_only_server; - TestCase == erlang_server_openssl_client_npn_only_client; - TestCase == erlang_client_openssl_server_npn_only_client; - TestCase == erlang_client_openssl_server_npn_only_server -> - check_openssl_npn_support(Config); - -special_init(TestCase, Config) - when TestCase == erlang_server_openssl_client_npn_renegotiate; - TestCase == erlang_client_openssl_server_npn_renegotiate -> - {ok, Version} = application:get_env(ssl, protocol_version), - case check_sane_openssl_renegotaite(Config, Version) of - {skip, _} = Skip -> - Skip; - _ -> - check_openssl_npn_support(Config) - end; - -special_init(TestCase, Config0) - when TestCase == erlang_server_openssl_client_sni_match; - TestCase == erlang_server_openssl_client_sni_no_match; - TestCase == erlang_server_openssl_client_sni_no_header; - TestCase == erlang_server_openssl_client_sni_match_fun; - TestCase == erlang_server_openssl_client_sni_no_match_fun; - TestCase == erlang_server_openssl_client_sni_no_header_fun -> - RsaOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config0), - Config = [{sni_server_opts, [{sni_hosts, - [{"a.server", [ - {certfile, proplists:get_value(certfile, RsaOpts)}, - {keyfile, proplists:get_value(keyfile, RsaOpts)} - ]}, - {"b.server", [ - {certfile, proplists:get_value(certfile, RsaOpts)}, - {keyfile, proplists:get_value(keyfile, RsaOpts)} - ]} - ]}]} | Config0], - check_openssl_sni_support(Config); -special_init(TestCase, Config) - when TestCase == erlang_server_openssl_client; - TestCase == erlang_server_openssl_client_client_cert; - TestCase == erlang_server_openssl_client_reuse_session -> - case ssl_test_lib:is_sane_oppenssl_client() of - true -> - Config; - false -> - {skip, "Broken OpenSSL client"} - end; -special_init(_, Config) -> - Config. - -end_per_testcase(reuse_session_expired, Config) -> - application:unset_env(ssl, session_lifetime), - Config; -end_per_testcase(_, Config) -> - Config. - -%%-------------------------------------------------------------------- -%% Test Cases -------------------------------------------------------- -%%-------------------------------------------------------------------- - -erlang_client_openssl_server() -> - [{doc,"Test erlang client with openssl server"}]. -erlang_client_openssl_server(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-key", KeyFile], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, - {options, ClientOpts}]), - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- -erlang_server_openssl_client() -> - [{doc,"Test erlang server with openssl client"}]. -erlang_server_openssl_client(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_client", "-connect", hostname_format(Hostname) ++":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version)], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - true = port_command(OpenSslPort, Data), - - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false). - -erlang_client_openssl_server_dsa_cert() -> - [{doc,"Test erlang server with openssl client"}]. -erlang_client_openssl_server_dsa_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ClientOpts = ssl_test_lib:ssl_options(client_dsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-CAfile", CaCertFile, - "-key", KeyFile, "-Verify", "2", "-msg"], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, - {options, ClientOpts}]), - - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client), - process_flag(trap_exit, false), - ok. -%%-------------------------------------------------------------------- -erlang_server_openssl_client_dsa_cert() -> - [{doc,"Test erlang server with openssl client"}]. -erlang_server_openssl_client_dsa_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ClientOpts = ssl_test_lib:ssl_options(client_dsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config), - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - CaCertFile = proplists:get_value(cacertfile, ClientOpts), - CertFile = proplists:get_value(certfile, ClientOpts), - KeyFile = proplists:get_value(keyfile, ClientOpts), - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_client", "-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", CertFile, - "-CAfile", CaCertFile, - "-key", KeyFile, "-msg"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - true = port_command(OpenSslPort, Data), - - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false). - - %%-------------------------------------------------------------------- -erlang_client_openssl_server_anon() -> - [{doc,"Test erlang client with openssl server, anonymous"}]. -erlang_client_openssl_server_anon(Config) when is_list(Config) -> - process_flag(trap_exit, true), - %% OpenSSL expects a certificate and key, even if the cipher spec - %% is restructed to aNULL, so we use 'server_rsa_opts' here - 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:ecdh_dh_anonymous_suites(VersionTuple), - - case openssl_has_common_ciphers(Ciphers) of - false -> - {skip, not_supported_by_openssl}; - true -> - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-key", KeyFile, - "-cipher", "aNULL", "-msg"], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, - {options, [{ciphers, Ciphers} | ClientOpts]}]), - - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client), - process_flag(trap_exit, false) - end. -%%-------------------------------------------------------------------- -erlang_server_openssl_client_anon() -> - [{doc,"Test erlang server with openssl client, anonymous"}]. -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:ecdh_dh_anonymous_suites(VersionTuple), - - case openssl_has_common_ciphers(Ciphers) of - false -> - {skip, not_supported_by_openssl}; - true -> - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, [{ciphers, Ciphers} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_client", "-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cipher", "aNULL", "-msg"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - true = port_command(OpenSslPort, Data), - - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false) - end. - -%%-------------------------------------------------------------------- -erlang_server_openssl_client_anon_with_cert() -> - [{doc,"Test erlang server with openssl client, anonymous (with cert)"}]. -erlang_server_openssl_client_anon_with_cert(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:ecdh_dh_anonymous_suites(VersionTuple), - - case openssl_has_common_ciphers(Ciphers) of - false -> - {skip, not_supported_by_openssl}; - true -> - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, [{ciphers, Ciphers} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_client", "-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cipher", "aNULL", "-msg"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - true = port_command(OpenSslPort, Data), - - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false) - end. - - %%-------------------------------------------------------------------- -erlang_server_openssl_client_reuse_session() -> - [{doc, "Test erlang server with openssl client that reconnects with the" - "same session id, to test reusing of sessions."}]. -erlang_server_openssl_client_reuse_session(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {reconnect_times, 5}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_client", "-connect", hostname_format(Hostname) - ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-reconnect"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - true = port_command(OpenSslPort, Data), - - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false), - ok. - -%%-------------------------------------------------------------------- - -erlang_client_openssl_server_renegotiate() -> - [{doc,"Test erlang client when openssl server issuses a renegotiate"}]. -erlang_client_openssl_server_renegotiate(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - ErlData = "From erlang to openssl", - OpenSslData = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-CAfile", CaCertFile, - "-cert", CertFile, "-key", KeyFile, "-msg"], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - delayed_send, [[ErlData, OpenSslData]]}}, - {options, ClientOpts}]), - - true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), - ct:sleep(?SLEEP), - true = port_command(OpensslPort, OpenSslData), - - ssl_test_lib:check_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client), - process_flag(trap_exit, false), - ok. -%%-------------------------------------------------------------------- -erlang_client_openssl_server_renegotiate_after_client_data() -> - [{doc,"Test erlang client when openssl server issuses a renegotiate after reading client data"}]. -erlang_client_openssl_server_renegotiate_after_client_data(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - ErlData = "From erlang to openssl", - OpenSslData = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-CAfile", CaCertFile, - "-cert", CertFile, "-key", KeyFile, "-msg"], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - send_wait_send, [[ErlData, OpenSslData]]}}, - {options, ClientOpts}]), - - true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), - ct:sleep(?SLEEP), - true = port_command(OpensslPort, OpenSslData), - - ssl_test_lib:check_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client), - process_flag(trap_exit, false), - ok. - -%%-------------------------------------------------------------------- - -erlang_client_openssl_server_nowrap_seqnum() -> - [{doc, "Test that erlang client will renegotiate session when", - "max sequence number celing is about to be reached. Although" - "in the testcase we use the test option renegotiate_at" - " to lower treashold substantially."}]. -erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - ErlData = "From erlang to openssl\n", - N = 10, - - Port = ssl_test_lib:inet_port(node()), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-CAfile", CaCertFile, - "-cert", CertFile, "-key", KeyFile, "-msg"], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - trigger_renegotiate, [[ErlData, N+2]]}}, - {options, [{reuse_sessions, false}, - {renegotiate_at, N} | ClientOpts]}]), - - ssl_test_lib:check_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client), - process_flag(trap_exit, false). -%%-------------------------------------------------------------------- -erlang_server_openssl_client_nowrap_seqnum() -> - [{doc, "Test that erlang client will renegotiate session when", - "max sequence number celing is about to be reached. Although" - "in the testcase we use the test option renegotiate_at" - " to lower treashold substantially."}]. -erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - N = 10, - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - trigger_renegotiate, [[Data, N+2]]}}, - {options, [{renegotiate_at, N}, {reuse_sessions, false} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_client","-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-msg"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - true = port_command(OpenSslPort, Data), - - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- - -erlang_client_openssl_server_no_server_ca_cert() -> - [{doc, "Test erlang client when openssl server sends a cert chain not" - "including the ca cert. Explicitly test this even if it is" - "implicitly tested eleswhere."}]. -erlang_client_openssl_server_no_server_ca_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-key", KeyFile, "-msg"], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, - {options, ClientOpts}]), - - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- -erlang_client_openssl_server_client_cert() -> - [{doc,"Test erlang client with openssl server when client sends cert"}]. -erlang_client_openssl_server_client_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-CAfile", CaCertFile, - "-key", KeyFile, "-Verify", "2"], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, - {options, ClientOpts}]), - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- -erlang_server_openssl_client_client_cert() -> - [{doc,"Test erlang server with openssl client when client sends cert"}]. -erlang_server_openssl_client_client_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, - {options, - [{verify , verify_peer} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - CaCertFile = proplists:get_value(cacertfile, ClientOpts), - CertFile = proplists:get_value(certfile, ClientOpts), - KeyFile = proplists:get_value(keyfile, ClientOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_client", "-cert", CertFile, - "-CAfile", CaCertFile, - "-key", KeyFile,"-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version)], - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - true = port_command(OpenSslPort, Data), - ssl_test_lib:check_result(Server, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpenSslPort), - ssl_test_lib:close(Server), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- -erlang_server_erlang_client_client_cert() -> - [{doc,"Test erlang server with erlang client when client sends cert"}]. -erlang_server_erlang_client_client_cert(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), - ClientOpts = proplists:get_value(client_rsa_verify_opts, Config), - Version = ssl_test_lib:protocol_version(Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From erlang to erlang", - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, - %% Due to 1/n-1 splitting countermeasure Rizzo/Duong-Beast - [Data]}}, - {options, - [{verify , verify_peer} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - %% Due to 1/n-1 splitting countermeasure Rizzo/Duong-Beast - {mfa, {ssl, send, [Data]}}, - {options, - [{versions, [Version]} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- - -ciphers_rsa_signed_certs() -> - [{doc,"Test cipher suites that uses rsa certs"}]. -ciphers_rsa_signed_certs(Config) when is_list(Config) -> - Version = ssl_test_lib:protocol_version(Config), - Ciphers = ssl_test_lib:rsa_suites(openssl), - run_suites(Ciphers, Version, Config, rsa). -%%-------------------------------------------------------------------- - -ciphers_dsa_signed_certs() -> - [{doc,"Test cipher suites that uses dsa certs"}]. -ciphers_dsa_signed_certs(Config) when is_list(Config) -> - Version = ssl_test_lib:protocol_version(Config), - NVersion = ssl_test_lib:protocol_version(Config, tuple), - Ciphers = ssl_test_lib:dsa_suites(NVersion), - run_suites(Ciphers, Version, Config, dsa). - -%%-------------------------------------------------------------------- -erlang_client_bad_openssl_server() -> - [{doc,"Test what happens if openssl server sends garbage to erlang ssl client"}]. -erlang_client_bad_openssl_server(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-key", KeyFile], - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, server_sent_garbage, []}}, - {options, - [{versions, [Version]} | ClientOpts]}]), - - %% Send garbage - true = port_command(OpensslPort, ?OPENSSL_GARBAGE), - - ct:sleep(?SLEEP), - - Client0 ! server_sent_garbage, - - ssl_test_lib:check_result(Client0, true), - - ssl_test_lib:close(Client0), - - %% Make sure openssl does not hang and leave zombie process - Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result, []}}, - {options, - [{versions, [Version]} | ClientOpts]}]), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client1), - process_flag(trap_exit, false), - ok. - -%%-------------------------------------------------------------------- - -expired_session() -> - [{doc, "Test our ssl client handling of expired sessions. Will make" - "better code coverage of the ssl_manager module"}]. -expired_session(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), - "-cert", CertFile,"-key", KeyFile], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, tls), - - Client0 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - - ssl_test_lib:close(Client0), - - %% Make sure session is registered - ct:sleep(?SLEEP), - - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - - ssl_test_lib:close(Client1), - %% Make sure session is unregistered due to expiration - ct:sleep((?EXPIRE+1) * 1000), - - Client2 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - ssl_test_lib:close(Client2), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- -ssl2_erlang_server_openssl_client() -> - [{doc,"Test that ssl v2 clients are rejected"}]. - -ssl2_erlang_server_openssl_client(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, - {from, self()}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Exe = "openssl", - Args = ["s_client", "-connect", hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - "-ssl2", "-msg"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ct:log("Ports ~p~n", [[erlang:port_info(P) || P <- erlang:ports()]]), - ssl_test_lib:consume_port_exit(OpenSslPort), - ssl_test_lib:check_server_alert(Server, unexpected_message), - process_flag(trap_exit, false). - -%%-------------------------------------------------------------------- - -erlang_client_alpn_openssl_server_alpn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------- - -erlang_server_alpn_openssl_client_alpn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------------- - -erlang_client_alpn_openssl_server(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_with_opts(Config, - [{alpn_advertised_protocols, [<<"spdy/2">>]}], - [], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------------- - -erlang_client_openssl_server_alpn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_with_opts(Config, - [], - ["-alpn", "spdy/2"], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------------- - -erlang_server_alpn_openssl_client(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_with_opts(Config, - [{alpn_preferred_protocols, [<<"spdy/2">>]}], - [], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------------- - -erlang_server_openssl_client_alpn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_with_opts(Config, - [], - ["-alpn", "spdy/2"], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------- - -erlang_client_alpn_openssl_server_alpn_renegotiate(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), - ct:sleep(?SLEEP), - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------- - -erlang_server_alpn_openssl_client_alpn_renegotiate(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), - ct:sleep(?SLEEP), - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------- - -erlang_client_alpn_npn_openssl_server_alpn_npn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------- - -erlang_server_alpn_npn_openssl_client_alpn_npn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------- -erlang_client_openssl_server_npn() -> - [{doc,"Test erlang client with openssl server doing npn negotiation"}]. - -erlang_client_openssl_server_npn(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, Data), - - ssl_test_lib:check_result(Client, ok) - end), - ok. - -%%-------------------------------------------------------------------- -erlang_client_openssl_server_npn_renegotiate() -> - [{doc,"Test erlang client with openssl server doing npn negotiation and renegotiate"}]. - -erlang_client_openssl_server_npn_renegotiate(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> - true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), - ct:sleep(?SLEEP), - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Client, ok) - end), - ok. -%%-------------------------------------------------------------------------- -erlang_server_openssl_client_npn() -> - [{doc,"Test erlang server with openssl client and npn negotiation"}]. - -erlang_server_openssl_client_npn(Config) when is_list(Config) -> - - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------------- -erlang_server_openssl_client_npn_renegotiate() -> - [{doc,"Test erlang server with openssl client and npn negotiation with renegotiation"}]. - -erlang_server_openssl_client_npn_renegotiate(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), - ct:sleep(?SLEEP), - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. -%%-------------------------------------------------------------------------- -erlang_client_openssl_server_npn_only_server(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_with_opts(Config, [], - ["-nextprotoneg", "spdy/2"], Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------------- - -erlang_client_openssl_server_npn_only_client(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_client_and_openssl_server_with_opts(Config, - [{client_preferred_next_protocols, - {client, [<<"spdy/2">>], <<"http/1.1">>}}], [], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -%%-------------------------------------------------------------------------- -erlang_server_openssl_client_npn_only_server(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_with_opts(Config, [{next_protocols_advertised, [<<"spdy/2">>]}], [], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. - -erlang_server_openssl_client_npn_only_client(Config) when is_list(Config) -> - Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_with_opts(Config, [], ["-nextprotoneg", "spdy/2"], - Data, fun(Server, OpensslPort) -> - true = port_command(OpensslPort, Data), - ssl_test_lib:check_result(Server, ok) - end), - ok. -%-------------------------------------------------------------------------- -erlang_server_openssl_client_sni_no_header(Config) when is_list(Config) -> - erlang_server_openssl_client_sni_test(Config, undefined, undefined, "server Peer cert"). - -erlang_server_openssl_client_sni_no_header_fun(Config) when is_list(Config) -> - erlang_server_openssl_client_sni_test_sni_fun(Config, undefined, undefined, "server Peer cert"). - -erlang_server_openssl_client_sni_match(Config) when is_list(Config) -> - erlang_server_openssl_client_sni_test(Config, "a.server", "a.server", "server Peer cert"). - -erlang_server_openssl_client_sni_match_fun(Config) when is_list(Config) -> - erlang_server_openssl_client_sni_test_sni_fun(Config, "a.server", "a.server", "server Peer cert"). - -erlang_server_openssl_client_sni_no_match(Config) when is_list(Config) -> - erlang_server_openssl_client_sni_test(Config, "c.server", undefined, "server Peer cert"). - -erlang_server_openssl_client_sni_no_match_fun(Config) when is_list(Config) -> - erlang_server_openssl_client_sni_test_sni_fun(Config, "c.server", undefined, "server Peer cert"). - - -%%-------------------------------------------------------------------- -%% Internal functions ------------------------------------------------ -%%-------------------------------------------------------------------- -run_suites(Ciphers, Version, Config, Type) -> - {ClientOpts, ServerOpts} = - case Type of - rsa -> - {ssl_test_lib:ssl_options(client_rsa_opts, Config), - ssl_test_lib:ssl_options(server_rsa_opts, Config)}; - dsa -> - {ssl_test_lib:ssl_options(client_dsa_opts, Config), - ssl_test_lib:ssl_options(server_dsa_verify_opts, Config)} - end, - - Result = lists:map(fun(Cipher) -> - cipher(Cipher, Version, Config, ClientOpts, ServerOpts) end, - Ciphers), - case lists:flatten(Result) of - [] -> - ok; - Error -> - ct:log("Cipher suite errors: ~p~n", [Error]), - ct:fail(cipher_suite_failed_see_test_case_log) - end. - -client_read_check([], _Data) -> - ok; -client_read_check([Hd | T], Data) -> - case binary:match(Data, list_to_binary(Hd)) of - nomatch -> - nomatch; - _ -> - client_read_check(T, Data) - end. -client_check_result(Port, DataExpected, DataReceived) -> - receive - {Port, {data, TheData}} -> - Data = list_to_binary(TheData), - NewData = <<DataReceived/binary, Data/binary>>, - ct:log("New Data: ~p", [NewData]), - case client_read_check(DataExpected, NewData) of - ok -> - ok; - _ -> - client_check_result(Port, DataExpected, NewData) - end - after 20000 -> - ct:fail({"Time out on openSSL Client", {expected, DataExpected}, - {got, DataReceived}}) - end. -client_check_result(Port, DataExpected) -> - client_check_result(Port, DataExpected, <<"">>). - -send_and_hostname(SSLSocket) -> - ssl:send(SSLSocket, "OK"), - case ssl:connection_information(SSLSocket, [sni_hostname]) of - {ok, []} -> - undefined; - {ok, [{sni_hostname, Hostname}]} -> - Hostname - end. - -erlang_server_openssl_client_sni_test(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> - Version = ssl_test_lib:protocol_version(Config), - ct:log("Start running handshake, Config: ~p, SNIHostname: ~p, ExpectedSNIHostname: ~p, ExpectedCN: ~p", [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), - ServerOptions = proplists:get_value(sni_server_opts, Config) ++ proplists:get_value(server_rsa_opts, Config), - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, - {options, ServerOptions}]), - Port = ssl_test_lib:inet_port(Server), - Exe = "openssl", - ClientArgs = case SNIHostname of - undefined -> - openssl_client_args(Version, Hostname,Port); - _ -> - openssl_client_args(Version, Hostname, Port, SNIHostname) - end, - ClientPort = ssl_test_lib:portable_open_port(Exe, ClientArgs), - - ssl_test_lib:check_result(Server, ExpectedSNIHostname), - ssl_test_lib:close_port(ClientPort), - ssl_test_lib:close(Server), - ok. - - -erlang_server_openssl_client_sni_test_sni_fun(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> - Version = ssl_test_lib:protocol_version(Config), - ct:log("Start running handshake for sni_fun, Config: ~p, SNIHostname: ~p, ExpectedSNIHostname: ~p, ExpectedCN: ~p", [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), - [{sni_hosts, ServerSNIConf}] = proplists:get_value(sni_server_opts, Config), - SNIFun = fun(Domain) -> proplists:get_value(Domain, ServerSNIConf, undefined) end, - ServerOptions = proplists:get_value(server_rsa_opts, Config) ++ [{sni_fun, SNIFun}], - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, - {options, ServerOptions}]), - Port = ssl_test_lib:inet_port(Server), - Exe = "openssl", - ClientArgs = case SNIHostname of - undefined -> - openssl_client_args(Version, Hostname,Port); - _ -> - openssl_client_args(Version, Hostname, Port, SNIHostname) - end, - - ClientPort = ssl_test_lib:portable_open_port(Exe, ClientArgs), - - ssl_test_lib:check_result(Server, ExpectedSNIHostname), - ssl_test_lib:close_port(ClientPort), - ssl_test_lib:close(Server). - - -cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> - process_flag(trap_exit, true), - ct:log("Testing CipherSuite ~p~n", [CipherSuite]), - {ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - - Exe = "openssl", - Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-key", KeyFile], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - ConnectionInfo = {ok, {Version, CipherSuite}}, - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, cipher_result, [ConnectionInfo]}}, - {options, - [{ciphers,[CipherSuite]} | - ClientOpts]}]), - - true = port_command(OpenSslPort, "Hello\n"), - - receive - {Port, {data, _}} when is_port(Port) -> - ok - after 500 -> - ct:log("Time out on openssl port, check that" - " the messages Hello and world are received" - " during close of port" , []), - ok - end, - - true = port_command(OpenSslPort, " world\n"), - - Result = ssl_test_lib:wait_for_result(Client, ok), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpenSslPort), - ssl_test_lib:close(Client), - - Return = case Result of - ok -> - []; - Error -> - [{CipherSuite, Error}] - end, - process_flag(trap_exit, false), - Return. - -start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, OpensslServerOpts, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ClientOpts = ErlangClientOpts ++ ClientOpts0, - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = case OpensslServerOpts of - [] -> - ["s_server", "-accept", - integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-CAfile", CaCertFile, - "-cert", CertFile,"-key", KeyFile]; - [Opt, Value] -> - ["s_server", Opt, Value, "-accept", - integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-CAfile", CaCertFile, - "-cert", CertFile,"-key", KeyFile] - end, - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, - {options, ClientOpts}]), - - Callback(Client, OpensslPort), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), - ClientOpts0 = proplists:get_value(client_rsa_verify_opts, Config), - ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]} | ClientOpts0], - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-CAfile", CaCertFile, - "-cert", CertFile, "-key", KeyFile], - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, ClientOpts}]), - - Callback(Client, OpensslPort), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts0 = proplists:get_value(server_rsa_opts, Config), - ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>]} | ServerOpts0], - - {_, ServerNode, _} = ssl_test_lib:run_where(Config), - - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_client", "-alpn", "http/1.0,spdy/2", "-msg", "-port", - integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-host", "localhost"], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - Callback(Server, OpenSslPort), - - ssl_test_lib:close(Server), - - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false). - -start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts = proplists:get_value(server_rsa_opts, Config), - ClientOpts0 = proplists:get_value(client_rsa_opts, Config), - ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]}, - {client_preferred_next_protocols, {client, [<<"spdy/3">>, <<"http/1.1">>]}} | ClientOpts0], - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-nextprotoneg", - "spdy/3", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-cert", CertFile, "-key", KeyFile], - - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, ClientOpts}]), - - Callback(Client, OpensslPort), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts0 = proplists:get_value(server_rsa_opts, Config), - ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>]}, - {next_protocols_advertised, [<<"spdy/3">>, <<"http/1.1">>]} | ServerOpts0], - - {_, ServerNode, _} = ssl_test_lib:run_where(Config), - - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - Exe = "openssl", - Args = ["s_client", "-alpn", "http/1.1,spdy/2", "-nextprotoneg", "spdy/3", - "-msg", "-port", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-host", "localhost"], - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - Callback(Server, OpenSslPort), - - ssl_test_lib:close(Server), - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false). - -start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), - ClientOpts = [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}} | ClientOpts0], - - {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - - Data = "From openssl to erlang", - - Port = ssl_test_lib:inet_port(node()), - CaCertFile = proplists:get_value(cacertfile, ServerOpts), - CertFile = proplists:get_value(certfile, ServerOpts), - KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_server", "-msg", "-nextprotoneg", "http/1.1,spdy/2", "-accept", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-CAfile", CaCertFile, - "-cert", CertFile, "-key", KeyFile], - OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - - ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {?MODULE, - erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, ClientOpts}]), - - Callback(Client, OpensslPort), - - %% Clean close down! Server needs to be closed first !! - ssl_test_lib:close_port(OpensslPort), - - ssl_test_lib:close(Client), - process_flag(trap_exit, false). - -start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ServerOpts = [{next_protocols_advertised, [<<"spdy/2">>]}, ServerOpts0], - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_client", "-nextprotoneg", "http/1.0,spdy/2", "-msg", "-connect", - hostname_format(Hostname) ++ ":" - ++ integer_to_list(Port), ssl_test_lib:version_flag(Version)], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - Callback(Server, OpenSslPort), - - ssl_test_lib:close(Server), - - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false). - - -start_erlang_server_and_openssl_client_with_opts(Config, ErlangServerOpts, OpenSSLClientOpts, Data, Callback) -> - process_flag(trap_exit, true), - ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ServerOpts = ErlangServerOpts ++ ServerOpts0, - - {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Version = ssl_test_lib:protocol_version(Config), - - Exe = "openssl", - Args = ["s_client"] ++ OpenSSLClientOpts ++ ["-msg", "-connect", - hostname_format(Hostname) ++ ":" ++ integer_to_list(Port), - ssl_test_lib:version_flag(Version)], - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - - Callback(Server, OpenSslPort), - - ssl_test_lib:close(Server), - - ssl_test_lib:close_port(OpenSslPort), - process_flag(trap_exit, false). - - -erlang_ssl_receive_and_assert_negotiated_protocol(Socket, Protocol, Data) -> - {ok, Protocol} = ssl:negotiated_protocol(Socket), - erlang_ssl_receive(Socket, Data), - {ok, Protocol} = ssl:negotiated_protocol(Socket), - ok. - -erlang_ssl_receive(Socket, Data) -> - ct:log("Connection info: ~p~n", - [ssl:connection_information(Socket)]), - receive - {ssl, Socket, "R\n"} -> - %% Swallow s_client renegotiation command. - %% openssl s_client connected commands can appear on - %% server side with some openssl versions. - erlang_ssl_receive(Socket,Data); - {ssl, Socket, Data} -> - io:format("Received ~p~n",[Data]), - %% open_ssl server sometimes hangs waiting in blocking read - ssl:send(Socket, "Got it"), - ok; - {ssl, Socket, Byte} when length(Byte) == 1 -> - erlang_ssl_receive(Socket, tl(Data)); - {Port, {data,Debug}} when is_port(Port) -> - io:format("openssl ~s~n",[Debug]), - erlang_ssl_receive(Socket,Data); - Other -> - ct:fail({unexpected_message, Other}) - end. - -connection_info(Socket, Version) -> - case ssl:connection_information(Socket, [version]) of - {ok, [{version, Version}] = Info} -> - ct:log("Connection info: ~p~n", [Info]), - ok; - {ok, [{version, OtherVersion}]} -> - {wrong_version, OtherVersion} - end. - -connection_info_result(Socket) -> - ssl:connection_information(Socket). - - -delayed_send(Socket, [ErlData, OpenSslData]) -> - ct:sleep(?SLEEP), - ssl:send(Socket, ErlData), - erlang_ssl_receive(Socket, OpenSslData). - -server_sent_garbage(Socket) -> - receive - server_sent_garbage -> - {error, closed} == ssl:send(Socket, "data") - - end. - -send_wait_send(Socket, [ErlData, OpenSslData]) -> - ssl:send(Socket, ErlData), - ct:sleep(?SLEEP), - ssl:send(Socket, ErlData), - erlang_ssl_receive(Socket, OpenSslData). - -check_openssl_sni_support(Config) -> - HelpText = os:cmd("openssl s_client --help"), - case ssl_test_lib:is_sane_oppenssl_client() of - true -> - case string:str(HelpText, "-servername") of - 0 -> - {skip, "Current openssl doesn't support SNI"}; - _ -> - Config - end; - false -> - {skip, "Current openssl doesn't support SNI or extension handling is flawed"} - end. - - -check_openssl_npn_support(Config) -> - HelpText = os:cmd("openssl s_client --help"), - case string:str(HelpText, "nextprotoneg") of - 0 -> - {skip, "Openssl not compiled with nextprotoneg support"}; - _ -> - Config - end. - -check_openssl_alpn_support(Config) -> - HelpText = os:cmd("openssl s_client --help"), - case string:str(HelpText, "alpn") of - 0 -> - {skip, "Openssl not compiled with alpn support"}; - _ -> - Config - end. - -check_sane_openssl_renegotaite(Config, Version) when Version == 'tlsv1.1'; - Version == 'tlsv1.2' -> - case os:cmd("openssl version") of - "OpenSSL 1.0.1c" ++ _ -> - {skip, "Known renegotiation bug in OpenSSL"}; - "OpenSSL 1.0.1b" ++ _ -> - {skip, "Known renegotiation bug in OpenSSL"}; - "OpenSSL 1.0.1a" ++ _ -> - {skip, "Known renegotiation bug in OpenSSL"}; - "OpenSSL 1.0.1 " ++ _ -> - {skip, "Known renegotiation bug in OpenSSL"}; - _ -> - check_sane_openssl_renegotaite(Config) - end; -check_sane_openssl_renegotaite(Config, _) -> - check_sane_openssl_renegotaite(Config). - -check_sane_openssl_renegotaite(Config) -> - case os:cmd("openssl version") of - "OpenSSL 1.0.0" ++ _ -> - {skip, "Known renegotiation bug in OpenSSL"}; - "OpenSSL 0.9.8" ++ _ -> - {skip, "Known renegotiation bug in OpenSSL"}; - "OpenSSL 0.9.7" ++ _ -> - {skip, "Known renegotiation bug in OpenSSL"}; - _ -> - Config - end. - -workaround_openssl_s_clinent() -> - %% http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=683159 - %% https://bugs.archlinux.org/task/33919 - %% Bug seems to manifests it self if TLS version is not - %% explicitly specified - case os:cmd("openssl version") of - "OpenSSL 1.0.1c" ++ _ -> - ["-no_tls1_2"]; - "OpenSSL 1.0.1d" ++ _ -> - ["-no_tls1_2"]; - "OpenSSL 1.0.1e" ++ _ -> - ["-no_tls1_2"]; - "OpenSSL 1.0.1f" ++ _ -> - ["-no_tls1_2"]; - _ -> - [] - end. - -openssl_client_args(Version, Hostname, Port) -> - ["s_client", "-connect", Hostname ++ ":" ++ integer_to_list(Port), ssl_test_lib:version_flag(Version)]. - -openssl_client_args(Version, Hostname, Port, ServerName) -> - ["s_client", "-connect", Hostname ++ ":" ++ - integer_to_list(Port), ssl_test_lib:version_flag(Version), "-servername", ServerName]. - - -hostname_format(Hostname) -> - case lists:member($., Hostname) of - true -> - Hostname; - false -> - "localhost" - end. - - -openssl_has_common_ciphers(Ciphers) -> - OCiphers = ssl_test_lib:common_ciphers(openssl), - has_common_ciphers(Ciphers, OCiphers). - -has_common_ciphers([], _) -> - false; -has_common_ciphers([Cipher | Rest], OCiphers) -> - case lists:member(Cipher, OCiphers) of - true -> - true; - _ -> - has_common_ciphers(Rest, OCiphers) - end. diff --git a/lib/ssl/test/tls_1_3_record_SUITE.erl b/lib/ssl/test/tls_1_3_record_SUITE.erl new file mode 100644 index 0000000000..5df8853b1b --- /dev/null +++ b/lib/ssl/test/tls_1_3_record_SUITE.erl @@ -0,0 +1,973 @@ +%% +%% %CopyrightBegin% +%% +%% 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 +%% +%% 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(tls_1_3_record_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("ssl/src/tls_record.hrl"). +-include_lib("ssl/src/tls_handshake.hrl"). +-include_lib("ssl/src/ssl_cipher.hrl"). +-include_lib("ssl/src/ssl_internal.hrl"). + +all() -> + [encode_decode, + finished_verify_data, + '1_RTT_handshake']. + +init_per_suite(Config) -> + catch crypto:stop(), + try (ok == crypto:start()) andalso ssl_test_lib:sufficient_crypto_support('tlsv1.3') of + true -> + ssl_test_lib:clean_start(), + Config; + false -> + {skip, "Not enough crypto support for TLS-1.3"} + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +encode_decode() -> + [{doc,"Test TLS 1.3 record encode/decode functions"}]. + +encode_decode(_Config) -> + ConnectionStates = + #{current_read => + #{beast_mitigation => one_n_minus_one, + cipher_state => + {cipher_state, + <<14,172,111,243,199,170,242,203,126,205,34,93,122,115,226,14, + 15,117,155,48,24,112,61,15,113,208,127,51,179,227,194,232>>, + <<197,54,168,218,54,91,157,58,30,201,197,142,51,58,53,231,228, + 131,57,122,170,78,82,196,30,48,23,16,95,255,185,236>>, + undefined,undefined,undefined,16}, + client_verify_data => undefined,compression_state => undefined, + mac_secret => undefined,secure_renegotiation => undefined, + security_parameters => + {security_parameters, + <<19,2>>, + 0,8,2,undefined,undefined,undefined,undefined,undefined, + sha384,undefined,undefined, + {handshake_secret, + <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121, + 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218, + 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56, + 157>>}, + undefined, + <<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207, + 147,61,168,145,177,118,160,153,33,53,48,108,191,174>>, + undefined}, + sequence_number => 0,server_verify_data => undefined}, + current_write => + #{beast_mitigation => one_n_minus_one, + cipher_state => + {cipher_state, + <<14,172,111,243,199,170,242,203,126,205,34,93,122,115,226,14, + 15,117,155,48,24,112,61,15,113,208,127,51,179,227,194,232>>, + <<197,54,168,218,54,91,157,58,30,201,197,142,51,58,53,231,228, + 131,57,122,170,78,82,196,30,48,23,16,95,255,185,236>>, + undefined,undefined,undefined,16}, + client_verify_data => undefined,compression_state => undefined, + mac_secret => undefined,secure_renegotiation => undefined, + security_parameters => + {security_parameters, + <<19,2>>, + 0,8,2,undefined,undefined,undefined,undefined,undefined, + sha384,undefined,undefined, + {handshake_secret, + <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121, + 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218, + 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56, + 157>>}, + undefined, + <<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207, + 147,61,168,145,177,118,160,153,33,53,48,108,191,174>>, + undefined}, + sequence_number => 0,server_verify_data => undefined}}, + + PlainText = [11, + <<0,2,175>>, + <<0,0,2,171,0,2,166,48,130,2,162,48,130,1,138,2,9,0,186,57,220,137,88,255, + 191,235,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,48,18,49,16,48,14,6,3,85, + 4,3,12,7,84,101,115,116,32,67,65,48,30,23,13,49,56,48,53,48,52,49,52,49,50, + 51,56,90,23,13,50,56,48,50,48,52,49,52,49,50,51,56,90,48,20,49,18,48,16,6, + 3,85,4,3,12,9,108,111,99,97,108,104,111,115,116,48,130,1,34,48,13,6,9,42, + 134,72,134,247,13,1,1,1,5,0,3,130,1,15,0,48,130,1,10,2,130,1,1,0,169,40, + 144,176,121,63,134,97,144,126,243,183,225,157,37,131,183,225,87,243,23,88, + 230,70,9,134,32,147,7,27,167,98,51,81,224,75,199,12,229,251,195,207,75,179, + 181,78,128,3,255,44,58,39,43,172,142,45,186,58,51,65,187,199,154,153,245, + 70,133,137,1,27,87,42,116,65,251,129,109,145,233,97,171,71,54,213,185,74, + 209,166,11,218,189,119,206,86,170,60,212,213,85,189,30,50,215,23,185,53, + 132,238,132,176,198,250,139,251,198,221,225,128,109,113,23,220,39,143,71, + 30,59,189,51,244,61,158,214,146,180,196,103,169,189,221,136,78,129,216,148, + 2,9,8,65,37,224,215,233,13,209,21,235,20,143,33,74,59,53,208,90,152,94,251, + 54,114,171,39,88,230,227,158,211,135,37,182,67,205,161,59,20,138,58,253,15, + 53,48,8,157,9,95,197,9,177,116,21,54,9,125,78,109,182,83,20,16,234,223,116, + 41,155,123,87,77,17,120,153,246,239,124,130,105,219,166,146,242,151,66,198, + 75,72,63,28,246,86,16,244,223,22,36,50,15,247,222,98,6,152,136,154,72,150, + 73,127,2,3,1,0,1,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,3,130,1,1,0,76, + 33,54,160,229,219,219,193,150,116,245,252,18,39,235,145,86,12,167,171,52, + 117,166,30,83,5,216,245,177,217,247,95,1,136,94,246,212,108,248,230,111, + 225,202,189,6,129,8,70,128,245,18,204,215,87,82,129,253,227,122,66,182,184, + 189,30,193,169,144,218,216,109,105,110,215,144,60,104,162,178,101,164,218, + 122,60,37,41,143,57,150,52,59,51,112,238,113,239,168,114,69,183,143,154,73, + 61,58,80,247,172,95,251,55,28,186,28,200,206,230,118,243,92,202,189,49,76, + 124,252,76,0,247,112,85,194,69,59,222,163,228,103,49,110,104,109,251,155, + 138,9,37,167,49,189,48,134,52,158,185,129,24,96,153,196,251,90,206,76,239, + 175,119,174,165,133,108,222,125,237,125,187,149,152,83,190,16,202,94,202, + 201,40,218,22,254,63,189,41,174,97,140,203,70,18,196,118,237,175,134,79,78, + 246,2,61,54,77,186,112,32,17,193,192,188,217,252,215,200,7,245,180,179,132, + 183,212,229,155,15,152,206,135,56,81,88,3,123,244,149,110,182,72,109,70,62, + 146,152,146,151,107,126,216,210,9,93,0,0>>], + + {[_Header|Encoded], _} = tls_record_1_3:encode_plain_text(22, PlainText, ConnectionStates), + CipherText = #ssl_tls{type = 23, version = {3,3}, fragment = Encoded}, + + {#ssl_tls{type = 22, version = {3,4}, fragment = DecodedText}, _} = + tls_record_1_3:decode_cipher_text(CipherText, ConnectionStates), + + DecodedText = iolist_to_binary(PlainText), + ct:log("Decoded: ~p ~n", [DecodedText]), + ok. +%%-------------------------------------------------------------------- +'1_RTT_handshake'() -> + [{doc,"Test TLS 1.3 1-RTT Handshake"}]. + +'1_RTT_handshake'(_Config) -> + %% ConnectionStates with NULL cipher + ConnStatesNull = + #{current_write => + #{security_parameters => + #security_parameters{cipher_suite = ?TLS_NULL_WITH_NULL_NULL}, + sequence_number => 0 + } + }, + + %% {client} construct a ClientHello handshake message: + %% + %% ClientHello (196 octets): 01 00 00 c0 03 03 cb 34 ec b1 e7 81 63 + %% ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83 + %% 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b + %% 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 + %% 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23 + %% 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2 + %% 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a + %% af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 + %% 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 + %% 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 + %% + %% {client} send handshake record: + %% + %% payload (196 octets): 01 00 00 c0 03 03 cb 34 ec b1 e7 81 63 ba + %% 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83 02 + %% 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b 00 + %% 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 12 + %% 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23 00 + %% 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2 3d + %% 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af + %% 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 02 + %% 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 02 + %% 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 + %% + %% complete record (201 octets): 16 03 01 00 c4 01 00 00 c0 03 03 cb + %% 34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 + %% ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 + %% 00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 + %% 00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 + %% 01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d + %% e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d + %% 54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e + %% 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 + %% 01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 + ClientHello = + hexstr2bin("01 00 00 c0 03 03 cb 34 ec b1 e7 81 63 + ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83 + 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b + 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 + 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23 + 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2 + 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a + af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 + 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 + 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01"), + + ClientHelloRecord = + %% Current implementation always sets + %% legacy_record_version to Ox0303 + hexstr2bin("16 03 03 00 c4 01 00 00 c0 03 03 cb + 34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 + ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 + 00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 + 00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 + 01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d + e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d + 54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e + 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 + 01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01"), + + {CHEncrypted, _} = + tls_record:encode_handshake(ClientHello, {3,4}, ConnStatesNull), + ClientHelloRecord = iolist_to_binary(CHEncrypted), + + %% {server} extract secret "early": + %% + %% salt: 0 (all zero octets) + %% + %% IKM (32 octets): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + %% + %% secret (32 octets): 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c + %% e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a + HKDFAlgo = sha256, + Salt = binary:copy(<<?BYTE(0)>>, 32), + IKM = binary:copy(<<?BYTE(0)>>, 32), + EarlySecret = + hexstr2bin("33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c + e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a"), + + {early_secret, EarlySecret} = tls_v1:key_schedule(early_secret, HKDFAlgo, {psk, Salt}), + + %% {client} create an ephemeral x25519 key pair: + %% + %% private key (32 octets): 49 af 42 ba 7f 79 94 85 2d 71 3e f2 78 + %% 4b cb ca a7 91 1d e2 6a dc 56 42 cb 63 45 40 e7 ea 50 05 + %% + %% public key (32 octets): 99 38 1d e5 60 e4 bd 43 d2 3d 8e 43 5a 7d + %% ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af 2c + CPublicKey = + hexstr2bin("99 38 1d e5 60 e4 bd 43 d2 3d 8e 43 5a 7d + ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a af 2c"), + + %% {server} create an ephemeral x25519 key pair: + %% + %% private key (32 octets): b1 58 0e ea df 6d d5 89 b8 ef 4f 2d 56 + %% 52 57 8c c8 10 e9 98 01 91 ec 8d 05 83 08 ce a2 16 a2 1e + %% + %% public key (32 octets): c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 + %% 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f + SPrivateKey = + hexstr2bin("b1 58 0e ea df 6d d5 89 b8 ef 4f 2d 56 + 52 57 8c c8 10 e9 98 01 91 ec 8d 05 83 08 ce a2 16 a2 1e"), + + SPublicKey = + hexstr2bin("c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 + 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f"), + + %% {server} construct a ServerHello handshake message: + %% + %% ServerHello (90 octets): 02 00 00 56 03 03 a6 af 06 a4 12 18 60 + %% dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e + %% d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88 + %% 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1 + %% dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04 + ServerHello = + hexstr2bin("02 00 00 56 03 03 a6 af 06 a4 12 18 60 + dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e + d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88 + 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1 + dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04"), + + %% {server} derive secret for handshake "tls13 derived": + %% + %% PRK (32 octets): 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c e2 + %% 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a + %% + %% hash (32 octets): e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 + %% 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55 + %% + %% info (49 octets): 00 20 0d 74 6c 73 31 33 20 64 65 72 69 76 65 64 + %% 20 e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4 + %% 64 9b 93 4c a4 95 99 1b 78 52 b8 55 + %% + %% expanded (32 octets): 6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba + %% b6 97 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba + Hash = + hexstr2bin("e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 + 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55"), + + Hash = crypto:hash(HKDFAlgo, <<>>), + + Info = + hexstr2bin("00 20 0d 74 6c 73 31 33 20 64 65 72 69 76 65 64 + 20 e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4 + 64 9b 93 4c a4 95 99 1b 78 52 b8 55"), + + Info = tls_v1:create_info(<<"derived">>, Hash, ssl_cipher:hash_size(HKDFAlgo)), + + Expanded = + hexstr2bin("6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba + b6 97 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba"), + + Expanded = tls_v1:derive_secret(EarlySecret, <<"derived">>, <<>>, HKDFAlgo), + + %% {server} extract secret "handshake": + %% + %% salt (32 octets): 6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba b6 97 + %% 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba + %% + %% IKM (32 octets): 8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d + %% 35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d + %% + %% secret (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b + %% 01 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac + + %% salt = Expanded + HandshakeIKM = + hexstr2bin("8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d + 35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d"), + + HandshakeSecret = + hexstr2bin("1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b + 01 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac"), + + HandshakeIKM = crypto:compute_key(ecdh, CPublicKey, SPrivateKey, x25519), + + {handshake_secret, HandshakeSecret} = + tls_v1:key_schedule(handshake_secret, HKDFAlgo, HandshakeIKM, + {early_secret, EarlySecret}), + + %% {server} derive secret "tls13 c hs traffic": + %% + %% PRK (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01 + %% 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac + %% + %% hash (32 octets): 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed + %% d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 + %% + %% info (54 octets): 00 20 12 74 6c 73 31 33 20 63 20 68 73 20 74 72 + %% 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 + %% ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 + %% + %% expanded (32 octets): b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e + %% 2d 8f 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21 + + %% PRK = HandshakeSecret + CHSTHash = + hexstr2bin("86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed + d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"), + + CHSTInfo = + hexstr2bin("00 20 12 74 6c 73 31 33 20 63 20 68 73 20 74 72 + 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 + ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"), + + CHSTrafficSecret = + hexstr2bin(" b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e + 2d 8f 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21"), + + CHSH = <<ClientHello/binary,ServerHello/binary>>, + CHSTHash = crypto:hash(HKDFAlgo, CHSH), + CHSTInfo = tls_v1:create_info(<<"c hs traffic">>, CHSTHash, ssl_cipher:hash_size(HKDFAlgo)), + + CHSTrafficSecret = + tls_v1:client_handshake_traffic_secret(HKDFAlgo, {handshake_secret, HandshakeSecret}, CHSH), + + %% {server} derive secret "tls13 s hs traffic": + %% + %% PRK (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01 + %% 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac + %% + %% hash (32 octets): 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed + %% d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 + %% + %% info (54 octets): 00 20 12 74 6c 73 31 33 20 73 20 68 73 20 74 72 + %% 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 + %% ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8 + %% + %% expanded (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d + %% 37 b4 e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38 + + %% PRK = HandshakeSecret + %% hash = CHSTHash + SHSTInfo = + hexstr2bin("00 20 12 74 6c 73 31 33 20 73 20 68 73 20 74 72 + 61 66 66 69 63 20 86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 + ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"), + + SHSTrafficSecret = + hexstr2bin("b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d + 37 b4 e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38"), + + SHSTInfo = tls_v1:create_info(<<"s hs traffic">>, CHSTHash, ssl_cipher:hash_size(HKDFAlgo)), + + SHSTrafficSecret = + tls_v1:server_handshake_traffic_secret(HKDFAlgo, {handshake_secret, HandshakeSecret}, CHSH), + + + %% {server} derive secret for master "tls13 derived": + %% + %% PRK (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01 + %% 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac + %% + %% hash (32 octets): e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 + %% 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55 + %% + %% info (49 octets): 00 20 0d 74 6c 73 31 33 20 64 65 72 69 76 65 64 + %% 20 e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4 + %% 64 9b 93 4c a4 95 99 1b 78 52 b8 55 + %% + %% expanded (32 octets): 43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 + %% 90 b5 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4 + + %% PRK = HandshakeSecret + %% hash = Hash + %% info = Info + MasterDeriveSecret = + hexstr2bin("43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 + 90 b5 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4"), + + MasterDeriveSecret = tls_v1:derive_secret(HandshakeSecret, <<"derived">>, <<>>, HKDFAlgo), + + %% {server} extract secret "master": + %% + %% salt (32 octets): 43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 90 b5 + %% 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4 + %% + %% IKM (32 octets): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + %% 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + %% + %% secret (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a + %% 47 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 + + %% salt = MasterDeriveSecret + %% IKM = IKM + MasterSecret = + hexstr2bin("18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a + 47 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19"), + + {master_secret, MasterSecret} = + tls_v1:key_schedule(master_secret, HKDFAlgo, {handshake_secret, HandshakeSecret}), + + %% {server} send handshake record: + %% + %% payload (90 octets): 02 00 00 56 03 03 a6 af 06 a4 12 18 60 dc 5e + %% 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e d3 e2 + %% 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88 76 11 + %% 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 + %% b1 b0 4e 75 1f 0f 00 2b 00 02 03 04 + %% + %% complete record (95 octets): 16 03 03 00 5a 02 00 00 56 03 03 a6 + %% af 06 a4 12 18 60 dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 + %% 34 da c1 55 77 2e d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 + %% 1d 00 20 c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 + %% cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04 + + %% payload = ServerHello + ServerHelloRecord = + hexstr2bin("16 03 03 00 5a 02 00 00 56 03 03 a6 + af 06 a4 12 18 60 dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 + 34 da c1 55 77 2e d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 + 1d 00 20 c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 + cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04"), + + {SHEncrypted, _} = + tls_record:encode_handshake(ServerHello, {3,4}, ConnStatesNull), + ServerHelloRecord = iolist_to_binary(SHEncrypted), + + %% {server} derive write traffic keys for handshake data: + %% + %% PRK (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d 37 b4 + %% e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38 + %% + %% key info (13 octets): 00 10 09 74 6c 73 31 33 20 6b 65 79 00 + %% + %% key expanded (16 octets): 3f ce 51 60 09 c2 17 27 d0 f2 e4 e8 6e + %% e4 03 bc + %% + %% iv info (12 octets): 00 0c 08 74 6c 73 31 33 20 69 76 00 + %% + %% iv expanded (12 octets): 5d 31 3e b2 67 12 76 ee 13 00 0b 30 + + %% PRK = SHSTrafficSecret + WriteKeyInfo = + hexstr2bin("00 10 09 74 6c 73 31 33 20 6b 65 79 00"), + + WriteKey = + hexstr2bin("3f ce 51 60 09 c2 17 27 d0 f2 e4 e8 6e e4 03 bc"), + + WriteIVInfo = + hexstr2bin("00 0c 08 74 6c 73 31 33 20 69 76 00"), + + WriteIV = + hexstr2bin(" 5d 31 3e b2 67 12 76 ee 13 00 0b 30"), + + Cipher = aes_128_gcm, %% TODO: get from ServerHello + + WriteKeyInfo = tls_v1:create_info(<<"key">>, <<>>, ssl_cipher:key_material(Cipher)), + %% TODO: remove hardcoded IV size + WriteIVInfo = tls_v1:create_info(<<"iv">>, <<>>, 12), + + {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, SHSTrafficSecret), + + %% {server} construct an EncryptedExtensions handshake message: + %% + %% EncryptedExtensions (40 octets): 08 00 00 24 00 22 00 0a 00 14 00 + %% 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 1c + %% 00 02 40 01 00 00 00 00 + %% + %% {server} construct a Certificate handshake message: + %% + %% Certificate (445 octets): 0b 00 01 b9 00 00 01 b5 00 01 b0 30 82 + %% 01 ac 30 82 01 15 a0 03 02 01 02 02 01 02 30 0d 06 09 2a 86 48 + %% 86 f7 0d 01 01 0b 05 00 30 0e 31 0c 30 0a 06 03 55 04 03 13 03 + %% 72 73 61 30 1e 17 0d 31 36 30 37 33 30 30 31 32 33 35 39 5a 17 + %% 0d 32 36 30 37 33 30 30 31 32 33 35 39 5a 30 0e 31 0c 30 0a 06 + %% 03 55 04 03 13 03 72 73 61 30 81 9f 30 0d 06 09 2a 86 48 86 f7 + %% 0d 01 01 01 05 00 03 81 8d 00 30 81 89 02 81 81 00 b4 bb 49 8f + %% 82 79 30 3d 98 08 36 39 9b 36 c6 98 8c 0c 68 de 55 e1 bd b8 26 + %% d3 90 1a 24 61 ea fd 2d e4 9a 91 d0 15 ab bc 9a 95 13 7a ce 6c + %% 1a f1 9e aa 6a f9 8c 7c ed 43 12 09 98 e1 87 a8 0e e0 cc b0 52 + %% 4b 1b 01 8c 3e 0b 63 26 4d 44 9a 6d 38 e2 2a 5f da 43 08 46 74 + %% 80 30 53 0e f0 46 1c 8c a9 d9 ef bf ae 8e a6 d1 d0 3e 2b d1 93 + %% ef f0 ab 9a 80 02 c4 74 28 a6 d3 5a 8d 88 d7 9f 7f 1e 3f 02 03 + %% 01 00 01 a3 1a 30 18 30 09 06 03 55 1d 13 04 02 30 00 30 0b 06 + %% 03 55 1d 0f 04 04 03 02 05 a0 30 0d 06 09 2a 86 48 86 f7 0d 01 + %% 01 0b 05 00 03 81 81 00 85 aa d2 a0 e5 b9 27 6b 90 8c 65 f7 3a + %% 72 67 17 06 18 a5 4c 5f 8a 7b 33 7d 2d f7 a5 94 36 54 17 f2 ea + %% e8 f8 a5 8c 8f 81 72 f9 31 9c f3 6b 7f d6 c5 5b 80 f2 1a 03 01 + %% 51 56 72 60 96 fd 33 5e 5e 67 f2 db f1 02 70 2e 60 8c ca e6 be + %% c1 fc 63 a4 2a 99 be 5c 3e b7 10 7c 3c 54 e9 b9 eb 2b d5 20 3b + %% 1c 3b 84 e0 a8 b2 f7 59 40 9b a3 ea c9 d9 1d 40 2d cc 0c c8 f8 + %% 96 12 29 ac 91 87 b4 2b 4d e1 00 00 + %% + %% {server} construct a CertificateVerify handshake message: + %% + %% CertificateVerify (136 octets): 0f 00 00 84 08 04 00 80 5a 74 7c + %% 5d 88 fa 9b d2 e5 5a b0 85 a6 10 15 b7 21 1f 82 4c d4 84 14 5a + %% b3 ff 52 f1 fd a8 47 7b 0b 7a bc 90 db 78 e2 d3 3a 5c 14 1a 07 + %% 86 53 fa 6b ef 78 0c 5e a2 48 ee aa a7 85 c4 f3 94 ca b6 d3 0b + %% be 8d 48 59 ee 51 1f 60 29 57 b1 54 11 ac 02 76 71 45 9e 46 44 + %% 5c 9e a5 8c 18 1e 81 8e 95 b8 c3 fb 0b f3 27 84 09 d3 be 15 2a + %% 3d a5 04 3e 06 3d da 65 cd f5 ae a2 0d 53 df ac d4 2f 74 f3 + EncryptedExtensions = + hexstr2bin("08 00 00 24 00 22 00 0a 00 14 00 + 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 1c + 00 02 40 01 00 00 00 00"), + + Certificate = + hexstr2bin("0b 00 01 b9 00 00 01 b5 00 01 b0 30 82 + 01 ac 30 82 01 15 a0 03 02 01 02 02 01 02 30 0d 06 09 2a 86 48 + 86 f7 0d 01 01 0b 05 00 30 0e 31 0c 30 0a 06 03 55 04 03 13 03 + 72 73 61 30 1e 17 0d 31 36 30 37 33 30 30 31 32 33 35 39 5a 17 + 0d 32 36 30 37 33 30 30 31 32 33 35 39 5a 30 0e 31 0c 30 0a 06 + 03 55 04 03 13 03 72 73 61 30 81 9f 30 0d 06 09 2a 86 48 86 f7 + 0d 01 01 01 05 00 03 81 8d 00 30 81 89 02 81 81 00 b4 bb 49 8f + 82 79 30 3d 98 08 36 39 9b 36 c6 98 8c 0c 68 de 55 e1 bd b8 26 + d3 90 1a 24 61 ea fd 2d e4 9a 91 d0 15 ab bc 9a 95 13 7a ce 6c + 1a f1 9e aa 6a f9 8c 7c ed 43 12 09 98 e1 87 a8 0e e0 cc b0 52 + 4b 1b 01 8c 3e 0b 63 26 4d 44 9a 6d 38 e2 2a 5f da 43 08 46 74 + 80 30 53 0e f0 46 1c 8c a9 d9 ef bf ae 8e a6 d1 d0 3e 2b d1 93 + ef f0 ab 9a 80 02 c4 74 28 a6 d3 5a 8d 88 d7 9f 7f 1e 3f 02 03 + 01 00 01 a3 1a 30 18 30 09 06 03 55 1d 13 04 02 30 00 30 0b 06 + 03 55 1d 0f 04 04 03 02 05 a0 30 0d 06 09 2a 86 48 86 f7 0d 01 + 01 0b 05 00 03 81 81 00 85 aa d2 a0 e5 b9 27 6b 90 8c 65 f7 3a + 72 67 17 06 18 a5 4c 5f 8a 7b 33 7d 2d f7 a5 94 36 54 17 f2 ea + e8 f8 a5 8c 8f 81 72 f9 31 9c f3 6b 7f d6 c5 5b 80 f2 1a 03 01 + 51 56 72 60 96 fd 33 5e 5e 67 f2 db f1 02 70 2e 60 8c ca e6 be + c1 fc 63 a4 2a 99 be 5c 3e b7 10 7c 3c 54 e9 b9 eb 2b d5 20 3b + 1c 3b 84 e0 a8 b2 f7 59 40 9b a3 ea c9 d9 1d 40 2d cc 0c c8 f8 + 96 12 29 ac 91 87 b4 2b 4d e1 00 00"), + + CertificateVerify = + hexstr2bin("0f 00 00 84 08 04 00 80 5a 74 7c + 5d 88 fa 9b d2 e5 5a b0 85 a6 10 15 b7 21 1f 82 4c d4 84 14 5a + b3 ff 52 f1 fd a8 47 7b 0b 7a bc 90 db 78 e2 d3 3a 5c 14 1a 07 + 86 53 fa 6b ef 78 0c 5e a2 48 ee aa a7 85 c4 f3 94 ca b6 d3 0b + be 8d 48 59 ee 51 1f 60 29 57 b1 54 11 ac 02 76 71 45 9e 46 44 + 5c 9e a5 8c 18 1e 81 8e 95 b8 c3 fb 0b f3 27 84 09 d3 be 15 2a + 3d a5 04 3e 06 3d da 65 cd f5 ae a2 0d 53 df ac d4 2f 74 f3"), + + %% {server} calculate finished "tls13 finished": + %% + %% PRK (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d 37 b4 + %% e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38 + %% + %% hash (0 octets): (empty) + %% + %% info (18 octets): 00 20 0e 74 6c 73 31 33 20 66 69 6e 69 73 68 65 + %% 64 00 + %% + %% expanded (32 octets): 00 8d 3b 66 f8 16 ea 55 9f 96 b5 37 e8 85 + %% c3 1f c0 68 bf 49 2c 65 2f 01 f2 88 a1 d8 cd c1 9f c8 + %% + %% finished (32 octets): 9b 9b 14 1d 90 63 37 fb d2 cb dc e7 1d f4 + %% de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 18 + + %% PRK = SHSTrafficSecret + FInfo = + hexstr2bin("00 20 0e 74 6c 73 31 33 20 66 69 6e 69 73 68 65 + 64 00"), + + FExpanded = + hexstr2bin("00 8d 3b 66 f8 16 ea 55 9f 96 b5 37 e8 85 + c3 1f c0 68 bf 49 2c 65 2f 01 f2 88 a1 d8 cd c1 9f c8"), + + FinishedVerifyData = + hexstr2bin("9b 9b 14 1d 90 63 37 fb d2 cb dc e7 1d f4 + de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 18"), + + FInfo = tls_v1:create_info(<<"finished">>, <<>>, ssl_cipher:hash_size(HKDFAlgo)), + + FExpanded = tls_v1:finished_key(SHSTrafficSecret, HKDFAlgo), + + MessageHistory0 = [CertificateVerify, + Certificate, + EncryptedExtensions, + ServerHello, + ClientHello], + + FinishedVerifyData = tls_v1:finished_verify_data(FExpanded, HKDFAlgo, MessageHistory0), + + %% {server} construct a Finished handshake message: + %% + %% Finished (36 octets): 14 00 00 20 9b 9b 14 1d 90 63 37 fb d2 cb + %% dc e7 1d f4 de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 + %% 18 + FinishedHSBin = + hexstr2bin("14 00 00 20 9b 9b 14 1d 90 63 37 fb d2 cb + dc e7 1d f4 de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 + 18"), + + FinishedHS = #finished{verify_data = FinishedVerifyData}, + + FinishedIOList = tls_handshake:encode_handshake(FinishedHS, {3,4}), + FinishedHSBin = iolist_to_binary(FinishedIOList), + + %% {server} derive secret "tls13 c ap traffic": + %% + %% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 + %% 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 + %% + %% hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a + %% 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + %% + %% info (54 octets): 00 20 12 74 6c 73 31 33 20 63 20 61 70 20 74 72 + %% 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b + %% 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + %% + %% expanded (32 octets): 9e 40 64 6c e7 9a 7f 9d c0 5a f8 88 9b ce + %% 65 52 87 5a fa 0b 06 df 00 87 f7 92 eb b7 c1 75 04 a5 + + %% PRK = MasterSecret + CAPTHash = + hexstr2bin("96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a + 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), + CAPTInfo = + hexstr2bin("00 20 12 74 6c 73 31 33 20 63 20 61 70 20 74 72 + 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b + 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), + + CAPTrafficSecret = + hexstr2bin("9e 40 64 6c e7 9a 7f 9d c0 5a f8 88 9b ce + 65 52 87 5a fa 0b 06 df 00 87 f7 92 eb b7 c1 75 04 a5"), + + CHSF = <<ClientHello/binary, + ServerHello/binary, + EncryptedExtensions/binary, + Certificate/binary, + CertificateVerify/binary, + FinishedHSBin/binary>>, + + CAPTHash = crypto:hash(HKDFAlgo, CHSF), + + CAPTInfo = + tls_v1:create_info(<<"c ap traffic">>, CAPTHash, ssl_cipher:hash_size(HKDFAlgo)), + + CAPTrafficSecret = + tls_v1:client_application_traffic_secret_0(HKDFAlgo, {master_secret, MasterSecret}, CHSF), + + %% {server} derive secret "tls13 s ap traffic": + %% + %% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 + %% 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 + %% + %% hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a + %% 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + %% + %% info (54 octets): 00 20 12 74 6c 73 31 33 20 73 20 61 70 20 74 72 + %% 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b + %% 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + %% + %% expanded (32 octets): a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 + %% 50 32 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43 + + %% PRK = MasterSecret + %% hash = CAPTHash + SAPTInfo = + hexstr2bin(" 00 20 12 74 6c 73 31 33 20 73 20 61 70 20 74 72 + 61 66 66 69 63 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b + 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), + + SAPTrafficSecret = + hexstr2bin("a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 + 50 32 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43"), + + SAPTInfo = + tls_v1:create_info(<<"s ap traffic">>, CAPTHash, ssl_cipher:hash_size(HKDFAlgo)), + + SAPTrafficSecret = + tls_v1:server_application_traffic_secret_0(HKDFAlgo, {master_secret, MasterSecret}, CHSF), + + %% {server} derive secret "tls13 exp master": + %% + %% PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 + %% 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 + %% + %% hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a + %% 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + %% + %% info (52 octets): 00 20 10 74 6c 73 31 33 20 65 78 70 20 6d 61 73 + %% 74 65 72 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a 00 + %% 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + %% + %% expanded (32 octets): fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67 + %% 92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50 + + %% PRK = MasterSecret + %% hash = CAPTHash + ExporterInfo = + hexstr2bin("00 20 10 74 6c 73 31 33 20 65 78 70 20 6d 61 73 + 74 65 72 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a 00 + 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"), + + ExporterMasterSecret = + hexstr2bin("fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67 + 92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50"), + + ExporterInfo = + tls_v1:create_info(<<"exp master">>, CAPTHash, ssl_cipher:hash_size(HKDFAlgo)), + + ExporterMasterSecret = + tls_v1:exporter_master_secret(HKDFAlgo, {master_secret, MasterSecret}, CHSF), + + %% {server} derive write traffic keys for application data: + %% + %% PRK (32 octets): a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 50 32 + %% 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43 + %% + %% key info (13 octets): 00 10 09 74 6c 73 31 33 20 6b 65 79 00 + %% + %% key expanded (16 octets): 9f 02 28 3b 6c 9c 07 ef c2 6b b9 f2 ac + %% 92 e3 56 + %% + %% iv info (12 octets): 00 0c 08 74 6c 73 31 33 20 69 76 00 + %% + %% iv expanded (12 octets): cf 78 2b 88 dd 83 54 9a ad f1 e9 84 + + %% PRK = SAPTrafficsecret + %% key info = WriteKeyInfo + %% iv info = WrtieIVInfo + SWKey = + hexstr2bin("9f 02 28 3b 6c 9c 07 ef c2 6b b9 f2 ac 92 e3 56"), + + SWIV = + hexstr2bin("cf 78 2b 88 dd 83 54 9a ad f1 e9 84"), + + {SWKey, SWIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, SAPTrafficSecret), + + %% {server} derive read traffic keys for handshake data: + %% + %% PRK (32 octets): b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e 2d 8f + %% 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21 + %% + %% key info (13 octets): 00 10 09 74 6c 73 31 33 20 6b 65 79 00 + %% + %% key expanded (16 octets): db fa a6 93 d1 76 2c 5b 66 6a f5 d9 50 + %% 25 8d 01 + %% + %% iv info (12 octets): 00 0c 08 74 6c 73 31 33 20 69 76 00 + %% + %% iv expanded (12 octets): 5b d3 c7 1b 83 6e 0b 76 bb 73 26 5f + + %% PRK = CHSTrafficsecret + %% key info = WriteKeyInfo + %% iv info = WrtieIVInfo + SRKey = + hexstr2bin("db fa a6 93 d1 76 2c 5b 66 6a f5 d9 50 25 8d 01"), + + SRIV = + hexstr2bin("5b d3 c7 1b 83 6e 0b 76 bb 73 26 5f"), + + {SRKey, SRIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, CHSTrafficSecret). + +%%-------------------------------------------------------------------- +finished_verify_data() -> + [{doc,"Test TLS 1.3 Finished message handling"}]. + +finished_verify_data(_Config) -> + ClientHello = + hexstr2bin("01 00 00 c6 03 03 00 01 02 03 04 05 06 07 08 09 + 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 + 1a 1b 1c 1d 1e 1f 20 e0 e1 e2 e3 e4 e5 e6 e7 e8 + e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 + f9 fa fb fc fd fe ff 00 06 13 01 13 02 13 03 01 + 00 00 77 00 00 00 18 00 16 00 00 13 65 78 61 6d + 70 6c 65 2e 75 6c 66 68 65 69 6d 2e 6e 65 74 00 + 0a 00 08 00 06 00 1d 00 17 00 18 00 0d 00 14 00 + 12 04 03 08 04 04 01 05 03 08 05 05 01 08 06 06 + 01 02 01 00 33 00 26 00 24 00 1d 00 20 35 80 72 + d6 36 58 80 d1 ae ea 32 9a df 91 21 38 38 51 ed + 21 a2 8e 3b 75 e9 65 d0 d2 cd 16 62 54 00 2d 00 + 02 01 01 00 2b 00 03 02 03 04"), + + ServerHello = + hexstr2bin("02 00 00 76 03 03 70 71 72 73 74 75 76 77 78 79 + 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 + 8a 8b 8c 8d 8e 8f 20 e0 e1 e2 e3 e4 e5 e6 e7 e8 + e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 + f9 fa fb fc fd fe ff 13 01 00 00 2e 00 33 00 24 + 00 1d 00 20 9f d7 ad 6d cf f4 29 8d d3 f9 6d 5b + 1b 2a f9 10 a0 53 5b 14 88 d7 f8 fa bb 34 9a 98 + 28 80 b6 15 00 2b 00 02 03 04"), + + EncryptedExtensions = + hexstr2bin("08 00 00 02 00 00"), + + Certificate = + hexstr2bin("0b 00 03 2e 00 00 03 2a 00 03 25 30 82 03 21 30 + 82 02 09 a0 03 02 01 02 02 08 15 5a 92 ad c2 04 + 8f 90 30 0d 06 09 2a 86 48 86 f7 0d 01 01 0b 05 + 00 30 22 31 0b 30 09 06 03 55 04 06 13 02 55 53 + 31 13 30 11 06 03 55 04 0a 13 0a 45 78 61 6d 70 + 6c 65 20 43 41 30 1e 17 0d 31 38 31 30 30 35 30 + 31 33 38 31 37 5a 17 0d 31 39 31 30 30 35 30 31 + 33 38 31 37 5a 30 2b 31 0b 30 09 06 03 55 04 06 + 13 02 55 53 31 1c 30 1a 06 03 55 04 03 13 13 65 + 78 61 6d 70 6c 65 2e 75 6c 66 68 65 69 6d 2e 6e + 65 74 30 82 01 22 30 0d 06 09 2a 86 48 86 f7 0d + 01 01 01 05 00 03 82 01 0f 00 30 82 01 0a 02 82 + 01 01 00 c4 80 36 06 ba e7 47 6b 08 94 04 ec a7 + b6 91 04 3f f7 92 bc 19 ee fb 7d 74 d7 a8 0d 00 + 1e 7b 4b 3a 4a e6 0f e8 c0 71 fc 73 e7 02 4c 0d + bc f4 bd d1 1d 39 6b ba 70 46 4a 13 e9 4a f8 3d + f3 e1 09 59 54 7b c9 55 fb 41 2d a3 76 52 11 e1 + f3 dc 77 6c aa 53 37 6e ca 3a ec be c3 aa b7 3b + 31 d5 6c b6 52 9c 80 98 bc c9 e0 28 18 e2 0b f7 + f8 a0 3a fd 17 04 50 9e ce 79 bd 9f 39 f1 ea 69 + ec 47 97 2e 83 0f b5 ca 95 de 95 a1 e6 04 22 d5 + ee be 52 79 54 a1 e7 bf 8a 86 f6 46 6d 0d 9f 16 + 95 1a 4c f7 a0 46 92 59 5c 13 52 f2 54 9e 5a fb + 4e bf d7 7a 37 95 01 44 e4 c0 26 87 4c 65 3e 40 + 7d 7d 23 07 44 01 f4 84 ff d0 8f 7a 1f a0 52 10 + d1 f4 f0 d5 ce 79 70 29 32 e2 ca be 70 1f df ad + 6b 4b b7 11 01 f4 4b ad 66 6a 11 13 0f e2 ee 82 + 9e 4d 02 9d c9 1c dd 67 16 db b9 06 18 86 ed c1 + ba 94 21 02 03 01 00 01 a3 52 30 50 30 0e 06 03 + 55 1d 0f 01 01 ff 04 04 03 02 05 a0 30 1d 06 03 + 55 1d 25 04 16 30 14 06 08 2b 06 01 05 05 07 03 + 02 06 08 2b 06 01 05 05 07 03 01 30 1f 06 03 55 + 1d 23 04 18 30 16 80 14 89 4f de 5b cc 69 e2 52 + cf 3e a3 00 df b1 97 b8 1d e1 c1 46 30 0d 06 09 + 2a 86 48 86 f7 0d 01 01 0b 05 00 03 82 01 01 00 + 59 16 45 a6 9a 2e 37 79 e4 f6 dd 27 1a ba 1c 0b + fd 6c d7 55 99 b5 e7 c3 6e 53 3e ff 36 59 08 43 + 24 c9 e7 a5 04 07 9d 39 e0 d4 29 87 ff e3 eb dd + 09 c1 cf 1d 91 44 55 87 0b 57 1d d1 9b df 1d 24 + f8 bb 9a 11 fe 80 fd 59 2b a0 39 8c de 11 e2 65 + 1e 61 8c e5 98 fa 96 e5 37 2e ef 3d 24 8a fd e1 + 74 63 eb bf ab b8 e4 d1 ab 50 2a 54 ec 00 64 e9 + 2f 78 19 66 0d 3f 27 cf 20 9e 66 7f ce 5a e2 e4 + ac 99 c7 c9 38 18 f8 b2 51 07 22 df ed 97 f3 2e + 3e 93 49 d4 c6 6c 9e a6 39 6d 74 44 62 a0 6b 42 + c6 d5 ba 68 8e ac 3a 01 7b dd fc 8e 2c fc ad 27 + cb 69 d3 cc dc a2 80 41 44 65 d3 ae 34 8c e0 f3 + 4a b2 fb 9c 61 83 71 31 2b 19 10 41 64 1c 23 7f + 11 a5 d6 5c 84 4f 04 04 84 99 38 71 2b 95 9e d6 + 85 bc 5c 5d d6 45 ed 19 90 94 73 40 29 26 dc b4 + 0e 34 69 a1 59 41 e8 e2 cc a8 4b b6 08 46 36 a0 + 00 00"), + + CertificateVerify = + hexstr2bin("0f 00 01 04 08 04 01 00 17 fe b5 33 ca 6d 00 7d + 00 58 25 79 68 42 4b bc 3a a6 90 9e 9d 49 55 75 + 76 a5 20 e0 4a 5e f0 5f 0e 86 d2 4f f4 3f 8e b8 + 61 ee f5 95 22 8d 70 32 aa 36 0f 71 4e 66 74 13 + 92 6e f4 f8 b5 80 3b 69 e3 55 19 e3 b2 3f 43 73 + df ac 67 87 06 6d cb 47 56 b5 45 60 e0 88 6e 9b + 96 2c 4a d2 8d ab 26 ba d1 ab c2 59 16 b0 9a f2 + 86 53 7f 68 4f 80 8a ef ee 73 04 6c b7 df 0a 84 + fb b5 96 7a ca 13 1f 4b 1c f3 89 79 94 03 a3 0c + 02 d2 9c bd ad b7 25 12 db 9c ec 2e 5e 1d 00 e5 + 0c af cf 6f 21 09 1e bc 4f 25 3c 5e ab 01 a6 79 + ba ea be ed b9 c9 61 8f 66 00 6b 82 44 d6 62 2a + aa 56 88 7c cf c6 6a 0f 38 51 df a1 3a 78 cf f7 + 99 1e 03 cb 2c 3a 0e d8 7d 73 67 36 2e b7 80 5b + 00 b2 52 4f f2 98 a4 da 48 7c ac de af 8a 23 36 + c5 63 1b 3e fa 93 5b b4 11 e7 53 ca 13 b0 15 fe + c7 e4 a7 30 f1 36 9f 9e"), + + BaseKey = + hexstr2bin("a2 06 72 65 e7 f0 65 2a 92 3d 5d 72 ab 04 67 c4 + 61 32 ee b9 68 b6 a3 2d 31 1c 80 58 68 54 88 14"), + + VerifyData = + hexstr2bin("ea 6e e1 76 dc cc 4a f1 85 9e 9e 4e 93 f7 97 ea + c9 a7 8c e4 39 30 1e 35 27 5a d4 3f 3c dd bd e3"), + + Messages = [CertificateVerify, + Certificate, + EncryptedExtensions, + ServerHello, + ClientHello], + + FinishedKey = tls_v1:finished_key(BaseKey, sha256), + VerifyData = tls_v1:finished_verify_data(FinishedKey, sha256, Messages). + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- + +hexstr2int(S) -> + B = hexstr2bin(S), + Bits = size(B) * 8, + <<Integer:Bits/integer>> = B, + Integer. + +hexstr2bin(S) when is_binary(S) -> + hexstr2bin(S, <<>>); +hexstr2bin(S) -> + hexstr2bin(list_to_binary(S), <<>>). +%% +hexstr2bin(<<>>, Acc) -> + Acc; +hexstr2bin(<<C,T/binary>>, Acc) when C =:= 32; %% SPACE + C =:= 10; %% LF + C =:= 13 -> %% CR + hexstr2bin(T, Acc); +hexstr2bin(<<X,Y,T/binary>>, Acc) -> + I = hex2int(X) * 16 + hex2int(Y), + hexstr2bin(T, <<Acc/binary,I>>). + +hex2int(C) when $0 =< C, C =< $9 -> + C - $0; +hex2int(C) when $A =< C, C =< $F -> + C - $A + 10; +hex2int(C) when $a =< C, C =< $f -> + C - $a + 10. diff --git a/lib/ssl/test/tls_1_3_version_SUITE.erl b/lib/ssl/test/tls_1_3_version_SUITE.erl new file mode 100644 index 0000000000..f0b224d4e5 --- /dev/null +++ b/lib/ssl/test/tls_1_3_version_SUITE.erl @@ -0,0 +1,153 @@ +%% +%% %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(tls_1_3_version_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() -> + [ + {group, 'tlsv1.3'} + ]. + +groups() -> + [ + {'tlsv1.3', [], cert_groups()}, + {rsa, [], tests()}, + {ecdsa, [], tests()} + ]. + +cert_groups() -> + [{group, rsa}, + {group, ecdsa}]. + +tests() -> + [tls13_client_tls12_server, + tls13_client_with_ext_tls12_server, + tls12_client_tls13_server]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + [{client_type, erlang}, {server_type, erlang} | + Config] + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto). + +init_per_group(rsa, Config0) -> + Config = ssl_test_lib:make_rsa_cert(Config0), + COpts = proplists:get_value(client_rsa_opts, Config), + SOpts = proplists:get_value(server_rsa_opts, Config), + [{client_cert_opts, COpts}, {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))]; +init_per_group(ecdsa, Config0) -> + PKAlg = crypto:supports(public_keys), + case lists:member(ecdsa, PKAlg) andalso + (lists:member(ecdh, PKAlg) orelse lists:member(dh, PKAlg)) of + true -> + Config = ssl_test_lib:make_ecdsa_cert(Config0), + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), + [{client_cert_opts, COpts}, {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))]; + false -> + {skip, "Missing EC crypto support"} + end; +init_per_group(GroupName, Config) -> + ssl_test_lib:clean_tls_version(Config), + case ssl_test_lib:is_tls_version(GroupName) andalso + ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + _ -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl:start(), + Config; + false -> + {skip, "Missing crypto support"} + end + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +tls13_client_tls12_server() -> + [{doc,"Test that a TLS 1.3 client can connect to a TLS 1.2 server."}]. + +tls13_client_tls12_server(Config) when is_list(Config) -> + ClientOpts = [{versions, + ['tlsv1.3', 'tlsv1.2']} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{versions, + ['tlsv1.1', 'tlsv1.2']} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +tls13_client_with_ext_tls12_server() -> + [{doc,"Test basic connection between TLS 1.2 server and TLS 1.3 client when " + "client has TLS 1.3 specsific extensions"}]. + +tls13_client_with_ext_tls12_server(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + ServerOpts = [{versions, ['tlsv1.2']}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {signature_algs_cert, [ecdsa_secp384r1_sha384, + ecdsa_secp256r1_sha256, + rsa_pss_rsae_sha256, + rsa_pkcs1_sha256, + {sha256,rsa},{sha256,dsa}]}|ClientOpts0], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +tls12_client_tls13_server() -> + [{doc,"Test that a TLS 1.2 client can connect to a TLS 1.3 server."}]. + +tls12_client_tls13_server(Config) when is_list(Config) -> + ClientOpts = [{versions, + ['tlsv1.1', 'tlsv1.2']} | ssl_test_lib:ssl_options(client_cert_opts, Config)], + ServerOpts = [{versions, + ['tlsv1.3', 'tlsv1.2']} | ssl_test_lib:ssl_options(server_cert_opts, Config)], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + diff --git a/lib/ssl/test/tls_api_SUITE.erl b/lib/ssl/test/tls_api_SUITE.erl new file mode 100644 index 0000000000..5a74ec1892 --- /dev/null +++ b/lib/ssl/test/tls_api_SUITE.erl @@ -0,0 +1,880 @@ +%% +%% %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(tls_api_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). +-include_lib("ssl/src/ssl_record.hrl"). +-include_lib("ssl/src/ssl_internal.hrl"). +-include_lib("ssl/src/ssl_api.hrl"). +-include_lib("ssl/src/tls_handshake.hrl"). + +-define(SLEEP, 500). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + [ + {group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'} + ]. + +groups() -> + [ + {'tlsv1.3', [], api_tests() -- [sockname]}, + {'tlsv1.2', [], api_tests()}, + {'tlsv1.1', [], api_tests()}, + {'tlsv1', [], api_tests()}, + {'sslv3', [], api_tests() ++ [ssl3_cipher_suite_limitation]} + ]. + +api_tests() -> + [ + tls_upgrade, + tls_upgrade_with_timeout, + tls_downgrade, + tls_shutdown, + tls_shutdown_write, + tls_shutdown_both, + tls_shutdown_error, + tls_client_closes_socket, + tls_closed_in_active_once, + tls_tcp_msg, + tls_tcp_msg_big, + tls_dont_crash_on_handshake_garbage, + tls_tcp_error_propagation_in_active_mode, + peername, + sockname, + tls_server_handshake_timeout, + transport_close, + emulated_options, + accept_pool, + reuseaddr + ]. + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl_test_lib:make_rsa_cert(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config); + false -> + {skip, "Missing crypto support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + ssl_test_lib:clean_tls_version(Config); + false -> + Config + end. + +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +tls_upgrade() -> + [{doc,"Test that you can upgrade an tcp connection to an ssl connection"}]. + +tls_upgrade(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}], + + Server = ssl_test_lib:start_upgrade_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, + upgrade_result, []}}, + {tcp_options, + [{active, false} | TcpOpts]}, + {ssl_options, [{verify, verify_peer} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_upgrade_client([{node, ClientNode}, + {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, upgrade_result, []}}, + {tcp_options, [binary]}, + {ssl_options, [{verify, verify_peer}, + {server_name_indication, Hostname} | ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +tls_upgrade_with_timeout() -> + [{doc,"Test ssl_accept/3"}]. + +tls_upgrade_with_timeout(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}], + + Server = ssl_test_lib:start_upgrade_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {timeout, 5000}, + {mfa, {?MODULE, + upgrade_result, []}}, + {tcp_options, + [{active, false} | TcpOpts]}, + {ssl_options, [{verify, verify_peer} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_upgrade_client([{node, ClientNode}, + {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, upgrade_result, []}}, + {tcp_options, TcpOpts}, + {ssl_options, [{verify, verify_peer}, + {server_name_indication, Hostname} | ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +tls_downgrade() -> + [{doc,"Test that you can downgarde an ssl connection to an tcp connection"}]. +tls_downgrade(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_downgrade_result, [self()]}}, + {options, [{active, false}, {verify, verify_peer} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, tls_downgrade_result, [self()]}}, + {options, [{active, false}, {verify, verify_peer} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ready, Client, ready), + + Server ! go, + Client ! go, + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +%%-------------------------------------------------------------------- +tls_shutdown() -> + [{doc,"Test API function ssl:shutdown/2"}]. +tls_shutdown(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_shutdown_result, [server]}}, + {options, [{exit_on_close, false}, + {active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, + {?MODULE, tls_shutdown_result, [client]}}, + {options, + [{exit_on_close, false}, + {active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +tls_shutdown_write() -> + [{doc,"Test API function ssl:shutdown/2 with option write."}]. +tls_shutdown_write(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_shutdown_write_result, [server]}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, tls_shutdown_write_result, [client]}}, + {options, [{active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, {error, closed}). + +%%-------------------------------------------------------------------- +tls_shutdown_both() -> + [{doc,"Test API function ssl:shutdown/2 with option both."}]. +tls_shutdown_both(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_shutdown_both_result, [server]}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, tls_shutdown_both_result, [client]}}, + {options, [{active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, {error, closed}). + +%%-------------------------------------------------------------------- +tls_shutdown_error() -> + [{doc,"Test ssl:shutdown/2 error handling"}]. +tls_shutdown_error(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + Port = ssl_test_lib:inet_port(node()), + {ok, Listen} = ssl:listen(Port, ServerOpts), + {error, enotconn} = ssl:shutdown(Listen, read_write), + ok = ssl:close(Listen), + {error, closed} = ssl:shutdown(Listen, read_write). +%%-------------------------------------------------------------------- +tls_client_closes_socket() -> + [{doc,"Test what happens when client closes socket before handshake is compleated"}]. + +tls_client_closes_socket(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}], + + Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {tcp_options, TcpOpts}, + {ssl_options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Connect = fun() -> + {ok, _Socket} = rpc:call(ClientNode, gen_tcp, connect, + [Hostname, Port, [binary]]), + %% Make sure that ssl_accept is called before + %% client process ends and closes socket. + ct:sleep(?SLEEP) + end, + + _Client = spawn_link(Connect), + + ssl_test_lib:check_result(Server, {error,closed}). + +%%-------------------------------------------------------------------- +tls_closed_in_active_once() -> + [{doc, "Test that ssl_closed is delivered in active once with non-empty buffer, check ERL-420."}]. + +tls_closed_in_active_once(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {_ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}], + Port = ssl_test_lib:inet_port(node()), + Server = fun() -> + {ok, Listen} = gen_tcp:listen(Port, TcpOpts), + {ok, TcpServerSocket} = gen_tcp:accept(Listen), + {ok, ServerSocket} = ssl:handshake(TcpServerSocket, ServerOpts), + lists:foreach( + fun(_) -> + ssl:send(ServerSocket, "some random message\r\n") + end, lists:seq(1, 20)), + %% Close TCP instead of SSL socket to trigger the bug: + gen_tcp:close(TcpServerSocket), + gen_tcp:close(Listen) + end, + spawn_link(Server), + {ok, Socket} = ssl:connect(Hostname, Port, [{active, false} | ClientOpts]), + Result = tls_closed_in_active_once_loop(Socket), + ssl:close(Socket), + case Result of + ok -> ok; + _ -> ct:fail(Result) + end. +%%-------------------------------------------------------------------- +tls_tcp_msg() -> + [{doc,"Test what happens when a tcp tries to connect, i,e. a bad (ssl) packet is sent first"}]. + +tls_tcp_msg(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}, {active, false}], + + Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {timeout, 5000}, + {mfa, {?MODULE, dummy, []}}, + {tcp_options, TcpOpts}, + {ssl_options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {packet, 0}]), + ct:log("Testcase ~p connected to Server ~p ~n", [self(), Server]), + gen_tcp:send(Socket, "<SOME GARBLED NON SSL MESSAGE>"), + + receive + {tcp_closed, Socket} -> + receive + {Server, {error, Error}} -> + ct:log("Error ~p", [Error]) + end + end. +%%-------------------------------------------------------------------- +tls_tcp_msg_big() -> + [{doc,"Test what happens when a tcp tries to connect, i,e. a bad big (ssl) packet is sent first"}]. + +tls_tcp_msg_big(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + TcpOpts = [binary, {reuseaddr, true}], + + Rand = crypto:strong_rand_bytes(?MAX_CIPHER_TEXT_LENGTH+1), + Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {timeout, 5000}, + {mfa, {?MODULE, dummy, []}}, + {tcp_options, TcpOpts}, + {ssl_options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {packet, 0}]), + ct:log("Testcase ~p connected to Server ~p ~n", [self(), Server]), + + gen_tcp:send(Socket, <<?BYTE(0), + ?BYTE(3), ?BYTE(1), ?UINT16(?MAX_CIPHER_TEXT_LENGTH), Rand/binary>>), + + receive + {tcp_closed, Socket} -> + receive + {Server, {error, timeout}} -> + ct:fail("hangs"); + {Server, {error, Error}} -> + ct:log("Error ~p", [Error]); + {'EXIT', Server, _} -> + ok + end + end. + +%%-------------------------------------------------------------------- +tls_dont_crash_on_handshake_garbage() -> + [{doc, "Ensure SSL server worker thows an alert on garbage during handshake " + "instead of crashing and exposing state to user code"}]. + +tls_dont_crash_on_handshake_garbage(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {_ClientNode, ServerNode, Hostname} = 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, ServerOpts}]), + unlink(Server), monitor(process, Server), + Port = ssl_test_lib:inet_port(Server), + + {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]), + + % Send hello and garbage record + ok = gen_tcp:send(Socket, + [<<22, 3,3, 49:16, 1, 45:24, 3,3, % client_hello + 16#deadbeef:256, % 32 'random' bytes = 256 bits + 0, 6:16, 0,255, 0,61, 0,57, 1, 0 >>, % some hello values + + <<22, 3,3, 5:16, 92,64,37,228,209>> % garbage + ]), + % Send unexpected change_cipher_spec + ok = gen_tcp:send(Socket, <<20, 3,3, 12:16, 111,40,244,7,137,224,16,109,197,110,249,152>>), + + % Ensure we receive an alert, not sudden disconnect + {ok, <<21, _/binary>>} = drop_handshakes(Socket, 1000). + +%%-------------------------------------------------------------------- +tls_tcp_error_propagation_in_active_mode() -> + [{doc,"Test that process recives {ssl_error, Socket, closed} when tcp error ocurres"}]. +tls_tcp_error_propagation_in_active_mode(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + {Client, #sslsocket{pid=[Pid|_]} = SslSocket} = ssl_test_lib:start_client([return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, receive_msg, []}}, + {options, ClientOpts}]), + + {status, _, _, StatusInfo} = sys:get_status(Pid), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + StaticEnv = element(2, State), + Socket = element(11, StaticEnv), + %% Fake tcp error + Pid ! {tcp_error, Socket, etimedout}, + + ssl_test_lib:check_result(Client, {ssl_closed, SslSocket}). + +%%-------------------------------------------------------------------- +peername() -> + [{doc,"Test API function peername/1"}]. + +peername(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, peername_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, peername_result, []}}, + {options, [{port, 0} | ClientOpts]}]), + + ClientPort = ssl_test_lib:inet_port(Client), + ServerIp = ssl_test_lib:node_to_hostip(ServerNode, server), + ClientIp = ssl_test_lib:node_to_hostip(ClientNode, client), + ServerMsg = {ok, {ClientIp, ClientPort}}, + ClientMsg = {ok, {ServerIp, Port}}, + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +sockname() -> + [{doc,"Test API function sockname/1"}]. +sockname(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, sockname_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, sockname_result, []}}, + {options, [{port, 0} | ClientOpts]}]), + + ClientPort = ssl_test_lib:inet_port(Client), + ServerIp = ssl_test_lib:node_to_hostip(ServerNode, server), + ClientIp = ssl_test_lib:node_to_hostip(ClientNode, client), + ServerMsg = {ok, {ServerIp, Port}}, + ClientMsg = {ok, {ClientIp, ClientPort}}, + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +tls_server_handshake_timeout() -> + [{doc,"Test server handshake timeout"}]. + +tls_server_handshake_timeout(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {timeout, 5000}, + {mfa, {ssl_test_lib, + no_result_msg, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + {ok, CSocket} = gen_tcp:connect(Hostname, Port, [binary, {active, true}]), + + receive + {tcp_closed, CSocket} -> + ssl_test_lib:check_result(Server, {error, timeout}), + receive + {'EXIT', Server, _} -> + %% Make sure supervisor had time to react on process exit + %% Could we come up with a better solution to this? + ct:sleep(500), + [] = supervisor:which_children(tls_connection_sup) + end + end. +transport_close() -> + [{doc, "Test what happens if socket is closed on TCP level after a while of normal operation"}]. +transport_close(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = 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, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + {ok, TcpS} = rpc:call(ClientNode, gen_tcp, connect, + [Hostname,Port,[binary, {active, false}]]), + {ok, SslS} = rpc:call(ClientNode, ssl, connect, + [TcpS,[{active, false}|ClientOpts]]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), self(), Server]), + ok = ssl:send(SslS, "Hello world"), + {ok,<<"Hello world">>} = ssl:recv(SslS, 11), + gen_tcp:close(TcpS), + {error, _} = ssl:send(SslS, "Hello world"). + +%%-------------------------------------------------------------------- +ssl3_cipher_suite_limitation() -> + [{doc,"Test a SSLv3 client cannot negotiate a TLSv* cipher suite."}]. +ssl3_cipher_suite_limitation(Config) when is_list(Config) -> + + {_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]), + ok = gen_tcp:send(Socket, + <<22, 3,0, 49:16, % handshake, SSL 3.0, length + 1, 45:24, % client_hello, length + 3,0, % SSL 3.0 + 16#deadbeef:256, % 32 'random' bytes = 256 bits + 0, % no session ID + %% three cipher suites -- null, one with sha256 hash and one with sha hash + 6:16, 0,255, 0,61, 0,57, + 1, 0 % no compression + >>), + {ok, <<22, RecMajor:8, RecMinor:8, _RecLen:16, 2, HelloLen:24>>} = gen_tcp:recv(Socket, 9, 10000), + {ok, <<HelloBin:HelloLen/binary>>} = gen_tcp:recv(Socket, HelloLen, 5000), + ServerHello = tls_handshake:decode_handshake({RecMajor, RecMinor}, 2, HelloBin), + case ServerHello of + #server_hello{server_version = {3,0}, cipher_suite = <<0,57>>} -> + ok; + _ -> + ct:fail({unexpected_server_hello, ServerHello}) + end. +%%-------------------------------------------------------------------- +emulated_options() -> + [{doc,"Test API function getopts/2 and setopts/2"}]. + +emulated_options(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Values = [{mode, list}, {packet, 0}, {header, 0}, + {active, true}], + %% Shall be the reverse order of Values! + Options = [active, header, packet, mode], + + NewValues = [{mode, binary}, {active, once}], + %% Shall be the reverse order of NewValues! + NewOptions = [active, mode], + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_socket_options_result, + [Options, Values, NewOptions, NewValues]}}, + {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, tls_socket_options_result, + [Options, Values, NewOptions, NewValues]}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + + {ok, Listen} = ssl:listen(0, ServerOpts), + {ok,[{mode,list}]} = ssl:getopts(Listen, [mode]), + ok = ssl:setopts(Listen, [{mode, binary}]), + {ok,[{mode, binary}]} = ssl:getopts(Listen, [mode]), + {ok,[{recbuf, _}]} = ssl:getopts(Listen, [recbuf]), + ssl:close(Listen). +accept_pool() -> + [{doc,"Test having an accept pool."}]. +accept_pool(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server0 = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {accepters, 3}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server0), + [Server1, Server2] = ssl_test_lib:accepters(2), + + Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts} + ]), + + Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts} + ]), + + Client2 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts} + ]), + + ssl_test_lib:check_ok([Server0, Server1, Server2, Client0, Client1, Client2]), + + ssl_test_lib:close(Server0), + ssl_test_lib:close(Server1), + ssl_test_lib:close(Server2), + ssl_test_lib:close(Client0), + ssl_test_lib:close(Client1), + ssl_test_lib:close(Client2). + +%%-------------------------------------------------------------------- +reuseaddr() -> + [{doc,"Test reuseaddr option"}]. + +reuseaddr(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{active, false}, {reuseaddr, true}| ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{active, false} | ClientOpts]}]), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + + Server1 = + ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false}, {reuseaddr, true} | ServerOpts]}]), + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ClientOpts]}]), + + ssl_test_lib:check_result(Server1, ok, Client1, ok), + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client1). + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- + +upgrade_result(Socket) -> + ssl:setopts(Socket, [{active, true}]), + ok = ssl:send(Socket, "Hello world"), + %% Make sure binary is inherited from tcp socket and that we do + %% not get the list default! + receive + {ssl, _, <<"H">>} -> + receive + {ssl, _, <<"ello world">>} -> + ok + end; + {ssl, _, <<"Hello world">>} -> + ok + end. +tls_downgrade_result(Socket, Pid) -> + ok = ssl_test_lib:send_recv_result(Socket), + Pid ! {self(), ready}, + receive + go -> + ok + end, + case ssl:close(Socket, {self(), 10000}) of + {ok, TCPSocket} -> + inet:setopts(TCPSocket, [{active, true}]), + gen_tcp:send(TCPSocket, "Downgraded"), + receive + {tcp, TCPSocket, <<"Downgraded">>} -> + ok; + {tcp_closed, TCPSocket} -> + ct:fail("Peer timed out, downgrade aborted"), + ok; + Other -> + {error, Other} + end; + {error, timeout} -> + ct:fail("Timed out, downgrade aborted"), + ok; + Fail -> + {error, Fail} + end. + +tls_shutdown_result(Socket, server) -> + ssl:send(Socket, "Hej"), + ok = ssl:shutdown(Socket, write), + {ok, "Hej hopp"} = ssl:recv(Socket, 8), + ok; + +tls_shutdown_result(Socket, client) -> + ssl:send(Socket, "Hej hopp"), + ok = ssl:shutdown(Socket, write), + {ok, "Hej"} = ssl:recv(Socket, 3), + ok. + +tls_shutdown_write_result(Socket, server) -> + ct:sleep(?SLEEP), + ssl:shutdown(Socket, write); +tls_shutdown_write_result(Socket, client) -> + ssl:recv(Socket, 0). + +tls_shutdown_both_result(Socket, server) -> + ct:sleep(?SLEEP), + ssl:shutdown(Socket, read_write); +tls_shutdown_both_result(Socket, client) -> + ssl:recv(Socket, 0). + +tls_closed_in_active_once_loop(Socket) -> + case ssl:setopts(Socket, [{active, once}]) of + ok -> + receive + {ssl, Socket, _} -> + tls_closed_in_active_once_loop(Socket); + {ssl_closed, Socket} -> + ok + after 5000 -> + no_ssl_closed_received + end; + {error, closed} -> + ok + end. + +drop_handshakes(Socket, Timeout) -> + {ok, <<RecType:8, _RecMajor:8, _RecMinor:8, RecLen:16>> = Header} = gen_tcp:recv(Socket, 5, Timeout), + {ok, <<Frag:RecLen/binary>>} = gen_tcp:recv(Socket, RecLen, Timeout), + case RecType of + 22 -> drop_handshakes(Socket, Timeout); + _ -> {ok, <<Header/binary, Frag/binary>>} + end. + +receive_msg(_) -> + receive + Msg -> + Msg + end. + +sockname_result(S) -> + ssl:sockname(S). + +peername_result(S) -> + ssl:peername(S). + +tls_socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) -> + %% Test get/set emulated opts + {ok, DefaultValues} = ssl:getopts(Socket, Options), + ssl:setopts(Socket, NewValues), + {ok, NewValues} = ssl:getopts(Socket, NewOptions), + %% Test get/set inet opts + {ok,[{nodelay,false}]} = ssl:getopts(Socket, [nodelay]), + ssl:setopts(Socket, [{nodelay, true}]), + {ok,[{nodelay, true}]} = ssl:getopts(Socket, [nodelay]), + {ok, All} = ssl:getopts(Socket, []), + ct:log("All opts ~p~n", [All]), + ok. + diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index 3c66ffd852..c9547cae36 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -1 +1 @@ -SSL_VSN = 9.3 +SSL_VSN = 9.3.5 diff --git a/lib/stdlib/doc/src/binary.xml b/lib/stdlib/doc/src/binary.xml index f3d4edd30f..fd991f258b 100644 --- a/lib/stdlib/doc/src/binary.xml +++ b/lib/stdlib/doc/src/binary.xml @@ -505,15 +505,16 @@ store(Binary, GBSet) -> <<1,1,1,1,1 ... 2> byte_size(A). 100 -3> binary:referenced_byte_size(A) +3> binary:referenced_byte_size(A). 100 -4> <<_:10/binary,B:10/binary,_/binary>> = A. +4> <<B:10/binary, C:90/binary>> = A. <<1,1,1,1,1 ... -5> byte_size(B). -10 -6> binary:referenced_byte_size(B) -100</code> - +5> {byte_size(B), binary:referenced_byte_size(B)}. +{10,10} +6> {byte_size(C), binary:referenced_byte_size(C)}. +{90,100}</code> + <p>In the above example, the small binary <c>B</c> was copied while the + larger binary <c>C</c> references binary <c>A</c>.</p> <note> <p>Binary data is shared among processes. If another process still references the larger binary, copying the part this diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml index 6f6849a19d..ef548ad643 100644 --- a/lib/stdlib/doc/src/gen_statem.xml +++ b/lib/stdlib/doc/src/gen_statem.xml @@ -40,7 +40,7 @@ <p> This reference manual describes types generated from the types in the <c>gen_statem</c> source code, so they are correct. - However, the generated descriptions also reflect the type hiearchy, + However, the generated descriptions also reflect the type hierarchy, which makes them kind of hard to read. </p> <p> diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml index 605a9f224d..66624c43be 100644 --- a/lib/stdlib/doc/src/notes.xml +++ b/lib/stdlib/doc/src/notes.xml @@ -31,6 +31,40 @@ </header> <p>This document describes the changes made to the STDLIB application.</p> +<section><title>STDLIB 3.9.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> Fix a bug that could cause a loop when formatting + terms using the control sequences <c>p</c> or <c>P</c> + and limiting the output with the option + <c>chars_limit</c>. </p> + <p> + Own Id: OTP-15875 Aux Id: ERL-967 </p> + </item> + </list> + </section> + +</section> + +<section><title>STDLIB 3.9.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> Fix a bug that could cause a failure when formatting + binaries using the control sequences <c>p</c> or <c>P</c> + and limiting the output with the option + <c>chars_limit</c>. </p> + <p> + Own Id: OTP-15847 Aux Id: ERL-957 </p> + </item> + </list> + </section> + +</section> + <section><title>STDLIB 3.9</title> <section><title>Fixed Bugs and Malfunctions</title> @@ -272,6 +306,40 @@ </section> +<section><title>STDLIB 3.8.2.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> Fix a bug that could cause a loop when formatting + terms using the control sequences <c>p</c> or <c>P</c> + and limiting the output with the option + <c>chars_limit</c>. </p> + <p> + Own Id: OTP-15875 Aux Id: ERL-967 </p> + </item> + </list> + </section> + +</section> + +<section><title>STDLIB 3.8.2.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> Fix a bug that could cause a failure when formatting + binaries using the control sequences <c>p</c> or <c>P</c> + and limiting the output with the option + <c>chars_limit</c>. </p> + <p> + Own Id: OTP-15847 Aux Id: ERL-957 </p> + </item> + </list> + </section> + +</section> + <section><title>STDLIB 3.8.2</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/stdlib/src/io_lib_pretty.erl b/lib/stdlib/src/io_lib_pretty.erl index b1a5991bf0..77f02eafe0 100644 --- a/lib/stdlib/src/io_lib_pretty.erl +++ b/lib/stdlib/src/io_lib_pretty.erl @@ -462,7 +462,9 @@ find_upper(Lower, Term, T, Dl, Dd, D, RF, Enc, Str) -> case If of {_, _, _Dots=0, _} -> % even if Len > T If; - {_, Len, _, _} when Len =< T, D1 < D orelse D < 0 -> + {_, _Len=T, _, _} -> % increasing the depth is meaningless + If; + {_, Len, _, _} when Len < T, D1 < D orelse D < 0 -> find_upper(If, Term, T, D1, Dd2, D, RF, Enc, Str); _ -> search_depth(Lower, If, Term, T, Dl, D1, RF, Enc, Str) @@ -780,6 +782,8 @@ printable_bin0(Bin, D, T, Enc) -> end, printable_bin(Bin, Len, D, Enc). +printable_bin(_Bin, 0, _D, _Enc) -> + false; printable_bin(Bin, Len, D, latin1) -> N = erlang:min(20, Len), L = binary_to_list(Bin, 1, N), diff --git a/lib/stdlib/src/ordsets.erl b/lib/stdlib/src/ordsets.erl index 176047079b..95b83ded8c 100644 --- a/lib/stdlib/src/ordsets.erl +++ b/lib/stdlib/src/ordsets.erl @@ -150,13 +150,8 @@ union(Es1, []) -> Es1. OrdsetList :: [ordset(T)], Ordset :: ordset(T). -union([S1,S2|Ss]) -> - union1(union(S1, S2), Ss); -union([S]) -> S; -union([]) -> []. - -union1(S1, [S2|Ss]) -> union1(union(S1, S2), Ss); -union1(S1, []) -> S1. +union(OrdsetList) -> + lists:umerge(OrdsetList). %% intersection(OrdSet1, OrdSet2) -> OrdSet. %% Return the intersection of OrdSet1 and OrdSet2. diff --git a/lib/stdlib/src/re.erl b/lib/stdlib/src/re.erl index 726b409d4d..197564b895 100644 --- a/lib/stdlib/src/re.erl +++ b/lib/stdlib/src/re.erl @@ -33,6 +33,8 @@ %%% BIFs +-export([internal_run/4]). + -export([version/0, compile/1, compile/2, run/2, run/3, inspect/2]). -spec version() -> binary(). @@ -100,6 +102,40 @@ run(_, _) -> run(_, _, _) -> erlang:nif_error(undef). +-spec internal_run(Subject, RE, Options, FirstCall) -> {match, Captured} | + match | + nomatch | + {error, ErrType} when + Subject :: iodata() | unicode:charlist(), + RE :: mp() | iodata() | unicode:charlist(), + Options :: [Option], + Option :: anchored | global | notbol | noteol | notempty + | notempty_atstart | report_errors + | {offset, non_neg_integer()} | + {match_limit, non_neg_integer()} | + {match_limit_recursion, non_neg_integer()} | + {newline, NLSpec :: nl_spec()} | + bsr_anycrlf | bsr_unicode | {capture, ValueSpec} | + {capture, ValueSpec, Type} | CompileOpt, + Type :: index | list | binary, + ValueSpec :: all | all_but_first | all_names | first | none | ValueList, + ValueList :: [ValueID], + ValueID :: integer() | string() | atom(), + CompileOpt :: compile_option(), + Captured :: [CaptureData] | [[CaptureData]], + CaptureData :: {integer(), integer()} + | ListConversionData + | binary(), + ListConversionData :: string() + | {error, string(), binary()} + | {incomplete, string(), binary()}, + ErrType :: match_limit | match_limit_recursion | {compile, CompileErr}, + CompileErr :: {ErrString :: string(), Position :: non_neg_integer()}, + FirstCall :: boolean(). + +internal_run(_, _, _, _) -> + erlang:nif_error(undef). + -spec inspect(MP,Item) -> {namelist, [ binary() ]} when MP :: mp(), Item :: namelist. @@ -765,17 +801,17 @@ do_grun(FlatSubject,Subject,Unicode,CRLF,RE,{Options0,NeedClean}) -> try postprocess(loopexec(FlatSubject,RE,InitialOffset, byte_size(FlatSubject), - Unicode,CRLF,StrippedOptions), + Unicode,CRLF,StrippedOptions,true), SelectReturn,ConvertReturn,FlatSubject,Unicode) catch throw:ErrTuple -> ErrTuple end. -loopexec(_,_,X,Y,_,_,_) when X > Y -> +loopexec(_,_,X,Y,_,_,_,_) when X > Y -> {match,[]}; -loopexec(Subject,RE,X,Y,Unicode,CRLF,Options) -> - case re:run(Subject,RE,[{offset,X}]++Options) of +loopexec(Subject,RE,X,Y,Unicode,CRLF,Options, First) -> + case re:internal_run(Subject,RE,[{offset,X}]++Options,First) of {error, Err} -> throw({error,Err}); nomatch -> @@ -784,11 +820,11 @@ loopexec(Subject,RE,X,Y,Unicode,CRLF,Options) -> {match,Rest} = case B>0 of true -> - loopexec(Subject,RE,A+B,Y,Unicode,CRLF,Options); + loopexec(Subject,RE,A+B,Y,Unicode,CRLF,Options,false); false -> {match,M} = - case re:run(Subject,RE,[{offset,X},notempty_atstart, - anchored]++Options) of + case re:internal_run(Subject,RE,[{offset,X},notempty_atstart, + anchored]++Options,false) of nomatch -> {match,[]}; {match,Other} -> @@ -801,7 +837,7 @@ loopexec(Subject,RE,X,Y,Unicode,CRLF,Options) -> forward(Subject,A,1,Unicode,CRLF) end, {match,MM} = loopexec(Subject,RE,NewA,Y, - Unicode,CRLF,Options), + Unicode,CRLF,Options,false), case M of [] -> {match,MM}; diff --git a/lib/stdlib/src/stdlib.app.src b/lib/stdlib/src/stdlib.app.src index ecb514e9f3..e6c42b9aac 100644 --- a/lib/stdlib/src/stdlib.app.src +++ b/lib/stdlib/src/stdlib.app.src @@ -108,7 +108,7 @@ dets]}, {applications, [kernel]}, {env, []}, - {runtime_dependencies, ["sasl-3.0","kernel-6.0","erts-10.4","crypto-3.3", + {runtime_dependencies, ["sasl-3.0","kernel-6.0","erts-@OTP-15831:OTP-15836:OTP-15889@","crypto-3.3", "compiler-5.0"]} ]}. diff --git a/lib/stdlib/src/stdlib.appup.src b/lib/stdlib/src/stdlib.appup.src index 7038cc159c..0c270e9dd5 100644 --- a/lib/stdlib/src/stdlib.appup.src +++ b/lib/stdlib/src/stdlib.appup.src @@ -38,7 +38,10 @@ {<<"^3\\.8$">>,[restart_new_emulator]}, {<<"^3\\.8\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, {<<"^3\\.8\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, - {<<"^3\\.8\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}], + {<<"^3\\.8\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.9$">>,[restart_new_emulator]}, + {<<"^3\\.9\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^3\\.9\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}], [{<<"^3\\.5$">>,[restart_new_emulator]}, {<<"^3\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, {<<"^3\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, @@ -50,4 +53,7 @@ {<<"^3\\.8$">>,[restart_new_emulator]}, {<<"^3\\.8\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, {<<"^3\\.8\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, - {<<"^3\\.8\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}. + {<<"^3\\.8\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.9$">>,[restart_new_emulator]}, + {<<"^3\\.9\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^3\\.9\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}. diff --git a/lib/stdlib/test/binary_module_SUITE.erl b/lib/stdlib/test/binary_module_SUITE.erl index 9b2033ec4a..be8ab3b98e 100644 --- a/lib/stdlib/test/binary_module_SUITE.erl +++ b/lib/stdlib/test/binary_module_SUITE.erl @@ -716,22 +716,22 @@ referenced(Config) when is_list(Config) -> badarg = ?MASK_ERROR(binary:referenced_byte_size(apa)), badarg = ?MASK_ERROR(binary:referenced_byte_size({})), badarg = ?MASK_ERROR(binary:referenced_byte_size(1)), - A = <<1,2,3>>, - B = binary:copy(A,1000), - 3 = binary:referenced_byte_size(A), - 3000 = binary:referenced_byte_size(B), - <<_:8,C:2/binary>> = A, - 3 = binary:referenced_byte_size(C), - 2 = binary:referenced_byte_size(binary:copy(C)), - <<_:7,D:2/binary,_:1>> = A, - 2 = binary:referenced_byte_size(binary:copy(D)), - 3 = binary:referenced_byte_size(D), - <<_:8,E:2/binary,_/binary>> = B, - 3000 = binary:referenced_byte_size(E), - 2 = binary:referenced_byte_size(binary:copy(E)), - <<_:7,F:2/binary,_:1,_/binary>> = B, - 2 = binary:referenced_byte_size(binary:copy(F)), - 3000 = binary:referenced_byte_size(F), + A = <<0:(1024 * 8)>>, + B = binary:copy(A, 1000), + 1024 = binary:referenced_byte_size(A), + 1024000 = binary:referenced_byte_size(B), + <<_:8,C:1023/binary>> = A, + 1024 = binary:referenced_byte_size(C), + 1023 = binary:referenced_byte_size(binary:copy(C)), + <<_:7,D:1023/binary,_:1>> = A, + 1023 = binary:referenced_byte_size(binary:copy(D)), + 1024 = binary:referenced_byte_size(D), + <<_:8,E:128/binary,_/binary>> = B, + 1024000 = binary:referenced_byte_size(E), + 128 = binary:referenced_byte_size(binary:copy(E)), + <<_:7,F:128/binary,_:1,_/binary>> = B, + 128 = binary:referenced_byte_size(binary:copy(F)), + 1024000 = binary:referenced_byte_size(F), ok. diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl index dd49288417..09238ae2b4 100644 --- a/lib/stdlib/test/ets_SUITE.erl +++ b/lib/stdlib/test/ets_SUITE.erl @@ -75,7 +75,8 @@ -export([throughput_benchmark/0, throughput_benchmark/1, test_throughput_benchmark/1, - long_throughput_benchmark/1]). + long_throughput_benchmark/1, + lookup_catree_par_vs_seq_init_benchmark/0]). -export([exit_large_table_owner/1, exit_many_large_table_owner/1, exit_many_tables_owner/1, @@ -6728,6 +6729,14 @@ do_work(WorksDoneSoFar, Table, ProbHelpTab, Range, Operations) -> end. prefill_table(T, KeyRange, Num, ObjFun) -> + Parent = self(), + spawn_link(fun() -> + prefill_table_helper(T, KeyRange, Num, ObjFun), + Parent ! done + end), + receive done -> ok end. + +prefill_table_helper(T, KeyRange, Num, ObjFun) -> Seed = rand:uniform(KeyRange), %%io:format("prefill_table: Seed = ~p\n", [Seed]), RState = unique_rand_start(KeyRange, Seed), @@ -6740,11 +6749,77 @@ prefill_table_loop(T, RS0, N, ObjFun) -> ets:insert(T, ObjFun(Key)), prefill_table_loop(T, RS1, N-1, ObjFun). +inserter_proc_starter(T, ToInsert, Parent) -> + receive + start -> ok + end, + inserter_proc(T, ToInsert, [], Parent, false). + +inserter_proc(T, [], Inserted, Parent, _) -> + inserter_proc(T, Inserted, [], Parent, true); +inserter_proc(T, [I | ToInsert], Inserted, Parent, CanStop) -> + Stop = + case CanStop of + true -> + receive + stop -> Parent ! stopped + after 0 -> no_stop + end; + false -> no_stop + end, + case Stop of + no_stop -> + ets:insert(T, I), + inserter_proc(T, ToInsert, [I | Inserted], Parent, CanStop); + _ -> ok + end. + +prefill_table_parallel(T, KeyRange, Num, ObjFun) -> + Parent = self(), + spawn_link(fun() -> + prefill_table_parallel_helper(T, KeyRange, Num, ObjFun), + Parent ! done + end), + receive done -> ok end. + +prefill_table_parallel_helper(T, KeyRange, Num, ObjFun) -> + NrOfSchedulers = erlang:system_info(schedulers), + Seed = rand:uniform(KeyRange), + %%io:format("prefill_table: Seed = ~p\n", [Seed]), + RState = unique_rand_start(KeyRange, Seed), + InsertMap = prefill_insert_map_loop(T, RState, Num, ObjFun, #{}, NrOfSchedulers), + Self = self(), + Pids = [ + begin + InserterFun = + fun() -> + inserter_proc_starter(T, ToInsert, Self) + end, + spawn_link(InserterFun) + end + || ToInsert <- maps:values(InsertMap)], + [Pid ! start || Pid <- Pids], + timer:sleep(1000), + [Pid ! stop || Pid <- Pids], + [receive stopped -> ok end || _Pid <- Pids]. + +prefill_insert_map_loop(_, _, 0, _, InsertMap, _NrOfSchedulers) -> + InsertMap; +prefill_insert_map_loop(T, RS0, N, ObjFun, InsertMap, NrOfSchedulers) -> + {Key, RS1} = unique_rand_next(RS0), + Sched = N rem NrOfSchedulers, + PrevInserts = maps:get(Sched, InsertMap, []), + NewPrevInserts = [ObjFun(Key) | PrevInserts], + NewInsertMap = maps:put(Sched, NewPrevInserts, InsertMap), + prefill_insert_map_loop(T, RS1, N-1, ObjFun, NewInsertMap, NrOfSchedulers). + -record(ets_throughput_bench_config, {benchmark_duration_ms = 3000, recover_time_ms = 1000, thread_counts = not_set, key_ranges = [1000000], + init_functions = [fun prefill_table/4], + nr_of_repeats = 1, scenarios = [ [ @@ -6838,7 +6913,7 @@ prefill_table_loop(T, RS0, N, ObjFun) -> notify_res_fun = fun(_Name, _Throughput) -> ok end, print_result_paths_fun = fun(ResultPath, _LatestResultPath) -> - Comment = + Comment = io_lib:format("<a href=\"file:///~s\">Result visualization</a>",[ResultPath]), {comment, Comment} end @@ -6848,7 +6923,7 @@ stdout_notify_res(ResultPath, LatestResultPath) -> io:format("Result Location: /~s~n", [ResultPath]), io:format("Latest Result Location: ~s~n", [LatestResultPath]). -throughput_benchmark() -> +throughput_benchmark() -> throughput_benchmark( #ets_throughput_bench_config{ print_result_paths_fun = fun stdout_notify_res/2}). @@ -6856,9 +6931,11 @@ throughput_benchmark() -> throughput_benchmark( #ets_throughput_bench_config{ benchmark_duration_ms = BenchmarkDurationMs, - recover_time_ms = RecoverTimeMs, - thread_counts = ThreadCountsOpt, - key_ranges = KeyRanges, + recover_time_ms = RecoverTimeMs, + thread_counts = ThreadCountsOpt, + key_ranges = KeyRanges, + init_functions = InitFuns, + nr_of_repeats = NrOfRepeats, scenarios = Scenarios, table_types = TableTypes, etsmem_fun = ETSMemFun, @@ -6872,21 +6949,21 @@ throughput_benchmark( Start = rand:uniform(KeyRange), Last = lists:foldl( - fun(_, Prev) -> + fun(_, Prev) -> case Prev of '$end_of_table'-> ok; _ -> try ets:next(T, Prev) of Normal -> Normal catch - error:badarg -> + error:badarg -> % sets (not ordered_sets) cannot handle when the argument % to next is not in the set rand:uniform(KeyRange) end end end, - Start, + Start, lists:seq(1, SeqSize)), case Last =:= -1 of true -> io:format("Will never be printed"); @@ -6898,26 +6975,26 @@ throughput_benchmark( Start = rand:uniform(KeyRange), Last = Start + SeqSize, case -1 =:= ets:select_count(T, - ets:fun2ms(fun({X}) when X > Start andalso X =< Last -> true end)) of + ets:fun2ms(fun({X}) when X > Start andalso X =< Last -> true end)) of true -> io:format("Will never be printed"); false -> ok end end, %% Mapping benchmark operation names to their corresponding functions that do them - Operations = + Operations = #{insert => - fun(T,KeyRange) -> + fun(T,KeyRange) -> Num = rand:uniform(KeyRange), ets:insert(T, {Num}) end, delete => - fun(T,KeyRange) -> + fun(T,KeyRange) -> Num = rand:uniform(KeyRange), ets:delete(T, Num) end, lookup => - fun(T,KeyRange) -> + fun(T,KeyRange) -> Num = rand:uniform(KeyRange), ets:lookup(T, Num) end, @@ -6928,8 +7005,8 @@ throughput_benchmark( nextseq1000 => fun(T,KeyRange) -> NextSeqOp(T,KeyRange,1000) end, selectAll => - fun(T,_KeyRange) -> - case -1 =:= ets:select_count(T, ets:fun2ms(fun(_X) -> true end)) of + fun(T,_KeyRange) -> + case -1 =:= ets:select_count(T, ets:fun2ms(fun(_X) -> true end)) of true -> io:format("Will never be printed"); false -> ok end @@ -6951,7 +7028,7 @@ throughput_benchmark( NewCurrent = Current + OpPropability, [{NewCurrent, OpName}| Calculate(Res, NewCurrent)] end, - RenderScenario = + RenderScenario = fun R([], StringSoFar) -> StringSoFar; R([{Fraction, Operation}], StringSoFar) -> @@ -6978,7 +7055,7 @@ throughput_benchmark( false -> ok end end, - DataHolder = + DataHolder = fun DataHolderFun(Data)-> receive {get_data, Pid} -> Pid ! {ets_bench_data, Data}; @@ -6992,18 +7069,21 @@ throughput_benchmark( DataHolderPid ! io_lib:format(Str, List) end, GetData = - fun () -> + fun () -> DataHolderPid ! {get_data, self()}, receive {ets_bench_data, Data} -> Data end end, %% Function that runs a benchmark instance and returns the number %% of operations that were performed RunBenchmark = - fun({NrOfProcs, TableConfig, Scenario, Range, Duration}) -> + fun({NrOfProcs, TableConfig, Scenario, Range, Duration, InitFun}) -> ProbHelpTab = CalculateOpsProbHelpTab(Scenario, 0), Table = ets:new(t, TableConfig), Nobj = Range div 2, - prefill_table(Table, Range, Nobj, fun(K) -> {K} end), + case InitFun of + not_set -> prefill_table(Table, Range, Nobj, fun(K) -> {K} end); + _ -> InitFun(Table, Range, Nobj, fun(K) -> {K} end) + end, Nobj = ets:info(Table, size), SafeFixTableIfRequired(Table, Scenario, true), ParentPid = self(), @@ -7016,12 +7096,14 @@ throughput_benchmark( end, ChildPids = lists:map(fun(_N) ->spawn_link(Worker)end, lists:seq(1, NrOfProcs)), + erlang:garbage_collect(), + timer:sleep(RecoverTimeMs), lists:foreach(fun(Pid) -> Pid ! start end, ChildPids), timer:sleep(Duration), lists:foreach(fun(Pid) -> Pid ! stop end, ChildPids), TotalWorksDone = lists:foldl( - fun(_, Sum) -> - receive + fun(_, Sum) -> + receive Count -> Sum + Count end end, 0, ChildPids), @@ -7032,27 +7114,32 @@ throughput_benchmark( RunBenchmarkInSepProcess = fun(ParameterTuple) -> P = self(), - spawn_link(fun()-> P ! {bench_result, RunBenchmark(ParameterTuple)} end), - Result = receive {bench_result, Res} -> Res end, - timer:sleep(RecoverTimeMs), - Result + Results = + [begin + spawn_link(fun()-> P ! {bench_result, RunBenchmark(ParameterTuple)} end), + receive {bench_result, Res} -> Res end + end || _ <- lists:seq(1, NrOfRepeats)], + lists:sum(Results) / NrOfRepeats end, RunBenchmarkAndReport = fun(ThreadCount, TableType, Scenario, KeyRange, - Duration) -> + Duration, + InitFunName, + InitFun) -> Result = RunBenchmarkInSepProcess({ThreadCount, TableType, Scenario, KeyRange, - Duration}), + Duration, + InitFun}), Throughput = Result/(Duration/1000.0), PrintData("; ~f",[Throughput]), - Name = io_lib:format("Scenario: ~w, Key Range Size: ~w, " + Name = io_lib:format("Scenario: ~s, ~w, Key Range Size: ~w, " "# of Processes: ~w, Table Type: ~w", - [Scenario, KeyRange, ThreadCount, TableType]), + [InitFunName, Scenario, KeyRange, ThreadCount, TableType]), NotifyResFun(Name, Throughput) end, ThreadCounts = @@ -7087,17 +7174,29 @@ throughput_benchmark( PrintData("$~n",[]), lists:foreach( fun(TableType) -> - PrintData("~w ",[TableType]), lists:foreach( - fun(ThreadCount) -> - RunBenchmarkAndReport(ThreadCount, - TableType, - Scenario, - KeyRange, - BenchmarkDurationMs) + fun(InitFunArg) -> + {InitFunName, InitFun} = + case InitFunArg of + {FunName, Fun} -> {FunName, Fun}; + Fun -> {"", Fun} + end, + PrintData("~s,~w ",[InitFunName,TableType]), + lists:foreach( + fun(ThreadCount) -> + RunBenchmarkAndReport(ThreadCount, + TableType, + Scenario, + KeyRange, + BenchmarkDurationMs, + InitFunName, + InitFun) + end, + ThreadCounts), + PrintData("$~n",[]) end, - ThreadCounts), - PrintData("$~n",[]) + InitFuns) + end, TableTypes) end, @@ -7121,7 +7220,7 @@ throughput_benchmark( test_throughput_benchmark(Config) when is_list(Config) -> throughput_benchmark( #ets_throughput_bench_config{ - benchmark_duration_ms = 100, + benchmark_duration_ms = 100, recover_time_ms = 0, thread_counts = [1, erlang:system_info(schedulers)], key_ranges = [50000], @@ -7136,7 +7235,7 @@ long_throughput_benchmark(Config) when is_list(Config) -> recover_time_ms = 1000, thread_counts = [1, N div 2, N], key_ranges = [1000000], - scenarios = + scenarios = [ [ {0.5, insert}, @@ -7171,15 +7270,15 @@ long_throughput_benchmark(Config) when is_list(Config) -> {0.01, partial_select1000} ] ], - table_types = + table_types = [ [ordered_set, public, {write_concurrency, true}, {read_concurrency, true}], [set, public, {write_concurrency, true}, {read_concurrency, true}] ], etsmem_fun = fun etsmem/0, verify_etsmem_fun = fun verify_etsmem/1, - notify_res_fun = - fun(Name, Throughput) -> + notify_res_fun = + fun(Name, Throughput) -> SummaryTable = proplists:get_value(ets_benchmark_result_summary_tab, Config), AddToSummaryCounter = @@ -7209,13 +7308,47 @@ long_throughput_benchmark(Config) when is_list(Config) -> total_throughput_ordered_set) end, ct_event:notify( - #event{name = benchmark_data, + #event{name = benchmark_data, data = [{suite,"ets_bench"}, {name, Name}, {value,Throughput}]}) end }). +%% This function compares the lookup operation's performance for +%% ordered_set ETS tables with and without write_concurrency enabled +%% when the data structures have been populated in parallel and +%% sequentially. +%% +%% The main purpose of this function is to check that the +%% implementation of ordered_set with write_concurrency (CA tree) +%% adapts its structure to contention even when only lookup operations +%% are used. +lookup_catree_par_vs_seq_init_benchmark() -> + N = erlang:system_info(schedulers), + throughput_benchmark( + #ets_throughput_bench_config{ + benchmark_duration_ms = 600000, + recover_time_ms = 1000, + thread_counts = [1, N div 2, N], + key_ranges = [1000000], + init_functions = [{"seq_init", fun prefill_table/4}, + {"par_init", fun prefill_table_parallel/4}], + nr_of_repeats = 1, + scenarios = + [ + [ + {1.0, lookup} + ] + ], + table_types = + [ + [ordered_set, public, {write_concurrency, true}], + [ordered_set, public] + ], + print_result_paths_fun = fun stdout_notify_res/2 + }). + add_lists(L1,L2) -> add_lists(L1,L2,[]). add_lists([],[],Acc) -> diff --git a/lib/stdlib/test/io_SUITE.erl b/lib/stdlib/test/io_SUITE.erl index 9b6d8d7401..4eb5b1772c 100644 --- a/lib/stdlib/test/io_SUITE.erl +++ b/lib/stdlib/test/io_SUITE.erl @@ -32,7 +32,7 @@ io_with_huge_message_queue/1, format_string/1, maps/1, coverage/1, otp_14178_unicode_atoms/1, otp_14175/1, otp_14285/1, limit_term/1, otp_14983/1, otp_15103/1, otp_15076/1, - otp_15159/1, otp_15639/1, otp_15705/1]). + otp_15159/1, otp_15639/1, otp_15705/1, otp_15847/1, otp_15875/1]). -export([pretty/2, trf/3]). @@ -65,7 +65,7 @@ all() -> io_lib_width_too_small, io_with_huge_message_queue, format_string, maps, coverage, otp_14178_unicode_atoms, otp_14175, otp_14285, limit_term, otp_14983, otp_15103, otp_15076, otp_15159, - otp_15639, otp_15705]. + otp_15639, otp_15705, otp_15847, otp_15875]. %% Error cases for output. error_1(Config) when is_list(Config) -> @@ -2708,3 +2708,13 @@ otp_15705(_Config) -> "|кирилли́чес|" = trf("|~10ts|", [U], -1), ok. + +otp_15847(_Config) -> + T = {someRecord,<<"1234567890">>,some,more}, + "{someRecord,<<...>>,...}" = + pretty(T, [{chars_limit,20}, {encoding,latin1}]), + ok. + +otp_15875(_Config) -> + S = io_lib:format("~tp", [[{0, [<<"00">>]}]], [{chars_limit, 18}]), + "[{0,[<<48,...>>]}]" = lists:flatten(S). diff --git a/lib/stdlib/test/lists_SUITE.erl b/lib/stdlib/test/lists_SUITE.erl index 5dab6f6697..c3c54710eb 100644 --- a/lib/stdlib/test/lists_SUITE.erl +++ b/lib/stdlib/test/lists_SUITE.erl @@ -2600,6 +2600,15 @@ subtract(Config) when is_list(Config) -> [1,2,3,4,5,6,7,8,9,9999,10000,20,21,22] = sub(lists:seq(1, 10000)++[20,21,22], lists:seq(10, 9998)), + %% ERL-986; an integer overflow relating to term comparison + %% caused subtraction to be inconsistent. + Ids = [2985095936,47540628,135460048,1266126295,240535295, + 115724671,161800351,4187206564,4178142725,234897063, + 14773162,6662515191,133150693,378034895,1874402262, + 3507611978,22850922,415521280,253360400,71683243], + + [] = id(Ids) -- id(Ids), + %% Floats/integers. [42.0,42.0] = sub([42.0,42,42.0], [42,42,42]), [1,2,3,4,43.0] = sub([1,2,3,4,5,42.0,43.0], [42.0,5]), @@ -2627,6 +2636,8 @@ subtract(Config) when is_list(Config) -> ok. +id(I) -> I. + sub_non_matching(A, B) -> A = sub(A, B). diff --git a/lib/stdlib/test/re_SUITE.erl b/lib/stdlib/test/re_SUITE.erl index c9ef9da990..06d8fe9255 100644 --- a/lib/stdlib/test/re_SUITE.erl +++ b/lib/stdlib/test/re_SUITE.erl @@ -28,7 +28,8 @@ pcre_compile_workspace_overflow/1,re_infinite_loop/1, re_backwards_accented/1,opt_dupnames/1,opt_all_names/1,inspect/1, opt_no_start_optimize/1,opt_never_utf/1,opt_ucp/1, - match_limit/1,sub_binaries/1,copt/1]). + match_limit/1,sub_binaries/1,copt/1,global_unicode_validation/1, + yield_on_subject_validation/1]). -include_lib("common_test/include/ct.hrl"). -include_lib("kernel/include/file.hrl"). @@ -45,7 +46,8 @@ all() -> pcre_compile_workspace_overflow, re_infinite_loop, re_backwards_accented, opt_dupnames, opt_all_names, inspect, opt_no_start_optimize,opt_never_utf,opt_ucp, - match_limit, sub_binaries, re_version]. + match_limit, sub_binaries, re_version, global_unicode_validation, + yield_on_subject_validation]. groups() -> []. @@ -200,7 +202,58 @@ re_version(_Config) -> {match,[Version]} = re:run(Version,"^[0-9]\\.[0-9]{2} 20[0-9]{2}-[0-9]{2}-[0-9]{2}",[{capture,all,binary}]), ok. +global_unicode_validation(Config) when is_list(Config) -> + %% Test that unicode validation of the subject is not done + %% for every match found... + Bin = binary:copy(<<"abc\n">>,100000), + {TimeAscii, _} = take_time(fun () -> + re:run(Bin, <<"b">>, [global]) + end), + {TimeUnicode, _} = take_time(fun () -> + re:run(Bin, <<"b">>, [unicode,global]) + end), + if TimeAscii == 0; TimeUnicode == 0 -> + {comment, "Not good enough resolution to compare results"}; + true -> + %% The time the operations takes should be in the + %% same order of magnitude. If validation of the + %% whole subject occurs for every match, the unicode + %% variant will take way longer time... + true = TimeUnicode div TimeAscii < 10 + end. + +take_time(Fun) -> + Start = erlang:monotonic_time(nanosecond), + Res = Fun(), + End = erlang:monotonic_time(nanosecond), + {End-Start, Res}. + +yield_on_subject_validation(Config) when is_list(Config) -> + Go = make_ref(), + Bin = binary:copy(<<"abc\n">>,100000), + {P, M} = spawn_opt(fun () -> + receive Go -> ok end, + {match,[{1,1}]} = re:run(Bin, <<"b">>, [unicode]) + end, + [link, monitor]), + 1 = erlang:trace(P, true, [running]), + P ! Go, + N = count_re_run_trap_out(P, M), + true = N >= 5, + ok. +count_re_run_trap_out(P, M) when is_reference(M) -> + receive {'DOWN',M,process,P,normal} -> ok end, + TD = erlang:trace_delivered(P), + receive {trace_delivered, P, TD} -> ok end, + count_re_run_trap_out(P, 0); +count_re_run_trap_out(P, N) when is_integer(N) -> + receive + {trace,P,out,{erlang,re_run_trap,3}} -> + count_re_run_trap_out(P, N+1) + after 0 -> + N + end. %% Test compile options given directly to run. combined_options(Config) when is_list(Config) -> diff --git a/lib/stdlib/test/re_SUITE_data/testoutput1 b/lib/stdlib/test/re_SUITE_data/testoutput1 index eff8ecc948..e6147e60b9 100644 --- a/lib/stdlib/test/re_SUITE_data/testoutput1 +++ b/lib/stdlib/test/re_SUITE_data/testoutput1 @@ -9446,4 +9446,28 @@ No match >XXX< 0: X +/ (?<word> \w+ )* \. /xi + pokus. + 0: pokus. + 1: pokus + +/(?(DEFINE) (?<word> \w+ ) ) (?&word)* \./xi + pokus. + 0: pokus. + +/(?(DEFINE) (?<word> \w+ ) ) ( (?&word)* ) \./xi + pokus. + 0: pokus. + 1: <unset> + 2: pokus + +/(?&word)* (?(DEFINE) (?<word> \w+ ) ) \./xi + pokus. + 0: pokus. + +/(?&word)* \. (?<word> \w+ )/xi + pokus.hokus + 0: pokus.hokus + 1: hokus + /-- End of testinput1 --/ diff --git a/lib/stdlib/test/re_SUITE_data/testoutput2 b/lib/stdlib/test/re_SUITE_data/testoutput2 index 61ed8d9d4e..4ccda27201 100644 --- a/lib/stdlib/test/re_SUITE_data/testoutput2 +++ b/lib/stdlib/test/re_SUITE_data/testoutput2 @@ -14721,4 +14721,8 @@ No need char 0: ab 1: a +/(?(?=^))b/ + abc + 0: b + /-- End of testinput2 --/ diff --git a/lib/stdlib/test/re_SUITE_data/testoutput4 b/lib/stdlib/test/re_SUITE_data/testoutput4 index d43c12392d..69e812cd35 100644 --- a/lib/stdlib/test/re_SUITE_data/testoutput4 +++ b/lib/stdlib/test/re_SUITE_data/testoutput4 @@ -1277,4 +1277,8 @@ No match \\C(\\W?ſ)'?{{ No match +/[^\x{100}-\x{ffff}]*[\x80-\xff]/8 + \x{99}\x{99}\x{99} + 0: \x{99}\x{99}\x{99} + /-- End of testinput4 --/ diff --git a/lib/stdlib/test/re_testoutput1_replacement_test.erl b/lib/stdlib/test/re_testoutput1_replacement_test.erl index f14df547ef..bb43047757 100644 --- a/lib/stdlib/test/re_testoutput1_replacement_test.erl +++ b/lib/stdlib/test/re_testoutput1_replacement_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2017. All Rights Reserved. +%% Copyright Ericsson AB 2008-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. @@ -20014,4 +20014,31 @@ run56() -> <<" MumdPEeFred:099">> = iolist_to_binary(re:replace(" Fred:099","(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[,;:])(?=.{8,16})(?!.*[\\s])","\\1&M&umdPE&e",[global])), <<" ugxGKrBXEcHyG">> = iolist_to_binary(re:replace(" X","(?=.*X)X$","ugxGKrB&EcHyG",[])), <<" ugxGKrBXEcHyG">> = iolist_to_binary(re:replace(" X","(?=.*X)X$","ugxGKrB&EcHyG",[global])), + <<">MHFvXX<">> = iolist_to_binary(re:replace(">XXX<","X+(?#comment)?","MHFv",[])), + <<">MHFvMHFvMHFv<">> = iolist_to_binary(re:replace(">XXX<","X+(?#comment)?","MHFv",[global])), + <<"lTpokusldxfHXOpokuswsrRorpokus.">> = iolist_to_binary(re:replace("pokus."," (?<word> \\w+ )* \\. ","lT\\1ldxfHXO\\1wsrRor&",[extended, + caseless])), + <<"lTpokusldxfHXOpokuswsrRorpokus.">> = iolist_to_binary(re:replace("pokus."," (?<word> \\w+ )* \\. ","lT\\1ldxfHXO\\1wsrRor&",[extended, + caseless, + global])), + <<"Oeapokus.xo">> = iolist_to_binary(re:replace("pokus.","(?(DEFINE) (?<word> \\w+ ) ) (?&word)* \\.","Oea&xo",[extended, + caseless])), + <<"Oeapokus.xo">> = iolist_to_binary(re:replace("pokus.","(?(DEFINE) (?<word> \\w+ ) ) (?&word)* \\.","Oea&xo",[extended, + caseless, + global])), + <<"Wpokus.pity">> = iolist_to_binary(re:replace("pokus.","(?(DEFINE) (?<word> \\w+ ) ) ( (?&word)* ) \\.","W&pity",[extended, + caseless])), + <<"Wpokus.pity">> = iolist_to_binary(re:replace("pokus.","(?(DEFINE) (?<word> \\w+ ) ) ( (?&word)* ) \\.","W&pity",[extended, + caseless, + global])), + <<"iujmNtBvmcyi">> = iolist_to_binary(re:replace("pokus.","(?&word)* (?(DEFINE) (?<word> \\w+ ) ) \\.","iuj\\1m\\1NtBvmcyi\\1",[extended, + caseless])), + <<"iujmNtBvmcyi">> = iolist_to_binary(re:replace("pokus.","(?&word)* (?(DEFINE) (?<word> \\w+ ) ) \\.","iuj\\1m\\1NtBvmcyi\\1",[extended, + caseless, + global])), + <<"Ipokus.hokusbQpokus.hokusB">> = iolist_to_binary(re:replace("pokus.hokus","(?&word)* \\. (?<word> \\w+ )","I&bQ&B",[extended, + caseless])), + <<"Ipokus.hokusbQpokus.hokusB">> = iolist_to_binary(re:replace("pokus.hokus","(?&word)* \\. (?<word> \\w+ )","I&bQ&B",[extended, + caseless, + global])), ok. diff --git a/lib/stdlib/test/re_testoutput1_split_test.erl b/lib/stdlib/test/re_testoutput1_split_test.erl index 8218cd9bd2..fcffa89e3f 100644 --- a/lib/stdlib/test/re_testoutput1_split_test.erl +++ b/lib/stdlib/test/re_testoutput1_split_test.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2017. All Rights Reserved. +%% Copyright Ericsson AB 2008-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. @@ -84,1360 +84,1360 @@ run() -> run56(), ok. run0() -> - <<"">> = iolist_to_binary(join(re:split("the quick brown fox","the quick brown fox",[trim]))), + <<"">> = iolist_to_binary(join(re:split("the quick brown fox","the quick brown fox",[trim]))), <<":">> = iolist_to_binary(join(re:split("the quick brown fox","the quick brown fox",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("the quick brown fox","the quick brown fox",[]))), - <<"The quick brown FOX">> = iolist_to_binary(join(re:split("The quick brown FOX","the quick brown fox",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("the quick brown fox","the quick brown fox",[]))), + <<"The quick brown FOX">> = iolist_to_binary(join(re:split("The quick brown FOX","the quick brown fox",[trim]))), <<"The quick brown FOX">> = iolist_to_binary(join(re:split("The quick brown FOX","the quick brown fox",[{parts, - 2}]))), - <<"The quick brown FOX">> = iolist_to_binary(join(re:split("The quick brown FOX","the quick brown fox",[]))), - <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about the quick brown fox?","the quick brown fox",[trim]))), + 2}]))), + <<"The quick brown FOX">> = iolist_to_binary(join(re:split("The quick brown FOX","the quick brown fox",[]))), + <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about the quick brown fox?","the quick brown fox",[trim]))), <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about the quick brown fox?","the quick brown fox",[{parts, - 2}]))), - <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about the quick brown fox?","the quick brown fox",[]))), - <<"What do you know about THE QUICK BROWN FOX?">> = iolist_to_binary(join(re:split("What do you know about THE QUICK BROWN FOX?","the quick brown fox",[trim]))), + 2}]))), + <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about the quick brown fox?","the quick brown fox",[]))), + <<"What do you know about THE QUICK BROWN FOX?">> = iolist_to_binary(join(re:split("What do you know about THE QUICK BROWN FOX?","the quick brown fox",[trim]))), <<"What do you know about THE QUICK BROWN FOX?">> = iolist_to_binary(join(re:split("What do you know about THE QUICK BROWN FOX?","the quick brown fox",[{parts, - 2}]))), - <<"What do you know about THE QUICK BROWN FOX?">> = iolist_to_binary(join(re:split("What do you know about THE QUICK BROWN FOX?","the quick brown fox",[]))), + 2}]))), + <<"What do you know about THE QUICK BROWN FOX?">> = iolist_to_binary(join(re:split("What do you know about THE QUICK BROWN FOX?","the quick brown fox",[]))), <<"">> = iolist_to_binary(join(re:split("the quick brown fox","The quick brown fox",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("the quick brown fox","The quick brown fox",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("the quick brown fox","The quick brown fox",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("the quick brown fox","The quick brown fox",[caseless]))), <<"">> = iolist_to_binary(join(re:split("The quick brown FOX","The quick brown fox",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("The quick brown FOX","The quick brown fox",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("The quick brown FOX","The quick brown fox",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("The quick brown FOX","The quick brown fox",[caseless]))), <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about the quick brown fox?","The quick brown fox",[caseless, - trim]))), + trim]))), <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about the quick brown fox?","The quick brown fox",[caseless, {parts, - 2}]))), - <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about the quick brown fox?","The quick brown fox",[caseless]))), + 2}]))), + <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about the quick brown fox?","The quick brown fox",[caseless]))), <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about THE QUICK BROWN FOX?","The quick brown fox",[caseless, - trim]))), + trim]))), <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about THE QUICK BROWN FOX?","The quick brown fox",[caseless, {parts, - 2}]))), - <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about THE QUICK BROWN FOX?","The quick brown fox",[caseless]))), + 2}]))), + <<"What do you know about :?">> = iolist_to_binary(join(re:split("What do you know about THE QUICK BROWN FOX?","The quick brown fox",[caseless]))), <<"">> = iolist_to_binary(join(re:split("abcd -
9;$\\?caxyz","abcd\\t\\n\\r\\f\\a\\e\\071\\x3b\\$\\\\\\?caxyz",[trim]))), +
9;$\\?caxyz","abcd\\t\\n\\r\\f\\a\\e\\071\\x3b\\$\\\\\\?caxyz",[trim]))), <<":">> = iolist_to_binary(join(re:split("abcd
9;$\\?caxyz","abcd\\t\\n\\r\\f\\a\\e\\071\\x3b\\$\\\\\\?caxyz",[{parts, - 2}]))), + 2}]))), <<":">> = iolist_to_binary(join(re:split("abcd -
9;$\\?caxyz","abcd\\t\\n\\r\\f\\a\\e\\071\\x3b\\$\\\\\\?caxyz",[]))), - <<"">> = iolist_to_binary(join(re:split("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), +
9;$\\?caxyz","abcd\\t\\n\\r\\f\\a\\e\\071\\x3b\\$\\\\\\?caxyz",[]))), + <<"">> = iolist_to_binary(join(re:split("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("abxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaabcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("abxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("abxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aabxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aabxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aabxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aabxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aabxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("abcxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaabxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("abcxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("abcxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abcxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aabcxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abcxyzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aabcxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aabcxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aabcxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aabcxyzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypABzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypABzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypABzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypABzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypABBzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypABzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypABBzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypABBzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypABBzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<">>>">> = iolist_to_binary(join(re:split(">>>aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypABBzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<">>>">> = iolist_to_binary(join(re:split(">>>aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<">>>:">> = iolist_to_binary(join(re:split(">>>aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<">>>:">> = iolist_to_binary(join(re:split(">>>aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<">">> = iolist_to_binary(join(re:split(">aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<">>>:">> = iolist_to_binary(join(re:split(">>>aaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<">">> = iolist_to_binary(join(re:split(">aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<">:">> = iolist_to_binary(join(re:split(">aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<">:">> = iolist_to_binary(join(re:split(">aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<">>>>">> = iolist_to_binary(join(re:split(">>>>abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<">:">> = iolist_to_binary(join(re:split(">aaaabxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<">>>>">> = iolist_to_binary(join(re:split(">>>>abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<">>>>:">> = iolist_to_binary(join(re:split(">>>>abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<">>>>:">> = iolist_to_binary(join(re:split(">>>>abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<">>>>:">> = iolist_to_binary(join(re:split(">>>>abcxyzpqrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"abxyzpqrrabbxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"abxyzpqrrabbxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<"abxyzpqrrabbxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<"abxyzpqrrabbxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"abxyzpqrrrrabbxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<"abxyzpqrrabbxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"abxyzpqrrrrabbxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<"abxyzpqrrrrabbxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<"abxyzpqrrrrabbxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"abxyzpqrrrabxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrrabxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<"abxyzpqrrrrabbxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrrrabbxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"abxyzpqrrrabxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrrabxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<"abxyzpqrrrabxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrrabxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<"abxyzpqrrrabxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrrabxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<"abxyzpqrrrabxyyyypqAzz">> = iolist_to_binary(join(re:split("abxyzpqrrrabxyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<"aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<"aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"aaaabcxyzzzzpqrrrabbbxyyypqAzz">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<"aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyyyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"aaaabcxyzzzzpqrrrabbbxyyypqAzz">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<"aaaabcxyzzzzpqrrrabbbxyyypqAzz">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<"aaaabcxyzzzzpqrrrabbbxyyypqAzz">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<"aaabcxyzpqrrrabbxyyyypqqqqqqqAzz">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), + 2}]))), + <<"aaaabcxyzzzzpqrrrabbbxyyypqAzz">> = iolist_to_binary(join(re:split("aaaabcxyzzzzpqrrrabbbxyyypqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<"aaabcxyzpqrrrabbxyyyypqqqqqqqAzz">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[trim]))), <<"aaabcxyzpqrrrabbxyyyypqqqqqqqAzz">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[{parts, - 2}]))), - <<"aaabcxyzpqrrrabbxyyyypqqqqqqqAzz">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), - <<":abc">> = iolist_to_binary(join(re:split("abczz","^(abc){1,2}zz",[trim]))), + 2}]))), + <<"aaabcxyzpqrrrabbxyyyypqqqqqqqAzz">> = iolist_to_binary(join(re:split("aaabcxyzpqrrrabbxyyyypqqqqqqqAzz","a*abc?xyz+pqr{3}ab{2,}xy{4,5}pq{0,6}AB{0,}zz",[]))), + <<":abc">> = iolist_to_binary(join(re:split("abczz","^(abc){1,2}zz",[trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abczz","^(abc){1,2}zz",[{parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abczz","^(abc){1,2}zz",[]))), - <<":abc">> = iolist_to_binary(join(re:split("abcabczz","^(abc){1,2}zz",[trim]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abczz","^(abc){1,2}zz",[]))), + <<":abc">> = iolist_to_binary(join(re:split("abcabczz","^(abc){1,2}zz",[trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abcabczz","^(abc){1,2}zz",[{parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abcabczz","^(abc){1,2}zz",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(abc){1,2}zz",[trim]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abcabczz","^(abc){1,2}zz",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(abc){1,2}zz",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(abc){1,2}zz",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(abc){1,2}zz",[]))), - <<"zz">> = iolist_to_binary(join(re:split("zz","^(abc){1,2}zz",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(abc){1,2}zz",[]))), + <<"zz">> = iolist_to_binary(join(re:split("zz","^(abc){1,2}zz",[trim]))), <<"zz">> = iolist_to_binary(join(re:split("zz","^(abc){1,2}zz",[{parts, - 2}]))), - <<"zz">> = iolist_to_binary(join(re:split("zz","^(abc){1,2}zz",[]))), - <<"abcabcabczz">> = iolist_to_binary(join(re:split("abcabcabczz","^(abc){1,2}zz",[trim]))), + 2}]))), + <<"zz">> = iolist_to_binary(join(re:split("zz","^(abc){1,2}zz",[]))), + <<"abcabcabczz">> = iolist_to_binary(join(re:split("abcabcabczz","^(abc){1,2}zz",[trim]))), <<"abcabcabczz">> = iolist_to_binary(join(re:split("abcabcabczz","^(abc){1,2}zz",[{parts, - 2}]))), - <<"abcabcabczz">> = iolist_to_binary(join(re:split("abcabcabczz","^(abc){1,2}zz",[]))), - <<">>abczz">> = iolist_to_binary(join(re:split(">>abczz","^(abc){1,2}zz",[trim]))), + 2}]))), + <<"abcabcabczz">> = iolist_to_binary(join(re:split("abcabcabczz","^(abc){1,2}zz",[]))), + <<">>abczz">> = iolist_to_binary(join(re:split(">>abczz","^(abc){1,2}zz",[trim]))), <<">>abczz">> = iolist_to_binary(join(re:split(">>abczz","^(abc){1,2}zz",[{parts, - 2}]))), - <<">>abczz">> = iolist_to_binary(join(re:split(">>abczz","^(abc){1,2}zz",[]))), - <<":b">> = iolist_to_binary(join(re:split("bc","^(b+?|a){1,2}?c",[trim]))), + 2}]))), + <<">>abczz">> = iolist_to_binary(join(re:split(">>abczz","^(abc){1,2}zz",[]))), + <<":b">> = iolist_to_binary(join(re:split("bc","^(b+?|a){1,2}?c",[trim]))), <<":b:">> = iolist_to_binary(join(re:split("bc","^(b+?|a){1,2}?c",[{parts, - 2}]))), - <<":b:">> = iolist_to_binary(join(re:split("bc","^(b+?|a){1,2}?c",[]))), - <<":b">> = iolist_to_binary(join(re:split("bbc","^(b+?|a){1,2}?c",[trim]))), + 2}]))), + <<":b:">> = iolist_to_binary(join(re:split("bc","^(b+?|a){1,2}?c",[]))), + <<":b">> = iolist_to_binary(join(re:split("bbc","^(b+?|a){1,2}?c",[trim]))), <<":b:">> = iolist_to_binary(join(re:split("bbc","^(b+?|a){1,2}?c",[{parts, - 2}]))), - <<":b:">> = iolist_to_binary(join(re:split("bbc","^(b+?|a){1,2}?c",[]))), - <<":bb">> = iolist_to_binary(join(re:split("bbbc","^(b+?|a){1,2}?c",[trim]))), + 2}]))), + <<":b:">> = iolist_to_binary(join(re:split("bbc","^(b+?|a){1,2}?c",[]))), + <<":bb">> = iolist_to_binary(join(re:split("bbbc","^(b+?|a){1,2}?c",[trim]))), <<":bb:">> = iolist_to_binary(join(re:split("bbbc","^(b+?|a){1,2}?c",[{parts, - 2}]))), - <<":bb:">> = iolist_to_binary(join(re:split("bbbc","^(b+?|a){1,2}?c",[]))), - <<":a">> = iolist_to_binary(join(re:split("bac","^(b+?|a){1,2}?c",[trim]))), + 2}]))), + <<":bb:">> = iolist_to_binary(join(re:split("bbbc","^(b+?|a){1,2}?c",[]))), + <<":a">> = iolist_to_binary(join(re:split("bac","^(b+?|a){1,2}?c",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+?|a){1,2}?c",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+?|a){1,2}?c",[]))), - <<":a">> = iolist_to_binary(join(re:split("bbac","^(b+?|a){1,2}?c",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+?|a){1,2}?c",[]))), + <<":a">> = iolist_to_binary(join(re:split("bbac","^(b+?|a){1,2}?c",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+?|a){1,2}?c",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+?|a){1,2}?c",[]))), - <<":a">> = iolist_to_binary(join(re:split("aac","^(b+?|a){1,2}?c",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+?|a){1,2}?c",[]))), + <<":a">> = iolist_to_binary(join(re:split("aac","^(b+?|a){1,2}?c",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aac","^(b+?|a){1,2}?c",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aac","^(b+?|a){1,2}?c",[]))), - <<":bbbbbbbbbbb">> = iolist_to_binary(join(re:split("abbbbbbbbbbbc","^(b+?|a){1,2}?c",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aac","^(b+?|a){1,2}?c",[]))), + <<":bbbbbbbbbbb">> = iolist_to_binary(join(re:split("abbbbbbbbbbbc","^(b+?|a){1,2}?c",[trim]))), <<":bbbbbbbbbbb:">> = iolist_to_binary(join(re:split("abbbbbbbbbbbc","^(b+?|a){1,2}?c",[{parts, - 2}]))), - <<":bbbbbbbbbbb:">> = iolist_to_binary(join(re:split("abbbbbbbbbbbc","^(b+?|a){1,2}?c",[]))), - <<":a">> = iolist_to_binary(join(re:split("bbbbbbbbbbbac","^(b+?|a){1,2}?c",[trim]))), + 2}]))), + <<":bbbbbbbbbbb:">> = iolist_to_binary(join(re:split("abbbbbbbbbbbc","^(b+?|a){1,2}?c",[]))), + <<":a">> = iolist_to_binary(join(re:split("bbbbbbbbbbbac","^(b+?|a){1,2}?c",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("bbbbbbbbbbbac","^(b+?|a){1,2}?c",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("bbbbbbbbbbbac","^(b+?|a){1,2}?c",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b+?|a){1,2}?c",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("bbbbbbbbbbbac","^(b+?|a){1,2}?c",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b+?|a){1,2}?c",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b+?|a){1,2}?c",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b+?|a){1,2}?c",[]))), - <<"aaac">> = iolist_to_binary(join(re:split("aaac","^(b+?|a){1,2}?c",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b+?|a){1,2}?c",[]))), + <<"aaac">> = iolist_to_binary(join(re:split("aaac","^(b+?|a){1,2}?c",[trim]))), <<"aaac">> = iolist_to_binary(join(re:split("aaac","^(b+?|a){1,2}?c",[{parts, - 2}]))), - <<"aaac">> = iolist_to_binary(join(re:split("aaac","^(b+?|a){1,2}?c",[]))), - <<"abbbbbbbbbbbac">> = iolist_to_binary(join(re:split("abbbbbbbbbbbac","^(b+?|a){1,2}?c",[trim]))), + 2}]))), + <<"aaac">> = iolist_to_binary(join(re:split("aaac","^(b+?|a){1,2}?c",[]))), + <<"abbbbbbbbbbbac">> = iolist_to_binary(join(re:split("abbbbbbbbbbbac","^(b+?|a){1,2}?c",[trim]))), <<"abbbbbbbbbbbac">> = iolist_to_binary(join(re:split("abbbbbbbbbbbac","^(b+?|a){1,2}?c",[{parts, - 2}]))), - <<"abbbbbbbbbbbac">> = iolist_to_binary(join(re:split("abbbbbbbbbbbac","^(b+?|a){1,2}?c",[]))), - <<":b">> = iolist_to_binary(join(re:split("bc","^(b+|a){1,2}c",[trim]))), + 2}]))), + <<"abbbbbbbbbbbac">> = iolist_to_binary(join(re:split("abbbbbbbbbbbac","^(b+?|a){1,2}?c",[]))), + <<":b">> = iolist_to_binary(join(re:split("bc","^(b+|a){1,2}c",[trim]))), <<":b:">> = iolist_to_binary(join(re:split("bc","^(b+|a){1,2}c",[{parts, - 2}]))), - <<":b:">> = iolist_to_binary(join(re:split("bc","^(b+|a){1,2}c",[]))), - <<":bb">> = iolist_to_binary(join(re:split("bbc","^(b+|a){1,2}c",[trim]))), + 2}]))), + <<":b:">> = iolist_to_binary(join(re:split("bc","^(b+|a){1,2}c",[]))), + <<":bb">> = iolist_to_binary(join(re:split("bbc","^(b+|a){1,2}c",[trim]))), <<":bb:">> = iolist_to_binary(join(re:split("bbc","^(b+|a){1,2}c",[{parts, - 2}]))), - <<":bb:">> = iolist_to_binary(join(re:split("bbc","^(b+|a){1,2}c",[]))), - <<":bbb">> = iolist_to_binary(join(re:split("bbbc","^(b+|a){1,2}c",[trim]))), + 2}]))), + <<":bb:">> = iolist_to_binary(join(re:split("bbc","^(b+|a){1,2}c",[]))), + <<":bbb">> = iolist_to_binary(join(re:split("bbbc","^(b+|a){1,2}c",[trim]))), <<":bbb:">> = iolist_to_binary(join(re:split("bbbc","^(b+|a){1,2}c",[{parts, - 2}]))), - <<":bbb:">> = iolist_to_binary(join(re:split("bbbc","^(b+|a){1,2}c",[]))), - <<":a">> = iolist_to_binary(join(re:split("bac","^(b+|a){1,2}c",[trim]))), + 2}]))), + <<":bbb:">> = iolist_to_binary(join(re:split("bbbc","^(b+|a){1,2}c",[]))), + <<":a">> = iolist_to_binary(join(re:split("bac","^(b+|a){1,2}c",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+|a){1,2}c",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+|a){1,2}c",[]))), - <<":a">> = iolist_to_binary(join(re:split("bbac","^(b+|a){1,2}c",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+|a){1,2}c",[]))), + <<":a">> = iolist_to_binary(join(re:split("bbac","^(b+|a){1,2}c",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+|a){1,2}c",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+|a){1,2}c",[]))), - <<":a">> = iolist_to_binary(join(re:split("aac","^(b+|a){1,2}c",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+|a){1,2}c",[]))), + <<":a">> = iolist_to_binary(join(re:split("aac","^(b+|a){1,2}c",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aac","^(b+|a){1,2}c",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aac","^(b+|a){1,2}c",[]))), - <<":bbbbbbbbbbb">> = iolist_to_binary(join(re:split("abbbbbbbbbbbc","^(b+|a){1,2}c",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aac","^(b+|a){1,2}c",[]))), + <<":bbbbbbbbbbb">> = iolist_to_binary(join(re:split("abbbbbbbbbbbc","^(b+|a){1,2}c",[trim]))), <<":bbbbbbbbbbb:">> = iolist_to_binary(join(re:split("abbbbbbbbbbbc","^(b+|a){1,2}c",[{parts, - 2}]))), - <<":bbbbbbbbbbb:">> = iolist_to_binary(join(re:split("abbbbbbbbbbbc","^(b+|a){1,2}c",[]))), - <<":a">> = iolist_to_binary(join(re:split("bbbbbbbbbbbac","^(b+|a){1,2}c",[trim]))), + 2}]))), + <<":bbbbbbbbbbb:">> = iolist_to_binary(join(re:split("abbbbbbbbbbbc","^(b+|a){1,2}c",[]))), + <<":a">> = iolist_to_binary(join(re:split("bbbbbbbbbbbac","^(b+|a){1,2}c",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("bbbbbbbbbbbac","^(b+|a){1,2}c",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("bbbbbbbbbbbac","^(b+|a){1,2}c",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b+|a){1,2}c",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("bbbbbbbbbbbac","^(b+|a){1,2}c",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b+|a){1,2}c",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b+|a){1,2}c",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b+|a){1,2}c",[]))), - <<"aaac">> = iolist_to_binary(join(re:split("aaac","^(b+|a){1,2}c",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b+|a){1,2}c",[]))), + <<"aaac">> = iolist_to_binary(join(re:split("aaac","^(b+|a){1,2}c",[trim]))), <<"aaac">> = iolist_to_binary(join(re:split("aaac","^(b+|a){1,2}c",[{parts, - 2}]))), - <<"aaac">> = iolist_to_binary(join(re:split("aaac","^(b+|a){1,2}c",[]))), - <<"abbbbbbbbbbbac">> = iolist_to_binary(join(re:split("abbbbbbbbbbbac","^(b+|a){1,2}c",[trim]))), + 2}]))), + <<"aaac">> = iolist_to_binary(join(re:split("aaac","^(b+|a){1,2}c",[]))), + <<"abbbbbbbbbbbac">> = iolist_to_binary(join(re:split("abbbbbbbbbbbac","^(b+|a){1,2}c",[trim]))), <<"abbbbbbbbbbbac">> = iolist_to_binary(join(re:split("abbbbbbbbbbbac","^(b+|a){1,2}c",[{parts, - 2}]))), - <<"abbbbbbbbbbbac">> = iolist_to_binary(join(re:split("abbbbbbbbbbbac","^(b+|a){1,2}c",[]))), - <<":b">> = iolist_to_binary(join(re:split("bbc","^(b+|a){1,2}?bc",[trim]))), + 2}]))), + <<"abbbbbbbbbbbac">> = iolist_to_binary(join(re:split("abbbbbbbbbbbac","^(b+|a){1,2}c",[]))), + <<":b">> = iolist_to_binary(join(re:split("bbc","^(b+|a){1,2}?bc",[trim]))), <<":b:">> = iolist_to_binary(join(re:split("bbc","^(b+|a){1,2}?bc",[{parts, - 2}]))), - <<":b:">> = iolist_to_binary(join(re:split("bbc","^(b+|a){1,2}?bc",[]))), - <<":ba">> = iolist_to_binary(join(re:split("babc","^(b*|ba){1,2}?bc",[trim]))), + 2}]))), + <<":b:">> = iolist_to_binary(join(re:split("bbc","^(b+|a){1,2}?bc",[]))), + <<":ba">> = iolist_to_binary(join(re:split("babc","^(b*|ba){1,2}?bc",[trim]))), <<":ba:">> = iolist_to_binary(join(re:split("babc","^(b*|ba){1,2}?bc",[{parts, - 2}]))), - <<":ba:">> = iolist_to_binary(join(re:split("babc","^(b*|ba){1,2}?bc",[]))), - <<":ba">> = iolist_to_binary(join(re:split("bbabc","^(b*|ba){1,2}?bc",[trim]))), + 2}]))), + <<":ba:">> = iolist_to_binary(join(re:split("babc","^(b*|ba){1,2}?bc",[]))), + <<":ba">> = iolist_to_binary(join(re:split("bbabc","^(b*|ba){1,2}?bc",[trim]))), <<":ba:">> = iolist_to_binary(join(re:split("bbabc","^(b*|ba){1,2}?bc",[{parts, - 2}]))), - <<":ba:">> = iolist_to_binary(join(re:split("bbabc","^(b*|ba){1,2}?bc",[]))), - <<":ba">> = iolist_to_binary(join(re:split("bababc","^(b*|ba){1,2}?bc",[trim]))), + 2}]))), + <<":ba:">> = iolist_to_binary(join(re:split("bbabc","^(b*|ba){1,2}?bc",[]))), + <<":ba">> = iolist_to_binary(join(re:split("bababc","^(b*|ba){1,2}?bc",[trim]))), <<":ba:">> = iolist_to_binary(join(re:split("bababc","^(b*|ba){1,2}?bc",[{parts, - 2}]))), - <<":ba:">> = iolist_to_binary(join(re:split("bababc","^(b*|ba){1,2}?bc",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b*|ba){1,2}?bc",[trim]))), + 2}]))), + <<":ba:">> = iolist_to_binary(join(re:split("bababc","^(b*|ba){1,2}?bc",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b*|ba){1,2}?bc",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b*|ba){1,2}?bc",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b*|ba){1,2}?bc",[]))), - <<"bababbc">> = iolist_to_binary(join(re:split("bababbc","^(b*|ba){1,2}?bc",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(b*|ba){1,2}?bc",[]))), + <<"bababbc">> = iolist_to_binary(join(re:split("bababbc","^(b*|ba){1,2}?bc",[trim]))), <<"bababbc">> = iolist_to_binary(join(re:split("bababbc","^(b*|ba){1,2}?bc",[{parts, - 2}]))), - <<"bababbc">> = iolist_to_binary(join(re:split("bababbc","^(b*|ba){1,2}?bc",[]))), - <<"babababc">> = iolist_to_binary(join(re:split("babababc","^(b*|ba){1,2}?bc",[trim]))), + 2}]))), + <<"bababbc">> = iolist_to_binary(join(re:split("bababbc","^(b*|ba){1,2}?bc",[]))), + <<"babababc">> = iolist_to_binary(join(re:split("babababc","^(b*|ba){1,2}?bc",[trim]))), <<"babababc">> = iolist_to_binary(join(re:split("babababc","^(b*|ba){1,2}?bc",[{parts, - 2}]))), - <<"babababc">> = iolist_to_binary(join(re:split("babababc","^(b*|ba){1,2}?bc",[]))), - <<":ba">> = iolist_to_binary(join(re:split("babc","^(ba|b*){1,2}?bc",[trim]))), + 2}]))), + <<"babababc">> = iolist_to_binary(join(re:split("babababc","^(b*|ba){1,2}?bc",[]))), + <<":ba">> = iolist_to_binary(join(re:split("babc","^(ba|b*){1,2}?bc",[trim]))), <<":ba:">> = iolist_to_binary(join(re:split("babc","^(ba|b*){1,2}?bc",[{parts, - 2}]))), - <<":ba:">> = iolist_to_binary(join(re:split("babc","^(ba|b*){1,2}?bc",[]))), - <<":ba">> = iolist_to_binary(join(re:split("bbabc","^(ba|b*){1,2}?bc",[trim]))), + 2}]))), + <<":ba:">> = iolist_to_binary(join(re:split("babc","^(ba|b*){1,2}?bc",[]))), + <<":ba">> = iolist_to_binary(join(re:split("bbabc","^(ba|b*){1,2}?bc",[trim]))), <<":ba:">> = iolist_to_binary(join(re:split("bbabc","^(ba|b*){1,2}?bc",[{parts, - 2}]))), - <<":ba:">> = iolist_to_binary(join(re:split("bbabc","^(ba|b*){1,2}?bc",[]))), - <<":ba">> = iolist_to_binary(join(re:split("bababc","^(ba|b*){1,2}?bc",[trim]))), + 2}]))), + <<":ba:">> = iolist_to_binary(join(re:split("bbabc","^(ba|b*){1,2}?bc",[]))), + <<":ba">> = iolist_to_binary(join(re:split("bababc","^(ba|b*){1,2}?bc",[trim]))), <<":ba:">> = iolist_to_binary(join(re:split("bababc","^(ba|b*){1,2}?bc",[{parts, - 2}]))), - <<":ba:">> = iolist_to_binary(join(re:split("bababc","^(ba|b*){1,2}?bc",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(ba|b*){1,2}?bc",[trim]))), + 2}]))), + <<":ba:">> = iolist_to_binary(join(re:split("bababc","^(ba|b*){1,2}?bc",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(ba|b*){1,2}?bc",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(ba|b*){1,2}?bc",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(ba|b*){1,2}?bc",[]))), - <<"bababbc">> = iolist_to_binary(join(re:split("bababbc","^(ba|b*){1,2}?bc",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(ba|b*){1,2}?bc",[]))), + <<"bababbc">> = iolist_to_binary(join(re:split("bababbc","^(ba|b*){1,2}?bc",[trim]))), <<"bababbc">> = iolist_to_binary(join(re:split("bababbc","^(ba|b*){1,2}?bc",[{parts, - 2}]))), - <<"bababbc">> = iolist_to_binary(join(re:split("bababbc","^(ba|b*){1,2}?bc",[]))), - <<"babababc">> = iolist_to_binary(join(re:split("babababc","^(ba|b*){1,2}?bc",[trim]))), + 2}]))), + <<"bababbc">> = iolist_to_binary(join(re:split("bababbc","^(ba|b*){1,2}?bc",[]))), + <<"babababc">> = iolist_to_binary(join(re:split("babababc","^(ba|b*){1,2}?bc",[trim]))), <<"babababc">> = iolist_to_binary(join(re:split("babababc","^(ba|b*){1,2}?bc",[{parts, - 2}]))), - <<"babababc">> = iolist_to_binary(join(re:split("babababc","^(ba|b*){1,2}?bc",[]))), - <<"">> = iolist_to_binary(join(re:split(";z","^\\ca\\cA\\c[;\\c:",[trim]))), + 2}]))), + <<"babababc">> = iolist_to_binary(join(re:split("babababc","^(ba|b*){1,2}?bc",[]))), + <<"">> = iolist_to_binary(join(re:split(";z","^\\ca\\cA\\c[;\\c:",[trim]))), <<":">> = iolist_to_binary(join(re:split(";z","^\\ca\\cA\\c[;\\c:",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split(";z","^\\ca\\cA\\c[;\\c:",[]))), - <<":thing">> = iolist_to_binary(join(re:split("athing","^[ab\\]cde]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split(";z","^\\ca\\cA\\c[;\\c:",[]))), + <<":thing">> = iolist_to_binary(join(re:split("athing","^[ab\\]cde]",[trim]))), <<":thing">> = iolist_to_binary(join(re:split("athing","^[ab\\]cde]",[{parts, - 2}]))), - <<":thing">> = iolist_to_binary(join(re:split("athing","^[ab\\]cde]",[]))), - <<":thing">> = iolist_to_binary(join(re:split("bthing","^[ab\\]cde]",[trim]))), + 2}]))), + <<":thing">> = iolist_to_binary(join(re:split("athing","^[ab\\]cde]",[]))), + <<":thing">> = iolist_to_binary(join(re:split("bthing","^[ab\\]cde]",[trim]))), <<":thing">> = iolist_to_binary(join(re:split("bthing","^[ab\\]cde]",[{parts, - 2}]))), - <<":thing">> = iolist_to_binary(join(re:split("bthing","^[ab\\]cde]",[]))), - <<":thing">> = iolist_to_binary(join(re:split("]thing","^[ab\\]cde]",[trim]))), + 2}]))), + <<":thing">> = iolist_to_binary(join(re:split("bthing","^[ab\\]cde]",[]))), + <<":thing">> = iolist_to_binary(join(re:split("]thing","^[ab\\]cde]",[trim]))), <<":thing">> = iolist_to_binary(join(re:split("]thing","^[ab\\]cde]",[{parts, - 2}]))), - <<":thing">> = iolist_to_binary(join(re:split("]thing","^[ab\\]cde]",[]))), - <<":thing">> = iolist_to_binary(join(re:split("cthing","^[ab\\]cde]",[trim]))), + 2}]))), + <<":thing">> = iolist_to_binary(join(re:split("]thing","^[ab\\]cde]",[]))), + <<":thing">> = iolist_to_binary(join(re:split("cthing","^[ab\\]cde]",[trim]))), <<":thing">> = iolist_to_binary(join(re:split("cthing","^[ab\\]cde]",[{parts, - 2}]))), - <<":thing">> = iolist_to_binary(join(re:split("cthing","^[ab\\]cde]",[]))), - <<":thing">> = iolist_to_binary(join(re:split("dthing","^[ab\\]cde]",[trim]))), + 2}]))), + <<":thing">> = iolist_to_binary(join(re:split("cthing","^[ab\\]cde]",[]))), + <<":thing">> = iolist_to_binary(join(re:split("dthing","^[ab\\]cde]",[trim]))), <<":thing">> = iolist_to_binary(join(re:split("dthing","^[ab\\]cde]",[{parts, - 2}]))), - <<":thing">> = iolist_to_binary(join(re:split("dthing","^[ab\\]cde]",[]))), - <<":thing">> = iolist_to_binary(join(re:split("ething","^[ab\\]cde]",[trim]))), + 2}]))), + <<":thing">> = iolist_to_binary(join(re:split("dthing","^[ab\\]cde]",[]))), + <<":thing">> = iolist_to_binary(join(re:split("ething","^[ab\\]cde]",[trim]))), <<":thing">> = iolist_to_binary(join(re:split("ething","^[ab\\]cde]",[{parts, - 2}]))), - <<":thing">> = iolist_to_binary(join(re:split("ething","^[ab\\]cde]",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[ab\\]cde]",[trim]))), + 2}]))), + <<":thing">> = iolist_to_binary(join(re:split("ething","^[ab\\]cde]",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[ab\\]cde]",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[ab\\]cde]",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[ab\\]cde]",[]))), - <<"fthing">> = iolist_to_binary(join(re:split("fthing","^[ab\\]cde]",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[ab\\]cde]",[]))), + <<"fthing">> = iolist_to_binary(join(re:split("fthing","^[ab\\]cde]",[trim]))), <<"fthing">> = iolist_to_binary(join(re:split("fthing","^[ab\\]cde]",[{parts, - 2}]))), - <<"fthing">> = iolist_to_binary(join(re:split("fthing","^[ab\\]cde]",[]))), - <<"[thing">> = iolist_to_binary(join(re:split("[thing","^[ab\\]cde]",[trim]))), + 2}]))), + <<"fthing">> = iolist_to_binary(join(re:split("fthing","^[ab\\]cde]",[]))), + <<"[thing">> = iolist_to_binary(join(re:split("[thing","^[ab\\]cde]",[trim]))), <<"[thing">> = iolist_to_binary(join(re:split("[thing","^[ab\\]cde]",[{parts, - 2}]))), - <<"[thing">> = iolist_to_binary(join(re:split("[thing","^[ab\\]cde]",[]))), - <<"\\thing">> = iolist_to_binary(join(re:split("\\thing","^[ab\\]cde]",[trim]))), + 2}]))), + <<"[thing">> = iolist_to_binary(join(re:split("[thing","^[ab\\]cde]",[]))), + <<"\\thing">> = iolist_to_binary(join(re:split("\\thing","^[ab\\]cde]",[trim]))), <<"\\thing">> = iolist_to_binary(join(re:split("\\thing","^[ab\\]cde]",[{parts, - 2}]))), - <<"\\thing">> = iolist_to_binary(join(re:split("\\thing","^[ab\\]cde]",[]))), - <<":thing">> = iolist_to_binary(join(re:split("]thing","^[]cde]",[trim]))), + 2}]))), + <<"\\thing">> = iolist_to_binary(join(re:split("\\thing","^[ab\\]cde]",[]))), + <<":thing">> = iolist_to_binary(join(re:split("]thing","^[]cde]",[trim]))), <<":thing">> = iolist_to_binary(join(re:split("]thing","^[]cde]",[{parts, - 2}]))), - <<":thing">> = iolist_to_binary(join(re:split("]thing","^[]cde]",[]))), - <<":thing">> = iolist_to_binary(join(re:split("cthing","^[]cde]",[trim]))), + 2}]))), + <<":thing">> = iolist_to_binary(join(re:split("]thing","^[]cde]",[]))), + <<":thing">> = iolist_to_binary(join(re:split("cthing","^[]cde]",[trim]))), <<":thing">> = iolist_to_binary(join(re:split("cthing","^[]cde]",[{parts, - 2}]))), - <<":thing">> = iolist_to_binary(join(re:split("cthing","^[]cde]",[]))), - <<":thing">> = iolist_to_binary(join(re:split("dthing","^[]cde]",[trim]))), + 2}]))), + <<":thing">> = iolist_to_binary(join(re:split("cthing","^[]cde]",[]))), + <<":thing">> = iolist_to_binary(join(re:split("dthing","^[]cde]",[trim]))), <<":thing">> = iolist_to_binary(join(re:split("dthing","^[]cde]",[{parts, - 2}]))), - <<":thing">> = iolist_to_binary(join(re:split("dthing","^[]cde]",[]))), - <<":thing">> = iolist_to_binary(join(re:split("ething","^[]cde]",[trim]))), + 2}]))), + <<":thing">> = iolist_to_binary(join(re:split("dthing","^[]cde]",[]))), + <<":thing">> = iolist_to_binary(join(re:split("ething","^[]cde]",[trim]))), <<":thing">> = iolist_to_binary(join(re:split("ething","^[]cde]",[{parts, - 2}]))), - <<":thing">> = iolist_to_binary(join(re:split("ething","^[]cde]",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[]cde]",[trim]))), + 2}]))), + <<":thing">> = iolist_to_binary(join(re:split("ething","^[]cde]",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[]cde]",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[]cde]",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[]cde]",[]))), - <<"athing">> = iolist_to_binary(join(re:split("athing","^[]cde]",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[]cde]",[]))), + <<"athing">> = iolist_to_binary(join(re:split("athing","^[]cde]",[trim]))), <<"athing">> = iolist_to_binary(join(re:split("athing","^[]cde]",[{parts, - 2}]))), - <<"athing">> = iolist_to_binary(join(re:split("athing","^[]cde]",[]))), - <<"fthing">> = iolist_to_binary(join(re:split("fthing","^[]cde]",[trim]))), + 2}]))), + <<"athing">> = iolist_to_binary(join(re:split("athing","^[]cde]",[]))), + <<"fthing">> = iolist_to_binary(join(re:split("fthing","^[]cde]",[trim]))), <<"fthing">> = iolist_to_binary(join(re:split("fthing","^[]cde]",[{parts, - 2}]))), - <<"fthing">> = iolist_to_binary(join(re:split("fthing","^[]cde]",[]))), - <<":thing">> = iolist_to_binary(join(re:split("fthing","^[^ab\\]cde]",[trim]))), + 2}]))), + <<"fthing">> = iolist_to_binary(join(re:split("fthing","^[]cde]",[]))), + <<":thing">> = iolist_to_binary(join(re:split("fthing","^[^ab\\]cde]",[trim]))), <<":thing">> = iolist_to_binary(join(re:split("fthing","^[^ab\\]cde]",[{parts, - 2}]))), - <<":thing">> = iolist_to_binary(join(re:split("fthing","^[^ab\\]cde]",[]))), - <<":thing">> = iolist_to_binary(join(re:split("[thing","^[^ab\\]cde]",[trim]))), + 2}]))), + <<":thing">> = iolist_to_binary(join(re:split("fthing","^[^ab\\]cde]",[]))), + <<":thing">> = iolist_to_binary(join(re:split("[thing","^[^ab\\]cde]",[trim]))), <<":thing">> = iolist_to_binary(join(re:split("[thing","^[^ab\\]cde]",[{parts, - 2}]))), - <<":thing">> = iolist_to_binary(join(re:split("[thing","^[^ab\\]cde]",[]))), - <<":thing">> = iolist_to_binary(join(re:split("\\thing","^[^ab\\]cde]",[trim]))), + 2}]))), + <<":thing">> = iolist_to_binary(join(re:split("[thing","^[^ab\\]cde]",[]))), + <<":thing">> = iolist_to_binary(join(re:split("\\thing","^[^ab\\]cde]",[trim]))), <<":thing">> = iolist_to_binary(join(re:split("\\thing","^[^ab\\]cde]",[{parts, - 2}]))), - <<":thing">> = iolist_to_binary(join(re:split("\\thing","^[^ab\\]cde]",[]))), - <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[^ab\\]cde]",[trim]))), + 2}]))), + <<":thing">> = iolist_to_binary(join(re:split("\\thing","^[^ab\\]cde]",[]))), + <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[^ab\\]cde]",[trim]))), <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[^ab\\]cde]",[{parts, - 2}]))), - <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[^ab\\]cde]",[]))), - <<"athing">> = iolist_to_binary(join(re:split("athing","^[^ab\\]cde]",[trim]))), + 2}]))), + <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[^ab\\]cde]",[]))), + <<"athing">> = iolist_to_binary(join(re:split("athing","^[^ab\\]cde]",[trim]))), <<"athing">> = iolist_to_binary(join(re:split("athing","^[^ab\\]cde]",[{parts, - 2}]))), - <<"athing">> = iolist_to_binary(join(re:split("athing","^[^ab\\]cde]",[]))), - <<"bthing">> = iolist_to_binary(join(re:split("bthing","^[^ab\\]cde]",[trim]))), + 2}]))), + <<"athing">> = iolist_to_binary(join(re:split("athing","^[^ab\\]cde]",[]))), + <<"bthing">> = iolist_to_binary(join(re:split("bthing","^[^ab\\]cde]",[trim]))), <<"bthing">> = iolist_to_binary(join(re:split("bthing","^[^ab\\]cde]",[{parts, - 2}]))), - <<"bthing">> = iolist_to_binary(join(re:split("bthing","^[^ab\\]cde]",[]))), - <<"]thing">> = iolist_to_binary(join(re:split("]thing","^[^ab\\]cde]",[trim]))), + 2}]))), + <<"bthing">> = iolist_to_binary(join(re:split("bthing","^[^ab\\]cde]",[]))), + <<"]thing">> = iolist_to_binary(join(re:split("]thing","^[^ab\\]cde]",[trim]))), <<"]thing">> = iolist_to_binary(join(re:split("]thing","^[^ab\\]cde]",[{parts, - 2}]))), - <<"]thing">> = iolist_to_binary(join(re:split("]thing","^[^ab\\]cde]",[]))), - <<"cthing">> = iolist_to_binary(join(re:split("cthing","^[^ab\\]cde]",[trim]))), + 2}]))), + <<"]thing">> = iolist_to_binary(join(re:split("]thing","^[^ab\\]cde]",[]))), + <<"cthing">> = iolist_to_binary(join(re:split("cthing","^[^ab\\]cde]",[trim]))), <<"cthing">> = iolist_to_binary(join(re:split("cthing","^[^ab\\]cde]",[{parts, - 2}]))), - <<"cthing">> = iolist_to_binary(join(re:split("cthing","^[^ab\\]cde]",[]))), - <<"dthing">> = iolist_to_binary(join(re:split("dthing","^[^ab\\]cde]",[trim]))), + 2}]))), + <<"cthing">> = iolist_to_binary(join(re:split("cthing","^[^ab\\]cde]",[]))), + <<"dthing">> = iolist_to_binary(join(re:split("dthing","^[^ab\\]cde]",[trim]))), <<"dthing">> = iolist_to_binary(join(re:split("dthing","^[^ab\\]cde]",[{parts, - 2}]))), - <<"dthing">> = iolist_to_binary(join(re:split("dthing","^[^ab\\]cde]",[]))), - <<"ething">> = iolist_to_binary(join(re:split("ething","^[^ab\\]cde]",[trim]))), + 2}]))), + <<"dthing">> = iolist_to_binary(join(re:split("dthing","^[^ab\\]cde]",[]))), + <<"ething">> = iolist_to_binary(join(re:split("ething","^[^ab\\]cde]",[trim]))), <<"ething">> = iolist_to_binary(join(re:split("ething","^[^ab\\]cde]",[{parts, - 2}]))), - <<"ething">> = iolist_to_binary(join(re:split("ething","^[^ab\\]cde]",[]))), - <<":thing">> = iolist_to_binary(join(re:split("athing","^[^]cde]",[trim]))), + 2}]))), + <<"ething">> = iolist_to_binary(join(re:split("ething","^[^ab\\]cde]",[]))), + <<":thing">> = iolist_to_binary(join(re:split("athing","^[^]cde]",[trim]))), <<":thing">> = iolist_to_binary(join(re:split("athing","^[^]cde]",[{parts, - 2}]))), - <<":thing">> = iolist_to_binary(join(re:split("athing","^[^]cde]",[]))), - <<":thing">> = iolist_to_binary(join(re:split("fthing","^[^]cde]",[trim]))), + 2}]))), + <<":thing">> = iolist_to_binary(join(re:split("athing","^[^]cde]",[]))), + <<":thing">> = iolist_to_binary(join(re:split("fthing","^[^]cde]",[trim]))), <<":thing">> = iolist_to_binary(join(re:split("fthing","^[^]cde]",[{parts, - 2}]))), - <<":thing">> = iolist_to_binary(join(re:split("fthing","^[^]cde]",[]))), - <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[^]cde]",[trim]))), + 2}]))), + <<":thing">> = iolist_to_binary(join(re:split("fthing","^[^]cde]",[]))), + <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[^]cde]",[trim]))), <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[^]cde]",[{parts, - 2}]))), - <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[^]cde]",[]))), - <<"]thing">> = iolist_to_binary(join(re:split("]thing","^[^]cde]",[trim]))), + 2}]))), + <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[^]cde]",[]))), + <<"]thing">> = iolist_to_binary(join(re:split("]thing","^[^]cde]",[trim]))), <<"]thing">> = iolist_to_binary(join(re:split("]thing","^[^]cde]",[{parts, - 2}]))), - <<"]thing">> = iolist_to_binary(join(re:split("]thing","^[^]cde]",[]))), - <<"cthing">> = iolist_to_binary(join(re:split("cthing","^[^]cde]",[trim]))), + 2}]))), + <<"]thing">> = iolist_to_binary(join(re:split("]thing","^[^]cde]",[]))), + <<"cthing">> = iolist_to_binary(join(re:split("cthing","^[^]cde]",[trim]))), <<"cthing">> = iolist_to_binary(join(re:split("cthing","^[^]cde]",[{parts, - 2}]))), - <<"cthing">> = iolist_to_binary(join(re:split("cthing","^[^]cde]",[]))), - <<"dthing">> = iolist_to_binary(join(re:split("dthing","^[^]cde]",[trim]))), + 2}]))), + <<"cthing">> = iolist_to_binary(join(re:split("cthing","^[^]cde]",[]))), + <<"dthing">> = iolist_to_binary(join(re:split("dthing","^[^]cde]",[trim]))), <<"dthing">> = iolist_to_binary(join(re:split("dthing","^[^]cde]",[{parts, - 2}]))), - <<"dthing">> = iolist_to_binary(join(re:split("dthing","^[^]cde]",[]))), - <<"ething">> = iolist_to_binary(join(re:split("ething","^[^]cde]",[trim]))), + 2}]))), + <<"dthing">> = iolist_to_binary(join(re:split("dthing","^[^]cde]",[]))), + <<"ething">> = iolist_to_binary(join(re:split("ething","^[^]cde]",[trim]))), <<"ething">> = iolist_to_binary(join(re:split("ething","^[^]cde]",[{parts, - 2}]))), - <<"ething">> = iolist_to_binary(join(re:split("ething","^[^]cde]",[]))), - <<"">> = iolist_to_binary(join(re:split("0","^[0-9]+$",[trim]))), + 2}]))), + <<"ething">> = iolist_to_binary(join(re:split("ething","^[^]cde]",[]))), + <<"">> = iolist_to_binary(join(re:split("0","^[0-9]+$",[trim]))), <<":">> = iolist_to_binary(join(re:split("0","^[0-9]+$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("0","^[0-9]+$",[]))), - <<"">> = iolist_to_binary(join(re:split("1","^[0-9]+$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("0","^[0-9]+$",[]))), + <<"">> = iolist_to_binary(join(re:split("1","^[0-9]+$",[trim]))), <<":">> = iolist_to_binary(join(re:split("1","^[0-9]+$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("1","^[0-9]+$",[]))), - <<"">> = iolist_to_binary(join(re:split("2","^[0-9]+$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("1","^[0-9]+$",[]))), + <<"">> = iolist_to_binary(join(re:split("2","^[0-9]+$",[trim]))), <<":">> = iolist_to_binary(join(re:split("2","^[0-9]+$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("2","^[0-9]+$",[]))), - <<"">> = iolist_to_binary(join(re:split("3","^[0-9]+$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("2","^[0-9]+$",[]))), + <<"">> = iolist_to_binary(join(re:split("3","^[0-9]+$",[trim]))), <<":">> = iolist_to_binary(join(re:split("3","^[0-9]+$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("3","^[0-9]+$",[]))), - <<"">> = iolist_to_binary(join(re:split("4","^[0-9]+$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("3","^[0-9]+$",[]))), + <<"">> = iolist_to_binary(join(re:split("4","^[0-9]+$",[trim]))), <<":">> = iolist_to_binary(join(re:split("4","^[0-9]+$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("4","^[0-9]+$",[]))), - <<"">> = iolist_to_binary(join(re:split("5","^[0-9]+$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("4","^[0-9]+$",[]))), + <<"">> = iolist_to_binary(join(re:split("5","^[0-9]+$",[trim]))), <<":">> = iolist_to_binary(join(re:split("5","^[0-9]+$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("5","^[0-9]+$",[]))), - <<"">> = iolist_to_binary(join(re:split("6","^[0-9]+$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("5","^[0-9]+$",[]))), + <<"">> = iolist_to_binary(join(re:split("6","^[0-9]+$",[trim]))), <<":">> = iolist_to_binary(join(re:split("6","^[0-9]+$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("6","^[0-9]+$",[]))), - <<"">> = iolist_to_binary(join(re:split("7","^[0-9]+$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("6","^[0-9]+$",[]))), + <<"">> = iolist_to_binary(join(re:split("7","^[0-9]+$",[trim]))), <<":">> = iolist_to_binary(join(re:split("7","^[0-9]+$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("7","^[0-9]+$",[]))), - <<"">> = iolist_to_binary(join(re:split("8","^[0-9]+$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("7","^[0-9]+$",[]))), + <<"">> = iolist_to_binary(join(re:split("8","^[0-9]+$",[trim]))), <<":">> = iolist_to_binary(join(re:split("8","^[0-9]+$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("8","^[0-9]+$",[]))), - <<"">> = iolist_to_binary(join(re:split("9","^[0-9]+$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("8","^[0-9]+$",[]))), + <<"">> = iolist_to_binary(join(re:split("9","^[0-9]+$",[trim]))), <<":">> = iolist_to_binary(join(re:split("9","^[0-9]+$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("9","^[0-9]+$",[]))), - <<"">> = iolist_to_binary(join(re:split("10","^[0-9]+$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("9","^[0-9]+$",[]))), + <<"">> = iolist_to_binary(join(re:split("10","^[0-9]+$",[trim]))), <<":">> = iolist_to_binary(join(re:split("10","^[0-9]+$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("10","^[0-9]+$",[]))), - <<"">> = iolist_to_binary(join(re:split("100","^[0-9]+$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("10","^[0-9]+$",[]))), + <<"">> = iolist_to_binary(join(re:split("100","^[0-9]+$",[trim]))), <<":">> = iolist_to_binary(join(re:split("100","^[0-9]+$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("100","^[0-9]+$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[0-9]+$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("100","^[0-9]+$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[0-9]+$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[0-9]+$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[0-9]+$",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","^[0-9]+$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[0-9]+$",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","^[0-9]+$",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","^[0-9]+$",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","^[0-9]+$",[]))), - <<"">> = iolist_to_binary(join(re:split("enter","^.*nter",[trim]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","^[0-9]+$",[]))), + <<"">> = iolist_to_binary(join(re:split("enter","^.*nter",[trim]))), <<":">> = iolist_to_binary(join(re:split("enter","^.*nter",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("enter","^.*nter",[]))), - <<"">> = iolist_to_binary(join(re:split("inter","^.*nter",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("enter","^.*nter",[]))), + <<"">> = iolist_to_binary(join(re:split("inter","^.*nter",[trim]))), <<":">> = iolist_to_binary(join(re:split("inter","^.*nter",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("inter","^.*nter",[]))), - <<"">> = iolist_to_binary(join(re:split("uponter","^.*nter",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("inter","^.*nter",[]))), + <<"">> = iolist_to_binary(join(re:split("uponter","^.*nter",[trim]))), <<":">> = iolist_to_binary(join(re:split("uponter","^.*nter",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("uponter","^.*nter",[]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("uponter","^.*nter",[]))), ok. run1() -> - <<"">> = iolist_to_binary(join(re:split("xxx0","^xxx[0-9]+$",[trim]))), + <<"">> = iolist_to_binary(join(re:split("xxx0","^xxx[0-9]+$",[trim]))), <<":">> = iolist_to_binary(join(re:split("xxx0","^xxx[0-9]+$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("xxx0","^xxx[0-9]+$",[]))), - <<"">> = iolist_to_binary(join(re:split("xxx1234","^xxx[0-9]+$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("xxx0","^xxx[0-9]+$",[]))), + <<"">> = iolist_to_binary(join(re:split("xxx1234","^xxx[0-9]+$",[trim]))), <<":">> = iolist_to_binary(join(re:split("xxx1234","^xxx[0-9]+$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("xxx1234","^xxx[0-9]+$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^xxx[0-9]+$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("xxx1234","^xxx[0-9]+$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^xxx[0-9]+$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^xxx[0-9]+$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^xxx[0-9]+$",[]))), - <<"xxx">> = iolist_to_binary(join(re:split("xxx","^xxx[0-9]+$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^xxx[0-9]+$",[]))), + <<"xxx">> = iolist_to_binary(join(re:split("xxx","^xxx[0-9]+$",[trim]))), <<"xxx">> = iolist_to_binary(join(re:split("xxx","^xxx[0-9]+$",[{parts, - 2}]))), - <<"xxx">> = iolist_to_binary(join(re:split("xxx","^xxx[0-9]+$",[]))), - <<"">> = iolist_to_binary(join(re:split("x123","^.+[0-9][0-9][0-9]$",[trim]))), + 2}]))), + <<"xxx">> = iolist_to_binary(join(re:split("xxx","^xxx[0-9]+$",[]))), + <<"">> = iolist_to_binary(join(re:split("x123","^.+[0-9][0-9][0-9]$",[trim]))), <<":">> = iolist_to_binary(join(re:split("x123","^.+[0-9][0-9][0-9]$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("x123","^.+[0-9][0-9][0-9]$",[]))), - <<"">> = iolist_to_binary(join(re:split("xx123","^.+[0-9][0-9][0-9]$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("x123","^.+[0-9][0-9][0-9]$",[]))), + <<"">> = iolist_to_binary(join(re:split("xx123","^.+[0-9][0-9][0-9]$",[trim]))), <<":">> = iolist_to_binary(join(re:split("xx123","^.+[0-9][0-9][0-9]$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("xx123","^.+[0-9][0-9][0-9]$",[]))), - <<"">> = iolist_to_binary(join(re:split("123456","^.+[0-9][0-9][0-9]$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("xx123","^.+[0-9][0-9][0-9]$",[]))), + <<"">> = iolist_to_binary(join(re:split("123456","^.+[0-9][0-9][0-9]$",[trim]))), <<":">> = iolist_to_binary(join(re:split("123456","^.+[0-9][0-9][0-9]$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("123456","^.+[0-9][0-9][0-9]$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.+[0-9][0-9][0-9]$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("123456","^.+[0-9][0-9][0-9]$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.+[0-9][0-9][0-9]$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.+[0-9][0-9][0-9]$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.+[0-9][0-9][0-9]$",[]))), - <<"123">> = iolist_to_binary(join(re:split("123","^.+[0-9][0-9][0-9]$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.+[0-9][0-9][0-9]$",[]))), + <<"123">> = iolist_to_binary(join(re:split("123","^.+[0-9][0-9][0-9]$",[trim]))), <<"123">> = iolist_to_binary(join(re:split("123","^.+[0-9][0-9][0-9]$",[{parts, - 2}]))), - <<"123">> = iolist_to_binary(join(re:split("123","^.+[0-9][0-9][0-9]$",[]))), - <<"">> = iolist_to_binary(join(re:split("x1234","^.+[0-9][0-9][0-9]$",[trim]))), + 2}]))), + <<"123">> = iolist_to_binary(join(re:split("123","^.+[0-9][0-9][0-9]$",[]))), + <<"">> = iolist_to_binary(join(re:split("x1234","^.+[0-9][0-9][0-9]$",[trim]))), <<":">> = iolist_to_binary(join(re:split("x1234","^.+[0-9][0-9][0-9]$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("x1234","^.+[0-9][0-9][0-9]$",[]))), - <<"">> = iolist_to_binary(join(re:split("x123","^.+?[0-9][0-9][0-9]$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("x1234","^.+[0-9][0-9][0-9]$",[]))), + <<"">> = iolist_to_binary(join(re:split("x123","^.+?[0-9][0-9][0-9]$",[trim]))), <<":">> = iolist_to_binary(join(re:split("x123","^.+?[0-9][0-9][0-9]$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("x123","^.+?[0-9][0-9][0-9]$",[]))), - <<"">> = iolist_to_binary(join(re:split("xx123","^.+?[0-9][0-9][0-9]$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("x123","^.+?[0-9][0-9][0-9]$",[]))), + <<"">> = iolist_to_binary(join(re:split("xx123","^.+?[0-9][0-9][0-9]$",[trim]))), <<":">> = iolist_to_binary(join(re:split("xx123","^.+?[0-9][0-9][0-9]$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("xx123","^.+?[0-9][0-9][0-9]$",[]))), - <<"">> = iolist_to_binary(join(re:split("123456","^.+?[0-9][0-9][0-9]$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("xx123","^.+?[0-9][0-9][0-9]$",[]))), + <<"">> = iolist_to_binary(join(re:split("123456","^.+?[0-9][0-9][0-9]$",[trim]))), <<":">> = iolist_to_binary(join(re:split("123456","^.+?[0-9][0-9][0-9]$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("123456","^.+?[0-9][0-9][0-9]$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.+?[0-9][0-9][0-9]$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("123456","^.+?[0-9][0-9][0-9]$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.+?[0-9][0-9][0-9]$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.+?[0-9][0-9][0-9]$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.+?[0-9][0-9][0-9]$",[]))), - <<"123">> = iolist_to_binary(join(re:split("123","^.+?[0-9][0-9][0-9]$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.+?[0-9][0-9][0-9]$",[]))), + <<"123">> = iolist_to_binary(join(re:split("123","^.+?[0-9][0-9][0-9]$",[trim]))), <<"123">> = iolist_to_binary(join(re:split("123","^.+?[0-9][0-9][0-9]$",[{parts, - 2}]))), - <<"123">> = iolist_to_binary(join(re:split("123","^.+?[0-9][0-9][0-9]$",[]))), - <<"">> = iolist_to_binary(join(re:split("x1234","^.+?[0-9][0-9][0-9]$",[trim]))), + 2}]))), + <<"123">> = iolist_to_binary(join(re:split("123","^.+?[0-9][0-9][0-9]$",[]))), + <<"">> = iolist_to_binary(join(re:split("x1234","^.+?[0-9][0-9][0-9]$",[trim]))), <<":">> = iolist_to_binary(join(re:split("x1234","^.+?[0-9][0-9][0-9]$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("x1234","^.+?[0-9][0-9][0-9]$",[]))), - <<":abc:pqr">> = iolist_to_binary(join(re:split("abc!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("x1234","^.+?[0-9][0-9][0-9]$",[]))), + <<":abc:pqr">> = iolist_to_binary(join(re:split("abc!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[trim]))), <<":abc:pqr:">> = iolist_to_binary(join(re:split("abc!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[{parts, - 2}]))), - <<":abc:pqr:">> = iolist_to_binary(join(re:split("abc!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[trim]))), + 2}]))), + <<":abc:pqr:">> = iolist_to_binary(join(re:split("abc!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[]))), - <<"!pqr=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[]))), + <<"!pqr=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[trim]))), <<"!pqr=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[{parts, - 2}]))), - <<"!pqr=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[]))), - <<"abc!=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("abc!=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[trim]))), + 2}]))), + <<"!pqr=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("!pqr=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[]))), + <<"abc!=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("abc!=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[trim]))), <<"abc!=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("abc!=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[{parts, - 2}]))), - <<"abc!=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("abc!=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[]))), - <<"abc!pqr=apquxz:ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("abc!pqr=apquxz:ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[trim]))), + 2}]))), + <<"abc!=apquxz.ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("abc!=apquxz.ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[]))), + <<"abc!pqr=apquxz:ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("abc!pqr=apquxz:ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[trim]))), <<"abc!pqr=apquxz:ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("abc!pqr=apquxz:ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[{parts, - 2}]))), - <<"abc!pqr=apquxz:ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("abc!pqr=apquxz:ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[]))), - <<"abc!pqr=apquxz.ixr.zzz.ac.ukk">> = iolist_to_binary(join(re:split("abc!pqr=apquxz.ixr.zzz.ac.ukk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[trim]))), + 2}]))), + <<"abc!pqr=apquxz:ixr.zzz.ac.uk">> = iolist_to_binary(join(re:split("abc!pqr=apquxz:ixr.zzz.ac.uk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[]))), + <<"abc!pqr=apquxz.ixr.zzz.ac.ukk">> = iolist_to_binary(join(re:split("abc!pqr=apquxz.ixr.zzz.ac.ukk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[trim]))), <<"abc!pqr=apquxz.ixr.zzz.ac.ukk">> = iolist_to_binary(join(re:split("abc!pqr=apquxz.ixr.zzz.ac.ukk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[{parts, - 2}]))), - <<"abc!pqr=apquxz.ixr.zzz.ac.ukk">> = iolist_to_binary(join(re:split("abc!pqr=apquxz.ixr.zzz.ac.ukk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[]))), - <<"Well, we need a colon: somewhere">> = iolist_to_binary(join(re:split("Well, we need a colon: somewhere",":",[trim]))), + 2}]))), + <<"abc!pqr=apquxz.ixr.zzz.ac.ukk">> = iolist_to_binary(join(re:split("abc!pqr=apquxz.ixr.zzz.ac.ukk","^([^!]+)!(.+)=apquxz\\.ixr\\.zzz\\.ac\\.uk$",[]))), + <<"Well, we need a colon: somewhere">> = iolist_to_binary(join(re:split("Well, we need a colon: somewhere",":",[trim]))), <<"Well, we need a colon: somewhere">> = iolist_to_binary(join(re:split("Well, we need a colon: somewhere",":",[{parts, - 2}]))), - <<"Well, we need a colon: somewhere">> = iolist_to_binary(join(re:split("Well, we need a colon: somewhere",":",[]))), - <<"*** Fail if we don't">> = iolist_to_binary(join(re:split("*** Fail if we don't",":",[trim]))), + 2}]))), + <<"Well, we need a colon: somewhere">> = iolist_to_binary(join(re:split("Well, we need a colon: somewhere",":",[]))), + <<"*** Fail if we don't">> = iolist_to_binary(join(re:split("*** Fail if we don't",":",[trim]))), <<"*** Fail if we don't">> = iolist_to_binary(join(re:split("*** Fail if we don't",":",[{parts, - 2}]))), - <<"*** Fail if we don't">> = iolist_to_binary(join(re:split("*** Fail if we don't",":",[]))), + 2}]))), + <<"*** Fail if we don't">> = iolist_to_binary(join(re:split("*** Fail if we don't",":",[]))), <<":0abc">> = iolist_to_binary(join(re:split("0abc","([\\da-f:]+)$",[caseless, - trim]))), + trim]))), <<":0abc:">> = iolist_to_binary(join(re:split("0abc","([\\da-f:]+)$",[caseless, {parts, - 2}]))), - <<":0abc:">> = iolist_to_binary(join(re:split("0abc","([\\da-f:]+)$",[caseless]))), + 2}]))), + <<":0abc:">> = iolist_to_binary(join(re:split("0abc","([\\da-f:]+)$",[caseless]))), <<":abc">> = iolist_to_binary(join(re:split("abc","([\\da-f:]+)$",[caseless, - trim]))), + trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abc","([\\da-f:]+)$",[caseless, {parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abc","([\\da-f:]+)$",[caseless]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abc","([\\da-f:]+)$",[caseless]))), <<":fed">> = iolist_to_binary(join(re:split("fed","([\\da-f:]+)$",[caseless, - trim]))), + trim]))), <<":fed:">> = iolist_to_binary(join(re:split("fed","([\\da-f:]+)$",[caseless, {parts, - 2}]))), - <<":fed:">> = iolist_to_binary(join(re:split("fed","([\\da-f:]+)$",[caseless]))), + 2}]))), + <<":fed:">> = iolist_to_binary(join(re:split("fed","([\\da-f:]+)$",[caseless]))), <<":E">> = iolist_to_binary(join(re:split("E","([\\da-f:]+)$",[caseless, - trim]))), + trim]))), <<":E:">> = iolist_to_binary(join(re:split("E","([\\da-f:]+)$",[caseless, {parts, - 2}]))), - <<":E:">> = iolist_to_binary(join(re:split("E","([\\da-f:]+)$",[caseless]))), + 2}]))), + <<":E:">> = iolist_to_binary(join(re:split("E","([\\da-f:]+)$",[caseless]))), <<":::">> = iolist_to_binary(join(re:split("::","([\\da-f:]+)$",[caseless, - trim]))), + trim]))), <<"::::">> = iolist_to_binary(join(re:split("::","([\\da-f:]+)$",[caseless, {parts, - 2}]))), - <<"::::">> = iolist_to_binary(join(re:split("::","([\\da-f:]+)$",[caseless]))), + 2}]))), + <<"::::">> = iolist_to_binary(join(re:split("::","([\\da-f:]+)$",[caseless]))), <<":5f03:12C0::932e">> = iolist_to_binary(join(re:split("5f03:12C0::932e","([\\da-f:]+)$",[caseless, - trim]))), + trim]))), <<":5f03:12C0::932e:">> = iolist_to_binary(join(re:split("5f03:12C0::932e","([\\da-f:]+)$",[caseless, {parts, - 2}]))), - <<":5f03:12C0::932e:">> = iolist_to_binary(join(re:split("5f03:12C0::932e","([\\da-f:]+)$",[caseless]))), + 2}]))), + <<":5f03:12C0::932e:">> = iolist_to_binary(join(re:split("5f03:12C0::932e","([\\da-f:]+)$",[caseless]))), <<"fed :def">> = iolist_to_binary(join(re:split("fed def","([\\da-f:]+)$",[caseless, - trim]))), + trim]))), <<"fed :def:">> = iolist_to_binary(join(re:split("fed def","([\\da-f:]+)$",[caseless, {parts, - 2}]))), - <<"fed :def:">> = iolist_to_binary(join(re:split("fed def","([\\da-f:]+)$",[caseless]))), + 2}]))), + <<"fed :def:">> = iolist_to_binary(join(re:split("fed def","([\\da-f:]+)$",[caseless]))), <<"Any old stu:ff">> = iolist_to_binary(join(re:split("Any old stuff","([\\da-f:]+)$",[caseless, - trim]))), + trim]))), <<"Any old stu:ff:">> = iolist_to_binary(join(re:split("Any old stuff","([\\da-f:]+)$",[caseless, {parts, - 2}]))), - <<"Any old stu:ff:">> = iolist_to_binary(join(re:split("Any old stuff","([\\da-f:]+)$",[caseless]))), + 2}]))), + <<"Any old stu:ff:">> = iolist_to_binary(join(re:split("Any old stuff","([\\da-f:]+)$",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","([\\da-f:]+)$",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","([\\da-f:]+)$",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","([\\da-f:]+)$",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","([\\da-f:]+)$",[caseless]))), <<"0zzz">> = iolist_to_binary(join(re:split("0zzz","([\\da-f:]+)$",[caseless, - trim]))), + trim]))), <<"0zzz">> = iolist_to_binary(join(re:split("0zzz","([\\da-f:]+)$",[caseless, {parts, - 2}]))), - <<"0zzz">> = iolist_to_binary(join(re:split("0zzz","([\\da-f:]+)$",[caseless]))), + 2}]))), + <<"0zzz">> = iolist_to_binary(join(re:split("0zzz","([\\da-f:]+)$",[caseless]))), <<"gzzz">> = iolist_to_binary(join(re:split("gzzz","([\\da-f:]+)$",[caseless, - trim]))), + trim]))), <<"gzzz">> = iolist_to_binary(join(re:split("gzzz","([\\da-f:]+)$",[caseless, {parts, - 2}]))), - <<"gzzz">> = iolist_to_binary(join(re:split("gzzz","([\\da-f:]+)$",[caseless]))), + 2}]))), + <<"gzzz">> = iolist_to_binary(join(re:split("gzzz","([\\da-f:]+)$",[caseless]))), <<"fed ">> = iolist_to_binary(join(re:split("fed ","([\\da-f:]+)$",[caseless, - trim]))), + trim]))), <<"fed ">> = iolist_to_binary(join(re:split("fed ","([\\da-f:]+)$",[caseless, {parts, - 2}]))), - <<"fed ">> = iolist_to_binary(join(re:split("fed ","([\\da-f:]+)$",[caseless]))), + 2}]))), + <<"fed ">> = iolist_to_binary(join(re:split("fed ","([\\da-f:]+)$",[caseless]))), <<"Any old rubbish">> = iolist_to_binary(join(re:split("Any old rubbish","([\\da-f:]+)$",[caseless, - trim]))), + trim]))), <<"Any old rubbish">> = iolist_to_binary(join(re:split("Any old rubbish","([\\da-f:]+)$",[caseless, {parts, - 2}]))), - <<"Any old rubbish">> = iolist_to_binary(join(re:split("Any old rubbish","([\\da-f:]+)$",[caseless]))), - <<":1:2:3">> = iolist_to_binary(join(re:split(".1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[trim]))), + 2}]))), + <<"Any old rubbish">> = iolist_to_binary(join(re:split("Any old rubbish","([\\da-f:]+)$",[caseless]))), + <<":1:2:3">> = iolist_to_binary(join(re:split(".1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[trim]))), <<":1:2:3:">> = iolist_to_binary(join(re:split(".1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[{parts, - 2}]))), - <<":1:2:3:">> = iolist_to_binary(join(re:split(".1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[]))), - <<":12:123:0">> = iolist_to_binary(join(re:split("A.12.123.0","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[trim]))), + 2}]))), + <<":1:2:3:">> = iolist_to_binary(join(re:split(".1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[]))), + <<":12:123:0">> = iolist_to_binary(join(re:split("A.12.123.0","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[trim]))), <<":12:123:0:">> = iolist_to_binary(join(re:split("A.12.123.0","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[{parts, - 2}]))), - <<":12:123:0:">> = iolist_to_binary(join(re:split("A.12.123.0","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[trim]))), + 2}]))), + <<":12:123:0:">> = iolist_to_binary(join(re:split("A.12.123.0","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[]))), - <<".1.2.3333">> = iolist_to_binary(join(re:split(".1.2.3333","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[]))), + <<".1.2.3333">> = iolist_to_binary(join(re:split(".1.2.3333","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[trim]))), <<".1.2.3333">> = iolist_to_binary(join(re:split(".1.2.3333","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[{parts, - 2}]))), - <<".1.2.3333">> = iolist_to_binary(join(re:split(".1.2.3333","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[]))), - <<"1.2.3">> = iolist_to_binary(join(re:split("1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[trim]))), + 2}]))), + <<".1.2.3333">> = iolist_to_binary(join(re:split(".1.2.3333","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[]))), + <<"1.2.3">> = iolist_to_binary(join(re:split("1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[trim]))), <<"1.2.3">> = iolist_to_binary(join(re:split("1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[{parts, - 2}]))), - <<"1.2.3">> = iolist_to_binary(join(re:split("1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[]))), - <<"1234.2.3">> = iolist_to_binary(join(re:split("1234.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[trim]))), + 2}]))), + <<"1.2.3">> = iolist_to_binary(join(re:split("1.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[]))), + <<"1234.2.3">> = iolist_to_binary(join(re:split("1234.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[trim]))), <<"1234.2.3">> = iolist_to_binary(join(re:split("1234.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[{parts, - 2}]))), - <<"1234.2.3">> = iolist_to_binary(join(re:split("1234.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[]))), - <<":1:non-sp1:non-sp2">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[trim]))), + 2}]))), + <<"1234.2.3">> = iolist_to_binary(join(re:split("1234.2.3","^.*\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$",[]))), + <<":1:non-sp1:non-sp2">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[trim]))), <<":1:non-sp1:non-sp2:">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[{parts, - 2}]))), - <<":1:non-sp1:non-sp2:">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[]))), - <<":1:non-sp1:non-sp2">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2 (","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[trim]))), + 2}]))), + <<":1:non-sp1:non-sp2:">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[]))), + <<":1:non-sp1:non-sp2">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2 (","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[trim]))), <<":1:non-sp1:non-sp2:">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2 (","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[{parts, - 2}]))), - <<":1:non-sp1:non-sp2:">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2 (","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[trim]))), + 2}]))), + <<":1:non-sp1:non-sp2:">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2 (","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[]))), - <<"1IN SOA non-sp1 non-sp2(">> = iolist_to_binary(join(re:split("1IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[]))), + <<"1IN SOA non-sp1 non-sp2(">> = iolist_to_binary(join(re:split("1IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[trim]))), <<"1IN SOA non-sp1 non-sp2(">> = iolist_to_binary(join(re:split("1IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[{parts, - 2}]))), - <<"1IN SOA non-sp1 non-sp2(">> = iolist_to_binary(join(re:split("1IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[]))), - <<"">> = iolist_to_binary(join(re:split("a.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))), + 2}]))), + <<"1IN SOA non-sp1 non-sp2(">> = iolist_to_binary(join(re:split("1IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[]))), + <<"">> = iolist_to_binary(join(re:split("a.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))), <<"::">> = iolist_to_binary(join(re:split("a.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("a.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))), - <<"">> = iolist_to_binary(join(re:split("Z.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("a.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))), + <<"">> = iolist_to_binary(join(re:split("Z.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))), <<"::">> = iolist_to_binary(join(re:split("Z.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("Z.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))), - <<"">> = iolist_to_binary(join(re:split("2.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("Z.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))), + <<"">> = iolist_to_binary(join(re:split("2.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))), <<"::">> = iolist_to_binary(join(re:split("2.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("2.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))), - <<":.pq-r">> = iolist_to_binary(join(re:split("ab-c.pq-r.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("2.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))), + <<":.pq-r">> = iolist_to_binary(join(re:split("ab-c.pq-r.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))), <<":.pq-r:">> = iolist_to_binary(join(re:split("ab-c.pq-r.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[{parts, - 2}]))), - <<":.pq-r:">> = iolist_to_binary(join(re:split("ab-c.pq-r.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))), - <<":.uk">> = iolist_to_binary(join(re:split("sxk.zzz.ac.uk.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))), + 2}]))), + <<":.pq-r:">> = iolist_to_binary(join(re:split("ab-c.pq-r.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))), + <<":.uk">> = iolist_to_binary(join(re:split("sxk.zzz.ac.uk.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))), <<":.uk:">> = iolist_to_binary(join(re:split("sxk.zzz.ac.uk.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[{parts, - 2}]))), - <<":.uk:">> = iolist_to_binary(join(re:split("sxk.zzz.ac.uk.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))), - <<":.y-">> = iolist_to_binary(join(re:split("x-.y-.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))), + 2}]))), + <<":.uk:">> = iolist_to_binary(join(re:split("sxk.zzz.ac.uk.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))), + <<":.y-">> = iolist_to_binary(join(re:split("x-.y-.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))), <<":.y-:">> = iolist_to_binary(join(re:split("x-.y-.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[{parts, - 2}]))), - <<":.y-:">> = iolist_to_binary(join(re:split("x-.y-.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))), + 2}]))), + <<":.y-:">> = iolist_to_binary(join(re:split("x-.y-.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))), - <<"-abc.peq.">> = iolist_to_binary(join(re:split("-abc.peq.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))), + <<"-abc.peq.">> = iolist_to_binary(join(re:split("-abc.peq.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[trim]))), <<"-abc.peq.">> = iolist_to_binary(join(re:split("-abc.peq.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[{parts, - 2}]))), - <<"-abc.peq.">> = iolist_to_binary(join(re:split("-abc.peq.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))), - <<"">> = iolist_to_binary(join(re:split("*.a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))), + 2}]))), + <<"-abc.peq.">> = iolist_to_binary(join(re:split("-abc.peq.","^[a-zA-Z\\d][a-zA-Z\\d\\-]*(\\.[a-zA-Z\\d][a-zA-z\\d\\-]*)*\\.$",[]))), + <<"">> = iolist_to_binary(join(re:split("*.a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))), <<"::::">> = iolist_to_binary(join(re:split("*.a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[{parts, - 2}]))), - <<"::::">> = iolist_to_binary(join(re:split("*.a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))), - <<":0-a">> = iolist_to_binary(join(re:split("*.b0-a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))), + 2}]))), + <<"::::">> = iolist_to_binary(join(re:split("*.a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))), + <<":0-a">> = iolist_to_binary(join(re:split("*.b0-a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))), <<":0-a:::">> = iolist_to_binary(join(re:split("*.b0-a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[{parts, - 2}]))), - <<":0-a:::">> = iolist_to_binary(join(re:split("*.b0-a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))), - <<":3-b:.c">> = iolist_to_binary(join(re:split("*.c3-b.c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))), + 2}]))), + <<":0-a:::">> = iolist_to_binary(join(re:split("*.b0-a","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))), + <<":3-b:.c">> = iolist_to_binary(join(re:split("*.c3-b.c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))), <<":3-b:.c::">> = iolist_to_binary(join(re:split("*.c3-b.c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[{parts, - 2}]))), - <<":3-b:.c::">> = iolist_to_binary(join(re:split("*.c3-b.c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))), - <<":-a:.b-c:-c">> = iolist_to_binary(join(re:split("*.c-a.b-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))), + 2}]))), + <<":3-b:.c::">> = iolist_to_binary(join(re:split("*.c3-b.c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))), + <<":-a:.b-c:-c">> = iolist_to_binary(join(re:split("*.c-a.b-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))), <<":-a:.b-c:-c:">> = iolist_to_binary(join(re:split("*.c-a.b-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[{parts, - 2}]))), - <<":-a:.b-c:-c:">> = iolist_to_binary(join(re:split("*.c-a.b-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))), + 2}]))), + <<":-a:.b-c:-c:">> = iolist_to_binary(join(re:split("*.c-a.b-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))), - <<"*.0">> = iolist_to_binary(join(re:split("*.0","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))), + <<"*.0">> = iolist_to_binary(join(re:split("*.0","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))), <<"*.0">> = iolist_to_binary(join(re:split("*.0","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[{parts, - 2}]))), - <<"*.0">> = iolist_to_binary(join(re:split("*.0","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))), - <<"*.a-">> = iolist_to_binary(join(re:split("*.a-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))), + 2}]))), + <<"*.0">> = iolist_to_binary(join(re:split("*.0","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))), + <<"*.a-">> = iolist_to_binary(join(re:split("*.a-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))), <<"*.a-">> = iolist_to_binary(join(re:split("*.a-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[{parts, - 2}]))), - <<"*.a-">> = iolist_to_binary(join(re:split("*.a-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))), - <<"*.a-b.c-">> = iolist_to_binary(join(re:split("*.a-b.c-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))), + 2}]))), + <<"*.a-">> = iolist_to_binary(join(re:split("*.a-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))), + <<"*.a-b.c-">> = iolist_to_binary(join(re:split("*.a-b.c-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))), <<"*.a-b.c-">> = iolist_to_binary(join(re:split("*.a-b.c-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[{parts, - 2}]))), - <<"*.a-b.c-">> = iolist_to_binary(join(re:split("*.a-b.c-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))), - <<"*.c-a.0-c">> = iolist_to_binary(join(re:split("*.c-a.0-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))), + 2}]))), + <<"*.a-b.c-">> = iolist_to_binary(join(re:split("*.a-b.c-","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))), + <<"*.c-a.0-c">> = iolist_to_binary(join(re:split("*.c-a.0-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[trim]))), <<"*.c-a.0-c">> = iolist_to_binary(join(re:split("*.c-a.0-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[{parts, - 2}]))), - <<"*.c-a.0-c">> = iolist_to_binary(join(re:split("*.c-a.0-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))), - <<":de:abd:e">> = iolist_to_binary(join(re:split("abde","^(?=ab(de))(abd)(e)",[trim]))), + 2}]))), + <<"*.c-a.0-c">> = iolist_to_binary(join(re:split("*.c-a.0-c","^\\*\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?(\\.[a-z]([a-z\\-\\d]*[a-z\\d]+)?)*$",[]))), + <<":de:abd:e">> = iolist_to_binary(join(re:split("abde","^(?=ab(de))(abd)(e)",[trim]))), <<":de:abd:e:">> = iolist_to_binary(join(re:split("abde","^(?=ab(de))(abd)(e)",[{parts, - 2}]))), - <<":de:abd:e:">> = iolist_to_binary(join(re:split("abde","^(?=ab(de))(abd)(e)",[]))), - <<"::abd:f">> = iolist_to_binary(join(re:split("abdf","^(?!(ab)de|x)(abd)(f)",[trim]))), + 2}]))), + <<":de:abd:e:">> = iolist_to_binary(join(re:split("abde","^(?=ab(de))(abd)(e)",[]))), + <<"::abd:f">> = iolist_to_binary(join(re:split("abdf","^(?!(ab)de|x)(abd)(f)",[trim]))), <<"::abd:f:">> = iolist_to_binary(join(re:split("abdf","^(?!(ab)de|x)(abd)(f)",[{parts, - 2}]))), - <<"::abd:f:">> = iolist_to_binary(join(re:split("abdf","^(?!(ab)de|x)(abd)(f)",[]))), - <<":abcd:cd:ab:cd">> = iolist_to_binary(join(re:split("abcd","^(?=(ab(cd)))(ab)",[trim]))), + 2}]))), + <<"::abd:f:">> = iolist_to_binary(join(re:split("abdf","^(?!(ab)de|x)(abd)(f)",[]))), + <<":abcd:cd:ab:cd">> = iolist_to_binary(join(re:split("abcd","^(?=(ab(cd)))(ab)",[trim]))), <<":abcd:cd:ab:cd">> = iolist_to_binary(join(re:split("abcd","^(?=(ab(cd)))(ab)",[{parts, - 2}]))), - <<":abcd:cd:ab:cd">> = iolist_to_binary(join(re:split("abcd","^(?=(ab(cd)))(ab)",[]))), + 2}]))), + <<":abcd:cd:ab:cd">> = iolist_to_binary(join(re:split("abcd","^(?=(ab(cd)))(ab)",[]))), <<":.d">> = iolist_to_binary(join(re:split("a.b.c.d","^[\\da-f](\\.[\\da-f])*$",[caseless, - trim]))), + trim]))), <<":.d:">> = iolist_to_binary(join(re:split("a.b.c.d","^[\\da-f](\\.[\\da-f])*$",[caseless, {parts, - 2}]))), - <<":.d:">> = iolist_to_binary(join(re:split("a.b.c.d","^[\\da-f](\\.[\\da-f])*$",[caseless]))), + 2}]))), + <<":.d:">> = iolist_to_binary(join(re:split("a.b.c.d","^[\\da-f](\\.[\\da-f])*$",[caseless]))), <<":.D">> = iolist_to_binary(join(re:split("A.B.C.D","^[\\da-f](\\.[\\da-f])*$",[caseless, - trim]))), + trim]))), <<":.D:">> = iolist_to_binary(join(re:split("A.B.C.D","^[\\da-f](\\.[\\da-f])*$",[caseless, {parts, - 2}]))), - <<":.D:">> = iolist_to_binary(join(re:split("A.B.C.D","^[\\da-f](\\.[\\da-f])*$",[caseless]))), + 2}]))), + <<":.D:">> = iolist_to_binary(join(re:split("A.B.C.D","^[\\da-f](\\.[\\da-f])*$",[caseless]))), <<":.C">> = iolist_to_binary(join(re:split("a.b.c.1.2.3.C","^[\\da-f](\\.[\\da-f])*$",[caseless, - trim]))), + trim]))), <<":.C:">> = iolist_to_binary(join(re:split("a.b.c.1.2.3.C","^[\\da-f](\\.[\\da-f])*$",[caseless, {parts, - 2}]))), - <<":.C:">> = iolist_to_binary(join(re:split("a.b.c.1.2.3.C","^[\\da-f](\\.[\\da-f])*$",[caseless]))), - <<"">> = iolist_to_binary(join(re:split("\"1234\"","^\\\".*\\\"\\s*(;.*)?$",[trim]))), + 2}]))), + <<":.C:">> = iolist_to_binary(join(re:split("a.b.c.1.2.3.C","^[\\da-f](\\.[\\da-f])*$",[caseless]))), + <<"">> = iolist_to_binary(join(re:split("\"1234\"","^\\\".*\\\"\\s*(;.*)?$",[trim]))), <<"::">> = iolist_to_binary(join(re:split("\"1234\"","^\\\".*\\\"\\s*(;.*)?$",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("\"1234\"","^\\\".*\\\"\\s*(;.*)?$",[]))), - <<":;">> = iolist_to_binary(join(re:split("\"abcd\" ;","^\\\".*\\\"\\s*(;.*)?$",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("\"1234\"","^\\\".*\\\"\\s*(;.*)?$",[]))), + <<":;">> = iolist_to_binary(join(re:split("\"abcd\" ;","^\\\".*\\\"\\s*(;.*)?$",[trim]))), <<":;:">> = iolist_to_binary(join(re:split("\"abcd\" ;","^\\\".*\\\"\\s*(;.*)?$",[{parts, - 2}]))), - <<":;:">> = iolist_to_binary(join(re:split("\"abcd\" ;","^\\\".*\\\"\\s*(;.*)?$",[]))), - <<":; rhubarb">> = iolist_to_binary(join(re:split("\"\" ; rhubarb","^\\\".*\\\"\\s*(;.*)?$",[trim]))), + 2}]))), + <<":;:">> = iolist_to_binary(join(re:split("\"abcd\" ;","^\\\".*\\\"\\s*(;.*)?$",[]))), + <<":; rhubarb">> = iolist_to_binary(join(re:split("\"\" ; rhubarb","^\\\".*\\\"\\s*(;.*)?$",[trim]))), <<":; rhubarb:">> = iolist_to_binary(join(re:split("\"\" ; rhubarb","^\\\".*\\\"\\s*(;.*)?$",[{parts, - 2}]))), - <<":; rhubarb:">> = iolist_to_binary(join(re:split("\"\" ; rhubarb","^\\\".*\\\"\\s*(;.*)?$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\\".*\\\"\\s*(;.*)?$",[trim]))), + 2}]))), + <<":; rhubarb:">> = iolist_to_binary(join(re:split("\"\" ; rhubarb","^\\\".*\\\"\\s*(;.*)?$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\\".*\\\"\\s*(;.*)?$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\\".*\\\"\\s*(;.*)?$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\\".*\\\"\\s*(;.*)?$",[]))), - <<"\"1234\" : things">> = iolist_to_binary(join(re:split("\"1234\" : things","^\\\".*\\\"\\s*(;.*)?$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\\".*\\\"\\s*(;.*)?$",[]))), + <<"\"1234\" : things">> = iolist_to_binary(join(re:split("\"1234\" : things","^\\\".*\\\"\\s*(;.*)?$",[trim]))), <<"\"1234\" : things">> = iolist_to_binary(join(re:split("\"1234\" : things","^\\\".*\\\"\\s*(;.*)?$",[{parts, - 2}]))), - <<"\"1234\" : things">> = iolist_to_binary(join(re:split("\"1234\" : things","^\\\".*\\\"\\s*(;.*)?$",[]))), - <<"">> = iolist_to_binary(join(re:split("","^$",[trim]))), + 2}]))), + <<"\"1234\" : things">> = iolist_to_binary(join(re:split("\"1234\" : things","^\\\".*\\\"\\s*(;.*)?$",[]))), + <<"">> = iolist_to_binary(join(re:split("","^$",[trim]))), <<"">> = iolist_to_binary(join(re:split("","^$",[{parts, - 2}]))), - <<"">> = iolist_to_binary(join(re:split("","^$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^$",[trim]))), + 2}]))), + <<"">> = iolist_to_binary(join(re:split("","^$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^$",[]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^$",[]))), <<"">> = iolist_to_binary(join(re:split("ab c"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ab c"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ab c"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ab c"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended]))), <<"abc">> = iolist_to_binary(join(re:split("abc"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended, - trim]))), + trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended, {parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended]))), <<"ab cde">> = iolist_to_binary(join(re:split("ab cde"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended, - trim]))), + trim]))), <<"ab cde">> = iolist_to_binary(join(re:split("ab cde"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended, {parts, - 2}]))), - <<"ab cde">> = iolist_to_binary(join(re:split("ab cde"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended]))), - <<"">> = iolist_to_binary(join(re:split("ab c","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[trim]))), + 2}]))), + <<"ab cde">> = iolist_to_binary(join(re:split("ab cde"," ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[extended]))), + <<"">> = iolist_to_binary(join(re:split("ab c","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[trim]))), <<":">> = iolist_to_binary(join(re:split("ab c","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ab c","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ab c","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[]))), - <<"ab cde">> = iolist_to_binary(join(re:split("ab cde","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[trim]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[]))), + <<"ab cde">> = iolist_to_binary(join(re:split("ab cde","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[trim]))), <<"ab cde">> = iolist_to_binary(join(re:split("ab cde","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[{parts, - 2}]))), - <<"ab cde">> = iolist_to_binary(join(re:split("ab cde","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[]))), + 2}]))), + <<"ab cde">> = iolist_to_binary(join(re:split("ab cde","(?x) ^ a (?# begins with a) b\\sc (?# then b c) $ (?# then end)",[]))), <<"">> = iolist_to_binary(join(re:split("a bcd","^ a\\ b[c ]d $",[extended, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("a bcd","^ a\\ b[c ]d $",[extended, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a bcd","^ a\\ b[c ]d $",[extended]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a bcd","^ a\\ b[c ]d $",[extended]))), <<"">> = iolist_to_binary(join(re:split("a b d","^ a\\ b[c ]d $",[extended, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("a b d","^ a\\ b[c ]d $",[extended, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a b d","^ a\\ b[c ]d $",[extended]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a b d","^ a\\ b[c ]d $",[extended]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^ a\\ b[c ]d $",[extended, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^ a\\ b[c ]d $",[extended, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^ a\\ b[c ]d $",[extended]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^ a\\ b[c ]d $",[extended]))), <<"abcd">> = iolist_to_binary(join(re:split("abcd","^ a\\ b[c ]d $",[extended, - trim]))), + trim]))), <<"abcd">> = iolist_to_binary(join(re:split("abcd","^ a\\ b[c ]d $",[extended, {parts, - 2}]))), - <<"abcd">> = iolist_to_binary(join(re:split("abcd","^ a\\ b[c ]d $",[extended]))), + 2}]))), + <<"abcd">> = iolist_to_binary(join(re:split("abcd","^ a\\ b[c ]d $",[extended]))), <<"ab d">> = iolist_to_binary(join(re:split("ab d","^ a\\ b[c ]d $",[extended, - trim]))), + trim]))), <<"ab d">> = iolist_to_binary(join(re:split("ab d","^ a\\ b[c ]d $",[extended, {parts, - 2}]))), - <<"ab d">> = iolist_to_binary(join(re:split("ab d","^ a\\ b[c ]d $",[extended]))), - <<":abc:bc:c:def:ef:f:hij:ij:j:klm:lm:m">> = iolist_to_binary(join(re:split("abcdefhijklm","^(a(b(c)))(d(e(f)))(h(i(j)))(k(l(m)))$",[trim]))), + 2}]))), + <<"ab d">> = iolist_to_binary(join(re:split("ab d","^ a\\ b[c ]d $",[extended]))), + <<":abc:bc:c:def:ef:f:hij:ij:j:klm:lm:m">> = iolist_to_binary(join(re:split("abcdefhijklm","^(a(b(c)))(d(e(f)))(h(i(j)))(k(l(m)))$",[trim]))), <<":abc:bc:c:def:ef:f:hij:ij:j:klm:lm:m:">> = iolist_to_binary(join(re:split("abcdefhijklm","^(a(b(c)))(d(e(f)))(h(i(j)))(k(l(m)))$",[{parts, - 2}]))), - <<":abc:bc:c:def:ef:f:hij:ij:j:klm:lm:m:">> = iolist_to_binary(join(re:split("abcdefhijklm","^(a(b(c)))(d(e(f)))(h(i(j)))(k(l(m)))$",[]))), + 2}]))), + <<":abc:bc:c:def:ef:f:hij:ij:j:klm:lm:m:">> = iolist_to_binary(join(re:split("abcdefhijklm","^(a(b(c)))(d(e(f)))(h(i(j)))(k(l(m)))$",[]))), ok. run2() -> - <<":bc:c:ef:f:ij:j:lm:m">> = iolist_to_binary(join(re:split("abcdefhijklm","^(?:a(b(c)))(?:d(e(f)))(?:h(i(j)))(?:k(l(m)))$",[trim]))), + <<":bc:c:ef:f:ij:j:lm:m">> = iolist_to_binary(join(re:split("abcdefhijklm","^(?:a(b(c)))(?:d(e(f)))(?:h(i(j)))(?:k(l(m)))$",[trim]))), <<":bc:c:ef:f:ij:j:lm:m:">> = iolist_to_binary(join(re:split("abcdefhijklm","^(?:a(b(c)))(?:d(e(f)))(?:h(i(j)))(?:k(l(m)))$",[{parts, - 2}]))), - <<":bc:c:ef:f:ij:j:lm:m:">> = iolist_to_binary(join(re:split("abcdefhijklm","^(?:a(b(c)))(?:d(e(f)))(?:h(i(j)))(?:k(l(m)))$",[]))), + 2}]))), + <<":bc:c:ef:f:ij:j:lm:m:">> = iolist_to_binary(join(re:split("abcdefhijklm","^(?:a(b(c)))(?:d(e(f)))(?:h(i(j)))(?:k(l(m)))$",[]))), <<"">> = iolist_to_binary(join(re:split("a+ Z0+ -","^[\\w][\\W][\\s][\\S][\\d][\\D][\\b][\\n][\\c]][\\022]",[trim]))), +","^[\\w][\\W][\\s][\\S][\\d][\\D][\\b][\\n][\\c]][\\022]",[trim]))), <<":">> = iolist_to_binary(join(re:split("a+ Z0+ ","^[\\w][\\W][\\s][\\S][\\d][\\D][\\b][\\n][\\c]][\\022]",[{parts, - 2}]))), + 2}]))), <<":">> = iolist_to_binary(join(re:split("a+ Z0+ -","^[\\w][\\W][\\s][\\S][\\d][\\D][\\b][\\n][\\c]][\\022]",[]))), - <<"">> = iolist_to_binary(join(re:split(".^$(*+)|{?,?}","^[.^$|()*+?{,}]+",[trim]))), +","^[\\w][\\W][\\s][\\S][\\d][\\D][\\b][\\n][\\c]][\\022]",[]))), + <<"">> = iolist_to_binary(join(re:split(".^$(*+)|{?,?}","^[.^$|()*+?{,}]+",[trim]))), <<":">> = iolist_to_binary(join(re:split(".^$(*+)|{?,?}","^[.^$|()*+?{,}]+",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split(".^$(*+)|{?,?}","^[.^$|()*+?{,}]+",[]))), - <<"">> = iolist_to_binary(join(re:split("z","^a*\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split(".^$(*+)|{?,?}","^[.^$|()*+?{,}]+",[]))), + <<"">> = iolist_to_binary(join(re:split("z","^a*\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("z","^a*\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("z","^a*\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("az","^a*\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("z","^a*\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("az","^a*\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("az","^a*\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("az","^a*\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaz","^a*\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("az","^a*\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaz","^a*\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaz","^a*\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaz","^a*\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("a","^a*\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaz","^a*\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("a","^a*\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("a","^a*\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","^a*\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("aa","^a*\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","^a*\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("aa","^a*\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("aa","^a*\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aa","^a*\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaa","^a*\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aa","^a*\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaa","^a*\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaa","^a*\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaa","^a*\\w",[]))), - <<":+">> = iolist_to_binary(join(re:split("a+","^a*\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaa","^a*\\w",[]))), + <<":+">> = iolist_to_binary(join(re:split("a+","^a*\\w",[trim]))), <<":+">> = iolist_to_binary(join(re:split("a+","^a*\\w",[{parts, - 2}]))), - <<":+">> = iolist_to_binary(join(re:split("a+","^a*\\w",[]))), - <<":+">> = iolist_to_binary(join(re:split("aa+","^a*\\w",[trim]))), + 2}]))), + <<":+">> = iolist_to_binary(join(re:split("a+","^a*\\w",[]))), + <<":+">> = iolist_to_binary(join(re:split("aa+","^a*\\w",[trim]))), <<":+">> = iolist_to_binary(join(re:split("aa+","^a*\\w",[{parts, - 2}]))), - <<":+">> = iolist_to_binary(join(re:split("aa+","^a*\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("z","^a*?\\w",[trim]))), + 2}]))), + <<":+">> = iolist_to_binary(join(re:split("aa+","^a*\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("z","^a*?\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("z","^a*?\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("z","^a*?\\w",[]))), - <<":z">> = iolist_to_binary(join(re:split("az","^a*?\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("z","^a*?\\w",[]))), + <<":z">> = iolist_to_binary(join(re:split("az","^a*?\\w",[trim]))), <<":z">> = iolist_to_binary(join(re:split("az","^a*?\\w",[{parts, - 2}]))), - <<":z">> = iolist_to_binary(join(re:split("az","^a*?\\w",[]))), - <<":aaz">> = iolist_to_binary(join(re:split("aaaz","^a*?\\w",[trim]))), + 2}]))), + <<":z">> = iolist_to_binary(join(re:split("az","^a*?\\w",[]))), + <<":aaz">> = iolist_to_binary(join(re:split("aaaz","^a*?\\w",[trim]))), <<":aaz">> = iolist_to_binary(join(re:split("aaaz","^a*?\\w",[{parts, - 2}]))), - <<":aaz">> = iolist_to_binary(join(re:split("aaaz","^a*?\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("a","^a*?\\w",[trim]))), + 2}]))), + <<":aaz">> = iolist_to_binary(join(re:split("aaaz","^a*?\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("a","^a*?\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("a","^a*?\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","^a*?\\w",[]))), - <<":a">> = iolist_to_binary(join(re:split("aa","^a*?\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","^a*?\\w",[]))), + <<":a">> = iolist_to_binary(join(re:split("aa","^a*?\\w",[trim]))), <<":a">> = iolist_to_binary(join(re:split("aa","^a*?\\w",[{parts, - 2}]))), - <<":a">> = iolist_to_binary(join(re:split("aa","^a*?\\w",[]))), - <<":aaa">> = iolist_to_binary(join(re:split("aaaa","^a*?\\w",[trim]))), + 2}]))), + <<":a">> = iolist_to_binary(join(re:split("aa","^a*?\\w",[]))), + <<":aaa">> = iolist_to_binary(join(re:split("aaaa","^a*?\\w",[trim]))), <<":aaa">> = iolist_to_binary(join(re:split("aaaa","^a*?\\w",[{parts, - 2}]))), - <<":aaa">> = iolist_to_binary(join(re:split("aaaa","^a*?\\w",[]))), - <<":+">> = iolist_to_binary(join(re:split("a+","^a*?\\w",[trim]))), + 2}]))), + <<":aaa">> = iolist_to_binary(join(re:split("aaaa","^a*?\\w",[]))), + <<":+">> = iolist_to_binary(join(re:split("a+","^a*?\\w",[trim]))), <<":+">> = iolist_to_binary(join(re:split("a+","^a*?\\w",[{parts, - 2}]))), - <<":+">> = iolist_to_binary(join(re:split("a+","^a*?\\w",[]))), - <<":a+">> = iolist_to_binary(join(re:split("aa+","^a*?\\w",[trim]))), + 2}]))), + <<":+">> = iolist_to_binary(join(re:split("a+","^a*?\\w",[]))), + <<":a+">> = iolist_to_binary(join(re:split("aa+","^a*?\\w",[trim]))), <<":a+">> = iolist_to_binary(join(re:split("aa+","^a*?\\w",[{parts, - 2}]))), - <<":a+">> = iolist_to_binary(join(re:split("aa+","^a*?\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("az","^a+\\w",[trim]))), + 2}]))), + <<":a+">> = iolist_to_binary(join(re:split("aa+","^a*?\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("az","^a+\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("az","^a+\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("az","^a+\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaz","^a+\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("az","^a+\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaz","^a+\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaz","^a+\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaz","^a+\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("aa","^a+\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaz","^a+\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("aa","^a+\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("aa","^a+\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aa","^a+\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaa","^a+\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aa","^a+\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaa","^a+\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaa","^a+\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaa","^a+\\w",[]))), - <<":+">> = iolist_to_binary(join(re:split("aa+","^a+\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaa","^a+\\w",[]))), + <<":+">> = iolist_to_binary(join(re:split("aa+","^a+\\w",[trim]))), <<":+">> = iolist_to_binary(join(re:split("aa+","^a+\\w",[{parts, - 2}]))), - <<":+">> = iolist_to_binary(join(re:split("aa+","^a+\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("az","^a+?\\w",[trim]))), + 2}]))), + <<":+">> = iolist_to_binary(join(re:split("aa+","^a+\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("az","^a+?\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("az","^a+?\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("az","^a+?\\w",[]))), - <<":az">> = iolist_to_binary(join(re:split("aaaz","^a+?\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("az","^a+?\\w",[]))), + <<":az">> = iolist_to_binary(join(re:split("aaaz","^a+?\\w",[trim]))), <<":az">> = iolist_to_binary(join(re:split("aaaz","^a+?\\w",[{parts, - 2}]))), - <<":az">> = iolist_to_binary(join(re:split("aaaz","^a+?\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("aa","^a+?\\w",[trim]))), + 2}]))), + <<":az">> = iolist_to_binary(join(re:split("aaaz","^a+?\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("aa","^a+?\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("aa","^a+?\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aa","^a+?\\w",[]))), - <<":aa">> = iolist_to_binary(join(re:split("aaaa","^a+?\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aa","^a+?\\w",[]))), + <<":aa">> = iolist_to_binary(join(re:split("aaaa","^a+?\\w",[trim]))), <<":aa">> = iolist_to_binary(join(re:split("aaaa","^a+?\\w",[{parts, - 2}]))), - <<":aa">> = iolist_to_binary(join(re:split("aaaa","^a+?\\w",[]))), - <<":+">> = iolist_to_binary(join(re:split("aa+","^a+?\\w",[trim]))), + 2}]))), + <<":aa">> = iolist_to_binary(join(re:split("aaaa","^a+?\\w",[]))), + <<":+">> = iolist_to_binary(join(re:split("aa+","^a+?\\w",[trim]))), <<":+">> = iolist_to_binary(join(re:split("aa+","^a+?\\w",[{parts, - 2}]))), - <<":+">> = iolist_to_binary(join(re:split("aa+","^a+?\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("1234567890","^\\d{8}\\w{2,}",[trim]))), + 2}]))), + <<":+">> = iolist_to_binary(join(re:split("aa+","^a+?\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("1234567890","^\\d{8}\\w{2,}",[trim]))), <<":">> = iolist_to_binary(join(re:split("1234567890","^\\d{8}\\w{2,}",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("1234567890","^\\d{8}\\w{2,}",[]))), - <<"">> = iolist_to_binary(join(re:split("12345678ab","^\\d{8}\\w{2,}",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("1234567890","^\\d{8}\\w{2,}",[]))), + <<"">> = iolist_to_binary(join(re:split("12345678ab","^\\d{8}\\w{2,}",[trim]))), <<":">> = iolist_to_binary(join(re:split("12345678ab","^\\d{8}\\w{2,}",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("12345678ab","^\\d{8}\\w{2,}",[]))), - <<"">> = iolist_to_binary(join(re:split("12345678__","^\\d{8}\\w{2,}",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("12345678ab","^\\d{8}\\w{2,}",[]))), + <<"">> = iolist_to_binary(join(re:split("12345678__","^\\d{8}\\w{2,}",[trim]))), <<":">> = iolist_to_binary(join(re:split("12345678__","^\\d{8}\\w{2,}",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("12345678__","^\\d{8}\\w{2,}",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\d{8}\\w{2,}",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("12345678__","^\\d{8}\\w{2,}",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\d{8}\\w{2,}",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\d{8}\\w{2,}",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\d{8}\\w{2,}",[]))), - <<"1234567">> = iolist_to_binary(join(re:split("1234567","^\\d{8}\\w{2,}",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\d{8}\\w{2,}",[]))), + <<"1234567">> = iolist_to_binary(join(re:split("1234567","^\\d{8}\\w{2,}",[trim]))), <<"1234567">> = iolist_to_binary(join(re:split("1234567","^\\d{8}\\w{2,}",[{parts, - 2}]))), - <<"1234567">> = iolist_to_binary(join(re:split("1234567","^\\d{8}\\w{2,}",[]))), - <<"">> = iolist_to_binary(join(re:split("uoie","^[aeiou\\d]{4,5}$",[trim]))), + 2}]))), + <<"1234567">> = iolist_to_binary(join(re:split("1234567","^\\d{8}\\w{2,}",[]))), + <<"">> = iolist_to_binary(join(re:split("uoie","^[aeiou\\d]{4,5}$",[trim]))), <<":">> = iolist_to_binary(join(re:split("uoie","^[aeiou\\d]{4,5}$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("uoie","^[aeiou\\d]{4,5}$",[]))), - <<"">> = iolist_to_binary(join(re:split("1234","^[aeiou\\d]{4,5}$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("uoie","^[aeiou\\d]{4,5}$",[]))), + <<"">> = iolist_to_binary(join(re:split("1234","^[aeiou\\d]{4,5}$",[trim]))), <<":">> = iolist_to_binary(join(re:split("1234","^[aeiou\\d]{4,5}$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("1234","^[aeiou\\d]{4,5}$",[]))), - <<"">> = iolist_to_binary(join(re:split("12345","^[aeiou\\d]{4,5}$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("1234","^[aeiou\\d]{4,5}$",[]))), + <<"">> = iolist_to_binary(join(re:split("12345","^[aeiou\\d]{4,5}$",[trim]))), <<":">> = iolist_to_binary(join(re:split("12345","^[aeiou\\d]{4,5}$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("12345","^[aeiou\\d]{4,5}$",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaaa","^[aeiou\\d]{4,5}$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("12345","^[aeiou\\d]{4,5}$",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaaa","^[aeiou\\d]{4,5}$",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaaa","^[aeiou\\d]{4,5}$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaaa","^[aeiou\\d]{4,5}$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[aeiou\\d]{4,5}$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaaa","^[aeiou\\d]{4,5}$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[aeiou\\d]{4,5}$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[aeiou\\d]{4,5}$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[aeiou\\d]{4,5}$",[]))), - <<"123456">> = iolist_to_binary(join(re:split("123456","^[aeiou\\d]{4,5}$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[aeiou\\d]{4,5}$",[]))), + <<"123456">> = iolist_to_binary(join(re:split("123456","^[aeiou\\d]{4,5}$",[trim]))), <<"123456">> = iolist_to_binary(join(re:split("123456","^[aeiou\\d]{4,5}$",[{parts, - 2}]))), - <<"123456">> = iolist_to_binary(join(re:split("123456","^[aeiou\\d]{4,5}$",[]))), - <<"">> = iolist_to_binary(join(re:split("uoie","^[aeiou\\d]{4,5}?",[trim]))), + 2}]))), + <<"123456">> = iolist_to_binary(join(re:split("123456","^[aeiou\\d]{4,5}$",[]))), + <<"">> = iolist_to_binary(join(re:split("uoie","^[aeiou\\d]{4,5}?",[trim]))), <<":">> = iolist_to_binary(join(re:split("uoie","^[aeiou\\d]{4,5}?",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("uoie","^[aeiou\\d]{4,5}?",[]))), - <<"">> = iolist_to_binary(join(re:split("1234","^[aeiou\\d]{4,5}?",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("uoie","^[aeiou\\d]{4,5}?",[]))), + <<"">> = iolist_to_binary(join(re:split("1234","^[aeiou\\d]{4,5}?",[trim]))), <<":">> = iolist_to_binary(join(re:split("1234","^[aeiou\\d]{4,5}?",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("1234","^[aeiou\\d]{4,5}?",[]))), - <<":5">> = iolist_to_binary(join(re:split("12345","^[aeiou\\d]{4,5}?",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("1234","^[aeiou\\d]{4,5}?",[]))), + <<":5">> = iolist_to_binary(join(re:split("12345","^[aeiou\\d]{4,5}?",[trim]))), <<":5">> = iolist_to_binary(join(re:split("12345","^[aeiou\\d]{4,5}?",[{parts, - 2}]))), - <<":5">> = iolist_to_binary(join(re:split("12345","^[aeiou\\d]{4,5}?",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaaaa","^[aeiou\\d]{4,5}?",[trim]))), + 2}]))), + <<":5">> = iolist_to_binary(join(re:split("12345","^[aeiou\\d]{4,5}?",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaaaa","^[aeiou\\d]{4,5}?",[trim]))), <<":a">> = iolist_to_binary(join(re:split("aaaaa","^[aeiou\\d]{4,5}?",[{parts, - 2}]))), - <<":a">> = iolist_to_binary(join(re:split("aaaaa","^[aeiou\\d]{4,5}?",[]))), - <<":56">> = iolist_to_binary(join(re:split("123456","^[aeiou\\d]{4,5}?",[trim]))), + 2}]))), + <<":a">> = iolist_to_binary(join(re:split("aaaaa","^[aeiou\\d]{4,5}?",[]))), + <<":56">> = iolist_to_binary(join(re:split("123456","^[aeiou\\d]{4,5}?",[trim]))), <<":56">> = iolist_to_binary(join(re:split("123456","^[aeiou\\d]{4,5}?",[{parts, - 2}]))), - <<":56">> = iolist_to_binary(join(re:split("123456","^[aeiou\\d]{4,5}?",[]))), - <<":abc:abc">> = iolist_to_binary(join(re:split("abc=abcabc","\\A(abc|def)=(\\1){2,3}\\Z",[trim]))), + 2}]))), + <<":56">> = iolist_to_binary(join(re:split("123456","^[aeiou\\d]{4,5}?",[]))), + <<":abc:abc">> = iolist_to_binary(join(re:split("abc=abcabc","\\A(abc|def)=(\\1){2,3}\\Z",[trim]))), <<":abc:abc:">> = iolist_to_binary(join(re:split("abc=abcabc","\\A(abc|def)=(\\1){2,3}\\Z",[{parts, - 2}]))), - <<":abc:abc:">> = iolist_to_binary(join(re:split("abc=abcabc","\\A(abc|def)=(\\1){2,3}\\Z",[]))), - <<":def:def">> = iolist_to_binary(join(re:split("def=defdefdef","\\A(abc|def)=(\\1){2,3}\\Z",[trim]))), + 2}]))), + <<":abc:abc:">> = iolist_to_binary(join(re:split("abc=abcabc","\\A(abc|def)=(\\1){2,3}\\Z",[]))), + <<":def:def">> = iolist_to_binary(join(re:split("def=defdefdef","\\A(abc|def)=(\\1){2,3}\\Z",[trim]))), <<":def:def:">> = iolist_to_binary(join(re:split("def=defdefdef","\\A(abc|def)=(\\1){2,3}\\Z",[{parts, - 2}]))), - <<":def:def:">> = iolist_to_binary(join(re:split("def=defdefdef","\\A(abc|def)=(\\1){2,3}\\Z",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\A(abc|def)=(\\1){2,3}\\Z",[trim]))), + 2}]))), + <<":def:def:">> = iolist_to_binary(join(re:split("def=defdefdef","\\A(abc|def)=(\\1){2,3}\\Z",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\A(abc|def)=(\\1){2,3}\\Z",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\A(abc|def)=(\\1){2,3}\\Z",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\A(abc|def)=(\\1){2,3}\\Z",[]))), - <<"abc=defdef">> = iolist_to_binary(join(re:split("abc=defdef","\\A(abc|def)=(\\1){2,3}\\Z",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\A(abc|def)=(\\1){2,3}\\Z",[]))), + <<"abc=defdef">> = iolist_to_binary(join(re:split("abc=defdef","\\A(abc|def)=(\\1){2,3}\\Z",[trim]))), <<"abc=defdef">> = iolist_to_binary(join(re:split("abc=defdef","\\A(abc|def)=(\\1){2,3}\\Z",[{parts, - 2}]))), - <<"abc=defdef">> = iolist_to_binary(join(re:split("abc=defdef","\\A(abc|def)=(\\1){2,3}\\Z",[]))), - <<":a:b:c:d:e:f:g:h:i:j:k:cd">> = iolist_to_binary(join(re:split("abcdefghijkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$",[trim]))), + 2}]))), + <<"abc=defdef">> = iolist_to_binary(join(re:split("abc=defdef","\\A(abc|def)=(\\1){2,3}\\Z",[]))), + <<":a:b:c:d:e:f:g:h:i:j:k:cd">> = iolist_to_binary(join(re:split("abcdefghijkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$",[trim]))), <<":a:b:c:d:e:f:g:h:i:j:k:cd:">> = iolist_to_binary(join(re:split("abcdefghijkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$",[{parts, - 2}]))), - <<":a:b:c:d:e:f:g:h:i:j:k:cd:">> = iolist_to_binary(join(re:split("abcdefghijkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$",[]))), - <<":a:b:c:d:e:f:g:h:i:j:k:cd">> = iolist_to_binary(join(re:split("abcdefghijkkkkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$",[trim]))), + 2}]))), + <<":a:b:c:d:e:f:g:h:i:j:k:cd:">> = iolist_to_binary(join(re:split("abcdefghijkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$",[]))), + <<":a:b:c:d:e:f:g:h:i:j:k:cd">> = iolist_to_binary(join(re:split("abcdefghijkkkkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$",[trim]))), <<":a:b:c:d:e:f:g:h:i:j:k:cd:">> = iolist_to_binary(join(re:split("abcdefghijkkkkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$",[{parts, - 2}]))), - <<":a:b:c:d:e:f:g:h:i:j:k:cd:">> = iolist_to_binary(join(re:split("abcdefghijkkkkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$",[]))), - <<":cataract:aract:ract::3">> = iolist_to_binary(join(re:split("cataract cataract23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[trim]))), + 2}]))), + <<":a:b:c:d:e:f:g:h:i:j:k:cd:">> = iolist_to_binary(join(re:split("abcdefghijkkkkcda2","^(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\11*(\\3\\4)\\1(?#)2$",[]))), + <<":cataract:aract:ract::3">> = iolist_to_binary(join(re:split("cataract cataract23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[trim]))), <<":cataract:aract:ract::3:">> = iolist_to_binary(join(re:split("cataract cataract23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[{parts, - 2}]))), - <<":cataract:aract:ract::3:">> = iolist_to_binary(join(re:split("cataract cataract23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[]))), - <<":catatonic:atonic:tonic::3">> = iolist_to_binary(join(re:split("catatonic catatonic23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[trim]))), + 2}]))), + <<":cataract:aract:ract::3:">> = iolist_to_binary(join(re:split("cataract cataract23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[]))), + <<":catatonic:atonic:tonic::3">> = iolist_to_binary(join(re:split("catatonic catatonic23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[trim]))), <<":catatonic:atonic:tonic::3:">> = iolist_to_binary(join(re:split("catatonic catatonic23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[{parts, - 2}]))), - <<":catatonic:atonic:tonic::3:">> = iolist_to_binary(join(re:split("catatonic catatonic23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[]))), - <<":caterpillar:erpillar:::3">> = iolist_to_binary(join(re:split("caterpillar caterpillar23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[trim]))), + 2}]))), + <<":catatonic:atonic:tonic::3:">> = iolist_to_binary(join(re:split("catatonic catatonic23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[]))), + <<":caterpillar:erpillar:::3">> = iolist_to_binary(join(re:split("caterpillar caterpillar23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[trim]))), <<":caterpillar:erpillar:::3:">> = iolist_to_binary(join(re:split("caterpillar caterpillar23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[{parts, - 2}]))), - <<":caterpillar:erpillar:::3:">> = iolist_to_binary(join(re:split("caterpillar caterpillar23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[]))), - <<":abcd::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 01 12:33:02 1997","^From +([^ ]+) +[a-zA-Z][a-zA-Z][a-zA-Z] +[a-zA-Z][a-zA-Z][a-zA-Z] +[0-9]?[0-9] +[0-9][0-9]:[0-9][0-9]",[trim]))), + 2}]))), + <<":caterpillar:erpillar:::3:">> = iolist_to_binary(join(re:split("caterpillar caterpillar23","(cat(a(ract|tonic)|erpillar)) \\1()2(3)",[]))), + <<":abcd::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 01 12:33:02 1997","^From +([^ ]+) +[a-zA-Z][a-zA-Z][a-zA-Z] +[a-zA-Z][a-zA-Z][a-zA-Z] +[0-9]?[0-9] +[0-9][0-9]:[0-9][0-9]",[trim]))), <<":abcd::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 01 12:33:02 1997","^From +([^ ]+) +[a-zA-Z][a-zA-Z][a-zA-Z] +[a-zA-Z][a-zA-Z][a-zA-Z] +[0-9]?[0-9] +[0-9][0-9]:[0-9][0-9]",[{parts, - 2}]))), - <<":abcd::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 01 12:33:02 1997","^From +([^ ]+) +[a-zA-Z][a-zA-Z][a-zA-Z] +[a-zA-Z][a-zA-Z][a-zA-Z] +[0-9]?[0-9] +[0-9][0-9]:[0-9][0-9]",[]))), - <<":Sep ::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[trim]))), + 2}]))), + <<":abcd::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 01 12:33:02 1997","^From +([^ ]+) +[a-zA-Z][a-zA-Z][a-zA-Z] +[a-zA-Z][a-zA-Z][a-zA-Z] +[0-9]?[0-9] +[0-9][0-9]:[0-9][0-9]",[]))), + <<":Sep ::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[trim]))), <<":Sep ::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[{parts, - 2}]))), - <<":Sep ::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[]))), - <<":Sep ::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 1 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[trim]))), + 2}]))), + <<":Sep ::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[]))), + <<":Sep ::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 1 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[trim]))), <<":Sep ::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 1 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[{parts, - 2}]))), - <<":Sep ::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 1 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[trim]))), + 2}]))), + <<":Sep ::02 1997">> = iolist_to_binary(join(re:split("From abcd Mon Sep 1 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[]))), - <<"From abcd Sep 01 12:33:02 1997">> = iolist_to_binary(join(re:split("From abcd Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[]))), + <<"From abcd Sep 01 12:33:02 1997">> = iolist_to_binary(join(re:split("From abcd Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[trim]))), <<"From abcd Sep 01 12:33:02 1997">> = iolist_to_binary(join(re:split("From abcd Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[{parts, - 2}]))), - <<"From abcd Sep 01 12:33:02 1997">> = iolist_to_binary(join(re:split("From abcd Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[]))), + 2}]))), + <<"From abcd Sep 01 12:33:02 1997">> = iolist_to_binary(join(re:split("From abcd Sep 01 12:33:02 1997","^From\\s+\\S+\\s+([a-zA-Z]{3}\\s+){2}\\d{1,2}\\s+\\d\\d:\\d\\d",[]))), <<"">> = iolist_to_binary(join(re:split("12 -34","^12.34",[dotall,trim]))), +34","^12.34",[dotall,trim]))), <<":">> = iolist_to_binary(join(re:split("12 -34","^12.34",[dotall,{parts,2}]))), +34","^12.34",[dotall,{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("12 -34","^12.34",[dotall]))), +34","^12.34",[dotall]))), <<"">> = iolist_to_binary(join(re:split("12
34","^12.34",[dotall, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("12
34","^12.34",[dotall, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("12
34","^12.34",[dotall]))), - <<"the quick : fox">> = iolist_to_binary(join(re:split("the quick brown fox","\\w+(?=\\t)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("12
34","^12.34",[dotall]))), + <<"the quick : fox">> = iolist_to_binary(join(re:split("the quick brown fox","\\w+(?=\\t)",[trim]))), <<"the quick : fox">> = iolist_to_binary(join(re:split("the quick brown fox","\\w+(?=\\t)",[{parts, - 2}]))), - <<"the quick : fox">> = iolist_to_binary(join(re:split("the quick brown fox","\\w+(?=\\t)",[]))), - <<"foobar is :lish see?">> = iolist_to_binary(join(re:split("foobar is foolish see?","foo(?!bar)(.*)",[trim]))), + 2}]))), + <<"the quick : fox">> = iolist_to_binary(join(re:split("the quick brown fox","\\w+(?=\\t)",[]))), + <<"foobar is :lish see?">> = iolist_to_binary(join(re:split("foobar is foolish see?","foo(?!bar)(.*)",[trim]))), <<"foobar is :lish see?:">> = iolist_to_binary(join(re:split("foobar is foolish see?","foo(?!bar)(.*)",[{parts, - 2}]))), - <<"foobar is :lish see?:">> = iolist_to_binary(join(re:split("foobar is foolish see?","foo(?!bar)(.*)",[]))), - <<"foobar c: etc">> = iolist_to_binary(join(re:split("foobar crowbar etc","(?:(?!foo)...|^.{0,2})bar(.*)",[trim]))), + 2}]))), + <<"foobar is :lish see?:">> = iolist_to_binary(join(re:split("foobar is foolish see?","foo(?!bar)(.*)",[]))), + <<"foobar c: etc">> = iolist_to_binary(join(re:split("foobar crowbar etc","(?:(?!foo)...|^.{0,2})bar(.*)",[trim]))), <<"foobar c: etc:">> = iolist_to_binary(join(re:split("foobar crowbar etc","(?:(?!foo)...|^.{0,2})bar(.*)",[{parts, - 2}]))), - <<"foobar c: etc:">> = iolist_to_binary(join(re:split("foobar crowbar etc","(?:(?!foo)...|^.{0,2})bar(.*)",[]))), - <<":rel">> = iolist_to_binary(join(re:split("barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[trim]))), + 2}]))), + <<"foobar c: etc:">> = iolist_to_binary(join(re:split("foobar crowbar etc","(?:(?!foo)...|^.{0,2})bar(.*)",[]))), + <<":rel">> = iolist_to_binary(join(re:split("barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[trim]))), <<":rel:">> = iolist_to_binary(join(re:split("barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[{parts, - 2}]))), - <<":rel:">> = iolist_to_binary(join(re:split("barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[]))), - <<":rel">> = iolist_to_binary(join(re:split("2barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[trim]))), + 2}]))), + <<":rel:">> = iolist_to_binary(join(re:split("barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[]))), + <<":rel">> = iolist_to_binary(join(re:split("2barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[trim]))), <<":rel:">> = iolist_to_binary(join(re:split("2barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[{parts, - 2}]))), - <<":rel:">> = iolist_to_binary(join(re:split("2barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[]))), - <<":rel">> = iolist_to_binary(join(re:split("A barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[trim]))), + 2}]))), + <<":rel:">> = iolist_to_binary(join(re:split("2barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[]))), + <<":rel">> = iolist_to_binary(join(re:split("A barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[trim]))), <<":rel:">> = iolist_to_binary(join(re:split("A barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[{parts, - 2}]))), - <<":rel:">> = iolist_to_binary(join(re:split("A barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[]))), - <<":abc:456">> = iolist_to_binary(join(re:split("abc456","^(\\D*)(?=\\d)(?!123)",[trim]))), + 2}]))), + <<":rel:">> = iolist_to_binary(join(re:split("A barrel","(?:(?!foo)...|^.{0,2})bar(.*)",[]))), + <<":abc:456">> = iolist_to_binary(join(re:split("abc456","^(\\D*)(?=\\d)(?!123)",[trim]))), <<":abc:456">> = iolist_to_binary(join(re:split("abc456","^(\\D*)(?=\\d)(?!123)",[{parts, - 2}]))), - <<":abc:456">> = iolist_to_binary(join(re:split("abc456","^(\\D*)(?=\\d)(?!123)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\D*)(?=\\d)(?!123)",[trim]))), + 2}]))), + <<":abc:456">> = iolist_to_binary(join(re:split("abc456","^(\\D*)(?=\\d)(?!123)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\D*)(?=\\d)(?!123)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\D*)(?=\\d)(?!123)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\D*)(?=\\d)(?!123)",[]))), - <<"abc123">> = iolist_to_binary(join(re:split("abc123","^(\\D*)(?=\\d)(?!123)",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\D*)(?=\\d)(?!123)",[]))), + <<"abc123">> = iolist_to_binary(join(re:split("abc123","^(\\D*)(?=\\d)(?!123)",[trim]))), <<"abc123">> = iolist_to_binary(join(re:split("abc123","^(\\D*)(?=\\d)(?!123)",[{parts, - 2}]))), - <<"abc123">> = iolist_to_binary(join(re:split("abc123","^(\\D*)(?=\\d)(?!123)",[]))), + 2}]))), + <<"abc123">> = iolist_to_binary(join(re:split("abc123","^(\\D*)(?=\\d)(?!123)",[]))), ok. run3() -> <<"">> = iolist_to_binary(join(re:split("1234","^1234(?# test newlines - inside)",[trim]))), + inside)",[trim]))), <<":">> = iolist_to_binary(join(re:split("1234","^1234(?# test newlines - inside)",[{parts,2}]))), + inside)",[{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("1234","^1234(?# test newlines - inside)",[]))), + inside)",[]))), <<"">> = iolist_to_binary(join(re:split("1234","^1234 #comment in extended re - ",[extended,trim]))), + ",[extended,trim]))), <<":">> = iolist_to_binary(join(re:split("1234","^1234 #comment in extended re - ",[extended,{parts,2}]))), + ",[extended,{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("1234","^1234 #comment in extended re - ",[extended]))), + ",[extended]))), <<"">> = iolist_to_binary(join(re:split("abcd","#rhubarb - abcd",[extended,trim]))), + abcd",[extended,trim]))), <<":">> = iolist_to_binary(join(re:split("abcd","#rhubarb - abcd",[extended,{parts,2}]))), + abcd",[extended,{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("abcd","#rhubarb - abcd",[extended]))), + abcd",[extended]))), <<"">> = iolist_to_binary(join(re:split("abcd","^abcd#rhubarb",[extended, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("abcd","^abcd#rhubarb",[extended, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abcd","^abcd#rhubarb",[extended]))), - <<":a:b">> = iolist_to_binary(join(re:split("aaab","^(a)\\1{2,3}(.)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abcd","^abcd#rhubarb",[extended]))), + <<":a:b">> = iolist_to_binary(join(re:split("aaab","^(a)\\1{2,3}(.)",[trim]))), <<":a:b:">> = iolist_to_binary(join(re:split("aaab","^(a)\\1{2,3}(.)",[{parts, - 2}]))), - <<":a:b:">> = iolist_to_binary(join(re:split("aaab","^(a)\\1{2,3}(.)",[]))), - <<":a:b">> = iolist_to_binary(join(re:split("aaaab","^(a)\\1{2,3}(.)",[trim]))), + 2}]))), + <<":a:b:">> = iolist_to_binary(join(re:split("aaab","^(a)\\1{2,3}(.)",[]))), + <<":a:b">> = iolist_to_binary(join(re:split("aaaab","^(a)\\1{2,3}(.)",[trim]))), <<":a:b:">> = iolist_to_binary(join(re:split("aaaab","^(a)\\1{2,3}(.)",[{parts, - 2}]))), - <<":a:b:">> = iolist_to_binary(join(re:split("aaaab","^(a)\\1{2,3}(.)",[]))), - <<":a:a:b">> = iolist_to_binary(join(re:split("aaaaab","^(a)\\1{2,3}(.)",[trim]))), + 2}]))), + <<":a:b:">> = iolist_to_binary(join(re:split("aaaab","^(a)\\1{2,3}(.)",[]))), + <<":a:a:b">> = iolist_to_binary(join(re:split("aaaaab","^(a)\\1{2,3}(.)",[trim]))), <<":a:a:b">> = iolist_to_binary(join(re:split("aaaaab","^(a)\\1{2,3}(.)",[{parts, - 2}]))), - <<":a:a:b">> = iolist_to_binary(join(re:split("aaaaab","^(a)\\1{2,3}(.)",[]))), - <<":a:a:ab">> = iolist_to_binary(join(re:split("aaaaaab","^(a)\\1{2,3}(.)",[trim]))), + 2}]))), + <<":a:a:b">> = iolist_to_binary(join(re:split("aaaaab","^(a)\\1{2,3}(.)",[]))), + <<":a:a:ab">> = iolist_to_binary(join(re:split("aaaaaab","^(a)\\1{2,3}(.)",[trim]))), <<":a:a:ab">> = iolist_to_binary(join(re:split("aaaaaab","^(a)\\1{2,3}(.)",[{parts, - 2}]))), - <<":a:a:ab">> = iolist_to_binary(join(re:split("aaaaaab","^(a)\\1{2,3}(.)",[]))), - <<"the ">> = iolist_to_binary(join(re:split("the abc","(?!^)abc",[trim]))), + 2}]))), + <<":a:a:ab">> = iolist_to_binary(join(re:split("aaaaaab","^(a)\\1{2,3}(.)",[]))), + <<"the ">> = iolist_to_binary(join(re:split("the abc","(?!^)abc",[trim]))), <<"the :">> = iolist_to_binary(join(re:split("the abc","(?!^)abc",[{parts, - 2}]))), - <<"the :">> = iolist_to_binary(join(re:split("the abc","(?!^)abc",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?!^)abc",[trim]))), + 2}]))), + <<"the :">> = iolist_to_binary(join(re:split("the abc","(?!^)abc",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?!^)abc",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?!^)abc",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?!^)abc",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(?!^)abc",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?!^)abc",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(?!^)abc",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","(?!^)abc",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(?!^)abc",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","(?=^)abc",[trim]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(?!^)abc",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","(?=^)abc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","(?=^)abc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","(?=^)abc",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?=^)abc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","(?=^)abc",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?=^)abc",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?=^)abc",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?=^)abc",[]))), - <<"the abc">> = iolist_to_binary(join(re:split("the abc","(?=^)abc",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?=^)abc",[]))), + <<"the abc">> = iolist_to_binary(join(re:split("the abc","(?=^)abc",[trim]))), <<"the abc">> = iolist_to_binary(join(re:split("the abc","(?=^)abc",[{parts, - 2}]))), - <<"the abc">> = iolist_to_binary(join(re:split("the abc","(?=^)abc",[]))), - <<":b:bbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}(ab*|b)",[trim]))), + 2}]))), + <<"the abc">> = iolist_to_binary(join(re:split("the abc","(?=^)abc",[]))), + <<":b:bbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}(ab*|b)",[trim]))), <<":b:bbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}(ab*|b)",[{parts, - 2}]))), - <<":b:bbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}(ab*|b)",[]))), - <<":abbbbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}?(ab*|b)",[trim]))), + 2}]))), + <<":b:bbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}(ab*|b)",[]))), + <<":abbbbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}?(ab*|b)",[trim]))), <<":abbbbb:">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}?(ab*|b)",[{parts, - 2}]))), - <<":abbbbb:">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}?(ab*|b)",[]))), - <<":a:bbbbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}?(ab*?|b)",[trim]))), + 2}]))), + <<":abbbbb:">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}?(ab*|b)",[]))), + <<":a:bbbbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}?(ab*?|b)",[trim]))), <<":a:bbbbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}?(ab*?|b)",[{parts, - 2}]))), - <<":a:bbbbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}?(ab*?|b)",[]))), - <<":b:bbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}(ab*?|b)",[trim]))), + 2}]))), + <<":a:bbbbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}?(ab*?|b)",[]))), + <<":b:bbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}(ab*?|b)",[trim]))), <<":b:bbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}(ab*?|b)",[{parts, - 2}]))), - <<":b:bbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}(ab*?|b)",[]))), + 2}]))), + <<":b:bbb">> = iolist_to_binary(join(re:split("aabbbbb","^[ab]{1,3}(ab*?|b)",[]))), <<"Alan Other <user.ain>">> = iolist_to_binary(join(re:split("Alan Other <user.ain>"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -1630,7 +1630,7 @@ run3() -> # name and address ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* -\\) )* # optional trailing comment",[extended,trim]))), +\\) )* # optional trailing comment",[extended,trim]))), <<"Alan Other <user.ain>">> = iolist_to_binary(join(re:split("Alan Other <user.ain>"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -1824,7 +1824,7 @@ run3() -> ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional trailing comment",[extended, - {parts,2}]))), + {parts,2}]))), <<"Alan Other <user.ain>">> = iolist_to_binary(join(re:split("Alan Other <user.ain>"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -2017,7 +2017,7 @@ run3() -> # name and address ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* -\\) )* # optional trailing comment",[extended]))), +\\) )* # optional trailing comment",[extended]))), <<"<user.ain>">> = iolist_to_binary(join(re:split("<user.ain>"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -2210,7 +2210,7 @@ run3() -> # name and address ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* -\\) )* # optional trailing comment",[extended,trim]))), +\\) )* # optional trailing comment",[extended,trim]))), <<"<user.ain>">> = iolist_to_binary(join(re:split("<user.ain>"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -2404,7 +2404,7 @@ run3() -> ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional trailing comment",[extended, - {parts,2}]))), + {parts,2}]))), <<"<user.ain>">> = iolist_to_binary(join(re:split("<user.ain>"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -2597,7 +2597,7 @@ run3() -> # name and address ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* -\\) )* # optional trailing comment",[extended]))), +\\) )* # optional trailing comment",[extended]))), <<"user.ain">> = iolist_to_binary(join(re:split("user.ain"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -2790,7 +2790,7 @@ run3() -> # name and address ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* -\\) )* # optional trailing comment",[extended,trim]))), +\\) )* # optional trailing comment",[extended,trim]))), <<"user.ain">> = iolist_to_binary(join(re:split("user.ain"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -2984,7 +2984,7 @@ run3() -> ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional trailing comment",[extended, - {parts,2}]))), + {parts,2}]))), <<"user.ain">> = iolist_to_binary(join(re:split("user.ain"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -3177,7 +3177,7 @@ run3() -> # name and address ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* -\\) )* # optional trailing comment",[extended]))), +\\) )* # optional trailing comment",[extended]))), <<"\"A. Other\" <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("\"A. Other\" <user.1234.ain> (a comment)"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -3370,7 +3370,7 @@ run3() -> # name and address ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* -\\) )* # optional trailing comment",[extended,trim]))), +\\) )* # optional trailing comment",[extended,trim]))), <<"\"A. Other\" <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("\"A. Other\" <user.1234.ain> (a comment)"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -3564,7 +3564,7 @@ run3() -> ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional trailing comment",[extended, - {parts,2}]))), + {parts,2}]))), <<"\"A. Other\" <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("\"A. Other\" <user.1234.ain> (a comment)"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -3757,7 +3757,7 @@ run3() -> # name and address ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* -\\) )* # optional trailing comment",[extended]))), +\\) )* # optional trailing comment",[extended]))), <<"A. Other <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("A. Other <user.1234.ain> (a comment)"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -3950,7 +3950,7 @@ run3() -> # name and address ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* -\\) )* # optional trailing comment",[extended,trim]))), +\\) )* # optional trailing comment",[extended,trim]))), <<"A. Other <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("A. Other <user.1234.ain> (a comment)"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -4144,7 +4144,7 @@ run3() -> ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional trailing comment",[extended, - {parts,2}]))), + {parts,2}]))), <<"A. Other <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("A. Other <user.1234.ain> (a comment)"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -4337,7 +4337,7 @@ run3() -> # name and address ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* -\\) )* # optional trailing comment",[extended]))), +\\) )* # optional trailing comment",[extended]))), <<"\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay">> = iolist_to_binary(join(re:split("\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -4530,7 +4530,7 @@ run3() -> # name and address ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* -\\) )* # optional trailing comment",[extended,trim]))), +\\) )* # optional trailing comment",[extended,trim]))), <<"\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay">> = iolist_to_binary(join(re:split("\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -4724,7 +4724,7 @@ run3() -> ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional trailing comment",[extended, - {parts,2}]))), + {parts,2}]))), <<"\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay">> = iolist_to_binary(join(re:split("\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -4917,7 +4917,7 @@ run3() -> # name and address ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* -\\) )* # optional trailing comment",[extended]))), +\\) )* # optional trailing comment",[extended]))), <<"A missing angle <user.where">> = iolist_to_binary(join(re:split("A missing angle <user.where"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -5110,7 +5110,7 @@ run3() -> # name and address ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* -\\) )* # optional trailing comment",[extended,trim]))), +\\) )* # optional trailing comment",[extended,trim]))), <<"A missing angle <user.where">> = iolist_to_binary(join(re:split("A missing angle <user.where"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -5304,7 +5304,7 @@ run3() -> ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional trailing comment",[extended, - {parts,2}]))), + {parts,2}]))), <<"A missing angle <user.where">> = iolist_to_binary(join(re:split("A missing angle <user.where"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -5497,7 +5497,7 @@ run3() -> # name and address ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* -\\) )* # optional trailing comment",[extended]))), +\\) )* # optional trailing comment",[extended]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -5690,7 +5690,7 @@ run3() -> # name and address ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* -\\) )* # optional trailing comment",[extended,trim]))), +\\) )* # optional trailing comment",[extended,trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -5884,7 +5884,7 @@ run3() -> ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional trailing comment",[extended, - {parts,2}]))), + {parts,2}]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -6077,7 +6077,7 @@ run3() -> # name and address ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* -\\) )* # optional trailing comment",[extended]))), +\\) )* # optional trailing comment",[extended]))), <<"The quick brown fox">> = iolist_to_binary(join(re:split("The quick brown fox"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -6270,7 +6270,7 @@ run3() -> # name and address ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* -\\) )* # optional trailing comment",[extended,trim]))), +\\) )* # optional trailing comment",[extended,trim]))), <<"The quick brown fox">> = iolist_to_binary(join(re:split("The quick brown fox"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -6464,7 +6464,7 @@ run3() -> ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional trailing comment",[extended, - {parts,2}]))), + {parts,2}]))), <<"The quick brown fox">> = iolist_to_binary(join(re:split("The quick brown fox"," (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* \\) )* # optional leading comment @@ -6657,7 +6657,7 @@ run3() -> # name and address ) (?: [\\040\\t] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] | \\( (?: [^\\\\\\x80-\\xff\\n\\015()] | \\\\ [^\\x80-\\xff] )* \\) )* -\\) )* # optional trailing comment",[extended]))), +\\) )* # optional trailing comment",[extended]))), <<"Alan Other <user.ain>">> = iolist_to_binary(join(re:split("Alan Other <user.ain>","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -7238,7 +7238,7 @@ run3() -> # address spec > # > # name and address -)",[extended,trim]))), +)",[extended,trim]))), <<"Alan Other <user.ain>">> = iolist_to_binary(join(re:split("Alan Other <user.ain>","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -7819,7 +7819,7 @@ run3() -> # address spec > # > # name and address -)",[extended,{parts,2}]))), +)",[extended,{parts,2}]))), <<"Alan Other <user.ain>">> = iolist_to_binary(join(re:split("Alan Other <user.ain>","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -8400,7 +8400,7 @@ run3() -> # address spec > # > # name and address -)",[extended]))), +)",[extended]))), <<"<user.ain>">> = iolist_to_binary(join(re:split("<user.ain>","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -8981,7 +8981,7 @@ run3() -> # address spec > # > # name and address -)",[extended,trim]))), +)",[extended,trim]))), <<"<user.ain>">> = iolist_to_binary(join(re:split("<user.ain>","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -9562,7 +9562,7 @@ run3() -> # address spec > # > # name and address -)",[extended,{parts,2}]))), +)",[extended,{parts,2}]))), <<"<user.ain>">> = iolist_to_binary(join(re:split("<user.ain>","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -10143,7 +10143,7 @@ run3() -> # address spec > # > # name and address -)",[extended]))), +)",[extended]))), <<"user.ain">> = iolist_to_binary(join(re:split("user.ain","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -10724,7 +10724,7 @@ run3() -> # address spec > # > # name and address -)",[extended,trim]))), +)",[extended,trim]))), <<"user.ain">> = iolist_to_binary(join(re:split("user.ain","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -11305,7 +11305,7 @@ run3() -> # address spec > # > # name and address -)",[extended,{parts,2}]))), +)",[extended,{parts,2}]))), <<"user.ain">> = iolist_to_binary(join(re:split("user.ain","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -11886,7 +11886,7 @@ run3() -> # address spec > # > # name and address -)",[extended]))), +)",[extended]))), <<"\"A. Other\" <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("\"A. Other\" <user.1234.ain> (a comment)","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -12467,7 +12467,7 @@ run3() -> # address spec > # > # name and address -)",[extended,trim]))), +)",[extended,trim]))), <<"\"A. Other\" <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("\"A. Other\" <user.1234.ain> (a comment)","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -13048,7 +13048,7 @@ run3() -> # address spec > # > # name and address -)",[extended,{parts,2}]))), +)",[extended,{parts,2}]))), <<"\"A. Other\" <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("\"A. Other\" <user.1234.ain> (a comment)","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -13629,7 +13629,7 @@ run3() -> # address spec > # > # name and address -)",[extended]))), +)",[extended]))), <<"A. Other <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("A. Other <user.1234.ain> (a comment)","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -14210,7 +14210,7 @@ run3() -> # address spec > # > # name and address -)",[extended,trim]))), +)",[extended,trim]))), <<"A. Other <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("A. Other <user.1234.ain> (a comment)","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -14791,7 +14791,7 @@ run3() -> # address spec > # > # name and address -)",[extended,{parts,2}]))), +)",[extended,{parts,2}]))), <<"A. Other <user.1234.ain> (a comment)">> = iolist_to_binary(join(re:split("A. Other <user.1234.ain> (a comment)","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -15372,7 +15372,7 @@ run3() -> # address spec > # > # name and address -)",[extended]))), +)",[extended]))), <<"\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay">> = iolist_to_binary(join(re:split("\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -15953,7 +15953,7 @@ run3() -> # address spec > # > # name and address -)",[extended,trim]))), +)",[extended,trim]))), <<"\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay">> = iolist_to_binary(join(re:split("\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -16534,7 +16534,7 @@ run3() -> # address spec > # > # name and address -)",[extended,{parts,2}]))), +)",[extended,{parts,2}]))), <<"\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay">> = iolist_to_binary(join(re:split("\"/s=user/ou=host/o=place/prmd=uu.yy/admd= /c=gb/\"-re.lay","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -17115,7 +17115,7 @@ run3() -> # address spec > # > # name and address -)",[extended]))), +)",[extended]))), <<"A missing angle <user.where">> = iolist_to_binary(join(re:split("A missing angle <user.where","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -17696,7 +17696,7 @@ run3() -> # address spec > # > # name and address -)",[extended,trim]))), +)",[extended,trim]))), <<"A missing angle <user.where">> = iolist_to_binary(join(re:split("A missing angle <user.where","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -18277,7 +18277,7 @@ run3() -> # address spec > # > # name and address -)",[extended,{parts,2}]))), +)",[extended,{parts,2}]))), <<"A missing angle <user.where">> = iolist_to_binary(join(re:split("A missing angle <user.where","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -18858,7 +18858,7 @@ run3() -> # address spec > # > # name and address -)",[extended]))), +)",[extended]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -19439,7 +19439,7 @@ run3() -> # address spec > # > # name and address -)",[extended,trim]))), +)",[extended,trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -20020,7 +20020,7 @@ run3() -> # address spec > # > # name and address -)",[extended,{parts,2}]))), +)",[extended,{parts,2}]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -20601,7 +20601,7 @@ run3() -> # address spec > # > # name and address -)",[extended]))), +)",[extended]))), <<"The quick brown fox">> = iolist_to_binary(join(re:split("The quick brown fox","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -21182,7 +21182,7 @@ run3() -> # address spec > # > # name and address -)",[extended,trim]))), +)",[extended,trim]))), <<"The quick brown fox">> = iolist_to_binary(join(re:split("The quick brown fox","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -21763,7 +21763,7 @@ run3() -> # address spec > # > # name and address -)",[extended,{parts,2}]))), +)",[extended,{parts,2}]))), <<"The quick brown fox">> = iolist_to_binary(join(re:split("The quick brown fox","[\\040\\t]* # Nab whitespace. (?: \\( # ( @@ -22344,5763 +22344,5763 @@ run3() -> # address spec > # > # name and address -)",[extended]))), - <<"abcdefpqrxyz0AB">> = iolist_to_binary(join(re:split("abcdefpqrxyz0AB","abc\\0def\\00pqr\\000xyz\\0000AB",[trim]))), +)",[extended]))), + <<"abcdefpqrxyz0AB">> = iolist_to_binary(join(re:split("abcdefpqrxyz0AB","abc\\0def\\00pqr\\000xyz\\0000AB",[trim]))), <<"abcdefpqrxyz0AB">> = iolist_to_binary(join(re:split("abcdefpqrxyz0AB","abc\\0def\\00pqr\\000xyz\\0000AB",[{parts, - 2}]))), - <<"abcdefpqrxyz0AB">> = iolist_to_binary(join(re:split("abcdefpqrxyz0AB","abc\\0def\\00pqr\\000xyz\\0000AB",[]))), - <<"abc456 abcdefpqrxyz0ABCDE">> = iolist_to_binary(join(re:split("abc456 abcdefpqrxyz0ABCDE","abc\\0def\\00pqr\\000xyz\\0000AB",[trim]))), + 2}]))), + <<"abcdefpqrxyz0AB">> = iolist_to_binary(join(re:split("abcdefpqrxyz0AB","abc\\0def\\00pqr\\000xyz\\0000AB",[]))), + <<"abc456 abcdefpqrxyz0ABCDE">> = iolist_to_binary(join(re:split("abc456 abcdefpqrxyz0ABCDE","abc\\0def\\00pqr\\000xyz\\0000AB",[trim]))), <<"abc456 abcdefpqrxyz0ABCDE">> = iolist_to_binary(join(re:split("abc456 abcdefpqrxyz0ABCDE","abc\\0def\\00pqr\\000xyz\\0000AB",[{parts, - 2}]))), - <<"abc456 abcdefpqrxyz0ABCDE">> = iolist_to_binary(join(re:split("abc456 abcdefpqrxyz0ABCDE","abc\\0def\\00pqr\\000xyz\\0000AB",[]))), - <<"abc
efpqr0xyz00AB">> = iolist_to_binary(join(re:split("abc
efpqr0xyz00AB","abc\\x0def\\x00pqr\\x000xyz\\x0000AB",[trim]))), + 2}]))), + <<"abc456 abcdefpqrxyz0ABCDE">> = iolist_to_binary(join(re:split("abc456 abcdefpqrxyz0ABCDE","abc\\0def\\00pqr\\000xyz\\0000AB",[]))), + <<"abc
efpqr0xyz00AB">> = iolist_to_binary(join(re:split("abc
efpqr0xyz00AB","abc\\x0def\\x00pqr\\x000xyz\\x0000AB",[trim]))), <<"abc
efpqr0xyz00AB">> = iolist_to_binary(join(re:split("abc
efpqr0xyz00AB","abc\\x0def\\x00pqr\\x000xyz\\x0000AB",[{parts, - 2}]))), - <<"abc
efpqr0xyz00AB">> = iolist_to_binary(join(re:split("abc
efpqr0xyz00AB","abc\\x0def\\x00pqr\\x000xyz\\x0000AB",[]))), - <<"abc456 abc
efpqr0xyz00ABCDE">> = iolist_to_binary(join(re:split("abc456 abc
efpqr0xyz00ABCDE","abc\\x0def\\x00pqr\\x000xyz\\x0000AB",[trim]))), + 2}]))), + <<"abc
efpqr0xyz00AB">> = iolist_to_binary(join(re:split("abc
efpqr0xyz00AB","abc\\x0def\\x00pqr\\x000xyz\\x0000AB",[]))), + <<"abc456 abc
efpqr0xyz00ABCDE">> = iolist_to_binary(join(re:split("abc456 abc
efpqr0xyz00ABCDE","abc\\x0def\\x00pqr\\x000xyz\\x0000AB",[trim]))), <<"abc456 abc
efpqr0xyz00ABCDE">> = iolist_to_binary(join(re:split("abc456 abc
efpqr0xyz00ABCDE","abc\\x0def\\x00pqr\\x000xyz\\x0000AB",[{parts, - 2}]))), - <<"abc456 abc
efpqr0xyz00ABCDE">> = iolist_to_binary(join(re:split("abc456 abc
efpqr0xyz00ABCDE","abc\\x0def\\x00pqr\\x000xyz\\x0000AB",[]))), - <<"A">> = iolist_to_binary(join(re:split("A","^[\\000-\\037]",[trim]))), + 2}]))), + <<"abc456 abc
efpqr0xyz00ABCDE">> = iolist_to_binary(join(re:split("abc456 abc
efpqr0xyz00ABCDE","abc\\x0def\\x00pqr\\x000xyz\\x0000AB",[]))), + <<"A">> = iolist_to_binary(join(re:split("A","^[\\000-\\037]",[trim]))), <<"A">> = iolist_to_binary(join(re:split("A","^[\\000-\\037]",[{parts, - 2}]))), - <<"A">> = iolist_to_binary(join(re:split("A","^[\\000-\\037]",[]))), - <<":B">> = iolist_to_binary(join(re:split("B","^[\\000-\\037]",[trim]))), + 2}]))), + <<"A">> = iolist_to_binary(join(re:split("A","^[\\000-\\037]",[]))), + <<":B">> = iolist_to_binary(join(re:split("B","^[\\000-\\037]",[trim]))), <<":B">> = iolist_to_binary(join(re:split("B","^[\\000-\\037]",[{parts, - 2}]))), - <<":B">> = iolist_to_binary(join(re:split("B","^[\\000-\\037]",[]))), - <<":C">> = iolist_to_binary(join(re:split("C","^[\\000-\\037]",[trim]))), + 2}]))), + <<":B">> = iolist_to_binary(join(re:split("B","^[\\000-\\037]",[]))), + <<":C">> = iolist_to_binary(join(re:split("C","^[\\000-\\037]",[trim]))), <<":C">> = iolist_to_binary(join(re:split("C","^[\\000-\\037]",[{parts, - 2}]))), - <<":C">> = iolist_to_binary(join(re:split("C","^[\\000-\\037]",[]))), - <<"">> = iolist_to_binary(join(re:split("","\\0*",[trim]))), + 2}]))), + <<":C">> = iolist_to_binary(join(re:split("C","^[\\000-\\037]",[]))), + <<"">> = iolist_to_binary(join(re:split("","\\0*",[trim]))), <<"">> = iolist_to_binary(join(re:split("","\\0*",[{parts, - 2}]))), - <<"">> = iolist_to_binary(join(re:split("","\\0*",[]))), - <<"The AZ">> = iolist_to_binary(join(re:split("The AZ","A\\x0{2,3}Z",[trim]))), + 2}]))), + <<"">> = iolist_to_binary(join(re:split("","\\0*",[]))), + <<"The AZ">> = iolist_to_binary(join(re:split("The AZ","A\\x0{2,3}Z",[trim]))), <<"The AZ">> = iolist_to_binary(join(re:split("The AZ","A\\x0{2,3}Z",[{parts, - 2}]))), - <<"The AZ">> = iolist_to_binary(join(re:split("The AZ","A\\x0{2,3}Z",[]))), - <<"An AZ">> = iolist_to_binary(join(re:split("An AZ","A\\x0{2,3}Z",[trim]))), + 2}]))), + <<"The AZ">> = iolist_to_binary(join(re:split("The AZ","A\\x0{2,3}Z",[]))), + <<"An AZ">> = iolist_to_binary(join(re:split("An AZ","A\\x0{2,3}Z",[trim]))), <<"An AZ">> = iolist_to_binary(join(re:split("An AZ","A\\x0{2,3}Z",[{parts, - 2}]))), - <<"An AZ">> = iolist_to_binary(join(re:split("An AZ","A\\x0{2,3}Z",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","A\\x0{2,3}Z",[trim]))), + 2}]))), + <<"An AZ">> = iolist_to_binary(join(re:split("An AZ","A\\x0{2,3}Z",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","A\\x0{2,3}Z",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","A\\x0{2,3}Z",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","A\\x0{2,3}Z",[]))), - <<"AZ">> = iolist_to_binary(join(re:split("AZ","A\\x0{2,3}Z",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","A\\x0{2,3}Z",[]))), + <<"AZ">> = iolist_to_binary(join(re:split("AZ","A\\x0{2,3}Z",[trim]))), <<"AZ">> = iolist_to_binary(join(re:split("AZ","A\\x0{2,3}Z",[{parts, - 2}]))), - <<"AZ">> = iolist_to_binary(join(re:split("AZ","A\\x0{2,3}Z",[]))), - <<"AZ">> = iolist_to_binary(join(re:split("AZ","A\\x0{2,3}Z",[trim]))), + 2}]))), + <<"AZ">> = iolist_to_binary(join(re:split("AZ","A\\x0{2,3}Z",[]))), + <<"AZ">> = iolist_to_binary(join(re:split("AZ","A\\x0{2,3}Z",[trim]))), <<"AZ">> = iolist_to_binary(join(re:split("AZ","A\\x0{2,3}Z",[{parts, - 2}]))), - <<"AZ">> = iolist_to_binary(join(re:split("AZ","A\\x0{2,3}Z",[]))), - <<":cow:bell">> = iolist_to_binary(join(re:split("cowcowbell","^(cow|)\\1(bell)",[trim]))), + 2}]))), + <<"AZ">> = iolist_to_binary(join(re:split("AZ","A\\x0{2,3}Z",[]))), + <<":cow:bell">> = iolist_to_binary(join(re:split("cowcowbell","^(cow|)\\1(bell)",[trim]))), <<":cow:bell:">> = iolist_to_binary(join(re:split("cowcowbell","^(cow|)\\1(bell)",[{parts, - 2}]))), - <<":cow:bell:">> = iolist_to_binary(join(re:split("cowcowbell","^(cow|)\\1(bell)",[]))), - <<"::bell">> = iolist_to_binary(join(re:split("bell","^(cow|)\\1(bell)",[trim]))), + 2}]))), + <<":cow:bell:">> = iolist_to_binary(join(re:split("cowcowbell","^(cow|)\\1(bell)",[]))), + <<"::bell">> = iolist_to_binary(join(re:split("bell","^(cow|)\\1(bell)",[trim]))), <<"::bell:">> = iolist_to_binary(join(re:split("bell","^(cow|)\\1(bell)",[{parts, - 2}]))), - <<"::bell:">> = iolist_to_binary(join(re:split("bell","^(cow|)\\1(bell)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(cow|)\\1(bell)",[trim]))), + 2}]))), + <<"::bell:">> = iolist_to_binary(join(re:split("bell","^(cow|)\\1(bell)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(cow|)\\1(bell)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(cow|)\\1(bell)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(cow|)\\1(bell)",[]))), - <<"cowbell">> = iolist_to_binary(join(re:split("cowbell","^(cow|)\\1(bell)",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(cow|)\\1(bell)",[]))), + <<"cowbell">> = iolist_to_binary(join(re:split("cowbell","^(cow|)\\1(bell)",[trim]))), <<"cowbell">> = iolist_to_binary(join(re:split("cowbell","^(cow|)\\1(bell)",[{parts, - 2}]))), - <<"cowbell">> = iolist_to_binary(join(re:split("cowbell","^(cow|)\\1(bell)",[]))), - <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[trim]))), + 2}]))), + <<"cowbell">> = iolist_to_binary(join(re:split("cowbell","^(cow|)\\1(bell)",[]))), + <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[trim]))), <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[{parts, - 2}]))), - <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[]))), - <<":abc">> = iolist_to_binary(join(re:split("abc","^\\s",[trim]))), + 2}]))), + <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[]))), + <<":abc">> = iolist_to_binary(join(re:split("abc","^\\s",[trim]))), <<":abc">> = iolist_to_binary(join(re:split("abc","^\\s",[{parts, - 2}]))), - <<":abc">> = iolist_to_binary(join(re:split("abc","^\\s",[]))), + 2}]))), + <<":abc">> = iolist_to_binary(join(re:split("abc","^\\s",[]))), <<":abc">> = iolist_to_binary(join(re:split(" -abc","^\\s",[trim]))), +abc","^\\s",[trim]))), <<":abc">> = iolist_to_binary(join(re:split(" -abc","^\\s",[{parts,2}]))), +abc","^\\s",[{parts,2}]))), <<":abc">> = iolist_to_binary(join(re:split(" -abc","^\\s",[]))), - <<":abc">> = iolist_to_binary(join(re:split("
abc","^\\s",[trim]))), +abc","^\\s",[]))), + <<":abc">> = iolist_to_binary(join(re:split("
abc","^\\s",[trim]))), <<":abc">> = iolist_to_binary(join(re:split("
abc","^\\s",[{parts, - 2}]))), - <<":abc">> = iolist_to_binary(join(re:split("
abc","^\\s",[]))), - <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[trim]))), + 2}]))), + <<":abc">> = iolist_to_binary(join(re:split("
abc","^\\s",[]))), + <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[trim]))), <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[{parts, - 2}]))), - <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\s",[trim]))), + 2}]))), + <<":abc">> = iolist_to_binary(join(re:split(" abc","^\\s",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\s",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\s",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\s",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","^\\s",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\s",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","^\\s",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","^\\s",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","^\\s",[]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","^\\s",[]))), ok. run4() -> <<"">> = iolist_to_binary(join(re:split("abc","^a b - c",[extended,trim]))), + c",[extended,trim]))), <<":">> = iolist_to_binary(join(re:split("abc","^a b - c",[extended,{parts,2}]))), + c",[extended,{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("abc","^a b - c",[extended]))), - <<":a">> = iolist_to_binary(join(re:split("ab","^(a|)\\1*b",[trim]))), + c",[extended]))), + <<":a">> = iolist_to_binary(join(re:split("ab","^(a|)\\1*b",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("ab","^(a|)\\1*b",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("ab","^(a|)\\1*b",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1*b",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("ab","^(a|)\\1*b",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1*b",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1*b",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1*b",[]))), - <<"">> = iolist_to_binary(join(re:split("b","^(a|)\\1*b",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1*b",[]))), + <<"">> = iolist_to_binary(join(re:split("b","^(a|)\\1*b",[trim]))), <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1*b",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1*b",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1*b",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1*b",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1*b",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1*b",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1*b",[]))), - <<"acb">> = iolist_to_binary(join(re:split("acb","^(a|)\\1*b",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1*b",[]))), + <<"acb">> = iolist_to_binary(join(re:split("acb","^(a|)\\1*b",[trim]))), <<"acb">> = iolist_to_binary(join(re:split("acb","^(a|)\\1*b",[{parts, - 2}]))), - <<"acb">> = iolist_to_binary(join(re:split("acb","^(a|)\\1*b",[]))), - <<":a">> = iolist_to_binary(join(re:split("aab","^(a|)\\1+b",[trim]))), + 2}]))), + <<"acb">> = iolist_to_binary(join(re:split("acb","^(a|)\\1*b",[]))), + <<":a">> = iolist_to_binary(join(re:split("aab","^(a|)\\1+b",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aab","^(a|)\\1+b",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aab","^(a|)\\1+b",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1+b",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aab","^(a|)\\1+b",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1+b",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1+b",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1+b",[]))), - <<"">> = iolist_to_binary(join(re:split("b","^(a|)\\1+b",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1+b",[]))), + <<"">> = iolist_to_binary(join(re:split("b","^(a|)\\1+b",[trim]))), <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1+b",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1+b",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1+b",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1+b",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1+b",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1+b",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1+b",[]))), - <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1+b",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1+b",[]))), + <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1+b",[trim]))), <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1+b",[{parts, - 2}]))), - <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1+b",[]))), - <<":a">> = iolist_to_binary(join(re:split("ab","^(a|)\\1?b",[trim]))), + 2}]))), + <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1+b",[]))), + <<":a">> = iolist_to_binary(join(re:split("ab","^(a|)\\1?b",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("ab","^(a|)\\1?b",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("ab","^(a|)\\1?b",[]))), - <<":a">> = iolist_to_binary(join(re:split("aab","^(a|)\\1?b",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("ab","^(a|)\\1?b",[]))), + <<":a">> = iolist_to_binary(join(re:split("aab","^(a|)\\1?b",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aab","^(a|)\\1?b",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aab","^(a|)\\1?b",[]))), - <<"">> = iolist_to_binary(join(re:split("b","^(a|)\\1?b",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aab","^(a|)\\1?b",[]))), + <<"">> = iolist_to_binary(join(re:split("b","^(a|)\\1?b",[trim]))), <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1?b",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1?b",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1?b",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1?b",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1?b",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1?b",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1?b",[]))), - <<"acb">> = iolist_to_binary(join(re:split("acb","^(a|)\\1?b",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1?b",[]))), + <<"acb">> = iolist_to_binary(join(re:split("acb","^(a|)\\1?b",[trim]))), <<"acb">> = iolist_to_binary(join(re:split("acb","^(a|)\\1?b",[{parts, - 2}]))), - <<"acb">> = iolist_to_binary(join(re:split("acb","^(a|)\\1?b",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaab","^(a|)\\1{2}b",[trim]))), + 2}]))), + <<"acb">> = iolist_to_binary(join(re:split("acb","^(a|)\\1?b",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaab","^(a|)\\1{2}b",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aaab","^(a|)\\1{2}b",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aaab","^(a|)\\1{2}b",[]))), - <<"">> = iolist_to_binary(join(re:split("b","^(a|)\\1{2}b",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aaab","^(a|)\\1{2}b",[]))), + <<"">> = iolist_to_binary(join(re:split("b","^(a|)\\1{2}b",[trim]))), <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1{2}b",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1{2}b",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1{2}b",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1{2}b",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1{2}b",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1{2}b",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1{2}b",[]))), - <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1{2}b",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1{2}b",[]))), + <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1{2}b",[trim]))), <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1{2}b",[{parts, - 2}]))), - <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1{2}b",[]))), - <<"aab">> = iolist_to_binary(join(re:split("aab","^(a|)\\1{2}b",[trim]))), + 2}]))), + <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1{2}b",[]))), + <<"aab">> = iolist_to_binary(join(re:split("aab","^(a|)\\1{2}b",[trim]))), <<"aab">> = iolist_to_binary(join(re:split("aab","^(a|)\\1{2}b",[{parts, - 2}]))), - <<"aab">> = iolist_to_binary(join(re:split("aab","^(a|)\\1{2}b",[]))), - <<"aaaab">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1{2}b",[trim]))), + 2}]))), + <<"aab">> = iolist_to_binary(join(re:split("aab","^(a|)\\1{2}b",[]))), + <<"aaaab">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1{2}b",[trim]))), <<"aaaab">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1{2}b",[{parts, - 2}]))), - <<"aaaab">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1{2}b",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaab","^(a|)\\1{2,3}b",[trim]))), + 2}]))), + <<"aaaab">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1{2}b",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaab","^(a|)\\1{2,3}b",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aaab","^(a|)\\1{2,3}b",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aaab","^(a|)\\1{2,3}b",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1{2,3}b",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aaab","^(a|)\\1{2,3}b",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1{2,3}b",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1{2,3}b",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1{2,3}b",[]))), - <<"">> = iolist_to_binary(join(re:split("b","^(a|)\\1{2,3}b",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aaaab","^(a|)\\1{2,3}b",[]))), + <<"">> = iolist_to_binary(join(re:split("b","^(a|)\\1{2,3}b",[trim]))), <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1{2,3}b",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1{2,3}b",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1{2,3}b",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("b","^(a|)\\1{2,3}b",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1{2,3}b",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1{2,3}b",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1{2,3}b",[]))), - <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1{2,3}b",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a|)\\1{2,3}b",[]))), + <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1{2,3}b",[trim]))), <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1{2,3}b",[{parts, - 2}]))), - <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1{2,3}b",[]))), - <<"aab">> = iolist_to_binary(join(re:split("aab","^(a|)\\1{2,3}b",[trim]))), + 2}]))), + <<"ab">> = iolist_to_binary(join(re:split("ab","^(a|)\\1{2,3}b",[]))), + <<"aab">> = iolist_to_binary(join(re:split("aab","^(a|)\\1{2,3}b",[trim]))), <<"aab">> = iolist_to_binary(join(re:split("aab","^(a|)\\1{2,3}b",[{parts, - 2}]))), - <<"aab">> = iolist_to_binary(join(re:split("aab","^(a|)\\1{2,3}b",[]))), - <<"aaaaab">> = iolist_to_binary(join(re:split("aaaaab","^(a|)\\1{2,3}b",[trim]))), + 2}]))), + <<"aab">> = iolist_to_binary(join(re:split("aab","^(a|)\\1{2,3}b",[]))), + <<"aaaaab">> = iolist_to_binary(join(re:split("aaaaab","^(a|)\\1{2,3}b",[trim]))), <<"aaaaab">> = iolist_to_binary(join(re:split("aaaaab","^(a|)\\1{2,3}b",[{parts, - 2}]))), - <<"aaaaab">> = iolist_to_binary(join(re:split("aaaaab","^(a|)\\1{2,3}b",[]))), - <<"">> = iolist_to_binary(join(re:split("abbbbc","ab{1,3}bc",[trim]))), + 2}]))), + <<"aaaaab">> = iolist_to_binary(join(re:split("aaaaab","^(a|)\\1{2,3}b",[]))), + <<"">> = iolist_to_binary(join(re:split("abbbbc","ab{1,3}bc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{1,3}bc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{1,3}bc",[]))), - <<"">> = iolist_to_binary(join(re:split("abbbc","ab{1,3}bc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{1,3}bc",[]))), + <<"">> = iolist_to_binary(join(re:split("abbbc","ab{1,3}bc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abbbc","ab{1,3}bc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abbbc","ab{1,3}bc",[]))), - <<"">> = iolist_to_binary(join(re:split("abbc","ab{1,3}bc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abbbc","ab{1,3}bc",[]))), + <<"">> = iolist_to_binary(join(re:split("abbc","ab{1,3}bc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abbc","ab{1,3}bc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abbc","ab{1,3}bc",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{1,3}bc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abbc","ab{1,3}bc",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{1,3}bc",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{1,3}bc",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{1,3}bc",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","ab{1,3}bc",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{1,3}bc",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","ab{1,3}bc",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","ab{1,3}bc",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","ab{1,3}bc",[]))), - <<"abbbbbc">> = iolist_to_binary(join(re:split("abbbbbc","ab{1,3}bc",[trim]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","ab{1,3}bc",[]))), + <<"abbbbbc">> = iolist_to_binary(join(re:split("abbbbbc","ab{1,3}bc",[trim]))), <<"abbbbbc">> = iolist_to_binary(join(re:split("abbbbbc","ab{1,3}bc",[{parts, - 2}]))), - <<"abbbbbc">> = iolist_to_binary(join(re:split("abbbbbc","ab{1,3}bc",[]))), - <<":track1:title:Blah blah blah">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[T ]+(.*)",[trim]))), + 2}]))), + <<"abbbbbc">> = iolist_to_binary(join(re:split("abbbbbc","ab{1,3}bc",[]))), + <<":track1:title:Blah blah blah">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[T ]+(.*)",[trim]))), <<":track1:title:Blah blah blah:">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[T ]+(.*)",[{parts, - 2}]))), - <<":track1:title:Blah blah blah:">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[T ]+(.*)",[]))), + 2}]))), + <<":track1:title:Blah blah blah:">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[T ]+(.*)",[]))), <<":track1:title:Blah blah blah">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[T ]+(.*)",[caseless, - trim]))), + trim]))), <<":track1:title:Blah blah blah:">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[T ]+(.*)",[caseless, {parts, - 2}]))), - <<":track1:title:Blah blah blah:">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[T ]+(.*)",[caseless]))), + 2}]))), + <<":track1:title:Blah blah blah:">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[T ]+(.*)",[caseless]))), <<":track1:title:Blah blah blah">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[t ]+(.*)",[caseless, - trim]))), + trim]))), <<":track1:title:Blah blah blah:">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[t ]+(.*)",[caseless, {parts, - 2}]))), - <<":track1:title:Blah blah blah:">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[t ]+(.*)",[caseless]))), - <<"">> = iolist_to_binary(join(re:split("WXY_^abc","^[W-c]+$",[trim]))), + 2}]))), + <<":track1:title:Blah blah blah:">> = iolist_to_binary(join(re:split("track1.title:TBlah blah blah","([^.]*)\\.([^:]*):[t ]+(.*)",[caseless]))), + <<"">> = iolist_to_binary(join(re:split("WXY_^abc","^[W-c]+$",[trim]))), <<":">> = iolist_to_binary(join(re:split("WXY_^abc","^[W-c]+$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("WXY_^abc","^[W-c]+$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-c]+$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("WXY_^abc","^[W-c]+$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-c]+$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-c]+$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-c]+$",[]))), - <<"wxy">> = iolist_to_binary(join(re:split("wxy","^[W-c]+$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-c]+$",[]))), + <<"wxy">> = iolist_to_binary(join(re:split("wxy","^[W-c]+$",[trim]))), <<"wxy">> = iolist_to_binary(join(re:split("wxy","^[W-c]+$",[{parts, - 2}]))), - <<"wxy">> = iolist_to_binary(join(re:split("wxy","^[W-c]+$",[]))), + 2}]))), + <<"wxy">> = iolist_to_binary(join(re:split("wxy","^[W-c]+$",[]))), <<"">> = iolist_to_binary(join(re:split("WXY_^abc","^[W-c]+$",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("WXY_^abc","^[W-c]+$",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("WXY_^abc","^[W-c]+$",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("WXY_^abc","^[W-c]+$",[caseless]))), <<"">> = iolist_to_binary(join(re:split("wxy_^ABC","^[W-c]+$",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("wxy_^ABC","^[W-c]+$",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("wxy_^ABC","^[W-c]+$",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("wxy_^ABC","^[W-c]+$",[caseless]))), <<"">> = iolist_to_binary(join(re:split("WXY_^abc","^[\\x3f-\\x5F]+$",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("WXY_^abc","^[\\x3f-\\x5F]+$",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("WXY_^abc","^[\\x3f-\\x5F]+$",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("WXY_^abc","^[\\x3f-\\x5F]+$",[caseless]))), <<"">> = iolist_to_binary(join(re:split("wxy_^ABC","^[\\x3f-\\x5F]+$",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("wxy_^ABC","^[\\x3f-\\x5F]+$",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("wxy_^ABC","^[\\x3f-\\x5F]+$",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("wxy_^ABC","^[\\x3f-\\x5F]+$",[caseless]))), <<"">> = iolist_to_binary(join(re:split("abc","^abc$",[multiline, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("abc","^abc$",[multiline, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","^abc$",[multiline]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","^abc$",[multiline]))), <<"qqq ">> = iolist_to_binary(join(re:split("qqq -abc","^abc$",[multiline,trim]))), +abc","^abc$",[multiline,trim]))), <<"qqq :">> = iolist_to_binary(join(re:split("qqq -abc","^abc$",[multiline,{parts,2}]))), +abc","^abc$",[multiline,{parts,2}]))), <<"qqq :">> = iolist_to_binary(join(re:split("qqq -abc","^abc$",[multiline]))), +abc","^abc$",[multiline]))), <<": zzz">> = iolist_to_binary(join(re:split("abc -zzz","^abc$",[multiline,trim]))), +zzz","^abc$",[multiline,trim]))), <<": zzz">> = iolist_to_binary(join(re:split("abc -zzz","^abc$",[multiline,{parts,2}]))), +zzz","^abc$",[multiline,{parts,2}]))), <<": zzz">> = iolist_to_binary(join(re:split("abc -zzz","^abc$",[multiline]))), +zzz","^abc$",[multiline]))), <<"qqq : zzz">> = iolist_to_binary(join(re:split("qqq abc -zzz","^abc$",[multiline,trim]))), +zzz","^abc$",[multiline,trim]))), <<"qqq : zzz">> = iolist_to_binary(join(re:split("qqq abc -zzz","^abc$",[multiline,{parts,2}]))), +zzz","^abc$",[multiline,{parts,2}]))), <<"qqq : zzz">> = iolist_to_binary(join(re:split("qqq abc -zzz","^abc$",[multiline]))), - <<"">> = iolist_to_binary(join(re:split("abc","^abc$",[trim]))), +zzz","^abc$",[multiline]))), + <<"">> = iolist_to_binary(join(re:split("abc","^abc$",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","^abc$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","^abc$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","^abc$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[]))), <<"qqq abc">> = iolist_to_binary(join(re:split("qqq -abc","^abc$",[trim]))), +abc","^abc$",[trim]))), <<"qqq abc">> = iolist_to_binary(join(re:split("qqq -abc","^abc$",[{parts,2}]))), +abc","^abc$",[{parts,2}]))), <<"qqq abc">> = iolist_to_binary(join(re:split("qqq -abc","^abc$",[]))), +abc","^abc$",[]))), <<"abc zzz">> = iolist_to_binary(join(re:split("abc -zzz","^abc$",[trim]))), +zzz","^abc$",[trim]))), <<"abc zzz">> = iolist_to_binary(join(re:split("abc -zzz","^abc$",[{parts,2}]))), +zzz","^abc$",[{parts,2}]))), <<"abc zzz">> = iolist_to_binary(join(re:split("abc -zzz","^abc$",[]))), +zzz","^abc$",[]))), <<"qqq abc zzz">> = iolist_to_binary(join(re:split("qqq abc -zzz","^abc$",[trim]))), +zzz","^abc$",[trim]))), <<"qqq abc zzz">> = iolist_to_binary(join(re:split("qqq abc -zzz","^abc$",[{parts,2}]))), +zzz","^abc$",[{parts,2}]))), <<"qqq abc zzz">> = iolist_to_binary(join(re:split("qqq abc -zzz","^abc$",[]))), +zzz","^abc$",[]))), <<"">> = iolist_to_binary(join(re:split("abc","\\Aabc\\Z",[multiline, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\Z",[multiline, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\Z",[multiline]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\Z",[multiline]))), <<"">> = iolist_to_binary(join(re:split("abc","\\Aabc\\Z",[multiline, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\Z",[multiline, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\Z",[multiline]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\Z",[multiline]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc\\Z",[multiline, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc\\Z",[multiline, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc\\Z",[multiline]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc\\Z",[multiline]))), <<"qqq abc">> = iolist_to_binary(join(re:split("qqq -abc","\\Aabc\\Z",[multiline,trim]))), +abc","\\Aabc\\Z",[multiline,trim]))), <<"qqq abc">> = iolist_to_binary(join(re:split("qqq -abc","\\Aabc\\Z",[multiline,{parts,2}]))), +abc","\\Aabc\\Z",[multiline,{parts,2}]))), <<"qqq abc">> = iolist_to_binary(join(re:split("qqq -abc","\\Aabc\\Z",[multiline]))), +abc","\\Aabc\\Z",[multiline]))), <<"abc zzz">> = iolist_to_binary(join(re:split("abc -zzz","\\Aabc\\Z",[multiline,trim]))), +zzz","\\Aabc\\Z",[multiline,trim]))), <<"abc zzz">> = iolist_to_binary(join(re:split("abc -zzz","\\Aabc\\Z",[multiline,{parts,2}]))), +zzz","\\Aabc\\Z",[multiline,{parts,2}]))), <<"abc zzz">> = iolist_to_binary(join(re:split("abc -zzz","\\Aabc\\Z",[multiline]))), +zzz","\\Aabc\\Z",[multiline]))), <<"qqq abc zzz">> = iolist_to_binary(join(re:split("qqq abc -zzz","\\Aabc\\Z",[multiline,trim]))), +zzz","\\Aabc\\Z",[multiline,trim]))), <<"qqq abc zzz">> = iolist_to_binary(join(re:split("qqq abc -zzz","\\Aabc\\Z",[multiline,{parts,2}]))), +zzz","\\Aabc\\Z",[multiline,{parts,2}]))), <<"qqq abc zzz">> = iolist_to_binary(join(re:split("qqq abc -zzz","\\Aabc\\Z",[multiline]))), +zzz","\\Aabc\\Z",[multiline]))), <<":f">> = iolist_to_binary(join(re:split("abc -def","\\A(.)*\\Z",[dotall,trim]))), +def","\\A(.)*\\Z",[dotall,trim]))), <<":f:">> = iolist_to_binary(join(re:split("abc -def","\\A(.)*\\Z",[dotall,{parts,2}]))), +def","\\A(.)*\\Z",[dotall,{parts,2}]))), <<":f:">> = iolist_to_binary(join(re:split("abc -def","\\A(.)*\\Z",[dotall]))), +def","\\A(.)*\\Z",[dotall]))), <<":s">> = iolist_to_binary(join(re:split("*** Failers","\\A(.)*\\Z",[multiline, - trim]))), + trim]))), <<":s:">> = iolist_to_binary(join(re:split("*** Failers","\\A(.)*\\Z",[multiline, {parts, - 2}]))), - <<":s:">> = iolist_to_binary(join(re:split("*** Failers","\\A(.)*\\Z",[multiline]))), + 2}]))), + <<":s:">> = iolist_to_binary(join(re:split("*** Failers","\\A(.)*\\Z",[multiline]))), <<"abc def">> = iolist_to_binary(join(re:split("abc -def","\\A(.)*\\Z",[multiline,trim]))), +def","\\A(.)*\\Z",[multiline,trim]))), <<"abc def">> = iolist_to_binary(join(re:split("abc -def","\\A(.)*\\Z",[multiline,{parts,2}]))), +def","\\A(.)*\\Z",[multiline,{parts,2}]))), <<"abc def">> = iolist_to_binary(join(re:split("abc -def","\\A(.)*\\Z",[multiline]))), - <<"::c">> = iolist_to_binary(join(re:split("b::c","(?:b)|(?::+)",[trim]))), +def","\\A(.)*\\Z",[multiline]))), + <<"::c">> = iolist_to_binary(join(re:split("b::c","(?:b)|(?::+)",[trim]))), <<":::c">> = iolist_to_binary(join(re:split("b::c","(?:b)|(?::+)",[{parts, - 2}]))), - <<"::c">> = iolist_to_binary(join(re:split("b::c","(?:b)|(?::+)",[]))), - <<"c">> = iolist_to_binary(join(re:split("c::b","(?:b)|(?::+)",[trim]))), + 2}]))), + <<"::c">> = iolist_to_binary(join(re:split("b::c","(?:b)|(?::+)",[]))), + <<"c">> = iolist_to_binary(join(re:split("c::b","(?:b)|(?::+)",[trim]))), <<"c:b">> = iolist_to_binary(join(re:split("c::b","(?:b)|(?::+)",[{parts, - 2}]))), - <<"c::">> = iolist_to_binary(join(re:split("c::b","(?:b)|(?::+)",[]))), - <<"">> = iolist_to_binary(join(re:split("az-","[-az]+",[trim]))), + 2}]))), + <<"c::">> = iolist_to_binary(join(re:split("c::b","(?:b)|(?::+)",[]))), + <<"">> = iolist_to_binary(join(re:split("az-","[-az]+",[trim]))), <<":">> = iolist_to_binary(join(re:split("az-","[-az]+",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("az-","[-az]+",[]))), - <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[-az]+",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("az-","[-az]+",[]))), + <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[-az]+",[trim]))), <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[-az]+",[{parts, - 2}]))), - <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[-az]+",[]))), - <<"b">> = iolist_to_binary(join(re:split("b","[-az]+",[trim]))), + 2}]))), + <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[-az]+",[]))), + <<"b">> = iolist_to_binary(join(re:split("b","[-az]+",[trim]))), <<"b">> = iolist_to_binary(join(re:split("b","[-az]+",[{parts, - 2}]))), - <<"b">> = iolist_to_binary(join(re:split("b","[-az]+",[]))), + 2}]))), + <<"b">> = iolist_to_binary(join(re:split("b","[-az]+",[]))), ok. run5() -> - <<"">> = iolist_to_binary(join(re:split("za-","[az-]+",[trim]))), + <<"">> = iolist_to_binary(join(re:split("za-","[az-]+",[trim]))), <<":">> = iolist_to_binary(join(re:split("za-","[az-]+",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("za-","[az-]+",[]))), - <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[az-]+",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("za-","[az-]+",[]))), + <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[az-]+",[trim]))), <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[az-]+",[{parts, - 2}]))), - <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[az-]+",[]))), - <<"b">> = iolist_to_binary(join(re:split("b","[az-]+",[trim]))), + 2}]))), + <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[az-]+",[]))), + <<"b">> = iolist_to_binary(join(re:split("b","[az-]+",[trim]))), <<"b">> = iolist_to_binary(join(re:split("b","[az-]+",[{parts, - 2}]))), - <<"b">> = iolist_to_binary(join(re:split("b","[az-]+",[]))), - <<"">> = iolist_to_binary(join(re:split("a-z","[a\\-z]+",[trim]))), + 2}]))), + <<"b">> = iolist_to_binary(join(re:split("b","[az-]+",[]))), + <<"">> = iolist_to_binary(join(re:split("a-z","[a\\-z]+",[trim]))), <<":">> = iolist_to_binary(join(re:split("a-z","[a\\-z]+",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a-z","[a\\-z]+",[]))), - <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[a\\-z]+",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a-z","[a\\-z]+",[]))), + <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[a\\-z]+",[trim]))), <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[a\\-z]+",[{parts, - 2}]))), - <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[a\\-z]+",[]))), - <<"b">> = iolist_to_binary(join(re:split("b","[a\\-z]+",[trim]))), + 2}]))), + <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[a\\-z]+",[]))), + <<"b">> = iolist_to_binary(join(re:split("b","[a\\-z]+",[trim]))), <<"b">> = iolist_to_binary(join(re:split("b","[a\\-z]+",[{parts, - 2}]))), - <<"b">> = iolist_to_binary(join(re:split("b","[a\\-z]+",[]))), - <<"">> = iolist_to_binary(join(re:split("abcdxyz","[a-z]+",[trim]))), + 2}]))), + <<"b">> = iolist_to_binary(join(re:split("b","[a\\-z]+",[]))), + <<"">> = iolist_to_binary(join(re:split("abcdxyz","[a-z]+",[trim]))), <<":">> = iolist_to_binary(join(re:split("abcdxyz","[a-z]+",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abcdxyz","[a-z]+",[]))), - <<"">> = iolist_to_binary(join(re:split("12-34","[\\d-]+",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abcdxyz","[a-z]+",[]))), + <<"">> = iolist_to_binary(join(re:split("12-34","[\\d-]+",[trim]))), <<":">> = iolist_to_binary(join(re:split("12-34","[\\d-]+",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("12-34","[\\d-]+",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\d-]+",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("12-34","[\\d-]+",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\d-]+",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\d-]+",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\d-]+",[]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","[\\d-]+",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\d-]+",[]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","[\\d-]+",[trim]))), <<"aaa">> = iolist_to_binary(join(re:split("aaa","[\\d-]+",[{parts, - 2}]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","[\\d-]+",[]))), - <<"">> = iolist_to_binary(join(re:split("12-34z","[\\d-z]+",[trim]))), + 2}]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","[\\d-]+",[]))), + <<"">> = iolist_to_binary(join(re:split("12-34z","[\\d-z]+",[trim]))), <<":">> = iolist_to_binary(join(re:split("12-34z","[\\d-z]+",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("12-34z","[\\d-z]+",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\d-z]+",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("12-34z","[\\d-z]+",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\d-z]+",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\d-z]+",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\d-z]+",[]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","[\\d-z]+",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\d-z]+",[]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","[\\d-z]+",[trim]))), <<"aaa">> = iolist_to_binary(join(re:split("aaa","[\\d-z]+",[{parts, - 2}]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","[\\d-z]+",[]))), - <<": ">> = iolist_to_binary(join(re:split("\\ ","\\x5c",[trim]))), + 2}]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","[\\d-z]+",[]))), + <<": ">> = iolist_to_binary(join(re:split("\\ ","\\x5c",[trim]))), <<": ">> = iolist_to_binary(join(re:split("\\ ","\\x5c",[{parts, - 2}]))), - <<": ">> = iolist_to_binary(join(re:split("\\ ","\\x5c",[]))), - <<"the:oo">> = iolist_to_binary(join(re:split("the Zoo","\\x20Z",[trim]))), + 2}]))), + <<": ">> = iolist_to_binary(join(re:split("\\ ","\\x5c",[]))), + <<"the:oo">> = iolist_to_binary(join(re:split("the Zoo","\\x20Z",[trim]))), <<"the:oo">> = iolist_to_binary(join(re:split("the Zoo","\\x20Z",[{parts, - 2}]))), - <<"the:oo">> = iolist_to_binary(join(re:split("the Zoo","\\x20Z",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\x20Z",[trim]))), + 2}]))), + <<"the:oo">> = iolist_to_binary(join(re:split("the Zoo","\\x20Z",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\x20Z",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\x20Z",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\x20Z",[]))), - <<"Zulu">> = iolist_to_binary(join(re:split("Zulu","\\x20Z",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\x20Z",[]))), + <<"Zulu">> = iolist_to_binary(join(re:split("Zulu","\\x20Z",[trim]))), <<"Zulu">> = iolist_to_binary(join(re:split("Zulu","\\x20Z",[{parts, - 2}]))), - <<"Zulu">> = iolist_to_binary(join(re:split("Zulu","\\x20Z",[]))), + 2}]))), + <<"Zulu">> = iolist_to_binary(join(re:split("Zulu","\\x20Z",[]))), <<":abc">> = iolist_to_binary(join(re:split("abcabc","(abc)\\1",[caseless, - trim]))), + trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abcabc","(abc)\\1",[caseless, {parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abcabc","(abc)\\1",[caseless]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abcabc","(abc)\\1",[caseless]))), <<":ABC">> = iolist_to_binary(join(re:split("ABCabc","(abc)\\1",[caseless, - trim]))), + trim]))), <<":ABC:">> = iolist_to_binary(join(re:split("ABCabc","(abc)\\1",[caseless, {parts, - 2}]))), - <<":ABC:">> = iolist_to_binary(join(re:split("ABCabc","(abc)\\1",[caseless]))), + 2}]))), + <<":ABC:">> = iolist_to_binary(join(re:split("ABCabc","(abc)\\1",[caseless]))), <<":abc">> = iolist_to_binary(join(re:split("abcABC","(abc)\\1",[caseless, - trim]))), + trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abcABC","(abc)\\1",[caseless, {parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abcABC","(abc)\\1",[caseless]))), - <<"">> = iolist_to_binary(join(re:split("ab{3cd","ab{3cd",[trim]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abcABC","(abc)\\1",[caseless]))), + <<"">> = iolist_to_binary(join(re:split("ab{3cd","ab{3cd",[trim]))), <<":">> = iolist_to_binary(join(re:split("ab{3cd","ab{3cd",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ab{3cd","ab{3cd",[]))), - <<"">> = iolist_to_binary(join(re:split("ab{3,cd","ab{3,cd",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ab{3cd","ab{3cd",[]))), + <<"">> = iolist_to_binary(join(re:split("ab{3,cd","ab{3,cd",[trim]))), <<":">> = iolist_to_binary(join(re:split("ab{3,cd","ab{3,cd",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ab{3,cd","ab{3,cd",[]))), - <<"">> = iolist_to_binary(join(re:split("ab{3,4a}cd","ab{3,4a}cd",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ab{3,cd","ab{3,cd",[]))), + <<"">> = iolist_to_binary(join(re:split("ab{3,4a}cd","ab{3,4a}cd",[trim]))), <<":">> = iolist_to_binary(join(re:split("ab{3,4a}cd","ab{3,4a}cd",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ab{3,4a}cd","ab{3,4a}cd",[]))), - <<"">> = iolist_to_binary(join(re:split("{4,5a}bc","{4,5a}bc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ab{3,4a}cd","ab{3,4a}cd",[]))), + <<"">> = iolist_to_binary(join(re:split("{4,5a}bc","{4,5a}bc",[trim]))), <<":">> = iolist_to_binary(join(re:split("{4,5a}bc","{4,5a}bc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("{4,5a}bc","{4,5a}bc",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","abc$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("{4,5a}bc","{4,5a}bc",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","abc$",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","abc$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","abc$",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","abc$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","abc$",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","abc$",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","abc$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","abc$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","abc$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc$",[]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc$",[]))), <<"abc def">> = iolist_to_binary(join(re:split("abc -def","abc$",[trim]))), +def","abc$",[trim]))), <<"abc def">> = iolist_to_binary(join(re:split("abc -def","abc$",[{parts,2}]))), +def","abc$",[{parts,2}]))), <<"abc def">> = iolist_to_binary(join(re:split("abc -def","abc$",[]))), - <<":abc">> = iolist_to_binary(join(re:split("abcS","(abc)\\123",[trim]))), +def","abc$",[]))), + <<":abc">> = iolist_to_binary(join(re:split("abcS","(abc)\\123",[trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abcS","(abc)\\123",[{parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abcS","(abc)\\123",[]))), - <<":abc">> = iolist_to_binary(join(re:split("abc","(abc)\\223",[trim]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abcS","(abc)\\123",[]))), + <<":abc">> = iolist_to_binary(join(re:split("abc","(abc)\\223",[trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abc","(abc)\\223",[{parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abc","(abc)\\223",[]))), - <<":abc">> = iolist_to_binary(join(re:split("abcÓ","(abc)\\323",[trim]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abc","(abc)\\223",[]))), + <<":abc">> = iolist_to_binary(join(re:split("abcÓ","(abc)\\323",[trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abcÓ","(abc)\\323",[{parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abcÓ","(abc)\\323",[]))), - <<":abc">> = iolist_to_binary(join(re:split("abc@","(abc)\\100",[trim]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abcÓ","(abc)\\323",[]))), + <<":abc">> = iolist_to_binary(join(re:split("abc@","(abc)\\100",[trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abc@","(abc)\\100",[{parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abc@","(abc)\\100",[]))), - <<":abc">> = iolist_to_binary(join(re:split("abc@","(abc)\\100",[trim]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abc@","(abc)\\100",[]))), + <<":abc">> = iolist_to_binary(join(re:split("abc@","(abc)\\100",[trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abc@","(abc)\\100",[{parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abc@","(abc)\\100",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[trim]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abc@","(abc)\\100",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[trim]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[trim]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[trim]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[trim]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[trim]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[]))), - <<":A:B:C:D:E:F:G:H:I">> = iolist_to_binary(join(re:split("ABCDEFGHIHI","^(A)(B)(C)(D)(E)(F)(G)(H)(I)\\8\\9$",[trim]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1000",[]))), + <<":A:B:C:D:E:F:G:H:I">> = iolist_to_binary(join(re:split("ABCDEFGHIHI","^(A)(B)(C)(D)(E)(F)(G)(H)(I)\\8\\9$",[trim]))), <<":A:B:C:D:E:F:G:H:I:">> = iolist_to_binary(join(re:split("ABCDEFGHIHI","^(A)(B)(C)(D)(E)(F)(G)(H)(I)\\8\\9$",[{parts, - 2}]))), - <<":A:B:C:D:E:F:G:H:I:">> = iolist_to_binary(join(re:split("ABCDEFGHIHI","^(A)(B)(C)(D)(E)(F)(G)(H)(I)\\8\\9$",[]))), + 2}]))), + <<":A:B:C:D:E:F:G:H:I:">> = iolist_to_binary(join(re:split("ABCDEFGHIHI","^(A)(B)(C)(D)(E)(F)(G)(H)(I)\\8\\9$",[]))), ok. run6() -> - <<"">> = iolist_to_binary(join(re:split("A8B9C","^[A\\8B\\9C]+$",[trim]))), + <<"">> = iolist_to_binary(join(re:split("A8B9C","^[A\\8B\\9C]+$",[trim]))), <<":">> = iolist_to_binary(join(re:split("A8B9C","^[A\\8B\\9C]+$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("A8B9C","^[A\\8B\\9C]+$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[A\\8B\\9C]+$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("A8B9C","^[A\\8B\\9C]+$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[A\\8B\\9C]+$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[A\\8B\\9C]+$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[A\\8B\\9C]+$",[]))), - <<"">> = iolist_to_binary(join(re:split("A8B9C","^[A\\8B\\9C]+$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[A\\8B\\9C]+$",[]))), + <<"">> = iolist_to_binary(join(re:split("A8B9C","^[A\\8B\\9C]+$",[trim]))), <<":">> = iolist_to_binary(join(re:split("A8B9C","^[A\\8B\\9C]+$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("A8B9C","^[A\\8B\\9C]+$",[]))), - <<":a:b:c:d:e:f:g:h:i:j:k:l">> = iolist_to_binary(join(re:split("abcdefghijkllS","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\\12\\123",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("A8B9C","^[A\\8B\\9C]+$",[]))), + <<":a:b:c:d:e:f:g:h:i:j:k:l">> = iolist_to_binary(join(re:split("abcdefghijkllS","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\\12\\123",[trim]))), <<":a:b:c:d:e:f:g:h:i:j:k:l:">> = iolist_to_binary(join(re:split("abcdefghijkllS","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\\12\\123",[{parts, - 2}]))), - <<":a:b:c:d:e:f:g:h:i:j:k:l:">> = iolist_to_binary(join(re:split("abcdefghijkllS","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\\12\\123",[]))), + 2}]))), + <<":a:b:c:d:e:f:g:h:i:j:k:l:">> = iolist_to_binary(join(re:split("abcdefghijkllS","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\\12\\123",[]))), <<":a:b:c:d:e:f:g:h:i:j:k">> = iolist_to_binary(join(re:split("abcdefghijk -S","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\12\\123",[trim]))), +S","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\12\\123",[trim]))), <<":a:b:c:d:e:f:g:h:i:j:k:">> = iolist_to_binary(join(re:split("abcdefghijk -S","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\12\\123",[{parts,2}]))), +S","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\12\\123",[{parts,2}]))), <<":a:b:c:d:e:f:g:h:i:j:k:">> = iolist_to_binary(join(re:split("abcdefghijk -S","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\12\\123",[]))), - <<"">> = iolist_to_binary(join(re:split("abidef","ab\\idef",[trim]))), +S","(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)\\12\\123",[]))), + <<"">> = iolist_to_binary(join(re:split("abidef","ab\\idef",[trim]))), <<":">> = iolist_to_binary(join(re:split("abidef","ab\\idef",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abidef","ab\\idef",[]))), - <<"">> = iolist_to_binary(join(re:split("bc","a{0}bc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abidef","ab\\idef",[]))), + <<"">> = iolist_to_binary(join(re:split("bc","a{0}bc",[trim]))), <<":">> = iolist_to_binary(join(re:split("bc","a{0}bc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("bc","a{0}bc",[]))), - <<"">> = iolist_to_binary(join(re:split("xyz","(a|(bc)){0,0}?xyz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("bc","a{0}bc",[]))), + <<"">> = iolist_to_binary(join(re:split("xyz","(a|(bc)){0,0}?xyz",[trim]))), <<":::">> = iolist_to_binary(join(re:split("xyz","(a|(bc)){0,0}?xyz",[{parts, - 2}]))), - <<":::">> = iolist_to_binary(join(re:split("xyz","(a|(bc)){0,0}?xyz",[]))), - <<"">> = iolist_to_binary(join(re:split("abcde","abc[\\10]de",[trim]))), + 2}]))), + <<":::">> = iolist_to_binary(join(re:split("xyz","(a|(bc)){0,0}?xyz",[]))), + <<"">> = iolist_to_binary(join(re:split("abcde","abc[\\10]de",[trim]))), <<":">> = iolist_to_binary(join(re:split("abcde","abc[\\10]de",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abcde","abc[\\10]de",[]))), - <<"">> = iolist_to_binary(join(re:split("abcde","abc[\\1]de",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abcde","abc[\\10]de",[]))), + <<"">> = iolist_to_binary(join(re:split("abcde","abc[\\1]de",[trim]))), <<":">> = iolist_to_binary(join(re:split("abcde","abc[\\1]de",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abcde","abc[\\1]de",[]))), - <<":abc">> = iolist_to_binary(join(re:split("abcde","(abc)[\\1]de",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abcde","abc[\\1]de",[]))), + <<":abc">> = iolist_to_binary(join(re:split("abcde","(abc)[\\1]de",[trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abcde","(abc)[\\1]de",[{parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abcde","(abc)[\\1]de",[]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abcde","(abc)[\\1]de",[]))), <<"">> = iolist_to_binary(join(re:split("a -b","(?s)a.b",[trim]))), +b","(?s)a.b",[trim]))), <<":">> = iolist_to_binary(join(re:split("a -b","(?s)a.b",[{parts,2}]))), +b","(?s)a.b",[{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("a -b","(?s)a.b",[]))), - <<":b:a:NOT:cccc:d">> = iolist_to_binary(join(re:split("baNOTccccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))), +b","(?s)a.b",[]))), + <<":b:a:NOT:cccc:d">> = iolist_to_binary(join(re:split("baNOTccccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))), <<":b:a:NOT:cccc:d">> = iolist_to_binary(join(re:split("baNOTccccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[{parts, - 2}]))), - <<":b:a:NOT:cccc:d">> = iolist_to_binary(join(re:split("baNOTccccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))), - <<":b:a:NOT:ccc:d">> = iolist_to_binary(join(re:split("baNOTcccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))), + 2}]))), + <<":b:a:NOT:cccc:d">> = iolist_to_binary(join(re:split("baNOTccccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))), + <<":b:a:NOT:ccc:d">> = iolist_to_binary(join(re:split("baNOTcccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))), <<":b:a:NOT:ccc:d">> = iolist_to_binary(join(re:split("baNOTcccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[{parts, - 2}]))), - <<":b:a:NOT:ccc:d">> = iolist_to_binary(join(re:split("baNOTcccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))), - <<":b:a:NO:Tcc:d">> = iolist_to_binary(join(re:split("baNOTccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))), + 2}]))), + <<":b:a:NOT:ccc:d">> = iolist_to_binary(join(re:split("baNOTcccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))), + <<":b:a:NO:Tcc:d">> = iolist_to_binary(join(re:split("baNOTccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))), <<":b:a:NO:Tcc:d">> = iolist_to_binary(join(re:split("baNOTccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[{parts, - 2}]))), - <<":b:a:NO:Tcc:d">> = iolist_to_binary(join(re:split("baNOTccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))), - <<":b:a::ccc:d">> = iolist_to_binary(join(re:split("bacccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))), + 2}]))), + <<":b:a:NO:Tcc:d">> = iolist_to_binary(join(re:split("baNOTccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))), + <<":b:a::ccc:d">> = iolist_to_binary(join(re:split("bacccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))), <<":b:a::ccc:d">> = iolist_to_binary(join(re:split("bacccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[{parts, - 2}]))), - <<":b:a::ccc:d">> = iolist_to_binary(join(re:split("bacccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))), - <<":*:*:* Fail:ers">> = iolist_to_binary(join(re:split("*** Failers","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))), + 2}]))), + <<":b:a::ccc:d">> = iolist_to_binary(join(re:split("bacccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))), + <<":*:*:* Fail:ers">> = iolist_to_binary(join(re:split("*** Failers","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))), <<":*:*:* Fail:ers:">> = iolist_to_binary(join(re:split("*** Failers","^([^a])([^\\b])([^c]*)([^d]{3,4})",[{parts, - 2}]))), - <<":*:*:* Fail:ers:">> = iolist_to_binary(join(re:split("*** Failers","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))), - <<"anything">> = iolist_to_binary(join(re:split("anything","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))), + 2}]))), + <<":*:*:* Fail:ers:">> = iolist_to_binary(join(re:split("*** Failers","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))), + <<"anything">> = iolist_to_binary(join(re:split("anything","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))), <<"anything">> = iolist_to_binary(join(re:split("anything","^([^a])([^\\b])([^c]*)([^d]{3,4})",[{parts, - 2}]))), - <<"anything">> = iolist_to_binary(join(re:split("anything","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))), - <<"bc">> = iolist_to_binary(join(re:split("bc","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))), + 2}]))), + <<"anything">> = iolist_to_binary(join(re:split("anything","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))), + <<"bc">> = iolist_to_binary(join(re:split("bc","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))), <<"bc">> = iolist_to_binary(join(re:split("bc","^([^a])([^\\b])([^c]*)([^d]{3,4})",[{parts, - 2}]))), - <<"bc">> = iolist_to_binary(join(re:split("bc","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))), - <<"baccd">> = iolist_to_binary(join(re:split("baccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))), + 2}]))), + <<"bc">> = iolist_to_binary(join(re:split("bc","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))), + <<"baccd">> = iolist_to_binary(join(re:split("baccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[trim]))), <<"baccd">> = iolist_to_binary(join(re:split("baccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[{parts, - 2}]))), - <<"baccd">> = iolist_to_binary(join(re:split("baccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))), - <<"">> = iolist_to_binary(join(re:split("Abc","[^a]",[trim]))), + 2}]))), + <<"baccd">> = iolist_to_binary(join(re:split("baccd","^([^a])([^\\b])([^c]*)([^d]{3,4})",[]))), + <<"">> = iolist_to_binary(join(re:split("Abc","[^a]",[trim]))), <<":bc">> = iolist_to_binary(join(re:split("Abc","[^a]",[{parts, - 2}]))), - <<":::">> = iolist_to_binary(join(re:split("Abc","[^a]",[]))), + 2}]))), + <<":::">> = iolist_to_binary(join(re:split("Abc","[^a]",[]))), <<"A">> = iolist_to_binary(join(re:split("Abc","[^a]",[caseless, - trim]))), + trim]))), <<"A:c">> = iolist_to_binary(join(re:split("Abc","[^a]",[caseless, {parts, - 2}]))), - <<"A::">> = iolist_to_binary(join(re:split("Abc","[^a]",[caseless]))), - <<":a">> = iolist_to_binary(join(re:split("AAAaAbc","[^a]+",[trim]))), + 2}]))), + <<"A::">> = iolist_to_binary(join(re:split("Abc","[^a]",[caseless]))), + <<":a">> = iolist_to_binary(join(re:split("AAAaAbc","[^a]+",[trim]))), <<":aAbc">> = iolist_to_binary(join(re:split("AAAaAbc","[^a]+",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("AAAaAbc","[^a]+",[]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("AAAaAbc","[^a]+",[]))), <<"AAAaA">> = iolist_to_binary(join(re:split("AAAaAbc","[^a]+",[caseless, - trim]))), + trim]))), <<"AAAaA:">> = iolist_to_binary(join(re:split("AAAaAbc","[^a]+",[caseless, {parts, - 2}]))), - <<"AAAaA:">> = iolist_to_binary(join(re:split("AAAaAbc","[^a]+",[caseless]))), + 2}]))), + <<"AAAaA:">> = iolist_to_binary(join(re:split("AAAaAbc","[^a]+",[caseless]))), <<"">> = iolist_to_binary(join(re:split("bbb -ccc","[^a]+",[trim]))), +ccc","[^a]+",[trim]))), <<":">> = iolist_to_binary(join(re:split("bbb -ccc","[^a]+",[{parts,2}]))), +ccc","[^a]+",[{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("bbb -ccc","[^a]+",[]))), - <<"ab">> = iolist_to_binary(join(re:split("abc","[^k]$",[trim]))), +ccc","[^a]+",[]))), + <<"ab">> = iolist_to_binary(join(re:split("abc","[^k]$",[trim]))), <<"ab:">> = iolist_to_binary(join(re:split("abc","[^k]$",[{parts, - 2}]))), - <<"ab:">> = iolist_to_binary(join(re:split("abc","[^k]$",[]))), - <<"*** Failer">> = iolist_to_binary(join(re:split("*** Failers","[^k]$",[trim]))), + 2}]))), + <<"ab:">> = iolist_to_binary(join(re:split("abc","[^k]$",[]))), + <<"*** Failer">> = iolist_to_binary(join(re:split("*** Failers","[^k]$",[trim]))), <<"*** Failer:">> = iolist_to_binary(join(re:split("*** Failers","[^k]$",[{parts, - 2}]))), - <<"*** Failer:">> = iolist_to_binary(join(re:split("*** Failers","[^k]$",[]))), - <<"abk">> = iolist_to_binary(join(re:split("abk","[^k]$",[trim]))), + 2}]))), + <<"*** Failer:">> = iolist_to_binary(join(re:split("*** Failers","[^k]$",[]))), + <<"abk">> = iolist_to_binary(join(re:split("abk","[^k]$",[trim]))), <<"abk">> = iolist_to_binary(join(re:split("abk","[^k]$",[{parts, - 2}]))), - <<"abk">> = iolist_to_binary(join(re:split("abk","[^k]$",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","[^k]{2,3}$",[trim]))), + 2}]))), + <<"abk">> = iolist_to_binary(join(re:split("abk","[^k]$",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","[^k]{2,3}$",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","[^k]{2,3}$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","[^k]{2,3}$",[]))), - <<"k">> = iolist_to_binary(join(re:split("kbc","[^k]{2,3}$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","[^k]{2,3}$",[]))), + <<"k">> = iolist_to_binary(join(re:split("kbc","[^k]{2,3}$",[trim]))), <<"k:">> = iolist_to_binary(join(re:split("kbc","[^k]{2,3}$",[{parts, - 2}]))), - <<"k:">> = iolist_to_binary(join(re:split("kbc","[^k]{2,3}$",[]))), - <<"k">> = iolist_to_binary(join(re:split("kabc","[^k]{2,3}$",[trim]))), + 2}]))), + <<"k:">> = iolist_to_binary(join(re:split("kbc","[^k]{2,3}$",[]))), + <<"k">> = iolist_to_binary(join(re:split("kabc","[^k]{2,3}$",[trim]))), <<"k:">> = iolist_to_binary(join(re:split("kabc","[^k]{2,3}$",[{parts, - 2}]))), - <<"k:">> = iolist_to_binary(join(re:split("kabc","[^k]{2,3}$",[]))), - <<"*** Fail">> = iolist_to_binary(join(re:split("*** Failers","[^k]{2,3}$",[trim]))), + 2}]))), + <<"k:">> = iolist_to_binary(join(re:split("kabc","[^k]{2,3}$",[]))), + <<"*** Fail">> = iolist_to_binary(join(re:split("*** Failers","[^k]{2,3}$",[trim]))), <<"*** Fail:">> = iolist_to_binary(join(re:split("*** Failers","[^k]{2,3}$",[{parts, - 2}]))), - <<"*** Fail:">> = iolist_to_binary(join(re:split("*** Failers","[^k]{2,3}$",[]))), - <<"abk">> = iolist_to_binary(join(re:split("abk","[^k]{2,3}$",[trim]))), + 2}]))), + <<"*** Fail:">> = iolist_to_binary(join(re:split("*** Failers","[^k]{2,3}$",[]))), + <<"abk">> = iolist_to_binary(join(re:split("abk","[^k]{2,3}$",[trim]))), <<"abk">> = iolist_to_binary(join(re:split("abk","[^k]{2,3}$",[{parts, - 2}]))), - <<"abk">> = iolist_to_binary(join(re:split("abk","[^k]{2,3}$",[]))), - <<"akb">> = iolist_to_binary(join(re:split("akb","[^k]{2,3}$",[trim]))), + 2}]))), + <<"abk">> = iolist_to_binary(join(re:split("abk","[^k]{2,3}$",[]))), + <<"akb">> = iolist_to_binary(join(re:split("akb","[^k]{2,3}$",[trim]))), <<"akb">> = iolist_to_binary(join(re:split("akb","[^k]{2,3}$",[{parts, - 2}]))), - <<"akb">> = iolist_to_binary(join(re:split("akb","[^k]{2,3}$",[]))), - <<"akk">> = iolist_to_binary(join(re:split("akk","[^k]{2,3}$",[trim]))), + 2}]))), + <<"akb">> = iolist_to_binary(join(re:split("akb","[^k]{2,3}$",[]))), + <<"akk">> = iolist_to_binary(join(re:split("akk","[^k]{2,3}$",[trim]))), <<"akk">> = iolist_to_binary(join(re:split("akk","[^k]{2,3}$",[{parts, - 2}]))), - <<"akk">> = iolist_to_binary(join(re:split("akk","[^k]{2,3}$",[]))), - <<"12345678.b.c.d">> = iolist_to_binary(join(re:split("12345678.b.c.d","^\\d{8,}\\@.+[^k]$",[trim]))), + 2}]))), + <<"akk">> = iolist_to_binary(join(re:split("akk","[^k]{2,3}$",[]))), + <<"12345678.b.c.d">> = iolist_to_binary(join(re:split("12345678.b.c.d","^\\d{8,}\\@.+[^k]$",[trim]))), <<"12345678.b.c.d">> = iolist_to_binary(join(re:split("12345678.b.c.d","^\\d{8,}\\@.+[^k]$",[{parts, - 2}]))), - <<"12345678.b.c.d">> = iolist_to_binary(join(re:split("12345678.b.c.d","^\\d{8,}\\@.+[^k]$",[]))), - <<"123456789.y.z">> = iolist_to_binary(join(re:split("123456789.y.z","^\\d{8,}\\@.+[^k]$",[trim]))), + 2}]))), + <<"12345678.b.c.d">> = iolist_to_binary(join(re:split("12345678.b.c.d","^\\d{8,}\\@.+[^k]$",[]))), + <<"123456789.y.z">> = iolist_to_binary(join(re:split("123456789.y.z","^\\d{8,}\\@.+[^k]$",[trim]))), <<"123456789.y.z">> = iolist_to_binary(join(re:split("123456789.y.z","^\\d{8,}\\@.+[^k]$",[{parts, - 2}]))), - <<"123456789.y.z">> = iolist_to_binary(join(re:split("123456789.y.z","^\\d{8,}\\@.+[^k]$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\d{8,}\\@.+[^k]$",[trim]))), + 2}]))), + <<"123456789.y.z">> = iolist_to_binary(join(re:split("123456789.y.z","^\\d{8,}\\@.+[^k]$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\d{8,}\\@.+[^k]$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\d{8,}\\@.+[^k]$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\d{8,}\\@.+[^k]$",[]))), - <<"12345678.y.uk">> = iolist_to_binary(join(re:split("12345678.y.uk","^\\d{8,}\\@.+[^k]$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\d{8,}\\@.+[^k]$",[]))), + <<"12345678.y.uk">> = iolist_to_binary(join(re:split("12345678.y.uk","^\\d{8,}\\@.+[^k]$",[trim]))), <<"12345678.y.uk">> = iolist_to_binary(join(re:split("12345678.y.uk","^\\d{8,}\\@.+[^k]$",[{parts, - 2}]))), - <<"12345678.y.uk">> = iolist_to_binary(join(re:split("12345678.y.uk","^\\d{8,}\\@.+[^k]$",[]))), - <<"1234567.b.c.d">> = iolist_to_binary(join(re:split("1234567.b.c.d","^\\d{8,}\\@.+[^k]$",[trim]))), + 2}]))), + <<"12345678.y.uk">> = iolist_to_binary(join(re:split("12345678.y.uk","^\\d{8,}\\@.+[^k]$",[]))), + <<"1234567.b.c.d">> = iolist_to_binary(join(re:split("1234567.b.c.d","^\\d{8,}\\@.+[^k]$",[trim]))), <<"1234567.b.c.d">> = iolist_to_binary(join(re:split("1234567.b.c.d","^\\d{8,}\\@.+[^k]$",[{parts, - 2}]))), - <<"1234567.b.c.d">> = iolist_to_binary(join(re:split("1234567.b.c.d","^\\d{8,}\\@.+[^k]$",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaaaaaaaa","(a)\\1{8,}",[trim]))), + 2}]))), + <<"1234567.b.c.d">> = iolist_to_binary(join(re:split("1234567.b.c.d","^\\d{8,}\\@.+[^k]$",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaaaaaaaa","(a)\\1{8,}",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaaa","(a)\\1{8,}",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaaa","(a)\\1{8,}",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaaaaaaaaa","(a)\\1{8,}",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaaa","(a)\\1{8,}",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaaaaaaaaa","(a)\\1{8,}",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","(a)\\1{8,}",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","(a)\\1{8,}",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a)\\1{8,}",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","(a)\\1{8,}",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a)\\1{8,}",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a)\\1{8,}",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a)\\1{8,}",[]))), - <<"aaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaa","(a)\\1{8,}",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a)\\1{8,}",[]))), + <<"aaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaa","(a)\\1{8,}",[trim]))), <<"aaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaa","(a)\\1{8,}",[{parts, - 2}]))), - <<"aaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaa","(a)\\1{8,}",[]))), + 2}]))), + <<"aaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaa","(a)\\1{8,}",[]))), ok. run7() -> - <<"aaaa">> = iolist_to_binary(join(re:split("aaaabcd","[^a]",[trim]))), + <<"aaaa">> = iolist_to_binary(join(re:split("aaaabcd","[^a]",[trim]))), <<"aaaa:cd">> = iolist_to_binary(join(re:split("aaaabcd","[^a]",[{parts, - 2}]))), - <<"aaaa:::">> = iolist_to_binary(join(re:split("aaaabcd","[^a]",[]))), - <<"aa:a">> = iolist_to_binary(join(re:split("aaAabcd","[^a]",[trim]))), + 2}]))), + <<"aaaa:::">> = iolist_to_binary(join(re:split("aaaabcd","[^a]",[]))), + <<"aa:a">> = iolist_to_binary(join(re:split("aaAabcd","[^a]",[trim]))), <<"aa:abcd">> = iolist_to_binary(join(re:split("aaAabcd","[^a]",[{parts, - 2}]))), - <<"aa:a:::">> = iolist_to_binary(join(re:split("aaAabcd","[^a]",[]))), + 2}]))), + <<"aa:a:::">> = iolist_to_binary(join(re:split("aaAabcd","[^a]",[]))), <<"aaaa">> = iolist_to_binary(join(re:split("aaaabcd","[^a]",[caseless, - trim]))), + trim]))), <<"aaaa:cd">> = iolist_to_binary(join(re:split("aaaabcd","[^a]",[caseless, {parts, - 2}]))), - <<"aaaa:::">> = iolist_to_binary(join(re:split("aaaabcd","[^a]",[caseless]))), + 2}]))), + <<"aaaa:::">> = iolist_to_binary(join(re:split("aaaabcd","[^a]",[caseless]))), <<"aaAa">> = iolist_to_binary(join(re:split("aaAabcd","[^a]",[caseless, - trim]))), + trim]))), <<"aaAa:cd">> = iolist_to_binary(join(re:split("aaAabcd","[^a]",[caseless, {parts, - 2}]))), - <<"aaAa:::">> = iolist_to_binary(join(re:split("aaAabcd","[^a]",[caseless]))), - <<"aaaa">> = iolist_to_binary(join(re:split("aaaabcd","[^az]",[trim]))), + 2}]))), + <<"aaAa:::">> = iolist_to_binary(join(re:split("aaAabcd","[^a]",[caseless]))), + <<"aaaa">> = iolist_to_binary(join(re:split("aaaabcd","[^az]",[trim]))), <<"aaaa:cd">> = iolist_to_binary(join(re:split("aaaabcd","[^az]",[{parts, - 2}]))), - <<"aaaa:::">> = iolist_to_binary(join(re:split("aaaabcd","[^az]",[]))), - <<"aa:a">> = iolist_to_binary(join(re:split("aaAabcd","[^az]",[trim]))), + 2}]))), + <<"aaaa:::">> = iolist_to_binary(join(re:split("aaaabcd","[^az]",[]))), + <<"aa:a">> = iolist_to_binary(join(re:split("aaAabcd","[^az]",[trim]))), <<"aa:abcd">> = iolist_to_binary(join(re:split("aaAabcd","[^az]",[{parts, - 2}]))), - <<"aa:a:::">> = iolist_to_binary(join(re:split("aaAabcd","[^az]",[]))), + 2}]))), + <<"aa:a:::">> = iolist_to_binary(join(re:split("aaAabcd","[^az]",[]))), <<"aaaa">> = iolist_to_binary(join(re:split("aaaabcd","[^az]",[caseless, - trim]))), + trim]))), <<"aaaa:cd">> = iolist_to_binary(join(re:split("aaaabcd","[^az]",[caseless, {parts, - 2}]))), - <<"aaaa:::">> = iolist_to_binary(join(re:split("aaaabcd","[^az]",[caseless]))), + 2}]))), + <<"aaaa:::">> = iolist_to_binary(join(re:split("aaaabcd","[^az]",[caseless]))), <<"aaAa">> = iolist_to_binary(join(re:split("aaAabcd","[^az]",[caseless, - trim]))), + trim]))), <<"aaAa:cd">> = iolist_to_binary(join(re:split("aaAabcd","[^az]",[caseless, {parts, - 2}]))), - <<"aaAa:::">> = iolist_to_binary(join(re:split("aaAabcd","[^az]",[caseless]))), - <<"xxxxxxxxxxx:xxxxxxxxx">> = iolist_to_binary(join(re:split("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,6}?LL",[trim]))), + 2}]))), + <<"aaAa:::">> = iolist_to_binary(join(re:split("aaAabcd","[^az]",[caseless]))), + <<"xxxxxxxxxxx:xxxxxxxxx">> = iolist_to_binary(join(re:split("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,6}?LL",[trim]))), <<"xxxxxxxxxxx:xxxxxxxxx">> = iolist_to_binary(join(re:split("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,6}?LL",[{parts, - 2}]))), - <<"xxxxxxxxxxx:xxxxxxxxx">> = iolist_to_binary(join(re:split("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,6}?LL",[]))), - <<"xxxxxxxxxxx:xxxxxxxxx">> = iolist_to_binary(join(re:split("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,}?LL",[trim]))), + 2}]))), + <<"xxxxxxxxxxx:xxxxxxxxx">> = iolist_to_binary(join(re:split("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,6}?LL",[]))), + <<"xxxxxxxxxxx:xxxxxxxxx">> = iolist_to_binary(join(re:split("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,}?LL",[trim]))), <<"xxxxxxxxxxx:xxxxxxxxx">> = iolist_to_binary(join(re:split("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,}?LL",[{parts, - 2}]))), - <<"xxxxxxxxxxx:xxxxxxxxx">> = iolist_to_binary(join(re:split("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,}?LL",[]))), - <<"1:.23">> = iolist_to_binary(join(re:split("1.230003938","(\\.\\d\\d[1-9]?)\\d+",[trim]))), + 2}]))), + <<"xxxxxxxxxxx:xxxxxxxxx">> = iolist_to_binary(join(re:split("xxxxxxxxxxxPSTAIREISLLxxxxxxxxx","P[^*]TAIRE[^*]{1,}?LL",[]))), + <<"1:.23">> = iolist_to_binary(join(re:split("1.230003938","(\\.\\d\\d[1-9]?)\\d+",[trim]))), <<"1:.23:">> = iolist_to_binary(join(re:split("1.230003938","(\\.\\d\\d[1-9]?)\\d+",[{parts, - 2}]))), - <<"1:.23:">> = iolist_to_binary(join(re:split("1.230003938","(\\.\\d\\d[1-9]?)\\d+",[]))), - <<"1:.875">> = iolist_to_binary(join(re:split("1.875000282","(\\.\\d\\d[1-9]?)\\d+",[trim]))), + 2}]))), + <<"1:.23:">> = iolist_to_binary(join(re:split("1.230003938","(\\.\\d\\d[1-9]?)\\d+",[]))), + <<"1:.875">> = iolist_to_binary(join(re:split("1.875000282","(\\.\\d\\d[1-9]?)\\d+",[trim]))), <<"1:.875:">> = iolist_to_binary(join(re:split("1.875000282","(\\.\\d\\d[1-9]?)\\d+",[{parts, - 2}]))), - <<"1:.875:">> = iolist_to_binary(join(re:split("1.875000282","(\\.\\d\\d[1-9]?)\\d+",[]))), - <<"1:.23">> = iolist_to_binary(join(re:split("1.235","(\\.\\d\\d[1-9]?)\\d+",[trim]))), + 2}]))), + <<"1:.875:">> = iolist_to_binary(join(re:split("1.875000282","(\\.\\d\\d[1-9]?)\\d+",[]))), + <<"1:.23">> = iolist_to_binary(join(re:split("1.235","(\\.\\d\\d[1-9]?)\\d+",[trim]))), <<"1:.23:">> = iolist_to_binary(join(re:split("1.235","(\\.\\d\\d[1-9]?)\\d+",[{parts, - 2}]))), - <<"1:.23:">> = iolist_to_binary(join(re:split("1.235","(\\.\\d\\d[1-9]?)\\d+",[]))), - <<"1:.23::0003938">> = iolist_to_binary(join(re:split("1.230003938","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[trim]))), + 2}]))), + <<"1:.23:">> = iolist_to_binary(join(re:split("1.235","(\\.\\d\\d[1-9]?)\\d+",[]))), + <<"1:.23::0003938">> = iolist_to_binary(join(re:split("1.230003938","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[trim]))), <<"1:.23::0003938">> = iolist_to_binary(join(re:split("1.230003938","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[{parts, - 2}]))), - <<"1:.23::0003938">> = iolist_to_binary(join(re:split("1.230003938","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[]))), - <<"1:.875:5:000282">> = iolist_to_binary(join(re:split("1.875000282","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[trim]))), + 2}]))), + <<"1:.23::0003938">> = iolist_to_binary(join(re:split("1.230003938","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[]))), + <<"1:.875:5:000282">> = iolist_to_binary(join(re:split("1.875000282","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[trim]))), <<"1:.875:5:000282">> = iolist_to_binary(join(re:split("1.875000282","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[{parts, - 2}]))), - <<"1:.875:5:000282">> = iolist_to_binary(join(re:split("1.875000282","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[trim]))), + 2}]))), + <<"1:.875:5:000282">> = iolist_to_binary(join(re:split("1.875000282","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[]))), - <<"1.235">> = iolist_to_binary(join(re:split("1.235","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[]))), + <<"1.235">> = iolist_to_binary(join(re:split("1.235","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[trim]))), <<"1.235">> = iolist_to_binary(join(re:split("1.235","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[{parts, - 2}]))), - <<"1.235">> = iolist_to_binary(join(re:split("1.235","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[]))), - <<"">> = iolist_to_binary(join(re:split("ab","a(?)b",[trim]))), + 2}]))), + <<"1.235">> = iolist_to_binary(join(re:split("1.235","(\\.\\d\\d((?=0)|\\d(?=\\d)))",[]))), + <<"">> = iolist_to_binary(join(re:split("ab","a(?)b",[trim]))), <<":">> = iolist_to_binary(join(re:split("ab","a(?)b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ab","a(?)b",[]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ab","a(?)b",[]))), <<"Food is on the :foo:table">> = iolist_to_binary(join(re:split("Food is on the foo table","\\b(foo)\\s+(\\w+)",[caseless, - trim]))), + trim]))), <<"Food is on the :foo:table:">> = iolist_to_binary(join(re:split("Food is on the foo table","\\b(foo)\\s+(\\w+)",[caseless, {parts, - 2}]))), - <<"Food is on the :foo:table:">> = iolist_to_binary(join(re:split("Food is on the foo table","\\b(foo)\\s+(\\w+)",[caseless]))), - <<"The :d is under the bar in the :n.">> = iolist_to_binary(join(re:split("The food is under the bar in the barn.","foo(.*)bar",[trim]))), + 2}]))), + <<"Food is on the :foo:table:">> = iolist_to_binary(join(re:split("Food is on the foo table","\\b(foo)\\s+(\\w+)",[caseless]))), + <<"The :d is under the bar in the :n.">> = iolist_to_binary(join(re:split("The food is under the bar in the barn.","foo(.*)bar",[trim]))), <<"The :d is under the bar in the :n.">> = iolist_to_binary(join(re:split("The food is under the bar in the barn.","foo(.*)bar",[{parts, - 2}]))), - <<"The :d is under the bar in the :n.">> = iolist_to_binary(join(re:split("The food is under the bar in the barn.","foo(.*)bar",[]))), - <<"The :d is under the : in the barn.">> = iolist_to_binary(join(re:split("The food is under the bar in the barn.","foo(.*?)bar",[trim]))), + 2}]))), + <<"The :d is under the bar in the :n.">> = iolist_to_binary(join(re:split("The food is under the bar in the barn.","foo(.*)bar",[]))), + <<"The :d is under the : in the barn.">> = iolist_to_binary(join(re:split("The food is under the bar in the barn.","foo(.*?)bar",[trim]))), <<"The :d is under the : in the barn.">> = iolist_to_binary(join(re:split("The food is under the bar in the barn.","foo(.*?)bar",[{parts, - 2}]))), - <<"The :d is under the : in the barn.">> = iolist_to_binary(join(re:split("The food is under the bar in the barn.","foo(.*?)bar",[]))), - <<":I have 2 numbers: 53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d*)",[trim]))), + 2}]))), + <<"The :d is under the : in the barn.">> = iolist_to_binary(join(re:split("The food is under the bar in the barn.","foo(.*?)bar",[]))), + <<":I have 2 numbers: 53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d*)",[trim]))), <<":I have 2 numbers: 53147::">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d*)",[{parts, - 2}]))), - <<":I have 2 numbers: 53147::">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d*)",[]))), - <<":I have 2 numbers: 5314:7">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d+)",[trim]))), + 2}]))), + <<":I have 2 numbers: 53147::">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d*)",[]))), + <<":I have 2 numbers: 5314:7">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d+)",[trim]))), <<":I have 2 numbers: 5314:7:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d+)",[{parts, - 2}]))), - <<":I have 2 numbers: 5314:7:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d+)",[]))), - <<":I::: :::h:::a:::v:::e::: :2:: :::n:::u:::m:::b:::e:::r:::s::::::: :53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d*)",[trim]))), + 2}]))), + <<":I have 2 numbers: 5314:7:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d+)",[]))), + <<":I::: :::h:::a:::v:::e::: :2:: :::n:::u:::m:::b:::e:::r:::s::::::: :53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d*)",[trim]))), <<":I:: have 2 numbers: 53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d*)",[{parts, - 2}]))), - <<":I::: :::h:::a:::v:::e::: :2:: :::n:::u:::m:::b:::e:::r:::s::::::: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d*)",[]))), - <<":I have :2:: numbers: :53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d+)",[trim]))), + 2}]))), + <<":I::: :::h:::a:::v:::e::: :2:: :::n:::u:::m:::b:::e:::r:::s::::::: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d*)",[]))), + <<":I have :2:: numbers: :53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d+)",[trim]))), <<":I have :2: numbers: 53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d+)",[{parts, - 2}]))), - <<":I have :2:: numbers: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d+)",[]))), - <<":I have 2 numbers: 5314:7">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d+)$",[trim]))), + 2}]))), + <<":I have :2:: numbers: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d+)",[]))), + <<":I have 2 numbers: 5314:7">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d+)$",[trim]))), <<":I have 2 numbers: 5314:7:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d+)$",[{parts, - 2}]))), - <<":I have 2 numbers: 5314:7:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d+)$",[]))), - <<":I have 2 numbers: :53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d+)$",[trim]))), + 2}]))), + <<":I have 2 numbers: 5314:7:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)(\\d+)$",[]))), + <<":I have 2 numbers: :53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d+)$",[trim]))), <<":I have 2 numbers: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d+)$",[{parts, - 2}]))), - <<":I have 2 numbers: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d+)$",[]))), - <<":I have 2 numbers: :53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)\\b(\\d+)$",[trim]))), + 2}]))), + <<":I have 2 numbers: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*?)(\\d+)$",[]))), + <<":I have 2 numbers: :53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)\\b(\\d+)$",[trim]))), <<":I have 2 numbers: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)\\b(\\d+)$",[{parts, - 2}]))), - <<":I have 2 numbers: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)\\b(\\d+)$",[]))), + 2}]))), + <<":I have 2 numbers: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*)\\b(\\d+)$",[]))), ok. run8() -> - <<":I have 2 numbers: :53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*\\D)(\\d+)$",[trim]))), + <<":I have 2 numbers: :53147">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*\\D)(\\d+)$",[trim]))), <<":I have 2 numbers: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*\\D)(\\d+)$",[{parts, - 2}]))), - <<":I have 2 numbers: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*\\D)(\\d+)$",[]))), - <<":C123">> = iolist_to_binary(join(re:split("ABC123","^\\D*(?!123)",[trim]))), + 2}]))), + <<":I have 2 numbers: :53147:">> = iolist_to_binary(join(re:split("I have 2 numbers: 53147","(.*\\D)(\\d+)$",[]))), + <<":C123">> = iolist_to_binary(join(re:split("ABC123","^\\D*(?!123)",[trim]))), <<":C123">> = iolist_to_binary(join(re:split("ABC123","^\\D*(?!123)",[{parts, - 2}]))), - <<":C123">> = iolist_to_binary(join(re:split("ABC123","^\\D*(?!123)",[]))), - <<":ABC:445">> = iolist_to_binary(join(re:split("ABC445","^(\\D*)(?=\\d)(?!123)",[trim]))), + 2}]))), + <<":C123">> = iolist_to_binary(join(re:split("ABC123","^\\D*(?!123)",[]))), + <<":ABC:445">> = iolist_to_binary(join(re:split("ABC445","^(\\D*)(?=\\d)(?!123)",[trim]))), <<":ABC:445">> = iolist_to_binary(join(re:split("ABC445","^(\\D*)(?=\\d)(?!123)",[{parts, - 2}]))), - <<":ABC:445">> = iolist_to_binary(join(re:split("ABC445","^(\\D*)(?=\\d)(?!123)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\D*)(?=\\d)(?!123)",[trim]))), + 2}]))), + <<":ABC:445">> = iolist_to_binary(join(re:split("ABC445","^(\\D*)(?=\\d)(?!123)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\D*)(?=\\d)(?!123)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\D*)(?=\\d)(?!123)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\D*)(?=\\d)(?!123)",[]))), - <<"ABC123">> = iolist_to_binary(join(re:split("ABC123","^(\\D*)(?=\\d)(?!123)",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\D*)(?=\\d)(?!123)",[]))), + <<"ABC123">> = iolist_to_binary(join(re:split("ABC123","^(\\D*)(?=\\d)(?!123)",[trim]))), <<"ABC123">> = iolist_to_binary(join(re:split("ABC123","^(\\D*)(?=\\d)(?!123)",[{parts, - 2}]))), - <<"ABC123">> = iolist_to_binary(join(re:split("ABC123","^(\\D*)(?=\\d)(?!123)",[]))), - <<":789">> = iolist_to_binary(join(re:split("W46]789","^[W-]46]",[trim]))), + 2}]))), + <<"ABC123">> = iolist_to_binary(join(re:split("ABC123","^(\\D*)(?=\\d)(?!123)",[]))), + <<":789">> = iolist_to_binary(join(re:split("W46]789","^[W-]46]",[trim]))), <<":789">> = iolist_to_binary(join(re:split("W46]789","^[W-]46]",[{parts, - 2}]))), - <<":789">> = iolist_to_binary(join(re:split("W46]789","^[W-]46]",[]))), - <<":789">> = iolist_to_binary(join(re:split("-46]789","^[W-]46]",[trim]))), + 2}]))), + <<":789">> = iolist_to_binary(join(re:split("W46]789","^[W-]46]",[]))), + <<":789">> = iolist_to_binary(join(re:split("-46]789","^[W-]46]",[trim]))), <<":789">> = iolist_to_binary(join(re:split("-46]789","^[W-]46]",[{parts, - 2}]))), - <<":789">> = iolist_to_binary(join(re:split("-46]789","^[W-]46]",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-]46]",[trim]))), + 2}]))), + <<":789">> = iolist_to_binary(join(re:split("-46]789","^[W-]46]",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-]46]",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-]46]",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-]46]",[]))), - <<"Wall">> = iolist_to_binary(join(re:split("Wall","^[W-]46]",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-]46]",[]))), + <<"Wall">> = iolist_to_binary(join(re:split("Wall","^[W-]46]",[trim]))), <<"Wall">> = iolist_to_binary(join(re:split("Wall","^[W-]46]",[{parts, - 2}]))), - <<"Wall">> = iolist_to_binary(join(re:split("Wall","^[W-]46]",[]))), - <<"Zebra">> = iolist_to_binary(join(re:split("Zebra","^[W-]46]",[trim]))), + 2}]))), + <<"Wall">> = iolist_to_binary(join(re:split("Wall","^[W-]46]",[]))), + <<"Zebra">> = iolist_to_binary(join(re:split("Zebra","^[W-]46]",[trim]))), <<"Zebra">> = iolist_to_binary(join(re:split("Zebra","^[W-]46]",[{parts, - 2}]))), - <<"Zebra">> = iolist_to_binary(join(re:split("Zebra","^[W-]46]",[]))), - <<"42">> = iolist_to_binary(join(re:split("42","^[W-]46]",[trim]))), + 2}]))), + <<"Zebra">> = iolist_to_binary(join(re:split("Zebra","^[W-]46]",[]))), + <<"42">> = iolist_to_binary(join(re:split("42","^[W-]46]",[trim]))), <<"42">> = iolist_to_binary(join(re:split("42","^[W-]46]",[{parts, - 2}]))), - <<"42">> = iolist_to_binary(join(re:split("42","^[W-]46]",[]))), - <<"[abcd]">> = iolist_to_binary(join(re:split("[abcd]","^[W-]46]",[trim]))), + 2}]))), + <<"42">> = iolist_to_binary(join(re:split("42","^[W-]46]",[]))), + <<"[abcd]">> = iolist_to_binary(join(re:split("[abcd]","^[W-]46]",[trim]))), <<"[abcd]">> = iolist_to_binary(join(re:split("[abcd]","^[W-]46]",[{parts, - 2}]))), - <<"[abcd]">> = iolist_to_binary(join(re:split("[abcd]","^[W-]46]",[]))), - <<"]abcd[">> = iolist_to_binary(join(re:split("]abcd[","^[W-]46]",[trim]))), + 2}]))), + <<"[abcd]">> = iolist_to_binary(join(re:split("[abcd]","^[W-]46]",[]))), + <<"]abcd[">> = iolist_to_binary(join(re:split("]abcd[","^[W-]46]",[trim]))), <<"]abcd[">> = iolist_to_binary(join(re:split("]abcd[","^[W-]46]",[{parts, - 2}]))), - <<"]abcd[">> = iolist_to_binary(join(re:split("]abcd[","^[W-]46]",[]))), - <<":46]789">> = iolist_to_binary(join(re:split("W46]789","^[W-\\]46]",[trim]))), + 2}]))), + <<"]abcd[">> = iolist_to_binary(join(re:split("]abcd[","^[W-]46]",[]))), + <<":46]789">> = iolist_to_binary(join(re:split("W46]789","^[W-\\]46]",[trim]))), <<":46]789">> = iolist_to_binary(join(re:split("W46]789","^[W-\\]46]",[{parts, - 2}]))), - <<":46]789">> = iolist_to_binary(join(re:split("W46]789","^[W-\\]46]",[]))), - <<":all">> = iolist_to_binary(join(re:split("Wall","^[W-\\]46]",[trim]))), + 2}]))), + <<":46]789">> = iolist_to_binary(join(re:split("W46]789","^[W-\\]46]",[]))), + <<":all">> = iolist_to_binary(join(re:split("Wall","^[W-\\]46]",[trim]))), <<":all">> = iolist_to_binary(join(re:split("Wall","^[W-\\]46]",[{parts, - 2}]))), - <<":all">> = iolist_to_binary(join(re:split("Wall","^[W-\\]46]",[]))), - <<":ebra">> = iolist_to_binary(join(re:split("Zebra","^[W-\\]46]",[trim]))), + 2}]))), + <<":all">> = iolist_to_binary(join(re:split("Wall","^[W-\\]46]",[]))), + <<":ebra">> = iolist_to_binary(join(re:split("Zebra","^[W-\\]46]",[trim]))), <<":ebra">> = iolist_to_binary(join(re:split("Zebra","^[W-\\]46]",[{parts, - 2}]))), - <<":ebra">> = iolist_to_binary(join(re:split("Zebra","^[W-\\]46]",[]))), - <<":ylophone">> = iolist_to_binary(join(re:split("Xylophone","^[W-\\]46]",[trim]))), + 2}]))), + <<":ebra">> = iolist_to_binary(join(re:split("Zebra","^[W-\\]46]",[]))), + <<":ylophone">> = iolist_to_binary(join(re:split("Xylophone","^[W-\\]46]",[trim]))), <<":ylophone">> = iolist_to_binary(join(re:split("Xylophone","^[W-\\]46]",[{parts, - 2}]))), - <<":ylophone">> = iolist_to_binary(join(re:split("Xylophone","^[W-\\]46]",[]))), - <<":2">> = iolist_to_binary(join(re:split("42","^[W-\\]46]",[trim]))), + 2}]))), + <<":ylophone">> = iolist_to_binary(join(re:split("Xylophone","^[W-\\]46]",[]))), + <<":2">> = iolist_to_binary(join(re:split("42","^[W-\\]46]",[trim]))), <<":2">> = iolist_to_binary(join(re:split("42","^[W-\\]46]",[{parts, - 2}]))), - <<":2">> = iolist_to_binary(join(re:split("42","^[W-\\]46]",[]))), - <<":abcd]">> = iolist_to_binary(join(re:split("[abcd]","^[W-\\]46]",[trim]))), + 2}]))), + <<":2">> = iolist_to_binary(join(re:split("42","^[W-\\]46]",[]))), + <<":abcd]">> = iolist_to_binary(join(re:split("[abcd]","^[W-\\]46]",[trim]))), <<":abcd]">> = iolist_to_binary(join(re:split("[abcd]","^[W-\\]46]",[{parts, - 2}]))), - <<":abcd]">> = iolist_to_binary(join(re:split("[abcd]","^[W-\\]46]",[]))), - <<":abcd[">> = iolist_to_binary(join(re:split("]abcd[","^[W-\\]46]",[trim]))), + 2}]))), + <<":abcd]">> = iolist_to_binary(join(re:split("[abcd]","^[W-\\]46]",[]))), + <<":abcd[">> = iolist_to_binary(join(re:split("]abcd[","^[W-\\]46]",[trim]))), <<":abcd[">> = iolist_to_binary(join(re:split("]abcd[","^[W-\\]46]",[{parts, - 2}]))), - <<":abcd[">> = iolist_to_binary(join(re:split("]abcd[","^[W-\\]46]",[]))), - <<":backslash">> = iolist_to_binary(join(re:split("\\backslash","^[W-\\]46]",[trim]))), + 2}]))), + <<":abcd[">> = iolist_to_binary(join(re:split("]abcd[","^[W-\\]46]",[]))), + <<":backslash">> = iolist_to_binary(join(re:split("\\backslash","^[W-\\]46]",[trim]))), <<":backslash">> = iolist_to_binary(join(re:split("\\backslash","^[W-\\]46]",[{parts, - 2}]))), - <<":backslash">> = iolist_to_binary(join(re:split("\\backslash","^[W-\\]46]",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-\\]46]",[trim]))), + 2}]))), + <<":backslash">> = iolist_to_binary(join(re:split("\\backslash","^[W-\\]46]",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-\\]46]",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-\\]46]",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-\\]46]",[]))), - <<"-46]789">> = iolist_to_binary(join(re:split("-46]789","^[W-\\]46]",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[W-\\]46]",[]))), + <<"-46]789">> = iolist_to_binary(join(re:split("-46]789","^[W-\\]46]",[trim]))), <<"-46]789">> = iolist_to_binary(join(re:split("-46]789","^[W-\\]46]",[{parts, - 2}]))), - <<"-46]789">> = iolist_to_binary(join(re:split("-46]789","^[W-\\]46]",[]))), - <<"well">> = iolist_to_binary(join(re:split("well","^[W-\\]46]",[trim]))), + 2}]))), + <<"-46]789">> = iolist_to_binary(join(re:split("-46]789","^[W-\\]46]",[]))), + <<"well">> = iolist_to_binary(join(re:split("well","^[W-\\]46]",[trim]))), <<"well">> = iolist_to_binary(join(re:split("well","^[W-\\]46]",[{parts, - 2}]))), - <<"well">> = iolist_to_binary(join(re:split("well","^[W-\\]46]",[]))), - <<"">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?:[a-zA-Z0-9]+ ){0,10}otherword",[trim]))), + 2}]))), + <<"well">> = iolist_to_binary(join(re:split("well","^[W-\\]46]",[]))), + <<"">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?:[a-zA-Z0-9]+ ){0,10}otherword",[trim]))), <<":">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?:[a-zA-Z0-9]+ ){0,10}otherword",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?:[a-zA-Z0-9]+ ){0,10}otherword",[]))), - <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark","word (?:[a-zA-Z0-9]+ ){0,10}otherword",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?:[a-zA-Z0-9]+ ){0,10}otherword",[]))), + <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark","word (?:[a-zA-Z0-9]+ ){0,10}otherword",[trim]))), <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark","word (?:[a-zA-Z0-9]+ ){0,10}otherword",[{parts, - 2}]))), - <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark","word (?:[a-zA-Z0-9]+ ){0,10}otherword",[]))), - <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?:[a-zA-Z0-9]+ ){0,300}otherword",[trim]))), + 2}]))), + <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark","word (?:[a-zA-Z0-9]+ ){0,10}otherword",[]))), + <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?:[a-zA-Z0-9]+ ){0,300}otherword",[trim]))), <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?:[a-zA-Z0-9]+ ){0,300}otherword",[{parts, - 2}]))), - <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?:[a-zA-Z0-9]+ ){0,300}otherword",[]))), - <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,0}",[trim]))), + 2}]))), + <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?:[a-zA-Z0-9]+ ){0,300}otherword",[]))), + <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,0}",[trim]))), <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,0}",[{parts, - 2}]))), - <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,0}",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","^(a){0,0}",[trim]))), + 2}]))), + <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,0}",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","^(a){0,0}",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","^(a){0,0}",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","^(a){0,0}",[]))), - <<"aab">> = iolist_to_binary(join(re:split("aab","^(a){0,0}",[trim]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","^(a){0,0}",[]))), + <<"aab">> = iolist_to_binary(join(re:split("aab","^(a){0,0}",[trim]))), <<"aab">> = iolist_to_binary(join(re:split("aab","^(a){0,0}",[{parts, - 2}]))), - <<"aab">> = iolist_to_binary(join(re:split("aab","^(a){0,0}",[]))), - <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,1}",[trim]))), + 2}]))), + <<"aab">> = iolist_to_binary(join(re:split("aab","^(a){0,0}",[]))), + <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,1}",[trim]))), <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,1}",[{parts, - 2}]))), - <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,1}",[]))), - <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,1}",[trim]))), + 2}]))), + <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,1}",[]))), + <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,1}",[trim]))), <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,1}",[{parts, - 2}]))), - <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,1}",[]))), - <<":a:ab">> = iolist_to_binary(join(re:split("aab","^(a){0,1}",[trim]))), + 2}]))), + <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,1}",[]))), + <<":a:ab">> = iolist_to_binary(join(re:split("aab","^(a){0,1}",[trim]))), <<":a:ab">> = iolist_to_binary(join(re:split("aab","^(a){0,1}",[{parts, - 2}]))), - <<":a:ab">> = iolist_to_binary(join(re:split("aab","^(a){0,1}",[]))), - <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,2}",[trim]))), + 2}]))), + <<":a:ab">> = iolist_to_binary(join(re:split("aab","^(a){0,1}",[]))), + <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,2}",[trim]))), <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,2}",[{parts, - 2}]))), - <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,2}",[]))), - <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,2}",[trim]))), + 2}]))), + <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,2}",[]))), + <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,2}",[trim]))), <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,2}",[{parts, - 2}]))), - <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,2}",[]))), - <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,2}",[trim]))), + 2}]))), + <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,2}",[]))), + <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,2}",[trim]))), <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,2}",[{parts, - 2}]))), - <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,2}",[]))), - <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,3}",[trim]))), + 2}]))), + <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,2}",[]))), + <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,3}",[trim]))), <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,3}",[{parts, - 2}]))), - <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,3}",[]))), - <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,3}",[trim]))), + 2}]))), + <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,3}",[]))), + <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,3}",[trim]))), <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,3}",[{parts, - 2}]))), - <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,3}",[]))), - <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,3}",[trim]))), + 2}]))), + <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,3}",[]))), + <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,3}",[trim]))), <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,3}",[{parts, - 2}]))), - <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,3}",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaa","^(a){0,3}",[trim]))), + 2}]))), + <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,3}",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaa","^(a){0,3}",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){0,3}",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){0,3}",[]))), - <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,}",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){0,3}",[]))), + <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,}",[trim]))), <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,}",[{parts, - 2}]))), - <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,}",[]))), - <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,}",[trim]))), + 2}]))), + <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){0,}",[]))), + <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,}",[trim]))), <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,}",[{parts, - 2}]))), - <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,}",[]))), - <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,}",[trim]))), + 2}]))), + <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){0,}",[]))), + <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,}",[trim]))), <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,}",[{parts, - 2}]))), - <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,}",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaa","^(a){0,}",[trim]))), + 2}]))), + <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){0,}",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaa","^(a){0,}",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){0,}",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){0,}",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a){0,}",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){0,}",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a){0,}",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a){0,}",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a){0,}",[]))), - <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,1}",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a){0,}",[]))), + <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,1}",[trim]))), <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,1}",[{parts, - 2}]))), - <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,1}",[]))), - <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,1}",[trim]))), + 2}]))), + <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,1}",[]))), + <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,1}",[trim]))), <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,1}",[{parts, - 2}]))), - <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,1}",[]))), - <<":a:ab">> = iolist_to_binary(join(re:split("aab","^(a){1,1}",[trim]))), + 2}]))), + <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,1}",[]))), + <<":a:ab">> = iolist_to_binary(join(re:split("aab","^(a){1,1}",[trim]))), <<":a:ab">> = iolist_to_binary(join(re:split("aab","^(a){1,1}",[{parts, - 2}]))), - <<":a:ab">> = iolist_to_binary(join(re:split("aab","^(a){1,1}",[]))), - <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,2}",[trim]))), + 2}]))), + <<":a:ab">> = iolist_to_binary(join(re:split("aab","^(a){1,1}",[]))), + <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,2}",[trim]))), <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,2}",[{parts, - 2}]))), - <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,2}",[]))), - <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,2}",[trim]))), + 2}]))), + <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,2}",[]))), + <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,2}",[trim]))), <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,2}",[{parts, - 2}]))), - <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,2}",[]))), - <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,2}",[trim]))), + 2}]))), + <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,2}",[]))), + <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,2}",[trim]))), <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,2}",[{parts, - 2}]))), - <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,2}",[]))), - <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,3}",[trim]))), + 2}]))), + <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,2}",[]))), + <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,3}",[trim]))), <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,3}",[{parts, - 2}]))), - <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,3}",[]))), - <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,3}",[trim]))), + 2}]))), + <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,3}",[]))), + <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,3}",[trim]))), <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,3}",[{parts, - 2}]))), - <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,3}",[]))), - <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,3}",[trim]))), + 2}]))), + <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,3}",[]))), + <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,3}",[trim]))), <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,3}",[{parts, - 2}]))), - <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,3}",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaa","^(a){1,3}",[trim]))), + 2}]))), + <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,3}",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaa","^(a){1,3}",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){1,3}",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){1,3}",[]))), - <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,}",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){1,3}",[]))), + <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,}",[trim]))), <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,}",[{parts, - 2}]))), - <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,}",[]))), - <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,}",[trim]))), + 2}]))), + <<"bcd">> = iolist_to_binary(join(re:split("bcd","^(a){1,}",[]))), + <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,}",[trim]))), <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,}",[{parts, - 2}]))), - <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,}",[]))), - <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,}",[trim]))), + 2}]))), + <<":a:bc">> = iolist_to_binary(join(re:split("abc","^(a){1,}",[]))), + <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,}",[trim]))), <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,}",[{parts, - 2}]))), - <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,}",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaa","^(a){1,}",[trim]))), + 2}]))), + <<":a:b">> = iolist_to_binary(join(re:split("aab","^(a){1,}",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaa","^(a){1,}",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){1,}",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){1,}",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a){1,}",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aaa","^(a){1,}",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a){1,}",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a){1,}",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a){1,}",[]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a){1,}",[]))), <<"borfle : no">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*\\.gif",[trim]))), +no",".*\\.gif",[trim]))), <<"borfle : no">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*\\.gif",[{parts,2}]))), +no",".*\\.gif",[{parts,2}]))), <<"borfle : no">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*\\.gif",[]))), +no",".*\\.gif",[]))), <<"borfle : no">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".{0,}\\.gif",[trim]))), +no",".{0,}\\.gif",[trim]))), <<"borfle : no">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".{0,}\\.gif",[{parts,2}]))), +no",".{0,}\\.gif",[{parts,2}]))), <<"borfle : no">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".{0,}\\.gif",[]))), +no",".{0,}\\.gif",[]))), <<"borfle : no">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*\\.gif",[multiline,trim]))), +no",".*\\.gif",[multiline,trim]))), <<"borfle : no">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*\\.gif",[multiline,{parts,2}]))), +no",".*\\.gif",[multiline,{parts,2}]))), <<"borfle : no">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*\\.gif",[multiline]))), +no",".*\\.gif",[multiline]))), ok. run9() -> <<": no">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*\\.gif",[dotall,trim]))), +no",".*\\.gif",[dotall,trim]))), <<": no">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*\\.gif",[dotall,{parts,2}]))), +no",".*\\.gif",[dotall,{parts,2}]))), <<": no">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*\\.gif",[dotall]))), +no",".*\\.gif",[dotall]))), <<": no">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*\\.gif",[multiline,dotall,trim]))), +no",".*\\.gif",[multiline,dotall,trim]))), <<": no">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*\\.gif",[multiline,dotall,{parts,2}]))), +no",".*\\.gif",[multiline,dotall,{parts,2}]))), <<": no">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*\\.gif",[multiline,dotall]))), +no",".*\\.gif",[multiline,dotall]))), <<"borfle bib.gif ">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[trim]))), +no",".*$",[trim]))), <<"borfle bib.gif :">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[{parts,2}]))), +no",".*$",[{parts,2}]))), <<"borfle bib.gif :">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[]))), +no",".*$",[]))), <<": : ">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[multiline,trim]))), +no",".*$",[multiline,trim]))), <<": bib.gif no">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[multiline,{parts,2}]))), +no",".*$",[multiline,{parts,2}]))), <<": : :">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[multiline]))), +no",".*$",[multiline]))), <<"">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[dotall,trim]))), +no",".*$",[dotall,trim]))), <<":">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[dotall,{parts,2}]))), +no",".*$",[dotall,{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[dotall]))), +no",".*$",[dotall]))), <<"">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[multiline,dotall,trim]))), +no",".*$",[multiline,dotall,trim]))), <<":">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[multiline,dotall,{parts,2}]))), +no",".*$",[multiline,dotall,{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[multiline,dotall]))), +no",".*$",[multiline,dotall]))), <<"borfle bib.gif ">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[trim]))), +no",".*$",[trim]))), <<"borfle bib.gif :">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[{parts,2}]))), +no",".*$",[{parts,2}]))), <<"borfle bib.gif :">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[]))), +no",".*$",[]))), <<": : ">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[multiline,trim]))), +no",".*$",[multiline,trim]))), <<": bib.gif no">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[multiline,{parts,2}]))), +no",".*$",[multiline,{parts,2}]))), <<": : :">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[multiline]))), +no",".*$",[multiline]))), <<"">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[dotall,trim]))), +no",".*$",[dotall,trim]))), <<":">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[dotall,{parts,2}]))), +no",".*$",[dotall,{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[dotall]))), +no",".*$",[dotall]))), <<"">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[multiline,dotall,trim]))), +no",".*$",[multiline,dotall,trim]))), <<":">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[multiline,dotall,{parts,2}]))), +no",".*$",[multiline,dotall,{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("borfle bib.gif -no",".*$",[multiline,dotall]))), +no",".*$",[multiline,dotall]))), <<"abcde :1234X:yz">> = iolist_to_binary(join(re:split("abcde -1234Xyz","(.*X|^B)",[trim]))), +1234Xyz","(.*X|^B)",[trim]))), <<"abcde :1234X:yz">> = iolist_to_binary(join(re:split("abcde -1234Xyz","(.*X|^B)",[{parts,2}]))), +1234Xyz","(.*X|^B)",[{parts,2}]))), <<"abcde :1234X:yz">> = iolist_to_binary(join(re:split("abcde -1234Xyz","(.*X|^B)",[]))), - <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[trim]))), +1234Xyz","(.*X|^B)",[]))), + <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[trim]))), <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[{parts, - 2}]))), - <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(.*X|^B)",[trim]))), + 2}]))), + <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(.*X|^B)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(.*X|^B)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(.*X|^B)",[]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(.*X|^B)",[]))), <<"abcde Bar">> = iolist_to_binary(join(re:split("abcde -Bar","(.*X|^B)",[trim]))), +Bar","(.*X|^B)",[trim]))), <<"abcde Bar">> = iolist_to_binary(join(re:split("abcde -Bar","(.*X|^B)",[{parts,2}]))), +Bar","(.*X|^B)",[{parts,2}]))), <<"abcde Bar">> = iolist_to_binary(join(re:split("abcde -Bar","(.*X|^B)",[]))), +Bar","(.*X|^B)",[]))), <<"abcde :1234X:yz">> = iolist_to_binary(join(re:split("abcde -1234Xyz","(.*X|^B)",[multiline,trim]))), +1234Xyz","(.*X|^B)",[multiline,trim]))), <<"abcde :1234X:yz">> = iolist_to_binary(join(re:split("abcde -1234Xyz","(.*X|^B)",[multiline,{parts,2}]))), +1234Xyz","(.*X|^B)",[multiline,{parts,2}]))), <<"abcde :1234X:yz">> = iolist_to_binary(join(re:split("abcde -1234Xyz","(.*X|^B)",[multiline]))), +1234Xyz","(.*X|^B)",[multiline]))), <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[multiline, - trim]))), + trim]))), <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[multiline, {parts, - 2}]))), - <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[multiline]))), + 2}]))), + <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[multiline]))), <<"abcde :B:ar">> = iolist_to_binary(join(re:split("abcde -Bar","(.*X|^B)",[multiline,trim]))), +Bar","(.*X|^B)",[multiline,trim]))), <<"abcde :B:ar">> = iolist_to_binary(join(re:split("abcde -Bar","(.*X|^B)",[multiline,{parts,2}]))), +Bar","(.*X|^B)",[multiline,{parts,2}]))), <<"abcde :B:ar">> = iolist_to_binary(join(re:split("abcde -Bar","(.*X|^B)",[multiline]))), +Bar","(.*X|^B)",[multiline]))), <<":abcde 1234X:yz">> = iolist_to_binary(join(re:split("abcde -1234Xyz","(.*X|^B)",[dotall,trim]))), +1234Xyz","(.*X|^B)",[dotall,trim]))), <<":abcde 1234X:yz">> = iolist_to_binary(join(re:split("abcde -1234Xyz","(.*X|^B)",[dotall,{parts,2}]))), +1234Xyz","(.*X|^B)",[dotall,{parts,2}]))), <<":abcde 1234X:yz">> = iolist_to_binary(join(re:split("abcde -1234Xyz","(.*X|^B)",[dotall]))), +1234Xyz","(.*X|^B)",[dotall]))), <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[dotall, - trim]))), + trim]))), <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[dotall, {parts, - 2}]))), - <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[dotall]))), + 2}]))), + <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[dotall]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(.*X|^B)",[dotall, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(.*X|^B)",[dotall, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(.*X|^B)",[dotall]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(.*X|^B)",[dotall]))), <<"abcde Bar">> = iolist_to_binary(join(re:split("abcde -Bar","(.*X|^B)",[dotall,trim]))), +Bar","(.*X|^B)",[dotall,trim]))), <<"abcde Bar">> = iolist_to_binary(join(re:split("abcde -Bar","(.*X|^B)",[dotall,{parts,2}]))), +Bar","(.*X|^B)",[dotall,{parts,2}]))), <<"abcde Bar">> = iolist_to_binary(join(re:split("abcde -Bar","(.*X|^B)",[dotall]))), +Bar","(.*X|^B)",[dotall]))), <<":abcde 1234X:yz">> = iolist_to_binary(join(re:split("abcde -1234Xyz","(.*X|^B)",[multiline,dotall,trim]))), +1234Xyz","(.*X|^B)",[multiline,dotall,trim]))), <<":abcde 1234X:yz">> = iolist_to_binary(join(re:split("abcde -1234Xyz","(.*X|^B)",[multiline,dotall,{parts,2}]))), +1234Xyz","(.*X|^B)",[multiline,dotall,{parts,2}]))), <<":abcde 1234X:yz">> = iolist_to_binary(join(re:split("abcde -1234Xyz","(.*X|^B)",[multiline,dotall]))), +1234Xyz","(.*X|^B)",[multiline,dotall]))), <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[multiline, dotall, - trim]))), + trim]))), <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[multiline, dotall, {parts, - 2}]))), + 2}]))), <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(.*X|^B)",[multiline, - dotall]))), + dotall]))), <<"abcde :B:ar">> = iolist_to_binary(join(re:split("abcde -Bar","(.*X|^B)",[multiline,dotall,trim]))), +Bar","(.*X|^B)",[multiline,dotall,trim]))), <<"abcde :B:ar">> = iolist_to_binary(join(re:split("abcde -Bar","(.*X|^B)",[multiline,dotall,{parts,2}]))), +Bar","(.*X|^B)",[multiline,dotall,{parts,2}]))), <<"abcde :B:ar">> = iolist_to_binary(join(re:split("abcde -Bar","(.*X|^B)",[multiline,dotall]))), +Bar","(.*X|^B)",[multiline,dotall]))), <<":abcde 1234X:yz">> = iolist_to_binary(join(re:split("abcde -1234Xyz","(?s)(.*X|^B)",[trim]))), +1234Xyz","(?s)(.*X|^B)",[trim]))), <<":abcde 1234X:yz">> = iolist_to_binary(join(re:split("abcde -1234Xyz","(?s)(.*X|^B)",[{parts,2}]))), +1234Xyz","(?s)(.*X|^B)",[{parts,2}]))), <<":abcde 1234X:yz">> = iolist_to_binary(join(re:split("abcde -1234Xyz","(?s)(.*X|^B)",[]))), - <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(?s)(.*X|^B)",[trim]))), +1234Xyz","(?s)(.*X|^B)",[]))), + <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(?s)(.*X|^B)",[trim]))), <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(?s)(.*X|^B)",[{parts, - 2}]))), - <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(?s)(.*X|^B)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s)(.*X|^B)",[trim]))), + 2}]))), + <<":B:arFoo">> = iolist_to_binary(join(re:split("BarFoo","(?s)(.*X|^B)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s)(.*X|^B)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s)(.*X|^B)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s)(.*X|^B)",[]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s)(.*X|^B)",[]))), <<"abcde Bar">> = iolist_to_binary(join(re:split("abcde -Bar","(?s)(.*X|^B)",[trim]))), +Bar","(?s)(.*X|^B)",[trim]))), <<"abcde Bar">> = iolist_to_binary(join(re:split("abcde -Bar","(?s)(.*X|^B)",[{parts,2}]))), +Bar","(?s)(.*X|^B)",[{parts,2}]))), <<"abcde Bar">> = iolist_to_binary(join(re:split("abcde -Bar","(?s)(.*X|^B)",[]))), +Bar","(?s)(.*X|^B)",[]))), <<":yz">> = iolist_to_binary(join(re:split("abcde -1234Xyz","(?s:.*X|^B)",[trim]))), +1234Xyz","(?s:.*X|^B)",[trim]))), <<":yz">> = iolist_to_binary(join(re:split("abcde -1234Xyz","(?s:.*X|^B)",[{parts,2}]))), +1234Xyz","(?s:.*X|^B)",[{parts,2}]))), <<":yz">> = iolist_to_binary(join(re:split("abcde -1234Xyz","(?s:.*X|^B)",[]))), - <<":arFoo">> = iolist_to_binary(join(re:split("BarFoo","(?s:.*X|^B)",[trim]))), +1234Xyz","(?s:.*X|^B)",[]))), + <<":arFoo">> = iolist_to_binary(join(re:split("BarFoo","(?s:.*X|^B)",[trim]))), <<":arFoo">> = iolist_to_binary(join(re:split("BarFoo","(?s:.*X|^B)",[{parts, - 2}]))), - <<":arFoo">> = iolist_to_binary(join(re:split("BarFoo","(?s:.*X|^B)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s:.*X|^B)",[trim]))), + 2}]))), + <<":arFoo">> = iolist_to_binary(join(re:split("BarFoo","(?s:.*X|^B)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s:.*X|^B)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s:.*X|^B)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s:.*X|^B)",[]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s:.*X|^B)",[]))), <<"abcde Bar">> = iolist_to_binary(join(re:split("abcde -Bar","(?s:.*X|^B)",[trim]))), +Bar","(?s:.*X|^B)",[trim]))), <<"abcde Bar">> = iolist_to_binary(join(re:split("abcde -Bar","(?s:.*X|^B)",[{parts,2}]))), +Bar","(?s:.*X|^B)",[{parts,2}]))), <<"abcde Bar">> = iolist_to_binary(join(re:split("abcde -Bar","(?s:.*X|^B)",[]))), - <<"**** Failers">> = iolist_to_binary(join(re:split("**** Failers","^.*B",[trim]))), +Bar","(?s:.*X|^B)",[]))), + <<"**** Failers">> = iolist_to_binary(join(re:split("**** Failers","^.*B",[trim]))), <<"**** Failers">> = iolist_to_binary(join(re:split("**** Failers","^.*B",[{parts, - 2}]))), - <<"**** Failers">> = iolist_to_binary(join(re:split("**** Failers","^.*B",[]))), + 2}]))), + <<"**** Failers">> = iolist_to_binary(join(re:split("**** Failers","^.*B",[]))), <<"abc B">> = iolist_to_binary(join(re:split("abc -B","^.*B",[trim]))), +B","^.*B",[trim]))), <<"abc B">> = iolist_to_binary(join(re:split("abc -B","^.*B",[{parts,2}]))), +B","^.*B",[{parts,2}]))), <<"abc B">> = iolist_to_binary(join(re:split("abc -B","^.*B",[]))), +B","^.*B",[]))), <<"">> = iolist_to_binary(join(re:split("abc -B","(?s)^.*B",[trim]))), +B","(?s)^.*B",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc -B","(?s)^.*B",[{parts,2}]))), +B","(?s)^.*B",[{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("abc -B","(?s)^.*B",[]))), +B","(?s)^.*B",[]))), <<"abc ">> = iolist_to_binary(join(re:split("abc -B","(?m)^.*B",[trim]))), +B","(?m)^.*B",[trim]))), <<"abc :">> = iolist_to_binary(join(re:split("abc -B","(?m)^.*B",[{parts,2}]))), +B","(?m)^.*B",[{parts,2}]))), <<"abc :">> = iolist_to_binary(join(re:split("abc -B","(?m)^.*B",[]))), +B","(?m)^.*B",[]))), <<"">> = iolist_to_binary(join(re:split("abc -B","(?ms)^.*B",[trim]))), +B","(?ms)^.*B",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc -B","(?ms)^.*B",[{parts,2}]))), +B","(?ms)^.*B",[{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("abc -B","(?ms)^.*B",[]))), +B","(?ms)^.*B",[]))), ok. run10() -> <<"abc ">> = iolist_to_binary(join(re:split("abc -B","(?ms)^B",[trim]))), +B","(?ms)^B",[trim]))), <<"abc :">> = iolist_to_binary(join(re:split("abc -B","(?ms)^B",[{parts,2}]))), +B","(?ms)^B",[{parts,2}]))), <<"abc :">> = iolist_to_binary(join(re:split("abc -B","(?ms)^B",[]))), - <<"">> = iolist_to_binary(join(re:split("B","(?s)B$",[trim]))), +B","(?ms)^B",[]))), + <<"">> = iolist_to_binary(join(re:split("B","(?s)B$",[trim]))), <<":">> = iolist_to_binary(join(re:split("B","(?s)B$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("B","(?s)B$",[]))), - <<"">> = iolist_to_binary(join(re:split("123456654321","^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("B","(?s)B$",[]))), + <<"">> = iolist_to_binary(join(re:split("123456654321","^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]",[trim]))), <<":">> = iolist_to_binary(join(re:split("123456654321","^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("123456654321","^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]",[]))), - <<"">> = iolist_to_binary(join(re:split("123456654321","^\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("123456654321","^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]",[]))), + <<"">> = iolist_to_binary(join(re:split("123456654321","^\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d",[trim]))), <<":">> = iolist_to_binary(join(re:split("123456654321","^\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("123456654321","^\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d",[]))), - <<"">> = iolist_to_binary(join(re:split("123456654321","^[\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("123456654321","^\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d",[]))), + <<"">> = iolist_to_binary(join(re:split("123456654321","^[\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d]",[trim]))), <<":">> = iolist_to_binary(join(re:split("123456654321","^[\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("123456654321","^[\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d]",[]))), - <<"">> = iolist_to_binary(join(re:split("abcabcabcabc","^[abc]{12}",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("123456654321","^[\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d][\\d]",[]))), + <<"">> = iolist_to_binary(join(re:split("abcabcabcabc","^[abc]{12}",[trim]))), <<":">> = iolist_to_binary(join(re:split("abcabcabcabc","^[abc]{12}",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abcabcabcabc","^[abc]{12}",[]))), - <<"">> = iolist_to_binary(join(re:split("abcabcabcabc","^[a-c]{12}",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abcabcabcabc","^[abc]{12}",[]))), + <<"">> = iolist_to_binary(join(re:split("abcabcabcabc","^[a-c]{12}",[trim]))), <<":">> = iolist_to_binary(join(re:split("abcabcabcabc","^[a-c]{12}",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abcabcabcabc","^[a-c]{12}",[]))), - <<":c">> = iolist_to_binary(join(re:split("abcabcabcabc","^(a|b|c){12}",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abcabcabcabc","^[a-c]{12}",[]))), + <<":c">> = iolist_to_binary(join(re:split("abcabcabcabc","^(a|b|c){12}",[trim]))), <<":c:">> = iolist_to_binary(join(re:split("abcabcabcabc","^(a|b|c){12}",[{parts, - 2}]))), - <<":c:">> = iolist_to_binary(join(re:split("abcabcabcabc","^(a|b|c){12}",[]))), - <<"">> = iolist_to_binary(join(re:split("n","^[abcdefghijklmnopqrstuvwxy0123456789]",[trim]))), + 2}]))), + <<":c:">> = iolist_to_binary(join(re:split("abcabcabcabc","^(a|b|c){12}",[]))), + <<"">> = iolist_to_binary(join(re:split("n","^[abcdefghijklmnopqrstuvwxy0123456789]",[trim]))), <<":">> = iolist_to_binary(join(re:split("n","^[abcdefghijklmnopqrstuvwxy0123456789]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("n","^[abcdefghijklmnopqrstuvwxy0123456789]",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[abcdefghijklmnopqrstuvwxy0123456789]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("n","^[abcdefghijklmnopqrstuvwxy0123456789]",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[abcdefghijklmnopqrstuvwxy0123456789]",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[abcdefghijklmnopqrstuvwxy0123456789]",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[abcdefghijklmnopqrstuvwxy0123456789]",[]))), - <<"z">> = iolist_to_binary(join(re:split("z","^[abcdefghijklmnopqrstuvwxy0123456789]",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[abcdefghijklmnopqrstuvwxy0123456789]",[]))), + <<"z">> = iolist_to_binary(join(re:split("z","^[abcdefghijklmnopqrstuvwxy0123456789]",[trim]))), <<"z">> = iolist_to_binary(join(re:split("z","^[abcdefghijklmnopqrstuvwxy0123456789]",[{parts, - 2}]))), - <<"z">> = iolist_to_binary(join(re:split("z","^[abcdefghijklmnopqrstuvwxy0123456789]",[]))), - <<"">> = iolist_to_binary(join(re:split("abcd","abcde{0,0}",[trim]))), + 2}]))), + <<"z">> = iolist_to_binary(join(re:split("z","^[abcdefghijklmnopqrstuvwxy0123456789]",[]))), + <<"">> = iolist_to_binary(join(re:split("abcd","abcde{0,0}",[trim]))), <<":">> = iolist_to_binary(join(re:split("abcd","abcde{0,0}",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abcd","abcde{0,0}",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abcde{0,0}",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abcd","abcde{0,0}",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abcde{0,0}",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abcde{0,0}",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abcde{0,0}",[]))), - <<"abce">> = iolist_to_binary(join(re:split("abce","abcde{0,0}",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abcde{0,0}",[]))), + <<"abce">> = iolist_to_binary(join(re:split("abce","abcde{0,0}",[trim]))), <<"abce">> = iolist_to_binary(join(re:split("abce","abcde{0,0}",[{parts, - 2}]))), - <<"abce">> = iolist_to_binary(join(re:split("abce","abcde{0,0}",[]))), - <<"">> = iolist_to_binary(join(re:split("abe","ab[cd]{0,0}e",[trim]))), + 2}]))), + <<"abce">> = iolist_to_binary(join(re:split("abce","abcde{0,0}",[]))), + <<"">> = iolist_to_binary(join(re:split("abe","ab[cd]{0,0}e",[trim]))), <<":">> = iolist_to_binary(join(re:split("abe","ab[cd]{0,0}e",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abe","ab[cd]{0,0}e",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab[cd]{0,0}e",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abe","ab[cd]{0,0}e",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab[cd]{0,0}e",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab[cd]{0,0}e",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab[cd]{0,0}e",[]))), - <<"abcde">> = iolist_to_binary(join(re:split("abcde","ab[cd]{0,0}e",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab[cd]{0,0}e",[]))), + <<"abcde">> = iolist_to_binary(join(re:split("abcde","ab[cd]{0,0}e",[trim]))), <<"abcde">> = iolist_to_binary(join(re:split("abcde","ab[cd]{0,0}e",[{parts, - 2}]))), - <<"abcde">> = iolist_to_binary(join(re:split("abcde","ab[cd]{0,0}e",[]))), - <<"">> = iolist_to_binary(join(re:split("abd","ab(c){0,0}d",[trim]))), + 2}]))), + <<"abcde">> = iolist_to_binary(join(re:split("abcde","ab[cd]{0,0}e",[]))), + <<"">> = iolist_to_binary(join(re:split("abd","ab(c){0,0}d",[trim]))), <<"::">> = iolist_to_binary(join(re:split("abd","ab(c){0,0}d",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("abd","ab(c){0,0}d",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab(c){0,0}d",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("abd","ab(c){0,0}d",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab(c){0,0}d",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab(c){0,0}d",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab(c){0,0}d",[]))), - <<"abcd">> = iolist_to_binary(join(re:split("abcd","ab(c){0,0}d",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab(c){0,0}d",[]))), + <<"abcd">> = iolist_to_binary(join(re:split("abcd","ab(c){0,0}d",[trim]))), <<"abcd">> = iolist_to_binary(join(re:split("abcd","ab(c){0,0}d",[{parts, - 2}]))), - <<"abcd">> = iolist_to_binary(join(re:split("abcd","ab(c){0,0}d",[]))), - <<"">> = iolist_to_binary(join(re:split("a","a(b*)",[trim]))), + 2}]))), + <<"abcd">> = iolist_to_binary(join(re:split("abcd","ab(c){0,0}d",[]))), + <<"">> = iolist_to_binary(join(re:split("a","a(b*)",[trim]))), <<"::">> = iolist_to_binary(join(re:split("a","a(b*)",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("a","a(b*)",[]))), - <<":b">> = iolist_to_binary(join(re:split("ab","a(b*)",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("a","a(b*)",[]))), + <<":b">> = iolist_to_binary(join(re:split("ab","a(b*)",[trim]))), <<":b:">> = iolist_to_binary(join(re:split("ab","a(b*)",[{parts, - 2}]))), - <<":b:">> = iolist_to_binary(join(re:split("ab","a(b*)",[]))), - <<":bbbb">> = iolist_to_binary(join(re:split("abbbb","a(b*)",[trim]))), + 2}]))), + <<":b:">> = iolist_to_binary(join(re:split("ab","a(b*)",[]))), + <<":bbbb">> = iolist_to_binary(join(re:split("abbbb","a(b*)",[trim]))), <<":bbbb:">> = iolist_to_binary(join(re:split("abbbb","a(b*)",[{parts, - 2}]))), - <<":bbbb:">> = iolist_to_binary(join(re:split("abbbb","a(b*)",[]))), - <<"*** F::ilers">> = iolist_to_binary(join(re:split("*** Failers","a(b*)",[trim]))), + 2}]))), + <<":bbbb:">> = iolist_to_binary(join(re:split("abbbb","a(b*)",[]))), + <<"*** F::ilers">> = iolist_to_binary(join(re:split("*** Failers","a(b*)",[trim]))), <<"*** F::ilers">> = iolist_to_binary(join(re:split("*** Failers","a(b*)",[{parts, - 2}]))), - <<"*** F::ilers">> = iolist_to_binary(join(re:split("*** Failers","a(b*)",[]))), - <<"bbbbb">> = iolist_to_binary(join(re:split("bbbbb","a(b*)",[trim]))), + 2}]))), + <<"*** F::ilers">> = iolist_to_binary(join(re:split("*** Failers","a(b*)",[]))), + <<"bbbbb">> = iolist_to_binary(join(re:split("bbbbb","a(b*)",[trim]))), <<"bbbbb">> = iolist_to_binary(join(re:split("bbbbb","a(b*)",[{parts, - 2}]))), - <<"bbbbb">> = iolist_to_binary(join(re:split("bbbbb","a(b*)",[]))), - <<"">> = iolist_to_binary(join(re:split("abe","ab\\d{0}e",[trim]))), + 2}]))), + <<"bbbbb">> = iolist_to_binary(join(re:split("bbbbb","a(b*)",[]))), + <<"">> = iolist_to_binary(join(re:split("abe","ab\\d{0}e",[trim]))), <<":">> = iolist_to_binary(join(re:split("abe","ab\\d{0}e",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abe","ab\\d{0}e",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab\\d{0}e",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abe","ab\\d{0}e",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab\\d{0}e",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab\\d{0}e",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab\\d{0}e",[]))), - <<"ab1e">> = iolist_to_binary(join(re:split("ab1e","ab\\d{0}e",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab\\d{0}e",[]))), + <<"ab1e">> = iolist_to_binary(join(re:split("ab1e","ab\\d{0}e",[trim]))), <<"ab1e">> = iolist_to_binary(join(re:split("ab1e","ab\\d{0}e",[{parts, - 2}]))), - <<"ab1e">> = iolist_to_binary(join(re:split("ab1e","ab\\d{0}e",[]))), - <<"the :quick: brown fox">> = iolist_to_binary(join(re:split("the \"quick\" brown fox","\"([^\\\\\"]+|\\\\.)*\"",[trim]))), + 2}]))), + <<"ab1e">> = iolist_to_binary(join(re:split("ab1e","ab\\d{0}e",[]))), + <<"the :quick: brown fox">> = iolist_to_binary(join(re:split("the \"quick\" brown fox","\"([^\\\\\"]+|\\\\.)*\"",[trim]))), <<"the :quick: brown fox">> = iolist_to_binary(join(re:split("the \"quick\" brown fox","\"([^\\\\\"]+|\\\\.)*\"",[{parts, - 2}]))), - <<"the :quick: brown fox">> = iolist_to_binary(join(re:split("the \"quick\" brown fox","\"([^\\\\\"]+|\\\\.)*\"",[]))), - <<": brown fox">> = iolist_to_binary(join(re:split("\"the \\\"quick\\\" brown fox\"","\"([^\\\\\"]+|\\\\.)*\"",[trim]))), + 2}]))), + <<"the :quick: brown fox">> = iolist_to_binary(join(re:split("the \"quick\" brown fox","\"([^\\\\\"]+|\\\\.)*\"",[]))), + <<": brown fox">> = iolist_to_binary(join(re:split("\"the \\\"quick\\\" brown fox\"","\"([^\\\\\"]+|\\\\.)*\"",[trim]))), <<": brown fox:">> = iolist_to_binary(join(re:split("\"the \\\"quick\\\" brown fox\"","\"([^\\\\\"]+|\\\\.)*\"",[{parts, - 2}]))), - <<": brown fox:">> = iolist_to_binary(join(re:split("\"the \\\"quick\\\" brown fox\"","\"([^\\\\\"]+|\\\\.)*\"",[]))), - <<"a:b:c">> = iolist_to_binary(join(re:split("abc","",[trim]))), + 2}]))), + <<": brown fox:">> = iolist_to_binary(join(re:split("\"the \\\"quick\\\" brown fox\"","\"([^\\\\\"]+|\\\\.)*\"",[]))), + <<"a:b:c">> = iolist_to_binary(join(re:split("abc","",[trim]))), <<"a:bc">> = iolist_to_binary(join(re:split("abc","",[{parts, - 2}]))), - <<"a:b:c:">> = iolist_to_binary(join(re:split("abc","",[]))), - <<"">> = iolist_to_binary(join(re:split("acb","a[^a]b",[trim]))), + 2}]))), + <<"a:b:c:">> = iolist_to_binary(join(re:split("abc","",[]))), + <<"">> = iolist_to_binary(join(re:split("acb","a[^a]b",[trim]))), <<":">> = iolist_to_binary(join(re:split("acb","a[^a]b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("acb","a[^a]b",[]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("acb","a[^a]b",[]))), <<"">> = iolist_to_binary(join(re:split("a -b","a[^a]b",[trim]))), +b","a[^a]b",[trim]))), <<":">> = iolist_to_binary(join(re:split("a -b","a[^a]b",[{parts,2}]))), +b","a[^a]b",[{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("a -b","a[^a]b",[]))), - <<"">> = iolist_to_binary(join(re:split("acb","a.b",[trim]))), +b","a[^a]b",[]))), + <<"">> = iolist_to_binary(join(re:split("acb","a.b",[trim]))), <<":">> = iolist_to_binary(join(re:split("acb","a.b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("acb","a.b",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a.b",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("acb","a.b",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a.b",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a.b",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a.b",[]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a.b",[]))), <<"a b">> = iolist_to_binary(join(re:split("a -b","a.b",[trim]))), +b","a.b",[trim]))), <<"a b">> = iolist_to_binary(join(re:split("a -b","a.b",[{parts,2}]))), +b","a.b",[{parts,2}]))), <<"a b">> = iolist_to_binary(join(re:split("a -b","a.b",[]))), +b","a.b",[]))), <<"">> = iolist_to_binary(join(re:split("acb","a[^a]b",[dotall, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("acb","a[^a]b",[dotall, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("acb","a[^a]b",[dotall]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("acb","a[^a]b",[dotall]))), <<"">> = iolist_to_binary(join(re:split("a -b","a[^a]b",[dotall,trim]))), +b","a[^a]b",[dotall,trim]))), <<":">> = iolist_to_binary(join(re:split("a -b","a[^a]b",[dotall,{parts,2}]))), +b","a[^a]b",[dotall,{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("a -b","a[^a]b",[dotall]))), +b","a[^a]b",[dotall]))), ok. run11() -> <<"">> = iolist_to_binary(join(re:split("acb","a.b",[dotall, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("acb","a.b",[dotall, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("acb","a.b",[dotall]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("acb","a.b",[dotall]))), <<"">> = iolist_to_binary(join(re:split("a -b","a.b",[dotall,trim]))), +b","a.b",[dotall,trim]))), <<":">> = iolist_to_binary(join(re:split("a -b","a.b",[dotall,{parts,2}]))), +b","a.b",[dotall,{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("a -b","a.b",[dotall]))), - <<":a">> = iolist_to_binary(join(re:split("bac","^(b+?|a){1,2}?c",[trim]))), +b","a.b",[dotall]))), + <<":a">> = iolist_to_binary(join(re:split("bac","^(b+?|a){1,2}?c",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+?|a){1,2}?c",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+?|a){1,2}?c",[]))), - <<":a">> = iolist_to_binary(join(re:split("bbac","^(b+?|a){1,2}?c",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+?|a){1,2}?c",[]))), + <<":a">> = iolist_to_binary(join(re:split("bbac","^(b+?|a){1,2}?c",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+?|a){1,2}?c",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+?|a){1,2}?c",[]))), - <<":a">> = iolist_to_binary(join(re:split("bbbac","^(b+?|a){1,2}?c",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+?|a){1,2}?c",[]))), + <<":a">> = iolist_to_binary(join(re:split("bbbac","^(b+?|a){1,2}?c",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("bbbac","^(b+?|a){1,2}?c",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("bbbac","^(b+?|a){1,2}?c",[]))), - <<":a">> = iolist_to_binary(join(re:split("bbbbac","^(b+?|a){1,2}?c",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("bbbac","^(b+?|a){1,2}?c",[]))), + <<":a">> = iolist_to_binary(join(re:split("bbbbac","^(b+?|a){1,2}?c",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("bbbbac","^(b+?|a){1,2}?c",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("bbbbac","^(b+?|a){1,2}?c",[]))), - <<":a">> = iolist_to_binary(join(re:split("bbbbbac","^(b+?|a){1,2}?c",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("bbbbac","^(b+?|a){1,2}?c",[]))), + <<":a">> = iolist_to_binary(join(re:split("bbbbbac","^(b+?|a){1,2}?c",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("bbbbbac","^(b+?|a){1,2}?c",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("bbbbbac","^(b+?|a){1,2}?c",[]))), - <<":a">> = iolist_to_binary(join(re:split("bac","^(b+|a){1,2}?c",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("bbbbbac","^(b+?|a){1,2}?c",[]))), + <<":a">> = iolist_to_binary(join(re:split("bac","^(b+|a){1,2}?c",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+|a){1,2}?c",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+|a){1,2}?c",[]))), - <<":a">> = iolist_to_binary(join(re:split("bbac","^(b+|a){1,2}?c",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("bac","^(b+|a){1,2}?c",[]))), + <<":a">> = iolist_to_binary(join(re:split("bbac","^(b+|a){1,2}?c",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+|a){1,2}?c",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+|a){1,2}?c",[]))), - <<":a">> = iolist_to_binary(join(re:split("bbbac","^(b+|a){1,2}?c",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("bbac","^(b+|a){1,2}?c",[]))), + <<":a">> = iolist_to_binary(join(re:split("bbbac","^(b+|a){1,2}?c",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("bbbac","^(b+|a){1,2}?c",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("bbbac","^(b+|a){1,2}?c",[]))), - <<":a">> = iolist_to_binary(join(re:split("bbbbac","^(b+|a){1,2}?c",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("bbbac","^(b+|a){1,2}?c",[]))), + <<":a">> = iolist_to_binary(join(re:split("bbbbac","^(b+|a){1,2}?c",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("bbbbac","^(b+|a){1,2}?c",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("bbbbac","^(b+|a){1,2}?c",[]))), - <<":a">> = iolist_to_binary(join(re:split("bbbbbac","^(b+|a){1,2}?c",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("bbbbac","^(b+|a){1,2}?c",[]))), + <<":a">> = iolist_to_binary(join(re:split("bbbbbac","^(b+|a){1,2}?c",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("bbbbbac","^(b+|a){1,2}?c",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("bbbbbac","^(b+|a){1,2}?c",[]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("bbbbbac","^(b+|a){1,2}?c",[]))), <<"x b">> = iolist_to_binary(join(re:split("x -b","(?!\\A)x",[multiline,trim]))), +b","(?!\\A)x",[multiline,trim]))), <<"x b">> = iolist_to_binary(join(re:split("x -b","(?!\\A)x",[multiline,{parts,2}]))), +b","(?!\\A)x",[multiline,{parts,2}]))), <<"x b">> = iolist_to_binary(join(re:split("x -b","(?!\\A)x",[multiline]))), +b","(?!\\A)x",[multiline]))), <<"a">> = iolist_to_binary(join(re:split("ax","(?!\\A)x",[multiline, - trim]))), + trim]))), <<"a:">> = iolist_to_binary(join(re:split("ax","(?!\\A)x",[multiline, {parts, - 2}]))), - <<"a:">> = iolist_to_binary(join(re:split("ax","(?!\\A)x",[multiline]))), - <<"{ab}">> = iolist_to_binary(join(re:split("{ab}","\\x0{ab}",[trim]))), + 2}]))), + <<"a:">> = iolist_to_binary(join(re:split("ax","(?!\\A)x",[multiline]))), + <<"{ab}">> = iolist_to_binary(join(re:split("{ab}","\\x0{ab}",[trim]))), <<"{ab}">> = iolist_to_binary(join(re:split("{ab}","\\x0{ab}",[{parts, - 2}]))), - <<"{ab}">> = iolist_to_binary(join(re:split("{ab}","\\x0{ab}",[]))), - <<"">> = iolist_to_binary(join(re:split("CD","(A|B)*?CD",[trim]))), + 2}]))), + <<"{ab}">> = iolist_to_binary(join(re:split("{ab}","\\x0{ab}",[]))), + <<"">> = iolist_to_binary(join(re:split("CD","(A|B)*?CD",[trim]))), <<"::">> = iolist_to_binary(join(re:split("CD","(A|B)*?CD",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("CD","(A|B)*?CD",[]))), - <<"">> = iolist_to_binary(join(re:split("CD","(A|B)*CD",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("CD","(A|B)*?CD",[]))), + <<"">> = iolist_to_binary(join(re:split("CD","(A|B)*CD",[trim]))), <<"::">> = iolist_to_binary(join(re:split("CD","(A|B)*CD",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("CD","(A|B)*CD",[]))), - <<":AB:AB">> = iolist_to_binary(join(re:split("ABABAB","(AB)*?\\1",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("CD","(A|B)*CD",[]))), + <<":AB:AB">> = iolist_to_binary(join(re:split("ABABAB","(AB)*?\\1",[trim]))), <<":AB:AB">> = iolist_to_binary(join(re:split("ABABAB","(AB)*?\\1",[{parts, - 2}]))), - <<":AB:AB">> = iolist_to_binary(join(re:split("ABABAB","(AB)*?\\1",[]))), - <<":AB">> = iolist_to_binary(join(re:split("ABABAB","(AB)*\\1",[trim]))), + 2}]))), + <<":AB:AB">> = iolist_to_binary(join(re:split("ABABAB","(AB)*?\\1",[]))), + <<":AB">> = iolist_to_binary(join(re:split("ABABAB","(AB)*\\1",[trim]))), <<":AB:">> = iolist_to_binary(join(re:split("ABABAB","(AB)*\\1",[{parts, - 2}]))), - <<":AB:">> = iolist_to_binary(join(re:split("ABABAB","(AB)*\\1",[]))), - <<"">> = iolist_to_binary(join(re:split("foo","(?<!bar)foo",[trim]))), + 2}]))), + <<":AB:">> = iolist_to_binary(join(re:split("ABABAB","(AB)*\\1",[]))), + <<"">> = iolist_to_binary(join(re:split("foo","(?<!bar)foo",[trim]))), <<":">> = iolist_to_binary(join(re:split("foo","(?<!bar)foo",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("foo","(?<!bar)foo",[]))), - <<"cat:d">> = iolist_to_binary(join(re:split("catfood","(?<!bar)foo",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("foo","(?<!bar)foo",[]))), + <<"cat:d">> = iolist_to_binary(join(re:split("catfood","(?<!bar)foo",[trim]))), <<"cat:d">> = iolist_to_binary(join(re:split("catfood","(?<!bar)foo",[{parts, - 2}]))), - <<"cat:d">> = iolist_to_binary(join(re:split("catfood","(?<!bar)foo",[]))), - <<"ar:tle">> = iolist_to_binary(join(re:split("arfootle","(?<!bar)foo",[trim]))), + 2}]))), + <<"cat:d">> = iolist_to_binary(join(re:split("catfood","(?<!bar)foo",[]))), + <<"ar:tle">> = iolist_to_binary(join(re:split("arfootle","(?<!bar)foo",[trim]))), <<"ar:tle">> = iolist_to_binary(join(re:split("arfootle","(?<!bar)foo",[{parts, - 2}]))), - <<"ar:tle">> = iolist_to_binary(join(re:split("arfootle","(?<!bar)foo",[]))), - <<"r:sh">> = iolist_to_binary(join(re:split("rfoosh","(?<!bar)foo",[trim]))), + 2}]))), + <<"ar:tle">> = iolist_to_binary(join(re:split("arfootle","(?<!bar)foo",[]))), + <<"r:sh">> = iolist_to_binary(join(re:split("rfoosh","(?<!bar)foo",[trim]))), <<"r:sh">> = iolist_to_binary(join(re:split("rfoosh","(?<!bar)foo",[{parts, - 2}]))), - <<"r:sh">> = iolist_to_binary(join(re:split("rfoosh","(?<!bar)foo",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<!bar)foo",[trim]))), + 2}]))), + <<"r:sh">> = iolist_to_binary(join(re:split("rfoosh","(?<!bar)foo",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<!bar)foo",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<!bar)foo",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<!bar)foo",[]))), - <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","(?<!bar)foo",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<!bar)foo",[]))), + <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","(?<!bar)foo",[trim]))), <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","(?<!bar)foo",[{parts, - 2}]))), - <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","(?<!bar)foo",[]))), - <<"towbarfoo">> = iolist_to_binary(join(re:split("towbarfoo","(?<!bar)foo",[trim]))), + 2}]))), + <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","(?<!bar)foo",[]))), + <<"towbarfoo">> = iolist_to_binary(join(re:split("towbarfoo","(?<!bar)foo",[trim]))), <<"towbarfoo">> = iolist_to_binary(join(re:split("towbarfoo","(?<!bar)foo",[{parts, - 2}]))), - <<"towbarfoo">> = iolist_to_binary(join(re:split("towbarfoo","(?<!bar)foo",[]))), - <<":d">> = iolist_to_binary(join(re:split("catfood","\\w{3}(?<!bar)foo",[trim]))), + 2}]))), + <<"towbarfoo">> = iolist_to_binary(join(re:split("towbarfoo","(?<!bar)foo",[]))), + <<":d">> = iolist_to_binary(join(re:split("catfood","\\w{3}(?<!bar)foo",[trim]))), <<":d">> = iolist_to_binary(join(re:split("catfood","\\w{3}(?<!bar)foo",[{parts, - 2}]))), - <<":d">> = iolist_to_binary(join(re:split("catfood","\\w{3}(?<!bar)foo",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\w{3}(?<!bar)foo",[trim]))), + 2}]))), + <<":d">> = iolist_to_binary(join(re:split("catfood","\\w{3}(?<!bar)foo",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\w{3}(?<!bar)foo",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\w{3}(?<!bar)foo",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\w{3}(?<!bar)foo",[]))), - <<"foo">> = iolist_to_binary(join(re:split("foo","\\w{3}(?<!bar)foo",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\w{3}(?<!bar)foo",[]))), + <<"foo">> = iolist_to_binary(join(re:split("foo","\\w{3}(?<!bar)foo",[trim]))), <<"foo">> = iolist_to_binary(join(re:split("foo","\\w{3}(?<!bar)foo",[{parts, - 2}]))), - <<"foo">> = iolist_to_binary(join(re:split("foo","\\w{3}(?<!bar)foo",[]))), - <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","\\w{3}(?<!bar)foo",[trim]))), + 2}]))), + <<"foo">> = iolist_to_binary(join(re:split("foo","\\w{3}(?<!bar)foo",[]))), + <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","\\w{3}(?<!bar)foo",[trim]))), <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","\\w{3}(?<!bar)foo",[{parts, - 2}]))), - <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","\\w{3}(?<!bar)foo",[]))), - <<"towbarfoo">> = iolist_to_binary(join(re:split("towbarfoo","\\w{3}(?<!bar)foo",[trim]))), + 2}]))), + <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","\\w{3}(?<!bar)foo",[]))), + <<"towbarfoo">> = iolist_to_binary(join(re:split("towbarfoo","\\w{3}(?<!bar)foo",[trim]))), <<"towbarfoo">> = iolist_to_binary(join(re:split("towbarfoo","\\w{3}(?<!bar)foo",[{parts, - 2}]))), - <<"towbarfoo">> = iolist_to_binary(join(re:split("towbarfoo","\\w{3}(?<!bar)foo",[]))), - <<"fooa:foo">> = iolist_to_binary(join(re:split("fooabar","(?<=(foo)a)bar",[trim]))), + 2}]))), + <<"towbarfoo">> = iolist_to_binary(join(re:split("towbarfoo","\\w{3}(?<!bar)foo",[]))), + <<"fooa:foo">> = iolist_to_binary(join(re:split("fooabar","(?<=(foo)a)bar",[trim]))), <<"fooa:foo:">> = iolist_to_binary(join(re:split("fooabar","(?<=(foo)a)bar",[{parts, - 2}]))), - <<"fooa:foo:">> = iolist_to_binary(join(re:split("fooabar","(?<=(foo)a)bar",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(foo)a)bar",[trim]))), + 2}]))), + <<"fooa:foo:">> = iolist_to_binary(join(re:split("fooabar","(?<=(foo)a)bar",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(foo)a)bar",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(foo)a)bar",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(foo)a)bar",[]))), - <<"bar">> = iolist_to_binary(join(re:split("bar","(?<=(foo)a)bar",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(foo)a)bar",[]))), + <<"bar">> = iolist_to_binary(join(re:split("bar","(?<=(foo)a)bar",[trim]))), <<"bar">> = iolist_to_binary(join(re:split("bar","(?<=(foo)a)bar",[{parts, - 2}]))), - <<"bar">> = iolist_to_binary(join(re:split("bar","(?<=(foo)a)bar",[]))), - <<"foobbar">> = iolist_to_binary(join(re:split("foobbar","(?<=(foo)a)bar",[trim]))), + 2}]))), + <<"bar">> = iolist_to_binary(join(re:split("bar","(?<=(foo)a)bar",[]))), + <<"foobbar">> = iolist_to_binary(join(re:split("foobbar","(?<=(foo)a)bar",[trim]))), <<"foobbar">> = iolist_to_binary(join(re:split("foobbar","(?<=(foo)a)bar",[{parts, - 2}]))), - <<"foobbar">> = iolist_to_binary(join(re:split("foobbar","(?<=(foo)a)bar",[]))), + 2}]))), + <<"foobbar">> = iolist_to_binary(join(re:split("foobbar","(?<=(foo)a)bar",[]))), <<"">> = iolist_to_binary(join(re:split("abc","\\Aabc\\z",[multiline, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\z",[multiline, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\z",[multiline]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\z",[multiline]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc\\z",[multiline, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc\\z",[multiline, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc\\z",[multiline]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc\\z",[multiline]))), <<"">> = iolist_to_binary(join(re:split("abc","\\Aabc\\z",[multiline, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\z",[multiline, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\z",[multiline]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc\\z",[multiline]))), <<"qqq abc">> = iolist_to_binary(join(re:split("qqq -abc","\\Aabc\\z",[multiline,trim]))), +abc","\\Aabc\\z",[multiline,trim]))), <<"qqq abc">> = iolist_to_binary(join(re:split("qqq -abc","\\Aabc\\z",[multiline,{parts,2}]))), +abc","\\Aabc\\z",[multiline,{parts,2}]))), <<"qqq abc">> = iolist_to_binary(join(re:split("qqq -abc","\\Aabc\\z",[multiline]))), +abc","\\Aabc\\z",[multiline]))), <<"abc zzz">> = iolist_to_binary(join(re:split("abc -zzz","\\Aabc\\z",[multiline,trim]))), +zzz","\\Aabc\\z",[multiline,trim]))), <<"abc zzz">> = iolist_to_binary(join(re:split("abc -zzz","\\Aabc\\z",[multiline,{parts,2}]))), +zzz","\\Aabc\\z",[multiline,{parts,2}]))), <<"abc zzz">> = iolist_to_binary(join(re:split("abc -zzz","\\Aabc\\z",[multiline]))), +zzz","\\Aabc\\z",[multiline]))), <<"qqq abc zzz">> = iolist_to_binary(join(re:split("qqq abc -zzz","\\Aabc\\z",[multiline,trim]))), +zzz","\\Aabc\\z",[multiline,trim]))), <<"qqq abc zzz">> = iolist_to_binary(join(re:split("qqq abc -zzz","\\Aabc\\z",[multiline,{parts,2}]))), +zzz","\\Aabc\\z",[multiline,{parts,2}]))), <<"qqq abc zzz">> = iolist_to_binary(join(re:split("qqq abc -zzz","\\Aabc\\z",[multiline]))), - <<"1:.23">> = iolist_to_binary(join(re:split("1.230003938","(?>(\\.\\d\\d[1-9]?))\\d+",[trim]))), +zzz","\\Aabc\\z",[multiline]))), + <<"1:.23">> = iolist_to_binary(join(re:split("1.230003938","(?>(\\.\\d\\d[1-9]?))\\d+",[trim]))), <<"1:.23:">> = iolist_to_binary(join(re:split("1.230003938","(?>(\\.\\d\\d[1-9]?))\\d+",[{parts, - 2}]))), - <<"1:.23:">> = iolist_to_binary(join(re:split("1.230003938","(?>(\\.\\d\\d[1-9]?))\\d+",[]))), - <<"1:.875">> = iolist_to_binary(join(re:split("1.875000282","(?>(\\.\\d\\d[1-9]?))\\d+",[trim]))), + 2}]))), + <<"1:.23:">> = iolist_to_binary(join(re:split("1.230003938","(?>(\\.\\d\\d[1-9]?))\\d+",[]))), + <<"1:.875">> = iolist_to_binary(join(re:split("1.875000282","(?>(\\.\\d\\d[1-9]?))\\d+",[trim]))), <<"1:.875:">> = iolist_to_binary(join(re:split("1.875000282","(?>(\\.\\d\\d[1-9]?))\\d+",[{parts, - 2}]))), - <<"1:.875:">> = iolist_to_binary(join(re:split("1.875000282","(?>(\\.\\d\\d[1-9]?))\\d+",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>(\\.\\d\\d[1-9]?))\\d+",[trim]))), + 2}]))), + <<"1:.875:">> = iolist_to_binary(join(re:split("1.875000282","(?>(\\.\\d\\d[1-9]?))\\d+",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>(\\.\\d\\d[1-9]?))\\d+",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>(\\.\\d\\d[1-9]?))\\d+",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>(\\.\\d\\d[1-9]?))\\d+",[]))), - <<"1.235">> = iolist_to_binary(join(re:split("1.235","(?>(\\.\\d\\d[1-9]?))\\d+",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>(\\.\\d\\d[1-9]?))\\d+",[]))), + <<"1.235">> = iolist_to_binary(join(re:split("1.235","(?>(\\.\\d\\d[1-9]?))\\d+",[trim]))), <<"1.235">> = iolist_to_binary(join(re:split("1.235","(?>(\\.\\d\\d[1-9]?))\\d+",[{parts, - 2}]))), - <<"1.235">> = iolist_to_binary(join(re:split("1.235","(?>(\\.\\d\\d[1-9]?))\\d+",[]))), - <<":party">> = iolist_to_binary(join(re:split("now is the time for all good men to come to the aid of the party","^((?>\\w+)|(?>\\s+))*$",[trim]))), + 2}]))), + <<"1.235">> = iolist_to_binary(join(re:split("1.235","(?>(\\.\\d\\d[1-9]?))\\d+",[]))), + <<":party">> = iolist_to_binary(join(re:split("now is the time for all good men to come to the aid of the party","^((?>\\w+)|(?>\\s+))*$",[trim]))), <<":party:">> = iolist_to_binary(join(re:split("now is the time for all good men to come to the aid of the party","^((?>\\w+)|(?>\\s+))*$",[{parts, - 2}]))), - <<":party:">> = iolist_to_binary(join(re:split("now is the time for all good men to come to the aid of the party","^((?>\\w+)|(?>\\s+))*$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^((?>\\w+)|(?>\\s+))*$",[trim]))), + 2}]))), + <<":party:">> = iolist_to_binary(join(re:split("now is the time for all good men to come to the aid of the party","^((?>\\w+)|(?>\\s+))*$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^((?>\\w+)|(?>\\s+))*$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^((?>\\w+)|(?>\\s+))*$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^((?>\\w+)|(?>\\s+))*$",[]))), - <<"this is not a line with only words and spaces!">> = iolist_to_binary(join(re:split("this is not a line with only words and spaces!","^((?>\\w+)|(?>\\s+))*$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^((?>\\w+)|(?>\\s+))*$",[]))), + <<"this is not a line with only words and spaces!">> = iolist_to_binary(join(re:split("this is not a line with only words and spaces!","^((?>\\w+)|(?>\\s+))*$",[trim]))), <<"this is not a line with only words and spaces!">> = iolist_to_binary(join(re:split("this is not a line with only words and spaces!","^((?>\\w+)|(?>\\s+))*$",[{parts, - 2}]))), - <<"this is not a line with only words and spaces!">> = iolist_to_binary(join(re:split("this is not a line with only words and spaces!","^((?>\\w+)|(?>\\s+))*$",[]))), - <<":12345:a">> = iolist_to_binary(join(re:split("12345a","(\\d+)(\\w)",[trim]))), + 2}]))), + <<"this is not a line with only words and spaces!">> = iolist_to_binary(join(re:split("this is not a line with only words and spaces!","^((?>\\w+)|(?>\\s+))*$",[]))), + <<":12345:a">> = iolist_to_binary(join(re:split("12345a","(\\d+)(\\w)",[trim]))), <<":12345:a:">> = iolist_to_binary(join(re:split("12345a","(\\d+)(\\w)",[{parts, - 2}]))), - <<":12345:a:">> = iolist_to_binary(join(re:split("12345a","(\\d+)(\\w)",[]))), - <<":1234:5:+">> = iolist_to_binary(join(re:split("12345+","(\\d+)(\\w)",[trim]))), + 2}]))), + <<":12345:a:">> = iolist_to_binary(join(re:split("12345a","(\\d+)(\\w)",[]))), + <<":1234:5:+">> = iolist_to_binary(join(re:split("12345+","(\\d+)(\\w)",[trim]))), <<":1234:5:+">> = iolist_to_binary(join(re:split("12345+","(\\d+)(\\w)",[{parts, - 2}]))), - <<":1234:5:+">> = iolist_to_binary(join(re:split("12345+","(\\d+)(\\w)",[]))), - <<":12345:a">> = iolist_to_binary(join(re:split("12345a","((?>\\d+))(\\w)",[trim]))), + 2}]))), + <<":1234:5:+">> = iolist_to_binary(join(re:split("12345+","(\\d+)(\\w)",[]))), + <<":12345:a">> = iolist_to_binary(join(re:split("12345a","((?>\\d+))(\\w)",[trim]))), <<":12345:a:">> = iolist_to_binary(join(re:split("12345a","((?>\\d+))(\\w)",[{parts, - 2}]))), - <<":12345:a:">> = iolist_to_binary(join(re:split("12345a","((?>\\d+))(\\w)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?>\\d+))(\\w)",[trim]))), + 2}]))), + <<":12345:a:">> = iolist_to_binary(join(re:split("12345a","((?>\\d+))(\\w)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?>\\d+))(\\w)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?>\\d+))(\\w)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?>\\d+))(\\w)",[]))), - <<"12345+">> = iolist_to_binary(join(re:split("12345+","((?>\\d+))(\\w)",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?>\\d+))(\\w)",[]))), + <<"12345+">> = iolist_to_binary(join(re:split("12345+","((?>\\d+))(\\w)",[trim]))), <<"12345+">> = iolist_to_binary(join(re:split("12345+","((?>\\d+))(\\w)",[{parts, - 2}]))), - <<"12345+">> = iolist_to_binary(join(re:split("12345+","((?>\\d+))(\\w)",[]))), - <<"">> = iolist_to_binary(join(re:split("aaab","(?>a+)b",[trim]))), + 2}]))), + <<"12345+">> = iolist_to_binary(join(re:split("12345+","((?>\\d+))(\\w)",[]))), + <<"">> = iolist_to_binary(join(re:split("aaab","(?>a+)b",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaab","(?>a+)b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaab","(?>a+)b",[]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaab","(?>a+)b",[]))), ok. run12() -> - <<":aaab">> = iolist_to_binary(join(re:split("aaab","((?>a+)b)",[trim]))), + <<":aaab">> = iolist_to_binary(join(re:split("aaab","((?>a+)b)",[trim]))), <<":aaab:">> = iolist_to_binary(join(re:split("aaab","((?>a+)b)",[{parts, - 2}]))), - <<":aaab:">> = iolist_to_binary(join(re:split("aaab","((?>a+)b)",[]))), - <<":aaa">> = iolist_to_binary(join(re:split("aaab","(?>(a+))b",[trim]))), + 2}]))), + <<":aaab:">> = iolist_to_binary(join(re:split("aaab","((?>a+)b)",[]))), + <<":aaa">> = iolist_to_binary(join(re:split("aaab","(?>(a+))b",[trim]))), <<":aaa:">> = iolist_to_binary(join(re:split("aaab","(?>(a+))b",[{parts, - 2}]))), - <<":aaa:">> = iolist_to_binary(join(re:split("aaab","(?>(a+))b",[]))), - <<"aaa:ccc">> = iolist_to_binary(join(re:split("aaabbbccc","(?>b)+",[trim]))), + 2}]))), + <<":aaa:">> = iolist_to_binary(join(re:split("aaab","(?>(a+))b",[]))), + <<"aaa:ccc">> = iolist_to_binary(join(re:split("aaabbbccc","(?>b)+",[trim]))), <<"aaa:ccc">> = iolist_to_binary(join(re:split("aaabbbccc","(?>b)+",[{parts, - 2}]))), - <<"aaa:ccc">> = iolist_to_binary(join(re:split("aaabbbccc","(?>b)+",[]))), - <<"::::d">> = iolist_to_binary(join(re:split("aaabbbbccccd","(?>a+|b+|c+)*c",[trim]))), + 2}]))), + <<"aaa:ccc">> = iolist_to_binary(join(re:split("aaabbbccc","(?>b)+",[]))), + <<"::::d">> = iolist_to_binary(join(re:split("aaabbbbccccd","(?>a+|b+|c+)*c",[trim]))), <<":cccd">> = iolist_to_binary(join(re:split("aaabbbbccccd","(?>a+|b+|c+)*c",[{parts, - 2}]))), - <<"::::d">> = iolist_to_binary(join(re:split("aaabbbbccccd","(?>a+|b+|c+)*c",[]))), - <<"((:x">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+",[trim]))), + 2}]))), + <<"::::d">> = iolist_to_binary(join(re:split("aaabbbbccccd","(?>a+|b+|c+)*c",[]))), + <<"((:x">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+",[trim]))), <<"((:x:">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+",[{parts, - 2}]))), - <<"((:x:">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+",[]))), - <<":abc">> = iolist_to_binary(join(re:split("(abc)","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[trim]))), + 2}]))), + <<"((:x:">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+",[]))), + <<":abc">> = iolist_to_binary(join(re:split("(abc)","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[trim]))), <<":abc:">> = iolist_to_binary(join(re:split("(abc)","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[{parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("(abc)","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[]))), - <<":xyz">> = iolist_to_binary(join(re:split("(abc(def)xyz)","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[trim]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("(abc)","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[]))), + <<":xyz">> = iolist_to_binary(join(re:split("(abc(def)xyz)","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[trim]))), <<":xyz:">> = iolist_to_binary(join(re:split("(abc(def)xyz)","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[{parts, - 2}]))), - <<":xyz:">> = iolist_to_binary(join(re:split("(abc(def)xyz)","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[trim]))), + 2}]))), + <<":xyz:">> = iolist_to_binary(join(re:split("(abc(def)xyz)","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[]))), - <<"((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[]))), + <<"((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[trim]))), <<"((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[{parts, - 2}]))), - <<"((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[]))), + 2}]))), + <<"((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","\\(((?>[^()]+)|\\([^()]+\\))+\\)",[]))), <<"">> = iolist_to_binary(join(re:split("ab","a(?-i)b",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ab","a(?-i)b",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ab","a(?-i)b",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ab","a(?-i)b",[caseless]))), <<"">> = iolist_to_binary(join(re:split("Ab","a(?-i)b",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("Ab","a(?-i)b",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("Ab","a(?-i)b",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("Ab","a(?-i)b",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?-i)b",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?-i)b",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?-i)b",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?-i)b",[caseless]))), <<"aB">> = iolist_to_binary(join(re:split("aB","a(?-i)b",[caseless, - trim]))), + trim]))), <<"aB">> = iolist_to_binary(join(re:split("aB","a(?-i)b",[caseless, {parts, - 2}]))), - <<"aB">> = iolist_to_binary(join(re:split("aB","a(?-i)b",[caseless]))), + 2}]))), + <<"aB">> = iolist_to_binary(join(re:split("aB","a(?-i)b",[caseless]))), <<"AB">> = iolist_to_binary(join(re:split("AB","a(?-i)b",[caseless, - trim]))), + trim]))), <<"AB">> = iolist_to_binary(join(re:split("AB","a(?-i)b",[caseless, {parts, - 2}]))), - <<"AB">> = iolist_to_binary(join(re:split("AB","a(?-i)b",[caseless]))), - <<":a bc">> = iolist_to_binary(join(re:split("a bcd e","(a (?x)b c)d e",[trim]))), + 2}]))), + <<"AB">> = iolist_to_binary(join(re:split("AB","a(?-i)b",[caseless]))), + <<":a bc">> = iolist_to_binary(join(re:split("a bcd e","(a (?x)b c)d e",[trim]))), <<":a bc:">> = iolist_to_binary(join(re:split("a bcd e","(a (?x)b c)d e",[{parts, - 2}]))), - <<":a bc:">> = iolist_to_binary(join(re:split("a bcd e","(a (?x)b c)d e",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a (?x)b c)d e",[trim]))), + 2}]))), + <<":a bc:">> = iolist_to_binary(join(re:split("a bcd e","(a (?x)b c)d e",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a (?x)b c)d e",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a (?x)b c)d e",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a (?x)b c)d e",[]))), - <<"a b cd e">> = iolist_to_binary(join(re:split("a b cd e","(a (?x)b c)d e",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a (?x)b c)d e",[]))), + <<"a b cd e">> = iolist_to_binary(join(re:split("a b cd e","(a (?x)b c)d e",[trim]))), <<"a b cd e">> = iolist_to_binary(join(re:split("a b cd e","(a (?x)b c)d e",[{parts, - 2}]))), - <<"a b cd e">> = iolist_to_binary(join(re:split("a b cd e","(a (?x)b c)d e",[]))), - <<"abcd e">> = iolist_to_binary(join(re:split("abcd e","(a (?x)b c)d e",[trim]))), + 2}]))), + <<"a b cd e">> = iolist_to_binary(join(re:split("a b cd e","(a (?x)b c)d e",[]))), + <<"abcd e">> = iolist_to_binary(join(re:split("abcd e","(a (?x)b c)d e",[trim]))), <<"abcd e">> = iolist_to_binary(join(re:split("abcd e","(a (?x)b c)d e",[{parts, - 2}]))), - <<"abcd e">> = iolist_to_binary(join(re:split("abcd e","(a (?x)b c)d e",[]))), - <<"a bcde">> = iolist_to_binary(join(re:split("a bcde","(a (?x)b c)d e",[trim]))), + 2}]))), + <<"abcd e">> = iolist_to_binary(join(re:split("abcd e","(a (?x)b c)d e",[]))), + <<"a bcde">> = iolist_to_binary(join(re:split("a bcde","(a (?x)b c)d e",[trim]))), <<"a bcde">> = iolist_to_binary(join(re:split("a bcde","(a (?x)b c)d e",[{parts, - 2}]))), - <<"a bcde">> = iolist_to_binary(join(re:split("a bcde","(a (?x)b c)d e",[]))), - <<":a bcde f">> = iolist_to_binary(join(re:split("a bcde f","(a b(?x)c d (?-x)e f)",[trim]))), + 2}]))), + <<"a bcde">> = iolist_to_binary(join(re:split("a bcde","(a (?x)b c)d e",[]))), + <<":a bcde f">> = iolist_to_binary(join(re:split("a bcde f","(a b(?x)c d (?-x)e f)",[trim]))), <<":a bcde f:">> = iolist_to_binary(join(re:split("a bcde f","(a b(?x)c d (?-x)e f)",[{parts, - 2}]))), - <<":a bcde f:">> = iolist_to_binary(join(re:split("a bcde f","(a b(?x)c d (?-x)e f)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a b(?x)c d (?-x)e f)",[trim]))), + 2}]))), + <<":a bcde f:">> = iolist_to_binary(join(re:split("a bcde f","(a b(?x)c d (?-x)e f)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a b(?x)c d (?-x)e f)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a b(?x)c d (?-x)e f)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a b(?x)c d (?-x)e f)",[]))), - <<"abcdef">> = iolist_to_binary(join(re:split("abcdef","(a b(?x)c d (?-x)e f)",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a b(?x)c d (?-x)e f)",[]))), + <<"abcdef">> = iolist_to_binary(join(re:split("abcdef","(a b(?x)c d (?-x)e f)",[trim]))), <<"abcdef">> = iolist_to_binary(join(re:split("abcdef","(a b(?x)c d (?-x)e f)",[{parts, - 2}]))), - <<"abcdef">> = iolist_to_binary(join(re:split("abcdef","(a b(?x)c d (?-x)e f)",[]))), - <<":ab">> = iolist_to_binary(join(re:split("abc","(a(?i)b)c",[trim]))), + 2}]))), + <<"abcdef">> = iolist_to_binary(join(re:split("abcdef","(a b(?x)c d (?-x)e f)",[]))), + <<":ab">> = iolist_to_binary(join(re:split("abc","(a(?i)b)c",[trim]))), <<":ab:">> = iolist_to_binary(join(re:split("abc","(a(?i)b)c",[{parts, - 2}]))), - <<":ab:">> = iolist_to_binary(join(re:split("abc","(a(?i)b)c",[]))), - <<":aB">> = iolist_to_binary(join(re:split("aBc","(a(?i)b)c",[trim]))), + 2}]))), + <<":ab:">> = iolist_to_binary(join(re:split("abc","(a(?i)b)c",[]))), + <<":aB">> = iolist_to_binary(join(re:split("aBc","(a(?i)b)c",[trim]))), <<":aB:">> = iolist_to_binary(join(re:split("aBc","(a(?i)b)c",[{parts, - 2}]))), - <<":aB:">> = iolist_to_binary(join(re:split("aBc","(a(?i)b)c",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a(?i)b)c",[trim]))), + 2}]))), + <<":aB:">> = iolist_to_binary(join(re:split("aBc","(a(?i)b)c",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a(?i)b)c",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a(?i)b)c",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a(?i)b)c",[]))), - <<"abC">> = iolist_to_binary(join(re:split("abC","(a(?i)b)c",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a(?i)b)c",[]))), + <<"abC">> = iolist_to_binary(join(re:split("abC","(a(?i)b)c",[trim]))), <<"abC">> = iolist_to_binary(join(re:split("abC","(a(?i)b)c",[{parts, - 2}]))), - <<"abC">> = iolist_to_binary(join(re:split("abC","(a(?i)b)c",[]))), - <<"aBC">> = iolist_to_binary(join(re:split("aBC","(a(?i)b)c",[trim]))), + 2}]))), + <<"abC">> = iolist_to_binary(join(re:split("abC","(a(?i)b)c",[]))), + <<"aBC">> = iolist_to_binary(join(re:split("aBC","(a(?i)b)c",[trim]))), <<"aBC">> = iolist_to_binary(join(re:split("aBC","(a(?i)b)c",[{parts, - 2}]))), - <<"aBC">> = iolist_to_binary(join(re:split("aBC","(a(?i)b)c",[]))), - <<"Abc">> = iolist_to_binary(join(re:split("Abc","(a(?i)b)c",[trim]))), + 2}]))), + <<"aBC">> = iolist_to_binary(join(re:split("aBC","(a(?i)b)c",[]))), + <<"Abc">> = iolist_to_binary(join(re:split("Abc","(a(?i)b)c",[trim]))), <<"Abc">> = iolist_to_binary(join(re:split("Abc","(a(?i)b)c",[{parts, - 2}]))), - <<"Abc">> = iolist_to_binary(join(re:split("Abc","(a(?i)b)c",[]))), - <<"ABc">> = iolist_to_binary(join(re:split("ABc","(a(?i)b)c",[trim]))), + 2}]))), + <<"Abc">> = iolist_to_binary(join(re:split("Abc","(a(?i)b)c",[]))), + <<"ABc">> = iolist_to_binary(join(re:split("ABc","(a(?i)b)c",[trim]))), <<"ABc">> = iolist_to_binary(join(re:split("ABc","(a(?i)b)c",[{parts, - 2}]))), - <<"ABc">> = iolist_to_binary(join(re:split("ABc","(a(?i)b)c",[]))), - <<"ABC">> = iolist_to_binary(join(re:split("ABC","(a(?i)b)c",[trim]))), + 2}]))), + <<"ABc">> = iolist_to_binary(join(re:split("ABc","(a(?i)b)c",[]))), + <<"ABC">> = iolist_to_binary(join(re:split("ABC","(a(?i)b)c",[trim]))), <<"ABC">> = iolist_to_binary(join(re:split("ABC","(a(?i)b)c",[{parts, - 2}]))), - <<"ABC">> = iolist_to_binary(join(re:split("ABC","(a(?i)b)c",[]))), - <<"AbC">> = iolist_to_binary(join(re:split("AbC","(a(?i)b)c",[trim]))), + 2}]))), + <<"ABC">> = iolist_to_binary(join(re:split("ABC","(a(?i)b)c",[]))), + <<"AbC">> = iolist_to_binary(join(re:split("AbC","(a(?i)b)c",[trim]))), <<"AbC">> = iolist_to_binary(join(re:split("AbC","(a(?i)b)c",[{parts, - 2}]))), - <<"AbC">> = iolist_to_binary(join(re:split("AbC","(a(?i)b)c",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","a(?i:b)c",[trim]))), + 2}]))), + <<"AbC">> = iolist_to_binary(join(re:split("AbC","(a(?i)b)c",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","a(?i:b)c",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","a(?i:b)c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","a(?i:b)c",[]))), - <<"">> = iolist_to_binary(join(re:split("aBc","a(?i:b)c",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","a(?i:b)c",[]))), + <<"">> = iolist_to_binary(join(re:split("aBc","a(?i:b)c",[trim]))), <<":">> = iolist_to_binary(join(re:split("aBc","a(?i:b)c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aBc","a(?i:b)c",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?i:b)c",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aBc","a(?i:b)c",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?i:b)c",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?i:b)c",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?i:b)c",[]))), - <<"ABC">> = iolist_to_binary(join(re:split("ABC","a(?i:b)c",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?i:b)c",[]))), + <<"ABC">> = iolist_to_binary(join(re:split("ABC","a(?i:b)c",[trim]))), <<"ABC">> = iolist_to_binary(join(re:split("ABC","a(?i:b)c",[{parts, - 2}]))), - <<"ABC">> = iolist_to_binary(join(re:split("ABC","a(?i:b)c",[]))), - <<"abC">> = iolist_to_binary(join(re:split("abC","a(?i:b)c",[trim]))), + 2}]))), + <<"ABC">> = iolist_to_binary(join(re:split("ABC","a(?i:b)c",[]))), + <<"abC">> = iolist_to_binary(join(re:split("abC","a(?i:b)c",[trim]))), <<"abC">> = iolist_to_binary(join(re:split("abC","a(?i:b)c",[{parts, - 2}]))), - <<"abC">> = iolist_to_binary(join(re:split("abC","a(?i:b)c",[]))), - <<"aBC">> = iolist_to_binary(join(re:split("aBC","a(?i:b)c",[trim]))), + 2}]))), + <<"abC">> = iolist_to_binary(join(re:split("abC","a(?i:b)c",[]))), + <<"aBC">> = iolist_to_binary(join(re:split("aBC","a(?i:b)c",[trim]))), <<"aBC">> = iolist_to_binary(join(re:split("aBC","a(?i:b)c",[{parts, - 2}]))), - <<"aBC">> = iolist_to_binary(join(re:split("aBC","a(?i:b)c",[]))), - <<"">> = iolist_to_binary(join(re:split("aBc","a(?i:b)*c",[trim]))), + 2}]))), + <<"aBC">> = iolist_to_binary(join(re:split("aBC","a(?i:b)c",[]))), + <<"">> = iolist_to_binary(join(re:split("aBc","a(?i:b)*c",[trim]))), <<":">> = iolist_to_binary(join(re:split("aBc","a(?i:b)*c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aBc","a(?i:b)*c",[]))), - <<"">> = iolist_to_binary(join(re:split("aBBc","a(?i:b)*c",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aBc","a(?i:b)*c",[]))), + <<"">> = iolist_to_binary(join(re:split("aBBc","a(?i:b)*c",[trim]))), <<":">> = iolist_to_binary(join(re:split("aBBc","a(?i:b)*c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aBBc","a(?i:b)*c",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?i:b)*c",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aBBc","a(?i:b)*c",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?i:b)*c",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?i:b)*c",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?i:b)*c",[]))), - <<"aBC">> = iolist_to_binary(join(re:split("aBC","a(?i:b)*c",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?i:b)*c",[]))), + <<"aBC">> = iolist_to_binary(join(re:split("aBC","a(?i:b)*c",[trim]))), <<"aBC">> = iolist_to_binary(join(re:split("aBC","a(?i:b)*c",[{parts, - 2}]))), - <<"aBC">> = iolist_to_binary(join(re:split("aBC","a(?i:b)*c",[]))), - <<"aBBC">> = iolist_to_binary(join(re:split("aBBC","a(?i:b)*c",[trim]))), + 2}]))), + <<"aBC">> = iolist_to_binary(join(re:split("aBC","a(?i:b)*c",[]))), + <<"aBBC">> = iolist_to_binary(join(re:split("aBBC","a(?i:b)*c",[trim]))), <<"aBBC">> = iolist_to_binary(join(re:split("aBBC","a(?i:b)*c",[{parts, - 2}]))), - <<"aBBC">> = iolist_to_binary(join(re:split("aBBC","a(?i:b)*c",[]))), - <<"">> = iolist_to_binary(join(re:split("abcd","a(?=b(?i)c)\\w\\wd",[trim]))), + 2}]))), + <<"aBBC">> = iolist_to_binary(join(re:split("aBBC","a(?i:b)*c",[]))), + <<"">> = iolist_to_binary(join(re:split("abcd","a(?=b(?i)c)\\w\\wd",[trim]))), <<":">> = iolist_to_binary(join(re:split("abcd","a(?=b(?i)c)\\w\\wd",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abcd","a(?=b(?i)c)\\w\\wd",[]))), - <<"">> = iolist_to_binary(join(re:split("abCd","a(?=b(?i)c)\\w\\wd",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abcd","a(?=b(?i)c)\\w\\wd",[]))), + <<"">> = iolist_to_binary(join(re:split("abCd","a(?=b(?i)c)\\w\\wd",[trim]))), <<":">> = iolist_to_binary(join(re:split("abCd","a(?=b(?i)c)\\w\\wd",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abCd","a(?=b(?i)c)\\w\\wd",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?=b(?i)c)\\w\\wd",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abCd","a(?=b(?i)c)\\w\\wd",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?=b(?i)c)\\w\\wd",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?=b(?i)c)\\w\\wd",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?=b(?i)c)\\w\\wd",[]))), - <<"aBCd">> = iolist_to_binary(join(re:split("aBCd","a(?=b(?i)c)\\w\\wd",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?=b(?i)c)\\w\\wd",[]))), + <<"aBCd">> = iolist_to_binary(join(re:split("aBCd","a(?=b(?i)c)\\w\\wd",[trim]))), <<"aBCd">> = iolist_to_binary(join(re:split("aBCd","a(?=b(?i)c)\\w\\wd",[{parts, - 2}]))), - <<"aBCd">> = iolist_to_binary(join(re:split("aBCd","a(?=b(?i)c)\\w\\wd",[]))), - <<"abcD">> = iolist_to_binary(join(re:split("abcD","a(?=b(?i)c)\\w\\wd",[trim]))), + 2}]))), + <<"aBCd">> = iolist_to_binary(join(re:split("aBCd","a(?=b(?i)c)\\w\\wd",[]))), + <<"abcD">> = iolist_to_binary(join(re:split("abcD","a(?=b(?i)c)\\w\\wd",[trim]))), <<"abcD">> = iolist_to_binary(join(re:split("abcD","a(?=b(?i)c)\\w\\wd",[{parts, - 2}]))), - <<"abcD">> = iolist_to_binary(join(re:split("abcD","a(?=b(?i)c)\\w\\wd",[]))), + 2}]))), + <<"abcD">> = iolist_to_binary(join(re:split("abcD","a(?=b(?i)c)\\w\\wd",[]))), <<"">> = iolist_to_binary(join(re:split("more than million","(?s-i:more.*than).*million",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("more than million","(?s-i:more.*than).*million",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("more than million","(?s-i:more.*than).*million",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("more than million","(?s-i:more.*than).*million",[caseless]))), <<"">> = iolist_to_binary(join(re:split("more than MILLION","(?s-i:more.*than).*million",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("more than MILLION","(?s-i:more.*than).*million",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("more than MILLION","(?s-i:more.*than).*million",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("more than MILLION","(?s-i:more.*than).*million",[caseless]))), <<"">> = iolist_to_binary(join(re:split("more - than Million","(?s-i:more.*than).*million",[caseless,trim]))), + than Million","(?s-i:more.*than).*million",[caseless,trim]))), <<":">> = iolist_to_binary(join(re:split("more - than Million","(?s-i:more.*than).*million",[caseless,{parts,2}]))), + than Million","(?s-i:more.*than).*million",[caseless,{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("more - than Million","(?s-i:more.*than).*million",[caseless]))), + than Million","(?s-i:more.*than).*million",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s-i:more.*than).*million",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s-i:more.*than).*million",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s-i:more.*than).*million",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?s-i:more.*than).*million",[caseless]))), <<"MORE THAN MILLION">> = iolist_to_binary(join(re:split("MORE THAN MILLION","(?s-i:more.*than).*million",[caseless, - trim]))), + trim]))), <<"MORE THAN MILLION">> = iolist_to_binary(join(re:split("MORE THAN MILLION","(?s-i:more.*than).*million",[caseless, {parts, - 2}]))), - <<"MORE THAN MILLION">> = iolist_to_binary(join(re:split("MORE THAN MILLION","(?s-i:more.*than).*million",[caseless]))), + 2}]))), + <<"MORE THAN MILLION">> = iolist_to_binary(join(re:split("MORE THAN MILLION","(?s-i:more.*than).*million",[caseless]))), <<"more than million">> = iolist_to_binary(join(re:split("more than - million","(?s-i:more.*than).*million",[caseless,trim]))), + million","(?s-i:more.*than).*million",[caseless,trim]))), <<"more than million">> = iolist_to_binary(join(re:split("more than - million","(?s-i:more.*than).*million",[caseless,{parts,2}]))), + million","(?s-i:more.*than).*million",[caseless,{parts,2}]))), <<"more than million">> = iolist_to_binary(join(re:split("more than - million","(?s-i:more.*than).*million",[caseless]))), + million","(?s-i:more.*than).*million",[caseless]))), <<"">> = iolist_to_binary(join(re:split("more than million","(?:(?s-i)more.*than).*million",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("more than million","(?:(?s-i)more.*than).*million",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("more than million","(?:(?s-i)more.*than).*million",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("more than million","(?:(?s-i)more.*than).*million",[caseless]))), <<"">> = iolist_to_binary(join(re:split("more than MILLION","(?:(?s-i)more.*than).*million",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("more than MILLION","(?:(?s-i)more.*than).*million",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("more than MILLION","(?:(?s-i)more.*than).*million",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("more than MILLION","(?:(?s-i)more.*than).*million",[caseless]))), <<"">> = iolist_to_binary(join(re:split("more - than Million","(?:(?s-i)more.*than).*million",[caseless,trim]))), + than Million","(?:(?s-i)more.*than).*million",[caseless,trim]))), <<":">> = iolist_to_binary(join(re:split("more - than Million","(?:(?s-i)more.*than).*million",[caseless,{parts,2}]))), + than Million","(?:(?s-i)more.*than).*million",[caseless,{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("more - than Million","(?:(?s-i)more.*than).*million",[caseless]))), + than Million","(?:(?s-i)more.*than).*million",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?s-i)more.*than).*million",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?s-i)more.*than).*million",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?s-i)more.*than).*million",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?s-i)more.*than).*million",[caseless]))), <<"MORE THAN MILLION">> = iolist_to_binary(join(re:split("MORE THAN MILLION","(?:(?s-i)more.*than).*million",[caseless, - trim]))), + trim]))), <<"MORE THAN MILLION">> = iolist_to_binary(join(re:split("MORE THAN MILLION","(?:(?s-i)more.*than).*million",[caseless, {parts, - 2}]))), - <<"MORE THAN MILLION">> = iolist_to_binary(join(re:split("MORE THAN MILLION","(?:(?s-i)more.*than).*million",[caseless]))), + 2}]))), + <<"MORE THAN MILLION">> = iolist_to_binary(join(re:split("MORE THAN MILLION","(?:(?s-i)more.*than).*million",[caseless]))), <<"more than million">> = iolist_to_binary(join(re:split("more than - million","(?:(?s-i)more.*than).*million",[caseless,trim]))), + million","(?:(?s-i)more.*than).*million",[caseless,trim]))), <<"more than million">> = iolist_to_binary(join(re:split("more than - million","(?:(?s-i)more.*than).*million",[caseless,{parts,2}]))), + million","(?:(?s-i)more.*than).*million",[caseless,{parts,2}]))), <<"more than million">> = iolist_to_binary(join(re:split("more than - million","(?:(?s-i)more.*than).*million",[caseless]))), - <<"">> = iolist_to_binary(join(re:split("abc","(?>a(?i)b+)+c",[trim]))), + million","(?:(?s-i)more.*than).*million",[caseless]))), + <<"">> = iolist_to_binary(join(re:split("abc","(?>a(?i)b+)+c",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","(?>a(?i)b+)+c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","(?>a(?i)b+)+c",[]))), - <<"">> = iolist_to_binary(join(re:split("aBbc","(?>a(?i)b+)+c",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","(?>a(?i)b+)+c",[]))), + <<"">> = iolist_to_binary(join(re:split("aBbc","(?>a(?i)b+)+c",[trim]))), <<":">> = iolist_to_binary(join(re:split("aBbc","(?>a(?i)b+)+c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aBbc","(?>a(?i)b+)+c",[]))), - <<"">> = iolist_to_binary(join(re:split("aBBc","(?>a(?i)b+)+c",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aBbc","(?>a(?i)b+)+c",[]))), + <<"">> = iolist_to_binary(join(re:split("aBBc","(?>a(?i)b+)+c",[trim]))), <<":">> = iolist_to_binary(join(re:split("aBBc","(?>a(?i)b+)+c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aBBc","(?>a(?i)b+)+c",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>a(?i)b+)+c",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aBBc","(?>a(?i)b+)+c",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>a(?i)b+)+c",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>a(?i)b+)+c",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>a(?i)b+)+c",[]))), - <<"Abc">> = iolist_to_binary(join(re:split("Abc","(?>a(?i)b+)+c",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>a(?i)b+)+c",[]))), + <<"Abc">> = iolist_to_binary(join(re:split("Abc","(?>a(?i)b+)+c",[trim]))), <<"Abc">> = iolist_to_binary(join(re:split("Abc","(?>a(?i)b+)+c",[{parts, - 2}]))), - <<"Abc">> = iolist_to_binary(join(re:split("Abc","(?>a(?i)b+)+c",[]))), - <<"abAb">> = iolist_to_binary(join(re:split("abAb","(?>a(?i)b+)+c",[trim]))), + 2}]))), + <<"Abc">> = iolist_to_binary(join(re:split("Abc","(?>a(?i)b+)+c",[]))), + <<"abAb">> = iolist_to_binary(join(re:split("abAb","(?>a(?i)b+)+c",[trim]))), <<"abAb">> = iolist_to_binary(join(re:split("abAb","(?>a(?i)b+)+c",[{parts, - 2}]))), - <<"abAb">> = iolist_to_binary(join(re:split("abAb","(?>a(?i)b+)+c",[]))), - <<"abbC">> = iolist_to_binary(join(re:split("abbC","(?>a(?i)b+)+c",[trim]))), + 2}]))), + <<"abAb">> = iolist_to_binary(join(re:split("abAb","(?>a(?i)b+)+c",[]))), + <<"abbC">> = iolist_to_binary(join(re:split("abbC","(?>a(?i)b+)+c",[trim]))), <<"abbC">> = iolist_to_binary(join(re:split("abbC","(?>a(?i)b+)+c",[{parts, - 2}]))), - <<"abbC">> = iolist_to_binary(join(re:split("abbC","(?>a(?i)b+)+c",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","(?=a(?i)b)\\w\\wc",[trim]))), + 2}]))), + <<"abbC">> = iolist_to_binary(join(re:split("abbC","(?>a(?i)b+)+c",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","(?=a(?i)b)\\w\\wc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","(?=a(?i)b)\\w\\wc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","(?=a(?i)b)\\w\\wc",[]))), - <<"">> = iolist_to_binary(join(re:split("aBc","(?=a(?i)b)\\w\\wc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","(?=a(?i)b)\\w\\wc",[]))), + <<"">> = iolist_to_binary(join(re:split("aBc","(?=a(?i)b)\\w\\wc",[trim]))), <<":">> = iolist_to_binary(join(re:split("aBc","(?=a(?i)b)\\w\\wc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aBc","(?=a(?i)b)\\w\\wc",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?=a(?i)b)\\w\\wc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aBc","(?=a(?i)b)\\w\\wc",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?=a(?i)b)\\w\\wc",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?=a(?i)b)\\w\\wc",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?=a(?i)b)\\w\\wc",[]))), - <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?=a(?i)b)\\w\\wc",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?=a(?i)b)\\w\\wc",[]))), + <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?=a(?i)b)\\w\\wc",[trim]))), <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?=a(?i)b)\\w\\wc",[{parts, - 2}]))), - <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?=a(?i)b)\\w\\wc",[]))), - <<"abC">> = iolist_to_binary(join(re:split("abC","(?=a(?i)b)\\w\\wc",[trim]))), + 2}]))), + <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?=a(?i)b)\\w\\wc",[]))), + <<"abC">> = iolist_to_binary(join(re:split("abC","(?=a(?i)b)\\w\\wc",[trim]))), <<"abC">> = iolist_to_binary(join(re:split("abC","(?=a(?i)b)\\w\\wc",[{parts, - 2}]))), - <<"abC">> = iolist_to_binary(join(re:split("abC","(?=a(?i)b)\\w\\wc",[]))), - <<"aBC">> = iolist_to_binary(join(re:split("aBC","(?=a(?i)b)\\w\\wc",[trim]))), + 2}]))), + <<"abC">> = iolist_to_binary(join(re:split("abC","(?=a(?i)b)\\w\\wc",[]))), + <<"aBC">> = iolist_to_binary(join(re:split("aBC","(?=a(?i)b)\\w\\wc",[trim]))), <<"aBC">> = iolist_to_binary(join(re:split("aBC","(?=a(?i)b)\\w\\wc",[{parts, - 2}]))), - <<"aBC">> = iolist_to_binary(join(re:split("aBC","(?=a(?i)b)\\w\\wc",[]))), - <<"ab:xx">> = iolist_to_binary(join(re:split("abxxc","(?<=a(?i)b)(\\w\\w)c",[trim]))), + 2}]))), + <<"aBC">> = iolist_to_binary(join(re:split("aBC","(?=a(?i)b)\\w\\wc",[]))), + <<"ab:xx">> = iolist_to_binary(join(re:split("abxxc","(?<=a(?i)b)(\\w\\w)c",[trim]))), <<"ab:xx:">> = iolist_to_binary(join(re:split("abxxc","(?<=a(?i)b)(\\w\\w)c",[{parts, - 2}]))), - <<"ab:xx:">> = iolist_to_binary(join(re:split("abxxc","(?<=a(?i)b)(\\w\\w)c",[]))), - <<"aB:xx">> = iolist_to_binary(join(re:split("aBxxc","(?<=a(?i)b)(\\w\\w)c",[trim]))), + 2}]))), + <<"ab:xx:">> = iolist_to_binary(join(re:split("abxxc","(?<=a(?i)b)(\\w\\w)c",[]))), + <<"aB:xx">> = iolist_to_binary(join(re:split("aBxxc","(?<=a(?i)b)(\\w\\w)c",[trim]))), <<"aB:xx:">> = iolist_to_binary(join(re:split("aBxxc","(?<=a(?i)b)(\\w\\w)c",[{parts, - 2}]))), - <<"aB:xx:">> = iolist_to_binary(join(re:split("aBxxc","(?<=a(?i)b)(\\w\\w)c",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=a(?i)b)(\\w\\w)c",[trim]))), + 2}]))), + <<"aB:xx:">> = iolist_to_binary(join(re:split("aBxxc","(?<=a(?i)b)(\\w\\w)c",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=a(?i)b)(\\w\\w)c",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=a(?i)b)(\\w\\w)c",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=a(?i)b)(\\w\\w)c",[]))), - <<"Abxxc">> = iolist_to_binary(join(re:split("Abxxc","(?<=a(?i)b)(\\w\\w)c",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=a(?i)b)(\\w\\w)c",[]))), + <<"Abxxc">> = iolist_to_binary(join(re:split("Abxxc","(?<=a(?i)b)(\\w\\w)c",[trim]))), <<"Abxxc">> = iolist_to_binary(join(re:split("Abxxc","(?<=a(?i)b)(\\w\\w)c",[{parts, - 2}]))), - <<"Abxxc">> = iolist_to_binary(join(re:split("Abxxc","(?<=a(?i)b)(\\w\\w)c",[]))), - <<"ABxxc">> = iolist_to_binary(join(re:split("ABxxc","(?<=a(?i)b)(\\w\\w)c",[trim]))), + 2}]))), + <<"Abxxc">> = iolist_to_binary(join(re:split("Abxxc","(?<=a(?i)b)(\\w\\w)c",[]))), + <<"ABxxc">> = iolist_to_binary(join(re:split("ABxxc","(?<=a(?i)b)(\\w\\w)c",[trim]))), <<"ABxxc">> = iolist_to_binary(join(re:split("ABxxc","(?<=a(?i)b)(\\w\\w)c",[{parts, - 2}]))), - <<"ABxxc">> = iolist_to_binary(join(re:split("ABxxc","(?<=a(?i)b)(\\w\\w)c",[]))), - <<"abxxC">> = iolist_to_binary(join(re:split("abxxC","(?<=a(?i)b)(\\w\\w)c",[trim]))), + 2}]))), + <<"ABxxc">> = iolist_to_binary(join(re:split("ABxxc","(?<=a(?i)b)(\\w\\w)c",[]))), + <<"abxxC">> = iolist_to_binary(join(re:split("abxxC","(?<=a(?i)b)(\\w\\w)c",[trim]))), <<"abxxC">> = iolist_to_binary(join(re:split("abxxC","(?<=a(?i)b)(\\w\\w)c",[{parts, - 2}]))), - <<"abxxC">> = iolist_to_binary(join(re:split("abxxC","(?<=a(?i)b)(\\w\\w)c",[]))), - <<":a">> = iolist_to_binary(join(re:split("aA","(?:(a)|b)(?(1)A|B)",[trim]))), + 2}]))), + <<"abxxC">> = iolist_to_binary(join(re:split("abxxC","(?<=a(?i)b)(\\w\\w)c",[]))), + <<":a">> = iolist_to_binary(join(re:split("aA","(?:(a)|b)(?(1)A|B)",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aA","(?:(a)|b)(?(1)A|B)",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aA","(?:(a)|b)(?(1)A|B)",[]))), - <<"">> = iolist_to_binary(join(re:split("bB","(?:(a)|b)(?(1)A|B)",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aA","(?:(a)|b)(?(1)A|B)",[]))), + <<"">> = iolist_to_binary(join(re:split("bB","(?:(a)|b)(?(1)A|B)",[trim]))), <<"::">> = iolist_to_binary(join(re:split("bB","(?:(a)|b)(?(1)A|B)",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("bB","(?:(a)|b)(?(1)A|B)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(a)|b)(?(1)A|B)",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("bB","(?:(a)|b)(?(1)A|B)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(a)|b)(?(1)A|B)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(a)|b)(?(1)A|B)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(a)|b)(?(1)A|B)",[]))), - <<"aB">> = iolist_to_binary(join(re:split("aB","(?:(a)|b)(?(1)A|B)",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(a)|b)(?(1)A|B)",[]))), + <<"aB">> = iolist_to_binary(join(re:split("aB","(?:(a)|b)(?(1)A|B)",[trim]))), <<"aB">> = iolist_to_binary(join(re:split("aB","(?:(a)|b)(?(1)A|B)",[{parts, - 2}]))), - <<"aB">> = iolist_to_binary(join(re:split("aB","(?:(a)|b)(?(1)A|B)",[]))), - <<"bA">> = iolist_to_binary(join(re:split("bA","(?:(a)|b)(?(1)A|B)",[trim]))), + 2}]))), + <<"aB">> = iolist_to_binary(join(re:split("aB","(?:(a)|b)(?(1)A|B)",[]))), + <<"bA">> = iolist_to_binary(join(re:split("bA","(?:(a)|b)(?(1)A|B)",[trim]))), <<"bA">> = iolist_to_binary(join(re:split("bA","(?:(a)|b)(?(1)A|B)",[{parts, - 2}]))), - <<"bA">> = iolist_to_binary(join(re:split("bA","(?:(a)|b)(?(1)A|B)",[]))), - <<":a">> = iolist_to_binary(join(re:split("aa","^(a)?(?(1)a|b)+$",[trim]))), + 2}]))), + <<"bA">> = iolist_to_binary(join(re:split("bA","(?:(a)|b)(?(1)A|B)",[]))), + <<":a">> = iolist_to_binary(join(re:split("aa","^(a)?(?(1)a|b)+$",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aa","^(a)?(?(1)a|b)+$",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aa","^(a)?(?(1)a|b)+$",[]))), - <<"">> = iolist_to_binary(join(re:split("b","^(a)?(?(1)a|b)+$",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aa","^(a)?(?(1)a|b)+$",[]))), + <<"">> = iolist_to_binary(join(re:split("b","^(a)?(?(1)a|b)+$",[trim]))), <<"::">> = iolist_to_binary(join(re:split("b","^(a)?(?(1)a|b)+$",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("b","^(a)?(?(1)a|b)+$",[]))), - <<"">> = iolist_to_binary(join(re:split("bb","^(a)?(?(1)a|b)+$",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("b","^(a)?(?(1)a|b)+$",[]))), + <<"">> = iolist_to_binary(join(re:split("bb","^(a)?(?(1)a|b)+$",[trim]))), <<"::">> = iolist_to_binary(join(re:split("bb","^(a)?(?(1)a|b)+$",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("bb","^(a)?(?(1)a|b)+$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a)?(?(1)a|b)+$",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("bb","^(a)?(?(1)a|b)+$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a)?(?(1)a|b)+$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a)?(?(1)a|b)+$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a)?(?(1)a|b)+$",[]))), - <<"ab">> = iolist_to_binary(join(re:split("ab","^(a)?(?(1)a|b)+$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a)?(?(1)a|b)+$",[]))), + <<"ab">> = iolist_to_binary(join(re:split("ab","^(a)?(?(1)a|b)+$",[trim]))), <<"ab">> = iolist_to_binary(join(re:split("ab","^(a)?(?(1)a|b)+$",[{parts, - 2}]))), - <<"ab">> = iolist_to_binary(join(re:split("ab","^(a)?(?(1)a|b)+$",[]))), + 2}]))), + <<"ab">> = iolist_to_binary(join(re:split("ab","^(a)?(?(1)a|b)+$",[]))), ok. run13() -> - <<"">> = iolist_to_binary(join(re:split("abc:","^(?(?=abc)\\w{3}:|\\d\\d)$",[trim]))), + <<"">> = iolist_to_binary(join(re:split("abc:","^(?(?=abc)\\w{3}:|\\d\\d)$",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc:","^(?(?=abc)\\w{3}:|\\d\\d)$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc:","^(?(?=abc)\\w{3}:|\\d\\d)$",[]))), - <<"">> = iolist_to_binary(join(re:split("12","^(?(?=abc)\\w{3}:|\\d\\d)$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc:","^(?(?=abc)\\w{3}:|\\d\\d)$",[]))), + <<"">> = iolist_to_binary(join(re:split("12","^(?(?=abc)\\w{3}:|\\d\\d)$",[trim]))), <<":">> = iolist_to_binary(join(re:split("12","^(?(?=abc)\\w{3}:|\\d\\d)$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("12","^(?(?=abc)\\w{3}:|\\d\\d)$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(?=abc)\\w{3}:|\\d\\d)$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("12","^(?(?=abc)\\w{3}:|\\d\\d)$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(?=abc)\\w{3}:|\\d\\d)$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(?=abc)\\w{3}:|\\d\\d)$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(?=abc)\\w{3}:|\\d\\d)$",[]))), - <<"123">> = iolist_to_binary(join(re:split("123","^(?(?=abc)\\w{3}:|\\d\\d)$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(?=abc)\\w{3}:|\\d\\d)$",[]))), + <<"123">> = iolist_to_binary(join(re:split("123","^(?(?=abc)\\w{3}:|\\d\\d)$",[trim]))), <<"123">> = iolist_to_binary(join(re:split("123","^(?(?=abc)\\w{3}:|\\d\\d)$",[{parts, - 2}]))), - <<"123">> = iolist_to_binary(join(re:split("123","^(?(?=abc)\\w{3}:|\\d\\d)$",[]))), - <<"xyz">> = iolist_to_binary(join(re:split("xyz","^(?(?=abc)\\w{3}:|\\d\\d)$",[trim]))), + 2}]))), + <<"123">> = iolist_to_binary(join(re:split("123","^(?(?=abc)\\w{3}:|\\d\\d)$",[]))), + <<"xyz">> = iolist_to_binary(join(re:split("xyz","^(?(?=abc)\\w{3}:|\\d\\d)$",[trim]))), <<"xyz">> = iolist_to_binary(join(re:split("xyz","^(?(?=abc)\\w{3}:|\\d\\d)$",[{parts, - 2}]))), - <<"xyz">> = iolist_to_binary(join(re:split("xyz","^(?(?=abc)\\w{3}:|\\d\\d)$",[]))), - <<"">> = iolist_to_binary(join(re:split("abc:","^(?(?!abc)\\d\\d|\\w{3}:)$",[trim]))), + 2}]))), + <<"xyz">> = iolist_to_binary(join(re:split("xyz","^(?(?=abc)\\w{3}:|\\d\\d)$",[]))), + <<"">> = iolist_to_binary(join(re:split("abc:","^(?(?!abc)\\d\\d|\\w{3}:)$",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc:","^(?(?!abc)\\d\\d|\\w{3}:)$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc:","^(?(?!abc)\\d\\d|\\w{3}:)$",[]))), - <<"">> = iolist_to_binary(join(re:split("12","^(?(?!abc)\\d\\d|\\w{3}:)$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc:","^(?(?!abc)\\d\\d|\\w{3}:)$",[]))), + <<"">> = iolist_to_binary(join(re:split("12","^(?(?!abc)\\d\\d|\\w{3}:)$",[trim]))), <<":">> = iolist_to_binary(join(re:split("12","^(?(?!abc)\\d\\d|\\w{3}:)$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("12","^(?(?!abc)\\d\\d|\\w{3}:)$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(?!abc)\\d\\d|\\w{3}:)$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("12","^(?(?!abc)\\d\\d|\\w{3}:)$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(?!abc)\\d\\d|\\w{3}:)$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(?!abc)\\d\\d|\\w{3}:)$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(?!abc)\\d\\d|\\w{3}:)$",[]))), - <<"123">> = iolist_to_binary(join(re:split("123","^(?(?!abc)\\d\\d|\\w{3}:)$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(?!abc)\\d\\d|\\w{3}:)$",[]))), + <<"123">> = iolist_to_binary(join(re:split("123","^(?(?!abc)\\d\\d|\\w{3}:)$",[trim]))), <<"123">> = iolist_to_binary(join(re:split("123","^(?(?!abc)\\d\\d|\\w{3}:)$",[{parts, - 2}]))), - <<"123">> = iolist_to_binary(join(re:split("123","^(?(?!abc)\\d\\d|\\w{3}:)$",[]))), - <<"xyz">> = iolist_to_binary(join(re:split("xyz","^(?(?!abc)\\d\\d|\\w{3}:)$",[trim]))), + 2}]))), + <<"123">> = iolist_to_binary(join(re:split("123","^(?(?!abc)\\d\\d|\\w{3}:)$",[]))), + <<"xyz">> = iolist_to_binary(join(re:split("xyz","^(?(?!abc)\\d\\d|\\w{3}:)$",[trim]))), <<"xyz">> = iolist_to_binary(join(re:split("xyz","^(?(?!abc)\\d\\d|\\w{3}:)$",[{parts, - 2}]))), - <<"xyz">> = iolist_to_binary(join(re:split("xyz","^(?(?!abc)\\d\\d|\\w{3}:)$",[]))), - <<"foo">> = iolist_to_binary(join(re:split("foobar","(?(?<=foo)bar|cat)",[trim]))), + 2}]))), + <<"xyz">> = iolist_to_binary(join(re:split("xyz","^(?(?!abc)\\d\\d|\\w{3}:)$",[]))), + <<"foo">> = iolist_to_binary(join(re:split("foobar","(?(?<=foo)bar|cat)",[trim]))), <<"foo:">> = iolist_to_binary(join(re:split("foobar","(?(?<=foo)bar|cat)",[{parts, - 2}]))), - <<"foo:">> = iolist_to_binary(join(re:split("foobar","(?(?<=foo)bar|cat)",[]))), - <<"">> = iolist_to_binary(join(re:split("cat","(?(?<=foo)bar|cat)",[trim]))), + 2}]))), + <<"foo:">> = iolist_to_binary(join(re:split("foobar","(?(?<=foo)bar|cat)",[]))), + <<"">> = iolist_to_binary(join(re:split("cat","(?(?<=foo)bar|cat)",[trim]))), <<":">> = iolist_to_binary(join(re:split("cat","(?(?<=foo)bar|cat)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("cat","(?(?<=foo)bar|cat)",[]))), - <<"f">> = iolist_to_binary(join(re:split("fcat","(?(?<=foo)bar|cat)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("cat","(?(?<=foo)bar|cat)",[]))), + <<"f">> = iolist_to_binary(join(re:split("fcat","(?(?<=foo)bar|cat)",[trim]))), <<"f:">> = iolist_to_binary(join(re:split("fcat","(?(?<=foo)bar|cat)",[{parts, - 2}]))), - <<"f:">> = iolist_to_binary(join(re:split("fcat","(?(?<=foo)bar|cat)",[]))), - <<"fo">> = iolist_to_binary(join(re:split("focat","(?(?<=foo)bar|cat)",[trim]))), + 2}]))), + <<"f:">> = iolist_to_binary(join(re:split("fcat","(?(?<=foo)bar|cat)",[]))), + <<"fo">> = iolist_to_binary(join(re:split("focat","(?(?<=foo)bar|cat)",[trim]))), <<"fo:">> = iolist_to_binary(join(re:split("focat","(?(?<=foo)bar|cat)",[{parts, - 2}]))), - <<"fo:">> = iolist_to_binary(join(re:split("focat","(?(?<=foo)bar|cat)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?<=foo)bar|cat)",[trim]))), + 2}]))), + <<"fo:">> = iolist_to_binary(join(re:split("focat","(?(?<=foo)bar|cat)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?<=foo)bar|cat)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?<=foo)bar|cat)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?<=foo)bar|cat)",[]))), - <<"foocat">> = iolist_to_binary(join(re:split("foocat","(?(?<=foo)bar|cat)",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?<=foo)bar|cat)",[]))), + <<"foocat">> = iolist_to_binary(join(re:split("foocat","(?(?<=foo)bar|cat)",[trim]))), <<"foocat">> = iolist_to_binary(join(re:split("foocat","(?(?<=foo)bar|cat)",[{parts, - 2}]))), - <<"foocat">> = iolist_to_binary(join(re:split("foocat","(?(?<=foo)bar|cat)",[]))), - <<"foo">> = iolist_to_binary(join(re:split("foobar","(?(?<!foo)cat|bar)",[trim]))), + 2}]))), + <<"foocat">> = iolist_to_binary(join(re:split("foocat","(?(?<=foo)bar|cat)",[]))), + <<"foo">> = iolist_to_binary(join(re:split("foobar","(?(?<!foo)cat|bar)",[trim]))), <<"foo:">> = iolist_to_binary(join(re:split("foobar","(?(?<!foo)cat|bar)",[{parts, - 2}]))), - <<"foo:">> = iolist_to_binary(join(re:split("foobar","(?(?<!foo)cat|bar)",[]))), - <<"">> = iolist_to_binary(join(re:split("cat","(?(?<!foo)cat|bar)",[trim]))), + 2}]))), + <<"foo:">> = iolist_to_binary(join(re:split("foobar","(?(?<!foo)cat|bar)",[]))), + <<"">> = iolist_to_binary(join(re:split("cat","(?(?<!foo)cat|bar)",[trim]))), <<":">> = iolist_to_binary(join(re:split("cat","(?(?<!foo)cat|bar)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("cat","(?(?<!foo)cat|bar)",[]))), - <<"f">> = iolist_to_binary(join(re:split("fcat","(?(?<!foo)cat|bar)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("cat","(?(?<!foo)cat|bar)",[]))), + <<"f">> = iolist_to_binary(join(re:split("fcat","(?(?<!foo)cat|bar)",[trim]))), <<"f:">> = iolist_to_binary(join(re:split("fcat","(?(?<!foo)cat|bar)",[{parts, - 2}]))), - <<"f:">> = iolist_to_binary(join(re:split("fcat","(?(?<!foo)cat|bar)",[]))), - <<"fo">> = iolist_to_binary(join(re:split("focat","(?(?<!foo)cat|bar)",[trim]))), + 2}]))), + <<"f:">> = iolist_to_binary(join(re:split("fcat","(?(?<!foo)cat|bar)",[]))), + <<"fo">> = iolist_to_binary(join(re:split("focat","(?(?<!foo)cat|bar)",[trim]))), <<"fo:">> = iolist_to_binary(join(re:split("focat","(?(?<!foo)cat|bar)",[{parts, - 2}]))), - <<"fo:">> = iolist_to_binary(join(re:split("focat","(?(?<!foo)cat|bar)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?<!foo)cat|bar)",[trim]))), + 2}]))), + <<"fo:">> = iolist_to_binary(join(re:split("focat","(?(?<!foo)cat|bar)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?<!foo)cat|bar)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?<!foo)cat|bar)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?<!foo)cat|bar)",[]))), - <<"foocat">> = iolist_to_binary(join(re:split("foocat","(?(?<!foo)cat|bar)",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?<!foo)cat|bar)",[]))), + <<"foocat">> = iolist_to_binary(join(re:split("foocat","(?(?<!foo)cat|bar)",[trim]))), <<"foocat">> = iolist_to_binary(join(re:split("foocat","(?(?<!foo)cat|bar)",[{parts, - 2}]))), - <<"foocat">> = iolist_to_binary(join(re:split("foocat","(?(?<!foo)cat|bar)",[]))), + 2}]))), + <<"foocat">> = iolist_to_binary(join(re:split("foocat","(?(?<!foo)cat|bar)",[]))), <<"">> = iolist_to_binary(join(re:split("abcd","( \\( )? [^()]+ (?(1) \\) |) ",[extended, - trim]))), + trim]))), <<"::">> = iolist_to_binary(join(re:split("abcd","( \\( )? [^()]+ (?(1) \\) |) ",[extended, {parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("abcd","( \\( )? [^()]+ (?(1) \\) |) ",[extended]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("abcd","( \\( )? [^()]+ (?(1) \\) |) ",[extended]))), <<":(">> = iolist_to_binary(join(re:split("(abcd)","( \\( )? [^()]+ (?(1) \\) |) ",[extended, - trim]))), + trim]))), <<":(:">> = iolist_to_binary(join(re:split("(abcd)","( \\( )? [^()]+ (?(1) \\) |) ",[extended, {parts, - 2}]))), - <<":(:">> = iolist_to_binary(join(re:split("(abcd)","( \\( )? [^()]+ (?(1) \\) |) ",[extended]))), + 2}]))), + <<":(:">> = iolist_to_binary(join(re:split("(abcd)","( \\( )? [^()]+ (?(1) \\) |) ",[extended]))), <<":::(">> = iolist_to_binary(join(re:split("the quick (abcd) fox","( \\( )? [^()]+ (?(1) \\) |) ",[extended, - trim]))), + trim]))), <<"::(abcd) fox">> = iolist_to_binary(join(re:split("the quick (abcd) fox","( \\( )? [^()]+ (?(1) \\) |) ",[extended, {parts, - 2}]))), - <<":::(:::">> = iolist_to_binary(join(re:split("the quick (abcd) fox","( \\( )? [^()]+ (?(1) \\) |) ",[extended]))), + 2}]))), + <<":::(:::">> = iolist_to_binary(join(re:split("the quick (abcd) fox","( \\( )? [^()]+ (?(1) \\) |) ",[extended]))), <<"(">> = iolist_to_binary(join(re:split("(abcd","( \\( )? [^()]+ (?(1) \\) |) ",[extended, - trim]))), + trim]))), <<"(::">> = iolist_to_binary(join(re:split("(abcd","( \\( )? [^()]+ (?(1) \\) |) ",[extended, {parts, - 2}]))), - <<"(::">> = iolist_to_binary(join(re:split("(abcd","( \\( )? [^()]+ (?(1) \\) |) ",[extended]))), + 2}]))), + <<"(::">> = iolist_to_binary(join(re:split("(abcd","( \\( )? [^()]+ (?(1) \\) |) ",[extended]))), <<"">> = iolist_to_binary(join(re:split("abcd","( \\( )? [^()]+ (?(1) \\) ) ",[extended, - trim]))), + trim]))), <<"::">> = iolist_to_binary(join(re:split("abcd","( \\( )? [^()]+ (?(1) \\) ) ",[extended, {parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("abcd","( \\( )? [^()]+ (?(1) \\) ) ",[extended]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("abcd","( \\( )? [^()]+ (?(1) \\) ) ",[extended]))), <<":(">> = iolist_to_binary(join(re:split("(abcd)","( \\( )? [^()]+ (?(1) \\) ) ",[extended, - trim]))), + trim]))), <<":(:">> = iolist_to_binary(join(re:split("(abcd)","( \\( )? [^()]+ (?(1) \\) ) ",[extended, {parts, - 2}]))), - <<":(:">> = iolist_to_binary(join(re:split("(abcd)","( \\( )? [^()]+ (?(1) \\) ) ",[extended]))), + 2}]))), + <<":(:">> = iolist_to_binary(join(re:split("(abcd)","( \\( )? [^()]+ (?(1) \\) ) ",[extended]))), <<":::(">> = iolist_to_binary(join(re:split("the quick (abcd) fox","( \\( )? [^()]+ (?(1) \\) ) ",[extended, - trim]))), + trim]))), <<"::(abcd) fox">> = iolist_to_binary(join(re:split("the quick (abcd) fox","( \\( )? [^()]+ (?(1) \\) ) ",[extended, {parts, - 2}]))), - <<":::(:::">> = iolist_to_binary(join(re:split("the quick (abcd) fox","( \\( )? [^()]+ (?(1) \\) ) ",[extended]))), + 2}]))), + <<":::(:::">> = iolist_to_binary(join(re:split("the quick (abcd) fox","( \\( )? [^()]+ (?(1) \\) ) ",[extended]))), <<"(">> = iolist_to_binary(join(re:split("(abcd","( \\( )? [^()]+ (?(1) \\) ) ",[extended, - trim]))), + trim]))), <<"(::">> = iolist_to_binary(join(re:split("(abcd","( \\( )? [^()]+ (?(1) \\) ) ",[extended, {parts, - 2}]))), - <<"(::">> = iolist_to_binary(join(re:split("(abcd","( \\( )? [^()]+ (?(1) \\) ) ",[extended]))), - <<":1:2">> = iolist_to_binary(join(re:split("12","^(?(2)a|(1)(2))+$",[trim]))), + 2}]))), + <<"(::">> = iolist_to_binary(join(re:split("(abcd","( \\( )? [^()]+ (?(1) \\) ) ",[extended]))), + <<":1:2">> = iolist_to_binary(join(re:split("12","^(?(2)a|(1)(2))+$",[trim]))), <<":1:2:">> = iolist_to_binary(join(re:split("12","^(?(2)a|(1)(2))+$",[{parts, - 2}]))), - <<":1:2:">> = iolist_to_binary(join(re:split("12","^(?(2)a|(1)(2))+$",[]))), - <<":1:2">> = iolist_to_binary(join(re:split("12a","^(?(2)a|(1)(2))+$",[trim]))), + 2}]))), + <<":1:2:">> = iolist_to_binary(join(re:split("12","^(?(2)a|(1)(2))+$",[]))), + <<":1:2">> = iolist_to_binary(join(re:split("12a","^(?(2)a|(1)(2))+$",[trim]))), <<":1:2:">> = iolist_to_binary(join(re:split("12a","^(?(2)a|(1)(2))+$",[{parts, - 2}]))), - <<":1:2:">> = iolist_to_binary(join(re:split("12a","^(?(2)a|(1)(2))+$",[]))), - <<":1:2">> = iolist_to_binary(join(re:split("12aa","^(?(2)a|(1)(2))+$",[trim]))), + 2}]))), + <<":1:2:">> = iolist_to_binary(join(re:split("12a","^(?(2)a|(1)(2))+$",[]))), + <<":1:2">> = iolist_to_binary(join(re:split("12aa","^(?(2)a|(1)(2))+$",[trim]))), <<":1:2:">> = iolist_to_binary(join(re:split("12aa","^(?(2)a|(1)(2))+$",[{parts, - 2}]))), - <<":1:2:">> = iolist_to_binary(join(re:split("12aa","^(?(2)a|(1)(2))+$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(2)a|(1)(2))+$",[trim]))), + 2}]))), + <<":1:2:">> = iolist_to_binary(join(re:split("12aa","^(?(2)a|(1)(2))+$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(2)a|(1)(2))+$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(2)a|(1)(2))+$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(2)a|(1)(2))+$",[]))), - <<"1234">> = iolist_to_binary(join(re:split("1234","^(?(2)a|(1)(2))+$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?(2)a|(1)(2))+$",[]))), + <<"1234">> = iolist_to_binary(join(re:split("1234","^(?(2)a|(1)(2))+$",[trim]))), <<"1234">> = iolist_to_binary(join(re:split("1234","^(?(2)a|(1)(2))+$",[{parts, - 2}]))), - <<"1234">> = iolist_to_binary(join(re:split("1234","^(?(2)a|(1)(2))+$",[]))), - <<":blah">> = iolist_to_binary(join(re:split("blah blah","((?i)blah)\\s+\\1",[trim]))), + 2}]))), + <<"1234">> = iolist_to_binary(join(re:split("1234","^(?(2)a|(1)(2))+$",[]))), + <<":blah">> = iolist_to_binary(join(re:split("blah blah","((?i)blah)\\s+\\1",[trim]))), <<":blah:">> = iolist_to_binary(join(re:split("blah blah","((?i)blah)\\s+\\1",[{parts, - 2}]))), - <<":blah:">> = iolist_to_binary(join(re:split("blah blah","((?i)blah)\\s+\\1",[]))), - <<":BLAH">> = iolist_to_binary(join(re:split("BLAH BLAH","((?i)blah)\\s+\\1",[trim]))), + 2}]))), + <<":blah:">> = iolist_to_binary(join(re:split("blah blah","((?i)blah)\\s+\\1",[]))), + <<":BLAH">> = iolist_to_binary(join(re:split("BLAH BLAH","((?i)blah)\\s+\\1",[trim]))), <<":BLAH:">> = iolist_to_binary(join(re:split("BLAH BLAH","((?i)blah)\\s+\\1",[{parts, - 2}]))), - <<":BLAH:">> = iolist_to_binary(join(re:split("BLAH BLAH","((?i)blah)\\s+\\1",[]))), - <<":Blah">> = iolist_to_binary(join(re:split("Blah Blah","((?i)blah)\\s+\\1",[trim]))), + 2}]))), + <<":BLAH:">> = iolist_to_binary(join(re:split("BLAH BLAH","((?i)blah)\\s+\\1",[]))), + <<":Blah">> = iolist_to_binary(join(re:split("Blah Blah","((?i)blah)\\s+\\1",[trim]))), <<":Blah:">> = iolist_to_binary(join(re:split("Blah Blah","((?i)blah)\\s+\\1",[{parts, - 2}]))), - <<":Blah:">> = iolist_to_binary(join(re:split("Blah Blah","((?i)blah)\\s+\\1",[]))), - <<":blaH">> = iolist_to_binary(join(re:split("blaH blaH","((?i)blah)\\s+\\1",[trim]))), + 2}]))), + <<":Blah:">> = iolist_to_binary(join(re:split("Blah Blah","((?i)blah)\\s+\\1",[]))), + <<":blaH">> = iolist_to_binary(join(re:split("blaH blaH","((?i)blah)\\s+\\1",[trim]))), <<":blaH:">> = iolist_to_binary(join(re:split("blaH blaH","((?i)blah)\\s+\\1",[{parts, - 2}]))), - <<":blaH:">> = iolist_to_binary(join(re:split("blaH blaH","((?i)blah)\\s+\\1",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?i)blah)\\s+\\1",[trim]))), + 2}]))), + <<":blaH:">> = iolist_to_binary(join(re:split("blaH blaH","((?i)blah)\\s+\\1",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?i)blah)\\s+\\1",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?i)blah)\\s+\\1",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?i)blah)\\s+\\1",[]))), - <<"blah BLAH">> = iolist_to_binary(join(re:split("blah BLAH","((?i)blah)\\s+\\1",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?i)blah)\\s+\\1",[]))), + <<"blah BLAH">> = iolist_to_binary(join(re:split("blah BLAH","((?i)blah)\\s+\\1",[trim]))), <<"blah BLAH">> = iolist_to_binary(join(re:split("blah BLAH","((?i)blah)\\s+\\1",[{parts, - 2}]))), - <<"blah BLAH">> = iolist_to_binary(join(re:split("blah BLAH","((?i)blah)\\s+\\1",[]))), - <<"Blah blah">> = iolist_to_binary(join(re:split("Blah blah","((?i)blah)\\s+\\1",[trim]))), + 2}]))), + <<"blah BLAH">> = iolist_to_binary(join(re:split("blah BLAH","((?i)blah)\\s+\\1",[]))), + <<"Blah blah">> = iolist_to_binary(join(re:split("Blah blah","((?i)blah)\\s+\\1",[trim]))), <<"Blah blah">> = iolist_to_binary(join(re:split("Blah blah","((?i)blah)\\s+\\1",[{parts, - 2}]))), - <<"Blah blah">> = iolist_to_binary(join(re:split("Blah blah","((?i)blah)\\s+\\1",[]))), - <<"blaH blah">> = iolist_to_binary(join(re:split("blaH blah","((?i)blah)\\s+\\1",[trim]))), + 2}]))), + <<"Blah blah">> = iolist_to_binary(join(re:split("Blah blah","((?i)blah)\\s+\\1",[]))), + <<"blaH blah">> = iolist_to_binary(join(re:split("blaH blah","((?i)blah)\\s+\\1",[trim]))), <<"blaH blah">> = iolist_to_binary(join(re:split("blaH blah","((?i)blah)\\s+\\1",[{parts, - 2}]))), - <<"blaH blah">> = iolist_to_binary(join(re:split("blaH blah","((?i)blah)\\s+\\1",[]))), - <<":blah">> = iolist_to_binary(join(re:split("blah blah","((?i)blah)\\s+(?i:\\1)",[trim]))), + 2}]))), + <<"blaH blah">> = iolist_to_binary(join(re:split("blaH blah","((?i)blah)\\s+\\1",[]))), + <<":blah">> = iolist_to_binary(join(re:split("blah blah","((?i)blah)\\s+(?i:\\1)",[trim]))), <<":blah:">> = iolist_to_binary(join(re:split("blah blah","((?i)blah)\\s+(?i:\\1)",[{parts, - 2}]))), - <<":blah:">> = iolist_to_binary(join(re:split("blah blah","((?i)blah)\\s+(?i:\\1)",[]))), - <<":BLAH">> = iolist_to_binary(join(re:split("BLAH BLAH","((?i)blah)\\s+(?i:\\1)",[trim]))), + 2}]))), + <<":blah:">> = iolist_to_binary(join(re:split("blah blah","((?i)blah)\\s+(?i:\\1)",[]))), + <<":BLAH">> = iolist_to_binary(join(re:split("BLAH BLAH","((?i)blah)\\s+(?i:\\1)",[trim]))), <<":BLAH:">> = iolist_to_binary(join(re:split("BLAH BLAH","((?i)blah)\\s+(?i:\\1)",[{parts, - 2}]))), - <<":BLAH:">> = iolist_to_binary(join(re:split("BLAH BLAH","((?i)blah)\\s+(?i:\\1)",[]))), - <<":Blah">> = iolist_to_binary(join(re:split("Blah Blah","((?i)blah)\\s+(?i:\\1)",[trim]))), + 2}]))), + <<":BLAH:">> = iolist_to_binary(join(re:split("BLAH BLAH","((?i)blah)\\s+(?i:\\1)",[]))), + <<":Blah">> = iolist_to_binary(join(re:split("Blah Blah","((?i)blah)\\s+(?i:\\1)",[trim]))), <<":Blah:">> = iolist_to_binary(join(re:split("Blah Blah","((?i)blah)\\s+(?i:\\1)",[{parts, - 2}]))), - <<":Blah:">> = iolist_to_binary(join(re:split("Blah Blah","((?i)blah)\\s+(?i:\\1)",[]))), - <<":blaH">> = iolist_to_binary(join(re:split("blaH blaH","((?i)blah)\\s+(?i:\\1)",[trim]))), + 2}]))), + <<":Blah:">> = iolist_to_binary(join(re:split("Blah Blah","((?i)blah)\\s+(?i:\\1)",[]))), + <<":blaH">> = iolist_to_binary(join(re:split("blaH blaH","((?i)blah)\\s+(?i:\\1)",[trim]))), <<":blaH:">> = iolist_to_binary(join(re:split("blaH blaH","((?i)blah)\\s+(?i:\\1)",[{parts, - 2}]))), - <<":blaH:">> = iolist_to_binary(join(re:split("blaH blaH","((?i)blah)\\s+(?i:\\1)",[]))), - <<":blah">> = iolist_to_binary(join(re:split("blah BLAH","((?i)blah)\\s+(?i:\\1)",[trim]))), + 2}]))), + <<":blaH:">> = iolist_to_binary(join(re:split("blaH blaH","((?i)blah)\\s+(?i:\\1)",[]))), + <<":blah">> = iolist_to_binary(join(re:split("blah BLAH","((?i)blah)\\s+(?i:\\1)",[trim]))), <<":blah:">> = iolist_to_binary(join(re:split("blah BLAH","((?i)blah)\\s+(?i:\\1)",[{parts, - 2}]))), - <<":blah:">> = iolist_to_binary(join(re:split("blah BLAH","((?i)blah)\\s+(?i:\\1)",[]))), - <<":Blah">> = iolist_to_binary(join(re:split("Blah blah","((?i)blah)\\s+(?i:\\1)",[trim]))), + 2}]))), + <<":blah:">> = iolist_to_binary(join(re:split("blah BLAH","((?i)blah)\\s+(?i:\\1)",[]))), + <<":Blah">> = iolist_to_binary(join(re:split("Blah blah","((?i)blah)\\s+(?i:\\1)",[trim]))), <<":Blah:">> = iolist_to_binary(join(re:split("Blah blah","((?i)blah)\\s+(?i:\\1)",[{parts, - 2}]))), - <<":Blah:">> = iolist_to_binary(join(re:split("Blah blah","((?i)blah)\\s+(?i:\\1)",[]))), - <<":blaH">> = iolist_to_binary(join(re:split("blaH blah","((?i)blah)\\s+(?i:\\1)",[trim]))), + 2}]))), + <<":Blah:">> = iolist_to_binary(join(re:split("Blah blah","((?i)blah)\\s+(?i:\\1)",[]))), + <<":blaH">> = iolist_to_binary(join(re:split("blaH blah","((?i)blah)\\s+(?i:\\1)",[trim]))), <<":blaH:">> = iolist_to_binary(join(re:split("blaH blah","((?i)blah)\\s+(?i:\\1)",[{parts, - 2}]))), - <<":blaH:">> = iolist_to_binary(join(re:split("blaH blah","((?i)blah)\\s+(?i:\\1)",[]))), - <<"">> = iolist_to_binary(join(re:split("a","(?>a*)*",[trim]))), + 2}]))), + <<":blaH:">> = iolist_to_binary(join(re:split("blaH blah","((?i)blah)\\s+(?i:\\1)",[]))), + <<"">> = iolist_to_binary(join(re:split("a","(?>a*)*",[trim]))), <<":">> = iolist_to_binary(join(re:split("a","(?>a*)*",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","(?>a*)*",[]))), - <<"">> = iolist_to_binary(join(re:split("aa","(?>a*)*",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","(?>a*)*",[]))), + <<"">> = iolist_to_binary(join(re:split("aa","(?>a*)*",[trim]))), <<":">> = iolist_to_binary(join(re:split("aa","(?>a*)*",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aa","(?>a*)*",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaa","(?>a*)*",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aa","(?>a*)*",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaa","(?>a*)*",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaa","(?>a*)*",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaa","(?>a*)*",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","(abc|)+",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaa","(?>a*)*",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","(abc|)+",[trim]))), <<"::">> = iolist_to_binary(join(re:split("abc","(abc|)+",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("abc","(abc|)+",[]))), - <<"">> = iolist_to_binary(join(re:split("abcabc","(abc|)+",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("abc","(abc|)+",[]))), + <<"">> = iolist_to_binary(join(re:split("abcabc","(abc|)+",[trim]))), <<"::">> = iolist_to_binary(join(re:split("abcabc","(abc|)+",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("abcabc","(abc|)+",[]))), - <<"">> = iolist_to_binary(join(re:split("abcabcabc","(abc|)+",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("abcabc","(abc|)+",[]))), + <<"">> = iolist_to_binary(join(re:split("abcabcabc","(abc|)+",[trim]))), <<"::">> = iolist_to_binary(join(re:split("abcabcabc","(abc|)+",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("abcabcabc","(abc|)+",[]))), - <<"x::y::z">> = iolist_to_binary(join(re:split("xyz","(abc|)+",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("abcabcabc","(abc|)+",[]))), + <<"x::y::z">> = iolist_to_binary(join(re:split("xyz","(abc|)+",[trim]))), <<"x::yz">> = iolist_to_binary(join(re:split("xyz","(abc|)+",[{parts, - 2}]))), - <<"x::y::z::">> = iolist_to_binary(join(re:split("xyz","(abc|)+",[]))), - <<"">> = iolist_to_binary(join(re:split("a","([a]*)*",[trim]))), + 2}]))), + <<"x::y::z::">> = iolist_to_binary(join(re:split("xyz","(abc|)+",[]))), + <<"">> = iolist_to_binary(join(re:split("a","([a]*)*",[trim]))), <<"::">> = iolist_to_binary(join(re:split("a","([a]*)*",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("a","([a]*)*",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaaa","([a]*)*",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("a","([a]*)*",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaaa","([a]*)*",[trim]))), <<"::">> = iolist_to_binary(join(re:split("aaaaa","([a]*)*",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("aaaaa","([a]*)*",[]))), - <<"">> = iolist_to_binary(join(re:split("a","([ab]*)*",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("aaaaa","([a]*)*",[]))), + <<"">> = iolist_to_binary(join(re:split("a","([ab]*)*",[trim]))), <<"::">> = iolist_to_binary(join(re:split("a","([ab]*)*",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("a","([ab]*)*",[]))), - <<"">> = iolist_to_binary(join(re:split("b","([ab]*)*",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("a","([ab]*)*",[]))), + <<"">> = iolist_to_binary(join(re:split("b","([ab]*)*",[trim]))), <<"::">> = iolist_to_binary(join(re:split("b","([ab]*)*",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("b","([ab]*)*",[]))), - <<"">> = iolist_to_binary(join(re:split("ababab","([ab]*)*",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("b","([ab]*)*",[]))), + <<"">> = iolist_to_binary(join(re:split("ababab","([ab]*)*",[trim]))), <<"::">> = iolist_to_binary(join(re:split("ababab","([ab]*)*",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("ababab","([ab]*)*",[]))), - <<"::c::d::e">> = iolist_to_binary(join(re:split("aaaabcde","([ab]*)*",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("ababab","([ab]*)*",[]))), + <<"::c::d::e">> = iolist_to_binary(join(re:split("aaaabcde","([ab]*)*",[trim]))), <<"::cde">> = iolist_to_binary(join(re:split("aaaabcde","([ab]*)*",[{parts, - 2}]))), - <<"::c::d::e::">> = iolist_to_binary(join(re:split("aaaabcde","([ab]*)*",[]))), - <<"">> = iolist_to_binary(join(re:split("bbbb","([ab]*)*",[trim]))), + 2}]))), + <<"::c::d::e::">> = iolist_to_binary(join(re:split("aaaabcde","([ab]*)*",[]))), + <<"">> = iolist_to_binary(join(re:split("bbbb","([ab]*)*",[trim]))), <<"::">> = iolist_to_binary(join(re:split("bbbb","([ab]*)*",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("bbbb","([ab]*)*",[]))), - <<"">> = iolist_to_binary(join(re:split("b","([^a]*)*",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("bbbb","([ab]*)*",[]))), + <<"">> = iolist_to_binary(join(re:split("b","([^a]*)*",[trim]))), <<"::">> = iolist_to_binary(join(re:split("b","([^a]*)*",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("b","([^a]*)*",[]))), - <<"">> = iolist_to_binary(join(re:split("bbbb","([^a]*)*",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("b","([^a]*)*",[]))), + <<"">> = iolist_to_binary(join(re:split("bbbb","([^a]*)*",[trim]))), <<"::">> = iolist_to_binary(join(re:split("bbbb","([^a]*)*",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("bbbb","([^a]*)*",[]))), - <<"a::a::a">> = iolist_to_binary(join(re:split("aaa","([^a]*)*",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("bbbb","([^a]*)*",[]))), + <<"a::a::a">> = iolist_to_binary(join(re:split("aaa","([^a]*)*",[trim]))), <<"a::aa">> = iolist_to_binary(join(re:split("aaa","([^a]*)*",[{parts, - 2}]))), - <<"a::a::a::">> = iolist_to_binary(join(re:split("aaa","([^a]*)*",[]))), - <<"">> = iolist_to_binary(join(re:split("cccc","([^ab]*)*",[trim]))), + 2}]))), + <<"a::a::a::">> = iolist_to_binary(join(re:split("aaa","([^a]*)*",[]))), + <<"">> = iolist_to_binary(join(re:split("cccc","([^ab]*)*",[trim]))), <<"::">> = iolist_to_binary(join(re:split("cccc","([^ab]*)*",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("cccc","([^ab]*)*",[]))), - <<"a::b::a::b">> = iolist_to_binary(join(re:split("abab","([^ab]*)*",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("cccc","([^ab]*)*",[]))), + <<"a::b::a::b">> = iolist_to_binary(join(re:split("abab","([^ab]*)*",[trim]))), <<"a::bab">> = iolist_to_binary(join(re:split("abab","([^ab]*)*",[{parts, - 2}]))), - <<"a::b::a::b::">> = iolist_to_binary(join(re:split("abab","([^ab]*)*",[]))), - <<"">> = iolist_to_binary(join(re:split("a","([a]*?)*",[trim]))), + 2}]))), + <<"a::b::a::b::">> = iolist_to_binary(join(re:split("abab","([^ab]*)*",[]))), + <<"">> = iolist_to_binary(join(re:split("a","([a]*?)*",[trim]))), <<"::">> = iolist_to_binary(join(re:split("a","([a]*?)*",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("a","([a]*?)*",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaa","([a]*?)*",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("a","([a]*?)*",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaa","([a]*?)*",[trim]))), <<"::aaa">> = iolist_to_binary(join(re:split("aaaa","([a]*?)*",[{parts, - 2}]))), - <<"::::::::">> = iolist_to_binary(join(re:split("aaaa","([a]*?)*",[]))), - <<"">> = iolist_to_binary(join(re:split("a","([ab]*?)*",[trim]))), + 2}]))), + <<"::::::::">> = iolist_to_binary(join(re:split("aaaa","([a]*?)*",[]))), + <<"">> = iolist_to_binary(join(re:split("a","([ab]*?)*",[trim]))), <<"::">> = iolist_to_binary(join(re:split("a","([ab]*?)*",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("a","([ab]*?)*",[]))), - <<"">> = iolist_to_binary(join(re:split("b","([ab]*?)*",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("a","([ab]*?)*",[]))), + <<"">> = iolist_to_binary(join(re:split("b","([ab]*?)*",[trim]))), <<"::">> = iolist_to_binary(join(re:split("b","([ab]*?)*",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("b","([ab]*?)*",[]))), - <<"">> = iolist_to_binary(join(re:split("abab","([ab]*?)*",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("b","([ab]*?)*",[]))), + <<"">> = iolist_to_binary(join(re:split("abab","([ab]*?)*",[trim]))), <<"::bab">> = iolist_to_binary(join(re:split("abab","([ab]*?)*",[{parts, - 2}]))), - <<"::::::::">> = iolist_to_binary(join(re:split("abab","([ab]*?)*",[]))), - <<"">> = iolist_to_binary(join(re:split("baba","([ab]*?)*",[trim]))), + 2}]))), + <<"::::::::">> = iolist_to_binary(join(re:split("abab","([ab]*?)*",[]))), + <<"">> = iolist_to_binary(join(re:split("baba","([ab]*?)*",[trim]))), <<"::aba">> = iolist_to_binary(join(re:split("baba","([ab]*?)*",[{parts, - 2}]))), - <<"::::::::">> = iolist_to_binary(join(re:split("baba","([ab]*?)*",[]))), - <<"">> = iolist_to_binary(join(re:split("b","([^a]*?)*",[trim]))), + 2}]))), + <<"::::::::">> = iolist_to_binary(join(re:split("baba","([ab]*?)*",[]))), + <<"">> = iolist_to_binary(join(re:split("b","([^a]*?)*",[trim]))), <<"::">> = iolist_to_binary(join(re:split("b","([^a]*?)*",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("b","([^a]*?)*",[]))), - <<"">> = iolist_to_binary(join(re:split("bbbb","([^a]*?)*",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("b","([^a]*?)*",[]))), + <<"">> = iolist_to_binary(join(re:split("bbbb","([^a]*?)*",[trim]))), <<"::bbb">> = iolist_to_binary(join(re:split("bbbb","([^a]*?)*",[{parts, - 2}]))), - <<"::::::::">> = iolist_to_binary(join(re:split("bbbb","([^a]*?)*",[]))), - <<"a::a::a">> = iolist_to_binary(join(re:split("aaa","([^a]*?)*",[trim]))), + 2}]))), + <<"::::::::">> = iolist_to_binary(join(re:split("bbbb","([^a]*?)*",[]))), + <<"a::a::a">> = iolist_to_binary(join(re:split("aaa","([^a]*?)*",[trim]))), <<"a::aa">> = iolist_to_binary(join(re:split("aaa","([^a]*?)*",[{parts, - 2}]))), - <<"a::a::a::">> = iolist_to_binary(join(re:split("aaa","([^a]*?)*",[]))), - <<"">> = iolist_to_binary(join(re:split("c","([^ab]*?)*",[trim]))), + 2}]))), + <<"a::a::a::">> = iolist_to_binary(join(re:split("aaa","([^a]*?)*",[]))), + <<"">> = iolist_to_binary(join(re:split("c","([^ab]*?)*",[trim]))), <<"::">> = iolist_to_binary(join(re:split("c","([^ab]*?)*",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("c","([^ab]*?)*",[]))), - <<"">> = iolist_to_binary(join(re:split("cccc","([^ab]*?)*",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("c","([^ab]*?)*",[]))), + <<"">> = iolist_to_binary(join(re:split("cccc","([^ab]*?)*",[trim]))), <<"::ccc">> = iolist_to_binary(join(re:split("cccc","([^ab]*?)*",[{parts, - 2}]))), - <<"::::::::">> = iolist_to_binary(join(re:split("cccc","([^ab]*?)*",[]))), - <<"b::a::b::a">> = iolist_to_binary(join(re:split("baba","([^ab]*?)*",[trim]))), + 2}]))), + <<"::::::::">> = iolist_to_binary(join(re:split("cccc","([^ab]*?)*",[]))), + <<"b::a::b::a">> = iolist_to_binary(join(re:split("baba","([^ab]*?)*",[trim]))), <<"b::aba">> = iolist_to_binary(join(re:split("baba","([^ab]*?)*",[{parts, - 2}]))), - <<"b::a::b::a::">> = iolist_to_binary(join(re:split("baba","([^ab]*?)*",[]))), - <<"">> = iolist_to_binary(join(re:split("a","(?>a*)*",[trim]))), + 2}]))), + <<"b::a::b::a::">> = iolist_to_binary(join(re:split("baba","([^ab]*?)*",[]))), + <<"">> = iolist_to_binary(join(re:split("a","(?>a*)*",[trim]))), <<":">> = iolist_to_binary(join(re:split("a","(?>a*)*",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","(?>a*)*",[]))), - <<":b:c:d:e">> = iolist_to_binary(join(re:split("aaabcde","(?>a*)*",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","(?>a*)*",[]))), + <<":b:c:d:e">> = iolist_to_binary(join(re:split("aaabcde","(?>a*)*",[trim]))), <<":bcde">> = iolist_to_binary(join(re:split("aaabcde","(?>a*)*",[{parts, - 2}]))), - <<":b:c:d:e:">> = iolist_to_binary(join(re:split("aaabcde","(?>a*)*",[]))), + 2}]))), + <<":b:c:d:e:">> = iolist_to_binary(join(re:split("aaabcde","(?>a*)*",[]))), ok. run14() -> - <<"">> = iolist_to_binary(join(re:split("aaaaa","((?>a*))*",[trim]))), + <<"">> = iolist_to_binary(join(re:split("aaaaa","((?>a*))*",[trim]))), <<"::">> = iolist_to_binary(join(re:split("aaaaa","((?>a*))*",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("aaaaa","((?>a*))*",[]))), - <<"::b::b">> = iolist_to_binary(join(re:split("aabbaa","((?>a*))*",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("aaaaa","((?>a*))*",[]))), + <<"::b::b">> = iolist_to_binary(join(re:split("aabbaa","((?>a*))*",[trim]))), <<"::bbaa">> = iolist_to_binary(join(re:split("aabbaa","((?>a*))*",[{parts, - 2}]))), - <<"::b::b::">> = iolist_to_binary(join(re:split("aabbaa","((?>a*))*",[]))), - <<"a::a::a::a::a">> = iolist_to_binary(join(re:split("aaaaa","((?>a*?))*",[trim]))), + 2}]))), + <<"::b::b::">> = iolist_to_binary(join(re:split("aabbaa","((?>a*))*",[]))), + <<"a::a::a::a::a">> = iolist_to_binary(join(re:split("aaaaa","((?>a*?))*",[trim]))), <<"a::aaaa">> = iolist_to_binary(join(re:split("aaaaa","((?>a*?))*",[{parts, - 2}]))), - <<"a::a::a::a::a::">> = iolist_to_binary(join(re:split("aaaaa","((?>a*?))*",[]))), - <<"a::a::b::b::a::a">> = iolist_to_binary(join(re:split("aabbaa","((?>a*?))*",[trim]))), + 2}]))), + <<"a::a::a::a::a::">> = iolist_to_binary(join(re:split("aaaaa","((?>a*?))*",[]))), + <<"a::a::b::b::a::a">> = iolist_to_binary(join(re:split("aabbaa","((?>a*?))*",[trim]))), <<"a::abbaa">> = iolist_to_binary(join(re:split("aabbaa","((?>a*?))*",[{parts, - 2}]))), - <<"a::a::b::b::a::a::">> = iolist_to_binary(join(re:split("aabbaa","((?>a*?))*",[]))), + 2}]))), + <<"a::a::b::b::a::a::">> = iolist_to_binary(join(re:split("aabbaa","((?>a*?))*",[]))), <<"">> = iolist_to_binary(join(re:split("12-sep-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("12-sep-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("12-sep-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("12-sep-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended]))), <<"">> = iolist_to_binary(join(re:split("12-09-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("12-09-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("12-09-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("12-09-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended]))), <<"sep-12-98">> = iolist_to_binary(join(re:split("sep-12-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended, - trim]))), + trim]))), <<"sep-12-98">> = iolist_to_binary(join(re:split("sep-12-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended, {parts, - 2}]))), - <<"sep-12-98">> = iolist_to_binary(join(re:split("sep-12-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended]))), - <<"foo:foo">> = iolist_to_binary(join(re:split("foobarfoo","(?<=(foo))bar\\1",[trim]))), + 2}]))), + <<"sep-12-98">> = iolist_to_binary(join(re:split("sep-12-98","(?(?=[^a-z]+[a-z]) \\d{2}-[a-z]{3}-\\d{2} | \\d{2}-\\d{2}-\\d{2} ) ",[extended]))), + <<"foo:foo">> = iolist_to_binary(join(re:split("foobarfoo","(?<=(foo))bar\\1",[trim]))), <<"foo:foo:">> = iolist_to_binary(join(re:split("foobarfoo","(?<=(foo))bar\\1",[{parts, - 2}]))), - <<"foo:foo:">> = iolist_to_binary(join(re:split("foobarfoo","(?<=(foo))bar\\1",[]))), - <<"foo:foo:tling">> = iolist_to_binary(join(re:split("foobarfootling","(?<=(foo))bar\\1",[trim]))), + 2}]))), + <<"foo:foo:">> = iolist_to_binary(join(re:split("foobarfoo","(?<=(foo))bar\\1",[]))), + <<"foo:foo:tling">> = iolist_to_binary(join(re:split("foobarfootling","(?<=(foo))bar\\1",[trim]))), <<"foo:foo:tling">> = iolist_to_binary(join(re:split("foobarfootling","(?<=(foo))bar\\1",[{parts, - 2}]))), - <<"foo:foo:tling">> = iolist_to_binary(join(re:split("foobarfootling","(?<=(foo))bar\\1",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(foo))bar\\1",[trim]))), + 2}]))), + <<"foo:foo:tling">> = iolist_to_binary(join(re:split("foobarfootling","(?<=(foo))bar\\1",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(foo))bar\\1",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(foo))bar\\1",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(foo))bar\\1",[]))), - <<"foobar">> = iolist_to_binary(join(re:split("foobar","(?<=(foo))bar\\1",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(foo))bar\\1",[]))), + <<"foobar">> = iolist_to_binary(join(re:split("foobar","(?<=(foo))bar\\1",[trim]))), <<"foobar">> = iolist_to_binary(join(re:split("foobar","(?<=(foo))bar\\1",[{parts, - 2}]))), - <<"foobar">> = iolist_to_binary(join(re:split("foobar","(?<=(foo))bar\\1",[]))), - <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","(?<=(foo))bar\\1",[trim]))), + 2}]))), + <<"foobar">> = iolist_to_binary(join(re:split("foobar","(?<=(foo))bar\\1",[]))), + <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","(?<=(foo))bar\\1",[trim]))), <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","(?<=(foo))bar\\1",[{parts, - 2}]))), - <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","(?<=(foo))bar\\1",[]))), - <<"">> = iolist_to_binary(join(re:split("saturday","(?i:saturday|sunday)",[trim]))), + 2}]))), + <<"barfoo">> = iolist_to_binary(join(re:split("barfoo","(?<=(foo))bar\\1",[]))), + <<"">> = iolist_to_binary(join(re:split("saturday","(?i:saturday|sunday)",[trim]))), <<":">> = iolist_to_binary(join(re:split("saturday","(?i:saturday|sunday)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("saturday","(?i:saturday|sunday)",[]))), - <<"">> = iolist_to_binary(join(re:split("sunday","(?i:saturday|sunday)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("saturday","(?i:saturday|sunday)",[]))), + <<"">> = iolist_to_binary(join(re:split("sunday","(?i:saturday|sunday)",[trim]))), <<":">> = iolist_to_binary(join(re:split("sunday","(?i:saturday|sunday)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("sunday","(?i:saturday|sunday)",[]))), - <<"">> = iolist_to_binary(join(re:split("Saturday","(?i:saturday|sunday)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("sunday","(?i:saturday|sunday)",[]))), + <<"">> = iolist_to_binary(join(re:split("Saturday","(?i:saturday|sunday)",[trim]))), <<":">> = iolist_to_binary(join(re:split("Saturday","(?i:saturday|sunday)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("Saturday","(?i:saturday|sunday)",[]))), - <<"">> = iolist_to_binary(join(re:split("Sunday","(?i:saturday|sunday)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("Saturday","(?i:saturday|sunday)",[]))), + <<"">> = iolist_to_binary(join(re:split("Sunday","(?i:saturday|sunday)",[trim]))), <<":">> = iolist_to_binary(join(re:split("Sunday","(?i:saturday|sunday)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("Sunday","(?i:saturday|sunday)",[]))), - <<"">> = iolist_to_binary(join(re:split("SATURDAY","(?i:saturday|sunday)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("Sunday","(?i:saturday|sunday)",[]))), + <<"">> = iolist_to_binary(join(re:split("SATURDAY","(?i:saturday|sunday)",[trim]))), <<":">> = iolist_to_binary(join(re:split("SATURDAY","(?i:saturday|sunday)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("SATURDAY","(?i:saturday|sunday)",[]))), - <<"">> = iolist_to_binary(join(re:split("SUNDAY","(?i:saturday|sunday)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("SATURDAY","(?i:saturday|sunday)",[]))), + <<"">> = iolist_to_binary(join(re:split("SUNDAY","(?i:saturday|sunday)",[trim]))), <<":">> = iolist_to_binary(join(re:split("SUNDAY","(?i:saturday|sunday)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("SUNDAY","(?i:saturday|sunday)",[]))), - <<"">> = iolist_to_binary(join(re:split("SunDay","(?i:saturday|sunday)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("SUNDAY","(?i:saturday|sunday)",[]))), + <<"">> = iolist_to_binary(join(re:split("SunDay","(?i:saturday|sunday)",[trim]))), <<":">> = iolist_to_binary(join(re:split("SunDay","(?i:saturday|sunday)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("SunDay","(?i:saturday|sunday)",[]))), - <<":abc">> = iolist_to_binary(join(re:split("abcx","(a(?i)bc|BB)x",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("SunDay","(?i:saturday|sunday)",[]))), + <<":abc">> = iolist_to_binary(join(re:split("abcx","(a(?i)bc|BB)x",[trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abcx","(a(?i)bc|BB)x",[{parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abcx","(a(?i)bc|BB)x",[]))), - <<":aBC">> = iolist_to_binary(join(re:split("aBCx","(a(?i)bc|BB)x",[trim]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abcx","(a(?i)bc|BB)x",[]))), + <<":aBC">> = iolist_to_binary(join(re:split("aBCx","(a(?i)bc|BB)x",[trim]))), <<":aBC:">> = iolist_to_binary(join(re:split("aBCx","(a(?i)bc|BB)x",[{parts, - 2}]))), - <<":aBC:">> = iolist_to_binary(join(re:split("aBCx","(a(?i)bc|BB)x",[]))), - <<":bb">> = iolist_to_binary(join(re:split("bbx","(a(?i)bc|BB)x",[trim]))), + 2}]))), + <<":aBC:">> = iolist_to_binary(join(re:split("aBCx","(a(?i)bc|BB)x",[]))), + <<":bb">> = iolist_to_binary(join(re:split("bbx","(a(?i)bc|BB)x",[trim]))), <<":bb:">> = iolist_to_binary(join(re:split("bbx","(a(?i)bc|BB)x",[{parts, - 2}]))), - <<":bb:">> = iolist_to_binary(join(re:split("bbx","(a(?i)bc|BB)x",[]))), - <<":BB">> = iolist_to_binary(join(re:split("BBx","(a(?i)bc|BB)x",[trim]))), + 2}]))), + <<":bb:">> = iolist_to_binary(join(re:split("bbx","(a(?i)bc|BB)x",[]))), + <<":BB">> = iolist_to_binary(join(re:split("BBx","(a(?i)bc|BB)x",[trim]))), <<":BB:">> = iolist_to_binary(join(re:split("BBx","(a(?i)bc|BB)x",[{parts, - 2}]))), - <<":BB:">> = iolist_to_binary(join(re:split("BBx","(a(?i)bc|BB)x",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a(?i)bc|BB)x",[trim]))), + 2}]))), + <<":BB:">> = iolist_to_binary(join(re:split("BBx","(a(?i)bc|BB)x",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a(?i)bc|BB)x",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a(?i)bc|BB)x",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a(?i)bc|BB)x",[]))), - <<"abcX">> = iolist_to_binary(join(re:split("abcX","(a(?i)bc|BB)x",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(a(?i)bc|BB)x",[]))), + <<"abcX">> = iolist_to_binary(join(re:split("abcX","(a(?i)bc|BB)x",[trim]))), <<"abcX">> = iolist_to_binary(join(re:split("abcX","(a(?i)bc|BB)x",[{parts, - 2}]))), - <<"abcX">> = iolist_to_binary(join(re:split("abcX","(a(?i)bc|BB)x",[]))), - <<"aBCX">> = iolist_to_binary(join(re:split("aBCX","(a(?i)bc|BB)x",[trim]))), + 2}]))), + <<"abcX">> = iolist_to_binary(join(re:split("abcX","(a(?i)bc|BB)x",[]))), + <<"aBCX">> = iolist_to_binary(join(re:split("aBCX","(a(?i)bc|BB)x",[trim]))), <<"aBCX">> = iolist_to_binary(join(re:split("aBCX","(a(?i)bc|BB)x",[{parts, - 2}]))), - <<"aBCX">> = iolist_to_binary(join(re:split("aBCX","(a(?i)bc|BB)x",[]))), - <<"bbX">> = iolist_to_binary(join(re:split("bbX","(a(?i)bc|BB)x",[trim]))), + 2}]))), + <<"aBCX">> = iolist_to_binary(join(re:split("aBCX","(a(?i)bc|BB)x",[]))), + <<"bbX">> = iolist_to_binary(join(re:split("bbX","(a(?i)bc|BB)x",[trim]))), <<"bbX">> = iolist_to_binary(join(re:split("bbX","(a(?i)bc|BB)x",[{parts, - 2}]))), - <<"bbX">> = iolist_to_binary(join(re:split("bbX","(a(?i)bc|BB)x",[]))), - <<"BBX">> = iolist_to_binary(join(re:split("BBX","(a(?i)bc|BB)x",[trim]))), + 2}]))), + <<"bbX">> = iolist_to_binary(join(re:split("bbX","(a(?i)bc|BB)x",[]))), + <<"BBX">> = iolist_to_binary(join(re:split("BBX","(a(?i)bc|BB)x",[trim]))), <<"BBX">> = iolist_to_binary(join(re:split("BBX","(a(?i)bc|BB)x",[{parts, - 2}]))), - <<"BBX">> = iolist_to_binary(join(re:split("BBX","(a(?i)bc|BB)x",[]))), - <<":ac">> = iolist_to_binary(join(re:split("ac","^([ab](?i)[cd]|[ef])",[trim]))), + 2}]))), + <<"BBX">> = iolist_to_binary(join(re:split("BBX","(a(?i)bc|BB)x",[]))), + <<":ac">> = iolist_to_binary(join(re:split("ac","^([ab](?i)[cd]|[ef])",[trim]))), <<":ac:">> = iolist_to_binary(join(re:split("ac","^([ab](?i)[cd]|[ef])",[{parts, - 2}]))), - <<":ac:">> = iolist_to_binary(join(re:split("ac","^([ab](?i)[cd]|[ef])",[]))), - <<":aC">> = iolist_to_binary(join(re:split("aC","^([ab](?i)[cd]|[ef])",[trim]))), + 2}]))), + <<":ac:">> = iolist_to_binary(join(re:split("ac","^([ab](?i)[cd]|[ef])",[]))), + <<":aC">> = iolist_to_binary(join(re:split("aC","^([ab](?i)[cd]|[ef])",[trim]))), <<":aC:">> = iolist_to_binary(join(re:split("aC","^([ab](?i)[cd]|[ef])",[{parts, - 2}]))), - <<":aC:">> = iolist_to_binary(join(re:split("aC","^([ab](?i)[cd]|[ef])",[]))), - <<":bD">> = iolist_to_binary(join(re:split("bD","^([ab](?i)[cd]|[ef])",[trim]))), + 2}]))), + <<":aC:">> = iolist_to_binary(join(re:split("aC","^([ab](?i)[cd]|[ef])",[]))), + <<":bD">> = iolist_to_binary(join(re:split("bD","^([ab](?i)[cd]|[ef])",[trim]))), <<":bD:">> = iolist_to_binary(join(re:split("bD","^([ab](?i)[cd]|[ef])",[{parts, - 2}]))), - <<":bD:">> = iolist_to_binary(join(re:split("bD","^([ab](?i)[cd]|[ef])",[]))), - <<":e:lephant">> = iolist_to_binary(join(re:split("elephant","^([ab](?i)[cd]|[ef])",[trim]))), + 2}]))), + <<":bD:">> = iolist_to_binary(join(re:split("bD","^([ab](?i)[cd]|[ef])",[]))), + <<":e:lephant">> = iolist_to_binary(join(re:split("elephant","^([ab](?i)[cd]|[ef])",[trim]))), <<":e:lephant">> = iolist_to_binary(join(re:split("elephant","^([ab](?i)[cd]|[ef])",[{parts, - 2}]))), - <<":e:lephant">> = iolist_to_binary(join(re:split("elephant","^([ab](?i)[cd]|[ef])",[]))), - <<":E:urope">> = iolist_to_binary(join(re:split("Europe","^([ab](?i)[cd]|[ef])",[trim]))), + 2}]))), + <<":e:lephant">> = iolist_to_binary(join(re:split("elephant","^([ab](?i)[cd]|[ef])",[]))), + <<":E:urope">> = iolist_to_binary(join(re:split("Europe","^([ab](?i)[cd]|[ef])",[trim]))), <<":E:urope">> = iolist_to_binary(join(re:split("Europe","^([ab](?i)[cd]|[ef])",[{parts, - 2}]))), - <<":E:urope">> = iolist_to_binary(join(re:split("Europe","^([ab](?i)[cd]|[ef])",[]))), - <<":f:rog">> = iolist_to_binary(join(re:split("frog","^([ab](?i)[cd]|[ef])",[trim]))), + 2}]))), + <<":E:urope">> = iolist_to_binary(join(re:split("Europe","^([ab](?i)[cd]|[ef])",[]))), + <<":f:rog">> = iolist_to_binary(join(re:split("frog","^([ab](?i)[cd]|[ef])",[trim]))), <<":f:rog">> = iolist_to_binary(join(re:split("frog","^([ab](?i)[cd]|[ef])",[{parts, - 2}]))), - <<":f:rog">> = iolist_to_binary(join(re:split("frog","^([ab](?i)[cd]|[ef])",[]))), - <<":F:rance">> = iolist_to_binary(join(re:split("France","^([ab](?i)[cd]|[ef])",[trim]))), + 2}]))), + <<":f:rog">> = iolist_to_binary(join(re:split("frog","^([ab](?i)[cd]|[ef])",[]))), + <<":F:rance">> = iolist_to_binary(join(re:split("France","^([ab](?i)[cd]|[ef])",[trim]))), <<":F:rance">> = iolist_to_binary(join(re:split("France","^([ab](?i)[cd]|[ef])",[{parts, - 2}]))), - <<":F:rance">> = iolist_to_binary(join(re:split("France","^([ab](?i)[cd]|[ef])",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^([ab](?i)[cd]|[ef])",[trim]))), + 2}]))), + <<":F:rance">> = iolist_to_binary(join(re:split("France","^([ab](?i)[cd]|[ef])",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^([ab](?i)[cd]|[ef])",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^([ab](?i)[cd]|[ef])",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^([ab](?i)[cd]|[ef])",[]))), - <<"Africa">> = iolist_to_binary(join(re:split("Africa","^([ab](?i)[cd]|[ef])",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^([ab](?i)[cd]|[ef])",[]))), + <<"Africa">> = iolist_to_binary(join(re:split("Africa","^([ab](?i)[cd]|[ef])",[trim]))), <<"Africa">> = iolist_to_binary(join(re:split("Africa","^([ab](?i)[cd]|[ef])",[{parts, - 2}]))), - <<"Africa">> = iolist_to_binary(join(re:split("Africa","^([ab](?i)[cd]|[ef])",[]))), - <<":ab">> = iolist_to_binary(join(re:split("ab","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))), + 2}]))), + <<"Africa">> = iolist_to_binary(join(re:split("Africa","^([ab](?i)[cd]|[ef])",[]))), + <<":ab">> = iolist_to_binary(join(re:split("ab","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))), <<":ab:">> = iolist_to_binary(join(re:split("ab","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[{parts, - 2}]))), - <<":ab:">> = iolist_to_binary(join(re:split("ab","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))), - <<":aBd">> = iolist_to_binary(join(re:split("aBd","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))), + 2}]))), + <<":ab:">> = iolist_to_binary(join(re:split("ab","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))), + <<":aBd">> = iolist_to_binary(join(re:split("aBd","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))), <<":aBd:">> = iolist_to_binary(join(re:split("aBd","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[{parts, - 2}]))), - <<":aBd:">> = iolist_to_binary(join(re:split("aBd","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))), - <<":xy">> = iolist_to_binary(join(re:split("xy","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))), + 2}]))), + <<":aBd:">> = iolist_to_binary(join(re:split("aBd","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))), + <<":xy">> = iolist_to_binary(join(re:split("xy","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))), <<":xy:">> = iolist_to_binary(join(re:split("xy","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[{parts, - 2}]))), - <<":xy:">> = iolist_to_binary(join(re:split("xy","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))), - <<":xY">> = iolist_to_binary(join(re:split("xY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))), + 2}]))), + <<":xy:">> = iolist_to_binary(join(re:split("xy","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))), + <<":xY">> = iolist_to_binary(join(re:split("xY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))), <<":xY:">> = iolist_to_binary(join(re:split("xY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[{parts, - 2}]))), - <<":xY:">> = iolist_to_binary(join(re:split("xY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))), - <<":z:ebra">> = iolist_to_binary(join(re:split("zebra","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))), + 2}]))), + <<":xY:">> = iolist_to_binary(join(re:split("xY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))), + <<":z:ebra">> = iolist_to_binary(join(re:split("zebra","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))), <<":z:ebra">> = iolist_to_binary(join(re:split("zebra","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[{parts, - 2}]))), - <<":z:ebra">> = iolist_to_binary(join(re:split("zebra","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))), - <<":Z:ambesi">> = iolist_to_binary(join(re:split("Zambesi","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))), + 2}]))), + <<":z:ebra">> = iolist_to_binary(join(re:split("zebra","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))), + <<":Z:ambesi">> = iolist_to_binary(join(re:split("Zambesi","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))), <<":Z:ambesi">> = iolist_to_binary(join(re:split("Zambesi","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[{parts, - 2}]))), - <<":Z:ambesi">> = iolist_to_binary(join(re:split("Zambesi","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))), + 2}]))), + <<":Z:ambesi">> = iolist_to_binary(join(re:split("Zambesi","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))), - <<"aCD">> = iolist_to_binary(join(re:split("aCD","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))), + <<"aCD">> = iolist_to_binary(join(re:split("aCD","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))), <<"aCD">> = iolist_to_binary(join(re:split("aCD","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[{parts, - 2}]))), - <<"aCD">> = iolist_to_binary(join(re:split("aCD","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))), - <<"XY">> = iolist_to_binary(join(re:split("XY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))), + 2}]))), + <<"aCD">> = iolist_to_binary(join(re:split("aCD","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))), + <<"XY">> = iolist_to_binary(join(re:split("XY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[trim]))), <<"XY">> = iolist_to_binary(join(re:split("XY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[{parts, - 2}]))), - <<"XY">> = iolist_to_binary(join(re:split("XY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))), + 2}]))), + <<"XY">> = iolist_to_binary(join(re:split("XY","^(ab|a(?i)[b-c](?m-i)d|x(?i)y|z)",[]))), <<"foo ">> = iolist_to_binary(join(re:split("foo -bar","(?<=foo\\n)^bar",[multiline,trim]))), +bar","(?<=foo\\n)^bar",[multiline,trim]))), <<"foo :">> = iolist_to_binary(join(re:split("foo -bar","(?<=foo\\n)^bar",[multiline,{parts,2}]))), +bar","(?<=foo\\n)^bar",[multiline,{parts,2}]))), <<"foo :">> = iolist_to_binary(join(re:split("foo -bar","(?<=foo\\n)^bar",[multiline]))), +bar","(?<=foo\\n)^bar",[multiline]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=foo\\n)^bar",[multiline, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=foo\\n)^bar",[multiline, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=foo\\n)^bar",[multiline]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=foo\\n)^bar",[multiline]))), <<"bar">> = iolist_to_binary(join(re:split("bar","(?<=foo\\n)^bar",[multiline, - trim]))), + trim]))), <<"bar">> = iolist_to_binary(join(re:split("bar","(?<=foo\\n)^bar",[multiline, {parts, - 2}]))), - <<"bar">> = iolist_to_binary(join(re:split("bar","(?<=foo\\n)^bar",[multiline]))), + 2}]))), + <<"bar">> = iolist_to_binary(join(re:split("bar","(?<=foo\\n)^bar",[multiline]))), <<"baz bar">> = iolist_to_binary(join(re:split("baz -bar","(?<=foo\\n)^bar",[multiline,trim]))), +bar","(?<=foo\\n)^bar",[multiline,trim]))), <<"baz bar">> = iolist_to_binary(join(re:split("baz -bar","(?<=foo\\n)^bar",[multiline,{parts,2}]))), +bar","(?<=foo\\n)^bar",[multiline,{parts,2}]))), <<"baz bar">> = iolist_to_binary(join(re:split("baz -bar","(?<=foo\\n)^bar",[multiline]))), - <<"bar">> = iolist_to_binary(join(re:split("barbaz","(?<=(?<!foo)bar)baz",[trim]))), +bar","(?<=foo\\n)^bar",[multiline]))), + <<"bar">> = iolist_to_binary(join(re:split("barbaz","(?<=(?<!foo)bar)baz",[trim]))), <<"bar:">> = iolist_to_binary(join(re:split("barbaz","(?<=(?<!foo)bar)baz",[{parts, - 2}]))), - <<"bar:">> = iolist_to_binary(join(re:split("barbaz","(?<=(?<!foo)bar)baz",[]))), - <<"barbar">> = iolist_to_binary(join(re:split("barbarbaz","(?<=(?<!foo)bar)baz",[trim]))), + 2}]))), + <<"bar:">> = iolist_to_binary(join(re:split("barbaz","(?<=(?<!foo)bar)baz",[]))), + <<"barbar">> = iolist_to_binary(join(re:split("barbarbaz","(?<=(?<!foo)bar)baz",[trim]))), <<"barbar:">> = iolist_to_binary(join(re:split("barbarbaz","(?<=(?<!foo)bar)baz",[{parts, - 2}]))), - <<"barbar:">> = iolist_to_binary(join(re:split("barbarbaz","(?<=(?<!foo)bar)baz",[]))), - <<"koobar">> = iolist_to_binary(join(re:split("koobarbaz","(?<=(?<!foo)bar)baz",[trim]))), + 2}]))), + <<"barbar:">> = iolist_to_binary(join(re:split("barbarbaz","(?<=(?<!foo)bar)baz",[]))), + <<"koobar">> = iolist_to_binary(join(re:split("koobarbaz","(?<=(?<!foo)bar)baz",[trim]))), <<"koobar:">> = iolist_to_binary(join(re:split("koobarbaz","(?<=(?<!foo)bar)baz",[{parts, - 2}]))), - <<"koobar:">> = iolist_to_binary(join(re:split("koobarbaz","(?<=(?<!foo)bar)baz",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(?<!foo)bar)baz",[trim]))), + 2}]))), + <<"koobar:">> = iolist_to_binary(join(re:split("koobarbaz","(?<=(?<!foo)bar)baz",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(?<!foo)bar)baz",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(?<!foo)bar)baz",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(?<!foo)bar)baz",[]))), - <<"baz">> = iolist_to_binary(join(re:split("baz","(?<=(?<!foo)bar)baz",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(?<!foo)bar)baz",[]))), + <<"baz">> = iolist_to_binary(join(re:split("baz","(?<=(?<!foo)bar)baz",[trim]))), <<"baz">> = iolist_to_binary(join(re:split("baz","(?<=(?<!foo)bar)baz",[{parts, - 2}]))), - <<"baz">> = iolist_to_binary(join(re:split("baz","(?<=(?<!foo)bar)baz",[]))), - <<"foobarbaz">> = iolist_to_binary(join(re:split("foobarbaz","(?<=(?<!foo)bar)baz",[trim]))), + 2}]))), + <<"baz">> = iolist_to_binary(join(re:split("baz","(?<=(?<!foo)bar)baz",[]))), + <<"foobarbaz">> = iolist_to_binary(join(re:split("foobarbaz","(?<=(?<!foo)bar)baz",[trim]))), <<"foobarbaz">> = iolist_to_binary(join(re:split("foobarbaz","(?<=(?<!foo)bar)baz",[{parts, - 2}]))), - <<"foobarbaz">> = iolist_to_binary(join(re:split("foobarbaz","(?<=(?<!foo)bar)baz",[]))), - <<"a">> = iolist_to_binary(join(re:split("a","^(a\\1?){4}$",[trim]))), + 2}]))), + <<"foobarbaz">> = iolist_to_binary(join(re:split("foobarbaz","(?<=(?<!foo)bar)baz",[]))), + <<"a">> = iolist_to_binary(join(re:split("a","^(a\\1?){4}$",[trim]))), <<"a">> = iolist_to_binary(join(re:split("a","^(a\\1?){4}$",[{parts, - 2}]))), - <<"a">> = iolist_to_binary(join(re:split("a","^(a\\1?){4}$",[]))), - <<"aa">> = iolist_to_binary(join(re:split("aa","^(a\\1?){4}$",[trim]))), + 2}]))), + <<"a">> = iolist_to_binary(join(re:split("a","^(a\\1?){4}$",[]))), + <<"aa">> = iolist_to_binary(join(re:split("aa","^(a\\1?){4}$",[trim]))), <<"aa">> = iolist_to_binary(join(re:split("aa","^(a\\1?){4}$",[{parts, - 2}]))), - <<"aa">> = iolist_to_binary(join(re:split("aa","^(a\\1?){4}$",[]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a\\1?){4}$",[trim]))), + 2}]))), + <<"aa">> = iolist_to_binary(join(re:split("aa","^(a\\1?){4}$",[]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a\\1?){4}$",[trim]))), <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a\\1?){4}$",[{parts, - 2}]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a\\1?){4}$",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaaaa","^(a\\1?){4}$",[trim]))), + 2}]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a\\1?){4}$",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaaaa","^(a\\1?){4}$",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aaaaa","^(a\\1?){4}$",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aaaaa","^(a\\1?){4}$",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaaaaaa","^(a\\1?){4}$",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aaaaa","^(a\\1?){4}$",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaaaaaa","^(a\\1?){4}$",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aaaaaaa","^(a\\1?){4}$",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aaaaaaa","^(a\\1?){4}$",[]))), - <<"aaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a\\1?){4}$",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aaaaaaa","^(a\\1?){4}$",[]))), + <<"aaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a\\1?){4}$",[trim]))), <<"aaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a\\1?){4}$",[{parts, - 2}]))), - <<"aaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a\\1?){4}$",[]))), - <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?){4}$",[trim]))), + 2}]))), + <<"aaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a\\1?){4}$",[]))), + <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?){4}$",[trim]))), <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?){4}$",[{parts, - 2}]))), - <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?){4}$",[]))), - <<":aaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?){4}$",[trim]))), + 2}]))), + <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?){4}$",[]))), + <<":aaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?){4}$",[trim]))), <<":aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?){4}$",[{parts, - 2}]))), - <<":aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?){4}$",[]))), - <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?){4}$",[trim]))), + 2}]))), + <<":aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?){4}$",[]))), + <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?){4}$",[trim]))), <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?){4}$",[{parts, - 2}]))), - <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?){4}$",[]))), - <<"aaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaa","^(a\\1?){4}$",[trim]))), + 2}]))), + <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?){4}$",[]))), + <<"aaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaa","^(a\\1?){4}$",[trim]))), <<"aaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaa","^(a\\1?){4}$",[{parts, - 2}]))), - <<"aaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaa","^(a\\1?){4}$",[]))), - <<"aaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaa","^(a\\1?){4}$",[trim]))), + 2}]))), + <<"aaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaa","^(a\\1?){4}$",[]))), + <<"aaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaa","^(a\\1?){4}$",[trim]))), <<"aaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaa","^(a\\1?){4}$",[{parts, - 2}]))), - <<"aaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaa","^(a\\1?){4}$",[]))), - <<"aaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaa","^(a\\1?){4}$",[trim]))), + 2}]))), + <<"aaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaa","^(a\\1?){4}$",[]))), + <<"aaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaa","^(a\\1?){4}$",[trim]))), <<"aaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaa","^(a\\1?){4}$",[{parts, - 2}]))), - <<"aaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaa","^(a\\1?){4}$",[]))), - <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaa","^(a\\1?){4}$",[trim]))), + 2}]))), + <<"aaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaa","^(a\\1?){4}$",[]))), + <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaa","^(a\\1?){4}$",[trim]))), <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaa","^(a\\1?){4}$",[{parts, - 2}]))), - <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaa","^(a\\1?){4}$",[]))), - <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaa","^(a\\1?){4}$",[trim]))), + 2}]))), + <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaa","^(a\\1?){4}$",[]))), + <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaa","^(a\\1?){4}$",[trim]))), <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaa","^(a\\1?){4}$",[{parts, - 2}]))), - <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaa","^(a\\1?){4}$",[]))), - <<"a">> = iolist_to_binary(join(re:split("a","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), + 2}]))), + <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaa","^(a\\1?){4}$",[]))), + <<"a">> = iolist_to_binary(join(re:split("a","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), <<"a">> = iolist_to_binary(join(re:split("a","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts, - 2}]))), - <<"a">> = iolist_to_binary(join(re:split("a","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), - <<"aa">> = iolist_to_binary(join(re:split("aa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), + 2}]))), + <<"a">> = iolist_to_binary(join(re:split("a","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), + <<"aa">> = iolist_to_binary(join(re:split("aa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), <<"aa">> = iolist_to_binary(join(re:split("aa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts, - 2}]))), - <<"aa">> = iolist_to_binary(join(re:split("aa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), + 2}]))), + <<"aa">> = iolist_to_binary(join(re:split("aa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts, - 2}]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), - <<":a:a:a:a">> = iolist_to_binary(join(re:split("aaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), + 2}]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), + <<":a:a:a:a">> = iolist_to_binary(join(re:split("aaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), <<":a:a:a:a:">> = iolist_to_binary(join(re:split("aaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts, - 2}]))), - <<":a:a:a:a:">> = iolist_to_binary(join(re:split("aaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), - <<":a:aa:a:a">> = iolist_to_binary(join(re:split("aaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), + 2}]))), + <<":a:a:a:a:">> = iolist_to_binary(join(re:split("aaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), + <<":a:aa:a:a">> = iolist_to_binary(join(re:split("aaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), <<":a:aa:a:a:">> = iolist_to_binary(join(re:split("aaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts, - 2}]))), - <<":a:aa:a:a:">> = iolist_to_binary(join(re:split("aaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), - <<":a:aa:a:aa">> = iolist_to_binary(join(re:split("aaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), + 2}]))), + <<":a:aa:a:a:">> = iolist_to_binary(join(re:split("aaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), + <<":a:aa:a:aa">> = iolist_to_binary(join(re:split("aaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), <<":a:aa:a:aa:">> = iolist_to_binary(join(re:split("aaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts, - 2}]))), - <<":a:aa:a:aa:">> = iolist_to_binary(join(re:split("aaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), - <<":a:aa:aaa:a">> = iolist_to_binary(join(re:split("aaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), + 2}]))), + <<":a:aa:a:aa:">> = iolist_to_binary(join(re:split("aaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), + <<":a:aa:aaa:a">> = iolist_to_binary(join(re:split("aaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), <<":a:aa:aaa:a:">> = iolist_to_binary(join(re:split("aaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts, - 2}]))), - <<":a:aa:aaa:a:">> = iolist_to_binary(join(re:split("aaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), - <<"aaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), + 2}]))), + <<":a:aa:aaa:a:">> = iolist_to_binary(join(re:split("aaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), + <<"aaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), <<"aaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts, - 2}]))), - <<"aaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), - <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), + 2}]))), + <<"aaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), + <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts, - 2}]))), - <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), - <<":a:aa:aaa:aaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), + 2}]))), + <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), + <<":a:aa:aaa:aaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), <<":a:aa:aaa:aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts, - 2}]))), - <<":a:aa:aaa:aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), - <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), + 2}]))), + <<":a:aa:aaa:aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), + <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts, - 2}]))), - <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), - <<"aaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), + 2}]))), + <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), + <<"aaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), <<"aaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts, - 2}]))), - <<"aaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), - <<"aaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), + 2}]))), + <<"aaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), + <<"aaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), <<"aaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts, - 2}]))), - <<"aaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), - <<"aaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), + 2}]))), + <<"aaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), + <<"aaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), <<"aaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts, - 2}]))), - <<"aaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), - <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), + 2}]))), + <<"aaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), + <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts, - 2}]))), - <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), - <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), + 2}]))), + <<"aaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), + <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[trim]))), <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[{parts, - 2}]))), - <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","abc",[trim]))), + 2}]))), + <<"aaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaa","^(a\\1?)(a\\1?)(a\\2?)(a\\3?)$",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","abc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","abc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","abc",[]))), - <<"x:y">> = iolist_to_binary(join(re:split("xabcy","abc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","abc",[]))), + <<"x:y">> = iolist_to_binary(join(re:split("xabcy","abc",[trim]))), <<"x:y">> = iolist_to_binary(join(re:split("xabcy","abc",[{parts, - 2}]))), - <<"x:y">> = iolist_to_binary(join(re:split("xabcy","abc",[]))), - <<"ab">> = iolist_to_binary(join(re:split("ababc","abc",[trim]))), + 2}]))), + <<"x:y">> = iolist_to_binary(join(re:split("xabcy","abc",[]))), + <<"ab">> = iolist_to_binary(join(re:split("ababc","abc",[trim]))), <<"ab:">> = iolist_to_binary(join(re:split("ababc","abc",[{parts, - 2}]))), - <<"ab:">> = iolist_to_binary(join(re:split("ababc","abc",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[trim]))), + 2}]))), + <<"ab:">> = iolist_to_binary(join(re:split("ababc","abc",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[]))), - <<"xbc">> = iolist_to_binary(join(re:split("xbc","abc",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[]))), + <<"xbc">> = iolist_to_binary(join(re:split("xbc","abc",[trim]))), <<"xbc">> = iolist_to_binary(join(re:split("xbc","abc",[{parts, - 2}]))), - <<"xbc">> = iolist_to_binary(join(re:split("xbc","abc",[]))), - <<"axc">> = iolist_to_binary(join(re:split("axc","abc",[trim]))), + 2}]))), + <<"xbc">> = iolist_to_binary(join(re:split("xbc","abc",[]))), + <<"axc">> = iolist_to_binary(join(re:split("axc","abc",[trim]))), <<"axc">> = iolist_to_binary(join(re:split("axc","abc",[{parts, - 2}]))), - <<"axc">> = iolist_to_binary(join(re:split("axc","abc",[]))), - <<"abx">> = iolist_to_binary(join(re:split("abx","abc",[trim]))), + 2}]))), + <<"axc">> = iolist_to_binary(join(re:split("axc","abc",[]))), + <<"abx">> = iolist_to_binary(join(re:split("abx","abc",[trim]))), <<"abx">> = iolist_to_binary(join(re:split("abx","abc",[{parts, - 2}]))), - <<"abx">> = iolist_to_binary(join(re:split("abx","abc",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","ab*c",[trim]))), + 2}]))), + <<"abx">> = iolist_to_binary(join(re:split("abx","abc",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","ab*c",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","ab*c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","ab*c",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","ab*bc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","ab*c",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","ab*bc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","ab*bc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","ab*bc",[]))), - <<"">> = iolist_to_binary(join(re:split("abbc","ab*bc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","ab*bc",[]))), + <<"">> = iolist_to_binary(join(re:split("abbc","ab*bc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abbc","ab*bc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abbc","ab*bc",[]))), - <<"">> = iolist_to_binary(join(re:split("abbbbc","ab*bc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abbc","ab*bc",[]))), + <<"">> = iolist_to_binary(join(re:split("abbbbc","ab*bc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abbbbc","ab*bc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abbbbc","ab*bc",[]))), - <<"">> = iolist_to_binary(join(re:split("abbbbc",".{1}",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abbbbc","ab*bc",[]))), + <<"">> = iolist_to_binary(join(re:split("abbbbc",".{1}",[trim]))), <<":bbbbc">> = iolist_to_binary(join(re:split("abbbbc",".{1}",[{parts, - 2}]))), - <<"::::::">> = iolist_to_binary(join(re:split("abbbbc",".{1}",[]))), - <<":bc">> = iolist_to_binary(join(re:split("abbbbc",".{3,4}",[trim]))), + 2}]))), + <<"::::::">> = iolist_to_binary(join(re:split("abbbbc",".{1}",[]))), + <<":bc">> = iolist_to_binary(join(re:split("abbbbc",".{3,4}",[trim]))), <<":bc">> = iolist_to_binary(join(re:split("abbbbc",".{3,4}",[{parts, - 2}]))), - <<":bc">> = iolist_to_binary(join(re:split("abbbbc",".{3,4}",[]))), - <<"">> = iolist_to_binary(join(re:split("abbbbc","ab{0,}bc",[trim]))), + 2}]))), + <<":bc">> = iolist_to_binary(join(re:split("abbbbc",".{3,4}",[]))), + <<"">> = iolist_to_binary(join(re:split("abbbbc","ab{0,}bc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{0,}bc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{0,}bc",[]))), - <<"">> = iolist_to_binary(join(re:split("abbc","ab+bc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{0,}bc",[]))), + <<"">> = iolist_to_binary(join(re:split("abbc","ab+bc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abbc","ab+bc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abbc","ab+bc",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab+bc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abbc","ab+bc",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab+bc",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab+bc",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab+bc",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","ab+bc",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab+bc",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","ab+bc",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","ab+bc",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","ab+bc",[]))), - <<"abq">> = iolist_to_binary(join(re:split("abq","ab+bc",[trim]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","ab+bc",[]))), + <<"abq">> = iolist_to_binary(join(re:split("abq","ab+bc",[trim]))), <<"abq">> = iolist_to_binary(join(re:split("abq","ab+bc",[{parts, - 2}]))), - <<"abq">> = iolist_to_binary(join(re:split("abq","ab+bc",[]))), + 2}]))), + <<"abq">> = iolist_to_binary(join(re:split("abq","ab+bc",[]))), ok. run15() -> - <<"">> = iolist_to_binary(join(re:split("abbbbc","ab+bc",[trim]))), + <<"">> = iolist_to_binary(join(re:split("abbbbc","ab+bc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abbbbc","ab+bc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abbbbc","ab+bc",[]))), - <<"">> = iolist_to_binary(join(re:split("abbbbc","ab{1,}bc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abbbbc","ab+bc",[]))), + <<"">> = iolist_to_binary(join(re:split("abbbbc","ab{1,}bc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{1,}bc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{1,}bc",[]))), - <<"">> = iolist_to_binary(join(re:split("abbbbc","ab{1,3}bc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{1,}bc",[]))), + <<"">> = iolist_to_binary(join(re:split("abbbbc","ab{1,3}bc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{1,3}bc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{1,3}bc",[]))), - <<"">> = iolist_to_binary(join(re:split("abbbbc","ab{3,4}bc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{1,3}bc",[]))), + <<"">> = iolist_to_binary(join(re:split("abbbbc","ab{3,4}bc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{3,4}bc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{3,4}bc",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{4,5}bc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abbbbc","ab{3,4}bc",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{4,5}bc",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{4,5}bc",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{4,5}bc",[]))), - <<"abq">> = iolist_to_binary(join(re:split("abq","ab{4,5}bc",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{4,5}bc",[]))), + <<"abq">> = iolist_to_binary(join(re:split("abq","ab{4,5}bc",[trim]))), <<"abq">> = iolist_to_binary(join(re:split("abq","ab{4,5}bc",[{parts, - 2}]))), - <<"abq">> = iolist_to_binary(join(re:split("abq","ab{4,5}bc",[]))), - <<"abbbbc">> = iolist_to_binary(join(re:split("abbbbc","ab{4,5}bc",[trim]))), + 2}]))), + <<"abq">> = iolist_to_binary(join(re:split("abq","ab{4,5}bc",[]))), + <<"abbbbc">> = iolist_to_binary(join(re:split("abbbbc","ab{4,5}bc",[trim]))), <<"abbbbc">> = iolist_to_binary(join(re:split("abbbbc","ab{4,5}bc",[{parts, - 2}]))), - <<"abbbbc">> = iolist_to_binary(join(re:split("abbbbc","ab{4,5}bc",[]))), - <<"">> = iolist_to_binary(join(re:split("abbc","ab?bc",[trim]))), + 2}]))), + <<"abbbbc">> = iolist_to_binary(join(re:split("abbbbc","ab{4,5}bc",[]))), + <<"">> = iolist_to_binary(join(re:split("abbc","ab?bc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abbc","ab?bc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abbc","ab?bc",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","ab?bc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abbc","ab?bc",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","ab?bc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","ab?bc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","ab?bc",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","ab{0,1}bc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","ab?bc",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","ab{0,1}bc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","ab{0,1}bc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","ab{0,1}bc",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","ab?c",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","ab{0,1}bc",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","ab?c",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","ab?c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","ab?c",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","ab{0,1}c",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","ab?c",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","ab{0,1}c",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","ab{0,1}c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","ab{0,1}c",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","^abc$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","ab{0,1}c",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","^abc$",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","^abc$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","^abc$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","^abc$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[]))), - <<"abbbbc">> = iolist_to_binary(join(re:split("abbbbc","^abc$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[]))), + <<"abbbbc">> = iolist_to_binary(join(re:split("abbbbc","^abc$",[trim]))), <<"abbbbc">> = iolist_to_binary(join(re:split("abbbbc","^abc$",[{parts, - 2}]))), - <<"abbbbc">> = iolist_to_binary(join(re:split("abbbbc","^abc$",[]))), - <<"abcc">> = iolist_to_binary(join(re:split("abcc","^abc$",[trim]))), + 2}]))), + <<"abbbbc">> = iolist_to_binary(join(re:split("abbbbc","^abc$",[]))), + <<"abcc">> = iolist_to_binary(join(re:split("abcc","^abc$",[trim]))), <<"abcc">> = iolist_to_binary(join(re:split("abcc","^abc$",[{parts, - 2}]))), - <<"abcc">> = iolist_to_binary(join(re:split("abcc","^abc$",[]))), - <<":c">> = iolist_to_binary(join(re:split("abcc","^abc",[trim]))), + 2}]))), + <<"abcc">> = iolist_to_binary(join(re:split("abcc","^abc$",[]))), + <<":c">> = iolist_to_binary(join(re:split("abcc","^abc",[trim]))), <<":c">> = iolist_to_binary(join(re:split("abcc","^abc",[{parts, - 2}]))), - <<":c">> = iolist_to_binary(join(re:split("abcc","^abc",[]))), - <<"a">> = iolist_to_binary(join(re:split("aabc","abc$",[trim]))), + 2}]))), + <<":c">> = iolist_to_binary(join(re:split("abcc","^abc",[]))), + <<"a">> = iolist_to_binary(join(re:split("aabc","abc$",[trim]))), <<"a:">> = iolist_to_binary(join(re:split("aabc","abc$",[{parts, - 2}]))), - <<"a:">> = iolist_to_binary(join(re:split("aabc","abc$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc$",[trim]))), + 2}]))), + <<"a:">> = iolist_to_binary(join(re:split("aabc","abc$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc$",[]))), - <<"a">> = iolist_to_binary(join(re:split("aabc","abc$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc$",[]))), + <<"a">> = iolist_to_binary(join(re:split("aabc","abc$",[trim]))), <<"a:">> = iolist_to_binary(join(re:split("aabc","abc$",[{parts, - 2}]))), - <<"a:">> = iolist_to_binary(join(re:split("aabc","abc$",[]))), - <<"aabcd">> = iolist_to_binary(join(re:split("aabcd","abc$",[trim]))), + 2}]))), + <<"a:">> = iolist_to_binary(join(re:split("aabc","abc$",[]))), + <<"aabcd">> = iolist_to_binary(join(re:split("aabcd","abc$",[trim]))), <<"aabcd">> = iolist_to_binary(join(re:split("aabcd","abc$",[{parts, - 2}]))), - <<"aabcd">> = iolist_to_binary(join(re:split("aabcd","abc$",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","^",[trim]))), + 2}]))), + <<"aabcd">> = iolist_to_binary(join(re:split("aabcd","abc$",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","^",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","^",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","^",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","$",[trim]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","^",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","$",[trim]))), <<"abc:">> = iolist_to_binary(join(re:split("abc","$",[{parts, - 2}]))), - <<"abc:">> = iolist_to_binary(join(re:split("abc","$",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","a.c",[trim]))), + 2}]))), + <<"abc:">> = iolist_to_binary(join(re:split("abc","$",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","a.c",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","a.c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","a.c",[]))), - <<"">> = iolist_to_binary(join(re:split("axc","a.c",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","a.c",[]))), + <<"">> = iolist_to_binary(join(re:split("axc","a.c",[trim]))), <<":">> = iolist_to_binary(join(re:split("axc","a.c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("axc","a.c",[]))), - <<"">> = iolist_to_binary(join(re:split("axyzc","a.*c",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("axc","a.c",[]))), + <<"">> = iolist_to_binary(join(re:split("axyzc","a.*c",[trim]))), <<":">> = iolist_to_binary(join(re:split("axyzc","a.*c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("axyzc","a.*c",[]))), - <<"">> = iolist_to_binary(join(re:split("abd","a[bc]d",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("axyzc","a.*c",[]))), + <<"">> = iolist_to_binary(join(re:split("abd","a[bc]d",[trim]))), <<":">> = iolist_to_binary(join(re:split("abd","a[bc]d",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abd","a[bc]d",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[bc]d",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abd","a[bc]d",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[bc]d",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[bc]d",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[bc]d",[]))), - <<"axyzd">> = iolist_to_binary(join(re:split("axyzd","a[bc]d",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[bc]d",[]))), + <<"axyzd">> = iolist_to_binary(join(re:split("axyzd","a[bc]d",[trim]))), <<"axyzd">> = iolist_to_binary(join(re:split("axyzd","a[bc]d",[{parts, - 2}]))), - <<"axyzd">> = iolist_to_binary(join(re:split("axyzd","a[bc]d",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","a[bc]d",[trim]))), + 2}]))), + <<"axyzd">> = iolist_to_binary(join(re:split("axyzd","a[bc]d",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","a[bc]d",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","a[bc]d",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","a[bc]d",[]))), - <<"">> = iolist_to_binary(join(re:split("ace","a[b-d]e",[trim]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","a[bc]d",[]))), + <<"">> = iolist_to_binary(join(re:split("ace","a[b-d]e",[trim]))), <<":">> = iolist_to_binary(join(re:split("ace","a[b-d]e",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ace","a[b-d]e",[]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ace","a[b-d]e",[]))), ok. run16() -> - <<"a">> = iolist_to_binary(join(re:split("aac","a[b-d]",[trim]))), + <<"a">> = iolist_to_binary(join(re:split("aac","a[b-d]",[trim]))), <<"a:">> = iolist_to_binary(join(re:split("aac","a[b-d]",[{parts, - 2}]))), - <<"a:">> = iolist_to_binary(join(re:split("aac","a[b-d]",[]))), - <<"">> = iolist_to_binary(join(re:split("a-","a[-b]",[trim]))), + 2}]))), + <<"a:">> = iolist_to_binary(join(re:split("aac","a[b-d]",[]))), + <<"">> = iolist_to_binary(join(re:split("a-","a[-b]",[trim]))), <<":">> = iolist_to_binary(join(re:split("a-","a[-b]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a-","a[-b]",[]))), - <<"">> = iolist_to_binary(join(re:split("a-","a[b-]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a-","a[-b]",[]))), + <<"">> = iolist_to_binary(join(re:split("a-","a[b-]",[trim]))), <<":">> = iolist_to_binary(join(re:split("a-","a[b-]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a-","a[b-]",[]))), - <<"">> = iolist_to_binary(join(re:split("a]","a]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a-","a[b-]",[]))), + <<"">> = iolist_to_binary(join(re:split("a]","a]",[trim]))), <<":">> = iolist_to_binary(join(re:split("a]","a]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a]","a]",[]))), - <<"">> = iolist_to_binary(join(re:split("a]b","a[]]b",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a]","a]",[]))), + <<"">> = iolist_to_binary(join(re:split("a]b","a[]]b",[trim]))), <<":">> = iolist_to_binary(join(re:split("a]b","a[]]b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a]b","a[]]b",[]))), - <<"">> = iolist_to_binary(join(re:split("aed","a[^bc]d",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a]b","a[]]b",[]))), + <<"">> = iolist_to_binary(join(re:split("aed","a[^bc]d",[trim]))), <<":">> = iolist_to_binary(join(re:split("aed","a[^bc]d",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aed","a[^bc]d",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^bc]d",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aed","a[^bc]d",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^bc]d",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^bc]d",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^bc]d",[]))), - <<"abd">> = iolist_to_binary(join(re:split("abd","a[^bc]d",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^bc]d",[]))), + <<"abd">> = iolist_to_binary(join(re:split("abd","a[^bc]d",[trim]))), <<"abd">> = iolist_to_binary(join(re:split("abd","a[^bc]d",[{parts, - 2}]))), - <<"abd">> = iolist_to_binary(join(re:split("abd","a[^bc]d",[]))), - <<"abd">> = iolist_to_binary(join(re:split("abd","a[^bc]d",[trim]))), + 2}]))), + <<"abd">> = iolist_to_binary(join(re:split("abd","a[^bc]d",[]))), + <<"abd">> = iolist_to_binary(join(re:split("abd","a[^bc]d",[trim]))), <<"abd">> = iolist_to_binary(join(re:split("abd","a[^bc]d",[{parts, - 2}]))), - <<"abd">> = iolist_to_binary(join(re:split("abd","a[^bc]d",[]))), - <<"">> = iolist_to_binary(join(re:split("adc","a[^-b]c",[trim]))), + 2}]))), + <<"abd">> = iolist_to_binary(join(re:split("abd","a[^bc]d",[]))), + <<"">> = iolist_to_binary(join(re:split("adc","a[^-b]c",[trim]))), <<":">> = iolist_to_binary(join(re:split("adc","a[^-b]c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("adc","a[^-b]c",[]))), - <<"">> = iolist_to_binary(join(re:split("adc","a[^]b]c",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("adc","a[^-b]c",[]))), + <<"">> = iolist_to_binary(join(re:split("adc","a[^]b]c",[trim]))), <<":">> = iolist_to_binary(join(re:split("adc","a[^]b]c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("adc","a[^]b]c",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^]b]c",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("adc","a[^]b]c",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^]b]c",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^]b]c",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^]b]c",[]))), - <<"">> = iolist_to_binary(join(re:split("a-c","a[^]b]c",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^]b]c",[]))), + <<"">> = iolist_to_binary(join(re:split("a-c","a[^]b]c",[trim]))), <<":">> = iolist_to_binary(join(re:split("a-c","a[^]b]c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a-c","a[^]b]c",[]))), - <<"a]c">> = iolist_to_binary(join(re:split("a]c","a[^]b]c",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a-c","a[^]b]c",[]))), + <<"a]c">> = iolist_to_binary(join(re:split("a]c","a[^]b]c",[trim]))), <<"a]c">> = iolist_to_binary(join(re:split("a]c","a[^]b]c",[{parts, - 2}]))), - <<"a]c">> = iolist_to_binary(join(re:split("a]c","a[^]b]c",[]))), - <<":-">> = iolist_to_binary(join(re:split("a-","\\ba\\b",[trim]))), + 2}]))), + <<"a]c">> = iolist_to_binary(join(re:split("a]c","a[^]b]c",[]))), + <<":-">> = iolist_to_binary(join(re:split("a-","\\ba\\b",[trim]))), <<":-">> = iolist_to_binary(join(re:split("a-","\\ba\\b",[{parts, - 2}]))), - <<":-">> = iolist_to_binary(join(re:split("a-","\\ba\\b",[]))), - <<"-">> = iolist_to_binary(join(re:split("-a","\\ba\\b",[trim]))), + 2}]))), + <<":-">> = iolist_to_binary(join(re:split("a-","\\ba\\b",[]))), + <<"-">> = iolist_to_binary(join(re:split("-a","\\ba\\b",[trim]))), <<"-:">> = iolist_to_binary(join(re:split("-a","\\ba\\b",[{parts, - 2}]))), - <<"-:">> = iolist_to_binary(join(re:split("-a","\\ba\\b",[]))), - <<"-:-">> = iolist_to_binary(join(re:split("-a-","\\ba\\b",[trim]))), + 2}]))), + <<"-:">> = iolist_to_binary(join(re:split("-a","\\ba\\b",[]))), + <<"-:-">> = iolist_to_binary(join(re:split("-a-","\\ba\\b",[trim]))), <<"-:-">> = iolist_to_binary(join(re:split("-a-","\\ba\\b",[{parts, - 2}]))), - <<"-:-">> = iolist_to_binary(join(re:split("-a-","\\ba\\b",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\by\\b",[trim]))), + 2}]))), + <<"-:-">> = iolist_to_binary(join(re:split("-a-","\\ba\\b",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\by\\b",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\by\\b",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\by\\b",[]))), - <<"xy">> = iolist_to_binary(join(re:split("xy","\\by\\b",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\by\\b",[]))), + <<"xy">> = iolist_to_binary(join(re:split("xy","\\by\\b",[trim]))), <<"xy">> = iolist_to_binary(join(re:split("xy","\\by\\b",[{parts, - 2}]))), - <<"xy">> = iolist_to_binary(join(re:split("xy","\\by\\b",[]))), - <<"yz">> = iolist_to_binary(join(re:split("yz","\\by\\b",[trim]))), + 2}]))), + <<"xy">> = iolist_to_binary(join(re:split("xy","\\by\\b",[]))), + <<"yz">> = iolist_to_binary(join(re:split("yz","\\by\\b",[trim]))), <<"yz">> = iolist_to_binary(join(re:split("yz","\\by\\b",[{parts, - 2}]))), - <<"yz">> = iolist_to_binary(join(re:split("yz","\\by\\b",[]))), - <<"xyz">> = iolist_to_binary(join(re:split("xyz","\\by\\b",[trim]))), + 2}]))), + <<"yz">> = iolist_to_binary(join(re:split("yz","\\by\\b",[]))), + <<"xyz">> = iolist_to_binary(join(re:split("xyz","\\by\\b",[trim]))), <<"xyz">> = iolist_to_binary(join(re:split("xyz","\\by\\b",[{parts, - 2}]))), - <<"xyz">> = iolist_to_binary(join(re:split("xyz","\\by\\b",[]))), - <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","\\Ba\\B",[trim]))), + 2}]))), + <<"xyz">> = iolist_to_binary(join(re:split("xyz","\\by\\b",[]))), + <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","\\Ba\\B",[trim]))), <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","\\Ba\\B",[{parts, - 2}]))), - <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","\\Ba\\B",[]))), - <<"a-">> = iolist_to_binary(join(re:split("a-","\\Ba\\B",[trim]))), + 2}]))), + <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","\\Ba\\B",[]))), + <<"a-">> = iolist_to_binary(join(re:split("a-","\\Ba\\B",[trim]))), <<"a-">> = iolist_to_binary(join(re:split("a-","\\Ba\\B",[{parts, - 2}]))), - <<"a-">> = iolist_to_binary(join(re:split("a-","\\Ba\\B",[]))), - <<"-a">> = iolist_to_binary(join(re:split("-a","\\Ba\\B",[trim]))), + 2}]))), + <<"a-">> = iolist_to_binary(join(re:split("a-","\\Ba\\B",[]))), + <<"-a">> = iolist_to_binary(join(re:split("-a","\\Ba\\B",[trim]))), <<"-a">> = iolist_to_binary(join(re:split("-a","\\Ba\\B",[{parts, - 2}]))), - <<"-a">> = iolist_to_binary(join(re:split("-a","\\Ba\\B",[]))), - <<"-a-">> = iolist_to_binary(join(re:split("-a-","\\Ba\\B",[trim]))), + 2}]))), + <<"-a">> = iolist_to_binary(join(re:split("-a","\\Ba\\B",[]))), + <<"-a-">> = iolist_to_binary(join(re:split("-a-","\\Ba\\B",[trim]))), <<"-a-">> = iolist_to_binary(join(re:split("-a-","\\Ba\\B",[{parts, - 2}]))), - <<"-a-">> = iolist_to_binary(join(re:split("-a-","\\Ba\\B",[]))), - <<"x">> = iolist_to_binary(join(re:split("xy","\\By\\b",[trim]))), + 2}]))), + <<"-a-">> = iolist_to_binary(join(re:split("-a-","\\Ba\\B",[]))), + <<"x">> = iolist_to_binary(join(re:split("xy","\\By\\b",[trim]))), <<"x:">> = iolist_to_binary(join(re:split("xy","\\By\\b",[{parts, - 2}]))), - <<"x:">> = iolist_to_binary(join(re:split("xy","\\By\\b",[]))), - <<":z">> = iolist_to_binary(join(re:split("yz","\\by\\B",[trim]))), + 2}]))), + <<"x:">> = iolist_to_binary(join(re:split("xy","\\By\\b",[]))), + <<":z">> = iolist_to_binary(join(re:split("yz","\\by\\B",[trim]))), <<":z">> = iolist_to_binary(join(re:split("yz","\\by\\B",[{parts, - 2}]))), - <<":z">> = iolist_to_binary(join(re:split("yz","\\by\\B",[]))), - <<"x:z">> = iolist_to_binary(join(re:split("xyz","\\By\\B",[trim]))), + 2}]))), + <<":z">> = iolist_to_binary(join(re:split("yz","\\by\\B",[]))), + <<"x:z">> = iolist_to_binary(join(re:split("xyz","\\By\\B",[trim]))), <<"x:z">> = iolist_to_binary(join(re:split("xyz","\\By\\B",[{parts, - 2}]))), - <<"x:z">> = iolist_to_binary(join(re:split("xyz","\\By\\B",[]))), - <<"">> = iolist_to_binary(join(re:split("a","\\w",[trim]))), + 2}]))), + <<"x:z">> = iolist_to_binary(join(re:split("xyz","\\By\\B",[]))), + <<"">> = iolist_to_binary(join(re:split("a","\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("a","\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("-","\\W",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("-","\\W",[trim]))), <<":">> = iolist_to_binary(join(re:split("-","\\W",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("-","\\W",[]))), - <<"::::Failers">> = iolist_to_binary(join(re:split("*** Failers","\\W",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("-","\\W",[]))), + <<"::::Failers">> = iolist_to_binary(join(re:split("*** Failers","\\W",[trim]))), <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\W",[{parts, - 2}]))), - <<"::::Failers">> = iolist_to_binary(join(re:split("*** Failers","\\W",[]))), - <<"">> = iolist_to_binary(join(re:split("-","\\W",[trim]))), + 2}]))), + <<"::::Failers">> = iolist_to_binary(join(re:split("*** Failers","\\W",[]))), + <<"">> = iolist_to_binary(join(re:split("-","\\W",[trim]))), <<":">> = iolist_to_binary(join(re:split("-","\\W",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("-","\\W",[]))), - <<"a">> = iolist_to_binary(join(re:split("a","\\W",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("-","\\W",[]))), + <<"a">> = iolist_to_binary(join(re:split("a","\\W",[trim]))), <<"a">> = iolist_to_binary(join(re:split("a","\\W",[{parts, - 2}]))), - <<"a">> = iolist_to_binary(join(re:split("a","\\W",[]))), - <<"">> = iolist_to_binary(join(re:split("a b","a\\sb",[trim]))), + 2}]))), + <<"a">> = iolist_to_binary(join(re:split("a","\\W",[]))), + <<"">> = iolist_to_binary(join(re:split("a b","a\\sb",[trim]))), <<":">> = iolist_to_binary(join(re:split("a b","a\\sb",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a b","a\\sb",[]))), - <<"">> = iolist_to_binary(join(re:split("a-b","a\\Sb",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a b","a\\sb",[]))), + <<"">> = iolist_to_binary(join(re:split("a-b","a\\Sb",[trim]))), <<":">> = iolist_to_binary(join(re:split("a-b","a\\Sb",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a-b","a\\Sb",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a\\Sb",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a-b","a\\Sb",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a\\Sb",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a\\Sb",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a\\Sb",[]))), - <<"">> = iolist_to_binary(join(re:split("a-b","a\\Sb",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a\\Sb",[]))), + <<"">> = iolist_to_binary(join(re:split("a-b","a\\Sb",[trim]))), <<":">> = iolist_to_binary(join(re:split("a-b","a\\Sb",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a-b","a\\Sb",[]))), - <<"a b">> = iolist_to_binary(join(re:split("a b","a\\Sb",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a-b","a\\Sb",[]))), + <<"a b">> = iolist_to_binary(join(re:split("a b","a\\Sb",[trim]))), <<"a b">> = iolist_to_binary(join(re:split("a b","a\\Sb",[{parts, - 2}]))), - <<"a b">> = iolist_to_binary(join(re:split("a b","a\\Sb",[]))), - <<"">> = iolist_to_binary(join(re:split("1","\\d",[trim]))), + 2}]))), + <<"a b">> = iolist_to_binary(join(re:split("a b","a\\Sb",[]))), + <<"">> = iolist_to_binary(join(re:split("1","\\d",[trim]))), <<":">> = iolist_to_binary(join(re:split("1","\\d",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("1","\\d",[]))), - <<"">> = iolist_to_binary(join(re:split("-","\\D",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("1","\\d",[]))), + <<"">> = iolist_to_binary(join(re:split("-","\\D",[trim]))), <<":">> = iolist_to_binary(join(re:split("-","\\D",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("-","\\D",[]))), - <<"">> = iolist_to_binary(join(re:split("*** Failers","\\D",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("-","\\D",[]))), + <<"">> = iolist_to_binary(join(re:split("*** Failers","\\D",[trim]))), <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\D",[{parts, - 2}]))), - <<":::::::::::">> = iolist_to_binary(join(re:split("*** Failers","\\D",[]))), - <<"">> = iolist_to_binary(join(re:split("-","\\D",[trim]))), + 2}]))), + <<":::::::::::">> = iolist_to_binary(join(re:split("*** Failers","\\D",[]))), + <<"">> = iolist_to_binary(join(re:split("-","\\D",[trim]))), <<":">> = iolist_to_binary(join(re:split("-","\\D",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("-","\\D",[]))), - <<"1">> = iolist_to_binary(join(re:split("1","\\D",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("-","\\D",[]))), + <<"1">> = iolist_to_binary(join(re:split("1","\\D",[trim]))), <<"1">> = iolist_to_binary(join(re:split("1","\\D",[{parts, - 2}]))), - <<"1">> = iolist_to_binary(join(re:split("1","\\D",[]))), + 2}]))), + <<"1">> = iolist_to_binary(join(re:split("1","\\D",[]))), ok. run17() -> - <<"">> = iolist_to_binary(join(re:split("a","[\\w]",[trim]))), + <<"">> = iolist_to_binary(join(re:split("a","[\\w]",[trim]))), <<":">> = iolist_to_binary(join(re:split("a","[\\w]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","[\\w]",[]))), - <<"">> = iolist_to_binary(join(re:split("-","[\\W]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","[\\w]",[]))), + <<"">> = iolist_to_binary(join(re:split("-","[\\W]",[trim]))), <<":">> = iolist_to_binary(join(re:split("-","[\\W]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("-","[\\W]",[]))), - <<"::::Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\W]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("-","[\\W]",[]))), + <<"::::Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\W]",[trim]))), <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\W]",[{parts, - 2}]))), - <<"::::Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\W]",[]))), - <<"">> = iolist_to_binary(join(re:split("-","[\\W]",[trim]))), + 2}]))), + <<"::::Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\W]",[]))), + <<"">> = iolist_to_binary(join(re:split("-","[\\W]",[trim]))), <<":">> = iolist_to_binary(join(re:split("-","[\\W]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("-","[\\W]",[]))), - <<"a">> = iolist_to_binary(join(re:split("a","[\\W]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("-","[\\W]",[]))), + <<"a">> = iolist_to_binary(join(re:split("a","[\\W]",[trim]))), <<"a">> = iolist_to_binary(join(re:split("a","[\\W]",[{parts, - 2}]))), - <<"a">> = iolist_to_binary(join(re:split("a","[\\W]",[]))), - <<"">> = iolist_to_binary(join(re:split("a b","a[\\s]b",[trim]))), + 2}]))), + <<"a">> = iolist_to_binary(join(re:split("a","[\\W]",[]))), + <<"">> = iolist_to_binary(join(re:split("a b","a[\\s]b",[trim]))), <<":">> = iolist_to_binary(join(re:split("a b","a[\\s]b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a b","a[\\s]b",[]))), - <<"">> = iolist_to_binary(join(re:split("a-b","a[\\S]b",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a b","a[\\s]b",[]))), + <<"">> = iolist_to_binary(join(re:split("a-b","a[\\S]b",[trim]))), <<":">> = iolist_to_binary(join(re:split("a-b","a[\\S]b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a-b","a[\\S]b",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[\\S]b",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a-b","a[\\S]b",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[\\S]b",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[\\S]b",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[\\S]b",[]))), - <<"">> = iolist_to_binary(join(re:split("a-b","a[\\S]b",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[\\S]b",[]))), + <<"">> = iolist_to_binary(join(re:split("a-b","a[\\S]b",[trim]))), <<":">> = iolist_to_binary(join(re:split("a-b","a[\\S]b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a-b","a[\\S]b",[]))), - <<"a b">> = iolist_to_binary(join(re:split("a b","a[\\S]b",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a-b","a[\\S]b",[]))), + <<"a b">> = iolist_to_binary(join(re:split("a b","a[\\S]b",[trim]))), <<"a b">> = iolist_to_binary(join(re:split("a b","a[\\S]b",[{parts, - 2}]))), - <<"a b">> = iolist_to_binary(join(re:split("a b","a[\\S]b",[]))), - <<"">> = iolist_to_binary(join(re:split("1","[\\d]",[trim]))), + 2}]))), + <<"a b">> = iolist_to_binary(join(re:split("a b","a[\\S]b",[]))), + <<"">> = iolist_to_binary(join(re:split("1","[\\d]",[trim]))), <<":">> = iolist_to_binary(join(re:split("1","[\\d]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("1","[\\d]",[]))), - <<"">> = iolist_to_binary(join(re:split("-","[\\D]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("1","[\\d]",[]))), + <<"">> = iolist_to_binary(join(re:split("-","[\\D]",[trim]))), <<":">> = iolist_to_binary(join(re:split("-","[\\D]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("-","[\\D]",[]))), - <<"">> = iolist_to_binary(join(re:split("*** Failers","[\\D]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("-","[\\D]",[]))), + <<"">> = iolist_to_binary(join(re:split("*** Failers","[\\D]",[trim]))), <<":** Failers">> = iolist_to_binary(join(re:split("*** Failers","[\\D]",[{parts, - 2}]))), - <<":::::::::::">> = iolist_to_binary(join(re:split("*** Failers","[\\D]",[]))), - <<"">> = iolist_to_binary(join(re:split("-","[\\D]",[trim]))), + 2}]))), + <<":::::::::::">> = iolist_to_binary(join(re:split("*** Failers","[\\D]",[]))), + <<"">> = iolist_to_binary(join(re:split("-","[\\D]",[trim]))), <<":">> = iolist_to_binary(join(re:split("-","[\\D]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("-","[\\D]",[]))), - <<"1">> = iolist_to_binary(join(re:split("1","[\\D]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("-","[\\D]",[]))), + <<"1">> = iolist_to_binary(join(re:split("1","[\\D]",[trim]))), <<"1">> = iolist_to_binary(join(re:split("1","[\\D]",[{parts, - 2}]))), - <<"1">> = iolist_to_binary(join(re:split("1","[\\D]",[]))), - <<":c">> = iolist_to_binary(join(re:split("abc","ab|cd",[trim]))), + 2}]))), + <<"1">> = iolist_to_binary(join(re:split("1","[\\D]",[]))), + <<":c">> = iolist_to_binary(join(re:split("abc","ab|cd",[trim]))), <<":c">> = iolist_to_binary(join(re:split("abc","ab|cd",[{parts, - 2}]))), - <<":c">> = iolist_to_binary(join(re:split("abc","ab|cd",[]))), - <<"">> = iolist_to_binary(join(re:split("abcd","ab|cd",[trim]))), + 2}]))), + <<":c">> = iolist_to_binary(join(re:split("abc","ab|cd",[]))), + <<"">> = iolist_to_binary(join(re:split("abcd","ab|cd",[trim]))), <<":cd">> = iolist_to_binary(join(re:split("abcd","ab|cd",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("abcd","ab|cd",[]))), - <<"d">> = iolist_to_binary(join(re:split("def","()ef",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("abcd","ab|cd",[]))), + <<"d">> = iolist_to_binary(join(re:split("def","()ef",[trim]))), <<"d::">> = iolist_to_binary(join(re:split("def","()ef",[{parts, - 2}]))), - <<"d::">> = iolist_to_binary(join(re:split("def","()ef",[]))), - <<"">> = iolist_to_binary(join(re:split("a(b","a\\(b",[trim]))), + 2}]))), + <<"d::">> = iolist_to_binary(join(re:split("def","()ef",[]))), + <<"">> = iolist_to_binary(join(re:split("a(b","a\\(b",[trim]))), <<":">> = iolist_to_binary(join(re:split("a(b","a\\(b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a(b","a\\(b",[]))), - <<"">> = iolist_to_binary(join(re:split("ab","a\\(*b",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a(b","a\\(b",[]))), + <<"">> = iolist_to_binary(join(re:split("ab","a\\(*b",[trim]))), <<":">> = iolist_to_binary(join(re:split("ab","a\\(*b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ab","a\\(*b",[]))), - <<"">> = iolist_to_binary(join(re:split("a((b","a\\(*b",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ab","a\\(*b",[]))), + <<"">> = iolist_to_binary(join(re:split("a((b","a\\(*b",[trim]))), <<":">> = iolist_to_binary(join(re:split("a((b","a\\(*b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a((b","a\\(*b",[]))), - <<"a">> = iolist_to_binary(join(re:split("a","a\\\\b",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a((b","a\\(*b",[]))), + <<"a">> = iolist_to_binary(join(re:split("a","a\\\\b",[trim]))), <<"a">> = iolist_to_binary(join(re:split("a","a\\\\b",[{parts, - 2}]))), - <<"a">> = iolist_to_binary(join(re:split("a","a\\\\b",[]))), - <<":a:a:bc">> = iolist_to_binary(join(re:split("abc","((a))",[trim]))), + 2}]))), + <<"a">> = iolist_to_binary(join(re:split("a","a\\\\b",[]))), + <<":a:a:bc">> = iolist_to_binary(join(re:split("abc","((a))",[trim]))), <<":a:a:bc">> = iolist_to_binary(join(re:split("abc","((a))",[{parts, - 2}]))), - <<":a:a:bc">> = iolist_to_binary(join(re:split("abc","((a))",[]))), - <<":a:c">> = iolist_to_binary(join(re:split("abc","(a)b(c)",[trim]))), + 2}]))), + <<":a:a:bc">> = iolist_to_binary(join(re:split("abc","((a))",[]))), + <<":a:c">> = iolist_to_binary(join(re:split("abc","(a)b(c)",[trim]))), <<":a:c:">> = iolist_to_binary(join(re:split("abc","(a)b(c)",[{parts, - 2}]))), - <<":a:c:">> = iolist_to_binary(join(re:split("abc","(a)b(c)",[]))), - <<"aabb">> = iolist_to_binary(join(re:split("aabbabc","a+b+c",[trim]))), + 2}]))), + <<":a:c:">> = iolist_to_binary(join(re:split("abc","(a)b(c)",[]))), + <<"aabb">> = iolist_to_binary(join(re:split("aabbabc","a+b+c",[trim]))), <<"aabb:">> = iolist_to_binary(join(re:split("aabbabc","a+b+c",[{parts, - 2}]))), - <<"aabb:">> = iolist_to_binary(join(re:split("aabbabc","a+b+c",[]))), - <<"aabb">> = iolist_to_binary(join(re:split("aabbabc","a{1,}b{1,}c",[trim]))), + 2}]))), + <<"aabb:">> = iolist_to_binary(join(re:split("aabbabc","a+b+c",[]))), + <<"aabb">> = iolist_to_binary(join(re:split("aabbabc","a{1,}b{1,}c",[trim]))), <<"aabb:">> = iolist_to_binary(join(re:split("aabbabc","a{1,}b{1,}c",[{parts, - 2}]))), - <<"aabb:">> = iolist_to_binary(join(re:split("aabbabc","a{1,}b{1,}c",[]))), - <<"">> = iolist_to_binary(join(re:split("abcabc","a.+?c",[trim]))), + 2}]))), + <<"aabb:">> = iolist_to_binary(join(re:split("aabbabc","a{1,}b{1,}c",[]))), + <<"">> = iolist_to_binary(join(re:split("abcabc","a.+?c",[trim]))), <<":abc">> = iolist_to_binary(join(re:split("abcabc","a.+?c",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("abcabc","a.+?c",[]))), - <<":b">> = iolist_to_binary(join(re:split("ab","(a+|b)*",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("abcabc","a.+?c",[]))), + <<":b">> = iolist_to_binary(join(re:split("ab","(a+|b)*",[trim]))), <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b)*",[{parts, - 2}]))), - <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b)*",[]))), - <<":b">> = iolist_to_binary(join(re:split("ab","(a+|b){0,}",[trim]))), + 2}]))), + <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b)*",[]))), + <<":b">> = iolist_to_binary(join(re:split("ab","(a+|b){0,}",[trim]))), <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b){0,}",[{parts, - 2}]))), - <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b){0,}",[]))), - <<":b">> = iolist_to_binary(join(re:split("ab","(a+|b)+",[trim]))), + 2}]))), + <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b){0,}",[]))), + <<":b">> = iolist_to_binary(join(re:split("ab","(a+|b)+",[trim]))), <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b)+",[{parts, - 2}]))), - <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b)+",[]))), + 2}]))), + <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b)+",[]))), ok. run18() -> - <<":b">> = iolist_to_binary(join(re:split("ab","(a+|b){1,}",[trim]))), + <<":b">> = iolist_to_binary(join(re:split("ab","(a+|b){1,}",[trim]))), <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b){1,}",[{parts, - 2}]))), - <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b){1,}",[]))), - <<":a::b">> = iolist_to_binary(join(re:split("ab","(a+|b)?",[trim]))), + 2}]))), + <<":b:">> = iolist_to_binary(join(re:split("ab","(a+|b){1,}",[]))), + <<":a::b">> = iolist_to_binary(join(re:split("ab","(a+|b)?",[trim]))), <<":a:b">> = iolist_to_binary(join(re:split("ab","(a+|b)?",[{parts, - 2}]))), - <<":a::b:">> = iolist_to_binary(join(re:split("ab","(a+|b)?",[]))), - <<":a::b">> = iolist_to_binary(join(re:split("ab","(a+|b){0,1}",[trim]))), + 2}]))), + <<":a::b:">> = iolist_to_binary(join(re:split("ab","(a+|b)?",[]))), + <<":a::b">> = iolist_to_binary(join(re:split("ab","(a+|b){0,1}",[trim]))), <<":a:b">> = iolist_to_binary(join(re:split("ab","(a+|b){0,1}",[{parts, - 2}]))), - <<":a::b:">> = iolist_to_binary(join(re:split("ab","(a+|b){0,1}",[]))), - <<"">> = iolist_to_binary(join(re:split("cde","[^ab]*",[trim]))), + 2}]))), + <<":a::b:">> = iolist_to_binary(join(re:split("ab","(a+|b){0,1}",[]))), + <<"">> = iolist_to_binary(join(re:split("cde","[^ab]*",[trim]))), <<":">> = iolist_to_binary(join(re:split("cde","[^ab]*",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("cde","[^ab]*",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("cde","[^ab]*",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[]))), - <<"b">> = iolist_to_binary(join(re:split("b","abc",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[]))), + <<"b">> = iolist_to_binary(join(re:split("b","abc",[trim]))), <<"b">> = iolist_to_binary(join(re:split("b","abc",[{parts, - 2}]))), - <<"b">> = iolist_to_binary(join(re:split("b","abc",[]))), - <<":c">> = iolist_to_binary(join(re:split("abbbcd","([abc])*d",[trim]))), + 2}]))), + <<"b">> = iolist_to_binary(join(re:split("b","abc",[]))), + <<":c">> = iolist_to_binary(join(re:split("abbbcd","([abc])*d",[trim]))), <<":c:">> = iolist_to_binary(join(re:split("abbbcd","([abc])*d",[{parts, - 2}]))), - <<":c:">> = iolist_to_binary(join(re:split("abbbcd","([abc])*d",[]))), - <<":a">> = iolist_to_binary(join(re:split("abcd","([abc])*bcd",[trim]))), + 2}]))), + <<":c:">> = iolist_to_binary(join(re:split("abbbcd","([abc])*d",[]))), + <<":a">> = iolist_to_binary(join(re:split("abcd","([abc])*bcd",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("abcd","([abc])*bcd",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("abcd","([abc])*bcd",[]))), - <<"">> = iolist_to_binary(join(re:split("e","a|b|c|d|e",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("abcd","([abc])*bcd",[]))), + <<"">> = iolist_to_binary(join(re:split("e","a|b|c|d|e",[trim]))), <<":">> = iolist_to_binary(join(re:split("e","a|b|c|d|e",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("e","a|b|c|d|e",[]))), - <<":e">> = iolist_to_binary(join(re:split("ef","(a|b|c|d|e)f",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("e","a|b|c|d|e",[]))), + <<":e">> = iolist_to_binary(join(re:split("ef","(a|b|c|d|e)f",[trim]))), <<":e:">> = iolist_to_binary(join(re:split("ef","(a|b|c|d|e)f",[{parts, - 2}]))), - <<":e:">> = iolist_to_binary(join(re:split("ef","(a|b|c|d|e)f",[]))), - <<"">> = iolist_to_binary(join(re:split("abcdefg","abcd*efg",[trim]))), + 2}]))), + <<":e:">> = iolist_to_binary(join(re:split("ef","(a|b|c|d|e)f",[]))), + <<"">> = iolist_to_binary(join(re:split("abcdefg","abcd*efg",[trim]))), <<":">> = iolist_to_binary(join(re:split("abcdefg","abcd*efg",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abcdefg","abcd*efg",[]))), - <<"x:y:z">> = iolist_to_binary(join(re:split("xabyabbbz","ab*",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abcdefg","abcd*efg",[]))), + <<"x:y:z">> = iolist_to_binary(join(re:split("xabyabbbz","ab*",[trim]))), <<"x:yabbbz">> = iolist_to_binary(join(re:split("xabyabbbz","ab*",[{parts, - 2}]))), - <<"x:y:z">> = iolist_to_binary(join(re:split("xabyabbbz","ab*",[]))), - <<"x:y:z">> = iolist_to_binary(join(re:split("xayabbbz","ab*",[trim]))), + 2}]))), + <<"x:y:z">> = iolist_to_binary(join(re:split("xabyabbbz","ab*",[]))), + <<"x:y:z">> = iolist_to_binary(join(re:split("xayabbbz","ab*",[trim]))), <<"x:yabbbz">> = iolist_to_binary(join(re:split("xayabbbz","ab*",[{parts, - 2}]))), - <<"x:y:z">> = iolist_to_binary(join(re:split("xayabbbz","ab*",[]))), - <<"ab:cd">> = iolist_to_binary(join(re:split("abcde","(ab|cd)e",[trim]))), + 2}]))), + <<"x:y:z">> = iolist_to_binary(join(re:split("xayabbbz","ab*",[]))), + <<"ab:cd">> = iolist_to_binary(join(re:split("abcde","(ab|cd)e",[trim]))), <<"ab:cd:">> = iolist_to_binary(join(re:split("abcde","(ab|cd)e",[{parts, - 2}]))), - <<"ab:cd:">> = iolist_to_binary(join(re:split("abcde","(ab|cd)e",[]))), - <<"">> = iolist_to_binary(join(re:split("hij","[abhgefdc]ij",[trim]))), + 2}]))), + <<"ab:cd:">> = iolist_to_binary(join(re:split("abcde","(ab|cd)e",[]))), + <<"">> = iolist_to_binary(join(re:split("hij","[abhgefdc]ij",[trim]))), <<":">> = iolist_to_binary(join(re:split("hij","[abhgefdc]ij",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("hij","[abhgefdc]ij",[]))), - <<"abcd">> = iolist_to_binary(join(re:split("abcdef","(abc|)ef",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("hij","[abhgefdc]ij",[]))), + <<"abcd">> = iolist_to_binary(join(re:split("abcdef","(abc|)ef",[trim]))), <<"abcd::">> = iolist_to_binary(join(re:split("abcdef","(abc|)ef",[{parts, - 2}]))), - <<"abcd::">> = iolist_to_binary(join(re:split("abcdef","(abc|)ef",[]))), - <<"a:b">> = iolist_to_binary(join(re:split("abcd","(a|b)c*d",[trim]))), + 2}]))), + <<"abcd::">> = iolist_to_binary(join(re:split("abcdef","(abc|)ef",[]))), + <<"a:b">> = iolist_to_binary(join(re:split("abcd","(a|b)c*d",[trim]))), <<"a:b:">> = iolist_to_binary(join(re:split("abcd","(a|b)c*d",[{parts, - 2}]))), - <<"a:b:">> = iolist_to_binary(join(re:split("abcd","(a|b)c*d",[]))), - <<":a">> = iolist_to_binary(join(re:split("abc","(ab|ab*)bc",[trim]))), + 2}]))), + <<"a:b:">> = iolist_to_binary(join(re:split("abcd","(a|b)c*d",[]))), + <<":a">> = iolist_to_binary(join(re:split("abc","(ab|ab*)bc",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("abc","(ab|ab*)bc",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("abc","(ab|ab*)bc",[]))), - <<":bc">> = iolist_to_binary(join(re:split("abc","a([bc]*)c*",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("abc","(ab|ab*)bc",[]))), + <<":bc">> = iolist_to_binary(join(re:split("abc","a([bc]*)c*",[trim]))), <<":bc:">> = iolist_to_binary(join(re:split("abc","a([bc]*)c*",[{parts, - 2}]))), - <<":bc:">> = iolist_to_binary(join(re:split("abc","a([bc]*)c*",[]))), - <<":bc:d">> = iolist_to_binary(join(re:split("abcd","a([bc]*)(c*d)",[trim]))), + 2}]))), + <<":bc:">> = iolist_to_binary(join(re:split("abc","a([bc]*)c*",[]))), + <<":bc:d">> = iolist_to_binary(join(re:split("abcd","a([bc]*)(c*d)",[trim]))), <<":bc:d:">> = iolist_to_binary(join(re:split("abcd","a([bc]*)(c*d)",[{parts, - 2}]))), - <<":bc:d:">> = iolist_to_binary(join(re:split("abcd","a([bc]*)(c*d)",[]))), + 2}]))), + <<":bc:d:">> = iolist_to_binary(join(re:split("abcd","a([bc]*)(c*d)",[]))), ok. run19() -> - <<":bc:d">> = iolist_to_binary(join(re:split("abcd","a([bc]+)(c*d)",[trim]))), + <<":bc:d">> = iolist_to_binary(join(re:split("abcd","a([bc]+)(c*d)",[trim]))), <<":bc:d:">> = iolist_to_binary(join(re:split("abcd","a([bc]+)(c*d)",[{parts, - 2}]))), - <<":bc:d:">> = iolist_to_binary(join(re:split("abcd","a([bc]+)(c*d)",[]))), - <<":b:cd">> = iolist_to_binary(join(re:split("abcd","a([bc]*)(c+d)",[trim]))), + 2}]))), + <<":bc:d:">> = iolist_to_binary(join(re:split("abcd","a([bc]+)(c*d)",[]))), + <<":b:cd">> = iolist_to_binary(join(re:split("abcd","a([bc]*)(c+d)",[trim]))), <<":b:cd:">> = iolist_to_binary(join(re:split("abcd","a([bc]*)(c+d)",[{parts, - 2}]))), - <<":b:cd:">> = iolist_to_binary(join(re:split("abcd","a([bc]*)(c+d)",[]))), - <<"">> = iolist_to_binary(join(re:split("adcdcde","a[bcd]*dcdcde",[trim]))), + 2}]))), + <<":b:cd:">> = iolist_to_binary(join(re:split("abcd","a([bc]*)(c+d)",[]))), + <<"">> = iolist_to_binary(join(re:split("adcdcde","a[bcd]*dcdcde",[trim]))), <<":">> = iolist_to_binary(join(re:split("adcdcde","a[bcd]*dcdcde",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("adcdcde","a[bcd]*dcdcde",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[bcd]+dcdcde",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("adcdcde","a[bcd]*dcdcde",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[bcd]+dcdcde",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[bcd]+dcdcde",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[bcd]+dcdcde",[]))), - <<"abcde">> = iolist_to_binary(join(re:split("abcde","a[bcd]+dcdcde",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[bcd]+dcdcde",[]))), + <<"abcde">> = iolist_to_binary(join(re:split("abcde","a[bcd]+dcdcde",[trim]))), <<"abcde">> = iolist_to_binary(join(re:split("abcde","a[bcd]+dcdcde",[{parts, - 2}]))), - <<"abcde">> = iolist_to_binary(join(re:split("abcde","a[bcd]+dcdcde",[]))), - <<"adcdcde">> = iolist_to_binary(join(re:split("adcdcde","a[bcd]+dcdcde",[trim]))), + 2}]))), + <<"abcde">> = iolist_to_binary(join(re:split("abcde","a[bcd]+dcdcde",[]))), + <<"adcdcde">> = iolist_to_binary(join(re:split("adcdcde","a[bcd]+dcdcde",[trim]))), <<"adcdcde">> = iolist_to_binary(join(re:split("adcdcde","a[bcd]+dcdcde",[{parts, - 2}]))), - <<"adcdcde">> = iolist_to_binary(join(re:split("adcdcde","a[bcd]+dcdcde",[]))), - <<":ab">> = iolist_to_binary(join(re:split("abc","(ab|a)b*c",[trim]))), + 2}]))), + <<"adcdcde">> = iolist_to_binary(join(re:split("adcdcde","a[bcd]+dcdcde",[]))), + <<":ab">> = iolist_to_binary(join(re:split("abc","(ab|a)b*c",[trim]))), <<":ab:">> = iolist_to_binary(join(re:split("abc","(ab|a)b*c",[{parts, - 2}]))), - <<":ab:">> = iolist_to_binary(join(re:split("abc","(ab|a)b*c",[]))), - <<":abc:a:b:d">> = iolist_to_binary(join(re:split("abcd","((a)(b)c)(d)",[trim]))), + 2}]))), + <<":ab:">> = iolist_to_binary(join(re:split("abc","(ab|a)b*c",[]))), + <<":abc:a:b:d">> = iolist_to_binary(join(re:split("abcd","((a)(b)c)(d)",[trim]))), <<":abc:a:b:d:">> = iolist_to_binary(join(re:split("abcd","((a)(b)c)(d)",[{parts, - 2}]))), - <<":abc:a:b:d:">> = iolist_to_binary(join(re:split("abcd","((a)(b)c)(d)",[]))), - <<"">> = iolist_to_binary(join(re:split("alpha","[a-zA-Z_][a-zA-Z0-9_]*",[trim]))), + 2}]))), + <<":abc:a:b:d:">> = iolist_to_binary(join(re:split("abcd","((a)(b)c)(d)",[]))), + <<"">> = iolist_to_binary(join(re:split("alpha","[a-zA-Z_][a-zA-Z0-9_]*",[trim]))), <<":">> = iolist_to_binary(join(re:split("alpha","[a-zA-Z_][a-zA-Z0-9_]*",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("alpha","[a-zA-Z_][a-zA-Z0-9_]*",[]))), - <<"a">> = iolist_to_binary(join(re:split("abh","^a(bc+|b[eh])g|.h$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("alpha","[a-zA-Z_][a-zA-Z0-9_]*",[]))), + <<"a">> = iolist_to_binary(join(re:split("abh","^a(bc+|b[eh])g|.h$",[trim]))), <<"a::">> = iolist_to_binary(join(re:split("abh","^a(bc+|b[eh])g|.h$",[{parts, - 2}]))), - <<"a::">> = iolist_to_binary(join(re:split("abh","^a(bc+|b[eh])g|.h$",[]))), - <<":effgz">> = iolist_to_binary(join(re:split("effgz","(bc+d$|ef*g.|h?i(j|k))",[trim]))), + 2}]))), + <<"a::">> = iolist_to_binary(join(re:split("abh","^a(bc+|b[eh])g|.h$",[]))), + <<":effgz">> = iolist_to_binary(join(re:split("effgz","(bc+d$|ef*g.|h?i(j|k))",[trim]))), <<":effgz::">> = iolist_to_binary(join(re:split("effgz","(bc+d$|ef*g.|h?i(j|k))",[{parts, - 2}]))), - <<":effgz::">> = iolist_to_binary(join(re:split("effgz","(bc+d$|ef*g.|h?i(j|k))",[]))), - <<":ij:j">> = iolist_to_binary(join(re:split("ij","(bc+d$|ef*g.|h?i(j|k))",[trim]))), + 2}]))), + <<":effgz::">> = iolist_to_binary(join(re:split("effgz","(bc+d$|ef*g.|h?i(j|k))",[]))), + <<":ij:j">> = iolist_to_binary(join(re:split("ij","(bc+d$|ef*g.|h?i(j|k))",[trim]))), <<":ij:j:">> = iolist_to_binary(join(re:split("ij","(bc+d$|ef*g.|h?i(j|k))",[{parts, - 2}]))), - <<":ij:j:">> = iolist_to_binary(join(re:split("ij","(bc+d$|ef*g.|h?i(j|k))",[]))), - <<"r:effgz">> = iolist_to_binary(join(re:split("reffgz","(bc+d$|ef*g.|h?i(j|k))",[trim]))), + 2}]))), + <<":ij:j:">> = iolist_to_binary(join(re:split("ij","(bc+d$|ef*g.|h?i(j|k))",[]))), + <<"r:effgz">> = iolist_to_binary(join(re:split("reffgz","(bc+d$|ef*g.|h?i(j|k))",[trim]))), <<"r:effgz::">> = iolist_to_binary(join(re:split("reffgz","(bc+d$|ef*g.|h?i(j|k))",[{parts, - 2}]))), - <<"r:effgz::">> = iolist_to_binary(join(re:split("reffgz","(bc+d$|ef*g.|h?i(j|k))",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(bc+d$|ef*g.|h?i(j|k))",[trim]))), + 2}]))), + <<"r:effgz::">> = iolist_to_binary(join(re:split("reffgz","(bc+d$|ef*g.|h?i(j|k))",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(bc+d$|ef*g.|h?i(j|k))",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(bc+d$|ef*g.|h?i(j|k))",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(bc+d$|ef*g.|h?i(j|k))",[]))), - <<"effg">> = iolist_to_binary(join(re:split("effg","(bc+d$|ef*g.|h?i(j|k))",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(bc+d$|ef*g.|h?i(j|k))",[]))), + <<"effg">> = iolist_to_binary(join(re:split("effg","(bc+d$|ef*g.|h?i(j|k))",[trim]))), <<"effg">> = iolist_to_binary(join(re:split("effg","(bc+d$|ef*g.|h?i(j|k))",[{parts, - 2}]))), - <<"effg">> = iolist_to_binary(join(re:split("effg","(bc+d$|ef*g.|h?i(j|k))",[]))), - <<"bcdd">> = iolist_to_binary(join(re:split("bcdd","(bc+d$|ef*g.|h?i(j|k))",[trim]))), + 2}]))), + <<"effg">> = iolist_to_binary(join(re:split("effg","(bc+d$|ef*g.|h?i(j|k))",[]))), + <<"bcdd">> = iolist_to_binary(join(re:split("bcdd","(bc+d$|ef*g.|h?i(j|k))",[trim]))), <<"bcdd">> = iolist_to_binary(join(re:split("bcdd","(bc+d$|ef*g.|h?i(j|k))",[{parts, - 2}]))), - <<"bcdd">> = iolist_to_binary(join(re:split("bcdd","(bc+d$|ef*g.|h?i(j|k))",[]))), - <<":a:a:a:a:a:a:a:a:a:a">> = iolist_to_binary(join(re:split("a","((((((((((a))))))))))",[trim]))), + 2}]))), + <<"bcdd">> = iolist_to_binary(join(re:split("bcdd","(bc+d$|ef*g.|h?i(j|k))",[]))), + <<":a:a:a:a:a:a:a:a:a:a">> = iolist_to_binary(join(re:split("a","((((((((((a))))))))))",[trim]))), <<":a:a:a:a:a:a:a:a:a:a:">> = iolist_to_binary(join(re:split("a","((((((((((a))))))))))",[{parts, - 2}]))), - <<":a:a:a:a:a:a:a:a:a:a:">> = iolist_to_binary(join(re:split("a","((((((((((a))))))))))",[]))), - <<":a:a:a:a:a:a:a:a:a:a">> = iolist_to_binary(join(re:split("aa","((((((((((a))))))))))\\10",[trim]))), + 2}]))), + <<":a:a:a:a:a:a:a:a:a:a:">> = iolist_to_binary(join(re:split("a","((((((((((a))))))))))",[]))), + <<":a:a:a:a:a:a:a:a:a:a">> = iolist_to_binary(join(re:split("aa","((((((((((a))))))))))\\10",[trim]))), <<":a:a:a:a:a:a:a:a:a:a:">> = iolist_to_binary(join(re:split("aa","((((((((((a))))))))))\\10",[{parts, - 2}]))), - <<":a:a:a:a:a:a:a:a:a:a:">> = iolist_to_binary(join(re:split("aa","((((((((((a))))))))))\\10",[]))), - <<":a:a:a:a:a:a:a:a:a">> = iolist_to_binary(join(re:split("a","(((((((((a)))))))))",[trim]))), + 2}]))), + <<":a:a:a:a:a:a:a:a:a:a:">> = iolist_to_binary(join(re:split("aa","((((((((((a))))))))))\\10",[]))), + <<":a:a:a:a:a:a:a:a:a">> = iolist_to_binary(join(re:split("a","(((((((((a)))))))))",[trim]))), <<":a:a:a:a:a:a:a:a:a:">> = iolist_to_binary(join(re:split("a","(((((((((a)))))))))",[{parts, - 2}]))), - <<":a:a:a:a:a:a:a:a:a:">> = iolist_to_binary(join(re:split("a","(((((((((a)))))))))",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","multiple words of text",[trim]))), + 2}]))), + <<":a:a:a:a:a:a:a:a:a:">> = iolist_to_binary(join(re:split("a","(((((((((a)))))))))",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","multiple words of text",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","multiple words of text",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","multiple words of text",[]))), - <<"aa">> = iolist_to_binary(join(re:split("aa","multiple words of text",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","multiple words of text",[]))), + <<"aa">> = iolist_to_binary(join(re:split("aa","multiple words of text",[trim]))), <<"aa">> = iolist_to_binary(join(re:split("aa","multiple words of text",[{parts, - 2}]))), - <<"aa">> = iolist_to_binary(join(re:split("aa","multiple words of text",[]))), - <<"uh-uh">> = iolist_to_binary(join(re:split("uh-uh","multiple words of text",[trim]))), + 2}]))), + <<"aa">> = iolist_to_binary(join(re:split("aa","multiple words of text",[]))), + <<"uh-uh">> = iolist_to_binary(join(re:split("uh-uh","multiple words of text",[trim]))), <<"uh-uh">> = iolist_to_binary(join(re:split("uh-uh","multiple words of text",[{parts, - 2}]))), - <<"uh-uh">> = iolist_to_binary(join(re:split("uh-uh","multiple words of text",[]))), - <<":, yeah">> = iolist_to_binary(join(re:split("multiple words, yeah","multiple words",[trim]))), + 2}]))), + <<"uh-uh">> = iolist_to_binary(join(re:split("uh-uh","multiple words of text",[]))), + <<":, yeah">> = iolist_to_binary(join(re:split("multiple words, yeah","multiple words",[trim]))), <<":, yeah">> = iolist_to_binary(join(re:split("multiple words, yeah","multiple words",[{parts, - 2}]))), - <<":, yeah">> = iolist_to_binary(join(re:split("multiple words, yeah","multiple words",[]))), - <<":ab:de">> = iolist_to_binary(join(re:split("abcde","(.*)c(.*)",[trim]))), + 2}]))), + <<":, yeah">> = iolist_to_binary(join(re:split("multiple words, yeah","multiple words",[]))), + <<":ab:de">> = iolist_to_binary(join(re:split("abcde","(.*)c(.*)",[trim]))), <<":ab:de:">> = iolist_to_binary(join(re:split("abcde","(.*)c(.*)",[{parts, - 2}]))), - <<":ab:de:">> = iolist_to_binary(join(re:split("abcde","(.*)c(.*)",[]))), - <<":a:b">> = iolist_to_binary(join(re:split("(a, b)","\\((.*), (.*)\\)",[trim]))), + 2}]))), + <<":ab:de:">> = iolist_to_binary(join(re:split("abcde","(.*)c(.*)",[]))), + <<":a:b">> = iolist_to_binary(join(re:split("(a, b)","\\((.*), (.*)\\)",[trim]))), <<":a:b:">> = iolist_to_binary(join(re:split("(a, b)","\\((.*), (.*)\\)",[{parts, - 2}]))), - <<":a:b:">> = iolist_to_binary(join(re:split("(a, b)","\\((.*), (.*)\\)",[]))), - <<"">> = iolist_to_binary(join(re:split("abcd","abcd",[trim]))), + 2}]))), + <<":a:b:">> = iolist_to_binary(join(re:split("(a, b)","\\((.*), (.*)\\)",[]))), + <<"">> = iolist_to_binary(join(re:split("abcd","abcd",[trim]))), <<":">> = iolist_to_binary(join(re:split("abcd","abcd",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abcd","abcd",[]))), - <<":bc">> = iolist_to_binary(join(re:split("abcd","a(bc)d",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abcd","abcd",[]))), + <<":bc">> = iolist_to_binary(join(re:split("abcd","a(bc)d",[trim]))), <<":bc:">> = iolist_to_binary(join(re:split("abcd","a(bc)d",[{parts, - 2}]))), - <<":bc:">> = iolist_to_binary(join(re:split("abcd","a(bc)d",[]))), - <<"">> = iolist_to_binary(join(re:split("ac","a[-]?c",[trim]))), + 2}]))), + <<":bc:">> = iolist_to_binary(join(re:split("abcd","a(bc)d",[]))), + <<"">> = iolist_to_binary(join(re:split("ac","a[-]?c",[trim]))), <<":">> = iolist_to_binary(join(re:split("ac","a[-]?c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ac","a[-]?c",[]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ac","a[-]?c",[]))), ok. run20() -> - <<":abc">> = iolist_to_binary(join(re:split("abcabc","(abc)\\1",[trim]))), + <<":abc">> = iolist_to_binary(join(re:split("abcabc","(abc)\\1",[trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abcabc","(abc)\\1",[{parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abcabc","(abc)\\1",[]))), - <<":abc">> = iolist_to_binary(join(re:split("abcabc","([a-c]*)\\1",[trim]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abcabc","(abc)\\1",[]))), + <<":abc">> = iolist_to_binary(join(re:split("abcabc","([a-c]*)\\1",[trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abcabc","([a-c]*)\\1",[{parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abcabc","([a-c]*)\\1",[]))), - <<":a">> = iolist_to_binary(join(re:split("a","(a)|\\1",[trim]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abcabc","([a-c]*)\\1",[]))), + <<":a">> = iolist_to_binary(join(re:split("a","(a)|\\1",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("a","(a)|\\1",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("a","(a)|\\1",[]))), - <<"*** F:a:ilers">> = iolist_to_binary(join(re:split("*** Failers","(a)|\\1",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("a","(a)|\\1",[]))), + <<"*** F:a:ilers">> = iolist_to_binary(join(re:split("*** Failers","(a)|\\1",[trim]))), <<"*** F:a:ilers">> = iolist_to_binary(join(re:split("*** Failers","(a)|\\1",[{parts, - 2}]))), - <<"*** F:a:ilers">> = iolist_to_binary(join(re:split("*** Failers","(a)|\\1",[]))), - <<":a:b">> = iolist_to_binary(join(re:split("ab","(a)|\\1",[trim]))), + 2}]))), + <<"*** F:a:ilers">> = iolist_to_binary(join(re:split("*** Failers","(a)|\\1",[]))), + <<":a:b">> = iolist_to_binary(join(re:split("ab","(a)|\\1",[trim]))), <<":a:b">> = iolist_to_binary(join(re:split("ab","(a)|\\1",[{parts, - 2}]))), - <<":a:b">> = iolist_to_binary(join(re:split("ab","(a)|\\1",[]))), - <<"x">> = iolist_to_binary(join(re:split("x","(a)|\\1",[trim]))), + 2}]))), + <<":a:b">> = iolist_to_binary(join(re:split("ab","(a)|\\1",[]))), + <<"x">> = iolist_to_binary(join(re:split("x","(a)|\\1",[trim]))), <<"x">> = iolist_to_binary(join(re:split("x","(a)|\\1",[{parts, - 2}]))), - <<"x">> = iolist_to_binary(join(re:split("x","(a)|\\1",[]))), - <<":bb:b:b:cbc:c">> = iolist_to_binary(join(re:split("ababbbcbc","(([a-c])b*?\\2)*",[trim]))), + 2}]))), + <<"x">> = iolist_to_binary(join(re:split("x","(a)|\\1",[]))), + <<":bb:b:b:cbc:c">> = iolist_to_binary(join(re:split("ababbbcbc","(([a-c])b*?\\2)*",[trim]))), <<":bb:b:bcbc">> = iolist_to_binary(join(re:split("ababbbcbc","(([a-c])b*?\\2)*",[{parts, - 2}]))), - <<":bb:b:b:cbc:c:">> = iolist_to_binary(join(re:split("ababbbcbc","(([a-c])b*?\\2)*",[]))), - <<":cbc:c">> = iolist_to_binary(join(re:split("ababbbcbc","(([a-c])b*?\\2){3}",[trim]))), + 2}]))), + <<":bb:b:b:cbc:c:">> = iolist_to_binary(join(re:split("ababbbcbc","(([a-c])b*?\\2)*",[]))), + <<":cbc:c">> = iolist_to_binary(join(re:split("ababbbcbc","(([a-c])b*?\\2){3}",[trim]))), <<":cbc:c:">> = iolist_to_binary(join(re:split("ababbbcbc","(([a-c])b*?\\2){3}",[{parts, - 2}]))), - <<":cbc:c:">> = iolist_to_binary(join(re:split("ababbbcbc","(([a-c])b*?\\2){3}",[]))), - <<"aaaxabaxbaax:bbax:b:a">> = iolist_to_binary(join(re:split("aaaxabaxbaaxbbax","((\\3|b)\\2(a)x)+",[trim]))), + 2}]))), + <<":cbc:c:">> = iolist_to_binary(join(re:split("ababbbcbc","(([a-c])b*?\\2){3}",[]))), + <<"aaaxabaxbaax:bbax:b:a">> = iolist_to_binary(join(re:split("aaaxabaxbaaxbbax","((\\3|b)\\2(a)x)+",[trim]))), <<"aaaxabaxbaax:bbax:b:a:">> = iolist_to_binary(join(re:split("aaaxabaxbaaxbbax","((\\3|b)\\2(a)x)+",[{parts, - 2}]))), - <<"aaaxabaxbaax:bbax:b:a:">> = iolist_to_binary(join(re:split("aaaxabaxbaaxbbax","((\\3|b)\\2(a)x)+",[]))), - <<"bbaababbabaaaaa:bba:b:a">> = iolist_to_binary(join(re:split("bbaababbabaaaaabbaaaabba","((\\3|b)\\2(a)){2,}",[trim]))), + 2}]))), + <<"aaaxabaxbaax:bbax:b:a:">> = iolist_to_binary(join(re:split("aaaxabaxbaaxbbax","((\\3|b)\\2(a)x)+",[]))), + <<"bbaababbabaaaaa:bba:b:a">> = iolist_to_binary(join(re:split("bbaababbabaaaaabbaaaabba","((\\3|b)\\2(a)){2,}",[trim]))), <<"bbaababbabaaaaa:bba:b:a:">> = iolist_to_binary(join(re:split("bbaababbabaaaaabbaaaabba","((\\3|b)\\2(a)){2,}",[{parts, - 2}]))), - <<"bbaababbabaaaaa:bba:b:a:">> = iolist_to_binary(join(re:split("bbaababbabaaaaabbaaaabba","((\\3|b)\\2(a)){2,}",[]))), + 2}]))), + <<"bbaababbabaaaaa:bba:b:a:">> = iolist_to_binary(join(re:split("bbaababbabaaaaabbaaaabba","((\\3|b)\\2(a)){2,}",[]))), <<"">> = iolist_to_binary(join(re:split("ABC","abc",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABC","abc",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABC","abc",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABC","abc",[caseless]))), <<"X:Y">> = iolist_to_binary(join(re:split("XABCY","abc",[caseless, - trim]))), + trim]))), <<"X:Y">> = iolist_to_binary(join(re:split("XABCY","abc",[caseless, {parts, - 2}]))), - <<"X:Y">> = iolist_to_binary(join(re:split("XABCY","abc",[caseless]))), + 2}]))), + <<"X:Y">> = iolist_to_binary(join(re:split("XABCY","abc",[caseless]))), <<"AB">> = iolist_to_binary(join(re:split("ABABC","abc",[caseless, - trim]))), + trim]))), <<"AB:">> = iolist_to_binary(join(re:split("ABABC","abc",[caseless, {parts, - 2}]))), - <<"AB:">> = iolist_to_binary(join(re:split("ABABC","abc",[caseless]))), + 2}]))), + <<"AB:">> = iolist_to_binary(join(re:split("ABABC","abc",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","abc",[caseless]))), <<"aaxabxbaxbbx">> = iolist_to_binary(join(re:split("aaxabxbaxbbx","abc",[caseless, - trim]))), + trim]))), <<"aaxabxbaxbbx">> = iolist_to_binary(join(re:split("aaxabxbaxbbx","abc",[caseless, {parts, - 2}]))), - <<"aaxabxbaxbbx">> = iolist_to_binary(join(re:split("aaxabxbaxbbx","abc",[caseless]))), + 2}]))), + <<"aaxabxbaxbbx">> = iolist_to_binary(join(re:split("aaxabxbaxbbx","abc",[caseless]))), <<"XBC">> = iolist_to_binary(join(re:split("XBC","abc",[caseless, - trim]))), + trim]))), <<"XBC">> = iolist_to_binary(join(re:split("XBC","abc",[caseless, {parts, - 2}]))), - <<"XBC">> = iolist_to_binary(join(re:split("XBC","abc",[caseless]))), + 2}]))), + <<"XBC">> = iolist_to_binary(join(re:split("XBC","abc",[caseless]))), <<"AXC">> = iolist_to_binary(join(re:split("AXC","abc",[caseless, - trim]))), + trim]))), <<"AXC">> = iolist_to_binary(join(re:split("AXC","abc",[caseless, {parts, - 2}]))), - <<"AXC">> = iolist_to_binary(join(re:split("AXC","abc",[caseless]))), + 2}]))), + <<"AXC">> = iolist_to_binary(join(re:split("AXC","abc",[caseless]))), <<"ABX">> = iolist_to_binary(join(re:split("ABX","abc",[caseless, - trim]))), + trim]))), <<"ABX">> = iolist_to_binary(join(re:split("ABX","abc",[caseless, {parts, - 2}]))), - <<"ABX">> = iolist_to_binary(join(re:split("ABX","abc",[caseless]))), + 2}]))), + <<"ABX">> = iolist_to_binary(join(re:split("ABX","abc",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABC","ab*c",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABC","ab*c",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABC","ab*c",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABC","ab*c",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABC","ab*bc",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABC","ab*bc",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABC","ab*bc",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABC","ab*bc",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABBC","ab*bc",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABBC","ab*bc",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABBC","ab*bc",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABBC","ab*bc",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABBBBC","ab*?bc",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab*?bc",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab*?bc",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab*?bc",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABBBBC","ab{0,}?bc",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{0,}?bc",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{0,}?bc",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{0,}?bc",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABBC","ab+?bc",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABBC","ab+?bc",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABBC","ab+?bc",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABBC","ab+?bc",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab+bc",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab+bc",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab+bc",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab+bc",[caseless]))), <<"ABC">> = iolist_to_binary(join(re:split("ABC","ab+bc",[caseless, - trim]))), + trim]))), <<"ABC">> = iolist_to_binary(join(re:split("ABC","ab+bc",[caseless, {parts, - 2}]))), - <<"ABC">> = iolist_to_binary(join(re:split("ABC","ab+bc",[caseless]))), + 2}]))), + <<"ABC">> = iolist_to_binary(join(re:split("ABC","ab+bc",[caseless]))), <<"ABQ">> = iolist_to_binary(join(re:split("ABQ","ab+bc",[caseless, - trim]))), + trim]))), <<"ABQ">> = iolist_to_binary(join(re:split("ABQ","ab+bc",[caseless, {parts, - 2}]))), - <<"ABQ">> = iolist_to_binary(join(re:split("ABQ","ab+bc",[caseless]))), + 2}]))), + <<"ABQ">> = iolist_to_binary(join(re:split("ABQ","ab+bc",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABBBBC","ab+bc",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab+bc",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab+bc",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab+bc",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABBBBC","ab{1,}?bc",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{1,}?bc",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{1,}?bc",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{1,}?bc",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABBBBC","ab{1,3}?bc",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{1,3}?bc",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{1,3}?bc",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{1,3}?bc",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABBBBC","ab{3,4}?bc",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{3,4}?bc",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{3,4}?bc",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABBBBC","ab{3,4}?bc",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{4,5}?bc",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{4,5}?bc",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{4,5}?bc",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","ab{4,5}?bc",[caseless]))), <<"ABQ">> = iolist_to_binary(join(re:split("ABQ","ab{4,5}?bc",[caseless, - trim]))), + trim]))), <<"ABQ">> = iolist_to_binary(join(re:split("ABQ","ab{4,5}?bc",[caseless, {parts, - 2}]))), - <<"ABQ">> = iolist_to_binary(join(re:split("ABQ","ab{4,5}?bc",[caseless]))), + 2}]))), + <<"ABQ">> = iolist_to_binary(join(re:split("ABQ","ab{4,5}?bc",[caseless]))), <<"ABBBBC">> = iolist_to_binary(join(re:split("ABBBBC","ab{4,5}?bc",[caseless, - trim]))), + trim]))), <<"ABBBBC">> = iolist_to_binary(join(re:split("ABBBBC","ab{4,5}?bc",[caseless, {parts, - 2}]))), - <<"ABBBBC">> = iolist_to_binary(join(re:split("ABBBBC","ab{4,5}?bc",[caseless]))), + 2}]))), + <<"ABBBBC">> = iolist_to_binary(join(re:split("ABBBBC","ab{4,5}?bc",[caseless]))), ok. run21() -> <<"">> = iolist_to_binary(join(re:split("ABBC","ab??bc",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABBC","ab??bc",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABBC","ab??bc",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABBC","ab??bc",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABC","ab??bc",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABC","ab??bc",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABC","ab??bc",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABC","ab??bc",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABC","ab{0,1}?bc",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABC","ab{0,1}?bc",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABC","ab{0,1}?bc",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABC","ab{0,1}?bc",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABC","ab??c",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABC","ab??c",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABC","ab??c",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABC","ab??c",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABC","ab{0,1}?c",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABC","ab{0,1}?c",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABC","ab{0,1}?c",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABC","ab{0,1}?c",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABC","^abc$",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABC","^abc$",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABC","^abc$",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABC","^abc$",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^abc$",[caseless]))), <<"ABBBBC">> = iolist_to_binary(join(re:split("ABBBBC","^abc$",[caseless, - trim]))), + trim]))), <<"ABBBBC">> = iolist_to_binary(join(re:split("ABBBBC","^abc$",[caseless, {parts, - 2}]))), - <<"ABBBBC">> = iolist_to_binary(join(re:split("ABBBBC","^abc$",[caseless]))), + 2}]))), + <<"ABBBBC">> = iolist_to_binary(join(re:split("ABBBBC","^abc$",[caseless]))), <<"ABCC">> = iolist_to_binary(join(re:split("ABCC","^abc$",[caseless, - trim]))), + trim]))), <<"ABCC">> = iolist_to_binary(join(re:split("ABCC","^abc$",[caseless, {parts, - 2}]))), - <<"ABCC">> = iolist_to_binary(join(re:split("ABCC","^abc$",[caseless]))), + 2}]))), + <<"ABCC">> = iolist_to_binary(join(re:split("ABCC","^abc$",[caseless]))), <<":C">> = iolist_to_binary(join(re:split("ABCC","^abc",[caseless, - trim]))), + trim]))), <<":C">> = iolist_to_binary(join(re:split("ABCC","^abc",[caseless, {parts, - 2}]))), - <<":C">> = iolist_to_binary(join(re:split("ABCC","^abc",[caseless]))), + 2}]))), + <<":C">> = iolist_to_binary(join(re:split("ABCC","^abc",[caseless]))), <<"A">> = iolist_to_binary(join(re:split("AABC","abc$",[caseless, - trim]))), + trim]))), <<"A:">> = iolist_to_binary(join(re:split("AABC","abc$",[caseless, {parts, - 2}]))), - <<"A:">> = iolist_to_binary(join(re:split("AABC","abc$",[caseless]))), + 2}]))), + <<"A:">> = iolist_to_binary(join(re:split("AABC","abc$",[caseless]))), <<"ABC">> = iolist_to_binary(join(re:split("ABC","^",[caseless, - trim]))), + trim]))), <<"ABC">> = iolist_to_binary(join(re:split("ABC","^",[caseless, {parts, - 2}]))), - <<"ABC">> = iolist_to_binary(join(re:split("ABC","^",[caseless]))), + 2}]))), + <<"ABC">> = iolist_to_binary(join(re:split("ABC","^",[caseless]))), <<"ABC">> = iolist_to_binary(join(re:split("ABC","$",[caseless, - trim]))), + trim]))), <<"ABC:">> = iolist_to_binary(join(re:split("ABC","$",[caseless, {parts, - 2}]))), - <<"ABC:">> = iolist_to_binary(join(re:split("ABC","$",[caseless]))), + 2}]))), + <<"ABC:">> = iolist_to_binary(join(re:split("ABC","$",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABC","a.c",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABC","a.c",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABC","a.c",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABC","a.c",[caseless]))), <<"">> = iolist_to_binary(join(re:split("AXC","a.c",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("AXC","a.c",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("AXC","a.c",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("AXC","a.c",[caseless]))), <<"">> = iolist_to_binary(join(re:split("AXYZC","a.*?c",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("AXYZC","a.*?c",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("AXYZC","a.*?c",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("AXYZC","a.*?c",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a.*c",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a.*c",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a.*c",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a.*c",[caseless]))), <<"">> = iolist_to_binary(join(re:split("AABC","a.*c",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("AABC","a.*c",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("AABC","a.*c",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("AABC","a.*c",[caseless]))), <<"AXYZD">> = iolist_to_binary(join(re:split("AXYZD","a.*c",[caseless, - trim]))), + trim]))), <<"AXYZD">> = iolist_to_binary(join(re:split("AXYZD","a.*c",[caseless, {parts, - 2}]))), - <<"AXYZD">> = iolist_to_binary(join(re:split("AXYZD","a.*c",[caseless]))), + 2}]))), + <<"AXYZD">> = iolist_to_binary(join(re:split("AXYZD","a.*c",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABD","a[bc]d",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABD","a[bc]d",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABD","a[bc]d",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABD","a[bc]d",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ACE","a[b-d]e",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ACE","a[b-d]e",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ACE","a[b-d]e",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ACE","a[b-d]e",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[b-d]e",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[b-d]e",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[b-d]e",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[b-d]e",[caseless]))), <<"ABC">> = iolist_to_binary(join(re:split("ABC","a[b-d]e",[caseless, - trim]))), + trim]))), <<"ABC">> = iolist_to_binary(join(re:split("ABC","a[b-d]e",[caseless, {parts, - 2}]))), - <<"ABC">> = iolist_to_binary(join(re:split("ABC","a[b-d]e",[caseless]))), + 2}]))), + <<"ABC">> = iolist_to_binary(join(re:split("ABC","a[b-d]e",[caseless]))), <<"ABD">> = iolist_to_binary(join(re:split("ABD","a[b-d]e",[caseless, - trim]))), + trim]))), <<"ABD">> = iolist_to_binary(join(re:split("ABD","a[b-d]e",[caseless, {parts, - 2}]))), - <<"ABD">> = iolist_to_binary(join(re:split("ABD","a[b-d]e",[caseless]))), + 2}]))), + <<"ABD">> = iolist_to_binary(join(re:split("ABD","a[b-d]e",[caseless]))), <<"A">> = iolist_to_binary(join(re:split("AAC","a[b-d]",[caseless, - trim]))), + trim]))), <<"A:">> = iolist_to_binary(join(re:split("AAC","a[b-d]",[caseless, {parts, - 2}]))), - <<"A:">> = iolist_to_binary(join(re:split("AAC","a[b-d]",[caseless]))), + 2}]))), + <<"A:">> = iolist_to_binary(join(re:split("AAC","a[b-d]",[caseless]))), <<"">> = iolist_to_binary(join(re:split("A-","a[-b]",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("A-","a[-b]",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("A-","a[-b]",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("A-","a[-b]",[caseless]))), <<"">> = iolist_to_binary(join(re:split("A-","a[b-]",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("A-","a[b-]",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("A-","a[b-]",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("A-","a[b-]",[caseless]))), <<"">> = iolist_to_binary(join(re:split("A]","a]",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("A]","a]",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("A]","a]",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("A]","a]",[caseless]))), ok. run22() -> <<"">> = iolist_to_binary(join(re:split("A]B","a[]]b",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("A]B","a[]]b",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("A]B","a[]]b",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("A]B","a[]]b",[caseless]))), <<"">> = iolist_to_binary(join(re:split("AED","a[^bc]d",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("AED","a[^bc]d",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("AED","a[^bc]d",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("AED","a[^bc]d",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ADC","a[^-b]c",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ADC","a[^-b]c",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ADC","a[^-b]c",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ADC","a[^-b]c",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^-b]c",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^-b]c",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^-b]c",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a[^-b]c",[caseless]))), <<"ABD">> = iolist_to_binary(join(re:split("ABD","a[^-b]c",[caseless, - trim]))), + trim]))), <<"ABD">> = iolist_to_binary(join(re:split("ABD","a[^-b]c",[caseless, {parts, - 2}]))), - <<"ABD">> = iolist_to_binary(join(re:split("ABD","a[^-b]c",[caseless]))), + 2}]))), + <<"ABD">> = iolist_to_binary(join(re:split("ABD","a[^-b]c",[caseless]))), <<"A-C">> = iolist_to_binary(join(re:split("A-C","a[^-b]c",[caseless, - trim]))), + trim]))), <<"A-C">> = iolist_to_binary(join(re:split("A-C","a[^-b]c",[caseless, {parts, - 2}]))), - <<"A-C">> = iolist_to_binary(join(re:split("A-C","a[^-b]c",[caseless]))), + 2}]))), + <<"A-C">> = iolist_to_binary(join(re:split("A-C","a[^-b]c",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ADC","a[^]b]c",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ADC","a[^]b]c",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ADC","a[^]b]c",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ADC","a[^]b]c",[caseless]))), <<":C">> = iolist_to_binary(join(re:split("ABC","ab|cd",[caseless, - trim]))), + trim]))), <<":C">> = iolist_to_binary(join(re:split("ABC","ab|cd",[caseless, {parts, - 2}]))), - <<":C">> = iolist_to_binary(join(re:split("ABC","ab|cd",[caseless]))), + 2}]))), + <<":C">> = iolist_to_binary(join(re:split("ABC","ab|cd",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABCD","ab|cd",[caseless, - trim]))), + trim]))), <<":CD">> = iolist_to_binary(join(re:split("ABCD","ab|cd",[caseless, {parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("ABCD","ab|cd",[caseless]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("ABCD","ab|cd",[caseless]))), <<"D">> = iolist_to_binary(join(re:split("DEF","()ef",[caseless, - trim]))), + trim]))), <<"D::">> = iolist_to_binary(join(re:split("DEF","()ef",[caseless, {parts, - 2}]))), - <<"D::">> = iolist_to_binary(join(re:split("DEF","()ef",[caseless]))), + 2}]))), + <<"D::">> = iolist_to_binary(join(re:split("DEF","()ef",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","$b",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","$b",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","$b",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","$b",[caseless]))), <<"A]C">> = iolist_to_binary(join(re:split("A]C","$b",[caseless, - trim]))), + trim]))), <<"A]C">> = iolist_to_binary(join(re:split("A]C","$b",[caseless, {parts, - 2}]))), - <<"A]C">> = iolist_to_binary(join(re:split("A]C","$b",[caseless]))), + 2}]))), + <<"A]C">> = iolist_to_binary(join(re:split("A]C","$b",[caseless]))), <<"B">> = iolist_to_binary(join(re:split("B","$b",[caseless, - trim]))), + trim]))), <<"B">> = iolist_to_binary(join(re:split("B","$b",[caseless, {parts, - 2}]))), - <<"B">> = iolist_to_binary(join(re:split("B","$b",[caseless]))), + 2}]))), + <<"B">> = iolist_to_binary(join(re:split("B","$b",[caseless]))), <<"">> = iolist_to_binary(join(re:split("A(B","a\\(b",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("A(B","a\\(b",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("A(B","a\\(b",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("A(B","a\\(b",[caseless]))), <<"">> = iolist_to_binary(join(re:split("AB","a\\(*b",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("AB","a\\(*b",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("AB","a\\(*b",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("AB","a\\(*b",[caseless]))), <<"">> = iolist_to_binary(join(re:split("A((B","a\\(*b",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("A((B","a\\(*b",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("A((B","a\\(*b",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("A((B","a\\(*b",[caseless]))), <<"A">> = iolist_to_binary(join(re:split("A","a\\\\b",[caseless, notbol, - trim]))), + trim]))), <<"A">> = iolist_to_binary(join(re:split("A","a\\\\b",[caseless, notbol, {parts, - 2}]))), + 2}]))), <<"A">> = iolist_to_binary(join(re:split("A","a\\\\b",[caseless, - notbol]))), + notbol]))), <<":A:A:BC">> = iolist_to_binary(join(re:split("ABC","((a))",[caseless, - trim]))), + trim]))), <<":A:A:BC">> = iolist_to_binary(join(re:split("ABC","((a))",[caseless, {parts, - 2}]))), - <<":A:A:BC">> = iolist_to_binary(join(re:split("ABC","((a))",[caseless]))), + 2}]))), + <<":A:A:BC">> = iolist_to_binary(join(re:split("ABC","((a))",[caseless]))), <<":A:C">> = iolist_to_binary(join(re:split("ABC","(a)b(c)",[caseless, - trim]))), + trim]))), <<":A:C:">> = iolist_to_binary(join(re:split("ABC","(a)b(c)",[caseless, {parts, - 2}]))), - <<":A:C:">> = iolist_to_binary(join(re:split("ABC","(a)b(c)",[caseless]))), + 2}]))), + <<":A:C:">> = iolist_to_binary(join(re:split("ABC","(a)b(c)",[caseless]))), <<"AABB">> = iolist_to_binary(join(re:split("AABBABC","a+b+c",[caseless, - trim]))), + trim]))), <<"AABB:">> = iolist_to_binary(join(re:split("AABBABC","a+b+c",[caseless, {parts, - 2}]))), - <<"AABB:">> = iolist_to_binary(join(re:split("AABBABC","a+b+c",[caseless]))), + 2}]))), + <<"AABB:">> = iolist_to_binary(join(re:split("AABBABC","a+b+c",[caseless]))), <<"AABB">> = iolist_to_binary(join(re:split("AABBABC","a{1,}b{1,}c",[caseless, - trim]))), + trim]))), <<"AABB:">> = iolist_to_binary(join(re:split("AABBABC","a{1,}b{1,}c",[caseless, {parts, - 2}]))), - <<"AABB:">> = iolist_to_binary(join(re:split("AABBABC","a{1,}b{1,}c",[caseless]))), + 2}]))), + <<"AABB:">> = iolist_to_binary(join(re:split("AABBABC","a{1,}b{1,}c",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABCABC","a.+?c",[caseless, - trim]))), + trim]))), <<":ABC">> = iolist_to_binary(join(re:split("ABCABC","a.+?c",[caseless, {parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("ABCABC","a.+?c",[caseless]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("ABCABC","a.+?c",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABCABC","a.*?c",[caseless, - trim]))), + trim]))), <<":ABC">> = iolist_to_binary(join(re:split("ABCABC","a.*?c",[caseless, {parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("ABCABC","a.*?c",[caseless]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("ABCABC","a.*?c",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABCABC","a.{0,5}?c",[caseless, - trim]))), + trim]))), <<":ABC">> = iolist_to_binary(join(re:split("ABCABC","a.{0,5}?c",[caseless, {parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("ABCABC","a.{0,5}?c",[caseless]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("ABCABC","a.{0,5}?c",[caseless]))), <<":B">> = iolist_to_binary(join(re:split("AB","(a+|b)*",[caseless, - trim]))), + trim]))), <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b)*",[caseless, {parts, - 2}]))), - <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b)*",[caseless]))), + 2}]))), + <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b)*",[caseless]))), <<":B">> = iolist_to_binary(join(re:split("AB","(a+|b){0,}",[caseless, - trim]))), + trim]))), <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b){0,}",[caseless, {parts, - 2}]))), - <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b){0,}",[caseless]))), + 2}]))), + <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b){0,}",[caseless]))), <<":B">> = iolist_to_binary(join(re:split("AB","(a+|b)+",[caseless, - trim]))), + trim]))), <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b)+",[caseless, {parts, - 2}]))), - <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b)+",[caseless]))), + 2}]))), + <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b)+",[caseless]))), ok. run23() -> <<":B">> = iolist_to_binary(join(re:split("AB","(a+|b){1,}",[caseless, - trim]))), + trim]))), <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b){1,}",[caseless, {parts, - 2}]))), - <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b){1,}",[caseless]))), + 2}]))), + <<":B:">> = iolist_to_binary(join(re:split("AB","(a+|b){1,}",[caseless]))), <<":A::B">> = iolist_to_binary(join(re:split("AB","(a+|b)?",[caseless, - trim]))), + trim]))), <<":A:B">> = iolist_to_binary(join(re:split("AB","(a+|b)?",[caseless, {parts, - 2}]))), - <<":A::B:">> = iolist_to_binary(join(re:split("AB","(a+|b)?",[caseless]))), + 2}]))), + <<":A::B:">> = iolist_to_binary(join(re:split("AB","(a+|b)?",[caseless]))), <<":A::B">> = iolist_to_binary(join(re:split("AB","(a+|b){0,1}",[caseless, - trim]))), + trim]))), <<":A:B">> = iolist_to_binary(join(re:split("AB","(a+|b){0,1}",[caseless, {parts, - 2}]))), - <<":A::B:">> = iolist_to_binary(join(re:split("AB","(a+|b){0,1}",[caseless]))), + 2}]))), + <<":A::B:">> = iolist_to_binary(join(re:split("AB","(a+|b){0,1}",[caseless]))), <<":A::B">> = iolist_to_binary(join(re:split("AB","(a+|b){0,1}?",[caseless, - trim]))), + trim]))), <<":A:B">> = iolist_to_binary(join(re:split("AB","(a+|b){0,1}?",[caseless, {parts, - 2}]))), - <<":A::B:">> = iolist_to_binary(join(re:split("AB","(a+|b){0,1}?",[caseless]))), + 2}]))), + <<":A::B:">> = iolist_to_binary(join(re:split("AB","(a+|b){0,1}?",[caseless]))), <<"">> = iolist_to_binary(join(re:split("CDE","[^ab]*",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("CDE","[^ab]*",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("CDE","[^ab]*",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("CDE","[^ab]*",[caseless]))), <<":C">> = iolist_to_binary(join(re:split("ABBBCD","([abc])*d",[caseless, - trim]))), + trim]))), <<":C:">> = iolist_to_binary(join(re:split("ABBBCD","([abc])*d",[caseless, {parts, - 2}]))), - <<":C:">> = iolist_to_binary(join(re:split("ABBBCD","([abc])*d",[caseless]))), + 2}]))), + <<":C:">> = iolist_to_binary(join(re:split("ABBBCD","([abc])*d",[caseless]))), <<":A">> = iolist_to_binary(join(re:split("ABCD","([abc])*bcd",[caseless, - trim]))), + trim]))), <<":A:">> = iolist_to_binary(join(re:split("ABCD","([abc])*bcd",[caseless, {parts, - 2}]))), - <<":A:">> = iolist_to_binary(join(re:split("ABCD","([abc])*bcd",[caseless]))), + 2}]))), + <<":A:">> = iolist_to_binary(join(re:split("ABCD","([abc])*bcd",[caseless]))), <<"">> = iolist_to_binary(join(re:split("E","a|b|c|d|e",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("E","a|b|c|d|e",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("E","a|b|c|d|e",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("E","a|b|c|d|e",[caseless]))), <<":E">> = iolist_to_binary(join(re:split("EF","(a|b|c|d|e)f",[caseless, - trim]))), + trim]))), <<":E:">> = iolist_to_binary(join(re:split("EF","(a|b|c|d|e)f",[caseless, {parts, - 2}]))), - <<":E:">> = iolist_to_binary(join(re:split("EF","(a|b|c|d|e)f",[caseless]))), + 2}]))), + <<":E:">> = iolist_to_binary(join(re:split("EF","(a|b|c|d|e)f",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ABCDEFG","abcd*efg",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABCDEFG","abcd*efg",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABCDEFG","abcd*efg",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABCDEFG","abcd*efg",[caseless]))), <<"X:Y:Z">> = iolist_to_binary(join(re:split("XABYABBBZ","ab*",[caseless, - trim]))), + trim]))), <<"X:YABBBZ">> = iolist_to_binary(join(re:split("XABYABBBZ","ab*",[caseless, {parts, - 2}]))), - <<"X:Y:Z">> = iolist_to_binary(join(re:split("XABYABBBZ","ab*",[caseless]))), + 2}]))), + <<"X:Y:Z">> = iolist_to_binary(join(re:split("XABYABBBZ","ab*",[caseless]))), <<"X:Y:Z">> = iolist_to_binary(join(re:split("XAYABBBZ","ab*",[caseless, - trim]))), + trim]))), <<"X:YABBBZ">> = iolist_to_binary(join(re:split("XAYABBBZ","ab*",[caseless, {parts, - 2}]))), - <<"X:Y:Z">> = iolist_to_binary(join(re:split("XAYABBBZ","ab*",[caseless]))), + 2}]))), + <<"X:Y:Z">> = iolist_to_binary(join(re:split("XAYABBBZ","ab*",[caseless]))), <<"AB:CD">> = iolist_to_binary(join(re:split("ABCDE","(ab|cd)e",[caseless, - trim]))), + trim]))), <<"AB:CD:">> = iolist_to_binary(join(re:split("ABCDE","(ab|cd)e",[caseless, {parts, - 2}]))), - <<"AB:CD:">> = iolist_to_binary(join(re:split("ABCDE","(ab|cd)e",[caseless]))), + 2}]))), + <<"AB:CD:">> = iolist_to_binary(join(re:split("ABCDE","(ab|cd)e",[caseless]))), <<"">> = iolist_to_binary(join(re:split("HIJ","[abhgefdc]ij",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("HIJ","[abhgefdc]ij",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("HIJ","[abhgefdc]ij",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("HIJ","[abhgefdc]ij",[caseless]))), <<"ABCDE">> = iolist_to_binary(join(re:split("ABCDE","^(ab|cd)e",[caseless, - trim]))), + trim]))), <<"ABCDE">> = iolist_to_binary(join(re:split("ABCDE","^(ab|cd)e",[caseless, {parts, - 2}]))), - <<"ABCDE">> = iolist_to_binary(join(re:split("ABCDE","^(ab|cd)e",[caseless]))), + 2}]))), + <<"ABCDE">> = iolist_to_binary(join(re:split("ABCDE","^(ab|cd)e",[caseless]))), <<"ABCD">> = iolist_to_binary(join(re:split("ABCDEF","(abc|)ef",[caseless, - trim]))), + trim]))), <<"ABCD::">> = iolist_to_binary(join(re:split("ABCDEF","(abc|)ef",[caseless, {parts, - 2}]))), - <<"ABCD::">> = iolist_to_binary(join(re:split("ABCDEF","(abc|)ef",[caseless]))), + 2}]))), + <<"ABCD::">> = iolist_to_binary(join(re:split("ABCDEF","(abc|)ef",[caseless]))), <<"A:B">> = iolist_to_binary(join(re:split("ABCD","(a|b)c*d",[caseless, - trim]))), + trim]))), <<"A:B:">> = iolist_to_binary(join(re:split("ABCD","(a|b)c*d",[caseless, {parts, - 2}]))), - <<"A:B:">> = iolist_to_binary(join(re:split("ABCD","(a|b)c*d",[caseless]))), + 2}]))), + <<"A:B:">> = iolist_to_binary(join(re:split("ABCD","(a|b)c*d",[caseless]))), <<":A">> = iolist_to_binary(join(re:split("ABC","(ab|ab*)bc",[caseless, - trim]))), + trim]))), <<":A:">> = iolist_to_binary(join(re:split("ABC","(ab|ab*)bc",[caseless, {parts, - 2}]))), - <<":A:">> = iolist_to_binary(join(re:split("ABC","(ab|ab*)bc",[caseless]))), + 2}]))), + <<":A:">> = iolist_to_binary(join(re:split("ABC","(ab|ab*)bc",[caseless]))), <<":BC">> = iolist_to_binary(join(re:split("ABC","a([bc]*)c*",[caseless, - trim]))), + trim]))), <<":BC:">> = iolist_to_binary(join(re:split("ABC","a([bc]*)c*",[caseless, {parts, - 2}]))), - <<":BC:">> = iolist_to_binary(join(re:split("ABC","a([bc]*)c*",[caseless]))), + 2}]))), + <<":BC:">> = iolist_to_binary(join(re:split("ABC","a([bc]*)c*",[caseless]))), ok. run24() -> <<":BC:D">> = iolist_to_binary(join(re:split("ABCD","a([bc]*)(c*d)",[caseless, - trim]))), + trim]))), <<":BC:D:">> = iolist_to_binary(join(re:split("ABCD","a([bc]*)(c*d)",[caseless, {parts, - 2}]))), - <<":BC:D:">> = iolist_to_binary(join(re:split("ABCD","a([bc]*)(c*d)",[caseless]))), + 2}]))), + <<":BC:D:">> = iolist_to_binary(join(re:split("ABCD","a([bc]*)(c*d)",[caseless]))), <<":BC:D">> = iolist_to_binary(join(re:split("ABCD","a([bc]+)(c*d)",[caseless, - trim]))), + trim]))), <<":BC:D:">> = iolist_to_binary(join(re:split("ABCD","a([bc]+)(c*d)",[caseless, {parts, - 2}]))), - <<":BC:D:">> = iolist_to_binary(join(re:split("ABCD","a([bc]+)(c*d)",[caseless]))), + 2}]))), + <<":BC:D:">> = iolist_to_binary(join(re:split("ABCD","a([bc]+)(c*d)",[caseless]))), <<":B:CD">> = iolist_to_binary(join(re:split("ABCD","a([bc]*)(c+d)",[caseless, - trim]))), + trim]))), <<":B:CD:">> = iolist_to_binary(join(re:split("ABCD","a([bc]*)(c+d)",[caseless, {parts, - 2}]))), - <<":B:CD:">> = iolist_to_binary(join(re:split("ABCD","a([bc]*)(c+d)",[caseless]))), + 2}]))), + <<":B:CD:">> = iolist_to_binary(join(re:split("ABCD","a([bc]*)(c+d)",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ADCDCDE","a[bcd]*dcdcde",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ADCDCDE","a[bcd]*dcdcde",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ADCDCDE","a[bcd]*dcdcde",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ADCDCDE","a[bcd]*dcdcde",[caseless]))), <<":AB">> = iolist_to_binary(join(re:split("ABC","(ab|a)b*c",[caseless, - trim]))), + trim]))), <<":AB:">> = iolist_to_binary(join(re:split("ABC","(ab|a)b*c",[caseless, {parts, - 2}]))), - <<":AB:">> = iolist_to_binary(join(re:split("ABC","(ab|a)b*c",[caseless]))), + 2}]))), + <<":AB:">> = iolist_to_binary(join(re:split("ABC","(ab|a)b*c",[caseless]))), <<":ABC:A:B:D">> = iolist_to_binary(join(re:split("ABCD","((a)(b)c)(d)",[caseless, - trim]))), + trim]))), <<":ABC:A:B:D:">> = iolist_to_binary(join(re:split("ABCD","((a)(b)c)(d)",[caseless, {parts, - 2}]))), - <<":ABC:A:B:D:">> = iolist_to_binary(join(re:split("ABCD","((a)(b)c)(d)",[caseless]))), + 2}]))), + <<":ABC:A:B:D:">> = iolist_to_binary(join(re:split("ABCD","((a)(b)c)(d)",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ALPHA","[a-zA-Z_][a-zA-Z0-9_]*",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ALPHA","[a-zA-Z_][a-zA-Z0-9_]*",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ALPHA","[a-zA-Z_][a-zA-Z0-9_]*",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ALPHA","[a-zA-Z_][a-zA-Z0-9_]*",[caseless]))), <<"A">> = iolist_to_binary(join(re:split("ABH","^a(bc+|b[eh])g|.h$",[caseless, - trim]))), + trim]))), <<"A::">> = iolist_to_binary(join(re:split("ABH","^a(bc+|b[eh])g|.h$",[caseless, {parts, - 2}]))), - <<"A::">> = iolist_to_binary(join(re:split("ABH","^a(bc+|b[eh])g|.h$",[caseless]))), + 2}]))), + <<"A::">> = iolist_to_binary(join(re:split("ABH","^a(bc+|b[eh])g|.h$",[caseless]))), <<":EFFGZ">> = iolist_to_binary(join(re:split("EFFGZ","(bc+d$|ef*g.|h?i(j|k))",[caseless, - trim]))), + trim]))), <<":EFFGZ::">> = iolist_to_binary(join(re:split("EFFGZ","(bc+d$|ef*g.|h?i(j|k))",[caseless, {parts, - 2}]))), - <<":EFFGZ::">> = iolist_to_binary(join(re:split("EFFGZ","(bc+d$|ef*g.|h?i(j|k))",[caseless]))), + 2}]))), + <<":EFFGZ::">> = iolist_to_binary(join(re:split("EFFGZ","(bc+d$|ef*g.|h?i(j|k))",[caseless]))), <<":IJ:J">> = iolist_to_binary(join(re:split("IJ","(bc+d$|ef*g.|h?i(j|k))",[caseless, - trim]))), + trim]))), <<":IJ:J:">> = iolist_to_binary(join(re:split("IJ","(bc+d$|ef*g.|h?i(j|k))",[caseless, {parts, - 2}]))), - <<":IJ:J:">> = iolist_to_binary(join(re:split("IJ","(bc+d$|ef*g.|h?i(j|k))",[caseless]))), + 2}]))), + <<":IJ:J:">> = iolist_to_binary(join(re:split("IJ","(bc+d$|ef*g.|h?i(j|k))",[caseless]))), <<"R:EFFGZ">> = iolist_to_binary(join(re:split("REFFGZ","(bc+d$|ef*g.|h?i(j|k))",[caseless, - trim]))), + trim]))), <<"R:EFFGZ::">> = iolist_to_binary(join(re:split("REFFGZ","(bc+d$|ef*g.|h?i(j|k))",[caseless, {parts, - 2}]))), - <<"R:EFFGZ::">> = iolist_to_binary(join(re:split("REFFGZ","(bc+d$|ef*g.|h?i(j|k))",[caseless]))), + 2}]))), + <<"R:EFFGZ::">> = iolist_to_binary(join(re:split("REFFGZ","(bc+d$|ef*g.|h?i(j|k))",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(bc+d$|ef*g.|h?i(j|k))",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(bc+d$|ef*g.|h?i(j|k))",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(bc+d$|ef*g.|h?i(j|k))",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(bc+d$|ef*g.|h?i(j|k))",[caseless]))), <<"ADCDCDE">> = iolist_to_binary(join(re:split("ADCDCDE","(bc+d$|ef*g.|h?i(j|k))",[caseless, - trim]))), + trim]))), <<"ADCDCDE">> = iolist_to_binary(join(re:split("ADCDCDE","(bc+d$|ef*g.|h?i(j|k))",[caseless, {parts, - 2}]))), - <<"ADCDCDE">> = iolist_to_binary(join(re:split("ADCDCDE","(bc+d$|ef*g.|h?i(j|k))",[caseless]))), + 2}]))), + <<"ADCDCDE">> = iolist_to_binary(join(re:split("ADCDCDE","(bc+d$|ef*g.|h?i(j|k))",[caseless]))), <<"EFFG">> = iolist_to_binary(join(re:split("EFFG","(bc+d$|ef*g.|h?i(j|k))",[caseless, - trim]))), + trim]))), <<"EFFG">> = iolist_to_binary(join(re:split("EFFG","(bc+d$|ef*g.|h?i(j|k))",[caseless, {parts, - 2}]))), - <<"EFFG">> = iolist_to_binary(join(re:split("EFFG","(bc+d$|ef*g.|h?i(j|k))",[caseless]))), + 2}]))), + <<"EFFG">> = iolist_to_binary(join(re:split("EFFG","(bc+d$|ef*g.|h?i(j|k))",[caseless]))), <<"BCDD">> = iolist_to_binary(join(re:split("BCDD","(bc+d$|ef*g.|h?i(j|k))",[caseless, - trim]))), + trim]))), <<"BCDD">> = iolist_to_binary(join(re:split("BCDD","(bc+d$|ef*g.|h?i(j|k))",[caseless, {parts, - 2}]))), - <<"BCDD">> = iolist_to_binary(join(re:split("BCDD","(bc+d$|ef*g.|h?i(j|k))",[caseless]))), + 2}]))), + <<"BCDD">> = iolist_to_binary(join(re:split("BCDD","(bc+d$|ef*g.|h?i(j|k))",[caseless]))), <<":A:A:A:A:A:A:A:A:A:A">> = iolist_to_binary(join(re:split("A","((((((((((a))))))))))",[caseless, - trim]))), + trim]))), <<":A:A:A:A:A:A:A:A:A:A:">> = iolist_to_binary(join(re:split("A","((((((((((a))))))))))",[caseless, {parts, - 2}]))), - <<":A:A:A:A:A:A:A:A:A:A:">> = iolist_to_binary(join(re:split("A","((((((((((a))))))))))",[caseless]))), + 2}]))), + <<":A:A:A:A:A:A:A:A:A:A:">> = iolist_to_binary(join(re:split("A","((((((((((a))))))))))",[caseless]))), <<":A:A:A:A:A:A:A:A:A:A">> = iolist_to_binary(join(re:split("AA","((((((((((a))))))))))\\10",[caseless, - trim]))), + trim]))), <<":A:A:A:A:A:A:A:A:A:A:">> = iolist_to_binary(join(re:split("AA","((((((((((a))))))))))\\10",[caseless, {parts, - 2}]))), - <<":A:A:A:A:A:A:A:A:A:A:">> = iolist_to_binary(join(re:split("AA","((((((((((a))))))))))\\10",[caseless]))), + 2}]))), + <<":A:A:A:A:A:A:A:A:A:A:">> = iolist_to_binary(join(re:split("AA","((((((((((a))))))))))\\10",[caseless]))), <<":A:A:A:A:A:A:A:A:A">> = iolist_to_binary(join(re:split("A","(((((((((a)))))))))",[caseless, - trim]))), + trim]))), <<":A:A:A:A:A:A:A:A:A:">> = iolist_to_binary(join(re:split("A","(((((((((a)))))))))",[caseless, {parts, - 2}]))), - <<":A:A:A:A:A:A:A:A:A:">> = iolist_to_binary(join(re:split("A","(((((((((a)))))))))",[caseless]))), + 2}]))), + <<":A:A:A:A:A:A:A:A:A:">> = iolist_to_binary(join(re:split("A","(((((((((a)))))))))",[caseless]))), <<":A">> = iolist_to_binary(join(re:split("A","(?:(?:(?:(?:(?:(?:(?:(?:(?:(a))))))))))",[caseless, - trim]))), + trim]))), <<":A:">> = iolist_to_binary(join(re:split("A","(?:(?:(?:(?:(?:(?:(?:(?:(?:(a))))))))))",[caseless, {parts, - 2}]))), - <<":A:">> = iolist_to_binary(join(re:split("A","(?:(?:(?:(?:(?:(?:(?:(?:(?:(a))))))))))",[caseless]))), + 2}]))), + <<":A:">> = iolist_to_binary(join(re:split("A","(?:(?:(?:(?:(?:(?:(?:(?:(?:(a))))))))))",[caseless]))), <<":C">> = iolist_to_binary(join(re:split("C","(?:(?:(?:(?:(?:(?:(?:(?:(?:(a|b|c))))))))))",[caseless, - trim]))), + trim]))), <<":C:">> = iolist_to_binary(join(re:split("C","(?:(?:(?:(?:(?:(?:(?:(?:(?:(a|b|c))))))))))",[caseless, {parts, - 2}]))), - <<":C:">> = iolist_to_binary(join(re:split("C","(?:(?:(?:(?:(?:(?:(?:(?:(?:(a|b|c))))))))))",[caseless]))), + 2}]))), + <<":C:">> = iolist_to_binary(join(re:split("C","(?:(?:(?:(?:(?:(?:(?:(?:(?:(a|b|c))))))))))",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","multiple words of text",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","multiple words of text",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","multiple words of text",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","multiple words of text",[caseless]))), <<"AA">> = iolist_to_binary(join(re:split("AA","multiple words of text",[caseless, - trim]))), + trim]))), <<"AA">> = iolist_to_binary(join(re:split("AA","multiple words of text",[caseless, {parts, - 2}]))), - <<"AA">> = iolist_to_binary(join(re:split("AA","multiple words of text",[caseless]))), + 2}]))), + <<"AA">> = iolist_to_binary(join(re:split("AA","multiple words of text",[caseless]))), <<"UH-UH">> = iolist_to_binary(join(re:split("UH-UH","multiple words of text",[caseless, - trim]))), + trim]))), <<"UH-UH">> = iolist_to_binary(join(re:split("UH-UH","multiple words of text",[caseless, {parts, - 2}]))), - <<"UH-UH">> = iolist_to_binary(join(re:split("UH-UH","multiple words of text",[caseless]))), + 2}]))), + <<"UH-UH">> = iolist_to_binary(join(re:split("UH-UH","multiple words of text",[caseless]))), <<":, YEAH">> = iolist_to_binary(join(re:split("MULTIPLE WORDS, YEAH","multiple words",[caseless, - trim]))), + trim]))), <<":, YEAH">> = iolist_to_binary(join(re:split("MULTIPLE WORDS, YEAH","multiple words",[caseless, {parts, - 2}]))), - <<":, YEAH">> = iolist_to_binary(join(re:split("MULTIPLE WORDS, YEAH","multiple words",[caseless]))), + 2}]))), + <<":, YEAH">> = iolist_to_binary(join(re:split("MULTIPLE WORDS, YEAH","multiple words",[caseless]))), <<":AB:DE">> = iolist_to_binary(join(re:split("ABCDE","(.*)c(.*)",[caseless, - trim]))), + trim]))), <<":AB:DE:">> = iolist_to_binary(join(re:split("ABCDE","(.*)c(.*)",[caseless, {parts, - 2}]))), - <<":AB:DE:">> = iolist_to_binary(join(re:split("ABCDE","(.*)c(.*)",[caseless]))), + 2}]))), + <<":AB:DE:">> = iolist_to_binary(join(re:split("ABCDE","(.*)c(.*)",[caseless]))), <<":A:B">> = iolist_to_binary(join(re:split("(A, B)","\\((.*), (.*)\\)",[caseless, - trim]))), + trim]))), <<":A:B:">> = iolist_to_binary(join(re:split("(A, B)","\\((.*), (.*)\\)",[caseless, {parts, - 2}]))), - <<":A:B:">> = iolist_to_binary(join(re:split("(A, B)","\\((.*), (.*)\\)",[caseless]))), + 2}]))), + <<":A:B:">> = iolist_to_binary(join(re:split("(A, B)","\\((.*), (.*)\\)",[caseless]))), ok. run25() -> <<"">> = iolist_to_binary(join(re:split("ABCD","abcd",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ABCD","abcd",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ABCD","abcd",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ABCD","abcd",[caseless]))), <<":BC">> = iolist_to_binary(join(re:split("ABCD","a(bc)d",[caseless, - trim]))), + trim]))), <<":BC:">> = iolist_to_binary(join(re:split("ABCD","a(bc)d",[caseless, {parts, - 2}]))), - <<":BC:">> = iolist_to_binary(join(re:split("ABCD","a(bc)d",[caseless]))), + 2}]))), + <<":BC:">> = iolist_to_binary(join(re:split("ABCD","a(bc)d",[caseless]))), <<"">> = iolist_to_binary(join(re:split("AC","a[-]?c",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("AC","a[-]?c",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("AC","a[-]?c",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("AC","a[-]?c",[caseless]))), <<":ABC">> = iolist_to_binary(join(re:split("ABCABC","(abc)\\1",[caseless, - trim]))), + trim]))), <<":ABC:">> = iolist_to_binary(join(re:split("ABCABC","(abc)\\1",[caseless, {parts, - 2}]))), - <<":ABC:">> = iolist_to_binary(join(re:split("ABCABC","(abc)\\1",[caseless]))), + 2}]))), + <<":ABC:">> = iolist_to_binary(join(re:split("ABCABC","(abc)\\1",[caseless]))), <<":ABC">> = iolist_to_binary(join(re:split("ABCABC","([a-c]*)\\1",[caseless, - trim]))), + trim]))), <<":ABC:">> = iolist_to_binary(join(re:split("ABCABC","([a-c]*)\\1",[caseless, {parts, - 2}]))), - <<":ABC:">> = iolist_to_binary(join(re:split("ABCABC","([a-c]*)\\1",[caseless]))), - <<"ab">> = iolist_to_binary(join(re:split("abad","a(?!b).",[trim]))), + 2}]))), + <<":ABC:">> = iolist_to_binary(join(re:split("ABCABC","([a-c]*)\\1",[caseless]))), + <<"ab">> = iolist_to_binary(join(re:split("abad","a(?!b).",[trim]))), <<"ab:">> = iolist_to_binary(join(re:split("abad","a(?!b).",[{parts, - 2}]))), - <<"ab:">> = iolist_to_binary(join(re:split("abad","a(?!b).",[]))), - <<"ab">> = iolist_to_binary(join(re:split("abad","a(?=d).",[trim]))), + 2}]))), + <<"ab:">> = iolist_to_binary(join(re:split("abad","a(?!b).",[]))), + <<"ab">> = iolist_to_binary(join(re:split("abad","a(?=d).",[trim]))), <<"ab:">> = iolist_to_binary(join(re:split("abad","a(?=d).",[{parts, - 2}]))), - <<"ab:">> = iolist_to_binary(join(re:split("abad","a(?=d).",[]))), - <<"ab">> = iolist_to_binary(join(re:split("abad","a(?=c|d).",[trim]))), + 2}]))), + <<"ab:">> = iolist_to_binary(join(re:split("abad","a(?=d).",[]))), + <<"ab">> = iolist_to_binary(join(re:split("abad","a(?=c|d).",[trim]))), <<"ab:">> = iolist_to_binary(join(re:split("abad","a(?=c|d).",[{parts, - 2}]))), - <<"ab:">> = iolist_to_binary(join(re:split("abad","a(?=c|d).",[]))), - <<":e">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)(.)",[trim]))), + 2}]))), + <<"ab:">> = iolist_to_binary(join(re:split("abad","a(?=c|d).",[]))), + <<":e">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)(.)",[trim]))), <<":e:">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)(.)",[{parts, - 2}]))), - <<":e:">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)(.)",[]))), - <<":e">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)*(.)",[trim]))), + 2}]))), + <<":e:">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)(.)",[]))), + <<":e">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)*(.)",[trim]))), <<":e:">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)*(.)",[{parts, - 2}]))), - <<":e:">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)*(.)",[]))), - <<":e">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)+?(.)",[trim]))), + 2}]))), + <<":e:">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)*(.)",[]))), + <<":e">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)+?(.)",[trim]))), <<":e:">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)+?(.)",[{parts, - 2}]))), - <<":e:">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)+?(.)",[]))), - <<":d:bcdbe">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d)+?(.)",[trim]))), + 2}]))), + <<":e:">> = iolist_to_binary(join(re:split("ace","a(?:b|c|d)+?(.)",[]))), + <<":d:bcdbe">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d)+?(.)",[trim]))), <<":d:bcdbe">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d)+?(.)",[{parts, - 2}]))), - <<":d:bcdbe">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d)+?(.)",[]))), - <<":e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d)+(.)",[trim]))), + 2}]))), + <<":d:bcdbe">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d)+?(.)",[]))), + <<":e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d)+(.)",[trim]))), <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d)+(.)",[{parts, - 2}]))), - <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d)+(.)",[]))), - <<":b:cdbe">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){2}(.)",[trim]))), + 2}]))), + <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d)+(.)",[]))), + <<":b:cdbe">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){2}(.)",[trim]))), <<":b:cdbe">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){2}(.)",[{parts, - 2}]))), - <<":b:cdbe">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){2}(.)",[]))), - <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){4,5}(.)",[trim]))), + 2}]))), + <<":b:cdbe">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){2}(.)",[]))), + <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){4,5}(.)",[trim]))), <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){4,5}(.)",[{parts, - 2}]))), - <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){4,5}(.)",[]))), - <<":d:be">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){4,5}?(.)",[trim]))), + 2}]))), + <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){4,5}(.)",[]))), + <<":d:be">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){4,5}?(.)",[trim]))), <<":d:be">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){4,5}?(.)",[{parts, - 2}]))), - <<":d:be">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){4,5}?(.)",[]))), - <<":bar:foo:bar">> = iolist_to_binary(join(re:split("foobar","((foo)|(bar))*",[trim]))), + 2}]))), + <<":d:be">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){4,5}?(.)",[]))), + <<":bar:foo:bar">> = iolist_to_binary(join(re:split("foobar","((foo)|(bar))*",[trim]))), <<":bar:foo:bar:">> = iolist_to_binary(join(re:split("foobar","((foo)|(bar))*",[{parts, - 2}]))), - <<":bar:foo:bar:">> = iolist_to_binary(join(re:split("foobar","((foo)|(bar))*",[]))), - <<":e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){6,7}(.)",[trim]))), + 2}]))), + <<":bar:foo:bar:">> = iolist_to_binary(join(re:split("foobar","((foo)|(bar))*",[]))), + <<":e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){6,7}(.)",[trim]))), <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){6,7}(.)",[{parts, - 2}]))), - <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){6,7}(.)",[]))), - <<":e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){6,7}?(.)",[trim]))), + 2}]))), + <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){6,7}(.)",[]))), + <<":e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){6,7}?(.)",[trim]))), <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){6,7}?(.)",[{parts, - 2}]))), - <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){6,7}?(.)",[]))), - <<":e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,6}(.)",[trim]))), + 2}]))), + <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){6,7}?(.)",[]))), + <<":e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,6}(.)",[trim]))), <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,6}(.)",[{parts, - 2}]))), - <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,6}(.)",[]))), - <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,6}?(.)",[trim]))), + 2}]))), + <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,6}(.)",[]))), + <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,6}?(.)",[trim]))), <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,6}?(.)",[{parts, - 2}]))), - <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,6}?(.)",[]))), + 2}]))), + <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,6}?(.)",[]))), ok. run26() -> - <<":e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,7}(.)",[trim]))), + <<":e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,7}(.)",[trim]))), <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,7}(.)",[{parts, - 2}]))), - <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,7}(.)",[]))), - <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,7}?(.)",[trim]))), + 2}]))), + <<":e:">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,7}(.)",[]))), + <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,7}?(.)",[trim]))), <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,7}?(.)",[{parts, - 2}]))), - <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,7}?(.)",[]))), - <<":c:e">> = iolist_to_binary(join(re:split("ace","a(?:b|(c|e){1,2}?|d)+?(.)",[trim]))), + 2}]))), + <<":b:e">> = iolist_to_binary(join(re:split("acdbcdbe","a(?:b|c|d){5,7}?(.)",[]))), + <<":c:e">> = iolist_to_binary(join(re:split("ace","a(?:b|(c|e){1,2}?|d)+?(.)",[trim]))), <<":c:e:">> = iolist_to_binary(join(re:split("ace","a(?:b|(c|e){1,2}?|d)+?(.)",[{parts, - 2}]))), - <<":c:e:">> = iolist_to_binary(join(re:split("ace","a(?:b|(c|e){1,2}?|d)+?(.)",[]))), - <<":A">> = iolist_to_binary(join(re:split("AB","^(.+)?B",[trim]))), + 2}]))), + <<":c:e:">> = iolist_to_binary(join(re:split("ace","a(?:b|(c|e){1,2}?|d)+?(.)",[]))), + <<":A">> = iolist_to_binary(join(re:split("AB","^(.+)?B",[trim]))), <<":A:">> = iolist_to_binary(join(re:split("AB","^(.+)?B",[{parts, - 2}]))), - <<":A:">> = iolist_to_binary(join(re:split("AB","^(.+)?B",[]))), - <<":.">> = iolist_to_binary(join(re:split(".","^([^a-z])|(\\^)$",[trim]))), + 2}]))), + <<":A:">> = iolist_to_binary(join(re:split("AB","^(.+)?B",[]))), + <<":.">> = iolist_to_binary(join(re:split(".","^([^a-z])|(\\^)$",[trim]))), <<":.::">> = iolist_to_binary(join(re:split(".","^([^a-z])|(\\^)$",[{parts, - 2}]))), - <<":.::">> = iolist_to_binary(join(re:split(".","^([^a-z])|(\\^)$",[]))), - <<":OUT">> = iolist_to_binary(join(re:split("<&OUT","^[<>]&",[trim]))), + 2}]))), + <<":.::">> = iolist_to_binary(join(re:split(".","^([^a-z])|(\\^)$",[]))), + <<":OUT">> = iolist_to_binary(join(re:split("<&OUT","^[<>]&",[trim]))), <<":OUT">> = iolist_to_binary(join(re:split("<&OUT","^[<>]&",[{parts, - 2}]))), - <<":OUT">> = iolist_to_binary(join(re:split("<&OUT","^[<>]&",[]))), - <<":aaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?){4}$",[trim]))), + 2}]))), + <<":OUT">> = iolist_to_binary(join(re:split("<&OUT","^[<>]&",[]))), + <<":aaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?){4}$",[trim]))), <<":aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?){4}$",[{parts, - 2}]))), - <<":aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?){4}$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a\\1?){4}$",[trim]))), + 2}]))), + <<":aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a\\1?){4}$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a\\1?){4}$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a\\1?){4}$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a\\1?){4}$",[]))), - <<"AB">> = iolist_to_binary(join(re:split("AB","^(a\\1?){4}$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a\\1?){4}$",[]))), + <<"AB">> = iolist_to_binary(join(re:split("AB","^(a\\1?){4}$",[trim]))), <<"AB">> = iolist_to_binary(join(re:split("AB","^(a\\1?){4}$",[{parts, - 2}]))), - <<"AB">> = iolist_to_binary(join(re:split("AB","^(a\\1?){4}$",[]))), - <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?){4}$",[trim]))), + 2}]))), + <<"AB">> = iolist_to_binary(join(re:split("AB","^(a\\1?){4}$",[]))), + <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?){4}$",[trim]))), <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?){4}$",[{parts, - 2}]))), - <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?){4}$",[]))), - <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?){4}$",[trim]))), + 2}]))), + <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a\\1?){4}$",[]))), + <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?){4}$",[trim]))), <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?){4}$",[{parts, - 2}]))), - <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?){4}$",[]))), - <<":aaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a(?(1)\\1)){4}$",[trim]))), + 2}]))), + <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a\\1?){4}$",[]))), + <<":aaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a(?(1)\\1)){4}$",[trim]))), <<":aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a(?(1)\\1)){4}$",[{parts, - 2}]))), - <<":aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a(?(1)\\1)){4}$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a(?(1)\\1)){4}$",[trim]))), + 2}]))), + <<":aaaa:">> = iolist_to_binary(join(re:split("aaaaaaaaaa","^(a(?(1)\\1)){4}$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a(?(1)\\1)){4}$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a(?(1)\\1)){4}$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a(?(1)\\1)){4}$",[]))), - <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a(?(1)\\1)){4}$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a(?(1)\\1)){4}$",[]))), + <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a(?(1)\\1)){4}$",[trim]))), <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a(?(1)\\1)){4}$",[{parts, - 2}]))), - <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a(?(1)\\1)){4}$",[]))), - <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a(?(1)\\1)){4}$",[trim]))), + 2}]))), + <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a(?(1)\\1)){4}$",[]))), + <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a(?(1)\\1)){4}$",[trim]))), <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a(?(1)\\1)){4}$",[{parts, - 2}]))), - <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a(?(1)\\1)){4}$",[]))), - <<":f:o:o:b:a:r">> = iolist_to_binary(join(re:split("foobar","(?:(f)(o)(o)|(b)(a)(r))*",[trim]))), + 2}]))), + <<"aaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaa","^(a(?(1)\\1)){4}$",[]))), + <<":f:o:o:b:a:r">> = iolist_to_binary(join(re:split("foobar","(?:(f)(o)(o)|(b)(a)(r))*",[trim]))), <<":f:o:o:b:a:r:">> = iolist_to_binary(join(re:split("foobar","(?:(f)(o)(o)|(b)(a)(r))*",[{parts, - 2}]))), - <<":f:o:o:b:a:r:">> = iolist_to_binary(join(re:split("foobar","(?:(f)(o)(o)|(b)(a)(r))*",[]))), - <<"a">> = iolist_to_binary(join(re:split("ab","(?<=a)b",[trim]))), + 2}]))), + <<":f:o:o:b:a:r:">> = iolist_to_binary(join(re:split("foobar","(?:(f)(o)(o)|(b)(a)(r))*",[]))), + <<"a">> = iolist_to_binary(join(re:split("ab","(?<=a)b",[trim]))), <<"a:">> = iolist_to_binary(join(re:split("ab","(?<=a)b",[{parts, - 2}]))), - <<"a:">> = iolist_to_binary(join(re:split("ab","(?<=a)b",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=a)b",[trim]))), + 2}]))), + <<"a:">> = iolist_to_binary(join(re:split("ab","(?<=a)b",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=a)b",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=a)b",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=a)b",[]))), - <<"cb">> = iolist_to_binary(join(re:split("cb","(?<=a)b",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=a)b",[]))), + <<"cb">> = iolist_to_binary(join(re:split("cb","(?<=a)b",[trim]))), <<"cb">> = iolist_to_binary(join(re:split("cb","(?<=a)b",[{parts, - 2}]))), - <<"cb">> = iolist_to_binary(join(re:split("cb","(?<=a)b",[]))), - <<"b">> = iolist_to_binary(join(re:split("b","(?<=a)b",[trim]))), + 2}]))), + <<"cb">> = iolist_to_binary(join(re:split("cb","(?<=a)b",[]))), + <<"b">> = iolist_to_binary(join(re:split("b","(?<=a)b",[trim]))), <<"b">> = iolist_to_binary(join(re:split("b","(?<=a)b",[{parts, - 2}]))), - <<"b">> = iolist_to_binary(join(re:split("b","(?<=a)b",[]))), - <<"a">> = iolist_to_binary(join(re:split("ab","(?<!c)b",[trim]))), + 2}]))), + <<"b">> = iolist_to_binary(join(re:split("b","(?<=a)b",[]))), + <<"a">> = iolist_to_binary(join(re:split("ab","(?<!c)b",[trim]))), <<"a:">> = iolist_to_binary(join(re:split("ab","(?<!c)b",[{parts, - 2}]))), - <<"a:">> = iolist_to_binary(join(re:split("ab","(?<!c)b",[]))), - <<"">> = iolist_to_binary(join(re:split("b","(?<!c)b",[trim]))), + 2}]))), + <<"a:">> = iolist_to_binary(join(re:split("ab","(?<!c)b",[]))), + <<"">> = iolist_to_binary(join(re:split("b","(?<!c)b",[trim]))), <<":">> = iolist_to_binary(join(re:split("b","(?<!c)b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("b","(?<!c)b",[]))), - <<"">> = iolist_to_binary(join(re:split("b","(?<!c)b",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("b","(?<!c)b",[]))), + <<"">> = iolist_to_binary(join(re:split("b","(?<!c)b",[trim]))), <<":">> = iolist_to_binary(join(re:split("b","(?<!c)b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("b","(?<!c)b",[]))), - <<"">> = iolist_to_binary(join(re:split("aba","(?:..)*a",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("b","(?<!c)b",[]))), + <<"">> = iolist_to_binary(join(re:split("aba","(?:..)*a",[trim]))), <<":">> = iolist_to_binary(join(re:split("aba","(?:..)*a",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aba","(?:..)*a",[]))), - <<":b">> = iolist_to_binary(join(re:split("aba","(?:..)*?a",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aba","(?:..)*a",[]))), + <<":b">> = iolist_to_binary(join(re:split("aba","(?:..)*?a",[trim]))), <<":ba">> = iolist_to_binary(join(re:split("aba","(?:..)*?a",[{parts, - 2}]))), - <<":b:">> = iolist_to_binary(join(re:split("aba","(?:..)*?a",[]))), - <<":b:c">> = iolist_to_binary(join(re:split("abc","^(?:b|a(?=(.)))*\\1",[trim]))), + 2}]))), + <<":b:">> = iolist_to_binary(join(re:split("aba","(?:..)*?a",[]))), + <<":b:c">> = iolist_to_binary(join(re:split("abc","^(?:b|a(?=(.)))*\\1",[trim]))), <<":b:c">> = iolist_to_binary(join(re:split("abc","^(?:b|a(?=(.)))*\\1",[{parts, - 2}]))), - <<":b:c">> = iolist_to_binary(join(re:split("abc","^(?:b|a(?=(.)))*\\1",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","^(){3,5}",[trim]))), + 2}]))), + <<":b:c">> = iolist_to_binary(join(re:split("abc","^(?:b|a(?=(.)))*\\1",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","^(){3,5}",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","^(){3,5}",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","^(){3,5}",[]))), - <<":a">> = iolist_to_binary(join(re:split("aax","^(a+)*ax",[trim]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","^(){3,5}",[]))), + <<":a">> = iolist_to_binary(join(re:split("aax","^(a+)*ax",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aax","^(a+)*ax",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aax","^(a+)*ax",[]))), - <<":a:a">> = iolist_to_binary(join(re:split("aax","^((a|b)+)*ax",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aax","^(a+)*ax",[]))), + <<":a:a">> = iolist_to_binary(join(re:split("aax","^((a|b)+)*ax",[trim]))), <<":a:a:">> = iolist_to_binary(join(re:split("aax","^((a|b)+)*ax",[{parts, - 2}]))), - <<":a:a:">> = iolist_to_binary(join(re:split("aax","^((a|b)+)*ax",[]))), - <<":a:a">> = iolist_to_binary(join(re:split("aax","^((a|bc)+)*ax",[trim]))), + 2}]))), + <<":a:a:">> = iolist_to_binary(join(re:split("aax","^((a|b)+)*ax",[]))), + <<":a:a">> = iolist_to_binary(join(re:split("aax","^((a|bc)+)*ax",[trim]))), <<":a:a:">> = iolist_to_binary(join(re:split("aax","^((a|bc)+)*ax",[{parts, - 2}]))), - <<":a:a:">> = iolist_to_binary(join(re:split("aax","^((a|bc)+)*ax",[]))), - <<"c">> = iolist_to_binary(join(re:split("cab","(a|x)*ab",[trim]))), + 2}]))), + <<":a:a:">> = iolist_to_binary(join(re:split("aax","^((a|bc)+)*ax",[]))), + <<"c">> = iolist_to_binary(join(re:split("cab","(a|x)*ab",[trim]))), <<"c::">> = iolist_to_binary(join(re:split("cab","(a|x)*ab",[{parts, - 2}]))), - <<"c::">> = iolist_to_binary(join(re:split("cab","(a|x)*ab",[]))), - <<"c">> = iolist_to_binary(join(re:split("cab","(a)*ab",[trim]))), + 2}]))), + <<"c::">> = iolist_to_binary(join(re:split("cab","(a|x)*ab",[]))), + <<"c">> = iolist_to_binary(join(re:split("cab","(a)*ab",[trim]))), <<"c::">> = iolist_to_binary(join(re:split("cab","(a)*ab",[{parts, - 2}]))), - <<"c::">> = iolist_to_binary(join(re:split("cab","(a)*ab",[]))), + 2}]))), + <<"c::">> = iolist_to_binary(join(re:split("cab","(a)*ab",[]))), ok. run27() -> - <<"">> = iolist_to_binary(join(re:split("ab","(?:(?i)a)b",[trim]))), + <<"">> = iolist_to_binary(join(re:split("ab","(?:(?i)a)b",[trim]))), <<":">> = iolist_to_binary(join(re:split("ab","(?:(?i)a)b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ab","(?:(?i)a)b",[]))), - <<":a">> = iolist_to_binary(join(re:split("ab","((?i)a)b",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ab","(?:(?i)a)b",[]))), + <<":a">> = iolist_to_binary(join(re:split("ab","((?i)a)b",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("ab","((?i)a)b",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("ab","((?i)a)b",[]))), - <<"">> = iolist_to_binary(join(re:split("Ab","(?:(?i)a)b",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("ab","((?i)a)b",[]))), + <<"">> = iolist_to_binary(join(re:split("Ab","(?:(?i)a)b",[trim]))), <<":">> = iolist_to_binary(join(re:split("Ab","(?:(?i)a)b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("Ab","(?:(?i)a)b",[]))), - <<":A">> = iolist_to_binary(join(re:split("Ab","((?i)a)b",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("Ab","(?:(?i)a)b",[]))), + <<":A">> = iolist_to_binary(join(re:split("Ab","((?i)a)b",[trim]))), <<":A:">> = iolist_to_binary(join(re:split("Ab","((?i)a)b",[{parts, - 2}]))), - <<":A:">> = iolist_to_binary(join(re:split("Ab","((?i)a)b",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?i)a)b",[trim]))), + 2}]))), + <<":A:">> = iolist_to_binary(join(re:split("Ab","((?i)a)b",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?i)a)b",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?i)a)b",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?i)a)b",[]))), - <<"cb">> = iolist_to_binary(join(re:split("cb","(?:(?i)a)b",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?i)a)b",[]))), + <<"cb">> = iolist_to_binary(join(re:split("cb","(?:(?i)a)b",[trim]))), <<"cb">> = iolist_to_binary(join(re:split("cb","(?:(?i)a)b",[{parts, - 2}]))), - <<"cb">> = iolist_to_binary(join(re:split("cb","(?:(?i)a)b",[]))), - <<"aB">> = iolist_to_binary(join(re:split("aB","(?:(?i)a)b",[trim]))), + 2}]))), + <<"cb">> = iolist_to_binary(join(re:split("cb","(?:(?i)a)b",[]))), + <<"aB">> = iolist_to_binary(join(re:split("aB","(?:(?i)a)b",[trim]))), <<"aB">> = iolist_to_binary(join(re:split("aB","(?:(?i)a)b",[{parts, - 2}]))), - <<"aB">> = iolist_to_binary(join(re:split("aB","(?:(?i)a)b",[]))), - <<"">> = iolist_to_binary(join(re:split("ab","(?i:a)b",[trim]))), + 2}]))), + <<"aB">> = iolist_to_binary(join(re:split("aB","(?:(?i)a)b",[]))), + <<"">> = iolist_to_binary(join(re:split("ab","(?i:a)b",[trim]))), <<":">> = iolist_to_binary(join(re:split("ab","(?i:a)b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ab","(?i:a)b",[]))), - <<":a">> = iolist_to_binary(join(re:split("ab","((?i:a))b",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ab","(?i:a)b",[]))), + <<":a">> = iolist_to_binary(join(re:split("ab","((?i:a))b",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("ab","((?i:a))b",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("ab","((?i:a))b",[]))), - <<"">> = iolist_to_binary(join(re:split("Ab","(?i:a)b",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("ab","((?i:a))b",[]))), + <<"">> = iolist_to_binary(join(re:split("Ab","(?i:a)b",[trim]))), <<":">> = iolist_to_binary(join(re:split("Ab","(?i:a)b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("Ab","(?i:a)b",[]))), - <<":A">> = iolist_to_binary(join(re:split("Ab","((?i:a))b",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("Ab","(?i:a)b",[]))), + <<":A">> = iolist_to_binary(join(re:split("Ab","((?i:a))b",[trim]))), <<":A:">> = iolist_to_binary(join(re:split("Ab","((?i:a))b",[{parts, - 2}]))), - <<":A:">> = iolist_to_binary(join(re:split("Ab","((?i:a))b",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?i:a)b",[trim]))), + 2}]))), + <<":A:">> = iolist_to_binary(join(re:split("Ab","((?i:a))b",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?i:a)b",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?i:a)b",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?i:a)b",[]))), - <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:a)b",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?i:a)b",[]))), + <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:a)b",[trim]))), <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:a)b",[{parts, - 2}]))), - <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:a)b",[]))), - <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:a)b",[trim]))), + 2}]))), + <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:a)b",[]))), + <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:a)b",[trim]))), <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:a)b",[{parts, - 2}]))), - <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:a)b",[]))), + 2}]))), + <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:a)b",[]))), <<"">> = iolist_to_binary(join(re:split("ab","(?:(?-i)a)b",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ab","(?:(?-i)a)b",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ab","(?:(?-i)a)b",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ab","(?:(?-i)a)b",[caseless]))), <<":a">> = iolist_to_binary(join(re:split("ab","((?-i)a)b",[caseless, - trim]))), + trim]))), <<":a:">> = iolist_to_binary(join(re:split("ab","((?-i)a)b",[caseless, {parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("ab","((?-i)a)b",[caseless]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("ab","((?-i)a)b",[caseless]))), <<"">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless]))), <<":a">> = iolist_to_binary(join(re:split("aB","((?-i)a)b",[caseless, - trim]))), + trim]))), <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i)a)b",[caseless, {parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i)a)b",[caseless]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i)a)b",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?-i)a)b",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?-i)a)b",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?-i)a)b",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?-i)a)b",[caseless]))), <<"">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless]))), <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?:(?-i)a)b",[caseless, - trim]))), + trim]))), <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?:(?-i)a)b",[caseless, {parts, - 2}]))), - <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?:(?-i)a)b",[caseless]))), + 2}]))), + <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?:(?-i)a)b",[caseless]))), <<"">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aB","(?:(?-i)a)b",[caseless]))), <<":a">> = iolist_to_binary(join(re:split("aB","((?-i)a)b",[caseless, - trim]))), + trim]))), <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i)a)b",[caseless, {parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i)a)b",[caseless]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i)a)b",[caseless]))), ok. run28() -> <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?-i)a)b",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?-i)a)b",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?-i)a)b",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?:(?-i)a)b",[caseless]))), <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?:(?-i)a)b",[caseless, - trim]))), + trim]))), <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?:(?-i)a)b",[caseless, {parts, - 2}]))), - <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?:(?-i)a)b",[caseless]))), + 2}]))), + <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?:(?-i)a)b",[caseless]))), <<"AB">> = iolist_to_binary(join(re:split("AB","(?:(?-i)a)b",[caseless, - trim]))), + trim]))), <<"AB">> = iolist_to_binary(join(re:split("AB","(?:(?-i)a)b",[caseless, {parts, - 2}]))), - <<"AB">> = iolist_to_binary(join(re:split("AB","(?:(?-i)a)b",[caseless]))), + 2}]))), + <<"AB">> = iolist_to_binary(join(re:split("AB","(?:(?-i)a)b",[caseless]))), <<"">> = iolist_to_binary(join(re:split("ab","(?-i:a)b",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ab","(?-i:a)b",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ab","(?-i:a)b",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ab","(?-i:a)b",[caseless]))), <<":a">> = iolist_to_binary(join(re:split("ab","((?-i:a))b",[caseless, - trim]))), + trim]))), <<":a:">> = iolist_to_binary(join(re:split("ab","((?-i:a))b",[caseless, {parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("ab","((?-i:a))b",[caseless]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("ab","((?-i:a))b",[caseless]))), <<"">> = iolist_to_binary(join(re:split("aB","(?-i:a)b",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("aB","(?-i:a)b",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aB","(?-i:a)b",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aB","(?-i:a)b",[caseless]))), <<":a">> = iolist_to_binary(join(re:split("aB","((?-i:a))b",[caseless, - trim]))), + trim]))), <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i:a))b",[caseless, {parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i:a))b",[caseless]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i:a))b",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?-i:a)b",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?-i:a)b",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?-i:a)b",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?-i:a)b",[caseless]))), <<"AB">> = iolist_to_binary(join(re:split("AB","(?-i:a)b",[caseless, - trim]))), + trim]))), <<"AB">> = iolist_to_binary(join(re:split("AB","(?-i:a)b",[caseless, {parts, - 2}]))), - <<"AB">> = iolist_to_binary(join(re:split("AB","(?-i:a)b",[caseless]))), + 2}]))), + <<"AB">> = iolist_to_binary(join(re:split("AB","(?-i:a)b",[caseless]))), <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?-i:a)b",[caseless, - trim]))), + trim]))), <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?-i:a)b",[caseless, {parts, - 2}]))), - <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?-i:a)b",[caseless]))), + 2}]))), + <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?-i:a)b",[caseless]))), <<"">> = iolist_to_binary(join(re:split("aB","(?-i:a)b",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("aB","(?-i:a)b",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aB","(?-i:a)b",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aB","(?-i:a)b",[caseless]))), <<":a">> = iolist_to_binary(join(re:split("aB","((?-i:a))b",[caseless, - trim]))), + trim]))), <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i:a))b",[caseless, {parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i:a))b",[caseless]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aB","((?-i:a))b",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?-i:a)b",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?-i:a)b",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?-i:a)b",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?-i:a)b",[caseless]))), <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?-i:a)b",[caseless, - trim]))), + trim]))), <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?-i:a)b",[caseless, {parts, - 2}]))), - <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?-i:a)b",[caseless]))), + 2}]))), + <<"Ab">> = iolist_to_binary(join(re:split("Ab","(?-i:a)b",[caseless]))), <<"AB">> = iolist_to_binary(join(re:split("AB","(?-i:a)b",[caseless, - trim]))), + trim]))), <<"AB">> = iolist_to_binary(join(re:split("AB","(?-i:a)b",[caseless, {parts, - 2}]))), - <<"AB">> = iolist_to_binary(join(re:split("AB","(?-i:a)b",[caseless]))), + 2}]))), + <<"AB">> = iolist_to_binary(join(re:split("AB","(?-i:a)b",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?-i:a.))b",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?-i:a.))b",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?-i:a.))b",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?-i:a.))b",[caseless]))), <<"AB">> = iolist_to_binary(join(re:split("AB","((?-i:a.))b",[caseless, - trim]))), + trim]))), <<"AB">> = iolist_to_binary(join(re:split("AB","((?-i:a.))b",[caseless, {parts, - 2}]))), - <<"AB">> = iolist_to_binary(join(re:split("AB","((?-i:a.))b",[caseless]))), + 2}]))), + <<"AB">> = iolist_to_binary(join(re:split("AB","((?-i:a.))b",[caseless]))), <<"a B">> = iolist_to_binary(join(re:split("a -B","((?-i:a.))b",[caseless,trim]))), +B","((?-i:a.))b",[caseless,trim]))), <<"a B">> = iolist_to_binary(join(re:split("a -B","((?-i:a.))b",[caseless,{parts,2}]))), +B","((?-i:a.))b",[caseless,{parts,2}]))), <<"a B">> = iolist_to_binary(join(re:split("a -B","((?-i:a.))b",[caseless]))), +B","((?-i:a.))b",[caseless]))), <<":a ">> = iolist_to_binary(join(re:split("a -B","((?s-i:a.))b",[caseless,trim]))), +B","((?s-i:a.))b",[caseless,trim]))), <<":a :">> = iolist_to_binary(join(re:split("a -B","((?s-i:a.))b",[caseless,{parts,2}]))), +B","((?s-i:a.))b",[caseless,{parts,2}]))), <<":a :">> = iolist_to_binary(join(re:split("a -B","((?s-i:a.))b",[caseless]))), - <<"">> = iolist_to_binary(join(re:split("cabbbb","(?:c|d)(?:)(?:a(?:)(?:b)(?:b(?:))(?:b(?:)(?:b)))",[trim]))), +B","((?s-i:a.))b",[caseless]))), + <<"">> = iolist_to_binary(join(re:split("cabbbb","(?:c|d)(?:)(?:a(?:)(?:b)(?:b(?:))(?:b(?:)(?:b)))",[trim]))), <<":">> = iolist_to_binary(join(re:split("cabbbb","(?:c|d)(?:)(?:a(?:)(?:b)(?:b(?:))(?:b(?:)(?:b)))",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("cabbbb","(?:c|d)(?:)(?:a(?:)(?:b)(?:b(?:))(?:b(?:)(?:b)))",[]))), - <<"">> = iolist_to_binary(join(re:split("caaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","(?:c|d)(?:)(?:aaaaaaaa(?:)(?:bbbbbbbb)(?:bbbbbbbb(?:))(?:bbbbbbbb(?:)(?:bbbbbbbb)))",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("cabbbb","(?:c|d)(?:)(?:a(?:)(?:b)(?:b(?:))(?:b(?:)(?:b)))",[]))), + <<"">> = iolist_to_binary(join(re:split("caaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","(?:c|d)(?:)(?:aaaaaaaa(?:)(?:bbbbbbbb)(?:bbbbbbbb(?:))(?:bbbbbbbb(?:)(?:bbbbbbbb)))",[trim]))), <<":">> = iolist_to_binary(join(re:split("caaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","(?:c|d)(?:)(?:aaaaaaaa(?:)(?:bbbbbbbb)(?:bbbbbbbb(?:))(?:bbbbbbbb(?:)(?:bbbbbbbb)))",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("caaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","(?:c|d)(?:)(?:aaaaaaaa(?:)(?:bbbbbbbb)(?:bbbbbbbb(?:))(?:bbbbbbbb(?:)(?:bbbbbbbb)))",[]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("caaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","(?:c|d)(?:)(?:aaaaaaaa(?:)(?:bbbbbbbb)(?:bbbbbbbb(?:))(?:bbbbbbbb(?:)(?:bbbbbbbb)))",[]))), <<":Ab">> = iolist_to_binary(join(re:split("Ab4ab","(ab)\\d\\1",[caseless, - trim]))), + trim]))), <<":Ab:">> = iolist_to_binary(join(re:split("Ab4ab","(ab)\\d\\1",[caseless, {parts, - 2}]))), - <<":Ab:">> = iolist_to_binary(join(re:split("Ab4ab","(ab)\\d\\1",[caseless]))), + 2}]))), + <<":Ab:">> = iolist_to_binary(join(re:split("Ab4ab","(ab)\\d\\1",[caseless]))), <<":ab">> = iolist_to_binary(join(re:split("ab4Ab","(ab)\\d\\1",[caseless, - trim]))), + trim]))), <<":ab:">> = iolist_to_binary(join(re:split("ab4Ab","(ab)\\d\\1",[caseless, {parts, - 2}]))), - <<":ab:">> = iolist_to_binary(join(re:split("ab4Ab","(ab)\\d\\1",[caseless]))), - <<"">> = iolist_to_binary(join(re:split("foobar1234baz","foo\\w*\\d{4}baz",[trim]))), + 2}]))), + <<":ab:">> = iolist_to_binary(join(re:split("ab4Ab","(ab)\\d\\1",[caseless]))), + <<"">> = iolist_to_binary(join(re:split("foobar1234baz","foo\\w*\\d{4}baz",[trim]))), <<":">> = iolist_to_binary(join(re:split("foobar1234baz","foo\\w*\\d{4}baz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("foobar1234baz","foo\\w*\\d{4}baz",[]))), - <<":~~">> = iolist_to_binary(join(re:split("x~~","x(~~)*(?:(?:F)?)?",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("foobar1234baz","foo\\w*\\d{4}baz",[]))), + <<":~~">> = iolist_to_binary(join(re:split("x~~","x(~~)*(?:(?:F)?)?",[trim]))), <<":~~:">> = iolist_to_binary(join(re:split("x~~","x(~~)*(?:(?:F)?)?",[{parts, - 2}]))), - <<":~~:">> = iolist_to_binary(join(re:split("x~~","x(~~)*(?:(?:F)?)?",[]))), - <<"">> = iolist_to_binary(join(re:split("aaac","^a(?#xxx){3}c",[trim]))), + 2}]))), + <<":~~:">> = iolist_to_binary(join(re:split("x~~","x(~~)*(?:(?:F)?)?",[]))), + <<"">> = iolist_to_binary(join(re:split("aaac","^a(?#xxx){3}c",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaac","^a(?#xxx){3}c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaac","^a(?#xxx){3}c",[]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaac","^a(?#xxx){3}c",[]))), ok. run29() -> <<"">> = iolist_to_binary(join(re:split("aaac","^a (?#xxx) (?#yyy) {3}c",[extended, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("aaac","^a (?#xxx) (?#yyy) {3}c",[extended, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaac","^a (?#xxx) (?#yyy) {3}c",[extended]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<![cd])b",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaac","^a (?#xxx) (?#yyy) {3}c",[extended]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<![cd])b",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<![cd])b",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<![cd])b",[]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<![cd])b",[]))), <<"B B">> = iolist_to_binary(join(re:split("B -B","(?<![cd])b",[trim]))), +B","(?<![cd])b",[trim]))), <<"B B">> = iolist_to_binary(join(re:split("B -B","(?<![cd])b",[{parts,2}]))), +B","(?<![cd])b",[{parts,2}]))), <<"B B">> = iolist_to_binary(join(re:split("B -B","(?<![cd])b",[]))), - <<"dbcb">> = iolist_to_binary(join(re:split("dbcb","(?<![cd])b",[trim]))), +B","(?<![cd])b",[]))), + <<"dbcb">> = iolist_to_binary(join(re:split("dbcb","(?<![cd])b",[trim]))), <<"dbcb">> = iolist_to_binary(join(re:split("dbcb","(?<![cd])b",[{parts, - 2}]))), - <<"dbcb">> = iolist_to_binary(join(re:split("dbcb","(?<![cd])b",[]))), - <<"db::cb">> = iolist_to_binary(join(re:split("dbaacb","(?<![cd])[ab]",[trim]))), + 2}]))), + <<"dbcb">> = iolist_to_binary(join(re:split("dbcb","(?<![cd])b",[]))), + <<"db::cb">> = iolist_to_binary(join(re:split("dbaacb","(?<![cd])[ab]",[trim]))), <<"db:acb">> = iolist_to_binary(join(re:split("dbaacb","(?<![cd])[ab]",[{parts, - 2}]))), - <<"db::cb">> = iolist_to_binary(join(re:split("dbaacb","(?<![cd])[ab]",[]))), - <<"db::::cb">> = iolist_to_binary(join(re:split("dbaacb","(?<!(c|d))[ab]",[trim]))), + 2}]))), + <<"db::cb">> = iolist_to_binary(join(re:split("dbaacb","(?<![cd])[ab]",[]))), + <<"db::::cb">> = iolist_to_binary(join(re:split("dbaacb","(?<!(c|d))[ab]",[trim]))), <<"db::acb">> = iolist_to_binary(join(re:split("dbaacb","(?<!(c|d))[ab]",[{parts, - 2}]))), - <<"db::::cb">> = iolist_to_binary(join(re:split("dbaacb","(?<!(c|d))[ab]",[]))), - <<"cdacc">> = iolist_to_binary(join(re:split("cdaccb","(?<!cd)[ab]",[trim]))), + 2}]))), + <<"db::::cb">> = iolist_to_binary(join(re:split("dbaacb","(?<!(c|d))[ab]",[]))), + <<"cdacc">> = iolist_to_binary(join(re:split("cdaccb","(?<!cd)[ab]",[trim]))), <<"cdacc:">> = iolist_to_binary(join(re:split("cdaccb","(?<!cd)[ab]",[{parts, - 2}]))), - <<"cdacc:">> = iolist_to_binary(join(re:split("cdaccb","(?<!cd)[ab]",[]))), - <<"">> = iolist_to_binary(join(re:split("","^(?:a?b?)*$",[trim]))), + 2}]))), + <<"cdacc:">> = iolist_to_binary(join(re:split("cdaccb","(?<!cd)[ab]",[]))), + <<"">> = iolist_to_binary(join(re:split("","^(?:a?b?)*$",[trim]))), <<"">> = iolist_to_binary(join(re:split("","^(?:a?b?)*$",[{parts, - 2}]))), - <<"">> = iolist_to_binary(join(re:split("","^(?:a?b?)*$",[]))), - <<"">> = iolist_to_binary(join(re:split("a","^(?:a?b?)*$",[trim]))), + 2}]))), + <<"">> = iolist_to_binary(join(re:split("","^(?:a?b?)*$",[]))), + <<"">> = iolist_to_binary(join(re:split("a","^(?:a?b?)*$",[trim]))), <<":">> = iolist_to_binary(join(re:split("a","^(?:a?b?)*$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","^(?:a?b?)*$",[]))), - <<"">> = iolist_to_binary(join(re:split("ab","^(?:a?b?)*$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","^(?:a?b?)*$",[]))), + <<"">> = iolist_to_binary(join(re:split("ab","^(?:a?b?)*$",[trim]))), <<":">> = iolist_to_binary(join(re:split("ab","^(?:a?b?)*$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ab","^(?:a?b?)*$",[]))), - <<"">> = iolist_to_binary(join(re:split("aaa","^(?:a?b?)*$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ab","^(?:a?b?)*$",[]))), + <<"">> = iolist_to_binary(join(re:split("aaa","^(?:a?b?)*$",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaa","^(?:a?b?)*$",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaa","^(?:a?b?)*$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?:a?b?)*$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaa","^(?:a?b?)*$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?:a?b?)*$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?:a?b?)*$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?:a?b?)*$",[]))), - <<"dbcb">> = iolist_to_binary(join(re:split("dbcb","^(?:a?b?)*$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?:a?b?)*$",[]))), + <<"dbcb">> = iolist_to_binary(join(re:split("dbcb","^(?:a?b?)*$",[trim]))), <<"dbcb">> = iolist_to_binary(join(re:split("dbcb","^(?:a?b?)*$",[{parts, - 2}]))), - <<"dbcb">> = iolist_to_binary(join(re:split("dbcb","^(?:a?b?)*$",[]))), - <<"a--">> = iolist_to_binary(join(re:split("a--","^(?:a?b?)*$",[trim]))), + 2}]))), + <<"dbcb">> = iolist_to_binary(join(re:split("dbcb","^(?:a?b?)*$",[]))), + <<"a--">> = iolist_to_binary(join(re:split("a--","^(?:a?b?)*$",[trim]))), <<"a--">> = iolist_to_binary(join(re:split("a--","^(?:a?b?)*$",[{parts, - 2}]))), - <<"a--">> = iolist_to_binary(join(re:split("a--","^(?:a?b?)*$",[]))), - <<"aa--">> = iolist_to_binary(join(re:split("aa--","^(?:a?b?)*$",[trim]))), + 2}]))), + <<"a--">> = iolist_to_binary(join(re:split("a--","^(?:a?b?)*$",[]))), + <<"aa--">> = iolist_to_binary(join(re:split("aa--","^(?:a?b?)*$",[trim]))), <<"aa--">> = iolist_to_binary(join(re:split("aa--","^(?:a?b?)*$",[{parts, - 2}]))), - <<"aa--">> = iolist_to_binary(join(re:split("aa--","^(?:a?b?)*$",[]))), + 2}]))), + <<"aa--">> = iolist_to_binary(join(re:split("aa--","^(?:a?b?)*$",[]))), <<":a : :b: c">> = iolist_to_binary(join(re:split("a b -c","((?s)^a(.))((?m)^b$)",[trim]))), +c","((?s)^a(.))((?m)^b$)",[trim]))), <<":a : :b: c">> = iolist_to_binary(join(re:split("a b -c","((?s)^a(.))((?m)^b$)",[{parts,2}]))), +c","((?s)^a(.))((?m)^b$)",[{parts,2}]))), <<":a : :b: c">> = iolist_to_binary(join(re:split("a b -c","((?s)^a(.))((?m)^b$)",[]))), +c","((?s)^a(.))((?m)^b$)",[]))), <<"a :b: c">> = iolist_to_binary(join(re:split("a b -c","((?m)^b$)",[trim]))), +c","((?m)^b$)",[trim]))), <<"a :b: c">> = iolist_to_binary(join(re:split("a b -c","((?m)^b$)",[{parts,2}]))), +c","((?m)^b$)",[{parts,2}]))), <<"a :b: c">> = iolist_to_binary(join(re:split("a b -c","((?m)^b$)",[]))), +c","((?m)^b$)",[]))), <<"a ">> = iolist_to_binary(join(re:split("a -b","(?m)^b",[trim]))), +b","(?m)^b",[trim]))), <<"a :">> = iolist_to_binary(join(re:split("a -b","(?m)^b",[{parts,2}]))), +b","(?m)^b",[{parts,2}]))), <<"a :">> = iolist_to_binary(join(re:split("a -b","(?m)^b",[]))), +b","(?m)^b",[]))), <<"a :b">> = iolist_to_binary(join(re:split("a -b","(?m)^(b)",[trim]))), +b","(?m)^(b)",[trim]))), <<"a :b:">> = iolist_to_binary(join(re:split("a -b","(?m)^(b)",[{parts,2}]))), +b","(?m)^(b)",[{parts,2}]))), <<"a :b:">> = iolist_to_binary(join(re:split("a -b","(?m)^(b)",[]))), +b","(?m)^(b)",[]))), <<"a :b">> = iolist_to_binary(join(re:split("a -b","((?m)^b)",[trim]))), +b","((?m)^b)",[trim]))), <<"a :b:">> = iolist_to_binary(join(re:split("a -b","((?m)^b)",[{parts,2}]))), +b","((?m)^b)",[{parts,2}]))), <<"a :b:">> = iolist_to_binary(join(re:split("a -b","((?m)^b)",[]))), +b","((?m)^b)",[]))), <<"a:b">> = iolist_to_binary(join(re:split("a -b","\\n((?m)^b)",[trim]))), +b","\\n((?m)^b)",[trim]))), <<"a:b:">> = iolist_to_binary(join(re:split("a -b","\\n((?m)^b)",[{parts,2}]))), +b","\\n((?m)^b)",[{parts,2}]))), <<"a:b:">> = iolist_to_binary(join(re:split("a -b","\\n((?m)^b)",[]))), +b","\\n((?m)^b)",[]))), <<"a b: ">> = iolist_to_binary(join(re:split("a b -c","((?s).)c(?!.)",[trim]))), +c","((?s).)c(?!.)",[trim]))), <<"a b: :">> = iolist_to_binary(join(re:split("a b -c","((?s).)c(?!.)",[{parts,2}]))), +c","((?s).)c(?!.)",[{parts,2}]))), <<"a b: :">> = iolist_to_binary(join(re:split("a b -c","((?s).)c(?!.)",[]))), +c","((?s).)c(?!.)",[]))), <<"a b: ">> = iolist_to_binary(join(re:split("a b -c","((?s).)c(?!.)",[trim]))), +c","((?s).)c(?!.)",[trim]))), <<"a b: :">> = iolist_to_binary(join(re:split("a b -c","((?s).)c(?!.)",[{parts,2}]))), +c","((?s).)c(?!.)",[{parts,2}]))), <<"a b: :">> = iolist_to_binary(join(re:split("a b -c","((?s).)c(?!.)",[]))), +c","((?s).)c(?!.)",[]))), <<"a :b ">> = iolist_to_binary(join(re:split("a b -c","((?s)b.)c(?!.)",[trim]))), +c","((?s)b.)c(?!.)",[trim]))), <<"a :b :">> = iolist_to_binary(join(re:split("a b -c","((?s)b.)c(?!.)",[{parts,2}]))), +c","((?s)b.)c(?!.)",[{parts,2}]))), <<"a :b :">> = iolist_to_binary(join(re:split("a b -c","((?s)b.)c(?!.)",[]))), +c","((?s)b.)c(?!.)",[]))), <<"a :b ">> = iolist_to_binary(join(re:split("a b -c","((?s)b.)c(?!.)",[trim]))), +c","((?s)b.)c(?!.)",[trim]))), <<"a :b :">> = iolist_to_binary(join(re:split("a b -c","((?s)b.)c(?!.)",[{parts,2}]))), +c","((?s)b.)c(?!.)",[{parts,2}]))), <<"a :b :">> = iolist_to_binary(join(re:split("a b -c","((?s)b.)c(?!.)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","()^b",[trim]))), +c","((?s)b.)c(?!.)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","()^b",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","()^b",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","()^b",[]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","()^b",[]))), <<"a b c">> = iolist_to_binary(join(re:split("a b -c","()^b",[trim]))), +c","()^b",[trim]))), <<"a b c">> = iolist_to_binary(join(re:split("a b -c","()^b",[{parts,2}]))), +c","()^b",[{parts,2}]))), <<"a b c">> = iolist_to_binary(join(re:split("a b -c","()^b",[]))), +c","()^b",[]))), <<"a b c">> = iolist_to_binary(join(re:split("a b -c","()^b",[trim]))), +c","()^b",[trim]))), <<"a b c">> = iolist_to_binary(join(re:split("a b -c","()^b",[{parts,2}]))), +c","()^b",[{parts,2}]))), <<"a b c">> = iolist_to_binary(join(re:split("a b -c","()^b",[]))), +c","()^b",[]))), <<"a :b: c">> = iolist_to_binary(join(re:split("a b -c","((?m)^b)",[trim]))), +c","((?m)^b)",[trim]))), <<"a :b: c">> = iolist_to_binary(join(re:split("a b -c","((?m)^b)",[{parts,2}]))), +c","((?m)^b)",[{parts,2}]))), <<"a :b: c">> = iolist_to_binary(join(re:split("a b -c","((?m)^b)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(x)?(?(1)a|b)",[trim]))), +c","((?m)^b)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(x)?(?(1)a|b)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(x)?(?(1)a|b)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(x)?(?(1)a|b)",[]))), - <<"a">> = iolist_to_binary(join(re:split("a","(x)?(?(1)a|b)",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(x)?(?(1)a|b)",[]))), + <<"a">> = iolist_to_binary(join(re:split("a","(x)?(?(1)a|b)",[trim]))), <<"a">> = iolist_to_binary(join(re:split("a","(x)?(?(1)a|b)",[{parts, - 2}]))), - <<"a">> = iolist_to_binary(join(re:split("a","(x)?(?(1)a|b)",[]))), - <<"a">> = iolist_to_binary(join(re:split("a","(x)?(?(1)a|b)",[trim]))), + 2}]))), + <<"a">> = iolist_to_binary(join(re:split("a","(x)?(?(1)a|b)",[]))), + <<"a">> = iolist_to_binary(join(re:split("a","(x)?(?(1)a|b)",[trim]))), <<"a">> = iolist_to_binary(join(re:split("a","(x)?(?(1)a|b)",[{parts, - 2}]))), - <<"a">> = iolist_to_binary(join(re:split("a","(x)?(?(1)a|b)",[]))), - <<"">> = iolist_to_binary(join(re:split("a","(x)?(?(1)b|a)",[trim]))), + 2}]))), + <<"a">> = iolist_to_binary(join(re:split("a","(x)?(?(1)a|b)",[]))), + <<"">> = iolist_to_binary(join(re:split("a","(x)?(?(1)b|a)",[trim]))), <<"::">> = iolist_to_binary(join(re:split("a","(x)?(?(1)b|a)",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("a","(x)?(?(1)b|a)",[]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("a","(x)?(?(1)b|a)",[]))), ok. run30() -> - <<"">> = iolist_to_binary(join(re:split("a","()?(?(1)b|a)",[trim]))), + <<"">> = iolist_to_binary(join(re:split("a","()?(?(1)b|a)",[trim]))), <<"::">> = iolist_to_binary(join(re:split("a","()?(?(1)b|a)",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("a","()?(?(1)b|a)",[]))), - <<"">> = iolist_to_binary(join(re:split("a","()?(?(1)a|b)",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("a","()?(?(1)b|a)",[]))), + <<"">> = iolist_to_binary(join(re:split("a","()?(?(1)a|b)",[trim]))), <<"::">> = iolist_to_binary(join(re:split("a","()?(?(1)a|b)",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("a","()?(?(1)a|b)",[]))), - <<":(:)">> = iolist_to_binary(join(re:split("(blah)","^(\\()?blah(?(1)(\\)))$",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("a","()?(?(1)a|b)",[]))), + <<":(:)">> = iolist_to_binary(join(re:split("(blah)","^(\\()?blah(?(1)(\\)))$",[trim]))), <<":(:):">> = iolist_to_binary(join(re:split("(blah)","^(\\()?blah(?(1)(\\)))$",[{parts, - 2}]))), - <<":(:):">> = iolist_to_binary(join(re:split("(blah)","^(\\()?blah(?(1)(\\)))$",[]))), - <<"">> = iolist_to_binary(join(re:split("blah","^(\\()?blah(?(1)(\\)))$",[trim]))), + 2}]))), + <<":(:):">> = iolist_to_binary(join(re:split("(blah)","^(\\()?blah(?(1)(\\)))$",[]))), + <<"">> = iolist_to_binary(join(re:split("blah","^(\\()?blah(?(1)(\\)))$",[trim]))), <<":::">> = iolist_to_binary(join(re:split("blah","^(\\()?blah(?(1)(\\)))$",[{parts, - 2}]))), - <<":::">> = iolist_to_binary(join(re:split("blah","^(\\()?blah(?(1)(\\)))$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\()?blah(?(1)(\\)))$",[trim]))), + 2}]))), + <<":::">> = iolist_to_binary(join(re:split("blah","^(\\()?blah(?(1)(\\)))$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\()?blah(?(1)(\\)))$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\()?blah(?(1)(\\)))$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\()?blah(?(1)(\\)))$",[]))), - <<"a">> = iolist_to_binary(join(re:split("a","^(\\()?blah(?(1)(\\)))$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\()?blah(?(1)(\\)))$",[]))), + <<"a">> = iolist_to_binary(join(re:split("a","^(\\()?blah(?(1)(\\)))$",[trim]))), <<"a">> = iolist_to_binary(join(re:split("a","^(\\()?blah(?(1)(\\)))$",[{parts, - 2}]))), - <<"a">> = iolist_to_binary(join(re:split("a","^(\\()?blah(?(1)(\\)))$",[]))), - <<"blah)">> = iolist_to_binary(join(re:split("blah)","^(\\()?blah(?(1)(\\)))$",[trim]))), + 2}]))), + <<"a">> = iolist_to_binary(join(re:split("a","^(\\()?blah(?(1)(\\)))$",[]))), + <<"blah)">> = iolist_to_binary(join(re:split("blah)","^(\\()?blah(?(1)(\\)))$",[trim]))), <<"blah)">> = iolist_to_binary(join(re:split("blah)","^(\\()?blah(?(1)(\\)))$",[{parts, - 2}]))), - <<"blah)">> = iolist_to_binary(join(re:split("blah)","^(\\()?blah(?(1)(\\)))$",[]))), - <<"(blah">> = iolist_to_binary(join(re:split("(blah","^(\\()?blah(?(1)(\\)))$",[trim]))), + 2}]))), + <<"blah)">> = iolist_to_binary(join(re:split("blah)","^(\\()?blah(?(1)(\\)))$",[]))), + <<"(blah">> = iolist_to_binary(join(re:split("(blah","^(\\()?blah(?(1)(\\)))$",[trim]))), <<"(blah">> = iolist_to_binary(join(re:split("(blah","^(\\()?blah(?(1)(\\)))$",[{parts, - 2}]))), - <<"(blah">> = iolist_to_binary(join(re:split("(blah","^(\\()?blah(?(1)(\\)))$",[]))), - <<":(:)">> = iolist_to_binary(join(re:split("(blah)","^(\\(+)?blah(?(1)(\\)))$",[trim]))), + 2}]))), + <<"(blah">> = iolist_to_binary(join(re:split("(blah","^(\\()?blah(?(1)(\\)))$",[]))), + <<":(:)">> = iolist_to_binary(join(re:split("(blah)","^(\\(+)?blah(?(1)(\\)))$",[trim]))), <<":(:):">> = iolist_to_binary(join(re:split("(blah)","^(\\(+)?blah(?(1)(\\)))$",[{parts, - 2}]))), - <<":(:):">> = iolist_to_binary(join(re:split("(blah)","^(\\(+)?blah(?(1)(\\)))$",[]))), - <<"">> = iolist_to_binary(join(re:split("blah","^(\\(+)?blah(?(1)(\\)))$",[trim]))), + 2}]))), + <<":(:):">> = iolist_to_binary(join(re:split("(blah)","^(\\(+)?blah(?(1)(\\)))$",[]))), + <<"">> = iolist_to_binary(join(re:split("blah","^(\\(+)?blah(?(1)(\\)))$",[trim]))), <<":::">> = iolist_to_binary(join(re:split("blah","^(\\(+)?blah(?(1)(\\)))$",[{parts, - 2}]))), - <<":::">> = iolist_to_binary(join(re:split("blah","^(\\(+)?blah(?(1)(\\)))$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\(+)?blah(?(1)(\\)))$",[trim]))), + 2}]))), + <<":::">> = iolist_to_binary(join(re:split("blah","^(\\(+)?blah(?(1)(\\)))$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\(+)?blah(?(1)(\\)))$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\(+)?blah(?(1)(\\)))$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\(+)?blah(?(1)(\\)))$",[]))), - <<"blah)">> = iolist_to_binary(join(re:split("blah)","^(\\(+)?blah(?(1)(\\)))$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\(+)?blah(?(1)(\\)))$",[]))), + <<"blah)">> = iolist_to_binary(join(re:split("blah)","^(\\(+)?blah(?(1)(\\)))$",[trim]))), <<"blah)">> = iolist_to_binary(join(re:split("blah)","^(\\(+)?blah(?(1)(\\)))$",[{parts, - 2}]))), - <<"blah)">> = iolist_to_binary(join(re:split("blah)","^(\\(+)?blah(?(1)(\\)))$",[]))), - <<"(blah">> = iolist_to_binary(join(re:split("(blah","^(\\(+)?blah(?(1)(\\)))$",[trim]))), + 2}]))), + <<"blah)">> = iolist_to_binary(join(re:split("blah)","^(\\(+)?blah(?(1)(\\)))$",[]))), + <<"(blah">> = iolist_to_binary(join(re:split("(blah","^(\\(+)?blah(?(1)(\\)))$",[trim]))), <<"(blah">> = iolist_to_binary(join(re:split("(blah","^(\\(+)?blah(?(1)(\\)))$",[{parts, - 2}]))), - <<"(blah">> = iolist_to_binary(join(re:split("(blah","^(\\(+)?blah(?(1)(\\)))$",[]))), - <<"">> = iolist_to_binary(join(re:split("a","(?(?!a)b|a)",[trim]))), + 2}]))), + <<"(blah">> = iolist_to_binary(join(re:split("(blah","^(\\(+)?blah(?(1)(\\)))$",[]))), + <<"">> = iolist_to_binary(join(re:split("a","(?(?!a)b|a)",[trim]))), <<":">> = iolist_to_binary(join(re:split("a","(?(?!a)b|a)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","(?(?!a)b|a)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?=a)b|a)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","(?(?!a)b|a)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?=a)b|a)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?=a)b|a)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?=a)b|a)",[]))), - <<"a">> = iolist_to_binary(join(re:split("a","(?(?=a)b|a)",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?(?=a)b|a)",[]))), + <<"a">> = iolist_to_binary(join(re:split("a","(?(?=a)b|a)",[trim]))), <<"a">> = iolist_to_binary(join(re:split("a","(?(?=a)b|a)",[{parts, - 2}]))), - <<"a">> = iolist_to_binary(join(re:split("a","(?(?=a)b|a)",[]))), - <<"a">> = iolist_to_binary(join(re:split("a","(?(?=a)b|a)",[trim]))), + 2}]))), + <<"a">> = iolist_to_binary(join(re:split("a","(?(?=a)b|a)",[]))), + <<"a">> = iolist_to_binary(join(re:split("a","(?(?=a)b|a)",[trim]))), <<"a">> = iolist_to_binary(join(re:split("a","(?(?=a)b|a)",[{parts, - 2}]))), - <<"a">> = iolist_to_binary(join(re:split("a","(?(?=a)b|a)",[]))), - <<"">> = iolist_to_binary(join(re:split("a","(?(?=a)a|b)",[trim]))), + 2}]))), + <<"a">> = iolist_to_binary(join(re:split("a","(?(?=a)b|a)",[]))), + <<"">> = iolist_to_binary(join(re:split("a","(?(?=a)a|b)",[trim]))), <<":">> = iolist_to_binary(join(re:split("a","(?(?=a)a|b)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","(?(?=a)a|b)",[]))), - <<"a:a:aab">> = iolist_to_binary(join(re:split("aaab","(?=(a+?))(\\1ab)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","(?(?=a)a|b)",[]))), + <<"a:a:aab">> = iolist_to_binary(join(re:split("aaab","(?=(a+?))(\\1ab)",[trim]))), <<"a:a:aab:">> = iolist_to_binary(join(re:split("aaab","(?=(a+?))(\\1ab)",[{parts, - 2}]))), - <<"a:a:aab:">> = iolist_to_binary(join(re:split("aaab","(?=(a+?))(\\1ab)",[]))), - <<":one:">> = iolist_to_binary(join(re:split("one:","(\\w+:)+",[trim]))), + 2}]))), + <<"a:a:aab:">> = iolist_to_binary(join(re:split("aaab","(?=(a+?))(\\1ab)",[]))), + <<":one:">> = iolist_to_binary(join(re:split("one:","(\\w+:)+",[trim]))), <<":one::">> = iolist_to_binary(join(re:split("one:","(\\w+:)+",[{parts, - 2}]))), - <<":one::">> = iolist_to_binary(join(re:split("one:","(\\w+:)+",[]))), - <<"a:a">> = iolist_to_binary(join(re:split("a","$(?<=^(a))",[trim]))), + 2}]))), + <<":one::">> = iolist_to_binary(join(re:split("one:","(\\w+:)+",[]))), + <<"a:a">> = iolist_to_binary(join(re:split("a","$(?<=^(a))",[trim]))), <<"a:a:">> = iolist_to_binary(join(re:split("a","$(?<=^(a))",[{parts, - 2}]))), - <<"a:a:">> = iolist_to_binary(join(re:split("a","$(?<=^(a))",[]))), - <<"a:a:aab">> = iolist_to_binary(join(re:split("aaab","(?=(a+?))(\\1ab)",[trim]))), + 2}]))), + <<"a:a:">> = iolist_to_binary(join(re:split("a","$(?<=^(a))",[]))), + <<"a:a:aab">> = iolist_to_binary(join(re:split("aaab","(?=(a+?))(\\1ab)",[trim]))), <<"a:a:aab:">> = iolist_to_binary(join(re:split("aaab","(?=(a+?))(\\1ab)",[{parts, - 2}]))), - <<"a:a:aab:">> = iolist_to_binary(join(re:split("aaab","(?=(a+?))(\\1ab)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?=(a+?))\\1ab",[trim]))), + 2}]))), + <<"a:a:aab:">> = iolist_to_binary(join(re:split("aaab","(?=(a+?))(\\1ab)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?=(a+?))\\1ab",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?=(a+?))\\1ab",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?=(a+?))\\1ab",[]))), - <<"aaab">> = iolist_to_binary(join(re:split("aaab","^(?=(a+?))\\1ab",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?=(a+?))\\1ab",[]))), + <<"aaab">> = iolist_to_binary(join(re:split("aaab","^(?=(a+?))\\1ab",[trim]))), <<"aaab">> = iolist_to_binary(join(re:split("aaab","^(?=(a+?))\\1ab",[{parts, - 2}]))), - <<"aaab">> = iolist_to_binary(join(re:split("aaab","^(?=(a+?))\\1ab",[]))), - <<"aaab">> = iolist_to_binary(join(re:split("aaab","^(?=(a+?))\\1ab",[trim]))), + 2}]))), + <<"aaab">> = iolist_to_binary(join(re:split("aaab","^(?=(a+?))\\1ab",[]))), + <<"aaab">> = iolist_to_binary(join(re:split("aaab","^(?=(a+?))\\1ab",[trim]))), <<"aaab">> = iolist_to_binary(join(re:split("aaab","^(?=(a+?))\\1ab",[{parts, - 2}]))), - <<"aaab">> = iolist_to_binary(join(re:split("aaab","^(?=(a+?))\\1ab",[]))), - <<"::abcd">> = iolist_to_binary(join(re:split("abcd","([\\w:]+::)?(\\w+)$",[trim]))), + 2}]))), + <<"aaab">> = iolist_to_binary(join(re:split("aaab","^(?=(a+?))\\1ab",[]))), + <<"::abcd">> = iolist_to_binary(join(re:split("abcd","([\\w:]+::)?(\\w+)$",[trim]))), <<"::abcd:">> = iolist_to_binary(join(re:split("abcd","([\\w:]+::)?(\\w+)$",[{parts, - 2}]))), - <<"::abcd:">> = iolist_to_binary(join(re:split("abcd","([\\w:]+::)?(\\w+)$",[]))), - <<":xy:z::::abcd">> = iolist_to_binary(join(re:split("xy:z:::abcd","([\\w:]+::)?(\\w+)$",[trim]))), + 2}]))), + <<"::abcd:">> = iolist_to_binary(join(re:split("abcd","([\\w:]+::)?(\\w+)$",[]))), + <<":xy:z::::abcd">> = iolist_to_binary(join(re:split("xy:z:::abcd","([\\w:]+::)?(\\w+)$",[trim]))), <<":xy:z::::abcd:">> = iolist_to_binary(join(re:split("xy:z:::abcd","([\\w:]+::)?(\\w+)$",[{parts, - 2}]))), - <<":xy:z::::abcd:">> = iolist_to_binary(join(re:split("xy:z:::abcd","([\\w:]+::)?(\\w+)$",[]))), - <<":c:d">> = iolist_to_binary(join(re:split("aexycd","^[^bcd]*(c+)",[trim]))), + 2}]))), + <<":xy:z::::abcd:">> = iolist_to_binary(join(re:split("xy:z:::abcd","([\\w:]+::)?(\\w+)$",[]))), + <<":c:d">> = iolist_to_binary(join(re:split("aexycd","^[^bcd]*(c+)",[trim]))), <<":c:d">> = iolist_to_binary(join(re:split("aexycd","^[^bcd]*(c+)",[{parts, - 2}]))), - <<":c:d">> = iolist_to_binary(join(re:split("aexycd","^[^bcd]*(c+)",[]))), - <<"c:aa">> = iolist_to_binary(join(re:split("caab","(a*)b+",[trim]))), + 2}]))), + <<":c:d">> = iolist_to_binary(join(re:split("aexycd","^[^bcd]*(c+)",[]))), + <<"c:aa">> = iolist_to_binary(join(re:split("caab","(a*)b+",[trim]))), <<"c:aa:">> = iolist_to_binary(join(re:split("caab","(a*)b+",[{parts, - 2}]))), - <<"c:aa:">> = iolist_to_binary(join(re:split("caab","(a*)b+",[]))), - <<"::abcd">> = iolist_to_binary(join(re:split("abcd","([\\w:]+::)?(\\w+)$",[trim]))), + 2}]))), + <<"c:aa:">> = iolist_to_binary(join(re:split("caab","(a*)b+",[]))), + <<"::abcd">> = iolist_to_binary(join(re:split("abcd","([\\w:]+::)?(\\w+)$",[trim]))), <<"::abcd:">> = iolist_to_binary(join(re:split("abcd","([\\w:]+::)?(\\w+)$",[{parts, - 2}]))), - <<"::abcd:">> = iolist_to_binary(join(re:split("abcd","([\\w:]+::)?(\\w+)$",[]))), - <<":xy:z::::abcd">> = iolist_to_binary(join(re:split("xy:z:::abcd","([\\w:]+::)?(\\w+)$",[trim]))), + 2}]))), + <<"::abcd:">> = iolist_to_binary(join(re:split("abcd","([\\w:]+::)?(\\w+)$",[]))), + <<":xy:z::::abcd">> = iolist_to_binary(join(re:split("xy:z:::abcd","([\\w:]+::)?(\\w+)$",[trim]))), <<":xy:z::::abcd:">> = iolist_to_binary(join(re:split("xy:z:::abcd","([\\w:]+::)?(\\w+)$",[{parts, - 2}]))), - <<":xy:z::::abcd:">> = iolist_to_binary(join(re:split("xy:z:::abcd","([\\w:]+::)?(\\w+)$",[]))), - <<"*** ::Failers">> = iolist_to_binary(join(re:split("*** Failers","([\\w:]+::)?(\\w+)$",[trim]))), + 2}]))), + <<":xy:z::::abcd:">> = iolist_to_binary(join(re:split("xy:z:::abcd","([\\w:]+::)?(\\w+)$",[]))), + <<"*** ::Failers">> = iolist_to_binary(join(re:split("*** Failers","([\\w:]+::)?(\\w+)$",[trim]))), <<"*** ::Failers:">> = iolist_to_binary(join(re:split("*** Failers","([\\w:]+::)?(\\w+)$",[{parts, - 2}]))), - <<"*** ::Failers:">> = iolist_to_binary(join(re:split("*** Failers","([\\w:]+::)?(\\w+)$",[]))), - <<"abcd:">> = iolist_to_binary(join(re:split("abcd:","([\\w:]+::)?(\\w+)$",[trim]))), + 2}]))), + <<"*** ::Failers:">> = iolist_to_binary(join(re:split("*** Failers","([\\w:]+::)?(\\w+)$",[]))), + <<"abcd:">> = iolist_to_binary(join(re:split("abcd:","([\\w:]+::)?(\\w+)$",[trim]))), <<"abcd:">> = iolist_to_binary(join(re:split("abcd:","([\\w:]+::)?(\\w+)$",[{parts, - 2}]))), - <<"abcd:">> = iolist_to_binary(join(re:split("abcd:","([\\w:]+::)?(\\w+)$",[]))), - <<"abcd:">> = iolist_to_binary(join(re:split("abcd:","([\\w:]+::)?(\\w+)$",[trim]))), + 2}]))), + <<"abcd:">> = iolist_to_binary(join(re:split("abcd:","([\\w:]+::)?(\\w+)$",[]))), + <<"abcd:">> = iolist_to_binary(join(re:split("abcd:","([\\w:]+::)?(\\w+)$",[trim]))), <<"abcd:">> = iolist_to_binary(join(re:split("abcd:","([\\w:]+::)?(\\w+)$",[{parts, - 2}]))), - <<"abcd:">> = iolist_to_binary(join(re:split("abcd:","([\\w:]+::)?(\\w+)$",[]))), - <<":c:d">> = iolist_to_binary(join(re:split("aexycd","^[^bcd]*(c+)",[trim]))), + 2}]))), + <<"abcd:">> = iolist_to_binary(join(re:split("abcd:","([\\w:]+::)?(\\w+)$",[]))), + <<":c:d">> = iolist_to_binary(join(re:split("aexycd","^[^bcd]*(c+)",[trim]))), <<":c:d">> = iolist_to_binary(join(re:split("aexycd","^[^bcd]*(c+)",[{parts, - 2}]))), - <<":c:d">> = iolist_to_binary(join(re:split("aexycd","^[^bcd]*(c+)",[]))), + 2}]))), + <<":c:d">> = iolist_to_binary(join(re:split("aexycd","^[^bcd]*(c+)",[]))), ok. run31() -> - <<"">> = iolist_to_binary(join(re:split("aaab","(?>a+)b",[trim]))), + <<"">> = iolist_to_binary(join(re:split("aaab","(?>a+)b",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaab","(?>a+)b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaab","(?>a+)b",[]))), - <<"a::[:b]::">> = iolist_to_binary(join(re:split("a:[b]:","([[:]+)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaab","(?>a+)b",[]))), + <<"a::[:b]::">> = iolist_to_binary(join(re:split("a:[b]:","([[:]+)",[trim]))), <<"a::[:b]:">> = iolist_to_binary(join(re:split("a:[b]:","([[:]+)",[{parts, - 2}]))), - <<"a::[:b]:::">> = iolist_to_binary(join(re:split("a:[b]:","([[:]+)",[]))), - <<"a:=[:b]:=">> = iolist_to_binary(join(re:split("a=[b]=","([[=]+)",[trim]))), + 2}]))), + <<"a::[:b]:::">> = iolist_to_binary(join(re:split("a:[b]:","([[:]+)",[]))), + <<"a:=[:b]:=">> = iolist_to_binary(join(re:split("a=[b]=","([[=]+)",[trim]))), <<"a:=[:b]=">> = iolist_to_binary(join(re:split("a=[b]=","([[=]+)",[{parts, - 2}]))), - <<"a:=[:b]:=:">> = iolist_to_binary(join(re:split("a=[b]=","([[=]+)",[]))), - <<"a:.[:b]:.">> = iolist_to_binary(join(re:split("a.[b].","([[.]+)",[trim]))), + 2}]))), + <<"a:=[:b]:=:">> = iolist_to_binary(join(re:split("a=[b]=","([[=]+)",[]))), + <<"a:.[:b]:.">> = iolist_to_binary(join(re:split("a.[b].","([[.]+)",[trim]))), <<"a:.[:b].">> = iolist_to_binary(join(re:split("a.[b].","([[.]+)",[{parts, - 2}]))), - <<"a:.[:b]:.:">> = iolist_to_binary(join(re:split("a.[b].","([[.]+)",[]))), - <<":aaab">> = iolist_to_binary(join(re:split("aaab","((?>a+)b)",[trim]))), + 2}]))), + <<"a:.[:b]:.:">> = iolist_to_binary(join(re:split("a.[b].","([[.]+)",[]))), + <<":aaab">> = iolist_to_binary(join(re:split("aaab","((?>a+)b)",[trim]))), <<":aaab:">> = iolist_to_binary(join(re:split("aaab","((?>a+)b)",[{parts, - 2}]))), - <<":aaab:">> = iolist_to_binary(join(re:split("aaab","((?>a+)b)",[]))), - <<":aaa">> = iolist_to_binary(join(re:split("aaab","(?>(a+))b",[trim]))), + 2}]))), + <<":aaab:">> = iolist_to_binary(join(re:split("aaab","((?>a+)b)",[]))), + <<":aaa">> = iolist_to_binary(join(re:split("aaab","(?>(a+))b",[trim]))), <<":aaa:">> = iolist_to_binary(join(re:split("aaab","(?>(a+))b",[{parts, - 2}]))), - <<":aaa:">> = iolist_to_binary(join(re:split("aaab","(?>(a+))b",[]))), - <<"((:x">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+",[trim]))), + 2}]))), + <<":aaa:">> = iolist_to_binary(join(re:split("aaab","(?>(a+))b",[]))), + <<"((:x">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+",[trim]))), <<"((:x:">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+",[{parts, - 2}]))), - <<"((:x:">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a\\Z",[trim]))), + 2}]))), + <<"((:x:">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","((?>[^()]+)|\\([^()]*\\))+",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a\\Z",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a\\Z",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a\\Z",[]))), - <<"aaab">> = iolist_to_binary(join(re:split("aaab","a\\Z",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a\\Z",[]))), + <<"aaab">> = iolist_to_binary(join(re:split("aaab","a\\Z",[trim]))), <<"aaab">> = iolist_to_binary(join(re:split("aaab","a\\Z",[{parts, - 2}]))), - <<"aaab">> = iolist_to_binary(join(re:split("aaab","a\\Z",[]))), + 2}]))), + <<"aaab">> = iolist_to_binary(join(re:split("aaab","a\\Z",[]))), <<"a b">> = iolist_to_binary(join(re:split("a -b","a\\Z",[trim]))), +b","a\\Z",[trim]))), <<"a b">> = iolist_to_binary(join(re:split("a -b","a\\Z",[{parts,2}]))), +b","a\\Z",[{parts,2}]))), <<"a b">> = iolist_to_binary(join(re:split("a -b","a\\Z",[]))), +b","a\\Z",[]))), <<"a ">> = iolist_to_binary(join(re:split("a -b","b\\Z",[trim]))), +b","b\\Z",[trim]))), <<"a :">> = iolist_to_binary(join(re:split("a -b","b\\Z",[{parts,2}]))), +b","b\\Z",[{parts,2}]))), <<"a :">> = iolist_to_binary(join(re:split("a -b","b\\Z",[]))), +b","b\\Z",[]))), <<"a ">> = iolist_to_binary(join(re:split("a -b","b\\Z",[trim]))), +b","b\\Z",[trim]))), <<"a :">> = iolist_to_binary(join(re:split("a -b","b\\Z",[{parts,2}]))), +b","b\\Z",[{parts,2}]))), <<"a :">> = iolist_to_binary(join(re:split("a -b","b\\Z",[]))), +b","b\\Z",[]))), <<"a ">> = iolist_to_binary(join(re:split("a -b","b\\z",[trim]))), +b","b\\z",[trim]))), <<"a :">> = iolist_to_binary(join(re:split("a -b","b\\z",[{parts,2}]))), +b","b\\z",[{parts,2}]))), <<"a :">> = iolist_to_binary(join(re:split("a -b","b\\z",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","b\\z",[trim]))), +b","b\\z",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","b\\z",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","b\\z",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","b\\z",[]))), - <<"">> = iolist_to_binary(join(re:split("a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","b\\z",[]))), + <<"">> = iolist_to_binary(join(re:split("a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"::">> = iolist_to_binary(join(re:split("a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"::">> = iolist_to_binary(join(re:split("abc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("abc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"">> = iolist_to_binary(join(re:split("a-b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("abc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"">> = iolist_to_binary(join(re:split("a-b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"::">> = iolist_to_binary(join(re:split("a-b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("a-b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"">> = iolist_to_binary(join(re:split("0-9","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("a-b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"">> = iolist_to_binary(join(re:split("0-9","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"::">> = iolist_to_binary(join(re:split("0-9","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("0-9","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"">> = iolist_to_binary(join(re:split("a.b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("0-9","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"">> = iolist_to_binary(join(re:split("a.b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"::">> = iolist_to_binary(join(re:split("a.b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("a.b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"">> = iolist_to_binary(join(re:split("5.6.7","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("a.b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"">> = iolist_to_binary(join(re:split("5.6.7","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"::">> = iolist_to_binary(join(re:split("5.6.7","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("5.6.7","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"">> = iolist_to_binary(join(re:split("the.quick.brown.fox","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("5.6.7","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"">> = iolist_to_binary(join(re:split("the.quick.brown.fox","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"::">> = iolist_to_binary(join(re:split("the.quick.brown.fox","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("the.quick.brown.fox","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"">> = iolist_to_binary(join(re:split("a100.b200.300c","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("the.quick.brown.fox","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"">> = iolist_to_binary(join(re:split("a100.b200.300c","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"::">> = iolist_to_binary(join(re:split("a100.b200.300c","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("a100.b200.300c","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"">> = iolist_to_binary(join(re:split("12-ab.1245","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("a100.b200.300c","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"">> = iolist_to_binary(join(re:split("12-ab.1245","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"::">> = iolist_to_binary(join(re:split("12-ab.1245","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("12-ab.1245","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("12-ab.1245","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"">> = iolist_to_binary(join(re:split("","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"">> = iolist_to_binary(join(re:split("","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"">> = iolist_to_binary(join(re:split("","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"">> = iolist_to_binary(join(re:split("","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<".a">> = iolist_to_binary(join(re:split(".a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"">> = iolist_to_binary(join(re:split("","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<".a">> = iolist_to_binary(join(re:split(".a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<".a">> = iolist_to_binary(join(re:split(".a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<".a">> = iolist_to_binary(join(re:split(".a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"-a">> = iolist_to_binary(join(re:split("-a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<".a">> = iolist_to_binary(join(re:split(".a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"-a">> = iolist_to_binary(join(re:split("-a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"-a">> = iolist_to_binary(join(re:split("-a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"-a">> = iolist_to_binary(join(re:split("-a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"a-">> = iolist_to_binary(join(re:split("a-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"-a">> = iolist_to_binary(join(re:split("-a","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"a-">> = iolist_to_binary(join(re:split("a-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"a-">> = iolist_to_binary(join(re:split("a-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"a-">> = iolist_to_binary(join(re:split("a-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"a.">> = iolist_to_binary(join(re:split("a.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"a-">> = iolist_to_binary(join(re:split("a-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"a.">> = iolist_to_binary(join(re:split("a.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"a.">> = iolist_to_binary(join(re:split("a.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"a.">> = iolist_to_binary(join(re:split("a.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"a_b">> = iolist_to_binary(join(re:split("a_b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"a.">> = iolist_to_binary(join(re:split("a.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"a_b">> = iolist_to_binary(join(re:split("a_b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"a_b">> = iolist_to_binary(join(re:split("a_b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"a_b">> = iolist_to_binary(join(re:split("a_b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"a.-">> = iolist_to_binary(join(re:split("a.-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"a_b">> = iolist_to_binary(join(re:split("a_b","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"a.-">> = iolist_to_binary(join(re:split("a.-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"a.-">> = iolist_to_binary(join(re:split("a.-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"a.-">> = iolist_to_binary(join(re:split("a.-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"a..">> = iolist_to_binary(join(re:split("a..","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"a.-">> = iolist_to_binary(join(re:split("a.-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"a..">> = iolist_to_binary(join(re:split("a..","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"a..">> = iolist_to_binary(join(re:split("a..","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"a..">> = iolist_to_binary(join(re:split("a..","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"ab..bc">> = iolist_to_binary(join(re:split("ab..bc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"a..">> = iolist_to_binary(join(re:split("a..","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"ab..bc">> = iolist_to_binary(join(re:split("ab..bc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"ab..bc">> = iolist_to_binary(join(re:split("ab..bc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"ab..bc">> = iolist_to_binary(join(re:split("ab..bc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"the.quick.brown.fox-">> = iolist_to_binary(join(re:split("the.quick.brown.fox-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"ab..bc">> = iolist_to_binary(join(re:split("ab..bc","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"the.quick.brown.fox-">> = iolist_to_binary(join(re:split("the.quick.brown.fox-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"the.quick.brown.fox-">> = iolist_to_binary(join(re:split("the.quick.brown.fox-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"the.quick.brown.fox-">> = iolist_to_binary(join(re:split("the.quick.brown.fox-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"the.quick.brown.fox.">> = iolist_to_binary(join(re:split("the.quick.brown.fox.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"the.quick.brown.fox-">> = iolist_to_binary(join(re:split("the.quick.brown.fox-","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"the.quick.brown.fox.">> = iolist_to_binary(join(re:split("the.quick.brown.fox.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"the.quick.brown.fox.">> = iolist_to_binary(join(re:split("the.quick.brown.fox.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"the.quick.brown.fox.">> = iolist_to_binary(join(re:split("the.quick.brown.fox.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"the.quick.brown.fox_">> = iolist_to_binary(join(re:split("the.quick.brown.fox_","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"the.quick.brown.fox.">> = iolist_to_binary(join(re:split("the.quick.brown.fox.","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"the.quick.brown.fox_">> = iolist_to_binary(join(re:split("the.quick.brown.fox_","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"the.quick.brown.fox_">> = iolist_to_binary(join(re:split("the.quick.brown.fox_","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"the.quick.brown.fox_">> = iolist_to_binary(join(re:split("the.quick.brown.fox_","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<"the.quick.brown.fox+">> = iolist_to_binary(join(re:split("the.quick.brown.fox+","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), + 2}]))), + <<"the.quick.brown.fox_">> = iolist_to_binary(join(re:split("the.quick.brown.fox_","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<"the.quick.brown.fox+">> = iolist_to_binary(join(re:split("the.quick.brown.fox+","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[trim]))), <<"the.quick.brown.fox+">> = iolist_to_binary(join(re:split("the.quick.brown.fox+","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[{parts, - 2}]))), - <<"the.quick.brown.fox+">> = iolist_to_binary(join(re:split("the.quick.brown.fox+","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), - <<":abcd">> = iolist_to_binary(join(re:split("alphabetabcd","(?>.*)(?<=(abcd|wxyz))",[trim]))), + 2}]))), + <<"the.quick.brown.fox+">> = iolist_to_binary(join(re:split("the.quick.brown.fox+","^(?>(?(1)\\.|())[^\\W_](?>[a-z0-9-]*[^\\W_])?)+$",[]))), + <<":abcd">> = iolist_to_binary(join(re:split("alphabetabcd","(?>.*)(?<=(abcd|wxyz))",[trim]))), <<":abcd:">> = iolist_to_binary(join(re:split("alphabetabcd","(?>.*)(?<=(abcd|wxyz))",[{parts, - 2}]))), - <<":abcd:">> = iolist_to_binary(join(re:split("alphabetabcd","(?>.*)(?<=(abcd|wxyz))",[]))), - <<":wxyz">> = iolist_to_binary(join(re:split("endingwxyz","(?>.*)(?<=(abcd|wxyz))",[trim]))), + 2}]))), + <<":abcd:">> = iolist_to_binary(join(re:split("alphabetabcd","(?>.*)(?<=(abcd|wxyz))",[]))), + <<":wxyz">> = iolist_to_binary(join(re:split("endingwxyz","(?>.*)(?<=(abcd|wxyz))",[trim]))), <<":wxyz:">> = iolist_to_binary(join(re:split("endingwxyz","(?>.*)(?<=(abcd|wxyz))",[{parts, - 2}]))), - <<":wxyz:">> = iolist_to_binary(join(re:split("endingwxyz","(?>.*)(?<=(abcd|wxyz))",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>.*)(?<=(abcd|wxyz))",[trim]))), + 2}]))), + <<":wxyz:">> = iolist_to_binary(join(re:split("endingwxyz","(?>.*)(?<=(abcd|wxyz))",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>.*)(?<=(abcd|wxyz))",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>.*)(?<=(abcd|wxyz))",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>.*)(?<=(abcd|wxyz))",[]))), - <<"a rather long string that doesn't end with one of them">> = iolist_to_binary(join(re:split("a rather long string that doesn't end with one of them","(?>.*)(?<=(abcd|wxyz))",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?>.*)(?<=(abcd|wxyz))",[]))), + <<"a rather long string that doesn't end with one of them">> = iolist_to_binary(join(re:split("a rather long string that doesn't end with one of them","(?>.*)(?<=(abcd|wxyz))",[trim]))), <<"a rather long string that doesn't end with one of them">> = iolist_to_binary(join(re:split("a rather long string that doesn't end with one of them","(?>.*)(?<=(abcd|wxyz))",[{parts, - 2}]))), - <<"a rather long string that doesn't end with one of them">> = iolist_to_binary(join(re:split("a rather long string that doesn't end with one of them","(?>.*)(?<=(abcd|wxyz))",[]))), - <<"">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword",[trim]))), + 2}]))), + <<"a rather long string that doesn't end with one of them">> = iolist_to_binary(join(re:split("a rather long string that doesn't end with one of them","(?>.*)(?<=(abcd|wxyz))",[]))), + <<"">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword",[trim]))), <<":">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword",[]))), - <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark otherword","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword",[]))), + <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword",[trim]))), <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword",[{parts, - 2}]))), - <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword",[]))), - <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?>[a-zA-Z0-9]+ ){0,30}otherword",[trim]))), + 2}]))), + <<"word cat dog elephant mussel cow horse canary baboon snake shark">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark","word (?>(?:(?!otherword)[a-zA-Z0-9]+ ){0,30})otherword",[]))), + <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?>[a-zA-Z0-9]+ ){0,30}otherword",[trim]))), <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?>[a-zA-Z0-9]+ ){0,30}otherword",[{parts, - 2}]))), - <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?>[a-zA-Z0-9]+ ){0,30}otherword",[]))), - <<"999">> = iolist_to_binary(join(re:split("999foo","(?<=\\d{3}(?!999))foo",[trim]))), + 2}]))), + <<"word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope">> = iolist_to_binary(join(re:split("word cat dog elephant mussel cow horse canary baboon snake shark the quick brown fox and the lazy dog and several other words getting close to thirty by now I hope","word (?>[a-zA-Z0-9]+ ){0,30}otherword",[]))), + <<"999">> = iolist_to_binary(join(re:split("999foo","(?<=\\d{3}(?!999))foo",[trim]))), <<"999:">> = iolist_to_binary(join(re:split("999foo","(?<=\\d{3}(?!999))foo",[{parts, - 2}]))), - <<"999:">> = iolist_to_binary(join(re:split("999foo","(?<=\\d{3}(?!999))foo",[]))), - <<"123999">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}(?!999))foo",[trim]))), + 2}]))), + <<"999:">> = iolist_to_binary(join(re:split("999foo","(?<=\\d{3}(?!999))foo",[]))), + <<"123999">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}(?!999))foo",[trim]))), <<"123999:">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}(?!999))foo",[{parts, - 2}]))), - <<"123999:">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}(?!999))foo",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}(?!999))foo",[trim]))), + 2}]))), + <<"123999:">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}(?!999))foo",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}(?!999))foo",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}(?!999))foo",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}(?!999))foo",[]))), - <<"123abcfoo">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}(?!999))foo",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}(?!999))foo",[]))), + <<"123abcfoo">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}(?!999))foo",[trim]))), <<"123abcfoo">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}(?!999))foo",[{parts, - 2}]))), - <<"123abcfoo">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}(?!999))foo",[]))), - <<"999">> = iolist_to_binary(join(re:split("999foo","(?<=(?!...999)\\d{3})foo",[trim]))), + 2}]))), + <<"123abcfoo">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}(?!999))foo",[]))), + <<"999">> = iolist_to_binary(join(re:split("999foo","(?<=(?!...999)\\d{3})foo",[trim]))), <<"999:">> = iolist_to_binary(join(re:split("999foo","(?<=(?!...999)\\d{3})foo",[{parts, - 2}]))), - <<"999:">> = iolist_to_binary(join(re:split("999foo","(?<=(?!...999)\\d{3})foo",[]))), - <<"123999">> = iolist_to_binary(join(re:split("123999foo","(?<=(?!...999)\\d{3})foo",[trim]))), + 2}]))), + <<"999:">> = iolist_to_binary(join(re:split("999foo","(?<=(?!...999)\\d{3})foo",[]))), + <<"123999">> = iolist_to_binary(join(re:split("123999foo","(?<=(?!...999)\\d{3})foo",[trim]))), <<"123999:">> = iolist_to_binary(join(re:split("123999foo","(?<=(?!...999)\\d{3})foo",[{parts, - 2}]))), - <<"123999:">> = iolist_to_binary(join(re:split("123999foo","(?<=(?!...999)\\d{3})foo",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(?!...999)\\d{3})foo",[trim]))), + 2}]))), + <<"123999:">> = iolist_to_binary(join(re:split("123999foo","(?<=(?!...999)\\d{3})foo",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(?!...999)\\d{3})foo",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(?!...999)\\d{3})foo",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(?!...999)\\d{3})foo",[]))), - <<"123abcfoo">> = iolist_to_binary(join(re:split("123abcfoo","(?<=(?!...999)\\d{3})foo",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=(?!...999)\\d{3})foo",[]))), + <<"123abcfoo">> = iolist_to_binary(join(re:split("123abcfoo","(?<=(?!...999)\\d{3})foo",[trim]))), <<"123abcfoo">> = iolist_to_binary(join(re:split("123abcfoo","(?<=(?!...999)\\d{3})foo",[{parts, - 2}]))), - <<"123abcfoo">> = iolist_to_binary(join(re:split("123abcfoo","(?<=(?!...999)\\d{3})foo",[]))), - <<"123abc">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}(?!999)...)foo",[trim]))), + 2}]))), + <<"123abcfoo">> = iolist_to_binary(join(re:split("123abcfoo","(?<=(?!...999)\\d{3})foo",[]))), + <<"123abc">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}(?!999)...)foo",[trim]))), <<"123abc:">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}(?!999)...)foo",[{parts, - 2}]))), - <<"123abc:">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}(?!999)...)foo",[]))), - <<"123456">> = iolist_to_binary(join(re:split("123456foo","(?<=\\d{3}(?!999)...)foo",[trim]))), + 2}]))), + <<"123abc:">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}(?!999)...)foo",[]))), + <<"123456">> = iolist_to_binary(join(re:split("123456foo","(?<=\\d{3}(?!999)...)foo",[trim]))), <<"123456:">> = iolist_to_binary(join(re:split("123456foo","(?<=\\d{3}(?!999)...)foo",[{parts, - 2}]))), - <<"123456:">> = iolist_to_binary(join(re:split("123456foo","(?<=\\d{3}(?!999)...)foo",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}(?!999)...)foo",[trim]))), + 2}]))), + <<"123456:">> = iolist_to_binary(join(re:split("123456foo","(?<=\\d{3}(?!999)...)foo",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}(?!999)...)foo",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}(?!999)...)foo",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}(?!999)...)foo",[]))), - <<"123999foo">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}(?!999)...)foo",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}(?!999)...)foo",[]))), + <<"123999foo">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}(?!999)...)foo",[trim]))), <<"123999foo">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}(?!999)...)foo",[{parts, - 2}]))), - <<"123999foo">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}(?!999)...)foo",[]))), + 2}]))), + <<"123999foo">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}(?!999)...)foo",[]))), ok. run32() -> - <<"123abc">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}...)(?<!999)foo",[trim]))), + <<"123abc">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}...)(?<!999)foo",[trim]))), <<"123abc:">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}...)(?<!999)foo",[{parts, - 2}]))), - <<"123abc:">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}...)(?<!999)foo",[]))), - <<"123456">> = iolist_to_binary(join(re:split("123456foo","(?<=\\d{3}...)(?<!999)foo",[trim]))), + 2}]))), + <<"123abc:">> = iolist_to_binary(join(re:split("123abcfoo","(?<=\\d{3}...)(?<!999)foo",[]))), + <<"123456">> = iolist_to_binary(join(re:split("123456foo","(?<=\\d{3}...)(?<!999)foo",[trim]))), <<"123456:">> = iolist_to_binary(join(re:split("123456foo","(?<=\\d{3}...)(?<!999)foo",[{parts, - 2}]))), - <<"123456:">> = iolist_to_binary(join(re:split("123456foo","(?<=\\d{3}...)(?<!999)foo",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}...)(?<!999)foo",[trim]))), + 2}]))), + <<"123456:">> = iolist_to_binary(join(re:split("123456foo","(?<=\\d{3}...)(?<!999)foo",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}...)(?<!999)foo",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}...)(?<!999)foo",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}...)(?<!999)foo",[]))), - <<"123999foo">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}...)(?<!999)foo",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?<=\\d{3}...)(?<!999)foo",[]))), + <<"123999foo">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}...)(?<!999)foo",[trim]))), <<"123999foo">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}...)(?<!999)foo",[{parts, - 2}]))), - <<"123999foo">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}...)(?<!999)foo",[]))), + 2}]))), + <<"123999foo">> = iolist_to_binary(join(re:split("123999foo","(?<=\\d{3}...)(?<!999)foo",[]))), <<":::abcd: xyz">> = iolist_to_binary(join(re:split("<a href=abcd xyz","<a[\\s]+href[\\s]*=[\\s]* # find <a href= ([\\\"\\'])? # find single or double quote (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching # quote, otherwise match up to next space",[caseless, dotall, extended, - trim]))), + trim]))), <<":::abcd: xyz">> = iolist_to_binary(join(re:split("<a href=abcd xyz","<a[\\s]+href[\\s]*=[\\s]* # find <a href= ([\\\"\\'])? # find single or double quote (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching @@ -28108,20 +28108,20 @@ run32() -> dotall, extended, {parts, - 2}]))), + 2}]))), <<":::abcd: xyz">> = iolist_to_binary(join(re:split("<a href=abcd xyz","<a[\\s]+href[\\s]*=[\\s]* # find <a href= ([\\\"\\'])? # find single or double quote (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching # quote, otherwise match up to next space",[caseless, dotall, - extended]))), + extended]))), <<":\":abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href=\"abcd xyz pqr\" cats","<a[\\s]+href[\\s]*=[\\s]* # find <a href= ([\\\"\\'])? # find single or double quote (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching # quote, otherwise match up to next space",[caseless, dotall, extended, - trim]))), + trim]))), <<":\":abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href=\"abcd xyz pqr\" cats","<a[\\s]+href[\\s]*=[\\s]* # find <a href= ([\\\"\\'])? # find single or double quote (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching @@ -28129,20 +28129,20 @@ run32() -> dotall, extended, {parts, - 2}]))), + 2}]))), <<":\":abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href=\"abcd xyz pqr\" cats","<a[\\s]+href[\\s]*=[\\s]* # find <a href= ([\\\"\\'])? # find single or double quote (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching # quote, otherwise match up to next space",[caseless, dotall, - extended]))), + extended]))), <<":':abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href='abcd xyz pqr' cats","<a[\\s]+href[\\s]*=[\\s]* # find <a href= ([\\\"\\'])? # find single or double quote (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching # quote, otherwise match up to next space",[caseless, dotall, extended, - trim]))), + trim]))), <<":':abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href='abcd xyz pqr' cats","<a[\\s]+href[\\s]*=[\\s]* # find <a href= ([\\\"\\'])? # find single or double quote (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching @@ -28150,20 +28150,20 @@ run32() -> dotall, extended, {parts, - 2}]))), + 2}]))), <<":':abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href='abcd xyz pqr' cats","<a[\\s]+href[\\s]*=[\\s]* # find <a href= ([\\\"\\'])? # find single or double quote (?(1) (.*?)\\1 | ([^\\s]+)) # if quote found, match up to next matching # quote, otherwise match up to next space",[caseless, dotall, - extended]))), + extended]))), <<":::abcd: xyz">> = iolist_to_binary(join(re:split("<a href=abcd xyz","<a\\s+href\\s*=\\s* # find <a href= ([\"'])? # find single or double quote (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching # quote, otherwise match up to next space",[caseless, dotall, extended, - trim]))), + trim]))), <<":::abcd: xyz">> = iolist_to_binary(join(re:split("<a href=abcd xyz","<a\\s+href\\s*=\\s* # find <a href= ([\"'])? # find single or double quote (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching @@ -28171,20 +28171,20 @@ run32() -> dotall, extended, {parts, - 2}]))), + 2}]))), <<":::abcd: xyz">> = iolist_to_binary(join(re:split("<a href=abcd xyz","<a\\s+href\\s*=\\s* # find <a href= ([\"'])? # find single or double quote (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching # quote, otherwise match up to next space",[caseless, dotall, - extended]))), + extended]))), <<":\":abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href=\"abcd xyz pqr\" cats","<a\\s+href\\s*=\\s* # find <a href= ([\"'])? # find single or double quote (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching # quote, otherwise match up to next space",[caseless, dotall, extended, - trim]))), + trim]))), <<":\":abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href=\"abcd xyz pqr\" cats","<a\\s+href\\s*=\\s* # find <a href= ([\"'])? # find single or double quote (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching @@ -28192,20 +28192,20 @@ run32() -> dotall, extended, {parts, - 2}]))), + 2}]))), <<":\":abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href=\"abcd xyz pqr\" cats","<a\\s+href\\s*=\\s* # find <a href= ([\"'])? # find single or double quote (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching # quote, otherwise match up to next space",[caseless, dotall, - extended]))), + extended]))), <<":':abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href = 'abcd xyz pqr' cats","<a\\s+href\\s*=\\s* # find <a href= ([\"'])? # find single or double quote (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching # quote, otherwise match up to next space",[caseless, dotall, extended, - trim]))), + trim]))), <<":':abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href = 'abcd xyz pqr' cats","<a\\s+href\\s*=\\s* # find <a href= ([\"'])? # find single or double quote (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching @@ -28213,20 +28213,20 @@ run32() -> dotall, extended, {parts, - 2}]))), + 2}]))), <<":':abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href = 'abcd xyz pqr' cats","<a\\s+href\\s*=\\s* # find <a href= ([\"'])? # find single or double quote (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching # quote, otherwise match up to next space",[caseless, dotall, - extended]))), + extended]))), <<":::abcd: xyz">> = iolist_to_binary(join(re:split("<a href=abcd xyz","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href= ([\"'])? # find single or double quote (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching # quote, otherwise match up to next space",[caseless, dotall, extended, - trim]))), + trim]))), <<":::abcd: xyz">> = iolist_to_binary(join(re:split("<a href=abcd xyz","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href= ([\"'])? # find single or double quote (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching @@ -28234,20 +28234,20 @@ run32() -> dotall, extended, {parts, - 2}]))), + 2}]))), <<":::abcd: xyz">> = iolist_to_binary(join(re:split("<a href=abcd xyz","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href= ([\"'])? # find single or double quote (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching # quote, otherwise match up to next space",[caseless, dotall, - extended]))), + extended]))), <<":\":abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href=\"abcd xyz pqr\" cats","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href= ([\"'])? # find single or double quote (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching # quote, otherwise match up to next space",[caseless, dotall, extended, - trim]))), + trim]))), <<":\":abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href=\"abcd xyz pqr\" cats","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href= ([\"'])? # find single or double quote (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching @@ -28255,20 +28255,20 @@ run32() -> dotall, extended, {parts, - 2}]))), + 2}]))), <<":\":abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href=\"abcd xyz pqr\" cats","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href= ([\"'])? # find single or double quote (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching # quote, otherwise match up to next space",[caseless, dotall, - extended]))), + extended]))), <<":':abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href = 'abcd xyz pqr' cats","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href= ([\"'])? # find single or double quote (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching # quote, otherwise match up to next space",[caseless, dotall, extended, - trim]))), + trim]))), <<":':abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href = 'abcd xyz pqr' cats","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href= ([\"'])? # find single or double quote (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching @@ -28276,303 +28276,303 @@ run32() -> dotall, extended, {parts, - 2}]))), + 2}]))), <<":':abcd xyz pqr:: cats">> = iolist_to_binary(join(re:split("<a href = 'abcd xyz pqr' cats","<a\\s+href(?>\\s*)=(?>\\s*) # find <a href= ([\"'])? # find single or double quote (?(1) (.*?)\\1 | (\\S+)) # if quote found, match up to next matching # quote, otherwise match up to next space",[caseless, dotall, - extended]))), - <<":A:Z:B:::C:::D:::E:::F:::G">> = iolist_to_binary(join(re:split("ZABCDEFG","((Z)+|A)*",[trim]))), + extended]))), + <<":A:Z:B:::C:::D:::E:::F:::G">> = iolist_to_binary(join(re:split("ZABCDEFG","((Z)+|A)*",[trim]))), <<":A:Z:BCDEFG">> = iolist_to_binary(join(re:split("ZABCDEFG","((Z)+|A)*",[{parts, - 2}]))), - <<":A:Z:B:::C:::D:::E:::F:::G:::">> = iolist_to_binary(join(re:split("ZABCDEFG","((Z)+|A)*",[]))), - <<":A::B:::C:::D:::E:::F:::G">> = iolist_to_binary(join(re:split("ZABCDEFG","(Z()|A)*",[trim]))), + 2}]))), + <<":A:Z:B:::C:::D:::E:::F:::G:::">> = iolist_to_binary(join(re:split("ZABCDEFG","((Z)+|A)*",[]))), + <<":A::B:::C:::D:::E:::F:::G">> = iolist_to_binary(join(re:split("ZABCDEFG","(Z()|A)*",[trim]))), <<":A::BCDEFG">> = iolist_to_binary(join(re:split("ZABCDEFG","(Z()|A)*",[{parts, - 2}]))), - <<":A::B:::C:::D:::E:::F:::G:::">> = iolist_to_binary(join(re:split("ZABCDEFG","(Z()|A)*",[]))), - <<":A:::B::::C::::D::::E::::F::::G">> = iolist_to_binary(join(re:split("ZABCDEFG","(Z(())|A)*",[trim]))), + 2}]))), + <<":A::B:::C:::D:::E:::F:::G:::">> = iolist_to_binary(join(re:split("ZABCDEFG","(Z()|A)*",[]))), + <<":A:::B::::C::::D::::E::::F::::G">> = iolist_to_binary(join(re:split("ZABCDEFG","(Z(())|A)*",[trim]))), <<":A:::BCDEFG">> = iolist_to_binary(join(re:split("ZABCDEFG","(Z(())|A)*",[{parts, - 2}]))), - <<":A:::B::::C::::D::::E::::F::::G::::">> = iolist_to_binary(join(re:split("ZABCDEFG","(Z(())|A)*",[]))), - <<":A:B::C::D::E::F::G">> = iolist_to_binary(join(re:split("ZABCDEFG","((?>Z)+|A)*",[trim]))), + 2}]))), + <<":A:::B::::C::::D::::E::::F::::G::::">> = iolist_to_binary(join(re:split("ZABCDEFG","(Z(())|A)*",[]))), + <<":A:B::C::D::E::F::G">> = iolist_to_binary(join(re:split("ZABCDEFG","((?>Z)+|A)*",[trim]))), <<":A:BCDEFG">> = iolist_to_binary(join(re:split("ZABCDEFG","((?>Z)+|A)*",[{parts, - 2}]))), - <<":A:B::C::D::E::F::G::">> = iolist_to_binary(join(re:split("ZABCDEFG","((?>Z)+|A)*",[]))), - <<"Z::::B::C::D::E::F::G">> = iolist_to_binary(join(re:split("ZABCDEFG","((?>)+|A)*",[trim]))), + 2}]))), + <<":A:B::C::D::E::F::G::">> = iolist_to_binary(join(re:split("ZABCDEFG","((?>Z)+|A)*",[]))), + <<"Z::::B::C::D::E::F::G">> = iolist_to_binary(join(re:split("ZABCDEFG","((?>)+|A)*",[trim]))), <<"Z::ABCDEFG">> = iolist_to_binary(join(re:split("ZABCDEFG","((?>)+|A)*",[{parts, - 2}]))), - <<"Z::::B::C::D::E::F::G::">> = iolist_to_binary(join(re:split("ZABCDEFG","((?>)+|A)*",[]))), - <<":b:b:b">> = iolist_to_binary(join(re:split("abbab","a*",[trim]))), + 2}]))), + <<"Z::::B::C::D::E::F::G::">> = iolist_to_binary(join(re:split("ZABCDEFG","((?>)+|A)*",[]))), + <<":b:b:b">> = iolist_to_binary(join(re:split("abbab","a*",[trim]))), <<":bbab">> = iolist_to_binary(join(re:split("abbab","a*",[{parts, - 2}]))), - <<":b:b:b:">> = iolist_to_binary(join(re:split("abbab","a*",[]))), - <<":bcde">> = iolist_to_binary(join(re:split("abcde","^[\\d-a]",[trim]))), + 2}]))), + <<":b:b:b:">> = iolist_to_binary(join(re:split("abbab","a*",[]))), + <<":bcde">> = iolist_to_binary(join(re:split("abcde","^[\\d-a]",[trim]))), <<":bcde">> = iolist_to_binary(join(re:split("abcde","^[\\d-a]",[{parts, - 2}]))), - <<":bcde">> = iolist_to_binary(join(re:split("abcde","^[\\d-a]",[]))), - <<":things">> = iolist_to_binary(join(re:split("-things","^[\\d-a]",[trim]))), + 2}]))), + <<":bcde">> = iolist_to_binary(join(re:split("abcde","^[\\d-a]",[]))), + <<":things">> = iolist_to_binary(join(re:split("-things","^[\\d-a]",[trim]))), <<":things">> = iolist_to_binary(join(re:split("-things","^[\\d-a]",[{parts, - 2}]))), - <<":things">> = iolist_to_binary(join(re:split("-things","^[\\d-a]",[]))), - <<":digit">> = iolist_to_binary(join(re:split("0digit","^[\\d-a]",[trim]))), + 2}]))), + <<":things">> = iolist_to_binary(join(re:split("-things","^[\\d-a]",[]))), + <<":digit">> = iolist_to_binary(join(re:split("0digit","^[\\d-a]",[trim]))), <<":digit">> = iolist_to_binary(join(re:split("0digit","^[\\d-a]",[{parts, - 2}]))), - <<":digit">> = iolist_to_binary(join(re:split("0digit","^[\\d-a]",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[\\d-a]",[trim]))), + 2}]))), + <<":digit">> = iolist_to_binary(join(re:split("0digit","^[\\d-a]",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[\\d-a]",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[\\d-a]",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[\\d-a]",[]))), - <<"bcdef">> = iolist_to_binary(join(re:split("bcdef","^[\\d-a]",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^[\\d-a]",[]))), + <<"bcdef">> = iolist_to_binary(join(re:split("bcdef","^[\\d-a]",[trim]))), <<"bcdef">> = iolist_to_binary(join(re:split("bcdef","^[\\d-a]",[{parts, - 2}]))), - <<"bcdef">> = iolist_to_binary(join(re:split("bcdef","^[\\d-a]",[]))), + 2}]))), + <<"bcdef">> = iolist_to_binary(join(re:split("bcdef","^[\\d-a]",[]))), <<">:<">> = iolist_to_binary(join(re:split("> -
<","[[:space:]]+",[trim]))), +
<","[[:space:]]+",[trim]))), <<">:<">> = iolist_to_binary(join(re:split("> -
<","[[:space:]]+",[{parts,2}]))), +
<","[[:space:]]+",[{parts,2}]))), <<">:<">> = iolist_to_binary(join(re:split("> -
<","[[:space:]]+",[]))), +
<","[[:space:]]+",[]))), <<">:
<">> = iolist_to_binary(join(re:split("> -
<","[[:blank:]]+",[trim]))), +
<","[[:blank:]]+",[trim]))), <<">:
<">> = iolist_to_binary(join(re:split("> -
<","[[:blank:]]+",[{parts,2}]))), +
<","[[:blank:]]+",[{parts,2}]))), <<">:
<">> = iolist_to_binary(join(re:split("> -
<","[[:blank:]]+",[]))), +
<","[[:blank:]]+",[]))), <<">:<">> = iolist_to_binary(join(re:split("> -
<","[\\s]+",[trim]))), +
<","[\\s]+",[trim]))), <<">:<">> = iolist_to_binary(join(re:split("> -
<","[\\s]+",[{parts,2}]))), +
<","[\\s]+",[{parts,2}]))), <<">:<">> = iolist_to_binary(join(re:split("> -
<","[\\s]+",[]))), +
<","[\\s]+",[]))), <<">:<">> = iolist_to_binary(join(re:split("> -
<","\\s+",[trim]))), +
<","\\s+",[trim]))), <<">:<">> = iolist_to_binary(join(re:split("> -
<","\\s+",[{parts,2}]))), +
<","\\s+",[{parts,2}]))), <<">:<">> = iolist_to_binary(join(re:split("> -
<","\\s+",[]))), +
<","\\s+",[]))), <<"">> = iolist_to_binary(join(re:split("ab","ab",[extended, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("ab","ab",[extended, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ab","ab",[extended]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ab","ab",[extended]))), <<"a :b">> = iolist_to_binary(join(re:split("a -xb","(?!\\A)x",[multiline,trim]))), +xb","(?!\\A)x",[multiline,trim]))), <<"a :b">> = iolist_to_binary(join(re:split("a -xb","(?!\\A)x",[multiline,{parts,2}]))), +xb","(?!\\A)x",[multiline,{parts,2}]))), <<"a :b">> = iolist_to_binary(join(re:split("a -xb","(?!\\A)x",[multiline]))), +xb","(?!\\A)x",[multiline]))), <<"a xb">> = iolist_to_binary(join(re:split("a -xb","(?!^)x",[multiline,trim]))), +xb","(?!^)x",[multiline,trim]))), <<"a xb">> = iolist_to_binary(join(re:split("a -xb","(?!^)x",[multiline,{parts,2}]))), +xb","(?!^)x",[multiline,{parts,2}]))), <<"a xb">> = iolist_to_binary(join(re:split("a -xb","(?!^)x",[multiline]))), - <<"">> = iolist_to_binary(join(re:split("abcabcabc","abc\\Qabc\\Eabc",[trim]))), +xb","(?!^)x",[multiline]))), + <<"">> = iolist_to_binary(join(re:split("abcabcabc","abc\\Qabc\\Eabc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abcabcabc","abc\\Qabc\\Eabc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abcabcabc","abc\\Qabc\\Eabc",[]))), - <<"">> = iolist_to_binary(join(re:split("abc(*+|abc","abc\\Q(*+|\\Eabc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abcabcabc","abc\\Qabc\\Eabc",[]))), + <<"">> = iolist_to_binary(join(re:split("abc(*+|abc","abc\\Q(*+|\\Eabc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc(*+|abc","abc\\Q(*+|\\Eabc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc(*+|abc","abc\\Q(*+|\\Eabc",[]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc(*+|abc","abc\\Q(*+|\\Eabc",[]))), ok. run33() -> <<"">> = iolist_to_binary(join(re:split("abc abcabc"," abc\\Q abc\\Eabc",[extended, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("abc abcabc"," abc\\Q abc\\Eabc",[extended, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc abcabc"," abc\\Q abc\\Eabc",[extended]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc abcabc"," abc\\Q abc\\Eabc",[extended]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," abc\\Q abc\\Eabc",[extended, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," abc\\Q abc\\Eabc",[extended, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," abc\\Q abc\\Eabc",[extended]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers"," abc\\Q abc\\Eabc",[extended]))), <<"abcabcabc">> = iolist_to_binary(join(re:split("abcabcabc"," abc\\Q abc\\Eabc",[extended, - trim]))), + trim]))), <<"abcabcabc">> = iolist_to_binary(join(re:split("abcabcabc"," abc\\Q abc\\Eabc",[extended, {parts, - 2}]))), - <<"abcabcabc">> = iolist_to_binary(join(re:split("abcabcabc"," abc\\Q abc\\Eabc",[extended]))), + 2}]))), + <<"abcabcabc">> = iolist_to_binary(join(re:split("abcabcabc"," abc\\Q abc\\Eabc",[extended]))), <<"">> = iolist_to_binary(join(re:split("abc#not comment literal","abc#comment \\Q#not comment - literal\\E",[extended,trim]))), + literal\\E",[extended,trim]))), <<":">> = iolist_to_binary(join(re:split("abc#not comment literal","abc#comment \\Q#not comment - literal\\E",[extended,{parts,2}]))), + literal\\E",[extended,{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("abc#not comment literal","abc#comment \\Q#not comment - literal\\E",[extended]))), + literal\\E",[extended]))), <<"">> = iolist_to_binary(join(re:split("abc#not comment literal","abc#comment \\Q#not comment - literal",[extended,trim]))), + literal",[extended,trim]))), <<":">> = iolist_to_binary(join(re:split("abc#not comment literal","abc#comment \\Q#not comment - literal",[extended,{parts,2}]))), + literal",[extended,{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("abc#not comment literal","abc#comment \\Q#not comment - literal",[extended]))), + literal",[extended]))), <<"">> = iolist_to_binary(join(re:split("abc#not comment literal","abc#comment \\Q#not comment literal\\E #more comment - ",[extended,trim]))), + ",[extended,trim]))), <<":">> = iolist_to_binary(join(re:split("abc#not comment literal","abc#comment \\Q#not comment literal\\E #more comment - ",[extended,{parts,2}]))), + ",[extended,{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("abc#not comment literal","abc#comment \\Q#not comment literal\\E #more comment - ",[extended]))), + ",[extended]))), <<"">> = iolist_to_binary(join(re:split("abc#not comment literal","abc#comment \\Q#not comment - literal\\E #more comment",[extended,trim]))), + literal\\E #more comment",[extended,trim]))), <<":">> = iolist_to_binary(join(re:split("abc#not comment literal","abc#comment \\Q#not comment - literal\\E #more comment",[extended,{parts,2}]))), + literal\\E #more comment",[extended,{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("abc#not comment literal","abc#comment \\Q#not comment - literal\\E #more comment",[extended]))), - <<"">> = iolist_to_binary(join(re:split("abc\\$xyz","\\Qabc\\$xyz\\E",[trim]))), + literal\\E #more comment",[extended]))), + <<"">> = iolist_to_binary(join(re:split("abc\\$xyz","\\Qabc\\$xyz\\E",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc\\$xyz","\\Qabc\\$xyz\\E",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc\\$xyz","\\Qabc\\$xyz\\E",[]))), - <<"">> = iolist_to_binary(join(re:split("abc$xyz","\\Qabc\\E\\$\\Qxyz\\E",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc\\$xyz","\\Qabc\\$xyz\\E",[]))), + <<"">> = iolist_to_binary(join(re:split("abc$xyz","\\Qabc\\E\\$\\Qxyz\\E",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc$xyz","\\Qabc\\E\\$\\Qxyz\\E",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc$xyz","\\Qabc\\E\\$\\Qxyz\\E",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","\\Aabc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc$xyz","\\Qabc\\E\\$\\Qxyz\\E",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","\\Aabc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","\\Aabc",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc",[]))), - <<"xyzabc">> = iolist_to_binary(join(re:split("xyzabc","\\Aabc",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\Aabc",[]))), + <<"xyzabc">> = iolist_to_binary(join(re:split("xyzabc","\\Aabc",[trim]))), <<"xyzabc">> = iolist_to_binary(join(re:split("xyzabc","\\Aabc",[{parts, - 2}]))), - <<"xyzabc">> = iolist_to_binary(join(re:split("xyzabc","\\Aabc",[]))), - <<":abc2xyzabc3">> = iolist_to_binary(join(re:split("abc1abc2xyzabc3","\\Aabc.",[trim]))), + 2}]))), + <<"xyzabc">> = iolist_to_binary(join(re:split("xyzabc","\\Aabc",[]))), + <<":abc2xyzabc3">> = iolist_to_binary(join(re:split("abc1abc2xyzabc3","\\Aabc.",[trim]))), <<":abc2xyzabc3">> = iolist_to_binary(join(re:split("abc1abc2xyzabc3","\\Aabc.",[{parts, - 2}]))), - <<":abc2xyzabc3">> = iolist_to_binary(join(re:split("abc1abc2xyzabc3","\\Aabc.",[]))), - <<"::xyz">> = iolist_to_binary(join(re:split("abc1abc2xyzabc3","abc.",[trim]))), + 2}]))), + <<":abc2xyzabc3">> = iolist_to_binary(join(re:split("abc1abc2xyzabc3","\\Aabc.",[]))), + <<"::xyz">> = iolist_to_binary(join(re:split("abc1abc2xyzabc3","abc.",[trim]))), <<":abc2xyzabc3">> = iolist_to_binary(join(re:split("abc1abc2xyzabc3","abc.",[{parts, - 2}]))), - <<"::xyz:">> = iolist_to_binary(join(re:split("abc1abc2xyzabc3","abc.",[]))), - <<"X:Y">> = iolist_to_binary(join(re:split("XabcdY","a(?x: b c )d",[trim]))), + 2}]))), + <<"::xyz:">> = iolist_to_binary(join(re:split("abc1abc2xyzabc3","abc.",[]))), + <<"X:Y">> = iolist_to_binary(join(re:split("XabcdY","a(?x: b c )d",[trim]))), <<"X:Y">> = iolist_to_binary(join(re:split("XabcdY","a(?x: b c )d",[{parts, - 2}]))), - <<"X:Y">> = iolist_to_binary(join(re:split("XabcdY","a(?x: b c )d",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?x: b c )d",[trim]))), + 2}]))), + <<"X:Y">> = iolist_to_binary(join(re:split("XabcdY","a(?x: b c )d",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?x: b c )d",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?x: b c )d",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?x: b c )d",[]))), - <<"Xa b c d Y">> = iolist_to_binary(join(re:split("Xa b c d Y","a(?x: b c )d",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","a(?x: b c )d",[]))), + <<"Xa b c d Y">> = iolist_to_binary(join(re:split("Xa b c d Y","a(?x: b c )d",[trim]))), <<"Xa b c d Y">> = iolist_to_binary(join(re:split("Xa b c d Y","a(?x: b c )d",[{parts, - 2}]))), - <<"Xa b c d Y">> = iolist_to_binary(join(re:split("Xa b c d Y","a(?x: b c )d",[]))), - <<"X:abc:Y">> = iolist_to_binary(join(re:split("XabcY","((?x)x y z | a b c)",[trim]))), + 2}]))), + <<"Xa b c d Y">> = iolist_to_binary(join(re:split("Xa b c d Y","a(?x: b c )d",[]))), + <<"X:abc:Y">> = iolist_to_binary(join(re:split("XabcY","((?x)x y z | a b c)",[trim]))), <<"X:abc:Y">> = iolist_to_binary(join(re:split("XabcY","((?x)x y z | a b c)",[{parts, - 2}]))), - <<"X:abc:Y">> = iolist_to_binary(join(re:split("XabcY","((?x)x y z | a b c)",[]))), - <<"A:xyz:B">> = iolist_to_binary(join(re:split("AxyzB","((?x)x y z | a b c)",[trim]))), + 2}]))), + <<"X:abc:Y">> = iolist_to_binary(join(re:split("XabcY","((?x)x y z | a b c)",[]))), + <<"A:xyz:B">> = iolist_to_binary(join(re:split("AxyzB","((?x)x y z | a b c)",[trim]))), <<"A:xyz:B">> = iolist_to_binary(join(re:split("AxyzB","((?x)x y z | a b c)",[{parts, - 2}]))), - <<"A:xyz:B">> = iolist_to_binary(join(re:split("AxyzB","((?x)x y z | a b c)",[]))), - <<"X:Y">> = iolist_to_binary(join(re:split("XabCY","(?i)AB(?-i)C",[trim]))), + 2}]))), + <<"A:xyz:B">> = iolist_to_binary(join(re:split("AxyzB","((?x)x y z | a b c)",[]))), + <<"X:Y">> = iolist_to_binary(join(re:split("XabCY","(?i)AB(?-i)C",[trim]))), <<"X:Y">> = iolist_to_binary(join(re:split("XabCY","(?i)AB(?-i)C",[{parts, - 2}]))), - <<"X:Y">> = iolist_to_binary(join(re:split("XabCY","(?i)AB(?-i)C",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?i)AB(?-i)C",[trim]))), + 2}]))), + <<"X:Y">> = iolist_to_binary(join(re:split("XabCY","(?i)AB(?-i)C",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?i)AB(?-i)C",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?i)AB(?-i)C",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?i)AB(?-i)C",[]))), - <<"XabcY">> = iolist_to_binary(join(re:split("XabcY","(?i)AB(?-i)C",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(?i)AB(?-i)C",[]))), + <<"XabcY">> = iolist_to_binary(join(re:split("XabcY","(?i)AB(?-i)C",[trim]))), <<"XabcY">> = iolist_to_binary(join(re:split("XabcY","(?i)AB(?-i)C",[{parts, - 2}]))), - <<"XabcY">> = iolist_to_binary(join(re:split("XabcY","(?i)AB(?-i)C",[]))), - <<":abC">> = iolist_to_binary(join(re:split("abCE","((?i)AB(?-i)C|D)E",[trim]))), + 2}]))), + <<"XabcY">> = iolist_to_binary(join(re:split("XabcY","(?i)AB(?-i)C",[]))), + <<":abC">> = iolist_to_binary(join(re:split("abCE","((?i)AB(?-i)C|D)E",[trim]))), <<":abC:">> = iolist_to_binary(join(re:split("abCE","((?i)AB(?-i)C|D)E",[{parts, - 2}]))), - <<":abC:">> = iolist_to_binary(join(re:split("abCE","((?i)AB(?-i)C|D)E",[]))), - <<":D">> = iolist_to_binary(join(re:split("DE","((?i)AB(?-i)C|D)E",[trim]))), + 2}]))), + <<":abC:">> = iolist_to_binary(join(re:split("abCE","((?i)AB(?-i)C|D)E",[]))), + <<":D">> = iolist_to_binary(join(re:split("DE","((?i)AB(?-i)C|D)E",[trim]))), <<":D:">> = iolist_to_binary(join(re:split("DE","((?i)AB(?-i)C|D)E",[{parts, - 2}]))), - <<":D:">> = iolist_to_binary(join(re:split("DE","((?i)AB(?-i)C|D)E",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?i)AB(?-i)C|D)E",[trim]))), + 2}]))), + <<":D:">> = iolist_to_binary(join(re:split("DE","((?i)AB(?-i)C|D)E",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?i)AB(?-i)C|D)E",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?i)AB(?-i)C|D)E",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?i)AB(?-i)C|D)E",[]))), - <<"abcE">> = iolist_to_binary(join(re:split("abcE","((?i)AB(?-i)C|D)E",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((?i)AB(?-i)C|D)E",[]))), + <<"abcE">> = iolist_to_binary(join(re:split("abcE","((?i)AB(?-i)C|D)E",[trim]))), <<"abcE">> = iolist_to_binary(join(re:split("abcE","((?i)AB(?-i)C|D)E",[{parts, - 2}]))), - <<"abcE">> = iolist_to_binary(join(re:split("abcE","((?i)AB(?-i)C|D)E",[]))), - <<"abCe">> = iolist_to_binary(join(re:split("abCe","((?i)AB(?-i)C|D)E",[trim]))), + 2}]))), + <<"abcE">> = iolist_to_binary(join(re:split("abcE","((?i)AB(?-i)C|D)E",[]))), + <<"abCe">> = iolist_to_binary(join(re:split("abCe","((?i)AB(?-i)C|D)E",[trim]))), <<"abCe">> = iolist_to_binary(join(re:split("abCe","((?i)AB(?-i)C|D)E",[{parts, - 2}]))), - <<"abCe">> = iolist_to_binary(join(re:split("abCe","((?i)AB(?-i)C|D)E",[]))), - <<"dE">> = iolist_to_binary(join(re:split("dE","((?i)AB(?-i)C|D)E",[trim]))), + 2}]))), + <<"abCe">> = iolist_to_binary(join(re:split("abCe","((?i)AB(?-i)C|D)E",[]))), + <<"dE">> = iolist_to_binary(join(re:split("dE","((?i)AB(?-i)C|D)E",[trim]))), <<"dE">> = iolist_to_binary(join(re:split("dE","((?i)AB(?-i)C|D)E",[{parts, - 2}]))), - <<"dE">> = iolist_to_binary(join(re:split("dE","((?i)AB(?-i)C|D)E",[]))), - <<"De">> = iolist_to_binary(join(re:split("De","((?i)AB(?-i)C|D)E",[trim]))), + 2}]))), + <<"dE">> = iolist_to_binary(join(re:split("dE","((?i)AB(?-i)C|D)E",[]))), + <<"De">> = iolist_to_binary(join(re:split("De","((?i)AB(?-i)C|D)E",[trim]))), <<"De">> = iolist_to_binary(join(re:split("De","((?i)AB(?-i)C|D)E",[{parts, - 2}]))), - <<"De">> = iolist_to_binary(join(re:split("De","((?i)AB(?-i)C|D)E",[]))), - <<":abc">> = iolist_to_binary(join(re:split("abc123abc","(.*)\\d+\\1",[trim]))), + 2}]))), + <<"De">> = iolist_to_binary(join(re:split("De","((?i)AB(?-i)C|D)E",[]))), + <<":abc">> = iolist_to_binary(join(re:split("abc123abc","(.*)\\d+\\1",[trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abc123abc","(.*)\\d+\\1",[{parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abc123abc","(.*)\\d+\\1",[]))), - <<"a:bc">> = iolist_to_binary(join(re:split("abc123bc","(.*)\\d+\\1",[trim]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abc123abc","(.*)\\d+\\1",[]))), + <<"a:bc">> = iolist_to_binary(join(re:split("abc123bc","(.*)\\d+\\1",[trim]))), <<"a:bc:">> = iolist_to_binary(join(re:split("abc123bc","(.*)\\d+\\1",[{parts, - 2}]))), - <<"a:bc:">> = iolist_to_binary(join(re:split("abc123bc","(.*)\\d+\\1",[]))), + 2}]))), + <<"a:bc:">> = iolist_to_binary(join(re:split("abc123bc","(.*)\\d+\\1",[]))), <<":abc">> = iolist_to_binary(join(re:split("abc123abc","(.*)\\d+\\1",[dotall, - trim]))), + trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abc123abc","(.*)\\d+\\1",[dotall, {parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abc123abc","(.*)\\d+\\1",[dotall]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abc123abc","(.*)\\d+\\1",[dotall]))), <<"a:bc">> = iolist_to_binary(join(re:split("abc123bc","(.*)\\d+\\1",[dotall, - trim]))), + trim]))), <<"a:bc:">> = iolist_to_binary(join(re:split("abc123bc","(.*)\\d+\\1",[dotall, {parts, - 2}]))), - <<"a:bc:">> = iolist_to_binary(join(re:split("abc123bc","(.*)\\d+\\1",[dotall]))), - <<":abc:abc">> = iolist_to_binary(join(re:split("abc123abc","((.*))\\d+\\1",[trim]))), + 2}]))), + <<"a:bc:">> = iolist_to_binary(join(re:split("abc123bc","(.*)\\d+\\1",[dotall]))), + <<":abc:abc">> = iolist_to_binary(join(re:split("abc123abc","((.*))\\d+\\1",[trim]))), <<":abc:abc:">> = iolist_to_binary(join(re:split("abc123abc","((.*))\\d+\\1",[{parts, - 2}]))), - <<":abc:abc:">> = iolist_to_binary(join(re:split("abc123abc","((.*))\\d+\\1",[]))), - <<"a:bc:bc">> = iolist_to_binary(join(re:split("abc123bc","((.*))\\d+\\1",[trim]))), + 2}]))), + <<":abc:abc:">> = iolist_to_binary(join(re:split("abc123abc","((.*))\\d+\\1",[]))), + <<"a:bc:bc">> = iolist_to_binary(join(re:split("abc123bc","((.*))\\d+\\1",[trim]))), <<"a:bc:bc:">> = iolist_to_binary(join(re:split("abc123bc","((.*))\\d+\\1",[{parts, - 2}]))), - <<"a:bc:bc:">> = iolist_to_binary(join(re:split("abc123bc","((.*))\\d+\\1",[]))), + 2}]))), + <<"a:bc:bc:">> = iolist_to_binary(join(re:split("abc123bc","((.*))\\d+\\1",[]))), <<"">> = iolist_to_binary(join(re:split("a123::a123","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28581,7 +28581,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,trim]))), + ",[extended,caseless,trim]))), <<"::">> = iolist_to_binary(join(re:split("a123::a123","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28590,7 +28590,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,{parts,2}]))), + ",[extended,caseless,{parts,2}]))), <<"::">> = iolist_to_binary(join(re:split("a123::a123","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28599,7 +28599,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless]))), + ",[extended,caseless]))), <<"">> = iolist_to_binary(join(re:split("a123:b342::abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28608,7 +28608,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,trim]))), + ",[extended,caseless,trim]))), <<"::">> = iolist_to_binary(join(re:split("a123:b342::abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28617,7 +28617,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,{parts,2}]))), + ",[extended,caseless,{parts,2}]))), <<"::">> = iolist_to_binary(join(re:split("a123:b342::abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28626,7 +28626,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless]))), + ",[extended,caseless]))), <<"">> = iolist_to_binary(join(re:split("a123:b342::324e:abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28635,7 +28635,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,trim]))), + ",[extended,caseless,trim]))), <<"::">> = iolist_to_binary(join(re:split("a123:b342::324e:abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28644,7 +28644,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,{parts,2}]))), + ",[extended,caseless,{parts,2}]))), <<"::">> = iolist_to_binary(join(re:split("a123:b342::324e:abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28653,7 +28653,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless]))), + ",[extended,caseless]))), <<"">> = iolist_to_binary(join(re:split("a123:ddde:b342::324e:abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28662,7 +28662,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,trim]))), + ",[extended,caseless,trim]))), <<"::">> = iolist_to_binary(join(re:split("a123:ddde:b342::324e:abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28671,7 +28671,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,{parts,2}]))), + ",[extended,caseless,{parts,2}]))), <<"::">> = iolist_to_binary(join(re:split("a123:ddde:b342::324e:abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28680,7 +28680,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless]))), + ",[extended,caseless]))), <<"">> = iolist_to_binary(join(re:split("a123:ddde:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28689,7 +28689,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,trim]))), + ",[extended,caseless,trim]))), <<"::">> = iolist_to_binary(join(re:split("a123:ddde:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28698,7 +28698,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,{parts,2}]))), + ",[extended,caseless,{parts,2}]))), <<"::">> = iolist_to_binary(join(re:split("a123:ddde:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28707,7 +28707,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless]))), + ",[extended,caseless]))), <<"">> = iolist_to_binary(join(re:split("a123:ddde:9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28716,7 +28716,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,trim]))), + ",[extended,caseless,trim]))), <<"::">> = iolist_to_binary(join(re:split("a123:ddde:9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28725,7 +28725,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,{parts,2}]))), + ",[extended,caseless,{parts,2}]))), <<"::">> = iolist_to_binary(join(re:split("a123:ddde:9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28734,7 +28734,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless]))), + ",[extended,caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28743,7 +28743,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,trim]))), + ",[extended,caseless,trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28752,7 +28752,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,{parts,2}]))), + ",[extended,caseless,{parts,2}]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28761,7 +28761,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless]))), + ",[extended,caseless]))), <<"1:2:3:4:5:6:7:8">> = iolist_to_binary(join(re:split("1:2:3:4:5:6:7:8","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28770,7 +28770,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,trim]))), + ",[extended,caseless,trim]))), <<"1:2:3:4:5:6:7:8">> = iolist_to_binary(join(re:split("1:2:3:4:5:6:7:8","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28779,7 +28779,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,{parts,2}]))), + ",[extended,caseless,{parts,2}]))), <<"1:2:3:4:5:6:7:8">> = iolist_to_binary(join(re:split("1:2:3:4:5:6:7:8","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28788,7 +28788,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless]))), + ",[extended,caseless]))), <<"a123:bce:ddde:9999:b342::324e:dcba:abcd">> = iolist_to_binary(join(re:split("a123:bce:ddde:9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28797,7 +28797,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,trim]))), + ",[extended,caseless,trim]))), <<"a123:bce:ddde:9999:b342::324e:dcba:abcd">> = iolist_to_binary(join(re:split("a123:bce:ddde:9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28806,7 +28806,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,{parts,2}]))), + ",[extended,caseless,{parts,2}]))), <<"a123:bce:ddde:9999:b342::324e:dcba:abcd">> = iolist_to_binary(join(re:split("a123:bce:ddde:9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28815,7 +28815,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless]))), + ",[extended,caseless]))), <<"a123::9999:b342::324e:dcba:abcd">> = iolist_to_binary(join(re:split("a123::9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28824,7 +28824,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,trim]))), + ",[extended,caseless,trim]))), <<"a123::9999:b342::324e:dcba:abcd">> = iolist_to_binary(join(re:split("a123::9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28833,7 +28833,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,{parts,2}]))), + ",[extended,caseless,{parts,2}]))), <<"a123::9999:b342::324e:dcba:abcd">> = iolist_to_binary(join(re:split("a123::9999:b342::324e:dcba:abcd","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28842,7 +28842,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless]))), + ",[extended,caseless]))), <<"abcde:2:3:4:5:6:7:8">> = iolist_to_binary(join(re:split("abcde:2:3:4:5:6:7:8","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28851,7 +28851,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,trim]))), + ",[extended,caseless,trim]))), <<"abcde:2:3:4:5:6:7:8">> = iolist_to_binary(join(re:split("abcde:2:3:4:5:6:7:8","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28860,7 +28860,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,{parts,2}]))), + ",[extended,caseless,{parts,2}]))), <<"abcde:2:3:4:5:6:7:8">> = iolist_to_binary(join(re:split("abcde:2:3:4:5:6:7:8","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28869,7 +28869,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless]))), + ",[extended,caseless]))), <<"::1">> = iolist_to_binary(join(re:split("::1","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28878,7 +28878,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,trim]))), + ",[extended,caseless,trim]))), <<"::1">> = iolist_to_binary(join(re:split("::1","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28887,7 +28887,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,{parts,2}]))), + ",[extended,caseless,{parts,2}]))), <<"::1">> = iolist_to_binary(join(re:split("::1","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28896,7 +28896,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless]))), + ",[extended,caseless]))), <<"abcd:fee0:123::">> = iolist_to_binary(join(re:split("abcd:fee0:123::","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28905,7 +28905,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,trim]))), + ",[extended,caseless,trim]))), <<"abcd:fee0:123::">> = iolist_to_binary(join(re:split("abcd:fee0:123::","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28914,7 +28914,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,{parts,2}]))), + ",[extended,caseless,{parts,2}]))), <<"abcd:fee0:123::">> = iolist_to_binary(join(re:split("abcd:fee0:123::","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28923,7 +28923,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless]))), + ",[extended,caseless]))), <<":1">> = iolist_to_binary(join(re:split(":1","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28932,7 +28932,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,trim]))), + ",[extended,caseless,trim]))), <<":1">> = iolist_to_binary(join(re:split(":1","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28941,7 +28941,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,{parts,2}]))), + ",[extended,caseless,{parts,2}]))), <<":1">> = iolist_to_binary(join(re:split(":1","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28950,7 +28950,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless]))), + ",[extended,caseless]))), <<"1:">> = iolist_to_binary(join(re:split("1:","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28959,7 +28959,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,trim]))), + ",[extended,caseless,trim]))), <<"1:">> = iolist_to_binary(join(re:split("1:","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28968,7 +28968,7 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless,{parts,2}]))), + ",[extended,caseless,{parts,2}]))), <<"1:">> = iolist_to_binary(join(re:split("1:","^(?!:) # colon disallowed at start (?: # start of item (?: [0-9a-f]{1,4} | # 1-4 hex digits or @@ -28977,3098 +28977,3147 @@ run33() -> ){1,7} # end item; 1-7 of them required [0-9a-f]{1,4} $ # final hex number at end of string (?(1)|.) # check that there was an empty component - ",[extended,caseless]))), - <<"">> = iolist_to_binary(join(re:split("z","[z\\Qa-d]\\E]",[trim]))), + ",[extended,caseless]))), + <<"">> = iolist_to_binary(join(re:split("z","[z\\Qa-d]\\E]",[trim]))), <<":">> = iolist_to_binary(join(re:split("z","[z\\Qa-d]\\E]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("z","[z\\Qa-d]\\E]",[]))), - <<"">> = iolist_to_binary(join(re:split("a","[z\\Qa-d]\\E]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("z","[z\\Qa-d]\\E]",[]))), + <<"">> = iolist_to_binary(join(re:split("a","[z\\Qa-d]\\E]",[trim]))), <<":">> = iolist_to_binary(join(re:split("a","[z\\Qa-d]\\E]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","[z\\Qa-d]\\E]",[]))), - <<"">> = iolist_to_binary(join(re:split("-","[z\\Qa-d]\\E]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","[z\\Qa-d]\\E]",[]))), + <<"">> = iolist_to_binary(join(re:split("-","[z\\Qa-d]\\E]",[trim]))), <<":">> = iolist_to_binary(join(re:split("-","[z\\Qa-d]\\E]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("-","[z\\Qa-d]\\E]",[]))), - <<"">> = iolist_to_binary(join(re:split("d","[z\\Qa-d]\\E]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("-","[z\\Qa-d]\\E]",[]))), + <<"">> = iolist_to_binary(join(re:split("d","[z\\Qa-d]\\E]",[trim]))), <<":">> = iolist_to_binary(join(re:split("d","[z\\Qa-d]\\E]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("d","[z\\Qa-d]\\E]",[]))), - <<"">> = iolist_to_binary(join(re:split("]","[z\\Qa-d]\\E]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("d","[z\\Qa-d]\\E]",[]))), + <<"">> = iolist_to_binary(join(re:split("]","[z\\Qa-d]\\E]",[trim]))), <<":">> = iolist_to_binary(join(re:split("]","[z\\Qa-d]\\E]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("]","[z\\Qa-d]\\E]",[]))), - <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[z\\Qa-d]\\E]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("]","[z\\Qa-d]\\E]",[]))), + <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[z\\Qa-d]\\E]",[trim]))), <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[z\\Qa-d]\\E]",[{parts, - 2}]))), - <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[z\\Qa-d]\\E]",[]))), - <<"b">> = iolist_to_binary(join(re:split("b","[z\\Qa-d]\\E]",[trim]))), + 2}]))), + <<"*** F:ilers">> = iolist_to_binary(join(re:split("*** Failers","[z\\Qa-d]\\E]",[]))), + <<"b">> = iolist_to_binary(join(re:split("b","[z\\Qa-d]\\E]",[trim]))), <<"b">> = iolist_to_binary(join(re:split("b","[z\\Qa-d]\\E]",[{parts, - 2}]))), - <<"b">> = iolist_to_binary(join(re:split("b","[z\\Qa-d]\\E]",[]))), + 2}]))), + <<"b">> = iolist_to_binary(join(re:split("b","[z\\Qa-d]\\E]",[]))), ok. run34() -> - <<"">> = iolist_to_binary(join(re:split("z","[\\z\\C]",[trim]))), + <<"">> = iolist_to_binary(join(re:split("z","[\\z\\C]",[trim]))), <<":">> = iolist_to_binary(join(re:split("z","[\\z\\C]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("z","[\\z\\C]",[]))), - <<"">> = iolist_to_binary(join(re:split("C","[\\z\\C]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("z","[\\z\\C]",[]))), + <<"">> = iolist_to_binary(join(re:split("C","[\\z\\C]",[trim]))), <<":">> = iolist_to_binary(join(re:split("C","[\\z\\C]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("C","[\\z\\C]",[]))), - <<"">> = iolist_to_binary(join(re:split("M","\\M",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("C","[\\z\\C]",[]))), + <<"">> = iolist_to_binary(join(re:split("M","\\M",[trim]))), <<":">> = iolist_to_binary(join(re:split("M","\\M",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("M","\\M",[]))), - <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a+)*b",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("M","\\M",[]))), + <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a+)*b",[trim]))), <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a+)*b",[{parts, - 2}]))), - <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a+)*b",[]))), - <<"XAZ">> = iolist_to_binary(join(re:split("XAZXB","(?<=Z)X.",[trim]))), + 2}]))), + <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a+)*b",[]))), + <<"XAZ">> = iolist_to_binary(join(re:split("XAZXB","(?<=Z)X.",[trim]))), <<"XAZ:">> = iolist_to_binary(join(re:split("XAZXB","(?<=Z)X.",[{parts, - 2}]))), - <<"XAZ:">> = iolist_to_binary(join(re:split("XAZXB","(?<=Z)X.",[]))), - <<"">> = iolist_to_binary(join(re:split("ab cd defg","ab cd (?x) de fg",[trim]))), + 2}]))), + <<"XAZ:">> = iolist_to_binary(join(re:split("XAZXB","(?<=Z)X.",[]))), + <<"">> = iolist_to_binary(join(re:split("ab cd defg","ab cd (?x) de fg",[trim]))), <<":">> = iolist_to_binary(join(re:split("ab cd defg","ab cd (?x) de fg",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ab cd defg","ab cd (?x) de fg",[]))), - <<"">> = iolist_to_binary(join(re:split("ab cddefg","ab cd(?x) de fg",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ab cd defg","ab cd (?x) de fg",[]))), + <<"">> = iolist_to_binary(join(re:split("ab cddefg","ab cd(?x) de fg",[trim]))), <<":">> = iolist_to_binary(join(re:split("ab cddefg","ab cd(?x) de fg",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ab cddefg","ab cd(?x) de fg",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","ab cd(?x) de fg",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ab cddefg","ab cd(?x) de fg",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","ab cd(?x) de fg",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","ab cd(?x) de fg",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","ab cd(?x) de fg",[]))), - <<"abcddefg">> = iolist_to_binary(join(re:split("abcddefg","ab cd(?x) de fg",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","ab cd(?x) de fg",[]))), + <<"abcddefg">> = iolist_to_binary(join(re:split("abcddefg","ab cd(?x) de fg",[trim]))), <<"abcddefg">> = iolist_to_binary(join(re:split("abcddefg","ab cd(?x) de fg",[{parts, - 2}]))), - <<"abcddefg">> = iolist_to_binary(join(re:split("abcddefg","ab cd(?x) de fg",[]))), - <<"foo:bar:X">> = iolist_to_binary(join(re:split("foobarX","(?<![^f]oo)(bar)",[trim]))), + 2}]))), + <<"abcddefg">> = iolist_to_binary(join(re:split("abcddefg","ab cd(?x) de fg",[]))), + <<"foo:bar:X">> = iolist_to_binary(join(re:split("foobarX","(?<![^f]oo)(bar)",[trim]))), <<"foo:bar:X">> = iolist_to_binary(join(re:split("foobarX","(?<![^f]oo)(bar)",[{parts, - 2}]))), - <<"foo:bar:X">> = iolist_to_binary(join(re:split("foobarX","(?<![^f]oo)(bar)",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<![^f]oo)(bar)",[trim]))), + 2}]))), + <<"foo:bar:X">> = iolist_to_binary(join(re:split("foobarX","(?<![^f]oo)(bar)",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<![^f]oo)(bar)",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<![^f]oo)(bar)",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<![^f]oo)(bar)",[]))), - <<"boobarX">> = iolist_to_binary(join(re:split("boobarX","(?<![^f]oo)(bar)",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<![^f]oo)(bar)",[]))), + <<"boobarX">> = iolist_to_binary(join(re:split("boobarX","(?<![^f]oo)(bar)",[trim]))), <<"boobarX">> = iolist_to_binary(join(re:split("boobarX","(?<![^f]oo)(bar)",[{parts, - 2}]))), - <<"boobarX">> = iolist_to_binary(join(re:split("boobarX","(?<![^f]oo)(bar)",[]))), - <<"off">> = iolist_to_binary(join(re:split("offX","(?<![^f])X",[trim]))), + 2}]))), + <<"boobarX">> = iolist_to_binary(join(re:split("boobarX","(?<![^f]oo)(bar)",[]))), + <<"off">> = iolist_to_binary(join(re:split("offX","(?<![^f])X",[trim]))), <<"off:">> = iolist_to_binary(join(re:split("offX","(?<![^f])X",[{parts, - 2}]))), - <<"off:">> = iolist_to_binary(join(re:split("offX","(?<![^f])X",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<![^f])X",[trim]))), + 2}]))), + <<"off:">> = iolist_to_binary(join(re:split("offX","(?<![^f])X",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<![^f])X",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<![^f])X",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<![^f])X",[]))), - <<"onyX">> = iolist_to_binary(join(re:split("onyX","(?<![^f])X",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<![^f])X",[]))), + <<"onyX">> = iolist_to_binary(join(re:split("onyX","(?<![^f])X",[trim]))), <<"onyX">> = iolist_to_binary(join(re:split("onyX","(?<![^f])X",[{parts, - 2}]))), - <<"onyX">> = iolist_to_binary(join(re:split("onyX","(?<![^f])X",[]))), - <<"ony">> = iolist_to_binary(join(re:split("onyX","(?<=[^f])X",[trim]))), + 2}]))), + <<"onyX">> = iolist_to_binary(join(re:split("onyX","(?<![^f])X",[]))), + <<"ony">> = iolist_to_binary(join(re:split("onyX","(?<=[^f])X",[trim]))), <<"ony:">> = iolist_to_binary(join(re:split("onyX","(?<=[^f])X",[{parts, - 2}]))), - <<"ony:">> = iolist_to_binary(join(re:split("onyX","(?<=[^f])X",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=[^f])X",[trim]))), + 2}]))), + <<"ony:">> = iolist_to_binary(join(re:split("onyX","(?<=[^f])X",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=[^f])X",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=[^f])X",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=[^f])X",[]))), - <<"offX">> = iolist_to_binary(join(re:split("offX","(?<=[^f])X",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=[^f])X",[]))), + <<"offX">> = iolist_to_binary(join(re:split("offX","(?<=[^f])X",[trim]))), <<"offX">> = iolist_to_binary(join(re:split("offX","(?<=[^f])X",[{parts, - 2}]))), - <<"offX">> = iolist_to_binary(join(re:split("offX","(?<=[^f])X",[]))), + 2}]))), + <<"offX">> = iolist_to_binary(join(re:split("offX","(?<=[^f])X",[]))), <<"a :b :c">> = iolist_to_binary(join(re:split("a b -c","^",[multiline,trim]))), +c","^",[multiline,trim]))), <<"a :b c">> = iolist_to_binary(join(re:split("a b -c","^",[multiline,{parts,2}]))), +c","^",[multiline,{parts,2}]))), <<"a :b :c">> = iolist_to_binary(join(re:split("a b -c","^",[multiline]))), +c","^",[multiline]))), <<"">> = iolist_to_binary(join(re:split("","^",[multiline, - trim]))), + trim]))), <<"">> = iolist_to_binary(join(re:split("","^",[multiline, {parts, - 2}]))), - <<"">> = iolist_to_binary(join(re:split("","^",[multiline]))), + 2}]))), + <<"">> = iolist_to_binary(join(re:split("","^",[multiline]))), <<"A C :C">> = iolist_to_binary(join(re:split("A C -C","(?<=C\\n)^",[multiline,trim]))), +C","(?<=C\\n)^",[multiline,trim]))), <<"A C :C">> = iolist_to_binary(join(re:split("A C -C","(?<=C\\n)^",[multiline,{parts,2}]))), +C","(?<=C\\n)^",[multiline,{parts,2}]))), <<"A C :C">> = iolist_to_binary(join(re:split("A C -C","(?<=C\\n)^",[multiline]))), - <<":X">> = iolist_to_binary(join(re:split("bXaX","(?:(?(1)a|b)(X))+",[trim]))), +C","(?<=C\\n)^",[multiline]))), + <<":X">> = iolist_to_binary(join(re:split("bXaX","(?:(?(1)a|b)(X))+",[trim]))), <<":X:">> = iolist_to_binary(join(re:split("bXaX","(?:(?(1)a|b)(X))+",[{parts, - 2}]))), - <<":X:">> = iolist_to_binary(join(re:split("bXaX","(?:(?(1)a|b)(X))+",[]))), - <<":Y">> = iolist_to_binary(join(re:split("bXXaYYaY","(?:(?(1)\\1a|b)(X|Y))+",[trim]))), + 2}]))), + <<":X:">> = iolist_to_binary(join(re:split("bXaX","(?:(?(1)a|b)(X))+",[]))), + <<":Y">> = iolist_to_binary(join(re:split("bXXaYYaY","(?:(?(1)\\1a|b)(X|Y))+",[trim]))), <<":Y:">> = iolist_to_binary(join(re:split("bXXaYYaY","(?:(?(1)\\1a|b)(X|Y))+",[{parts, - 2}]))), - <<":Y:">> = iolist_to_binary(join(re:split("bXXaYYaY","(?:(?(1)\\1a|b)(X|Y))+",[]))), - <<":X:YaXXaX">> = iolist_to_binary(join(re:split("bXYaXXaX","(?:(?(1)\\1a|b)(X|Y))+",[trim]))), + 2}]))), + <<":Y:">> = iolist_to_binary(join(re:split("bXXaYYaY","(?:(?(1)\\1a|b)(X|Y))+",[]))), + <<":X:YaXXaX">> = iolist_to_binary(join(re:split("bXYaXXaX","(?:(?(1)\\1a|b)(X|Y))+",[trim]))), <<":X:YaXXaX">> = iolist_to_binary(join(re:split("bXYaXXaX","(?:(?(1)\\1a|b)(X|Y))+",[{parts, - 2}]))), - <<":X:YaXXaX">> = iolist_to_binary(join(re:split("bXYaXXaX","(?:(?(1)\\1a|b)(X|Y))+",[]))), - <<"::::::::::X:XaYYaY">> = iolist_to_binary(join(re:split("bXXaYYaY","()()()()()()()()()(?:(?(10)\\10a|b)(X|Y))+",[trim]))), + 2}]))), + <<":X:YaXXaX">> = iolist_to_binary(join(re:split("bXYaXXaX","(?:(?(1)\\1a|b)(X|Y))+",[]))), + <<"::::::::::X:XaYYaY">> = iolist_to_binary(join(re:split("bXXaYYaY","()()()()()()()()()(?:(?(10)\\10a|b)(X|Y))+",[trim]))), <<"::::::::::X:XaYYaY">> = iolist_to_binary(join(re:split("bXXaYYaY","()()()()()()()()()(?:(?(10)\\10a|b)(X|Y))+",[{parts, - 2}]))), - <<"::::::::::X:XaYYaY">> = iolist_to_binary(join(re:split("bXXaYYaY","()()()()()()()()()(?:(?(10)\\10a|b)(X|Y))+",[]))), - <<"">> = iolist_to_binary(join(re:split("abc]","[[,abc,]+]",[trim]))), + 2}]))), + <<"::::::::::X:XaYYaY">> = iolist_to_binary(join(re:split("bXXaYYaY","()()()()()()()()()(?:(?(10)\\10a|b)(X|Y))+",[]))), + <<"">> = iolist_to_binary(join(re:split("abc]","[[,abc,]+]",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc]","[[,abc,]+]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc]","[[,abc,]+]",[]))), - <<"">> = iolist_to_binary(join(re:split("a,b]","[[,abc,]+]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc]","[[,abc,]+]",[]))), + <<"">> = iolist_to_binary(join(re:split("a,b]","[[,abc,]+]",[trim]))), <<":">> = iolist_to_binary(join(re:split("a,b]","[[,abc,]+]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a,b]","[[,abc,]+]",[]))), - <<"">> = iolist_to_binary(join(re:split("[a,b,c]","[[,abc,]+]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a,b]","[[,abc,]+]",[]))), + <<"">> = iolist_to_binary(join(re:split("[a,b,c]","[[,abc,]+]",[trim]))), <<":">> = iolist_to_binary(join(re:split("[a,b,c]","[[,abc,]+]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("[a,b,c]","[[,abc,]+]",[]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("[a,b,c]","[[,abc,]+]",[]))), <<"A:B">> = iolist_to_binary(join(re:split("A B","(?-x: )",[extended, - trim]))), + trim]))), <<"A:B">> = iolist_to_binary(join(re:split("A B","(?-x: )",[extended, {parts, - 2}]))), - <<"A:B">> = iolist_to_binary(join(re:split("A B","(?-x: )",[extended]))), - <<"A:B">> = iolist_to_binary(join(re:split("A # B","(?x)(?-x: \\s*#\\s*)",[trim]))), + 2}]))), + <<"A:B">> = iolist_to_binary(join(re:split("A B","(?-x: )",[extended]))), + <<"A:B">> = iolist_to_binary(join(re:split("A # B","(?x)(?-x: \\s*#\\s*)",[trim]))), <<"A:B">> = iolist_to_binary(join(re:split("A # B","(?x)(?-x: \\s*#\\s*)",[{parts, - 2}]))), - <<"A:B">> = iolist_to_binary(join(re:split("A # B","(?x)(?-x: \\s*#\\s*)",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?x)(?-x: \\s*#\\s*)",[trim]))), + 2}]))), + <<"A:B">> = iolist_to_binary(join(re:split("A # B","(?x)(?-x: \\s*#\\s*)",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?x)(?-x: \\s*#\\s*)",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?x)(?-x: \\s*#\\s*)",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?x)(?-x: \\s*#\\s*)",[]))), - <<"#">> = iolist_to_binary(join(re:split("#","(?x)(?-x: \\s*#\\s*)",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?x)(?-x: \\s*#\\s*)",[]))), + <<"#">> = iolist_to_binary(join(re:split("#","(?x)(?-x: \\s*#\\s*)",[trim]))), <<"#">> = iolist_to_binary(join(re:split("#","(?x)(?-x: \\s*#\\s*)",[{parts, - 2}]))), - <<"#">> = iolist_to_binary(join(re:split("#","(?x)(?-x: \\s*#\\s*)",[]))), - <<"A">> = iolist_to_binary(join(re:split("A #include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[trim]))), + 2}]))), + <<"#">> = iolist_to_binary(join(re:split("#","(?x)(?-x: \\s*#\\s*)",[]))), + <<"A">> = iolist_to_binary(join(re:split("A #include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[trim]))), <<"A:">> = iolist_to_binary(join(re:split("A #include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[{parts, - 2}]))), - <<"A:">> = iolist_to_binary(join(re:split("A #include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[trim]))), + 2}]))), + <<"A:">> = iolist_to_binary(join(re:split("A #include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[]))), - <<"A#include">> = iolist_to_binary(join(re:split("A#include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[]))), + <<"A#include">> = iolist_to_binary(join(re:split("A#include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[trim]))), <<"A#include">> = iolist_to_binary(join(re:split("A#include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[{parts, - 2}]))), - <<"A#include">> = iolist_to_binary(join(re:split("A#include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[]))), - <<"A #Include">> = iolist_to_binary(join(re:split("A #Include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[trim]))), + 2}]))), + <<"A#include">> = iolist_to_binary(join(re:split("A#include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[]))), + <<"A #Include">> = iolist_to_binary(join(re:split("A #Include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[trim]))), <<"A #Include">> = iolist_to_binary(join(re:split("A #Include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[{parts, - 2}]))), - <<"A #Include">> = iolist_to_binary(join(re:split("A #Include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[]))), + 2}]))), + <<"A #Include">> = iolist_to_binary(join(re:split("A #Include","(?x-is)(?:(?-ixs) \\s*#\\s*) include",[]))), ok. run35() -> - <<"">> = iolist_to_binary(join(re:split("aaabbbb","a*b*\\w",[trim]))), + <<"">> = iolist_to_binary(join(re:split("aaabbbb","a*b*\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaabbbb","a*b*\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaabbbb","a*b*\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaa","a*b*\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaabbbb","a*b*\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaa","a*b*\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaa","a*b*\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaa","a*b*\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("a","a*b*\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaa","a*b*\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("a","a*b*\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("a","a*b*\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","a*b*\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("aaabbbb","a*b?\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","a*b*\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("aaabbbb","a*b?\\w",[trim]))), <<":bb">> = iolist_to_binary(join(re:split("aaabbbb","a*b?\\w",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("aaabbbb","a*b?\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaa","a*b?\\w",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("aaabbbb","a*b?\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaa","a*b?\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaa","a*b?\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaa","a*b?\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("a","a*b?\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaa","a*b?\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("a","a*b?\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("a","a*b?\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","a*b?\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("aaabbbb","a*b{0,4}\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","a*b?\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("aaabbbb","a*b{0,4}\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaabbbb","a*b{0,4}\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaabbbb","a*b{0,4}\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaa","a*b{0,4}\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaabbbb","a*b{0,4}\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaa","a*b{0,4}\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaa","a*b{0,4}\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaa","a*b{0,4}\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("a","a*b{0,4}\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaa","a*b{0,4}\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("a","a*b{0,4}\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("a","a*b{0,4}\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","a*b{0,4}\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("aaabbbb","a*b{0,}\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","a*b{0,4}\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("aaabbbb","a*b{0,}\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaabbbb","a*b{0,}\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaabbbb","a*b{0,}\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaa","a*b{0,}\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaabbbb","a*b{0,}\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaa","a*b{0,}\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaa","a*b{0,}\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaa","a*b{0,}\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("a","a*b{0,}\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaa","a*b{0,}\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("a","a*b{0,}\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("a","a*b{0,}\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","a*b{0,}\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("0a","a*\\d*\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","a*b{0,}\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("0a","a*\\d*\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("0a","a*\\d*\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("0a","a*\\d*\\w",[]))), - <<"">> = iolist_to_binary(join(re:split("a","a*\\d*\\w",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("0a","a*\\d*\\w",[]))), + <<"">> = iolist_to_binary(join(re:split("a","a*\\d*\\w",[trim]))), <<":">> = iolist_to_binary(join(re:split("a","a*\\d*\\w",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","a*\\d*\\w",[]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","a*\\d*\\w",[]))), <<"">> = iolist_to_binary(join(re:split("a","a*b *\\w",[extended, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("a","a*b *\\w",[extended, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","a*b *\\w",[extended]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","a*b *\\w",[extended]))), <<"">> = iolist_to_binary(join(re:split("a","a*b#comment - *\\w",[extended,trim]))), + *\\w",[extended,trim]))), <<":">> = iolist_to_binary(join(re:split("a","a*b#comment - *\\w",[extended,{parts,2}]))), + *\\w",[extended,{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("a","a*b#comment - *\\w",[extended]))), + *\\w",[extended]))), <<"">> = iolist_to_binary(join(re:split("a","a* b *\\w",[extended, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("a","a* b *\\w",[extended, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","a* b *\\w",[extended]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","a* b *\\w",[extended]))), <<":: pqr">> = iolist_to_binary(join(re:split("abc=xyz\\ -pqr","^\\w+=.*(\\\\\\n.*)*",[trim]))), +pqr","^\\w+=.*(\\\\\\n.*)*",[trim]))), <<":: pqr">> = iolist_to_binary(join(re:split("abc=xyz\\ -pqr","^\\w+=.*(\\\\\\n.*)*",[{parts,2}]))), +pqr","^\\w+=.*(\\\\\\n.*)*",[{parts,2}]))), <<":: pqr">> = iolist_to_binary(join(re:split("abc=xyz\\ -pqr","^\\w+=.*(\\\\\\n.*)*",[]))), - <<":abcd">> = iolist_to_binary(join(re:split("abcd:","(?=(\\w+))\\1:",[trim]))), +pqr","^\\w+=.*(\\\\\\n.*)*",[]))), + <<":abcd">> = iolist_to_binary(join(re:split("abcd:","(?=(\\w+))\\1:",[trim]))), <<":abcd:">> = iolist_to_binary(join(re:split("abcd:","(?=(\\w+))\\1:",[{parts, - 2}]))), - <<":abcd:">> = iolist_to_binary(join(re:split("abcd:","(?=(\\w+))\\1:",[]))), - <<":abcd">> = iolist_to_binary(join(re:split("abcd:","^(?=(\\w+))\\1:",[trim]))), + 2}]))), + <<":abcd:">> = iolist_to_binary(join(re:split("abcd:","(?=(\\w+))\\1:",[]))), + <<":abcd">> = iolist_to_binary(join(re:split("abcd:","^(?=(\\w+))\\1:",[trim]))), <<":abcd:">> = iolist_to_binary(join(re:split("abcd:","^(?=(\\w+))\\1:",[{parts, - 2}]))), - <<":abcd:">> = iolist_to_binary(join(re:split("abcd:","^(?=(\\w+))\\1:",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","^\\Eabc",[trim]))), + 2}]))), + <<":abcd:">> = iolist_to_binary(join(re:split("abcd:","^(?=(\\w+))\\1:",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","^\\Eabc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","^\\Eabc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","^\\Eabc",[]))), - <<"">> = iolist_to_binary(join(re:split("a","^[\\Eabc]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","^\\Eabc",[]))), + <<"">> = iolist_to_binary(join(re:split("a","^[\\Eabc]",[trim]))), <<":">> = iolist_to_binary(join(re:split("a","^[\\Eabc]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","^[\\Eabc]",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\Eabc]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","^[\\Eabc]",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\Eabc]",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\Eabc]",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\Eabc]",[]))), - <<"E">> = iolist_to_binary(join(re:split("E","^[\\Eabc]",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\Eabc]",[]))), + <<"E">> = iolist_to_binary(join(re:split("E","^[\\Eabc]",[trim]))), <<"E">> = iolist_to_binary(join(re:split("E","^[\\Eabc]",[{parts, - 2}]))), - <<"E">> = iolist_to_binary(join(re:split("E","^[\\Eabc]",[]))), - <<"">> = iolist_to_binary(join(re:split("b","^[a-\\Ec]",[trim]))), + 2}]))), + <<"E">> = iolist_to_binary(join(re:split("E","^[\\Eabc]",[]))), + <<"">> = iolist_to_binary(join(re:split("b","^[a-\\Ec]",[trim]))), <<":">> = iolist_to_binary(join(re:split("b","^[a-\\Ec]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("b","^[a-\\Ec]",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[a-\\Ec]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("b","^[a-\\Ec]",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[a-\\Ec]",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[a-\\Ec]",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[a-\\Ec]",[]))), - <<"-">> = iolist_to_binary(join(re:split("-","^[a-\\Ec]",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[a-\\Ec]",[]))), + <<"-">> = iolist_to_binary(join(re:split("-","^[a-\\Ec]",[trim]))), <<"-">> = iolist_to_binary(join(re:split("-","^[a-\\Ec]",[{parts, - 2}]))), - <<"-">> = iolist_to_binary(join(re:split("-","^[a-\\Ec]",[]))), - <<"E">> = iolist_to_binary(join(re:split("E","^[a-\\Ec]",[trim]))), + 2}]))), + <<"-">> = iolist_to_binary(join(re:split("-","^[a-\\Ec]",[]))), + <<"E">> = iolist_to_binary(join(re:split("E","^[a-\\Ec]",[trim]))), <<"E">> = iolist_to_binary(join(re:split("E","^[a-\\Ec]",[{parts, - 2}]))), - <<"E">> = iolist_to_binary(join(re:split("E","^[a-\\Ec]",[]))), - <<"">> = iolist_to_binary(join(re:split("b","^[a\\E\\E-\\Ec]",[trim]))), + 2}]))), + <<"E">> = iolist_to_binary(join(re:split("E","^[a-\\Ec]",[]))), + <<"">> = iolist_to_binary(join(re:split("b","^[a\\E\\E-\\Ec]",[trim]))), <<":">> = iolist_to_binary(join(re:split("b","^[a\\E\\E-\\Ec]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("b","^[a\\E\\E-\\Ec]",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[a\\E\\E-\\Ec]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("b","^[a\\E\\E-\\Ec]",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[a\\E\\E-\\Ec]",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[a\\E\\E-\\Ec]",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[a\\E\\E-\\Ec]",[]))), - <<"-">> = iolist_to_binary(join(re:split("-","^[a\\E\\E-\\Ec]",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[a\\E\\E-\\Ec]",[]))), + <<"-">> = iolist_to_binary(join(re:split("-","^[a\\E\\E-\\Ec]",[trim]))), <<"-">> = iolist_to_binary(join(re:split("-","^[a\\E\\E-\\Ec]",[{parts, - 2}]))), - <<"-">> = iolist_to_binary(join(re:split("-","^[a\\E\\E-\\Ec]",[]))), - <<"E">> = iolist_to_binary(join(re:split("E","^[a\\E\\E-\\Ec]",[trim]))), + 2}]))), + <<"-">> = iolist_to_binary(join(re:split("-","^[a\\E\\E-\\Ec]",[]))), + <<"E">> = iolist_to_binary(join(re:split("E","^[a\\E\\E-\\Ec]",[trim]))), <<"E">> = iolist_to_binary(join(re:split("E","^[a\\E\\E-\\Ec]",[{parts, - 2}]))), - <<"E">> = iolist_to_binary(join(re:split("E","^[a\\E\\E-\\Ec]",[]))), - <<"">> = iolist_to_binary(join(re:split("b","^[\\E\\Qa\\E-\\Qz\\E]+",[trim]))), + 2}]))), + <<"E">> = iolist_to_binary(join(re:split("E","^[a\\E\\E-\\Ec]",[]))), + <<"">> = iolist_to_binary(join(re:split("b","^[\\E\\Qa\\E-\\Qz\\E]+",[trim]))), <<":">> = iolist_to_binary(join(re:split("b","^[\\E\\Qa\\E-\\Qz\\E]+",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("b","^[\\E\\Qa\\E-\\Qz\\E]+",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\E\\Qa\\E-\\Qz\\E]+",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("b","^[\\E\\Qa\\E-\\Qz\\E]+",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\E\\Qa\\E-\\Qz\\E]+",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\E\\Qa\\E-\\Qz\\E]+",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\E\\Qa\\E-\\Qz\\E]+",[]))), - <<"-">> = iolist_to_binary(join(re:split("-","^[\\E\\Qa\\E-\\Qz\\E]+",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\E\\Qa\\E-\\Qz\\E]+",[]))), + <<"-">> = iolist_to_binary(join(re:split("-","^[\\E\\Qa\\E-\\Qz\\E]+",[trim]))), <<"-">> = iolist_to_binary(join(re:split("-","^[\\E\\Qa\\E-\\Qz\\E]+",[{parts, - 2}]))), - <<"-">> = iolist_to_binary(join(re:split("-","^[\\E\\Qa\\E-\\Qz\\E]+",[]))), - <<"">> = iolist_to_binary(join(re:split("a","^[a\\Q]bc\\E]",[trim]))), + 2}]))), + <<"-">> = iolist_to_binary(join(re:split("-","^[\\E\\Qa\\E-\\Qz\\E]+",[]))), + <<"">> = iolist_to_binary(join(re:split("a","^[a\\Q]bc\\E]",[trim]))), <<":">> = iolist_to_binary(join(re:split("a","^[a\\Q]bc\\E]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","^[a\\Q]bc\\E]",[]))), - <<"">> = iolist_to_binary(join(re:split("]","^[a\\Q]bc\\E]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","^[a\\Q]bc\\E]",[]))), + <<"">> = iolist_to_binary(join(re:split("]","^[a\\Q]bc\\E]",[trim]))), <<":">> = iolist_to_binary(join(re:split("]","^[a\\Q]bc\\E]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("]","^[a\\Q]bc\\E]",[]))), - <<"">> = iolist_to_binary(join(re:split("c","^[a\\Q]bc\\E]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("]","^[a\\Q]bc\\E]",[]))), + <<"">> = iolist_to_binary(join(re:split("c","^[a\\Q]bc\\E]",[trim]))), <<":">> = iolist_to_binary(join(re:split("c","^[a\\Q]bc\\E]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("c","^[a\\Q]bc\\E]",[]))), - <<"">> = iolist_to_binary(join(re:split("a","^[a-\\Q\\E]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("c","^[a\\Q]bc\\E]",[]))), + <<"">> = iolist_to_binary(join(re:split("a","^[a-\\Q\\E]",[trim]))), <<":">> = iolist_to_binary(join(re:split("a","^[a-\\Q\\E]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","^[a-\\Q\\E]",[]))), - <<"">> = iolist_to_binary(join(re:split("-","^[a-\\Q\\E]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","^[a-\\Q\\E]",[]))), + <<"">> = iolist_to_binary(join(re:split("-","^[a-\\Q\\E]",[trim]))), <<":">> = iolist_to_binary(join(re:split("-","^[a-\\Q\\E]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("-","^[a-\\Q\\E]",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaaa","^(a()*)*",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("-","^[a-\\Q\\E]",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaaa","^(a()*)*",[trim]))), <<":a::">> = iolist_to_binary(join(re:split("aaaa","^(a()*)*",[{parts, - 2}]))), - <<":a::">> = iolist_to_binary(join(re:split("aaaa","^(a()*)*",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaa","^(?:a(?:(?:))*)*",[trim]))), + 2}]))), + <<":a::">> = iolist_to_binary(join(re:split("aaaa","^(a()*)*",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaa","^(?:a(?:(?:))*)*",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaa","^(?:a(?:(?:))*)*",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaa","^(?:a(?:(?:))*)*",[]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaa","^(?:a(?:(?:))*)*",[]))), ok. run36() -> - <<":a">> = iolist_to_binary(join(re:split("aaaa","^(a()+)+",[trim]))), + <<":a">> = iolist_to_binary(join(re:split("aaaa","^(a()+)+",[trim]))), <<":a::">> = iolist_to_binary(join(re:split("aaaa","^(a()+)+",[{parts, - 2}]))), - <<":a::">> = iolist_to_binary(join(re:split("aaaa","^(a()+)+",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaa","^(?:a(?:(?:))+)+",[trim]))), + 2}]))), + <<":a::">> = iolist_to_binary(join(re:split("aaaa","^(a()+)+",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaa","^(?:a(?:(?:))+)+",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaa","^(?:a(?:(?:))+)+",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaa","^(?:a(?:(?:))+)+",[]))), - <<":a">> = iolist_to_binary(join(re:split("abbD","(a){0,3}(?(1)b|(c|))*D",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaa","^(?:a(?:(?:))+)+",[]))), + <<":a">> = iolist_to_binary(join(re:split("abbD","(a){0,3}(?(1)b|(c|))*D",[trim]))), <<":a::">> = iolist_to_binary(join(re:split("abbD","(a){0,3}(?(1)b|(c|))*D",[{parts, - 2}]))), - <<":a::">> = iolist_to_binary(join(re:split("abbD","(a){0,3}(?(1)b|(c|))*D",[]))), - <<"">> = iolist_to_binary(join(re:split("ccccD","(a){0,3}(?(1)b|(c|))*D",[trim]))), + 2}]))), + <<":a::">> = iolist_to_binary(join(re:split("abbD","(a){0,3}(?(1)b|(c|))*D",[]))), + <<"">> = iolist_to_binary(join(re:split("ccccD","(a){0,3}(?(1)b|(c|))*D",[trim]))), <<":::">> = iolist_to_binary(join(re:split("ccccD","(a){0,3}(?(1)b|(c|))*D",[{parts, - 2}]))), - <<":::">> = iolist_to_binary(join(re:split("ccccD","(a){0,3}(?(1)b|(c|))*D",[]))), - <<"">> = iolist_to_binary(join(re:split("D","(a){0,3}(?(1)b|(c|))*D",[trim]))), + 2}]))), + <<":::">> = iolist_to_binary(join(re:split("ccccD","(a){0,3}(?(1)b|(c|))*D",[]))), + <<"">> = iolist_to_binary(join(re:split("D","(a){0,3}(?(1)b|(c|))*D",[trim]))), <<":::">> = iolist_to_binary(join(re:split("D","(a){0,3}(?(1)b|(c|))*D",[{parts, - 2}]))), - <<":::">> = iolist_to_binary(join(re:split("D","(a){0,3}(?(1)b|(c|))*D",[]))), - <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a|)*\\d",[trim]))), + 2}]))), + <<":::">> = iolist_to_binary(join(re:split("D","(a){0,3}(?(1)b|(c|))*D",[]))), + <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a|)*\\d",[trim]))), <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a|)*\\d",[{parts, - 2}]))), - <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a|)*\\d",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(a|)*\\d",[trim]))), + 2}]))), + <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(a|)*\\d",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(a|)*\\d",[trim]))), <<"::">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(a|)*\\d",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(a|)*\\d",[]))), - <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?>a|)*\\d",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(a|)*\\d",[]))), + <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?>a|)*\\d",[trim]))), <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?>a|)*\\d",[{parts, - 2}]))), - <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?>a|)*\\d",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?>a|)*\\d",[trim]))), + 2}]))), + <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?>a|)*\\d",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?>a|)*\\d",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?>a|)*\\d",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?>a|)*\\d",[]))), - <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?:a|)*\\d",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?>a|)*\\d",[]))), + <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?:a|)*\\d",[trim]))), <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?:a|)*\\d",[{parts, - 2}]))), - <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?:a|)*\\d",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?:a|)*\\d",[trim]))), + 2}]))), + <<"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","(?:a|)*\\d",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?:a|)*\\d",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?:a|)*\\d",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?:a|)*\\d",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","\\Z",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4","(?:a|)*\\d",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","\\Z",[trim]))), <<"abc:">> = iolist_to_binary(join(re:split("abc","\\Z",[{parts, - 2}]))), - <<"abc:">> = iolist_to_binary(join(re:split("abc","\\Z",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","^(?s)(?>.*)(?<!\\n)",[trim]))), + 2}]))), + <<"abc:">> = iolist_to_binary(join(re:split("abc","\\Z",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","^(?s)(?>.*)(?<!\\n)",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","^(?s)(?>.*)(?<!\\n)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","^(?s)(?>.*)(?<!\\n)",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","^(?s)(?>.*)(?<!\\n)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","^(?s)(?>.*)(?<!\\n)",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","^(?s)(?>.*)(?<!\\n)",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","^(?s)(?>.*)(?<!\\n)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","^(?s)(?>.*)(?<!\\n)",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","^(?![^\\n]*\\n\\z)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","^(?s)(?>.*)(?<!\\n)",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","^(?![^\\n]*\\n\\z)",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","^(?![^\\n]*\\n\\z)",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","^(?![^\\n]*\\n\\z)",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","^(?![^\\n]*\\n\\z)",[trim]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","^(?![^\\n]*\\n\\z)",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","^(?![^\\n]*\\n\\z)",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","^(?![^\\n]*\\n\\z)",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","^(?![^\\n]*\\n\\z)",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","\\z(?<!\\n)",[trim]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","^(?![^\\n]*\\n\\z)",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","\\z(?<!\\n)",[trim]))), <<"abc:">> = iolist_to_binary(join(re:split("abc","\\z(?<!\\n)",[{parts, - 2}]))), - <<"abc:">> = iolist_to_binary(join(re:split("abc","\\z(?<!\\n)",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","\\z(?<!\\n)",[trim]))), + 2}]))), + <<"abc:">> = iolist_to_binary(join(re:split("abc","\\z(?<!\\n)",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","\\z(?<!\\n)",[trim]))), <<"abc:">> = iolist_to_binary(join(re:split("abc","\\z(?<!\\n)",[{parts, - 2}]))), - <<"abc:">> = iolist_to_binary(join(re:split("abc","\\z(?<!\\n)",[]))), - <<"">> = iolist_to_binary(join(re:split("abcd","(.*(.)?)*",[trim]))), + 2}]))), + <<"abc:">> = iolist_to_binary(join(re:split("abc","\\z(?<!\\n)",[]))), + <<"">> = iolist_to_binary(join(re:split("abcd","(.*(.)?)*",[trim]))), <<":::">> = iolist_to_binary(join(re:split("abcd","(.*(.)?)*",[{parts, - 2}]))), - <<":::">> = iolist_to_binary(join(re:split("abcd","(.*(.)?)*",[]))), + 2}]))), + <<":::">> = iolist_to_binary(join(re:split("abcd","(.*(.)?)*",[]))), <<"a:::b:::c:::d">> = iolist_to_binary(join(re:split("abcd","( (A | (?(1)0|) )* )",[extended, - trim]))), + trim]))), <<"a:::bcd">> = iolist_to_binary(join(re:split("abcd","( (A | (?(1)0|) )* )",[extended, {parts, - 2}]))), - <<"a:::b:::c:::d:::">> = iolist_to_binary(join(re:split("abcd","( (A | (?(1)0|) )* )",[extended]))), + 2}]))), + <<"a:::b:::c:::d:::">> = iolist_to_binary(join(re:split("abcd","( (A | (?(1)0|) )* )",[extended]))), <<"a:::b:::c:::d">> = iolist_to_binary(join(re:split("abcd","( ( (?(1)0|) )* )",[extended, - trim]))), + trim]))), <<"a:::bcd">> = iolist_to_binary(join(re:split("abcd","( ( (?(1)0|) )* )",[extended, {parts, - 2}]))), - <<"a:::b:::c:::d:::">> = iolist_to_binary(join(re:split("abcd","( ( (?(1)0|) )* )",[extended]))), + 2}]))), + <<"a:::b:::c:::d:::">> = iolist_to_binary(join(re:split("abcd","( ( (?(1)0|) )* )",[extended]))), <<"a::b::c::d">> = iolist_to_binary(join(re:split("abcd","( (?(1)0|)* )",[extended, - trim]))), + trim]))), <<"a::bcd">> = iolist_to_binary(join(re:split("abcd","( (?(1)0|)* )",[extended, {parts, - 2}]))), - <<"a::b::c::d::">> = iolist_to_binary(join(re:split("abcd","( (?(1)0|)* )",[extended]))), - <<"">> = iolist_to_binary(join(re:split("a]","[[:abcd:xyz]]",[trim]))), + 2}]))), + <<"a::b::c::d::">> = iolist_to_binary(join(re:split("abcd","( (?(1)0|)* )",[extended]))), + <<"">> = iolist_to_binary(join(re:split("a]","[[:abcd:xyz]]",[trim]))), <<":">> = iolist_to_binary(join(re:split("a]","[[:abcd:xyz]]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a]","[[:abcd:xyz]]",[]))), - <<"">> = iolist_to_binary(join(re:split(":]","[[:abcd:xyz]]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a]","[[:abcd:xyz]]",[]))), + <<"">> = iolist_to_binary(join(re:split(":]","[[:abcd:xyz]]",[trim]))), <<":">> = iolist_to_binary(join(re:split(":]","[[:abcd:xyz]]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split(":]","[[:abcd:xyz]]",[]))), - <<"">> = iolist_to_binary(join(re:split("a","[abc[:x\\]pqr]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split(":]","[[:abcd:xyz]]",[]))), + <<"">> = iolist_to_binary(join(re:split("a","[abc[:x\\]pqr]",[trim]))), <<":">> = iolist_to_binary(join(re:split("a","[abc[:x\\]pqr]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("a","[abc[:x\\]pqr]",[]))), - <<"">> = iolist_to_binary(join(re:split("[","[abc[:x\\]pqr]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("a","[abc[:x\\]pqr]",[]))), + <<"">> = iolist_to_binary(join(re:split("[","[abc[:x\\]pqr]",[trim]))), <<":">> = iolist_to_binary(join(re:split("[","[abc[:x\\]pqr]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("[","[abc[:x\\]pqr]",[]))), - <<"">> = iolist_to_binary(join(re:split(":","[abc[:x\\]pqr]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("[","[abc[:x\\]pqr]",[]))), + <<"">> = iolist_to_binary(join(re:split(":","[abc[:x\\]pqr]",[trim]))), <<":">> = iolist_to_binary(join(re:split(":","[abc[:x\\]pqr]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split(":","[abc[:x\\]pqr]",[]))), - <<"">> = iolist_to_binary(join(re:split("]","[abc[:x\\]pqr]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split(":","[abc[:x\\]pqr]",[]))), + <<"">> = iolist_to_binary(join(re:split("]","[abc[:x\\]pqr]",[trim]))), <<":">> = iolist_to_binary(join(re:split("]","[abc[:x\\]pqr]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("]","[abc[:x\\]pqr]",[]))), - <<"">> = iolist_to_binary(join(re:split("p","[abc[:x\\]pqr]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("]","[abc[:x\\]pqr]",[]))), + <<"">> = iolist_to_binary(join(re:split("p","[abc[:x\\]pqr]",[trim]))), <<":">> = iolist_to_binary(join(re:split("p","[abc[:x\\]pqr]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("p","[abc[:x\\]pqr]",[]))), - <<"fooabcfoo">> = iolist_to_binary(join(re:split("fooabcfoo",".*[op][xyz]",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("p","[abc[:x\\]pqr]",[]))), + <<"fooabcfoo">> = iolist_to_binary(join(re:split("fooabcfoo",".*[op][xyz]",[trim]))), <<"fooabcfoo">> = iolist_to_binary(join(re:split("fooabcfoo",".*[op][xyz]",[{parts, - 2}]))), - <<"fooabcfoo">> = iolist_to_binary(join(re:split("fooabcfoo",".*[op][xyz]",[]))), - <<"adc">> = iolist_to_binary(join(re:split("adc","(?(?=.*b)b|^)",[trim]))), + 2}]))), + <<"fooabcfoo">> = iolist_to_binary(join(re:split("fooabcfoo",".*[op][xyz]",[]))), + <<"adc">> = iolist_to_binary(join(re:split("adc","(?(?=.*b)b|^)",[trim]))), <<"adc">> = iolist_to_binary(join(re:split("adc","(?(?=.*b)b|^)",[{parts, - 2}]))), - <<"adc">> = iolist_to_binary(join(re:split("adc","(?(?=.*b)b|^)",[]))), - <<"a:c">> = iolist_to_binary(join(re:split("abc","(?(?=.*b)b|^)",[trim]))), + 2}]))), + <<"adc">> = iolist_to_binary(join(re:split("adc","(?(?=.*b)b|^)",[]))), + <<"a:c">> = iolist_to_binary(join(re:split("abc","(?(?=.*b)b|^)",[trim]))), <<"a:c">> = iolist_to_binary(join(re:split("abc","(?(?=.*b)b|^)",[{parts, - 2}]))), - <<"a:c">> = iolist_to_binary(join(re:split("abc","(?(?=.*b)b|^)",[]))), - <<"adc">> = iolist_to_binary(join(re:split("adc","(?(?=^.*b)b|^)",[trim]))), + 2}]))), + <<"a:c">> = iolist_to_binary(join(re:split("abc","(?(?=.*b)b|^)",[]))), + <<"adc">> = iolist_to_binary(join(re:split("adc","(?(?=^.*b)b|^)",[trim]))), <<"adc">> = iolist_to_binary(join(re:split("adc","(?(?=^.*b)b|^)",[{parts, - 2}]))), - <<"adc">> = iolist_to_binary(join(re:split("adc","(?(?=^.*b)b|^)",[]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(?(?=^.*b)b|^)",[trim]))), + 2}]))), + <<"adc">> = iolist_to_binary(join(re:split("adc","(?(?=^.*b)b|^)",[]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(?(?=^.*b)b|^)",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","(?(?=^.*b)b|^)",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(?(?=^.*b)b|^)",[]))), - <<"a:d:c">> = iolist_to_binary(join(re:split("adc","(?(?=.*b)b|^)*",[trim]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(?(?=^.*b)b|^)",[]))), + <<"a:d:c">> = iolist_to_binary(join(re:split("adc","(?(?=.*b)b|^)*",[trim]))), <<"a:dc">> = iolist_to_binary(join(re:split("adc","(?(?=.*b)b|^)*",[{parts, - 2}]))), - <<"a:d:c:">> = iolist_to_binary(join(re:split("adc","(?(?=.*b)b|^)*",[]))), - <<"a:c">> = iolist_to_binary(join(re:split("abc","(?(?=.*b)b|^)*",[trim]))), + 2}]))), + <<"a:d:c:">> = iolist_to_binary(join(re:split("adc","(?(?=.*b)b|^)*",[]))), + <<"a:c">> = iolist_to_binary(join(re:split("abc","(?(?=.*b)b|^)*",[trim]))), <<"a:c">> = iolist_to_binary(join(re:split("abc","(?(?=.*b)b|^)*",[{parts, - 2}]))), - <<"a:c:">> = iolist_to_binary(join(re:split("abc","(?(?=.*b)b|^)*",[]))), + 2}]))), + <<"a:c:">> = iolist_to_binary(join(re:split("abc","(?(?=.*b)b|^)*",[]))), ok. run37() -> - <<"adc">> = iolist_to_binary(join(re:split("adc","(?(?=.*b)b|^)+",[trim]))), + <<"adc">> = iolist_to_binary(join(re:split("adc","(?(?=.*b)b|^)+",[trim]))), <<"adc">> = iolist_to_binary(join(re:split("adc","(?(?=.*b)b|^)+",[{parts, - 2}]))), - <<"adc">> = iolist_to_binary(join(re:split("adc","(?(?=.*b)b|^)+",[]))), - <<"a:c">> = iolist_to_binary(join(re:split("abc","(?(?=.*b)b|^)+",[trim]))), + 2}]))), + <<"adc">> = iolist_to_binary(join(re:split("adc","(?(?=.*b)b|^)+",[]))), + <<"a:c">> = iolist_to_binary(join(re:split("abc","(?(?=.*b)b|^)+",[trim]))), <<"a:c">> = iolist_to_binary(join(re:split("abc","(?(?=.*b)b|^)+",[{parts, - 2}]))), - <<"a:c">> = iolist_to_binary(join(re:split("abc","(?(?=.*b)b|^)+",[]))), - <<"a:c">> = iolist_to_binary(join(re:split("abc","(?(?=b).*b|^d)",[trim]))), + 2}]))), + <<"a:c">> = iolist_to_binary(join(re:split("abc","(?(?=.*b)b|^)+",[]))), + <<"a:c">> = iolist_to_binary(join(re:split("abc","(?(?=b).*b|^d)",[trim]))), <<"a:c">> = iolist_to_binary(join(re:split("abc","(?(?=b).*b|^d)",[{parts, - 2}]))), - <<"a:c">> = iolist_to_binary(join(re:split("abc","(?(?=b).*b|^d)",[]))), - <<":c">> = iolist_to_binary(join(re:split("abc","(?(?=.*b).*b|^d)",[trim]))), + 2}]))), + <<"a:c">> = iolist_to_binary(join(re:split("abc","(?(?=b).*b|^d)",[]))), + <<":c">> = iolist_to_binary(join(re:split("abc","(?(?=.*b).*b|^d)",[trim]))), <<":c">> = iolist_to_binary(join(re:split("abc","(?(?=.*b).*b|^d)",[{parts, - 2}]))), - <<":c">> = iolist_to_binary(join(re:split("abc","(?(?=.*b).*b|^d)",[]))), - <<"">> = iolist_to_binary(join(re:split("%ab%","^%((?(?=[a])[^%])|b)*%$",[trim]))), + 2}]))), + <<":c">> = iolist_to_binary(join(re:split("abc","(?(?=.*b).*b|^d)",[]))), + <<"">> = iolist_to_binary(join(re:split("%ab%","^%((?(?=[a])[^%])|b)*%$",[trim]))), <<"::">> = iolist_to_binary(join(re:split("%ab%","^%((?(?=[a])[^%])|b)*%$",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("%ab%","^%((?(?=[a])[^%])|b)*%$",[]))), - <<"X:X">> = iolist_to_binary(join(re:split("XabX","(?i)a(?-i)b|c",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("%ab%","^%((?(?=[a])[^%])|b)*%$",[]))), + <<"X:X">> = iolist_to_binary(join(re:split("XabX","(?i)a(?-i)b|c",[trim]))), <<"X:X">> = iolist_to_binary(join(re:split("XabX","(?i)a(?-i)b|c",[{parts, - 2}]))), - <<"X:X">> = iolist_to_binary(join(re:split("XabX","(?i)a(?-i)b|c",[]))), - <<"X:X">> = iolist_to_binary(join(re:split("XAbX","(?i)a(?-i)b|c",[trim]))), + 2}]))), + <<"X:X">> = iolist_to_binary(join(re:split("XabX","(?i)a(?-i)b|c",[]))), + <<"X:X">> = iolist_to_binary(join(re:split("XAbX","(?i)a(?-i)b|c",[trim]))), <<"X:X">> = iolist_to_binary(join(re:split("XAbX","(?i)a(?-i)b|c",[{parts, - 2}]))), - <<"X:X">> = iolist_to_binary(join(re:split("XAbX","(?i)a(?-i)b|c",[]))), - <<"C:C">> = iolist_to_binary(join(re:split("CcC","(?i)a(?-i)b|c",[trim]))), + 2}]))), + <<"X:X">> = iolist_to_binary(join(re:split("XAbX","(?i)a(?-i)b|c",[]))), + <<"C:C">> = iolist_to_binary(join(re:split("CcC","(?i)a(?-i)b|c",[trim]))), <<"C:C">> = iolist_to_binary(join(re:split("CcC","(?i)a(?-i)b|c",[{parts, - 2}]))), - <<"C:C">> = iolist_to_binary(join(re:split("CcC","(?i)a(?-i)b|c",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?i)a(?-i)b|c",[trim]))), + 2}]))), + <<"C:C">> = iolist_to_binary(join(re:split("CcC","(?i)a(?-i)b|c",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?i)a(?-i)b|c",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?i)a(?-i)b|c",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?i)a(?-i)b|c",[]))), - <<"XABX">> = iolist_to_binary(join(re:split("XABX","(?i)a(?-i)b|c",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?i)a(?-i)b|c",[]))), + <<"XABX">> = iolist_to_binary(join(re:split("XABX","(?i)a(?-i)b|c",[trim]))), <<"XABX">> = iolist_to_binary(join(re:split("XABX","(?i)a(?-i)b|c",[{parts, - 2}]))), - <<"XABX">> = iolist_to_binary(join(re:split("XABX","(?i)a(?-i)b|c",[]))), + 2}]))), + <<"XABX">> = iolist_to_binary(join(re:split("XABX","(?i)a(?-i)b|c",[]))), <<"">> = iolist_to_binary(join(re:split(" -
","[\\x00-\\xff\\s]+",[trim]))), +
","[\\x00-\\xff\\s]+",[trim]))), <<":">> = iolist_to_binary(join(re:split(" -
","[\\x00-\\xff\\s]+",[{parts,2}]))), +
","[\\x00-\\xff\\s]+",[{parts,2}]))), <<":">> = iolist_to_binary(join(re:split(" -
","[\\x00-\\xff\\s]+",[]))), +
","[\\x00-\\xff\\s]+",[]))), <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1",[caseless, - trim]))), + trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1",[caseless, {parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1",[caseless]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1",[trim]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1",[caseless]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1",[trim]))), <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1",[{parts, - 2}]))), - <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1",[]))), + 2}]))), + <<"abc">> = iolist_to_binary(join(re:split("abc","(abc)\\1",[]))), <<":a">> = iolist_to_binary(join(re:split("12abc","[^a]*",[caseless, - trim]))), + trim]))), <<":abc">> = iolist_to_binary(join(re:split("12abc","[^a]*",[caseless, {parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("12abc","[^a]*",[caseless]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("12abc","[^a]*",[caseless]))), <<":A">> = iolist_to_binary(join(re:split("12ABC","[^a]*",[caseless, - trim]))), + trim]))), <<":ABC">> = iolist_to_binary(join(re:split("12ABC","[^a]*",[caseless, {parts, - 2}]))), - <<":A:">> = iolist_to_binary(join(re:split("12ABC","[^a]*",[caseless]))), + 2}]))), + <<":A:">> = iolist_to_binary(join(re:split("12ABC","[^a]*",[caseless]))), <<":a">> = iolist_to_binary(join(re:split("12abc","[^a]*+",[caseless, - trim]))), + trim]))), <<":abc">> = iolist_to_binary(join(re:split("12abc","[^a]*+",[caseless, {parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("12abc","[^a]*+",[caseless]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("12abc","[^a]*+",[caseless]))), <<":A">> = iolist_to_binary(join(re:split("12ABC","[^a]*+",[caseless, - trim]))), + trim]))), <<":ABC">> = iolist_to_binary(join(re:split("12ABC","[^a]*+",[caseless, {parts, - 2}]))), - <<":A:">> = iolist_to_binary(join(re:split("12ABC","[^a]*+",[caseless]))), + 2}]))), + <<":A:">> = iolist_to_binary(join(re:split("12ABC","[^a]*+",[caseless]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","[^a]*?X",[caseless, - trim]))), + trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","[^a]*?X",[caseless, {parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","[^a]*?X",[caseless]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","[^a]*?X",[caseless]))), <<"12abc">> = iolist_to_binary(join(re:split("12abc","[^a]*?X",[caseless, - trim]))), + trim]))), <<"12abc">> = iolist_to_binary(join(re:split("12abc","[^a]*?X",[caseless, {parts, - 2}]))), - <<"12abc">> = iolist_to_binary(join(re:split("12abc","[^a]*?X",[caseless]))), + 2}]))), + <<"12abc">> = iolist_to_binary(join(re:split("12abc","[^a]*?X",[caseless]))), <<"12ABC">> = iolist_to_binary(join(re:split("12ABC","[^a]*?X",[caseless, - trim]))), + trim]))), <<"12ABC">> = iolist_to_binary(join(re:split("12ABC","[^a]*?X",[caseless, {parts, - 2}]))), - <<"12ABC">> = iolist_to_binary(join(re:split("12ABC","[^a]*?X",[caseless]))), + 2}]))), + <<"12ABC">> = iolist_to_binary(join(re:split("12ABC","[^a]*?X",[caseless]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","[^a]+?X",[caseless, - trim]))), + trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","[^a]+?X",[caseless, {parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","[^a]+?X",[caseless]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","[^a]+?X",[caseless]))), <<"12abc">> = iolist_to_binary(join(re:split("12abc","[^a]+?X",[caseless, - trim]))), + trim]))), <<"12abc">> = iolist_to_binary(join(re:split("12abc","[^a]+?X",[caseless, {parts, - 2}]))), - <<"12abc">> = iolist_to_binary(join(re:split("12abc","[^a]+?X",[caseless]))), + 2}]))), + <<"12abc">> = iolist_to_binary(join(re:split("12abc","[^a]+?X",[caseless]))), <<"12ABC">> = iolist_to_binary(join(re:split("12ABC","[^a]+?X",[caseless, - trim]))), + trim]))), <<"12ABC">> = iolist_to_binary(join(re:split("12ABC","[^a]+?X",[caseless, {parts, - 2}]))), - <<"12ABC">> = iolist_to_binary(join(re:split("12ABC","[^a]+?X",[caseless]))), + 2}]))), + <<"12ABC">> = iolist_to_binary(join(re:split("12ABC","[^a]+?X",[caseless]))), <<"12a:b">> = iolist_to_binary(join(re:split("12aXbcX","[^a]?X",[caseless, - trim]))), + trim]))), <<"12a:bcX">> = iolist_to_binary(join(re:split("12aXbcX","[^a]?X",[caseless, {parts, - 2}]))), - <<"12a:b:">> = iolist_to_binary(join(re:split("12aXbcX","[^a]?X",[caseless]))), + 2}]))), + <<"12a:b:">> = iolist_to_binary(join(re:split("12aXbcX","[^a]?X",[caseless]))), <<"12A:B">> = iolist_to_binary(join(re:split("12AXBCX","[^a]?X",[caseless, - trim]))), + trim]))), <<"12A:BCX">> = iolist_to_binary(join(re:split("12AXBCX","[^a]?X",[caseless, {parts, - 2}]))), - <<"12A:B:">> = iolist_to_binary(join(re:split("12AXBCX","[^a]?X",[caseless]))), + 2}]))), + <<"12A:B:">> = iolist_to_binary(join(re:split("12AXBCX","[^a]?X",[caseless]))), <<"B">> = iolist_to_binary(join(re:split("BCX","[^a]?X",[caseless, - trim]))), + trim]))), <<"B:">> = iolist_to_binary(join(re:split("BCX","[^a]?X",[caseless, {parts, - 2}]))), - <<"B:">> = iolist_to_binary(join(re:split("BCX","[^a]?X",[caseless]))), + 2}]))), + <<"B:">> = iolist_to_binary(join(re:split("BCX","[^a]?X",[caseless]))), <<"12a:b">> = iolist_to_binary(join(re:split("12aXbcX","[^a]??X",[caseless, - trim]))), + trim]))), <<"12a:bcX">> = iolist_to_binary(join(re:split("12aXbcX","[^a]??X",[caseless, {parts, - 2}]))), - <<"12a:b:">> = iolist_to_binary(join(re:split("12aXbcX","[^a]??X",[caseless]))), + 2}]))), + <<"12a:b:">> = iolist_to_binary(join(re:split("12aXbcX","[^a]??X",[caseless]))), <<"12A:B">> = iolist_to_binary(join(re:split("12AXBCX","[^a]??X",[caseless, - trim]))), + trim]))), <<"12A:BCX">> = iolist_to_binary(join(re:split("12AXBCX","[^a]??X",[caseless, {parts, - 2}]))), - <<"12A:B:">> = iolist_to_binary(join(re:split("12AXBCX","[^a]??X",[caseless]))), + 2}]))), + <<"12A:B:">> = iolist_to_binary(join(re:split("12AXBCX","[^a]??X",[caseless]))), <<"B">> = iolist_to_binary(join(re:split("BCX","[^a]??X",[caseless, - trim]))), + trim]))), <<"B:">> = iolist_to_binary(join(re:split("BCX","[^a]??X",[caseless, {parts, - 2}]))), - <<"B:">> = iolist_to_binary(join(re:split("BCX","[^a]??X",[caseless]))), + 2}]))), + <<"B:">> = iolist_to_binary(join(re:split("BCX","[^a]??X",[caseless]))), <<"12aXb">> = iolist_to_binary(join(re:split("12aXbcX","[^a]?+X",[caseless, - trim]))), + trim]))), <<"12aXb:">> = iolist_to_binary(join(re:split("12aXbcX","[^a]?+X",[caseless, {parts, - 2}]))), - <<"12aXb:">> = iolist_to_binary(join(re:split("12aXbcX","[^a]?+X",[caseless]))), + 2}]))), + <<"12aXb:">> = iolist_to_binary(join(re:split("12aXbcX","[^a]?+X",[caseless]))), <<"12AXB">> = iolist_to_binary(join(re:split("12AXBCX","[^a]?+X",[caseless, - trim]))), + trim]))), <<"12AXB:">> = iolist_to_binary(join(re:split("12AXBCX","[^a]?+X",[caseless, {parts, - 2}]))), - <<"12AXB:">> = iolist_to_binary(join(re:split("12AXBCX","[^a]?+X",[caseless]))), + 2}]))), + <<"12AXB:">> = iolist_to_binary(join(re:split("12AXBCX","[^a]?+X",[caseless]))), <<"B">> = iolist_to_binary(join(re:split("BCX","[^a]?+X",[caseless, - trim]))), + trim]))), <<"B:">> = iolist_to_binary(join(re:split("BCX","[^a]?+X",[caseless, {parts, - 2}]))), - <<"B:">> = iolist_to_binary(join(re:split("BCX","[^a]?+X",[caseless]))), + 2}]))), + <<"B:">> = iolist_to_binary(join(re:split("BCX","[^a]?+X",[caseless]))), <<"a">> = iolist_to_binary(join(re:split("abcdef","[^a]{2,3}",[caseless, - trim]))), + trim]))), <<"a:ef">> = iolist_to_binary(join(re:split("abcdef","[^a]{2,3}",[caseless, {parts, - 2}]))), - <<"a::">> = iolist_to_binary(join(re:split("abcdef","[^a]{2,3}",[caseless]))), + 2}]))), + <<"a::">> = iolist_to_binary(join(re:split("abcdef","[^a]{2,3}",[caseless]))), <<"A">> = iolist_to_binary(join(re:split("ABCDEF","[^a]{2,3}",[caseless, - trim]))), + trim]))), <<"A:EF">> = iolist_to_binary(join(re:split("ABCDEF","[^a]{2,3}",[caseless, {parts, - 2}]))), - <<"A::">> = iolist_to_binary(join(re:split("ABCDEF","[^a]{2,3}",[caseless]))), + 2}]))), + <<"A::">> = iolist_to_binary(join(re:split("ABCDEF","[^a]{2,3}",[caseless]))), <<"a::f">> = iolist_to_binary(join(re:split("abcdef","[^a]{2,3}?",[caseless, - trim]))), + trim]))), <<"a:def">> = iolist_to_binary(join(re:split("abcdef","[^a]{2,3}?",[caseless, {parts, - 2}]))), - <<"a::f">> = iolist_to_binary(join(re:split("abcdef","[^a]{2,3}?",[caseless]))), + 2}]))), + <<"a::f">> = iolist_to_binary(join(re:split("abcdef","[^a]{2,3}?",[caseless]))), <<"A::F">> = iolist_to_binary(join(re:split("ABCDEF","[^a]{2,3}?",[caseless, - trim]))), + trim]))), <<"A:DEF">> = iolist_to_binary(join(re:split("ABCDEF","[^a]{2,3}?",[caseless, {parts, - 2}]))), - <<"A::F">> = iolist_to_binary(join(re:split("ABCDEF","[^a]{2,3}?",[caseless]))), + 2}]))), + <<"A::F">> = iolist_to_binary(join(re:split("ABCDEF","[^a]{2,3}?",[caseless]))), <<"a">> = iolist_to_binary(join(re:split("abcdef","[^a]{2,3}+",[caseless, - trim]))), + trim]))), <<"a:ef">> = iolist_to_binary(join(re:split("abcdef","[^a]{2,3}+",[caseless, {parts, - 2}]))), - <<"a::">> = iolist_to_binary(join(re:split("abcdef","[^a]{2,3}+",[caseless]))), + 2}]))), + <<"a::">> = iolist_to_binary(join(re:split("abcdef","[^a]{2,3}+",[caseless]))), <<"A">> = iolist_to_binary(join(re:split("ABCDEF","[^a]{2,3}+",[caseless, - trim]))), + trim]))), <<"A:EF">> = iolist_to_binary(join(re:split("ABCDEF","[^a]{2,3}+",[caseless, {parts, - 2}]))), - <<"A::">> = iolist_to_binary(join(re:split("ABCDEF","[^a]{2,3}+",[caseless]))), - <<"">> = iolist_to_binary(join(re:split("Z","((a|)+)+Z",[trim]))), + 2}]))), + <<"A::">> = iolist_to_binary(join(re:split("ABCDEF","[^a]{2,3}+",[caseless]))), + <<"">> = iolist_to_binary(join(re:split("Z","((a|)+)+Z",[trim]))), <<":::">> = iolist_to_binary(join(re:split("Z","((a|)+)+Z",[{parts, - 2}]))), - <<":::">> = iolist_to_binary(join(re:split("Z","((a|)+)+Z",[]))), + 2}]))), + <<":::">> = iolist_to_binary(join(re:split("Z","((a|)+)+Z",[]))), ok. run38() -> - <<"::a">> = iolist_to_binary(join(re:split("ac","(a)b|(a)c",[trim]))), + <<"::a">> = iolist_to_binary(join(re:split("ac","(a)b|(a)c",[trim]))), <<"::a:">> = iolist_to_binary(join(re:split("ac","(a)b|(a)c",[{parts, - 2}]))), - <<"::a:">> = iolist_to_binary(join(re:split("ac","(a)b|(a)c",[]))), - <<"::a">> = iolist_to_binary(join(re:split("ac","(?>(a))b|(a)c",[trim]))), + 2}]))), + <<"::a:">> = iolist_to_binary(join(re:split("ac","(a)b|(a)c",[]))), + <<"::a">> = iolist_to_binary(join(re:split("ac","(?>(a))b|(a)c",[trim]))), <<"::a:">> = iolist_to_binary(join(re:split("ac","(?>(a))b|(a)c",[{parts, - 2}]))), - <<"::a:">> = iolist_to_binary(join(re:split("ac","(?>(a))b|(a)c",[]))), - <<"::a">> = iolist_to_binary(join(re:split("ac","(?=(a))ab|(a)c",[trim]))), + 2}]))), + <<"::a:">> = iolist_to_binary(join(re:split("ac","(?>(a))b|(a)c",[]))), + <<"::a">> = iolist_to_binary(join(re:split("ac","(?=(a))ab|(a)c",[trim]))), <<"::a:">> = iolist_to_binary(join(re:split("ac","(?=(a))ab|(a)c",[{parts, - 2}]))), - <<"::a:">> = iolist_to_binary(join(re:split("ac","(?=(a))ab|(a)c",[]))), - <<":ac::a">> = iolist_to_binary(join(re:split("ac","((?>(a))b|(a)c)",[trim]))), + 2}]))), + <<"::a:">> = iolist_to_binary(join(re:split("ac","(?=(a))ab|(a)c",[]))), + <<":ac::a">> = iolist_to_binary(join(re:split("ac","((?>(a))b|(a)c)",[trim]))), <<":ac::a:">> = iolist_to_binary(join(re:split("ac","((?>(a))b|(a)c)",[{parts, - 2}]))), - <<":ac::a:">> = iolist_to_binary(join(re:split("ac","((?>(a))b|(a)c)",[]))), - <<":ac::a">> = iolist_to_binary(join(re:split("ac","((?>(a))b|(a)c)++",[trim]))), + 2}]))), + <<":ac::a:">> = iolist_to_binary(join(re:split("ac","((?>(a))b|(a)c)",[]))), + <<":ac::a">> = iolist_to_binary(join(re:split("ac","((?>(a))b|(a)c)++",[trim]))), <<":ac::a:">> = iolist_to_binary(join(re:split("ac","((?>(a))b|(a)c)++",[{parts, - 2}]))), - <<":ac::a:">> = iolist_to_binary(join(re:split("ac","((?>(a))b|(a)c)++",[]))), - <<"::a">> = iolist_to_binary(join(re:split("ac","(?:(?>(a))b|(a)c)++",[trim]))), + 2}]))), + <<":ac::a:">> = iolist_to_binary(join(re:split("ac","((?>(a))b|(a)c)++",[]))), + <<"::a">> = iolist_to_binary(join(re:split("ac","(?:(?>(a))b|(a)c)++",[trim]))), <<"::a:">> = iolist_to_binary(join(re:split("ac","(?:(?>(a))b|(a)c)++",[{parts, - 2}]))), - <<"::a:">> = iolist_to_binary(join(re:split("ac","(?:(?>(a))b|(a)c)++",[]))), - <<"::a:ac">> = iolist_to_binary(join(re:split("ac","(?=(?>(a))b|(a)c)(..)",[trim]))), + 2}]))), + <<"::a:">> = iolist_to_binary(join(re:split("ac","(?:(?>(a))b|(a)c)++",[]))), + <<"::a:ac">> = iolist_to_binary(join(re:split("ac","(?=(?>(a))b|(a)c)(..)",[trim]))), <<"::a:ac:">> = iolist_to_binary(join(re:split("ac","(?=(?>(a))b|(a)c)(..)",[{parts, - 2}]))), - <<"::a:ac:">> = iolist_to_binary(join(re:split("ac","(?=(?>(a))b|(a)c)(..)",[]))), - <<"::a">> = iolist_to_binary(join(re:split("ac","(?>(?>(a))b|(a)c)",[trim]))), + 2}]))), + <<"::a:ac:">> = iolist_to_binary(join(re:split("ac","(?=(?>(a))b|(a)c)(..)",[]))), + <<"::a">> = iolist_to_binary(join(re:split("ac","(?>(?>(a))b|(a)c)",[trim]))), <<"::a:">> = iolist_to_binary(join(re:split("ac","(?>(?>(a))b|(a)c)",[{parts, - 2}]))), - <<"::a:">> = iolist_to_binary(join(re:split("ac","(?>(?>(a))b|(a)c)",[]))), - <<":aaaabaaabaabab:aaa:aabab">> = iolist_to_binary(join(re:split("aaaabaaabaabab","((?>(a+)b)+(aabab))",[trim]))), + 2}]))), + <<"::a:">> = iolist_to_binary(join(re:split("ac","(?>(?>(a))b|(a)c)",[]))), + <<":aaaabaaabaabab:aaa:aabab">> = iolist_to_binary(join(re:split("aaaabaaabaabab","((?>(a+)b)+(aabab))",[trim]))), <<":aaaabaaabaabab:aaa:aabab:">> = iolist_to_binary(join(re:split("aaaabaaabaabab","((?>(a+)b)+(aabab))",[{parts, - 2}]))), - <<":aaaabaaabaabab:aaa:aabab:">> = iolist_to_binary(join(re:split("aaaabaaabaabab","((?>(a+)b)+(aabab))",[]))), - <<"aabc">> = iolist_to_binary(join(re:split("aabc","(?>a+|ab)+?c",[trim]))), + 2}]))), + <<":aaaabaaabaabab:aaa:aabab:">> = iolist_to_binary(join(re:split("aaaabaaabaabab","((?>(a+)b)+(aabab))",[]))), + <<"aabc">> = iolist_to_binary(join(re:split("aabc","(?>a+|ab)+?c",[trim]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","(?>a+|ab)+?c",[{parts, - 2}]))), - <<"aabc">> = iolist_to_binary(join(re:split("aabc","(?>a+|ab)+?c",[]))), - <<"aabc">> = iolist_to_binary(join(re:split("aabc","(?>a+|ab)+c",[trim]))), + 2}]))), + <<"aabc">> = iolist_to_binary(join(re:split("aabc","(?>a+|ab)+?c",[]))), + <<"aabc">> = iolist_to_binary(join(re:split("aabc","(?>a+|ab)+c",[trim]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","(?>a+|ab)+c",[{parts, - 2}]))), - <<"aabc">> = iolist_to_binary(join(re:split("aabc","(?>a+|ab)+c",[]))), - <<"">> = iolist_to_binary(join(re:split("aabc","(?:a+|ab)+c",[trim]))), + 2}]))), + <<"aabc">> = iolist_to_binary(join(re:split("aabc","(?>a+|ab)+c",[]))), + <<"">> = iolist_to_binary(join(re:split("aabc","(?:a+|ab)+c",[trim]))), <<":">> = iolist_to_binary(join(re:split("aabc","(?:a+|ab)+c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aabc","(?:a+|ab)+c",[]))), - <<":a">> = iolist_to_binary(join(re:split("a","(?(?=(a))a)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aabc","(?:a+|ab)+c",[]))), + <<":a">> = iolist_to_binary(join(re:split("a","(?(?=(a))a)",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("a","(?(?=(a))a)",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("a","(?(?=(a))a)",[]))), - <<":a:b">> = iolist_to_binary(join(re:split("ab","(?(?=(a))a)(b)",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("a","(?(?=(a))a)",[]))), + <<":a:b">> = iolist_to_binary(join(re:split("ab","(?(?=(a))a)(b)",[trim]))), <<":a:b:">> = iolist_to_binary(join(re:split("ab","(?(?=(a))a)(b)",[{parts, - 2}]))), - <<":a:b:">> = iolist_to_binary(join(re:split("ab","(?(?=(a))a)(b)",[]))), - <<"aaaabc">> = iolist_to_binary(join(re:split("aaaabc","^(?:a|ab)++c",[trim]))), + 2}]))), + <<":a:b:">> = iolist_to_binary(join(re:split("ab","(?(?=(a))a)(b)",[]))), + <<"aaaabc">> = iolist_to_binary(join(re:split("aaaabc","^(?:a|ab)++c",[trim]))), <<"aaaabc">> = iolist_to_binary(join(re:split("aaaabc","^(?:a|ab)++c",[{parts, - 2}]))), - <<"aaaabc">> = iolist_to_binary(join(re:split("aaaabc","^(?:a|ab)++c",[]))), - <<"aaaabc">> = iolist_to_binary(join(re:split("aaaabc","^(?>a|ab)++c",[trim]))), + 2}]))), + <<"aaaabc">> = iolist_to_binary(join(re:split("aaaabc","^(?:a|ab)++c",[]))), + <<"aaaabc">> = iolist_to_binary(join(re:split("aaaabc","^(?>a|ab)++c",[trim]))), <<"aaaabc">> = iolist_to_binary(join(re:split("aaaabc","^(?>a|ab)++c",[{parts, - 2}]))), - <<"aaaabc">> = iolist_to_binary(join(re:split("aaaabc","^(?>a|ab)++c",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaabc","^(?:a|ab)+c",[trim]))), + 2}]))), + <<"aaaabc">> = iolist_to_binary(join(re:split("aaaabc","^(?>a|ab)++c",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaabc","^(?:a|ab)+c",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaabc","^(?:a|ab)+c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaabc","^(?:a|ab)+c",[]))), - <<"">> = iolist_to_binary(join(re:split("xyz","(?=abc){0}xyz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaabc","^(?:a|ab)+c",[]))), + <<"">> = iolist_to_binary(join(re:split("xyz","(?=abc){0}xyz",[trim]))), <<":">> = iolist_to_binary(join(re:split("xyz","(?=abc){0}xyz",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("xyz","(?=abc){0}xyz",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?=abc){1}xyz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("xyz","(?=abc){0}xyz",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?=abc){1}xyz",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?=abc){1}xyz",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?=abc){1}xyz",[]))), - <<"xyz">> = iolist_to_binary(join(re:split("xyz","(?=abc){1}xyz",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?=abc){1}xyz",[]))), + <<"xyz">> = iolist_to_binary(join(re:split("xyz","(?=abc){1}xyz",[trim]))), <<"xyz">> = iolist_to_binary(join(re:split("xyz","(?=abc){1}xyz",[{parts, - 2}]))), - <<"xyz">> = iolist_to_binary(join(re:split("xyz","(?=abc){1}xyz",[]))), - <<":a">> = iolist_to_binary(join(re:split("ab","(?=(a))?.",[trim]))), + 2}]))), + <<"xyz">> = iolist_to_binary(join(re:split("xyz","(?=abc){1}xyz",[]))), + <<":a">> = iolist_to_binary(join(re:split("ab","(?=(a))?.",[trim]))), <<":a:b">> = iolist_to_binary(join(re:split("ab","(?=(a))?.",[{parts, - 2}]))), - <<":a:::">> = iolist_to_binary(join(re:split("ab","(?=(a))?.",[]))), - <<"">> = iolist_to_binary(join(re:split("bc","(?=(a))?.",[trim]))), + 2}]))), + <<":a:::">> = iolist_to_binary(join(re:split("ab","(?=(a))?.",[]))), + <<"">> = iolist_to_binary(join(re:split("bc","(?=(a))?.",[trim]))), <<"::c">> = iolist_to_binary(join(re:split("bc","(?=(a))?.",[{parts, - 2}]))), - <<"::::">> = iolist_to_binary(join(re:split("bc","(?=(a))?.",[]))), + 2}]))), + <<"::::">> = iolist_to_binary(join(re:split("bc","(?=(a))?.",[]))), ok. run39() -> - <<"">> = iolist_to_binary(join(re:split("ab","(?=(a))??.",[trim]))), + <<"">> = iolist_to_binary(join(re:split("ab","(?=(a))??.",[trim]))), <<"::b">> = iolist_to_binary(join(re:split("ab","(?=(a))??.",[{parts, - 2}]))), - <<"::::">> = iolist_to_binary(join(re:split("ab","(?=(a))??.",[]))), - <<"">> = iolist_to_binary(join(re:split("bc","(?=(a))??.",[trim]))), + 2}]))), + <<"::::">> = iolist_to_binary(join(re:split("ab","(?=(a))??.",[]))), + <<"">> = iolist_to_binary(join(re:split("bc","(?=(a))??.",[trim]))), <<"::c">> = iolist_to_binary(join(re:split("bc","(?=(a))??.",[{parts, - 2}]))), - <<"::::">> = iolist_to_binary(join(re:split("bc","(?=(a))??.",[]))), - <<":b">> = iolist_to_binary(join(re:split("abd","^(?=(?1))?[az]([abc])d",[trim]))), + 2}]))), + <<"::::">> = iolist_to_binary(join(re:split("bc","(?=(a))??.",[]))), + <<":b">> = iolist_to_binary(join(re:split("abd","^(?=(?1))?[az]([abc])d",[trim]))), <<":b:">> = iolist_to_binary(join(re:split("abd","^(?=(?1))?[az]([abc])d",[{parts, - 2}]))), - <<":b:">> = iolist_to_binary(join(re:split("abd","^(?=(?1))?[az]([abc])d",[]))), - <<":c:xx">> = iolist_to_binary(join(re:split("zcdxx","^(?=(?1))?[az]([abc])d",[trim]))), + 2}]))), + <<":b:">> = iolist_to_binary(join(re:split("abd","^(?=(?1))?[az]([abc])d",[]))), + <<":c:xx">> = iolist_to_binary(join(re:split("zcdxx","^(?=(?1))?[az]([abc])d",[trim]))), <<":c:xx">> = iolist_to_binary(join(re:split("zcdxx","^(?=(?1))?[az]([abc])d",[{parts, - 2}]))), - <<":c:xx">> = iolist_to_binary(join(re:split("zcdxx","^(?=(?1))?[az]([abc])d",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaaa","^(?!a){0}\\w+",[trim]))), + 2}]))), + <<":c:xx">> = iolist_to_binary(join(re:split("zcdxx","^(?=(?1))?[az]([abc])d",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaaa","^(?!a){0}\\w+",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaaa","^(?!a){0}\\w+",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaaa","^(?!a){0}\\w+",[]))), - <<"abc:abc">> = iolist_to_binary(join(re:split("abcxyz","(?<=(abc))?xyz",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaaa","^(?!a){0}\\w+",[]))), + <<"abc:abc">> = iolist_to_binary(join(re:split("abcxyz","(?<=(abc))?xyz",[trim]))), <<"abc:abc:">> = iolist_to_binary(join(re:split("abcxyz","(?<=(abc))?xyz",[{parts, - 2}]))), - <<"abc:abc:">> = iolist_to_binary(join(re:split("abcxyz","(?<=(abc))?xyz",[]))), - <<"pqr">> = iolist_to_binary(join(re:split("pqrxyz","(?<=(abc))?xyz",[trim]))), + 2}]))), + <<"abc:abc:">> = iolist_to_binary(join(re:split("abcxyz","(?<=(abc))?xyz",[]))), + <<"pqr">> = iolist_to_binary(join(re:split("pqrxyz","(?<=(abc))?xyz",[trim]))), <<"pqr::">> = iolist_to_binary(join(re:split("pqrxyz","(?<=(abc))?xyz",[{parts, - 2}]))), - <<"pqr::">> = iolist_to_binary(join(re:split("pqrxyz","(?<=(abc))?xyz",[]))), - <<"">> = iolist_to_binary(join(re:split("ggg<<<aaa>>>","^[\\g<a>]+",[trim]))), + 2}]))), + <<"pqr::">> = iolist_to_binary(join(re:split("pqrxyz","(?<=(abc))?xyz",[]))), + <<"">> = iolist_to_binary(join(re:split("ggg<<<aaa>>>","^[\\g<a>]+",[trim]))), <<":">> = iolist_to_binary(join(re:split("ggg<<<aaa>>>","^[\\g<a>]+",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ggg<<<aaa>>>","^[\\g<a>]+",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\g<a>]+",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ggg<<<aaa>>>","^[\\g<a>]+",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\g<a>]+",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\g<a>]+",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\g<a>]+",[]))), - <<"\\ga">> = iolist_to_binary(join(re:split("\\ga","^[\\g<a>]+",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^[\\g<a>]+",[]))), + <<"\\ga">> = iolist_to_binary(join(re:split("\\ga","^[\\g<a>]+",[trim]))), <<"\\ga">> = iolist_to_binary(join(re:split("\\ga","^[\\g<a>]+",[{parts, - 2}]))), - <<"\\ga">> = iolist_to_binary(join(re:split("\\ga","^[\\g<a>]+",[]))), - <<":xyz">> = iolist_to_binary(join(re:split("gggagagaxyz","^[\\ga]+",[trim]))), + 2}]))), + <<"\\ga">> = iolist_to_binary(join(re:split("\\ga","^[\\g<a>]+",[]))), + <<":xyz">> = iolist_to_binary(join(re:split("gggagagaxyz","^[\\ga]+",[trim]))), <<":xyz">> = iolist_to_binary(join(re:split("gggagagaxyz","^[\\ga]+",[{parts, - 2}]))), - <<":xyz">> = iolist_to_binary(join(re:split("gggagagaxyz","^[\\ga]+",[]))), - <<":Z">> = iolist_to_binary(join(re:split("aaaa444:::Z","^[:a[:digit:]]+",[trim]))), + 2}]))), + <<":xyz">> = iolist_to_binary(join(re:split("gggagagaxyz","^[\\ga]+",[]))), + <<":Z">> = iolist_to_binary(join(re:split("aaaa444:::Z","^[:a[:digit:]]+",[trim]))), <<":Z">> = iolist_to_binary(join(re:split("aaaa444:::Z","^[:a[:digit:]]+",[{parts, - 2}]))), - <<":Z">> = iolist_to_binary(join(re:split("aaaa444:::Z","^[:a[:digit:]]+",[]))), - <<":Z">> = iolist_to_binary(join(re:split("aaaa444:::bbbZ","^[:a[:digit:]:b]+",[trim]))), + 2}]))), + <<":Z">> = iolist_to_binary(join(re:split("aaaa444:::Z","^[:a[:digit:]]+",[]))), + <<":Z">> = iolist_to_binary(join(re:split("aaaa444:::bbbZ","^[:a[:digit:]:b]+",[trim]))), <<":Z">> = iolist_to_binary(join(re:split("aaaa444:::bbbZ","^[:a[:digit:]:b]+",[{parts, - 2}]))), - <<":Z">> = iolist_to_binary(join(re:split("aaaa444:::bbbZ","^[:a[:digit:]:b]+",[]))), - <<"">> = iolist_to_binary(join(re:split(":xxx:","[:a]xxx[b:]",[trim]))), + 2}]))), + <<":Z">> = iolist_to_binary(join(re:split("aaaa444:::bbbZ","^[:a[:digit:]:b]+",[]))), + <<"">> = iolist_to_binary(join(re:split(":xxx:","[:a]xxx[b:]",[trim]))), <<":">> = iolist_to_binary(join(re:split(":xxx:","[:a]xxx[b:]",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split(":xxx:","[:a]xxx[b:]",[]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split(":xxx:","[:a]xxx[b:]",[]))), <<"xaa:c">> = iolist_to_binary(join(re:split("xaabc","(?<=a{2})b",[caseless, - trim]))), + trim]))), <<"xaa:c">> = iolist_to_binary(join(re:split("xaabc","(?<=a{2})b",[caseless, {parts, - 2}]))), - <<"xaa:c">> = iolist_to_binary(join(re:split("xaabc","(?<=a{2})b",[caseless]))), + 2}]))), + <<"xaa:c">> = iolist_to_binary(join(re:split("xaabc","(?<=a{2})b",[caseless]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=a{2})b",[caseless, - trim]))), + trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=a{2})b",[caseless, {parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=a{2})b",[caseless]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=a{2})b",[caseless]))), <<"xabc">> = iolist_to_binary(join(re:split("xabc","(?<=a{2})b",[caseless, - trim]))), + trim]))), <<"xabc">> = iolist_to_binary(join(re:split("xabc","(?<=a{2})b",[caseless, {parts, - 2}]))), - <<"xabc">> = iolist_to_binary(join(re:split("xabc","(?<=a{2})b",[caseless]))), + 2}]))), + <<"xabc">> = iolist_to_binary(join(re:split("xabc","(?<=a{2})b",[caseless]))), <<"xa:c">> = iolist_to_binary(join(re:split("xabc","(?<!a{2})b",[caseless, - trim]))), + trim]))), <<"xa:c">> = iolist_to_binary(join(re:split("xabc","(?<!a{2})b",[caseless, {parts, - 2}]))), - <<"xa:c">> = iolist_to_binary(join(re:split("xabc","(?<!a{2})b",[caseless]))), + 2}]))), + <<"xa:c">> = iolist_to_binary(join(re:split("xabc","(?<!a{2})b",[caseless]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<!a{2})b",[caseless, - trim]))), + trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<!a{2})b",[caseless, {parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<!a{2})b",[caseless]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<!a{2})b",[caseless]))), <<"xaabc">> = iolist_to_binary(join(re:split("xaabc","(?<!a{2})b",[caseless, - trim]))), + trim]))), <<"xaabc">> = iolist_to_binary(join(re:split("xaabc","(?<!a{2})b",[caseless, {parts, - 2}]))), - <<"xaabc">> = iolist_to_binary(join(re:split("xaabc","(?<!a{2})b",[caseless]))), - <<"xa ">> = iolist_to_binary(join(re:split("xa c","(?<=a\\h)c",[trim]))), + 2}]))), + <<"xaabc">> = iolist_to_binary(join(re:split("xaabc","(?<!a{2})b",[caseless]))), + <<"xa ">> = iolist_to_binary(join(re:split("xa c","(?<=a\\h)c",[trim]))), <<"xa :">> = iolist_to_binary(join(re:split("xa c","(?<=a\\h)c",[{parts, - 2}]))), - <<"xa :">> = iolist_to_binary(join(re:split("xa c","(?<=a\\h)c",[]))), - <<"axx:c">> = iolist_to_binary(join(re:split("axxbc","(?<=[^a]{2})b",[trim]))), + 2}]))), + <<"xa :">> = iolist_to_binary(join(re:split("xa c","(?<=a\\h)c",[]))), + <<"axx:c">> = iolist_to_binary(join(re:split("axxbc","(?<=[^a]{2})b",[trim]))), <<"axx:c">> = iolist_to_binary(join(re:split("axxbc","(?<=[^a]{2})b",[{parts, - 2}]))), - <<"axx:c">> = iolist_to_binary(join(re:split("axxbc","(?<=[^a]{2})b",[]))), - <<"aAA:c">> = iolist_to_binary(join(re:split("aAAbc","(?<=[^a]{2})b",[trim]))), + 2}]))), + <<"axx:c">> = iolist_to_binary(join(re:split("axxbc","(?<=[^a]{2})b",[]))), + <<"aAA:c">> = iolist_to_binary(join(re:split("aAAbc","(?<=[^a]{2})b",[trim]))), <<"aAA:c">> = iolist_to_binary(join(re:split("aAAbc","(?<=[^a]{2})b",[{parts, - 2}]))), - <<"aAA:c">> = iolist_to_binary(join(re:split("aAAbc","(?<=[^a]{2})b",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=[^a]{2})b",[trim]))), + 2}]))), + <<"aAA:c">> = iolist_to_binary(join(re:split("aAAbc","(?<=[^a]{2})b",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=[^a]{2})b",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=[^a]{2})b",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=[^a]{2})b",[]))), - <<"xaabc">> = iolist_to_binary(join(re:split("xaabc","(?<=[^a]{2})b",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=[^a]{2})b",[]))), + <<"xaabc">> = iolist_to_binary(join(re:split("xaabc","(?<=[^a]{2})b",[trim]))), <<"xaabc">> = iolist_to_binary(join(re:split("xaabc","(?<=[^a]{2})b",[{parts, - 2}]))), - <<"xaabc">> = iolist_to_binary(join(re:split("xaabc","(?<=[^a]{2})b",[]))), + 2}]))), + <<"xaabc">> = iolist_to_binary(join(re:split("xaabc","(?<=[^a]{2})b",[]))), <<"axx:c">> = iolist_to_binary(join(re:split("axxbc","(?<=[^a]{2})b",[caseless, - trim]))), + trim]))), <<"axx:c">> = iolist_to_binary(join(re:split("axxbc","(?<=[^a]{2})b",[caseless, {parts, - 2}]))), - <<"axx:c">> = iolist_to_binary(join(re:split("axxbc","(?<=[^a]{2})b",[caseless]))), + 2}]))), + <<"axx:c">> = iolist_to_binary(join(re:split("axxbc","(?<=[^a]{2})b",[caseless]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=[^a]{2})b",[caseless, - trim]))), + trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=[^a]{2})b",[caseless, {parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=[^a]{2})b",[caseless]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=[^a]{2})b",[caseless]))), <<"aAAbc">> = iolist_to_binary(join(re:split("aAAbc","(?<=[^a]{2})b",[caseless, - trim]))), + trim]))), <<"aAAbc">> = iolist_to_binary(join(re:split("aAAbc","(?<=[^a]{2})b",[caseless, {parts, - 2}]))), - <<"aAAbc">> = iolist_to_binary(join(re:split("aAAbc","(?<=[^a]{2})b",[caseless]))), + 2}]))), + <<"aAAbc">> = iolist_to_binary(join(re:split("aAAbc","(?<=[^a]{2})b",[caseless]))), <<"xaabc">> = iolist_to_binary(join(re:split("xaabc","(?<=[^a]{2})b",[caseless, - trim]))), + trim]))), <<"xaabc">> = iolist_to_binary(join(re:split("xaabc","(?<=[^a]{2})b",[caseless, {parts, - 2}]))), - <<"xaabc">> = iolist_to_binary(join(re:split("xaabc","(?<=[^a]{2})b",[caseless]))), - <<"ab">> = iolist_to_binary(join(re:split("abc","(?<=a\\H)c",[trim]))), + 2}]))), + <<"xaabc">> = iolist_to_binary(join(re:split("xaabc","(?<=[^a]{2})b",[caseless]))), + <<"ab">> = iolist_to_binary(join(re:split("abc","(?<=a\\H)c",[trim]))), <<"ab:">> = iolist_to_binary(join(re:split("abc","(?<=a\\H)c",[{parts, - 2}]))), - <<"ab:">> = iolist_to_binary(join(re:split("abc","(?<=a\\H)c",[]))), - <<"ab">> = iolist_to_binary(join(re:split("abc","(?<=a\\V)c",[trim]))), + 2}]))), + <<"ab:">> = iolist_to_binary(join(re:split("abc","(?<=a\\H)c",[]))), + <<"ab">> = iolist_to_binary(join(re:split("abc","(?<=a\\V)c",[trim]))), <<"ab:">> = iolist_to_binary(join(re:split("abc","(?<=a\\V)c",[{parts, - 2}]))), - <<"ab:">> = iolist_to_binary(join(re:split("abc","(?<=a\\V)c",[]))), + 2}]))), + <<"ab:">> = iolist_to_binary(join(re:split("abc","(?<=a\\V)c",[]))), <<"a ">> = iolist_to_binary(join(re:split("a -c","(?<=a\\v)c",[trim]))), +c","(?<=a\\v)c",[trim]))), <<"a :">> = iolist_to_binary(join(re:split("a -c","(?<=a\\v)c",[{parts,2}]))), +c","(?<=a\\v)c",[{parts,2}]))), <<"a :">> = iolist_to_binary(join(re:split("a -c","(?<=a\\v)c",[]))), - <<"X:X">> = iolist_to_binary(join(re:split("XcccddYX","(?(?=c)c|d)++Y",[trim]))), +c","(?<=a\\v)c",[]))), + <<"X:X">> = iolist_to_binary(join(re:split("XcccddYX","(?(?=c)c|d)++Y",[trim]))), <<"X:X">> = iolist_to_binary(join(re:split("XcccddYX","(?(?=c)c|d)++Y",[{parts, - 2}]))), - <<"X:X">> = iolist_to_binary(join(re:split("XcccddYX","(?(?=c)c|d)++Y",[]))), - <<"X:X">> = iolist_to_binary(join(re:split("XcccddYX","(?(?=c)c|d)*+Y",[trim]))), + 2}]))), + <<"X:X">> = iolist_to_binary(join(re:split("XcccddYX","(?(?=c)c|d)++Y",[]))), + <<"X:X">> = iolist_to_binary(join(re:split("XcccddYX","(?(?=c)c|d)*+Y",[trim]))), <<"X:X">> = iolist_to_binary(join(re:split("XcccddYX","(?(?=c)c|d)*+Y",[{parts, - 2}]))), - <<"X:X">> = iolist_to_binary(join(re:split("XcccddYX","(?(?=c)c|d)*+Y",[]))), - <<":aaa">> = iolist_to_binary(join(re:split("aaaaaaa","^(a{2,3}){2,}+a",[trim]))), + 2}]))), + <<"X:X">> = iolist_to_binary(join(re:split("XcccddYX","(?(?=c)c|d)*+Y",[]))), + <<":aaa">> = iolist_to_binary(join(re:split("aaaaaaa","^(a{2,3}){2,}+a",[trim]))), <<":aaa:">> = iolist_to_binary(join(re:split("aaaaaaa","^(a{2,3}){2,}+a",[{parts, - 2}]))), - <<":aaa:">> = iolist_to_binary(join(re:split("aaaaaaa","^(a{2,3}){2,}+a",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a{2,3}){2,}+a",[trim]))), + 2}]))), + <<":aaa:">> = iolist_to_binary(join(re:split("aaaaaaa","^(a{2,3}){2,}+a",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a{2,3}){2,}+a",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a{2,3}){2,}+a",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a{2,3}){2,}+a",[]))), - <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(a{2,3}){2,}+a",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a{2,3}){2,}+a",[]))), + <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(a{2,3}){2,}+a",[trim]))), <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(a{2,3}){2,}+a",[{parts, - 2}]))), - <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(a{2,3}){2,}+a",[]))), - <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a{2,3}){2,}+a",[trim]))), + 2}]))), + <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(a{2,3}){2,}+a",[]))), + <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a{2,3}){2,}+a",[trim]))), <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a{2,3}){2,}+a",[{parts, - 2}]))), - <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a{2,3}){2,}+a",[]))), + 2}]))), + <<"aaaaaaaaa">> = iolist_to_binary(join(re:split("aaaaaaaaa","^(a{2,3}){2,}+a",[]))), ok. run40() -> - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a{2,3})++a",[trim]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a{2,3})++a",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a{2,3})++a",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a{2,3})++a",[]))), - <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(a{2,3})++a",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a{2,3})++a",[]))), + <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(a{2,3})++a",[trim]))), <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(a{2,3})++a",[{parts, - 2}]))), - <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(a{2,3})++a",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a{2,3})*+a",[trim]))), + 2}]))), + <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(a{2,3})++a",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a{2,3})*+a",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a{2,3})*+a",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a{2,3})*+a",[]))), - <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(a{2,3})*+a",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a{2,3})*+a",[]))), + <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(a{2,3})*+a",[trim]))), <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(a{2,3})*+a",[{parts, - 2}]))), - <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(a{2,3})*+a",[]))), - <<"">> = iolist_to_binary(join(re:split("abXde","ab\\Cde",[trim]))), + 2}]))), + <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(a{2,3})*+a",[]))), + <<"">> = iolist_to_binary(join(re:split("abXde","ab\\Cde",[trim]))), <<":">> = iolist_to_binary(join(re:split("abXde","ab\\Cde",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abXde","ab\\Cde",[]))), - <<"abZde">> = iolist_to_binary(join(re:split("abZdeX","(?<=ab\\Cde)X",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abXde","ab\\Cde",[]))), + <<"abZde">> = iolist_to_binary(join(re:split("abZdeX","(?<=ab\\Cde)X",[trim]))), <<"abZde:">> = iolist_to_binary(join(re:split("abZdeX","(?<=ab\\Cde)X",[{parts, - 2}]))), - <<"abZde:">> = iolist_to_binary(join(re:split("abZdeX","(?<=ab\\Cde)X",[]))), - <<"">> = iolist_to_binary(join(re:split("aCb","a[\\CD]b",[trim]))), + 2}]))), + <<"abZde:">> = iolist_to_binary(join(re:split("abZdeX","(?<=ab\\Cde)X",[]))), + <<"">> = iolist_to_binary(join(re:split("aCb","a[\\CD]b",[trim]))), <<":">> = iolist_to_binary(join(re:split("aCb","a[\\CD]b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aCb","a[\\CD]b",[]))), - <<"">> = iolist_to_binary(join(re:split("aDb","a[\\CD]b",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aCb","a[\\CD]b",[]))), + <<"">> = iolist_to_binary(join(re:split("aDb","a[\\CD]b",[trim]))), <<":">> = iolist_to_binary(join(re:split("aDb","a[\\CD]b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aDb","a[\\CD]b",[]))), - <<"">> = iolist_to_binary(join(re:split("aJb","a[\\C-X]b",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aDb","a[\\CD]b",[]))), + <<"">> = iolist_to_binary(join(re:split("aJb","a[\\C-X]b",[trim]))), <<":">> = iolist_to_binary(join(re:split("aJb","a[\\C-X]b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aJb","a[\\C-X]b",[]))), - <<"X X">> = iolist_to_binary(join(re:split("X X","\\H\\h\\V\\v",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aJb","a[\\C-X]b",[]))), + <<"X X">> = iolist_to_binary(join(re:split("X X","\\H\\h\\V\\v",[trim]))), <<"X X">> = iolist_to_binary(join(re:split("X X","\\H\\h\\V\\v",[{parts, - 2}]))), - <<"X X">> = iolist_to_binary(join(re:split("X X","\\H\\h\\V\\v",[]))), - <<"">> = iolist_to_binary(join(re:split("X X","\\H\\h\\V\\v",[trim]))), + 2}]))), + <<"X X">> = iolist_to_binary(join(re:split("X X","\\H\\h\\V\\v",[]))), + <<"">> = iolist_to_binary(join(re:split("X X","\\H\\h\\V\\v",[trim]))), <<":">> = iolist_to_binary(join(re:split("X X","\\H\\h\\V\\v",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("X X","\\H\\h\\V\\v",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\H\\h\\V\\v",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("X X","\\H\\h\\V\\v",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\H\\h\\V\\v",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\H\\h\\V\\v",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\H\\h\\V\\v",[]))), - <<" X">> = iolist_to_binary(join(re:split(" X","\\H\\h\\V\\v",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\H\\h\\V\\v",[]))), + <<" X">> = iolist_to_binary(join(re:split(" X","\\H\\h\\V\\v",[trim]))), <<" X">> = iolist_to_binary(join(re:split(" X","\\H\\h\\V\\v",[{parts, - 2}]))), - <<" X">> = iolist_to_binary(join(re:split(" X","\\H\\h\\V\\v",[]))), + 2}]))), + <<" X">> = iolist_to_binary(join(re:split(" X","\\H\\h\\V\\v",[]))), <<"">> = iolist_to_binary(join(re:split(" X -
","\\H*\\h+\\V?\\v{3,4}",[trim]))), +
","\\H*\\h+\\V?\\v{3,4}",[trim]))), <<":">> = iolist_to_binary(join(re:split(" X -
","\\H*\\h+\\V?\\v{3,4}",[{parts,2}]))), +
","\\H*\\h+\\V?\\v{3,4}",[{parts,2}]))), <<":">> = iolist_to_binary(join(re:split(" X -
","\\H*\\h+\\V?\\v{3,4}",[]))), +
","\\H*\\h+\\V?\\v{3,4}",[]))), <<"">> = iolist_to_binary(join(re:split(" -
","\\H*\\h+\\V?\\v{3,4}",[trim]))), +
","\\H*\\h+\\V?\\v{3,4}",[trim]))), <<":">> = iolist_to_binary(join(re:split(" -
","\\H*\\h+\\V?\\v{3,4}",[{parts,2}]))), +
","\\H*\\h+\\V?\\v{3,4}",[{parts,2}]))), <<":">> = iolist_to_binary(join(re:split(" -
","\\H*\\h+\\V?\\v{3,4}",[]))), +
","\\H*\\h+\\V?\\v{3,4}",[]))), <<"">> = iolist_to_binary(join(re:split(" -","\\H*\\h+\\V?\\v{3,4}",[trim]))), +","\\H*\\h+\\V?\\v{3,4}",[trim]))), <<":">> = iolist_to_binary(join(re:split(" -","\\H*\\h+\\V?\\v{3,4}",[{parts,2}]))), +","\\H*\\h+\\V?\\v{3,4}",[{parts,2}]))), <<":">> = iolist_to_binary(join(re:split(" -","\\H*\\h+\\V?\\v{3,4}",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\H*\\h+\\V?\\v{3,4}",[trim]))), +","\\H*\\h+\\V?\\v{3,4}",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\H*\\h+\\V?\\v{3,4}",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\H*\\h+\\V?\\v{3,4}",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\H*\\h+\\V?\\v{3,4}",[]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\H*\\h+\\V?\\v{3,4}",[]))), <<" ">> = iolist_to_binary(join(re:split(" -","\\H*\\h+\\V?\\v{3,4}",[trim]))), +","\\H*\\h+\\V?\\v{3,4}",[trim]))), <<" ">> = iolist_to_binary(join(re:split(" -","\\H*\\h+\\V?\\v{3,4}",[{parts,2}]))), +","\\H*\\h+\\V?\\v{3,4}",[{parts,2}]))), <<" ">> = iolist_to_binary(join(re:split(" -","\\H*\\h+\\V?\\v{3,4}",[]))), - <<"XY :E">> = iolist_to_binary(join(re:split("XY ABCDE","\\H{3,4}",[trim]))), +","\\H*\\h+\\V?\\v{3,4}",[]))), + <<"XY :E">> = iolist_to_binary(join(re:split("XY ABCDE","\\H{3,4}",[trim]))), <<"XY :E">> = iolist_to_binary(join(re:split("XY ABCDE","\\H{3,4}",[{parts, - 2}]))), - <<"XY :E">> = iolist_to_binary(join(re:split("XY ABCDE","\\H{3,4}",[]))), - <<"XY : ST">> = iolist_to_binary(join(re:split("XY PQR ST","\\H{3,4}",[trim]))), + 2}]))), + <<"XY :E">> = iolist_to_binary(join(re:split("XY ABCDE","\\H{3,4}",[]))), + <<"XY : ST">> = iolist_to_binary(join(re:split("XY PQR ST","\\H{3,4}",[trim]))), <<"XY : ST">> = iolist_to_binary(join(re:split("XY PQR ST","\\H{3,4}",[{parts, - 2}]))), - <<"XY : ST">> = iolist_to_binary(join(re:split("XY PQR ST","\\H{3,4}",[]))), - <<"XY A:QRS">> = iolist_to_binary(join(re:split("XY AB PQRS",".\\h{3,4}.",[trim]))), + 2}]))), + <<"XY : ST">> = iolist_to_binary(join(re:split("XY PQR ST","\\H{3,4}",[]))), + <<"XY A:QRS">> = iolist_to_binary(join(re:split("XY AB PQRS",".\\h{3,4}.",[trim]))), <<"XY A:QRS">> = iolist_to_binary(join(re:split("XY AB PQRS",".\\h{3,4}.",[{parts, - 2}]))), - <<"XY A:QRS">> = iolist_to_binary(join(re:split("XY AB PQRS",".\\h{3,4}.",[]))), - <<">">> = iolist_to_binary(join(re:split(">XNNNYZ","\\h*X\\h?\\H+Y\\H?Z",[trim]))), + 2}]))), + <<"XY A:QRS">> = iolist_to_binary(join(re:split("XY AB PQRS",".\\h{3,4}.",[]))), + <<">">> = iolist_to_binary(join(re:split(">XNNNYZ","\\h*X\\h?\\H+Y\\H?Z",[trim]))), <<">:">> = iolist_to_binary(join(re:split(">XNNNYZ","\\h*X\\h?\\H+Y\\H?Z",[{parts, - 2}]))), - <<">:">> = iolist_to_binary(join(re:split(">XNNNYZ","\\h*X\\h?\\H+Y\\H?Z",[]))), - <<">">> = iolist_to_binary(join(re:split("> X NYQZ","\\h*X\\h?\\H+Y\\H?Z",[trim]))), + 2}]))), + <<">:">> = iolist_to_binary(join(re:split(">XNNNYZ","\\h*X\\h?\\H+Y\\H?Z",[]))), + <<">">> = iolist_to_binary(join(re:split("> X NYQZ","\\h*X\\h?\\H+Y\\H?Z",[trim]))), <<">:">> = iolist_to_binary(join(re:split("> X NYQZ","\\h*X\\h?\\H+Y\\H?Z",[{parts, - 2}]))), - <<">:">> = iolist_to_binary(join(re:split("> X NYQZ","\\h*X\\h?\\H+Y\\H?Z",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\h*X\\h?\\H+Y\\H?Z",[trim]))), + 2}]))), + <<">:">> = iolist_to_binary(join(re:split("> X NYQZ","\\h*X\\h?\\H+Y\\H?Z",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\h*X\\h?\\H+Y\\H?Z",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\h*X\\h?\\H+Y\\H?Z",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\h*X\\h?\\H+Y\\H?Z",[]))), - <<">XYZ">> = iolist_to_binary(join(re:split(">XYZ","\\h*X\\h?\\H+Y\\H?Z",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\h*X\\h?\\H+Y\\H?Z",[]))), + <<">XYZ">> = iolist_to_binary(join(re:split(">XYZ","\\h*X\\h?\\H+Y\\H?Z",[trim]))), <<">XYZ">> = iolist_to_binary(join(re:split(">XYZ","\\h*X\\h?\\H+Y\\H?Z",[{parts, - 2}]))), - <<">XYZ">> = iolist_to_binary(join(re:split(">XYZ","\\h*X\\h?\\H+Y\\H?Z",[]))), - <<"> X NY Z">> = iolist_to_binary(join(re:split("> X NY Z","\\h*X\\h?\\H+Y\\H?Z",[trim]))), + 2}]))), + <<">XYZ">> = iolist_to_binary(join(re:split(">XYZ","\\h*X\\h?\\H+Y\\H?Z",[]))), + <<"> X NY Z">> = iolist_to_binary(join(re:split("> X NY Z","\\h*X\\h?\\H+Y\\H?Z",[trim]))), <<"> X NY Z">> = iolist_to_binary(join(re:split("> X NY Z","\\h*X\\h?\\H+Y\\H?Z",[{parts, - 2}]))), - <<"> X NY Z">> = iolist_to_binary(join(re:split("> X NY Z","\\h*X\\h?\\H+Y\\H?Z",[]))), + 2}]))), + <<"> X NY Z">> = iolist_to_binary(join(re:split("> X NY Z","\\h*X\\h?\\H+Y\\H?Z",[]))), <<">">> = iolist_to_binary(join(re:split(">XY Z -ANN","\\v*X\\v?Y\\v+Z\\V*\\x0a\\V+\\x0b\\V{2,3}\\x0c",[trim]))), +ANN","\\v*X\\v?Y\\v+Z\\V*\\x0a\\V+\\x0b\\V{2,3}\\x0c",[trim]))), <<">:">> = iolist_to_binary(join(re:split(">XY Z ANN","\\v*X\\v?Y\\v+Z\\V*\\x0a\\V+\\x0b\\V{2,3}\\x0c",[{parts, - 2}]))), + 2}]))), <<">:">> = iolist_to_binary(join(re:split(">XY Z -ANN","\\v*X\\v?Y\\v+Z\\V*\\x0a\\V+\\x0b\\V{2,3}\\x0c",[]))), +ANN","\\v*X\\v?Y\\v+Z\\V*\\x0a\\V+\\x0b\\V{2,3}\\x0c",[]))), <<">">> = iolist_to_binary(join(re:split(">
X Y ZZZ -AAANNN","\\v*X\\v?Y\\v+Z\\V*\\x0a\\V+\\x0b\\V{2,3}\\x0c",[trim]))), +AAANNN","\\v*X\\v?Y\\v+Z\\V*\\x0a\\V+\\x0b\\V{2,3}\\x0c",[trim]))), <<">:">> = iolist_to_binary(join(re:split(">
X Y ZZZ AAANNN","\\v*X\\v?Y\\v+Z\\V*\\x0a\\V+\\x0b\\V{2,3}\\x0c",[{parts, - 2}]))), + 2}]))), <<">:">> = iolist_to_binary(join(re:split(">
X Y ZZZ -AAANNN","\\v*X\\v?Y\\v+Z\\V*\\x0a\\V+\\x0b\\V{2,3}\\x0c",[]))), - <<"foo:foo">> = iolist_to_binary(join(re:split("foobar","(foo)\\Kbar",[trim]))), +AAANNN","\\v*X\\v?Y\\v+Z\\V*\\x0a\\V+\\x0b\\V{2,3}\\x0c",[]))), + <<"foo:foo">> = iolist_to_binary(join(re:split("foobar","(foo)\\Kbar",[trim]))), <<"foo:foo:">> = iolist_to_binary(join(re:split("foobar","(foo)\\Kbar",[{parts, - 2}]))), - <<"foo:foo:">> = iolist_to_binary(join(re:split("foobar","(foo)\\Kbar",[]))), - <<"foo:foo:bar">> = iolist_to_binary(join(re:split("foobar","(foo)(\\Kbar|baz)",[trim]))), + 2}]))), + <<"foo:foo:">> = iolist_to_binary(join(re:split("foobar","(foo)\\Kbar",[]))), + <<"foo:foo:bar">> = iolist_to_binary(join(re:split("foobar","(foo)(\\Kbar|baz)",[trim]))), <<"foo:foo:bar:">> = iolist_to_binary(join(re:split("foobar","(foo)(\\Kbar|baz)",[{parts, - 2}]))), - <<"foo:foo:bar:">> = iolist_to_binary(join(re:split("foobar","(foo)(\\Kbar|baz)",[]))), - <<":foo:baz">> = iolist_to_binary(join(re:split("foobaz","(foo)(\\Kbar|baz)",[trim]))), + 2}]))), + <<"foo:foo:bar:">> = iolist_to_binary(join(re:split("foobar","(foo)(\\Kbar|baz)",[]))), + <<":foo:baz">> = iolist_to_binary(join(re:split("foobaz","(foo)(\\Kbar|baz)",[trim]))), <<":foo:baz:">> = iolist_to_binary(join(re:split("foobaz","(foo)(\\Kbar|baz)",[{parts, - 2}]))), - <<":foo:baz:">> = iolist_to_binary(join(re:split("foobaz","(foo)(\\Kbar|baz)",[]))), - <<"foo:foobar">> = iolist_to_binary(join(re:split("foobarbaz","(foo\\Kbar)baz",[trim]))), + 2}]))), + <<":foo:baz:">> = iolist_to_binary(join(re:split("foobaz","(foo)(\\Kbar|baz)",[]))), + <<"foo:foobar">> = iolist_to_binary(join(re:split("foobarbaz","(foo\\Kbar)baz",[trim]))), <<"foo:foobar:">> = iolist_to_binary(join(re:split("foobarbaz","(foo\\Kbar)baz",[{parts, - 2}]))), - <<"foo:foobar:">> = iolist_to_binary(join(re:split("foobarbaz","(foo\\Kbar)baz",[]))), - <<":tom">> = iolist_to_binary(join(re:split("tom-tom","(?<A>tom|bon)-\\g{A}",[trim]))), + 2}]))), + <<"foo:foobar:">> = iolist_to_binary(join(re:split("foobarbaz","(foo\\Kbar)baz",[]))), + <<":tom">> = iolist_to_binary(join(re:split("tom-tom","(?<A>tom|bon)-\\g{A}",[trim]))), <<":tom:">> = iolist_to_binary(join(re:split("tom-tom","(?<A>tom|bon)-\\g{A}",[{parts, - 2}]))), - <<":tom:">> = iolist_to_binary(join(re:split("tom-tom","(?<A>tom|bon)-\\g{A}",[]))), - <<":bon">> = iolist_to_binary(join(re:split("bon-bon","(?<A>tom|bon)-\\g{A}",[trim]))), + 2}]))), + <<":tom:">> = iolist_to_binary(join(re:split("tom-tom","(?<A>tom|bon)-\\g{A}",[]))), + <<":bon">> = iolist_to_binary(join(re:split("bon-bon","(?<A>tom|bon)-\\g{A}",[trim]))), <<":bon:">> = iolist_to_binary(join(re:split("bon-bon","(?<A>tom|bon)-\\g{A}",[{parts, - 2}]))), - <<":bon:">> = iolist_to_binary(join(re:split("bon-bon","(?<A>tom|bon)-\\g{A}",[]))), - <<"bacxxx">> = iolist_to_binary(join(re:split("bacxxx","(^(a|b\\g{-1}))",[trim]))), + 2}]))), + <<":bon:">> = iolist_to_binary(join(re:split("bon-bon","(?<A>tom|bon)-\\g{A}",[]))), + <<"bacxxx">> = iolist_to_binary(join(re:split("bacxxx","(^(a|b\\g{-1}))",[trim]))), <<"bacxxx">> = iolist_to_binary(join(re:split("bacxxx","(^(a|b\\g{-1}))",[{parts, - 2}]))), - <<"bacxxx">> = iolist_to_binary(join(re:split("bacxxx","(^(a|b\\g{-1}))",[]))), - <<":abc">> = iolist_to_binary(join(re:split("abcabc","(?|(abc)|(xyz))\\1",[trim]))), + 2}]))), + <<"bacxxx">> = iolist_to_binary(join(re:split("bacxxx","(^(a|b\\g{-1}))",[]))), + <<":abc">> = iolist_to_binary(join(re:split("abcabc","(?|(abc)|(xyz))\\1",[trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abcabc","(?|(abc)|(xyz))\\1",[{parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abcabc","(?|(abc)|(xyz))\\1",[]))), - <<":xyz">> = iolist_to_binary(join(re:split("xyzxyz","(?|(abc)|(xyz))\\1",[trim]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abcabc","(?|(abc)|(xyz))\\1",[]))), + <<":xyz">> = iolist_to_binary(join(re:split("xyzxyz","(?|(abc)|(xyz))\\1",[trim]))), <<":xyz:">> = iolist_to_binary(join(re:split("xyzxyz","(?|(abc)|(xyz))\\1",[{parts, - 2}]))), - <<":xyz:">> = iolist_to_binary(join(re:split("xyzxyz","(?|(abc)|(xyz))\\1",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?|(abc)|(xyz))\\1",[trim]))), + 2}]))), + <<":xyz:">> = iolist_to_binary(join(re:split("xyzxyz","(?|(abc)|(xyz))\\1",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?|(abc)|(xyz))\\1",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?|(abc)|(xyz))\\1",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?|(abc)|(xyz))\\1",[]))), - <<"abcxyz">> = iolist_to_binary(join(re:split("abcxyz","(?|(abc)|(xyz))\\1",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?|(abc)|(xyz))\\1",[]))), + <<"abcxyz">> = iolist_to_binary(join(re:split("abcxyz","(?|(abc)|(xyz))\\1",[trim]))), <<"abcxyz">> = iolist_to_binary(join(re:split("abcxyz","(?|(abc)|(xyz))\\1",[{parts, - 2}]))), - <<"abcxyz">> = iolist_to_binary(join(re:split("abcxyz","(?|(abc)|(xyz))\\1",[]))), - <<"xyzabc">> = iolist_to_binary(join(re:split("xyzabc","(?|(abc)|(xyz))\\1",[trim]))), + 2}]))), + <<"abcxyz">> = iolist_to_binary(join(re:split("abcxyz","(?|(abc)|(xyz))\\1",[]))), + <<"xyzabc">> = iolist_to_binary(join(re:split("xyzabc","(?|(abc)|(xyz))\\1",[trim]))), <<"xyzabc">> = iolist_to_binary(join(re:split("xyzabc","(?|(abc)|(xyz))\\1",[{parts, - 2}]))), - <<"xyzabc">> = iolist_to_binary(join(re:split("xyzabc","(?|(abc)|(xyz))\\1",[]))), - <<":abc">> = iolist_to_binary(join(re:split("abcabc","(?|(abc)|(xyz))(?1)",[trim]))), + 2}]))), + <<"xyzabc">> = iolist_to_binary(join(re:split("xyzabc","(?|(abc)|(xyz))\\1",[]))), + <<":abc">> = iolist_to_binary(join(re:split("abcabc","(?|(abc)|(xyz))(?1)",[trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abcabc","(?|(abc)|(xyz))(?1)",[{parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abcabc","(?|(abc)|(xyz))(?1)",[]))), - <<":xyz">> = iolist_to_binary(join(re:split("xyzabc","(?|(abc)|(xyz))(?1)",[trim]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abcabc","(?|(abc)|(xyz))(?1)",[]))), + <<":xyz">> = iolist_to_binary(join(re:split("xyzabc","(?|(abc)|(xyz))(?1)",[trim]))), <<":xyz:">> = iolist_to_binary(join(re:split("xyzabc","(?|(abc)|(xyz))(?1)",[{parts, - 2}]))), - <<":xyz:">> = iolist_to_binary(join(re:split("xyzabc","(?|(abc)|(xyz))(?1)",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?|(abc)|(xyz))(?1)",[trim]))), + 2}]))), + <<":xyz:">> = iolist_to_binary(join(re:split("xyzabc","(?|(abc)|(xyz))(?1)",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?|(abc)|(xyz))(?1)",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?|(abc)|(xyz))(?1)",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?|(abc)|(xyz))(?1)",[]))), - <<"xyzxyz">> = iolist_to_binary(join(re:split("xyzxyz","(?|(abc)|(xyz))(?1)",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?|(abc)|(xyz))(?1)",[]))), + <<"xyzxyz">> = iolist_to_binary(join(re:split("xyzxyz","(?|(abc)|(xyz))(?1)",[trim]))), <<"xyzxyz">> = iolist_to_binary(join(re:split("xyzxyz","(?|(abc)|(xyz))(?1)",[{parts, - 2}]))), - <<"xyzxyz">> = iolist_to_binary(join(re:split("xyzxyz","(?|(abc)|(xyz))(?1)",[]))), + 2}]))), + <<"xyzxyz">> = iolist_to_binary(join(re:split("xyzxyz","(?|(abc)|(xyz))(?1)",[]))), ok. run41() -> - <<":a:b:c:d:Y">> = iolist_to_binary(join(re:split("XYabcdY","^X(?5)(a)(?|(b)|(q))(c)(d)(Y)",[trim]))), + <<":a:b:c:d:Y">> = iolist_to_binary(join(re:split("XYabcdY","^X(?5)(a)(?|(b)|(q))(c)(d)(Y)",[trim]))), <<":a:b:c:d:Y:">> = iolist_to_binary(join(re:split("XYabcdY","^X(?5)(a)(?|(b)|(q))(c)(d)(Y)",[{parts, - 2}]))), - <<":a:b:c:d:Y:">> = iolist_to_binary(join(re:split("XYabcdY","^X(?5)(a)(?|(b)|(q))(c)(d)(Y)",[]))), - <<":a:b:::c:d:Y">> = iolist_to_binary(join(re:split("XYabcdY","^X(?7)(a)(?|(b|(r)(s))|(q))(c)(d)(Y)",[trim]))), + 2}]))), + <<":a:b:c:d:Y:">> = iolist_to_binary(join(re:split("XYabcdY","^X(?5)(a)(?|(b)|(q))(c)(d)(Y)",[]))), + <<":a:b:::c:d:Y">> = iolist_to_binary(join(re:split("XYabcdY","^X(?7)(a)(?|(b|(r)(s))|(q))(c)(d)(Y)",[trim]))), <<":a:b:::c:d:Y:">> = iolist_to_binary(join(re:split("XYabcdY","^X(?7)(a)(?|(b|(r)(s))|(q))(c)(d)(Y)",[{parts, - 2}]))), - <<":a:b:::c:d:Y:">> = iolist_to_binary(join(re:split("XYabcdY","^X(?7)(a)(?|(b|(r)(s))|(q))(c)(d)(Y)",[]))), - <<":a:b:::c:d:Y">> = iolist_to_binary(join(re:split("XYabcdY","^X(?7)(a)(?|(b|(?|(r)|(t))(s))|(q))(c)(d)(Y)",[trim]))), + 2}]))), + <<":a:b:::c:d:Y:">> = iolist_to_binary(join(re:split("XYabcdY","^X(?7)(a)(?|(b|(r)(s))|(q))(c)(d)(Y)",[]))), + <<":a:b:::c:d:Y">> = iolist_to_binary(join(re:split("XYabcdY","^X(?7)(a)(?|(b|(?|(r)|(t))(s))|(q))(c)(d)(Y)",[trim]))), <<":a:b:::c:d:Y:">> = iolist_to_binary(join(re:split("XYabcdY","^X(?7)(a)(?|(b|(?|(r)|(t))(s))|(q))(c)(d)(Y)",[{parts, - 2}]))), - <<":a:b:::c:d:Y:">> = iolist_to_binary(join(re:split("XYabcdY","^X(?7)(a)(?|(b|(?|(r)|(t))(s))|(q))(c)(d)(Y)",[]))), - <<":a:xyz">> = iolist_to_binary(join(re:split("a:aaxyz","(?'abc'\\w+):\\k<abc>{2}",[trim]))), + 2}]))), + <<":a:b:::c:d:Y:">> = iolist_to_binary(join(re:split("XYabcdY","^X(?7)(a)(?|(b|(?|(r)|(t))(s))|(q))(c)(d)(Y)",[]))), + <<":a:xyz">> = iolist_to_binary(join(re:split("a:aaxyz","(?'abc'\\w+):\\k<abc>{2}",[trim]))), <<":a:xyz">> = iolist_to_binary(join(re:split("a:aaxyz","(?'abc'\\w+):\\k<abc>{2}",[{parts, - 2}]))), - <<":a:xyz">> = iolist_to_binary(join(re:split("a:aaxyz","(?'abc'\\w+):\\k<abc>{2}",[]))), - <<":ab:xyz">> = iolist_to_binary(join(re:split("ab:ababxyz","(?'abc'\\w+):\\k<abc>{2}",[trim]))), + 2}]))), + <<":a:xyz">> = iolist_to_binary(join(re:split("a:aaxyz","(?'abc'\\w+):\\k<abc>{2}",[]))), + <<":ab:xyz">> = iolist_to_binary(join(re:split("ab:ababxyz","(?'abc'\\w+):\\k<abc>{2}",[trim]))), <<":ab:xyz">> = iolist_to_binary(join(re:split("ab:ababxyz","(?'abc'\\w+):\\k<abc>{2}",[{parts, - 2}]))), - <<":ab:xyz">> = iolist_to_binary(join(re:split("ab:ababxyz","(?'abc'\\w+):\\k<abc>{2}",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?'abc'\\w+):\\k<abc>{2}",[trim]))), + 2}]))), + <<":ab:xyz">> = iolist_to_binary(join(re:split("ab:ababxyz","(?'abc'\\w+):\\k<abc>{2}",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?'abc'\\w+):\\k<abc>{2}",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?'abc'\\w+):\\k<abc>{2}",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?'abc'\\w+):\\k<abc>{2}",[]))), - <<"a:axyz">> = iolist_to_binary(join(re:split("a:axyz","(?'abc'\\w+):\\k<abc>{2}",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?'abc'\\w+):\\k<abc>{2}",[]))), + <<"a:axyz">> = iolist_to_binary(join(re:split("a:axyz","(?'abc'\\w+):\\k<abc>{2}",[trim]))), <<"a:axyz">> = iolist_to_binary(join(re:split("a:axyz","(?'abc'\\w+):\\k<abc>{2}",[{parts, - 2}]))), - <<"a:axyz">> = iolist_to_binary(join(re:split("a:axyz","(?'abc'\\w+):\\k<abc>{2}",[]))), - <<"ab:abxyz">> = iolist_to_binary(join(re:split("ab:abxyz","(?'abc'\\w+):\\k<abc>{2}",[trim]))), + 2}]))), + <<"a:axyz">> = iolist_to_binary(join(re:split("a:axyz","(?'abc'\\w+):\\k<abc>{2}",[]))), + <<"ab:abxyz">> = iolist_to_binary(join(re:split("ab:abxyz","(?'abc'\\w+):\\k<abc>{2}",[trim]))), <<"ab:abxyz">> = iolist_to_binary(join(re:split("ab:abxyz","(?'abc'\\w+):\\k<abc>{2}",[{parts, - 2}]))), - <<"ab:abxyz">> = iolist_to_binary(join(re:split("ab:abxyz","(?'abc'\\w+):\\k<abc>{2}",[]))), - <<":a:xyz">> = iolist_to_binary(join(re:split("a:aaxyz","(?'abc'\\w+):\\g{abc}{2}",[trim]))), + 2}]))), + <<"ab:abxyz">> = iolist_to_binary(join(re:split("ab:abxyz","(?'abc'\\w+):\\k<abc>{2}",[]))), + <<":a:xyz">> = iolist_to_binary(join(re:split("a:aaxyz","(?'abc'\\w+):\\g{abc}{2}",[trim]))), <<":a:xyz">> = iolist_to_binary(join(re:split("a:aaxyz","(?'abc'\\w+):\\g{abc}{2}",[{parts, - 2}]))), - <<":a:xyz">> = iolist_to_binary(join(re:split("a:aaxyz","(?'abc'\\w+):\\g{abc}{2}",[]))), - <<":ab:xyz">> = iolist_to_binary(join(re:split("ab:ababxyz","(?'abc'\\w+):\\g{abc}{2}",[trim]))), + 2}]))), + <<":a:xyz">> = iolist_to_binary(join(re:split("a:aaxyz","(?'abc'\\w+):\\g{abc}{2}",[]))), + <<":ab:xyz">> = iolist_to_binary(join(re:split("ab:ababxyz","(?'abc'\\w+):\\g{abc}{2}",[trim]))), <<":ab:xyz">> = iolist_to_binary(join(re:split("ab:ababxyz","(?'abc'\\w+):\\g{abc}{2}",[{parts, - 2}]))), - <<":ab:xyz">> = iolist_to_binary(join(re:split("ab:ababxyz","(?'abc'\\w+):\\g{abc}{2}",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?'abc'\\w+):\\g{abc}{2}",[trim]))), + 2}]))), + <<":ab:xyz">> = iolist_to_binary(join(re:split("ab:ababxyz","(?'abc'\\w+):\\g{abc}{2}",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?'abc'\\w+):\\g{abc}{2}",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?'abc'\\w+):\\g{abc}{2}",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?'abc'\\w+):\\g{abc}{2}",[]))), - <<"a:axyz">> = iolist_to_binary(join(re:split("a:axyz","(?'abc'\\w+):\\g{abc}{2}",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?'abc'\\w+):\\g{abc}{2}",[]))), + <<"a:axyz">> = iolist_to_binary(join(re:split("a:axyz","(?'abc'\\w+):\\g{abc}{2}",[trim]))), <<"a:axyz">> = iolist_to_binary(join(re:split("a:axyz","(?'abc'\\w+):\\g{abc}{2}",[{parts, - 2}]))), - <<"a:axyz">> = iolist_to_binary(join(re:split("a:axyz","(?'abc'\\w+):\\g{abc}{2}",[]))), - <<"ab:abxyz">> = iolist_to_binary(join(re:split("ab:abxyz","(?'abc'\\w+):\\g{abc}{2}",[trim]))), + 2}]))), + <<"a:axyz">> = iolist_to_binary(join(re:split("a:axyz","(?'abc'\\w+):\\g{abc}{2}",[]))), + <<"ab:abxyz">> = iolist_to_binary(join(re:split("ab:abxyz","(?'abc'\\w+):\\g{abc}{2}",[trim]))), <<"ab:abxyz">> = iolist_to_binary(join(re:split("ab:abxyz","(?'abc'\\w+):\\g{abc}{2}",[{parts, - 2}]))), - <<"ab:abxyz">> = iolist_to_binary(join(re:split("ab:abxyz","(?'abc'\\w+):\\g{abc}{2}",[]))), + 2}]))), + <<"ab:abxyz">> = iolist_to_binary(join(re:split("ab:abxyz","(?'abc'\\w+):\\g{abc}{2}",[]))), <<":a">> = iolist_to_binary(join(re:split("abd","^(?<ab>a)? (?(<ab>)b|c) (?('ab')d|e)",[extended, - trim]))), + trim]))), <<":a:">> = iolist_to_binary(join(re:split("abd","^(?<ab>a)? (?(<ab>)b|c) (?('ab')d|e)",[extended, {parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("abd","^(?<ab>a)? (?(<ab>)b|c) (?('ab')d|e)",[extended]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("abd","^(?<ab>a)? (?(<ab>)b|c) (?('ab')d|e)",[extended]))), <<"">> = iolist_to_binary(join(re:split("ce","^(?<ab>a)? (?(<ab>)b|c) (?('ab')d|e)",[extended, - trim]))), + trim]))), <<"::">> = iolist_to_binary(join(re:split("ce","^(?<ab>a)? (?(<ab>)b|c) (?('ab')d|e)",[extended, {parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("ce","^(?<ab>a)? (?(<ab>)b|c) (?('ab')d|e)",[extended]))), - <<":aX">> = iolist_to_binary(join(re:split("aXaXZ","^(a.)\\g-1Z",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("ce","^(?<ab>a)? (?(<ab>)b|c) (?('ab')d|e)",[extended]))), + <<":aX">> = iolist_to_binary(join(re:split("aXaXZ","^(a.)\\g-1Z",[trim]))), <<":aX:">> = iolist_to_binary(join(re:split("aXaXZ","^(a.)\\g-1Z",[{parts, - 2}]))), - <<":aX:">> = iolist_to_binary(join(re:split("aXaXZ","^(a.)\\g-1Z",[]))), - <<":aX">> = iolist_to_binary(join(re:split("aXaXZ","^(a.)\\g{-1}Z",[trim]))), + 2}]))), + <<":aX:">> = iolist_to_binary(join(re:split("aXaXZ","^(a.)\\g-1Z",[]))), + <<":aX">> = iolist_to_binary(join(re:split("aXaXZ","^(a.)\\g{-1}Z",[trim]))), <<":aX:">> = iolist_to_binary(join(re:split("aXaXZ","^(a.)\\g{-1}Z",[{parts, - 2}]))), - <<":aX:">> = iolist_to_binary(join(re:split("aXaXZ","^(a.)\\g{-1}Z",[]))), + 2}]))), + <<":aX:">> = iolist_to_binary(join(re:split("aXaXZ","^(a.)\\g{-1}Z",[]))), <<":::cd">> = iolist_to_binary(join(re:split("abcd","^(?(DEFINE) (?<A> a) (?<B> b) ) (?&A) (?&B) ",[extended, - trim]))), + trim]))), <<":::cd">> = iolist_to_binary(join(re:split("abcd","^(?(DEFINE) (?<A> a) (?<B> b) ) (?&A) (?&B) ",[extended, {parts, - 2}]))), - <<":::cd">> = iolist_to_binary(join(re:split("abcd","^(?(DEFINE) (?<A> a) (?<B> b) ) (?&A) (?&B) ",[extended]))), + 2}]))), + <<":::cd">> = iolist_to_binary(join(re:split("abcd","^(?(DEFINE) (?<A> a) (?<B> b) ) (?&A) (?&B) ",[extended]))), <<":metcalfe:33">> = iolist_to_binary(join(re:split("metcalfe 33","(?<NAME>(?&NAME_PAT))\\s+(?<ADDR>(?&ADDRESS_PAT)) (?(DEFINE) (?<NAME_PAT>[a-z]+) (?<ADDRESS_PAT>\\d+) - )",[extended,trim]))), + )",[extended,trim]))), <<":metcalfe:33:::">> = iolist_to_binary(join(re:split("metcalfe 33","(?<NAME>(?&NAME_PAT))\\s+(?<ADDR>(?&ADDRESS_PAT)) (?(DEFINE) (?<NAME_PAT>[a-z]+) (?<ADDRESS_PAT>\\d+) - )",[extended,{parts,2}]))), + )",[extended,{parts,2}]))), <<":metcalfe:33:::">> = iolist_to_binary(join(re:split("metcalfe 33","(?<NAME>(?&NAME_PAT))\\s+(?<ADDR>(?&ADDRESS_PAT)) (?(DEFINE) (?<NAME_PAT>[a-z]+) (?<ADDRESS_PAT>\\d+) - )",[extended]))), - <<"::.4">> = iolist_to_binary(join(re:split("1.2.3.4","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[trim]))), + )",[extended]))), + <<"::.4">> = iolist_to_binary(join(re:split("1.2.3.4","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[trim]))), <<"::.4:">> = iolist_to_binary(join(re:split("1.2.3.4","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[{parts, - 2}]))), - <<"::.4:">> = iolist_to_binary(join(re:split("1.2.3.4","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[]))), - <<"::.206">> = iolist_to_binary(join(re:split("131.111.10.206","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[trim]))), + 2}]))), + <<"::.4:">> = iolist_to_binary(join(re:split("1.2.3.4","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[]))), + <<"::.206">> = iolist_to_binary(join(re:split("131.111.10.206","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[trim]))), <<"::.206:">> = iolist_to_binary(join(re:split("131.111.10.206","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[{parts, - 2}]))), - <<"::.206:">> = iolist_to_binary(join(re:split("131.111.10.206","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[]))), - <<"::.0">> = iolist_to_binary(join(re:split("10.0.0.0","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[trim]))), + 2}]))), + <<"::.206:">> = iolist_to_binary(join(re:split("131.111.10.206","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[]))), + <<"::.0">> = iolist_to_binary(join(re:split("10.0.0.0","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[trim]))), <<"::.0:">> = iolist_to_binary(join(re:split("10.0.0.0","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[{parts, - 2}]))), - <<"::.0:">> = iolist_to_binary(join(re:split("10.0.0.0","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[trim]))), + 2}]))), + <<"::.0:">> = iolist_to_binary(join(re:split("10.0.0.0","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[]))), - <<"10.6">> = iolist_to_binary(join(re:split("10.6","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[]))), + <<"10.6">> = iolist_to_binary(join(re:split("10.6","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[trim]))), <<"10.6">> = iolist_to_binary(join(re:split("10.6","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[{parts, - 2}]))), - <<"10.6">> = iolist_to_binary(join(re:split("10.6","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[]))), - <<"455.3.4.5">> = iolist_to_binary(join(re:split("455.3.4.5","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[trim]))), + 2}]))), + <<"10.6">> = iolist_to_binary(join(re:split("10.6","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[]))), + <<"455.3.4.5">> = iolist_to_binary(join(re:split("455.3.4.5","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[trim]))), <<"455.3.4.5">> = iolist_to_binary(join(re:split("455.3.4.5","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[{parts, - 2}]))), - <<"455.3.4.5">> = iolist_to_binary(join(re:split("455.3.4.5","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[]))), - <<":.4">> = iolist_to_binary(join(re:split("1.2.3.4","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[trim]))), + 2}]))), + <<"455.3.4.5">> = iolist_to_binary(join(re:split("455.3.4.5","(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))\\b(?&byte)(\\.(?&byte)){3}",[]))), + <<":.4">> = iolist_to_binary(join(re:split("1.2.3.4","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[trim]))), <<":.4::">> = iolist_to_binary(join(re:split("1.2.3.4","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[{parts, - 2}]))), - <<":.4::">> = iolist_to_binary(join(re:split("1.2.3.4","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[]))), - <<":.206">> = iolist_to_binary(join(re:split("131.111.10.206","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[trim]))), + 2}]))), + <<":.4::">> = iolist_to_binary(join(re:split("1.2.3.4","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[]))), + <<":.206">> = iolist_to_binary(join(re:split("131.111.10.206","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[trim]))), <<":.206::">> = iolist_to_binary(join(re:split("131.111.10.206","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[{parts, - 2}]))), - <<":.206::">> = iolist_to_binary(join(re:split("131.111.10.206","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[]))), - <<":.0">> = iolist_to_binary(join(re:split("10.0.0.0","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[trim]))), + 2}]))), + <<":.206::">> = iolist_to_binary(join(re:split("131.111.10.206","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[]))), + <<":.0">> = iolist_to_binary(join(re:split("10.0.0.0","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[trim]))), <<":.0::">> = iolist_to_binary(join(re:split("10.0.0.0","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[{parts, - 2}]))), - <<":.0::">> = iolist_to_binary(join(re:split("10.0.0.0","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[trim]))), + 2}]))), + <<":.0::">> = iolist_to_binary(join(re:split("10.0.0.0","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[]))), - <<"10.6">> = iolist_to_binary(join(re:split("10.6","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[]))), + <<"10.6">> = iolist_to_binary(join(re:split("10.6","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[trim]))), <<"10.6">> = iolist_to_binary(join(re:split("10.6","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[{parts, - 2}]))), - <<"10.6">> = iolist_to_binary(join(re:split("10.6","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[]))), - <<"455.3.4.5">> = iolist_to_binary(join(re:split("455.3.4.5","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[trim]))), + 2}]))), + <<"10.6">> = iolist_to_binary(join(re:split("10.6","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[]))), + <<"455.3.4.5">> = iolist_to_binary(join(re:split("455.3.4.5","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[trim]))), <<"455.3.4.5">> = iolist_to_binary(join(re:split("455.3.4.5","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[{parts, - 2}]))), - <<"455.3.4.5">> = iolist_to_binary(join(re:split("455.3.4.5","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[]))), - <<":party">> = iolist_to_binary(join(re:split("now is the time for all good men to come to the aid of the party","^(\\w++|\\s++)*$",[trim]))), + 2}]))), + <<"455.3.4.5">> = iolist_to_binary(join(re:split("455.3.4.5","\\b(?&byte)(\\.(?&byte)){3}(?(DEFINE)(?<byte>2[0-4]\\d|25[0-5]|1\\d\\d|[1-9]?\\d))",[]))), + <<":party">> = iolist_to_binary(join(re:split("now is the time for all good men to come to the aid of the party","^(\\w++|\\s++)*$",[trim]))), <<":party:">> = iolist_to_binary(join(re:split("now is the time for all good men to come to the aid of the party","^(\\w++|\\s++)*$",[{parts, - 2}]))), - <<":party:">> = iolist_to_binary(join(re:split("now is the time for all good men to come to the aid of the party","^(\\w++|\\s++)*$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\w++|\\s++)*$",[trim]))), + 2}]))), + <<":party:">> = iolist_to_binary(join(re:split("now is the time for all good men to come to the aid of the party","^(\\w++|\\s++)*$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\w++|\\s++)*$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\w++|\\s++)*$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\w++|\\s++)*$",[]))), - <<"this is not a line with only words and spaces!">> = iolist_to_binary(join(re:split("this is not a line with only words and spaces!","^(\\w++|\\s++)*$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\w++|\\s++)*$",[]))), + <<"this is not a line with only words and spaces!">> = iolist_to_binary(join(re:split("this is not a line with only words and spaces!","^(\\w++|\\s++)*$",[trim]))), <<"this is not a line with only words and spaces!">> = iolist_to_binary(join(re:split("this is not a line with only words and spaces!","^(\\w++|\\s++)*$",[{parts, - 2}]))), - <<"this is not a line with only words and spaces!">> = iolist_to_binary(join(re:split("this is not a line with only words and spaces!","^(\\w++|\\s++)*$",[]))), - <<":12345:a">> = iolist_to_binary(join(re:split("12345a","(\\d++)(\\w)",[trim]))), + 2}]))), + <<"this is not a line with only words and spaces!">> = iolist_to_binary(join(re:split("this is not a line with only words and spaces!","^(\\w++|\\s++)*$",[]))), + <<":12345:a">> = iolist_to_binary(join(re:split("12345a","(\\d++)(\\w)",[trim]))), <<":12345:a:">> = iolist_to_binary(join(re:split("12345a","(\\d++)(\\w)",[{parts, - 2}]))), - <<":12345:a:">> = iolist_to_binary(join(re:split("12345a","(\\d++)(\\w)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(\\d++)(\\w)",[trim]))), + 2}]))), + <<":12345:a:">> = iolist_to_binary(join(re:split("12345a","(\\d++)(\\w)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(\\d++)(\\w)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(\\d++)(\\w)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(\\d++)(\\w)",[]))), - <<"12345+">> = iolist_to_binary(join(re:split("12345+","(\\d++)(\\w)",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","(\\d++)(\\w)",[]))), + <<"12345+">> = iolist_to_binary(join(re:split("12345+","(\\d++)(\\w)",[trim]))), <<"12345+">> = iolist_to_binary(join(re:split("12345+","(\\d++)(\\w)",[{parts, - 2}]))), - <<"12345+">> = iolist_to_binary(join(re:split("12345+","(\\d++)(\\w)",[]))), - <<"">> = iolist_to_binary(join(re:split("aaab","a++b",[trim]))), + 2}]))), + <<"12345+">> = iolist_to_binary(join(re:split("12345+","(\\d++)(\\w)",[]))), + <<"">> = iolist_to_binary(join(re:split("aaab","a++b",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaab","a++b",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaab","a++b",[]))), - <<":aaab">> = iolist_to_binary(join(re:split("aaab","(a++b)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaab","a++b",[]))), + <<":aaab">> = iolist_to_binary(join(re:split("aaab","(a++b)",[trim]))), <<":aaab:">> = iolist_to_binary(join(re:split("aaab","(a++b)",[{parts, - 2}]))), - <<":aaab:">> = iolist_to_binary(join(re:split("aaab","(a++b)",[]))), - <<":aaa">> = iolist_to_binary(join(re:split("aaab","(a++)b",[trim]))), + 2}]))), + <<":aaab:">> = iolist_to_binary(join(re:split("aaab","(a++b)",[]))), + <<":aaa">> = iolist_to_binary(join(re:split("aaab","(a++)b",[trim]))), <<":aaa:">> = iolist_to_binary(join(re:split("aaab","(a++)b",[{parts, - 2}]))), - <<":aaa:">> = iolist_to_binary(join(re:split("aaab","(a++)b",[]))), - <<"((:x">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","([^()]++|\\([^()]*\\))+",[trim]))), + 2}]))), + <<":aaa:">> = iolist_to_binary(join(re:split("aaab","(a++)b",[]))), + <<"((:x">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","([^()]++|\\([^()]*\\))+",[trim]))), <<"((:x:">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","([^()]++|\\([^()]*\\))+",[{parts, - 2}]))), - <<"((:x:">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","([^()]++|\\([^()]*\\))+",[]))), - <<":abc">> = iolist_to_binary(join(re:split("(abc)","\\(([^()]++|\\([^()]+\\))+\\)",[trim]))), + 2}]))), + <<"((:x:">> = iolist_to_binary(join(re:split("((abc(ade)ufh()()x","([^()]++|\\([^()]*\\))+",[]))), + <<":abc">> = iolist_to_binary(join(re:split("(abc)","\\(([^()]++|\\([^()]+\\))+\\)",[trim]))), <<":abc:">> = iolist_to_binary(join(re:split("(abc)","\\(([^()]++|\\([^()]+\\))+\\)",[{parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("(abc)","\\(([^()]++|\\([^()]+\\))+\\)",[]))), - <<":xyz">> = iolist_to_binary(join(re:split("(abc(def)xyz)","\\(([^()]++|\\([^()]+\\))+\\)",[trim]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("(abc)","\\(([^()]++|\\([^()]+\\))+\\)",[]))), + <<":xyz">> = iolist_to_binary(join(re:split("(abc(def)xyz)","\\(([^()]++|\\([^()]+\\))+\\)",[trim]))), <<":xyz:">> = iolist_to_binary(join(re:split("(abc(def)xyz)","\\(([^()]++|\\([^()]+\\))+\\)",[{parts, - 2}]))), - <<":xyz:">> = iolist_to_binary(join(re:split("(abc(def)xyz)","\\(([^()]++|\\([^()]+\\))+\\)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\(([^()]++|\\([^()]+\\))+\\)",[trim]))), + 2}]))), + <<":xyz:">> = iolist_to_binary(join(re:split("(abc(def)xyz)","\\(([^()]++|\\([^()]+\\))+\\)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\(([^()]++|\\([^()]+\\))+\\)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\(([^()]++|\\([^()]+\\))+\\)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\(([^()]++|\\([^()]+\\))+\\)",[]))), - <<"((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","\\(([^()]++|\\([^()]+\\))+\\)",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","\\(([^()]++|\\([^()]+\\))+\\)",[]))), + <<"((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","\\(([^()]++|\\([^()]+\\))+\\)",[trim]))), <<"((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","\\(([^()]++|\\([^()]+\\))+\\)",[{parts, - 2}]))), - <<"((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","\\(([^()]++|\\([^()]+\\))+\\)",[]))), - <<":c">> = iolist_to_binary(join(re:split("abc","^([^()]|\\((?1)*\\))*$",[trim]))), + 2}]))), + <<"((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">> = iolist_to_binary(join(re:split("((()aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","\\(([^()]++|\\([^()]+\\))+\\)",[]))), + <<":c">> = iolist_to_binary(join(re:split("abc","^([^()]|\\((?1)*\\))*$",[trim]))), <<":c:">> = iolist_to_binary(join(re:split("abc","^([^()]|\\((?1)*\\))*$",[{parts, - 2}]))), - <<":c:">> = iolist_to_binary(join(re:split("abc","^([^()]|\\((?1)*\\))*$",[]))), - <<":c">> = iolist_to_binary(join(re:split("a(b)c","^([^()]|\\((?1)*\\))*$",[trim]))), + 2}]))), + <<":c:">> = iolist_to_binary(join(re:split("abc","^([^()]|\\((?1)*\\))*$",[]))), + <<":c">> = iolist_to_binary(join(re:split("a(b)c","^([^()]|\\((?1)*\\))*$",[trim]))), <<":c:">> = iolist_to_binary(join(re:split("a(b)c","^([^()]|\\((?1)*\\))*$",[{parts, - 2}]))), - <<":c:">> = iolist_to_binary(join(re:split("a(b)c","^([^()]|\\((?1)*\\))*$",[]))), - <<":d">> = iolist_to_binary(join(re:split("a(b(c))d","^([^()]|\\((?1)*\\))*$",[trim]))), + 2}]))), + <<":c:">> = iolist_to_binary(join(re:split("a(b)c","^([^()]|\\((?1)*\\))*$",[]))), + <<":d">> = iolist_to_binary(join(re:split("a(b(c))d","^([^()]|\\((?1)*\\))*$",[trim]))), <<":d:">> = iolist_to_binary(join(re:split("a(b(c))d","^([^()]|\\((?1)*\\))*$",[{parts, - 2}]))), - <<":d:">> = iolist_to_binary(join(re:split("a(b(c))d","^([^()]|\\((?1)*\\))*$",[]))), - <<"*** Failers)">> = iolist_to_binary(join(re:split("*** Failers)","^([^()]|\\((?1)*\\))*$",[trim]))), + 2}]))), + <<":d:">> = iolist_to_binary(join(re:split("a(b(c))d","^([^()]|\\((?1)*\\))*$",[]))), + <<"*** Failers)">> = iolist_to_binary(join(re:split("*** Failers)","^([^()]|\\((?1)*\\))*$",[trim]))), <<"*** Failers)">> = iolist_to_binary(join(re:split("*** Failers)","^([^()]|\\((?1)*\\))*$",[{parts, - 2}]))), - <<"*** Failers)">> = iolist_to_binary(join(re:split("*** Failers)","^([^()]|\\((?1)*\\))*$",[]))), - <<"a(b(c)d">> = iolist_to_binary(join(re:split("a(b(c)d","^([^()]|\\((?1)*\\))*$",[trim]))), + 2}]))), + <<"*** Failers)">> = iolist_to_binary(join(re:split("*** Failers)","^([^()]|\\((?1)*\\))*$",[]))), + <<"a(b(c)d">> = iolist_to_binary(join(re:split("a(b(c)d","^([^()]|\\((?1)*\\))*$",[trim]))), <<"a(b(c)d">> = iolist_to_binary(join(re:split("a(b(c)d","^([^()]|\\((?1)*\\))*$",[{parts, - 2}]))), - <<"a(b(c)d">> = iolist_to_binary(join(re:split("a(b(c)d","^([^()]|\\((?1)*\\))*$",[]))), + 2}]))), + <<"a(b(c)d">> = iolist_to_binary(join(re:split("a(b(c)d","^([^()]|\\((?1)*\\))*$",[]))), ok. run42() -> - <<":3">> = iolist_to_binary(join(re:split(">abc>123<xyz<","^>abc>([^()]|\\((?1)*\\))*<xyz<$",[trim]))), + <<":3">> = iolist_to_binary(join(re:split(">abc>123<xyz<","^>abc>([^()]|\\((?1)*\\))*<xyz<$",[trim]))), <<":3:">> = iolist_to_binary(join(re:split(">abc>123<xyz<","^>abc>([^()]|\\((?1)*\\))*<xyz<$",[{parts, - 2}]))), - <<":3:">> = iolist_to_binary(join(re:split(">abc>123<xyz<","^>abc>([^()]|\\((?1)*\\))*<xyz<$",[]))), - <<":3">> = iolist_to_binary(join(re:split(">abc>1(2)3<xyz<","^>abc>([^()]|\\((?1)*\\))*<xyz<$",[trim]))), + 2}]))), + <<":3:">> = iolist_to_binary(join(re:split(">abc>123<xyz<","^>abc>([^()]|\\((?1)*\\))*<xyz<$",[]))), + <<":3">> = iolist_to_binary(join(re:split(">abc>1(2)3<xyz<","^>abc>([^()]|\\((?1)*\\))*<xyz<$",[trim]))), <<":3:">> = iolist_to_binary(join(re:split(">abc>1(2)3<xyz<","^>abc>([^()]|\\((?1)*\\))*<xyz<$",[{parts, - 2}]))), - <<":3:">> = iolist_to_binary(join(re:split(">abc>1(2)3<xyz<","^>abc>([^()]|\\((?1)*\\))*<xyz<$",[]))), - <<":(1(2)3)">> = iolist_to_binary(join(re:split(">abc>(1(2)3)<xyz<","^>abc>([^()]|\\((?1)*\\))*<xyz<$",[trim]))), + 2}]))), + <<":3:">> = iolist_to_binary(join(re:split(">abc>1(2)3<xyz<","^>abc>([^()]|\\((?1)*\\))*<xyz<$",[]))), + <<":(1(2)3)">> = iolist_to_binary(join(re:split(">abc>(1(2)3)<xyz<","^>abc>([^()]|\\((?1)*\\))*<xyz<$",[trim]))), <<":(1(2)3):">> = iolist_to_binary(join(re:split(">abc>(1(2)3)<xyz<","^>abc>([^()]|\\((?1)*\\))*<xyz<$",[{parts, - 2}]))), - <<":(1(2)3):">> = iolist_to_binary(join(re:split(">abc>(1(2)3)<xyz<","^>abc>([^()]|\\((?1)*\\))*<xyz<$",[]))), + 2}]))), + <<":(1(2)3):">> = iolist_to_binary(join(re:split(">abc>(1(2)3)<xyz<","^>abc>([^()]|\\((?1)*\\))*<xyz<$",[]))), <<":1221:1">> = iolist_to_binary(join(re:split("1221","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless, - trim]))), + trim]))), <<":1221:1:::">> = iolist_to_binary(join(re:split("1221","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless, {parts, - 2}]))), - <<":1221:1:::">> = iolist_to_binary(join(re:split("1221","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless]))), + 2}]))), + <<":1221:1:::">> = iolist_to_binary(join(re:split("1221","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless]))), <<":::Satanoscillatemymetallicsonatas:S">> = iolist_to_binary(join(re:split("Satanoscillatemymetallicsonatas","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless, - trim]))), + trim]))), <<":::Satanoscillatemymetallicsonatas:S:">> = iolist_to_binary(join(re:split("Satanoscillatemymetallicsonatas","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless, {parts, - 2}]))), - <<":::Satanoscillatemymetallicsonatas:S:">> = iolist_to_binary(join(re:split("Satanoscillatemymetallicsonatas","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless]))), + 2}]))), + <<":::Satanoscillatemymetallicsonatas:S:">> = iolist_to_binary(join(re:split("Satanoscillatemymetallicsonatas","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless]))), <<":::AmanaplanacanalPanama:A">> = iolist_to_binary(join(re:split("AmanaplanacanalPanama","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless, - trim]))), + trim]))), <<":::AmanaplanacanalPanama:A:">> = iolist_to_binary(join(re:split("AmanaplanacanalPanama","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless, {parts, - 2}]))), - <<":::AmanaplanacanalPanama:A:">> = iolist_to_binary(join(re:split("AmanaplanacanalPanama","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless]))), + 2}]))), + <<":::AmanaplanacanalPanama:A:">> = iolist_to_binary(join(re:split("AmanaplanacanalPanama","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless]))), <<":::AblewasIereIsawElba:A">> = iolist_to_binary(join(re:split("AblewasIereIsawElba","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless, - trim]))), + trim]))), <<":::AblewasIereIsawElba:A:">> = iolist_to_binary(join(re:split("AblewasIereIsawElba","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless, {parts, - 2}]))), - <<":::AblewasIereIsawElba:A:">> = iolist_to_binary(join(re:split("AblewasIereIsawElba","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless]))), + 2}]))), + <<":::AblewasIereIsawElba:A:">> = iolist_to_binary(join(re:split("AblewasIereIsawElba","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless]))), <<"Thequickbrownfox">> = iolist_to_binary(join(re:split("Thequickbrownfox","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless, - trim]))), + trim]))), <<"Thequickbrownfox">> = iolist_to_binary(join(re:split("Thequickbrownfox","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless, {parts, - 2}]))), - <<"Thequickbrownfox">> = iolist_to_binary(join(re:split("Thequickbrownfox","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless]))), - <<":12">> = iolist_to_binary(join(re:split("12","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[trim]))), + 2}]))), + <<"Thequickbrownfox">> = iolist_to_binary(join(re:split("Thequickbrownfox","^(?:((.)(?1)\\2|)|((.)(?3)\\4|.))$",[caseless]))), + <<":12">> = iolist_to_binary(join(re:split("12","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[trim]))), <<":12::">> = iolist_to_binary(join(re:split("12","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[{parts, - 2}]))), - <<":12::">> = iolist_to_binary(join(re:split("12","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[]))), - <<":(((2+2)*-3)-7):-">> = iolist_to_binary(join(re:split("(((2+2)*-3)-7)","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[trim]))), + 2}]))), + <<":12::">> = iolist_to_binary(join(re:split("12","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[]))), + <<":(((2+2)*-3)-7):-">> = iolist_to_binary(join(re:split("(((2+2)*-3)-7)","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[trim]))), <<":(((2+2)*-3)-7):-:">> = iolist_to_binary(join(re:split("(((2+2)*-3)-7)","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[{parts, - 2}]))), - <<":(((2+2)*-3)-7):-:">> = iolist_to_binary(join(re:split("(((2+2)*-3)-7)","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[]))), - <<":-12">> = iolist_to_binary(join(re:split("-12","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[trim]))), + 2}]))), + <<":(((2+2)*-3)-7):-:">> = iolist_to_binary(join(re:split("(((2+2)*-3)-7)","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[]))), + <<":-12">> = iolist_to_binary(join(re:split("-12","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[trim]))), <<":-12::">> = iolist_to_binary(join(re:split("-12","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[{parts, - 2}]))), - <<":-12::">> = iolist_to_binary(join(re:split("-12","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[trim]))), + 2}]))), + <<":-12::">> = iolist_to_binary(join(re:split("-12","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[]))), - <<"((2+2)*-3)-7)">> = iolist_to_binary(join(re:split("((2+2)*-3)-7)","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[]))), + <<"((2+2)*-3)-7)">> = iolist_to_binary(join(re:split("((2+2)*-3)-7)","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[trim]))), <<"((2+2)*-3)-7)">> = iolist_to_binary(join(re:split("((2+2)*-3)-7)","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[{parts, - 2}]))), - <<"((2+2)*-3)-7)">> = iolist_to_binary(join(re:split("((2+2)*-3)-7)","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[]))), - <<":xyz:y">> = iolist_to_binary(join(re:split("xyz","^(x(y|(?1){2})z)",[trim]))), + 2}]))), + <<"((2+2)*-3)-7)">> = iolist_to_binary(join(re:split("((2+2)*-3)-7)","^(\\d+|\\((?1)([+*-])(?1)\\)|-(?1))$",[]))), + <<":xyz:y">> = iolist_to_binary(join(re:split("xyz","^(x(y|(?1){2})z)",[trim]))), <<":xyz:y:">> = iolist_to_binary(join(re:split("xyz","^(x(y|(?1){2})z)",[{parts, - 2}]))), - <<":xyz:y:">> = iolist_to_binary(join(re:split("xyz","^(x(y|(?1){2})z)",[]))), - <<":xxyzxyzz:xyzxyz">> = iolist_to_binary(join(re:split("xxyzxyzz","^(x(y|(?1){2})z)",[trim]))), + 2}]))), + <<":xyz:y:">> = iolist_to_binary(join(re:split("xyz","^(x(y|(?1){2})z)",[]))), + <<":xxyzxyzz:xyzxyz">> = iolist_to_binary(join(re:split("xxyzxyzz","^(x(y|(?1){2})z)",[trim]))), <<":xxyzxyzz:xyzxyz:">> = iolist_to_binary(join(re:split("xxyzxyzz","^(x(y|(?1){2})z)",[{parts, - 2}]))), - <<":xxyzxyzz:xyzxyz:">> = iolist_to_binary(join(re:split("xxyzxyzz","^(x(y|(?1){2})z)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(x(y|(?1){2})z)",[trim]))), + 2}]))), + <<":xxyzxyzz:xyzxyz:">> = iolist_to_binary(join(re:split("xxyzxyzz","^(x(y|(?1){2})z)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(x(y|(?1){2})z)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(x(y|(?1){2})z)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(x(y|(?1){2})z)",[]))), - <<"xxyzz">> = iolist_to_binary(join(re:split("xxyzz","^(x(y|(?1){2})z)",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(x(y|(?1){2})z)",[]))), + <<"xxyzz">> = iolist_to_binary(join(re:split("xxyzz","^(x(y|(?1){2})z)",[trim]))), <<"xxyzz">> = iolist_to_binary(join(re:split("xxyzz","^(x(y|(?1){2})z)",[{parts, - 2}]))), - <<"xxyzz">> = iolist_to_binary(join(re:split("xxyzz","^(x(y|(?1){2})z)",[]))), - <<"xxyzxyzxyzz">> = iolist_to_binary(join(re:split("xxyzxyzxyzz","^(x(y|(?1){2})z)",[trim]))), + 2}]))), + <<"xxyzz">> = iolist_to_binary(join(re:split("xxyzz","^(x(y|(?1){2})z)",[]))), + <<"xxyzxyzxyzz">> = iolist_to_binary(join(re:split("xxyzxyzxyzz","^(x(y|(?1){2})z)",[trim]))), <<"xxyzxyzxyzz">> = iolist_to_binary(join(re:split("xxyzxyzxyzz","^(x(y|(?1){2})z)",[{parts, - 2}]))), - <<"xxyzxyzxyzz">> = iolist_to_binary(join(re:split("xxyzxyzxyzz","^(x(y|(?1){2})z)",[]))), + 2}]))), + <<"xxyzxyzxyzz">> = iolist_to_binary(join(re:split("xxyzxyzxyzz","^(x(y|(?1){2})z)",[]))), <<":<>:<>">> = iolist_to_binary(join(re:split("<>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended, - trim]))), + trim]))), <<":<>:<>:">> = iolist_to_binary(join(re:split("<>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended, {parts, - 2}]))), - <<":<>:<>:">> = iolist_to_binary(join(re:split("<>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended]))), + 2}]))), + <<":<>:<>:">> = iolist_to_binary(join(re:split("<>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended]))), <<":<abcd>:<abcd>">> = iolist_to_binary(join(re:split("<abcd>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended, - trim]))), + trim]))), <<":<abcd>:<abcd>:">> = iolist_to_binary(join(re:split("<abcd>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended, {parts, - 2}]))), - <<":<abcd>:<abcd>:">> = iolist_to_binary(join(re:split("<abcd>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended]))), + 2}]))), + <<":<abcd>:<abcd>:">> = iolist_to_binary(join(re:split("<abcd>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended]))), <<":<abc <123> hij>:<abc <123> hij>">> = iolist_to_binary(join(re:split("<abc <123> hij>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended, - trim]))), + trim]))), <<":<abc <123> hij>:<abc <123> hij>:">> = iolist_to_binary(join(re:split("<abc <123> hij>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended, {parts, - 2}]))), - <<":<abc <123> hij>:<abc <123> hij>:">> = iolist_to_binary(join(re:split("<abc <123> hij>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended]))), + 2}]))), + <<":<abc <123> hij>:<abc <123> hij>:">> = iolist_to_binary(join(re:split("<abc <123> hij>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended]))), <<"<abc :<def>:<def>: hij>">> = iolist_to_binary(join(re:split("<abc <def> hij>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended, - trim]))), + trim]))), <<"<abc :<def>:<def>: hij>">> = iolist_to_binary(join(re:split("<abc <def> hij>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended, {parts, - 2}]))), - <<"<abc :<def>:<def>: hij>">> = iolist_to_binary(join(re:split("<abc <def> hij>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended]))), + 2}]))), + <<"<abc :<def>:<def>: hij>">> = iolist_to_binary(join(re:split("<abc <def> hij>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended]))), <<":<abc<>def>:<abc<>def>">> = iolist_to_binary(join(re:split("<abc<>def>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended, - trim]))), + trim]))), <<":<abc<>def>:<abc<>def>:">> = iolist_to_binary(join(re:split("<abc<>def>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended, {parts, - 2}]))), - <<":<abc<>def>:<abc<>def>:">> = iolist_to_binary(join(re:split("<abc<>def>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended]))), + 2}]))), + <<":<abc<>def>:<abc<>def>:">> = iolist_to_binary(join(re:split("<abc<>def>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended]))), <<"<abc:<>:<>">> = iolist_to_binary(join(re:split("<abc<>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended, - trim]))), + trim]))), <<"<abc:<>:<>:">> = iolist_to_binary(join(re:split("<abc<>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended, {parts, - 2}]))), - <<"<abc:<>:<>:">> = iolist_to_binary(join(re:split("<abc<>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended]))), + 2}]))), + <<"<abc:<>:<>:">> = iolist_to_binary(join(re:split("<abc<>","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended]))), <<"<abc">> = iolist_to_binary(join(re:split("<abc","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended, - trim]))), + trim]))), <<"<abc">> = iolist_to_binary(join(re:split("<abc","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended, {parts, - 2}]))), - <<"<abc">> = iolist_to_binary(join(re:split("<abc","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended]))), - <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^a+(*FAIL)",[trim]))), + 2}]))), + <<"<abc">> = iolist_to_binary(join(re:split("<abc","((< (?: (?(R) \\d++ | [^<>]*+) | (?2)) * >))",[extended]))), + <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^a+(*FAIL)",[trim]))), <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^a+(*FAIL)",[{parts, - 2}]))), - <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^a+(*FAIL)",[]))), - <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?c+(*FAIL)",[trim]))), + 2}]))), + <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^a+(*FAIL)",[]))), + <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?c+(*FAIL)",[trim]))), <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?c+(*FAIL)",[{parts, - 2}]))), - <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?c+(*FAIL)",[]))), - <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?(*PRUNE)c+(*FAIL)",[trim]))), + 2}]))), + <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?c+(*FAIL)",[]))), + <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?(*PRUNE)c+(*FAIL)",[trim]))), <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?(*PRUNE)c+(*FAIL)",[{parts, - 2}]))), - <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?(*PRUNE)c+(*FAIL)",[]))), - <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?(*COMMIT)c+(*FAIL)",[trim]))), + 2}]))), + <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?(*PRUNE)c+(*FAIL)",[]))), + <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?(*COMMIT)c+(*FAIL)",[trim]))), <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?(*COMMIT)c+(*FAIL)",[{parts, - 2}]))), - <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?(*COMMIT)c+(*FAIL)",[]))), - <<"aaabcccaaabccc">> = iolist_to_binary(join(re:split("aaabcccaaabccc","a+b?(*SKIP)c+(*FAIL)",[trim]))), + 2}]))), + <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?(*COMMIT)c+(*FAIL)",[]))), + <<"aaabcccaaabccc">> = iolist_to_binary(join(re:split("aaabcccaaabccc","a+b?(*SKIP)c+(*FAIL)",[trim]))), <<"aaabcccaaabccc">> = iolist_to_binary(join(re:split("aaabcccaaabccc","a+b?(*SKIP)c+(*FAIL)",[{parts, - 2}]))), - <<"aaabcccaaabccc">> = iolist_to_binary(join(re:split("aaabcccaaabccc","a+b?(*SKIP)c+(*FAIL)",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaxxxxxx","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), + 2}]))), + <<"aaabcccaaabccc">> = iolist_to_binary(join(re:split("aaabcccaaabccc","a+b?(*SKIP)c+(*FAIL)",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaxxxxxx","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaxxxxxx","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaxxxxxx","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), - <<":++++++">> = iolist_to_binary(join(re:split("aaa++++++","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaxxxxxx","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), + <<":++++++">> = iolist_to_binary(join(re:split("aaa++++++","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), <<":++++++">> = iolist_to_binary(join(re:split("aaa++++++","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[{parts, - 2}]))), - <<":++++++">> = iolist_to_binary(join(re:split("aaa++++++","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), - <<"">> = iolist_to_binary(join(re:split("bbbxxxxx","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), + 2}]))), + <<":++++++">> = iolist_to_binary(join(re:split("aaa++++++","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), + <<"">> = iolist_to_binary(join(re:split("bbbxxxxx","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), <<":">> = iolist_to_binary(join(re:split("bbbxxxxx","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("bbbxxxxx","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), - <<":+++++">> = iolist_to_binary(join(re:split("bbb+++++","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("bbbxxxxx","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), + <<":+++++">> = iolist_to_binary(join(re:split("bbb+++++","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), <<":+++++">> = iolist_to_binary(join(re:split("bbb+++++","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[{parts, - 2}]))), - <<":+++++">> = iolist_to_binary(join(re:split("bbb+++++","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), - <<"">> = iolist_to_binary(join(re:split("cccxxxx","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), + 2}]))), + <<":+++++">> = iolist_to_binary(join(re:split("bbb+++++","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), + <<"">> = iolist_to_binary(join(re:split("cccxxxx","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), <<":">> = iolist_to_binary(join(re:split("cccxxxx","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("cccxxxx","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), - <<":++++">> = iolist_to_binary(join(re:split("ccc++++","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("cccxxxx","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), + <<":++++">> = iolist_to_binary(join(re:split("ccc++++","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), <<":++++">> = iolist_to_binary(join(re:split("ccc++++","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[{parts, - 2}]))), - <<":++++">> = iolist_to_binary(join(re:split("ccc++++","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), - <<":ddddd">> = iolist_to_binary(join(re:split("dddddddd","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), + 2}]))), + <<":++++">> = iolist_to_binary(join(re:split("ccc++++","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), + <<":ddddd">> = iolist_to_binary(join(re:split("dddddddd","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), <<":ddddd">> = iolist_to_binary(join(re:split("dddddddd","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[{parts, - 2}]))), - <<":ddddd">> = iolist_to_binary(join(re:split("dddddddd","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), - <<":aaaxxxxxx">> = iolist_to_binary(join(re:split("aaaxxxxxx","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), + 2}]))), + <<":ddddd">> = iolist_to_binary(join(re:split("dddddddd","^(?:aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), + <<":aaaxxxxxx">> = iolist_to_binary(join(re:split("aaaxxxxxx","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), <<":aaaxxxxxx:">> = iolist_to_binary(join(re:split("aaaxxxxxx","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[{parts, - 2}]))), - <<":aaaxxxxxx:">> = iolist_to_binary(join(re:split("aaaxxxxxx","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), - <<":aaa:++++++">> = iolist_to_binary(join(re:split("aaa++++++","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), + 2}]))), + <<":aaaxxxxxx:">> = iolist_to_binary(join(re:split("aaaxxxxxx","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), + <<":aaa:++++++">> = iolist_to_binary(join(re:split("aaa++++++","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), <<":aaa:++++++">> = iolist_to_binary(join(re:split("aaa++++++","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[{parts, - 2}]))), - <<":aaa:++++++">> = iolist_to_binary(join(re:split("aaa++++++","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), - <<":bbbxxxxx">> = iolist_to_binary(join(re:split("bbbxxxxx","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), + 2}]))), + <<":aaa:++++++">> = iolist_to_binary(join(re:split("aaa++++++","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), + <<":bbbxxxxx">> = iolist_to_binary(join(re:split("bbbxxxxx","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), <<":bbbxxxxx:">> = iolist_to_binary(join(re:split("bbbxxxxx","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[{parts, - 2}]))), - <<":bbbxxxxx:">> = iolist_to_binary(join(re:split("bbbxxxxx","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), - <<":bbb:+++++">> = iolist_to_binary(join(re:split("bbb+++++","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), + 2}]))), + <<":bbbxxxxx:">> = iolist_to_binary(join(re:split("bbbxxxxx","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), + <<":bbb:+++++">> = iolist_to_binary(join(re:split("bbb+++++","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), <<":bbb:+++++">> = iolist_to_binary(join(re:split("bbb+++++","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[{parts, - 2}]))), - <<":bbb:+++++">> = iolist_to_binary(join(re:split("bbb+++++","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), - <<":cccxxxx">> = iolist_to_binary(join(re:split("cccxxxx","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), + 2}]))), + <<":bbb:+++++">> = iolist_to_binary(join(re:split("bbb+++++","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), + <<":cccxxxx">> = iolist_to_binary(join(re:split("cccxxxx","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), <<":cccxxxx:">> = iolist_to_binary(join(re:split("cccxxxx","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[{parts, - 2}]))), - <<":cccxxxx:">> = iolist_to_binary(join(re:split("cccxxxx","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), - <<":ccc:++++">> = iolist_to_binary(join(re:split("ccc++++","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), + 2}]))), + <<":cccxxxx:">> = iolist_to_binary(join(re:split("cccxxxx","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), + <<":ccc:++++">> = iolist_to_binary(join(re:split("ccc++++","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), <<":ccc:++++">> = iolist_to_binary(join(re:split("ccc++++","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[{parts, - 2}]))), - <<":ccc:++++">> = iolist_to_binary(join(re:split("ccc++++","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), - <<":ddd:ddddd">> = iolist_to_binary(join(re:split("dddddddd","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), + 2}]))), + <<":ccc:++++">> = iolist_to_binary(join(re:split("ccc++++","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), + <<":ddd:ddddd">> = iolist_to_binary(join(re:split("dddddddd","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[trim]))), <<":ddd:ddddd">> = iolist_to_binary(join(re:split("dddddddd","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[{parts, - 2}]))), - <<":ddd:ddddd">> = iolist_to_binary(join(re:split("dddddddd","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), - <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?(*THEN)c+(*FAIL)",[trim]))), + 2}]))), + <<":ddd:ddddd">> = iolist_to_binary(join(re:split("dddddddd","^(aaa(*THEN)\\w{6}|bbb(*THEN)\\w{5}|ccc(*THEN)\\w{4}|\\w{3})",[]))), + <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?(*THEN)c+(*FAIL)",[trim]))), <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?(*THEN)c+(*FAIL)",[{parts, - 2}]))), - <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?(*THEN)c+(*FAIL)",[]))), + 2}]))), + <<"aaabccc">> = iolist_to_binary(join(re:split("aaabccc","a+b?(*THEN)c+(*FAIL)",[]))), <<":AB:B">> = iolist_to_binary(join(re:split("AB","(A (A|B(*ACCEPT)|C) D)(E)",[extended, - trim]))), + trim]))), <<":AB:B::">> = iolist_to_binary(join(re:split("AB","(A (A|B(*ACCEPT)|C) D)(E)",[extended, {parts, - 2}]))), - <<":AB:B::">> = iolist_to_binary(join(re:split("AB","(A (A|B(*ACCEPT)|C) D)(E)",[extended]))), + 2}]))), + <<":AB:B::">> = iolist_to_binary(join(re:split("AB","(A (A|B(*ACCEPT)|C) D)(E)",[extended]))), <<":AB:B::X">> = iolist_to_binary(join(re:split("ABX","(A (A|B(*ACCEPT)|C) D)(E)",[extended, - trim]))), + trim]))), <<":AB:B::X">> = iolist_to_binary(join(re:split("ABX","(A (A|B(*ACCEPT)|C) D)(E)",[extended, {parts, - 2}]))), - <<":AB:B::X">> = iolist_to_binary(join(re:split("ABX","(A (A|B(*ACCEPT)|C) D)(E)",[extended]))), + 2}]))), + <<":AB:B::X">> = iolist_to_binary(join(re:split("ABX","(A (A|B(*ACCEPT)|C) D)(E)",[extended]))), <<":AAD:A:E">> = iolist_to_binary(join(re:split("AADE","(A (A|B(*ACCEPT)|C) D)(E)",[extended, - trim]))), + trim]))), <<":AAD:A:E:">> = iolist_to_binary(join(re:split("AADE","(A (A|B(*ACCEPT)|C) D)(E)",[extended, {parts, - 2}]))), - <<":AAD:A:E:">> = iolist_to_binary(join(re:split("AADE","(A (A|B(*ACCEPT)|C) D)(E)",[extended]))), + 2}]))), + <<":AAD:A:E:">> = iolist_to_binary(join(re:split("AADE","(A (A|B(*ACCEPT)|C) D)(E)",[extended]))), <<":ACD:C:E">> = iolist_to_binary(join(re:split("ACDE","(A (A|B(*ACCEPT)|C) D)(E)",[extended, - trim]))), + trim]))), <<":ACD:C:E:">> = iolist_to_binary(join(re:split("ACDE","(A (A|B(*ACCEPT)|C) D)(E)",[extended, {parts, - 2}]))), - <<":ACD:C:E:">> = iolist_to_binary(join(re:split("ACDE","(A (A|B(*ACCEPT)|C) D)(E)",[extended]))), + 2}]))), + <<":ACD:C:E:">> = iolist_to_binary(join(re:split("ACDE","(A (A|B(*ACCEPT)|C) D)(E)",[extended]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(A (A|B(*ACCEPT)|C) D)(E)",[extended, - trim]))), + trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(A (A|B(*ACCEPT)|C) D)(E)",[extended, {parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(A (A|B(*ACCEPT)|C) D)(E)",[extended]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(A (A|B(*ACCEPT)|C) D)(E)",[extended]))), <<"AD">> = iolist_to_binary(join(re:split("AD","(A (A|B(*ACCEPT)|C) D)(E)",[extended, - trim]))), + trim]))), <<"AD">> = iolist_to_binary(join(re:split("AD","(A (A|B(*ACCEPT)|C) D)(E)",[extended, {parts, - 2}]))), - <<"AD">> = iolist_to_binary(join(re:split("AD","(A (A|B(*ACCEPT)|C) D)(E)",[extended]))), + 2}]))), + <<"AD">> = iolist_to_binary(join(re:split("AD","(A (A|B(*ACCEPT)|C) D)(E)",[extended]))), <<":1221:1">> = iolist_to_binary(join(re:split("1221","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless, - trim]))), + trim]))), <<":1221:1:::">> = iolist_to_binary(join(re:split("1221","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless, {parts, - 2}]))), - <<":1221:1:::">> = iolist_to_binary(join(re:split("1221","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless]))), + 2}]))), + <<":1221:1:::">> = iolist_to_binary(join(re:split("1221","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless]))), <<":::Satan, oscillate my metallic sonatas:S">> = iolist_to_binary(join(re:split("Satan, oscillate my metallic sonatas!","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless, - trim]))), + trim]))), <<":::Satan, oscillate my metallic sonatas:S:">> = iolist_to_binary(join(re:split("Satan, oscillate my metallic sonatas!","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless, {parts, - 2}]))), - <<":::Satan, oscillate my metallic sonatas:S:">> = iolist_to_binary(join(re:split("Satan, oscillate my metallic sonatas!","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless]))), + 2}]))), + <<":::Satan, oscillate my metallic sonatas:S:">> = iolist_to_binary(join(re:split("Satan, oscillate my metallic sonatas!","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless]))), <<":::A man, a plan, a canal: Panama:A">> = iolist_to_binary(join(re:split("A man, a plan, a canal: Panama!","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless, - trim]))), + trim]))), <<":::A man, a plan, a canal: Panama:A:">> = iolist_to_binary(join(re:split("A man, a plan, a canal: Panama!","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless, {parts, - 2}]))), - <<":::A man, a plan, a canal: Panama:A:">> = iolist_to_binary(join(re:split("A man, a plan, a canal: Panama!","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless]))), + 2}]))), + <<":::A man, a plan, a canal: Panama:A:">> = iolist_to_binary(join(re:split("A man, a plan, a canal: Panama!","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless]))), <<":::Able was I ere I saw Elba:A">> = iolist_to_binary(join(re:split("Able was I ere I saw Elba.","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless, - trim]))), + trim]))), <<":::Able was I ere I saw Elba:A:">> = iolist_to_binary(join(re:split("Able was I ere I saw Elba.","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless, {parts, - 2}]))), - <<":::Able was I ere I saw Elba:A:">> = iolist_to_binary(join(re:split("Able was I ere I saw Elba.","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless]))), + 2}]))), + <<":::Able was I ere I saw Elba:A:">> = iolist_to_binary(join(re:split("Able was I ere I saw Elba.","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless, - trim]))), + trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless, {parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless]))), <<"The quick brown fox">> = iolist_to_binary(join(re:split("The quick brown fox","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless, - trim]))), + trim]))), <<"The quick brown fox">> = iolist_to_binary(join(re:split("The quick brown fox","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless, {parts, - 2}]))), - <<"The quick brown fox">> = iolist_to_binary(join(re:split("The quick brown fox","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless]))), - <<":a">> = iolist_to_binary(join(re:split("a","^((.)(?1)\\2|.)$",[trim]))), + 2}]))), + <<"The quick brown fox">> = iolist_to_binary(join(re:split("The quick brown fox","^\\W*+(?:((.)\\W*+(?1)\\W*+\\2|)|((.)\\W*+(?3)\\W*+\\4|\\W*+.\\W*+))\\W*+$",[caseless]))), + <<":a">> = iolist_to_binary(join(re:split("a","^((.)(?1)\\2|.)$",[trim]))), <<":a::">> = iolist_to_binary(join(re:split("a","^((.)(?1)\\2|.)$",[{parts, - 2}]))), - <<":a::">> = iolist_to_binary(join(re:split("a","^((.)(?1)\\2|.)$",[]))), - <<":aba:a">> = iolist_to_binary(join(re:split("aba","^((.)(?1)\\2|.)$",[trim]))), + 2}]))), + <<":a::">> = iolist_to_binary(join(re:split("a","^((.)(?1)\\2|.)$",[]))), + <<":aba:a">> = iolist_to_binary(join(re:split("aba","^((.)(?1)\\2|.)$",[trim]))), <<":aba:a:">> = iolist_to_binary(join(re:split("aba","^((.)(?1)\\2|.)$",[{parts, - 2}]))), - <<":aba:a:">> = iolist_to_binary(join(re:split("aba","^((.)(?1)\\2|.)$",[]))), - <<":aabaa:a">> = iolist_to_binary(join(re:split("aabaa","^((.)(?1)\\2|.)$",[trim]))), + 2}]))), + <<":aba:a:">> = iolist_to_binary(join(re:split("aba","^((.)(?1)\\2|.)$",[]))), + <<":aabaa:a">> = iolist_to_binary(join(re:split("aabaa","^((.)(?1)\\2|.)$",[trim]))), <<":aabaa:a:">> = iolist_to_binary(join(re:split("aabaa","^((.)(?1)\\2|.)$",[{parts, - 2}]))), - <<":aabaa:a:">> = iolist_to_binary(join(re:split("aabaa","^((.)(?1)\\2|.)$",[]))), - <<":abcdcba:a">> = iolist_to_binary(join(re:split("abcdcba","^((.)(?1)\\2|.)$",[trim]))), + 2}]))), + <<":aabaa:a:">> = iolist_to_binary(join(re:split("aabaa","^((.)(?1)\\2|.)$",[]))), + <<":abcdcba:a">> = iolist_to_binary(join(re:split("abcdcba","^((.)(?1)\\2|.)$",[trim]))), <<":abcdcba:a:">> = iolist_to_binary(join(re:split("abcdcba","^((.)(?1)\\2|.)$",[{parts, - 2}]))), - <<":abcdcba:a:">> = iolist_to_binary(join(re:split("abcdcba","^((.)(?1)\\2|.)$",[]))), - <<":pqaabaaqp:p">> = iolist_to_binary(join(re:split("pqaabaaqp","^((.)(?1)\\2|.)$",[trim]))), + 2}]))), + <<":abcdcba:a:">> = iolist_to_binary(join(re:split("abcdcba","^((.)(?1)\\2|.)$",[]))), + <<":pqaabaaqp:p">> = iolist_to_binary(join(re:split("pqaabaaqp","^((.)(?1)\\2|.)$",[trim]))), <<":pqaabaaqp:p:">> = iolist_to_binary(join(re:split("pqaabaaqp","^((.)(?1)\\2|.)$",[{parts, - 2}]))), - <<":pqaabaaqp:p:">> = iolist_to_binary(join(re:split("pqaabaaqp","^((.)(?1)\\2|.)$",[]))), - <<":ablewasiereisawelba:a">> = iolist_to_binary(join(re:split("ablewasiereisawelba","^((.)(?1)\\2|.)$",[trim]))), + 2}]))), + <<":pqaabaaqp:p:">> = iolist_to_binary(join(re:split("pqaabaaqp","^((.)(?1)\\2|.)$",[]))), + <<":ablewasiereisawelba:a">> = iolist_to_binary(join(re:split("ablewasiereisawelba","^((.)(?1)\\2|.)$",[trim]))), <<":ablewasiereisawelba:a:">> = iolist_to_binary(join(re:split("ablewasiereisawelba","^((.)(?1)\\2|.)$",[{parts, - 2}]))), - <<":ablewasiereisawelba:a:">> = iolist_to_binary(join(re:split("ablewasiereisawelba","^((.)(?1)\\2|.)$",[]))), - <<"rhubarb">> = iolist_to_binary(join(re:split("rhubarb","^((.)(?1)\\2|.)$",[trim]))), + 2}]))), + <<":ablewasiereisawelba:a:">> = iolist_to_binary(join(re:split("ablewasiereisawelba","^((.)(?1)\\2|.)$",[]))), + <<"rhubarb">> = iolist_to_binary(join(re:split("rhubarb","^((.)(?1)\\2|.)$",[trim]))), <<"rhubarb">> = iolist_to_binary(join(re:split("rhubarb","^((.)(?1)\\2|.)$",[{parts, - 2}]))), - <<"rhubarb">> = iolist_to_binary(join(re:split("rhubarb","^((.)(?1)\\2|.)$",[]))), - <<"the quick brown fox">> = iolist_to_binary(join(re:split("the quick brown fox","^((.)(?1)\\2|.)$",[trim]))), + 2}]))), + <<"rhubarb">> = iolist_to_binary(join(re:split("rhubarb","^((.)(?1)\\2|.)$",[]))), + <<"the quick brown fox">> = iolist_to_binary(join(re:split("the quick brown fox","^((.)(?1)\\2|.)$",[trim]))), <<"the quick brown fox">> = iolist_to_binary(join(re:split("the quick brown fox","^((.)(?1)\\2|.)$",[{parts, - 2}]))), - <<"the quick brown fox">> = iolist_to_binary(join(re:split("the quick brown fox","^((.)(?1)\\2|.)$",[]))), - <<"b:a:z">> = iolist_to_binary(join(re:split("baz","(a)(?<=b(?1))",[trim]))), + 2}]))), + <<"the quick brown fox">> = iolist_to_binary(join(re:split("the quick brown fox","^((.)(?1)\\2|.)$",[]))), + <<"b:a:z">> = iolist_to_binary(join(re:split("baz","(a)(?<=b(?1))",[trim]))), <<"b:a:z">> = iolist_to_binary(join(re:split("baz","(a)(?<=b(?1))",[{parts, - 2}]))), - <<"b:a:z">> = iolist_to_binary(join(re:split("baz","(a)(?<=b(?1))",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(a)(?<=b(?1))",[trim]))), + 2}]))), + <<"b:a:z">> = iolist_to_binary(join(re:split("baz","(a)(?<=b(?1))",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(a)(?<=b(?1))",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(a)(?<=b(?1))",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(a)(?<=b(?1))",[]))), - <<"caz">> = iolist_to_binary(join(re:split("caz","(a)(?<=b(?1))",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(a)(?<=b(?1))",[]))), + <<"caz">> = iolist_to_binary(join(re:split("caz","(a)(?<=b(?1))",[trim]))), <<"caz">> = iolist_to_binary(join(re:split("caz","(a)(?<=b(?1))",[{parts, - 2}]))), - <<"caz">> = iolist_to_binary(join(re:split("caz","(a)(?<=b(?1))",[]))), - <<"zba:a:z">> = iolist_to_binary(join(re:split("zbaaz","(?<=b(?1))(a)",[trim]))), + 2}]))), + <<"caz">> = iolist_to_binary(join(re:split("caz","(a)(?<=b(?1))",[]))), + <<"zba:a:z">> = iolist_to_binary(join(re:split("zbaaz","(?<=b(?1))(a)",[trim]))), <<"zba:a:z">> = iolist_to_binary(join(re:split("zbaaz","(?<=b(?1))(a)",[{parts, - 2}]))), - <<"zba:a:z">> = iolist_to_binary(join(re:split("zbaaz","(?<=b(?1))(a)",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=b(?1))(a)",[trim]))), + 2}]))), + <<"zba:a:z">> = iolist_to_binary(join(re:split("zbaaz","(?<=b(?1))(a)",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=b(?1))(a)",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=b(?1))(a)",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=b(?1))(a)",[]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","(?<=b(?1))(a)",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=b(?1))(a)",[]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","(?<=b(?1))(a)",[trim]))), <<"aaa">> = iolist_to_binary(join(re:split("aaa","(?<=b(?1))(a)",[{parts, - 2}]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","(?<=b(?1))(a)",[]))), - <<"b:a:z">> = iolist_to_binary(join(re:split("baz","(?<X>a)(?<=b(?&X))",[trim]))), + 2}]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","(?<=b(?1))(a)",[]))), + <<"b:a:z">> = iolist_to_binary(join(re:split("baz","(?<X>a)(?<=b(?&X))",[trim]))), <<"b:a:z">> = iolist_to_binary(join(re:split("baz","(?<X>a)(?<=b(?&X))",[{parts, - 2}]))), - <<"b:a:z">> = iolist_to_binary(join(re:split("baz","(?<X>a)(?<=b(?&X))",[]))), - <<":abc">> = iolist_to_binary(join(re:split("abcabc","^(?|(abc)|(def))\\1",[trim]))), + 2}]))), + <<"b:a:z">> = iolist_to_binary(join(re:split("baz","(?<X>a)(?<=b(?&X))",[]))), + <<":abc">> = iolist_to_binary(join(re:split("abcabc","^(?|(abc)|(def))\\1",[trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abcabc","^(?|(abc)|(def))\\1",[{parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abcabc","^(?|(abc)|(def))\\1",[]))), - <<":def">> = iolist_to_binary(join(re:split("defdef","^(?|(abc)|(def))\\1",[trim]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abcabc","^(?|(abc)|(def))\\1",[]))), + <<":def">> = iolist_to_binary(join(re:split("defdef","^(?|(abc)|(def))\\1",[trim]))), <<":def:">> = iolist_to_binary(join(re:split("defdef","^(?|(abc)|(def))\\1",[{parts, - 2}]))), - <<":def:">> = iolist_to_binary(join(re:split("defdef","^(?|(abc)|(def))\\1",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?|(abc)|(def))\\1",[trim]))), + 2}]))), + <<":def:">> = iolist_to_binary(join(re:split("defdef","^(?|(abc)|(def))\\1",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?|(abc)|(def))\\1",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?|(abc)|(def))\\1",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?|(abc)|(def))\\1",[]))), - <<"abcdef">> = iolist_to_binary(join(re:split("abcdef","^(?|(abc)|(def))\\1",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?|(abc)|(def))\\1",[]))), + <<"abcdef">> = iolist_to_binary(join(re:split("abcdef","^(?|(abc)|(def))\\1",[trim]))), <<"abcdef">> = iolist_to_binary(join(re:split("abcdef","^(?|(abc)|(def))\\1",[{parts, - 2}]))), - <<"abcdef">> = iolist_to_binary(join(re:split("abcdef","^(?|(abc)|(def))\\1",[]))), - <<"defabc">> = iolist_to_binary(join(re:split("defabc","^(?|(abc)|(def))\\1",[trim]))), + 2}]))), + <<"abcdef">> = iolist_to_binary(join(re:split("abcdef","^(?|(abc)|(def))\\1",[]))), + <<"defabc">> = iolist_to_binary(join(re:split("defabc","^(?|(abc)|(def))\\1",[trim]))), <<"defabc">> = iolist_to_binary(join(re:split("defabc","^(?|(abc)|(def))\\1",[{parts, - 2}]))), - <<"defabc">> = iolist_to_binary(join(re:split("defabc","^(?|(abc)|(def))\\1",[]))), + 2}]))), + <<"defabc">> = iolist_to_binary(join(re:split("defabc","^(?|(abc)|(def))\\1",[]))), ok. run43() -> - <<":abc">> = iolist_to_binary(join(re:split("abcabc","^(?|(abc)|(def))(?1)",[trim]))), + <<":abc">> = iolist_to_binary(join(re:split("abcabc","^(?|(abc)|(def))(?1)",[trim]))), <<":abc:">> = iolist_to_binary(join(re:split("abcabc","^(?|(abc)|(def))(?1)",[{parts, - 2}]))), - <<":abc:">> = iolist_to_binary(join(re:split("abcabc","^(?|(abc)|(def))(?1)",[]))), - <<":def">> = iolist_to_binary(join(re:split("defabc","^(?|(abc)|(def))(?1)",[trim]))), + 2}]))), + <<":abc:">> = iolist_to_binary(join(re:split("abcabc","^(?|(abc)|(def))(?1)",[]))), + <<":def">> = iolist_to_binary(join(re:split("defabc","^(?|(abc)|(def))(?1)",[trim]))), <<":def:">> = iolist_to_binary(join(re:split("defabc","^(?|(abc)|(def))(?1)",[{parts, - 2}]))), - <<":def:">> = iolist_to_binary(join(re:split("defabc","^(?|(abc)|(def))(?1)",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?|(abc)|(def))(?1)",[trim]))), + 2}]))), + <<":def:">> = iolist_to_binary(join(re:split("defabc","^(?|(abc)|(def))(?1)",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?|(abc)|(def))(?1)",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?|(abc)|(def))(?1)",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?|(abc)|(def))(?1)",[]))), - <<"defdef">> = iolist_to_binary(join(re:split("defdef","^(?|(abc)|(def))(?1)",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?|(abc)|(def))(?1)",[]))), + <<"defdef">> = iolist_to_binary(join(re:split("defdef","^(?|(abc)|(def))(?1)",[trim]))), <<"defdef">> = iolist_to_binary(join(re:split("defdef","^(?|(abc)|(def))(?1)",[{parts, - 2}]))), - <<"defdef">> = iolist_to_binary(join(re:split("defdef","^(?|(abc)|(def))(?1)",[]))), - <<"abcdef">> = iolist_to_binary(join(re:split("abcdef","^(?|(abc)|(def))(?1)",[trim]))), + 2}]))), + <<"defdef">> = iolist_to_binary(join(re:split("defdef","^(?|(abc)|(def))(?1)",[]))), + <<"abcdef">> = iolist_to_binary(join(re:split("abcdef","^(?|(abc)|(def))(?1)",[trim]))), <<"abcdef">> = iolist_to_binary(join(re:split("abcdef","^(?|(abc)|(def))(?1)",[{parts, - 2}]))), - <<"abcdef">> = iolist_to_binary(join(re:split("abcdef","^(?|(abc)|(def))(?1)",[]))), - <<"A:C:D">> = iolist_to_binary(join(re:split("ABCD","(?:(?1)|B)(A(*F)|C)",[trim]))), + 2}]))), + <<"abcdef">> = iolist_to_binary(join(re:split("abcdef","^(?|(abc)|(def))(?1)",[]))), + <<"A:C:D">> = iolist_to_binary(join(re:split("ABCD","(?:(?1)|B)(A(*F)|C)",[trim]))), <<"A:C:D">> = iolist_to_binary(join(re:split("ABCD","(?:(?1)|B)(A(*F)|C)",[{parts, - 2}]))), - <<"A:C:D">> = iolist_to_binary(join(re:split("ABCD","(?:(?1)|B)(A(*F)|C)",[]))), - <<":C:D">> = iolist_to_binary(join(re:split("CCD","(?:(?1)|B)(A(*F)|C)",[trim]))), + 2}]))), + <<"A:C:D">> = iolist_to_binary(join(re:split("ABCD","(?:(?1)|B)(A(*F)|C)",[]))), + <<":C:D">> = iolist_to_binary(join(re:split("CCD","(?:(?1)|B)(A(*F)|C)",[trim]))), <<":C:D">> = iolist_to_binary(join(re:split("CCD","(?:(?1)|B)(A(*F)|C)",[{parts, - 2}]))), - <<":C:D">> = iolist_to_binary(join(re:split("CCD","(?:(?1)|B)(A(*F)|C)",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?:(?1)|B)(A(*F)|C)",[trim]))), + 2}]))), + <<":C:D">> = iolist_to_binary(join(re:split("CCD","(?:(?1)|B)(A(*F)|C)",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?:(?1)|B)(A(*F)|C)",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?:(?1)|B)(A(*F)|C)",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?:(?1)|B)(A(*F)|C)",[]))), - <<"CAD">> = iolist_to_binary(join(re:split("CAD","(?:(?1)|B)(A(*F)|C)",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?:(?1)|B)(A(*F)|C)",[]))), + <<"CAD">> = iolist_to_binary(join(re:split("CAD","(?:(?1)|B)(A(*F)|C)",[trim]))), <<"CAD">> = iolist_to_binary(join(re:split("CAD","(?:(?1)|B)(A(*F)|C)",[{parts, - 2}]))), - <<"CAD">> = iolist_to_binary(join(re:split("CAD","(?:(?1)|B)(A(*F)|C)",[]))), - <<":C:D">> = iolist_to_binary(join(re:split("CCD","^(?:(?1)|B)(A(*F)|C)",[trim]))), + 2}]))), + <<"CAD">> = iolist_to_binary(join(re:split("CAD","(?:(?1)|B)(A(*F)|C)",[]))), + <<":C:D">> = iolist_to_binary(join(re:split("CCD","^(?:(?1)|B)(A(*F)|C)",[trim]))), <<":C:D">> = iolist_to_binary(join(re:split("CCD","^(?:(?1)|B)(A(*F)|C)",[{parts, - 2}]))), - <<":C:D">> = iolist_to_binary(join(re:split("CCD","^(?:(?1)|B)(A(*F)|C)",[]))), - <<":C:D">> = iolist_to_binary(join(re:split("BCD","^(?:(?1)|B)(A(*F)|C)",[trim]))), + 2}]))), + <<":C:D">> = iolist_to_binary(join(re:split("CCD","^(?:(?1)|B)(A(*F)|C)",[]))), + <<":C:D">> = iolist_to_binary(join(re:split("BCD","^(?:(?1)|B)(A(*F)|C)",[trim]))), <<":C:D">> = iolist_to_binary(join(re:split("BCD","^(?:(?1)|B)(A(*F)|C)",[{parts, - 2}]))), - <<":C:D">> = iolist_to_binary(join(re:split("BCD","^(?:(?1)|B)(A(*F)|C)",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:(?1)|B)(A(*F)|C)",[trim]))), + 2}]))), + <<":C:D">> = iolist_to_binary(join(re:split("BCD","^(?:(?1)|B)(A(*F)|C)",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:(?1)|B)(A(*F)|C)",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:(?1)|B)(A(*F)|C)",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:(?1)|B)(A(*F)|C)",[]))), - <<"ABCD">> = iolist_to_binary(join(re:split("ABCD","^(?:(?1)|B)(A(*F)|C)",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:(?1)|B)(A(*F)|C)",[]))), + <<"ABCD">> = iolist_to_binary(join(re:split("ABCD","^(?:(?1)|B)(A(*F)|C)",[trim]))), <<"ABCD">> = iolist_to_binary(join(re:split("ABCD","^(?:(?1)|B)(A(*F)|C)",[{parts, - 2}]))), - <<"ABCD">> = iolist_to_binary(join(re:split("ABCD","^(?:(?1)|B)(A(*F)|C)",[]))), - <<"CAD">> = iolist_to_binary(join(re:split("CAD","^(?:(?1)|B)(A(*F)|C)",[trim]))), + 2}]))), + <<"ABCD">> = iolist_to_binary(join(re:split("ABCD","^(?:(?1)|B)(A(*F)|C)",[]))), + <<"CAD">> = iolist_to_binary(join(re:split("CAD","^(?:(?1)|B)(A(*F)|C)",[trim]))), <<"CAD">> = iolist_to_binary(join(re:split("CAD","^(?:(?1)|B)(A(*F)|C)",[{parts, - 2}]))), - <<"CAD">> = iolist_to_binary(join(re:split("CAD","^(?:(?1)|B)(A(*F)|C)",[]))), - <<"BAD">> = iolist_to_binary(join(re:split("BAD","^(?:(?1)|B)(A(*F)|C)",[trim]))), + 2}]))), + <<"CAD">> = iolist_to_binary(join(re:split("CAD","^(?:(?1)|B)(A(*F)|C)",[]))), + <<"BAD">> = iolist_to_binary(join(re:split("BAD","^(?:(?1)|B)(A(*F)|C)",[trim]))), <<"BAD">> = iolist_to_binary(join(re:split("BAD","^(?:(?1)|B)(A(*F)|C)",[{parts, - 2}]))), - <<"BAD">> = iolist_to_binary(join(re:split("BAD","^(?:(?1)|B)(A(*F)|C)",[]))), - <<":A:D">> = iolist_to_binary(join(re:split("AAD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[trim]))), + 2}]))), + <<"BAD">> = iolist_to_binary(join(re:split("BAD","^(?:(?1)|B)(A(*F)|C)",[]))), + <<":A:D">> = iolist_to_binary(join(re:split("AAD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[trim]))), <<":A:D">> = iolist_to_binary(join(re:split("AAD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[{parts, - 2}]))), - <<":A:D">> = iolist_to_binary(join(re:split("AAD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[]))), - <<":C">> = iolist_to_binary(join(re:split("ACD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[trim]))), + 2}]))), + <<":A:D">> = iolist_to_binary(join(re:split("AAD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[]))), + <<":C">> = iolist_to_binary(join(re:split("ACD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[trim]))), <<":C:">> = iolist_to_binary(join(re:split("ACD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[{parts, - 2}]))), - <<":C:">> = iolist_to_binary(join(re:split("ACD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[]))), - <<":A:D">> = iolist_to_binary(join(re:split("BAD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[trim]))), + 2}]))), + <<":C:">> = iolist_to_binary(join(re:split("ACD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[]))), + <<":A:D">> = iolist_to_binary(join(re:split("BAD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[trim]))), <<":A:D">> = iolist_to_binary(join(re:split("BAD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[{parts, - 2}]))), - <<":A:D">> = iolist_to_binary(join(re:split("BAD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[]))), - <<":C">> = iolist_to_binary(join(re:split("BCD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[trim]))), + 2}]))), + <<":A:D">> = iolist_to_binary(join(re:split("BAD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[]))), + <<":C">> = iolist_to_binary(join(re:split("BCD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[trim]))), <<":C:">> = iolist_to_binary(join(re:split("BCD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[{parts, - 2}]))), - <<":C:">> = iolist_to_binary(join(re:split("BCD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[]))), - <<":A:X">> = iolist_to_binary(join(re:split("BAX","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[trim]))), + 2}]))), + <<":C:">> = iolist_to_binary(join(re:split("BCD","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[]))), + <<":A:X">> = iolist_to_binary(join(re:split("BAX","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[trim]))), <<":A:X">> = iolist_to_binary(join(re:split("BAX","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[{parts, - 2}]))), - <<":A:X">> = iolist_to_binary(join(re:split("BAX","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[trim]))), + 2}]))), + <<":A:X">> = iolist_to_binary(join(re:split("BAX","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[]))), - <<"ACX">> = iolist_to_binary(join(re:split("ACX","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[]))), + <<"ACX">> = iolist_to_binary(join(re:split("ACX","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[trim]))), <<"ACX">> = iolist_to_binary(join(re:split("ACX","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[{parts, - 2}]))), - <<"ACX">> = iolist_to_binary(join(re:split("ACX","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[]))), - <<"ABC">> = iolist_to_binary(join(re:split("ABC","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[trim]))), + 2}]))), + <<"ACX">> = iolist_to_binary(join(re:split("ACX","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[]))), + <<"ABC">> = iolist_to_binary(join(re:split("ABC","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[trim]))), <<"ABC">> = iolist_to_binary(join(re:split("ABC","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[{parts, - 2}]))), - <<"ABC">> = iolist_to_binary(join(re:split("ABC","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[]))), - <<"">> = iolist_to_binary(join(re:split("BAC","(?(DEFINE)(A))B(?1)C",[trim]))), + 2}]))), + <<"ABC">> = iolist_to_binary(join(re:split("ABC","(?:(?1)|B)(A(*ACCEPT)XX|C)D",[]))), + <<"">> = iolist_to_binary(join(re:split("BAC","(?(DEFINE)(A))B(?1)C",[trim]))), <<"::">> = iolist_to_binary(join(re:split("BAC","(?(DEFINE)(A))B(?1)C",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("BAC","(?(DEFINE)(A))B(?1)C",[]))), - <<"">> = iolist_to_binary(join(re:split("BAAC","(?(DEFINE)((A)\\2))B(?1)C",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("BAC","(?(DEFINE)(A))B(?1)C",[]))), + <<"">> = iolist_to_binary(join(re:split("BAAC","(?(DEFINE)((A)\\2))B(?1)C",[trim]))), <<":::">> = iolist_to_binary(join(re:split("BAAC","(?(DEFINE)((A)\\2))B(?1)C",[{parts, - 2}]))), - <<":::">> = iolist_to_binary(join(re:split("BAAC","(?(DEFINE)((A)\\2))B(?1)C",[]))), + 2}]))), + <<":::">> = iolist_to_binary(join(re:split("BAAC","(?(DEFINE)((A)\\2))B(?1)C",[]))), <<":(ab(cd)ef):ef">> = iolist_to_binary(join(re:split("(ab(cd)ef)","(?<pn> \\( ( [^()]++ | (?&pn) )* \\) )",[extended, - trim]))), + trim]))), <<":(ab(cd)ef):ef:">> = iolist_to_binary(join(re:split("(ab(cd)ef)","(?<pn> \\( ( [^()]++ | (?&pn) )* \\) )",[extended, {parts, - 2}]))), - <<":(ab(cd)ef):ef:">> = iolist_to_binary(join(re:split("(ab(cd)ef)","(?<pn> \\( ( [^()]++ | (?&pn) )* \\) )",[extended]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?=a(*SKIP)b|ac)",[trim]))), + 2}]))), + <<":(ab(cd)ef):ef:">> = iolist_to_binary(join(re:split("(ab(cd)ef)","(?<pn> \\( ( [^()]++ | (?&pn) )* \\) )",[extended]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?=a(*SKIP)b|ac)",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?=a(*SKIP)b|ac)",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?=a(*SKIP)b|ac)",[]))), - <<"ac">> = iolist_to_binary(join(re:split("ac","^(?=a(*SKIP)b|ac)",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?=a(*SKIP)b|ac)",[]))), + <<"ac">> = iolist_to_binary(join(re:split("ac","^(?=a(*SKIP)b|ac)",[trim]))), <<"ac">> = iolist_to_binary(join(re:split("ac","^(?=a(*SKIP)b|ac)",[{parts, - 2}]))), - <<"ac">> = iolist_to_binary(join(re:split("ac","^(?=a(*SKIP)b|ac)",[]))), - <<"ab">> = iolist_to_binary(join(re:split("ab","^(?=a(*PRUNE)b)",[trim]))), + 2}]))), + <<"ac">> = iolist_to_binary(join(re:split("ac","^(?=a(*SKIP)b|ac)",[]))), + <<"ab">> = iolist_to_binary(join(re:split("ab","^(?=a(*PRUNE)b)",[trim]))), <<"ab">> = iolist_to_binary(join(re:split("ab","^(?=a(*PRUNE)b)",[{parts, - 2}]))), - <<"ab">> = iolist_to_binary(join(re:split("ab","^(?=a(*PRUNE)b)",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?=a(*PRUNE)b)",[trim]))), + 2}]))), + <<"ab">> = iolist_to_binary(join(re:split("ab","^(?=a(*PRUNE)b)",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?=a(*PRUNE)b)",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?=a(*PRUNE)b)",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?=a(*PRUNE)b)",[]))), - <<"ac">> = iolist_to_binary(join(re:split("ac","^(?=a(*PRUNE)b)",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?=a(*PRUNE)b)",[]))), + <<"ac">> = iolist_to_binary(join(re:split("ac","^(?=a(*PRUNE)b)",[trim]))), <<"ac">> = iolist_to_binary(join(re:split("ac","^(?=a(*PRUNE)b)",[{parts, - 2}]))), - <<"ac">> = iolist_to_binary(join(re:split("ac","^(?=a(*PRUNE)b)",[]))), - <<"ac">> = iolist_to_binary(join(re:split("ac","^(?=a(*ACCEPT)b)",[trim]))), + 2}]))), + <<"ac">> = iolist_to_binary(join(re:split("ac","^(?=a(*PRUNE)b)",[]))), + <<"ac">> = iolist_to_binary(join(re:split("ac","^(?=a(*ACCEPT)b)",[trim]))), <<"ac">> = iolist_to_binary(join(re:split("ac","^(?=a(*ACCEPT)b)",[{parts, - 2}]))), - <<"ac">> = iolist_to_binary(join(re:split("ac","^(?=a(*ACCEPT)b)",[]))), - <<"a">> = iolist_to_binary(join(re:split("ab","(?>a\\Kb)",[trim]))), + 2}]))), + <<"ac">> = iolist_to_binary(join(re:split("ac","^(?=a(*ACCEPT)b)",[]))), + <<"a">> = iolist_to_binary(join(re:split("ab","(?>a\\Kb)",[trim]))), <<"a:">> = iolist_to_binary(join(re:split("ab","(?>a\\Kb)",[{parts, - 2}]))), - <<"a:">> = iolist_to_binary(join(re:split("ab","(?>a\\Kb)",[]))), - <<"a:ab">> = iolist_to_binary(join(re:split("ab","((?>a\\Kb))",[trim]))), + 2}]))), + <<"a:">> = iolist_to_binary(join(re:split("ab","(?>a\\Kb)",[]))), + <<"a:ab">> = iolist_to_binary(join(re:split("ab","((?>a\\Kb))",[trim]))), <<"a:ab:">> = iolist_to_binary(join(re:split("ab","((?>a\\Kb))",[{parts, - 2}]))), - <<"a:ab:">> = iolist_to_binary(join(re:split("ab","((?>a\\Kb))",[]))), - <<"a:ab">> = iolist_to_binary(join(re:split("ab","(a\\Kb)",[trim]))), + 2}]))), + <<"a:ab:">> = iolist_to_binary(join(re:split("ab","((?>a\\Kb))",[]))), + <<"a:ab">> = iolist_to_binary(join(re:split("ab","(a\\Kb)",[trim]))), <<"a:ab:">> = iolist_to_binary(join(re:split("ab","(a\\Kb)",[{parts, - 2}]))), - <<"a:ab:">> = iolist_to_binary(join(re:split("ab","(a\\Kb)",[]))), - <<"">> = iolist_to_binary(join(re:split("ac","^a\\Kcz|ac",[trim]))), + 2}]))), + <<"a:ab:">> = iolist_to_binary(join(re:split("ab","(a\\Kb)",[]))), + <<"">> = iolist_to_binary(join(re:split("ac","^a\\Kcz|ac",[trim]))), <<":">> = iolist_to_binary(join(re:split("ac","^a\\Kcz|ac",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ac","^a\\Kcz|ac",[]))), - <<"">> = iolist_to_binary(join(re:split("ab","(?>a\\Kbz|ab)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ac","^a\\Kcz|ac",[]))), + <<"">> = iolist_to_binary(join(re:split("ab","(?>a\\Kbz|ab)",[trim]))), <<":">> = iolist_to_binary(join(re:split("ab","(?>a\\Kbz|ab)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ab","(?>a\\Kbz|ab)",[]))), - <<"a">> = iolist_to_binary(join(re:split("ab","^(?&t)(?(DEFINE)(?<t>a\\Kb))$",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ab","(?>a\\Kbz|ab)",[]))), + <<"a">> = iolist_to_binary(join(re:split("ab","^(?&t)(?(DEFINE)(?<t>a\\Kb))$",[trim]))), <<"a::">> = iolist_to_binary(join(re:split("ab","^(?&t)(?(DEFINE)(?<t>a\\Kb))$",[{parts, - 2}]))), - <<"a::">> = iolist_to_binary(join(re:split("ab","^(?&t)(?(DEFINE)(?<t>a\\Kb))$",[]))), - <<":c">> = iolist_to_binary(join(re:split("a(b)c","^([^()]|\\((?1)*\\))*$",[trim]))), + 2}]))), + <<"a::">> = iolist_to_binary(join(re:split("ab","^(?&t)(?(DEFINE)(?<t>a\\Kb))$",[]))), + <<":c">> = iolist_to_binary(join(re:split("a(b)c","^([^()]|\\((?1)*\\))*$",[trim]))), <<":c:">> = iolist_to_binary(join(re:split("a(b)c","^([^()]|\\((?1)*\\))*$",[{parts, - 2}]))), - <<":c:">> = iolist_to_binary(join(re:split("a(b)c","^([^()]|\\((?1)*\\))*$",[]))), - <<":e">> = iolist_to_binary(join(re:split("a(b(c)d)e","^([^()]|\\((?1)*\\))*$",[trim]))), + 2}]))), + <<":c:">> = iolist_to_binary(join(re:split("a(b)c","^([^()]|\\((?1)*\\))*$",[]))), + <<":e">> = iolist_to_binary(join(re:split("a(b(c)d)e","^([^()]|\\((?1)*\\))*$",[trim]))), <<":e:">> = iolist_to_binary(join(re:split("a(b(c)d)e","^([^()]|\\((?1)*\\))*$",[{parts, - 2}]))), - <<":e:">> = iolist_to_binary(join(re:split("a(b(c)d)e","^([^()]|\\((?1)*\\))*$",[]))), - <<":0">> = iolist_to_binary(join(re:split("0","(?P<L1>(?P<L2>0)(?P>L1)|(?P>L2))",[trim]))), + 2}]))), + <<":e:">> = iolist_to_binary(join(re:split("a(b(c)d)e","^([^()]|\\((?1)*\\))*$",[]))), + <<":0">> = iolist_to_binary(join(re:split("0","(?P<L1>(?P<L2>0)(?P>L1)|(?P>L2))",[trim]))), <<":0::">> = iolist_to_binary(join(re:split("0","(?P<L1>(?P<L2>0)(?P>L1)|(?P>L2))",[{parts, - 2}]))), - <<":0::">> = iolist_to_binary(join(re:split("0","(?P<L1>(?P<L2>0)(?P>L1)|(?P>L2))",[]))), - <<":00:0">> = iolist_to_binary(join(re:split("00","(?P<L1>(?P<L2>0)(?P>L1)|(?P>L2))",[trim]))), + 2}]))), + <<":0::">> = iolist_to_binary(join(re:split("0","(?P<L1>(?P<L2>0)(?P>L1)|(?P>L2))",[]))), + <<":00:0">> = iolist_to_binary(join(re:split("00","(?P<L1>(?P<L2>0)(?P>L1)|(?P>L2))",[trim]))), <<":00:0:">> = iolist_to_binary(join(re:split("00","(?P<L1>(?P<L2>0)(?P>L1)|(?P>L2))",[{parts, - 2}]))), - <<":00:0:">> = iolist_to_binary(join(re:split("00","(?P<L1>(?P<L2>0)(?P>L1)|(?P>L2))",[]))), - <<":0000:0">> = iolist_to_binary(join(re:split("0000","(?P<L1>(?P<L2>0)(?P>L1)|(?P>L2))",[trim]))), + 2}]))), + <<":00:0:">> = iolist_to_binary(join(re:split("00","(?P<L1>(?P<L2>0)(?P>L1)|(?P>L2))",[]))), + <<":0000:0">> = iolist_to_binary(join(re:split("0000","(?P<L1>(?P<L2>0)(?P>L1)|(?P>L2))",[trim]))), <<":0000:0:">> = iolist_to_binary(join(re:split("0000","(?P<L1>(?P<L2>0)(?P>L1)|(?P>L2))",[{parts, - 2}]))), - <<":0000:0:">> = iolist_to_binary(join(re:split("0000","(?P<L1>(?P<L2>0)(?P>L1)|(?P>L2))",[]))), - <<":0:0">> = iolist_to_binary(join(re:split("0","(?P<L1>(?P<L2>0)|(?P>L2)(?P>L1))",[trim]))), + 2}]))), + <<":0000:0:">> = iolist_to_binary(join(re:split("0000","(?P<L1>(?P<L2>0)(?P>L1)|(?P>L2))",[]))), + <<":0:0">> = iolist_to_binary(join(re:split("0","(?P<L1>(?P<L2>0)|(?P>L2)(?P>L1))",[trim]))), <<":0:0:">> = iolist_to_binary(join(re:split("0","(?P<L1>(?P<L2>0)|(?P>L2)(?P>L1))",[{parts, - 2}]))), - <<":0:0:">> = iolist_to_binary(join(re:split("0","(?P<L1>(?P<L2>0)|(?P>L2)(?P>L1))",[]))), - <<":0:0::0:0">> = iolist_to_binary(join(re:split("00","(?P<L1>(?P<L2>0)|(?P>L2)(?P>L1))",[trim]))), + 2}]))), + <<":0:0:">> = iolist_to_binary(join(re:split("0","(?P<L1>(?P<L2>0)|(?P>L2)(?P>L1))",[]))), + <<":0:0::0:0">> = iolist_to_binary(join(re:split("00","(?P<L1>(?P<L2>0)|(?P>L2)(?P>L1))",[trim]))), <<":0:0:0">> = iolist_to_binary(join(re:split("00","(?P<L1>(?P<L2>0)|(?P>L2)(?P>L1))",[{parts, - 2}]))), - <<":0:0::0:0:">> = iolist_to_binary(join(re:split("00","(?P<L1>(?P<L2>0)|(?P>L2)(?P>L1))",[]))), - <<":0:0::0:0::0:0::0:0">> = iolist_to_binary(join(re:split("0000","(?P<L1>(?P<L2>0)|(?P>L2)(?P>L1))",[trim]))), + 2}]))), + <<":0:0::0:0:">> = iolist_to_binary(join(re:split("00","(?P<L1>(?P<L2>0)|(?P>L2)(?P>L1))",[]))), + <<":0:0::0:0::0:0::0:0">> = iolist_to_binary(join(re:split("0000","(?P<L1>(?P<L2>0)|(?P>L2)(?P>L1))",[trim]))), <<":0:0:000">> = iolist_to_binary(join(re:split("0000","(?P<L1>(?P<L2>0)|(?P>L2)(?P>L1))",[{parts, - 2}]))), - <<":0:0::0:0::0:0::0:0:">> = iolist_to_binary(join(re:split("0000","(?P<L1>(?P<L2>0)|(?P>L2)(?P>L1))",[]))), + 2}]))), + <<":0:0::0:0::0:0::0:0:">> = iolist_to_binary(join(re:split("0000","(?P<L1>(?P<L2>0)|(?P>L2)(?P>L1))",[]))), ok. run44() -> - <<"ACABX">> = iolist_to_binary(join(re:split("ACABX","A(*COMMIT)(B|D)",[trim]))), + <<"ACABX">> = iolist_to_binary(join(re:split("ACABX","A(*COMMIT)(B|D)",[trim]))), <<"ACABX">> = iolist_to_binary(join(re:split("ACABX","A(*COMMIT)(B|D)",[{parts, - 2}]))), - <<"ACABX">> = iolist_to_binary(join(re:split("ACABX","A(*COMMIT)(B|D)",[]))), - <<":A:B:C:DEFG">> = iolist_to_binary(join(re:split("ABCDEFG","(*COMMIT)(A|P)(B|P)(C|P)",[trim]))), + 2}]))), + <<"ACABX">> = iolist_to_binary(join(re:split("ACABX","A(*COMMIT)(B|D)",[]))), + <<":A:B:C:DEFG">> = iolist_to_binary(join(re:split("ABCDEFG","(*COMMIT)(A|P)(B|P)(C|P)",[trim]))), <<":A:B:C:DEFG">> = iolist_to_binary(join(re:split("ABCDEFG","(*COMMIT)(A|P)(B|P)(C|P)",[{parts, - 2}]))), - <<":A:B:C:DEFG">> = iolist_to_binary(join(re:split("ABCDEFG","(*COMMIT)(A|P)(B|P)(C|P)",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(*COMMIT)(A|P)(B|P)(C|P)",[trim]))), + 2}]))), + <<":A:B:C:DEFG">> = iolist_to_binary(join(re:split("ABCDEFG","(*COMMIT)(A|P)(B|P)(C|P)",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(*COMMIT)(A|P)(B|P)(C|P)",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(*COMMIT)(A|P)(B|P)(C|P)",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(*COMMIT)(A|P)(B|P)(C|P)",[]))), - <<"DEFGABC">> = iolist_to_binary(join(re:split("DEFGABC","(*COMMIT)(A|P)(B|P)(C|P)",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(*COMMIT)(A|P)(B|P)(C|P)",[]))), + <<"DEFGABC">> = iolist_to_binary(join(re:split("DEFGABC","(*COMMIT)(A|P)(B|P)(C|P)",[trim]))), <<"DEFGABC">> = iolist_to_binary(join(re:split("DEFGABC","(*COMMIT)(A|P)(B|P)(C|P)",[{parts, - 2}]))), - <<"DEFGABC">> = iolist_to_binary(join(re:split("DEFGABC","(*COMMIT)(A|P)(B|P)(C|P)",[]))), - <<":a">> = iolist_to_binary(join(re:split("abbb","(\\w+)(?>b(*COMMIT))\\w{2}",[trim]))), + 2}]))), + <<"DEFGABC">> = iolist_to_binary(join(re:split("DEFGABC","(*COMMIT)(A|P)(B|P)(C|P)",[]))), + <<":a">> = iolist_to_binary(join(re:split("abbb","(\\w+)(?>b(*COMMIT))\\w{2}",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("abbb","(\\w+)(?>b(*COMMIT))\\w{2}",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("abbb","(\\w+)(?>b(*COMMIT))\\w{2}",[]))), - <<"abbb">> = iolist_to_binary(join(re:split("abbb","(\\w+)b(*COMMIT)\\w{2}",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("abbb","(\\w+)(?>b(*COMMIT))\\w{2}",[]))), + <<"abbb">> = iolist_to_binary(join(re:split("abbb","(\\w+)b(*COMMIT)\\w{2}",[trim]))), <<"abbb">> = iolist_to_binary(join(re:split("abbb","(\\w+)b(*COMMIT)\\w{2}",[{parts, - 2}]))), - <<"abbb">> = iolist_to_binary(join(re:split("abbb","(\\w+)b(*COMMIT)\\w{2}",[]))), - <<"b::c">> = iolist_to_binary(join(re:split("bac","(?&t)(?#()(?(DEFINE)(?<t>a))",[trim]))), + 2}]))), + <<"abbb">> = iolist_to_binary(join(re:split("abbb","(\\w+)b(*COMMIT)\\w{2}",[]))), + <<"b::c">> = iolist_to_binary(join(re:split("bac","(?&t)(?#()(?(DEFINE)(?<t>a))",[trim]))), <<"b::c">> = iolist_to_binary(join(re:split("bac","(?&t)(?#()(?(DEFINE)(?<t>a))",[{parts, - 2}]))), - <<"b::c">> = iolist_to_binary(join(re:split("bac","(?&t)(?#()(?(DEFINE)(?<t>a))",[]))), - <<"yes">> = iolist_to_binary(join(re:split("yes","(?>(*COMMIT)(?>yes|no)(*THEN)(*F))?",[trim]))), + 2}]))), + <<"b::c">> = iolist_to_binary(join(re:split("bac","(?&t)(?#()(?(DEFINE)(?<t>a))",[]))), + <<"yes">> = iolist_to_binary(join(re:split("yes","(?>(*COMMIT)(?>yes|no)(*THEN)(*F))?",[trim]))), <<"yes">> = iolist_to_binary(join(re:split("yes","(?>(*COMMIT)(?>yes|no)(*THEN)(*F))?",[{parts, - 2}]))), - <<"yes">> = iolist_to_binary(join(re:split("yes","(?>(*COMMIT)(?>yes|no)(*THEN)(*F))?",[]))), - <<"yes">> = iolist_to_binary(join(re:split("yes","(?>(*COMMIT)(yes|no)(*THEN)(*F))?",[trim]))), + 2}]))), + <<"yes">> = iolist_to_binary(join(re:split("yes","(?>(*COMMIT)(?>yes|no)(*THEN)(*F))?",[]))), + <<"yes">> = iolist_to_binary(join(re:split("yes","(?>(*COMMIT)(yes|no)(*THEN)(*F))?",[trim]))), <<"yes">> = iolist_to_binary(join(re:split("yes","(?>(*COMMIT)(yes|no)(*THEN)(*F))?",[{parts, - 2}]))), - <<"yes">> = iolist_to_binary(join(re:split("yes","(?>(*COMMIT)(yes|no)(*THEN)(*F))?",[]))), - <<"">> = iolist_to_binary(join(re:split("bc","b?(*SKIP)c",[trim]))), + 2}]))), + <<"yes">> = iolist_to_binary(join(re:split("yes","(?>(*COMMIT)(yes|no)(*THEN)(*F))?",[]))), + <<"">> = iolist_to_binary(join(re:split("bc","b?(*SKIP)c",[trim]))), <<":">> = iolist_to_binary(join(re:split("bc","b?(*SKIP)c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("bc","b?(*SKIP)c",[]))), - <<"a">> = iolist_to_binary(join(re:split("abc","b?(*SKIP)c",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("bc","b?(*SKIP)c",[]))), + <<"a">> = iolist_to_binary(join(re:split("abc","b?(*SKIP)c",[trim]))), <<"a:">> = iolist_to_binary(join(re:split("abc","b?(*SKIP)c",[{parts, - 2}]))), - <<"a:">> = iolist_to_binary(join(re:split("abc","b?(*SKIP)c",[]))), + 2}]))), + <<"a:">> = iolist_to_binary(join(re:split("abc","b?(*SKIP)c",[]))), ok. run45() -> - <<"a">> = iolist_to_binary(join(re:split("a","(*SKIP)bc",[trim]))), + <<"a">> = iolist_to_binary(join(re:split("a","(*SKIP)bc",[trim]))), <<"a">> = iolist_to_binary(join(re:split("a","(*SKIP)bc",[{parts, - 2}]))), - <<"a">> = iolist_to_binary(join(re:split("a","(*SKIP)bc",[]))), - <<"a">> = iolist_to_binary(join(re:split("a","(*SKIP)b",[trim]))), + 2}]))), + <<"a">> = iolist_to_binary(join(re:split("a","(*SKIP)bc",[]))), + <<"a">> = iolist_to_binary(join(re:split("a","(*SKIP)b",[trim]))), <<"a">> = iolist_to_binary(join(re:split("a","(*SKIP)b",[{parts, - 2}]))), - <<"a">> = iolist_to_binary(join(re:split("a","(*SKIP)b",[]))), - <<"x::x::x">> = iolist_to_binary(join(re:split("xxx","(?P<abn>(?P=abn)xxx|)+",[trim]))), + 2}]))), + <<"a">> = iolist_to_binary(join(re:split("a","(*SKIP)b",[]))), + <<"x::x::x">> = iolist_to_binary(join(re:split("xxx","(?P<abn>(?P=abn)xxx|)+",[trim]))), <<"x::xx">> = iolist_to_binary(join(re:split("xxx","(?P<abn>(?P=abn)xxx|)+",[{parts, - 2}]))), - <<"x::x::x::">> = iolist_to_binary(join(re:split("xxx","(?P<abn>(?P=abn)xxx|)+",[]))), - <<":a">> = iolist_to_binary(join(re:split("aa","(?i:([^b]))(?1)",[trim]))), + 2}]))), + <<"x::x::x::">> = iolist_to_binary(join(re:split("xxx","(?P<abn>(?P=abn)xxx|)+",[]))), + <<":a">> = iolist_to_binary(join(re:split("aa","(?i:([^b]))(?1)",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aa","(?i:([^b]))(?1)",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aa","(?i:([^b]))(?1)",[]))), - <<":a">> = iolist_to_binary(join(re:split("aA","(?i:([^b]))(?1)",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aa","(?i:([^b]))(?1)",[]))), + <<":a">> = iolist_to_binary(join(re:split("aA","(?i:([^b]))(?1)",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aA","(?i:([^b]))(?1)",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aA","(?i:([^b]))(?1)",[]))), - <<":*:: ::a::l::r">> = iolist_to_binary(join(re:split("** Failers","(?i:([^b]))(?1)",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aA","(?i:([^b]))(?1)",[]))), + <<":*:: ::a::l::r">> = iolist_to_binary(join(re:split("** Failers","(?i:([^b]))(?1)",[trim]))), <<":*: Failers">> = iolist_to_binary(join(re:split("** Failers","(?i:([^b]))(?1)",[{parts, - 2}]))), - <<":*:: ::a::l::r:">> = iolist_to_binary(join(re:split("** Failers","(?i:([^b]))(?1)",[]))), - <<"ab">> = iolist_to_binary(join(re:split("ab","(?i:([^b]))(?1)",[trim]))), + 2}]))), + <<":*:: ::a::l::r:">> = iolist_to_binary(join(re:split("** Failers","(?i:([^b]))(?1)",[]))), + <<"ab">> = iolist_to_binary(join(re:split("ab","(?i:([^b]))(?1)",[trim]))), <<"ab">> = iolist_to_binary(join(re:split("ab","(?i:([^b]))(?1)",[{parts, - 2}]))), - <<"ab">> = iolist_to_binary(join(re:split("ab","(?i:([^b]))(?1)",[]))), - <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:([^b]))(?1)",[trim]))), + 2}]))), + <<"ab">> = iolist_to_binary(join(re:split("ab","(?i:([^b]))(?1)",[]))), + <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:([^b]))(?1)",[trim]))), <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:([^b]))(?1)",[{parts, - 2}]))), - <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:([^b]))(?1)",[]))), - <<"Ba">> = iolist_to_binary(join(re:split("Ba","(?i:([^b]))(?1)",[trim]))), + 2}]))), + <<"aB">> = iolist_to_binary(join(re:split("aB","(?i:([^b]))(?1)",[]))), + <<"Ba">> = iolist_to_binary(join(re:split("Ba","(?i:([^b]))(?1)",[trim]))), <<"Ba">> = iolist_to_binary(join(re:split("Ba","(?i:([^b]))(?1)",[{parts, - 2}]))), - <<"Ba">> = iolist_to_binary(join(re:split("Ba","(?i:([^b]))(?1)",[]))), - <<"ba">> = iolist_to_binary(join(re:split("ba","(?i:([^b]))(?1)",[trim]))), + 2}]))), + <<"Ba">> = iolist_to_binary(join(re:split("Ba","(?i:([^b]))(?1)",[]))), + <<"ba">> = iolist_to_binary(join(re:split("ba","(?i:([^b]))(?1)",[trim]))), <<"ba">> = iolist_to_binary(join(re:split("ba","(?i:([^b]))(?1)",[{parts, - 2}]))), - <<"ba">> = iolist_to_binary(join(re:split("ba","(?i:([^b]))(?1)",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaaaaX","^(?&t)*+(?(DEFINE)(?<t>a))\\w$",[trim]))), + 2}]))), + <<"ba">> = iolist_to_binary(join(re:split("ba","(?i:([^b]))(?1)",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaaaaX","^(?&t)*+(?(DEFINE)(?<t>a))\\w$",[trim]))), <<"::">> = iolist_to_binary(join(re:split("aaaaaaX","^(?&t)*+(?(DEFINE)(?<t>a))\\w$",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("aaaaaaX","^(?&t)*+(?(DEFINE)(?<t>a))\\w$",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?&t)*+(?(DEFINE)(?<t>a))\\w$",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("aaaaaaX","^(?&t)*+(?(DEFINE)(?<t>a))\\w$",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?&t)*+(?(DEFINE)(?<t>a))\\w$",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?&t)*+(?(DEFINE)(?<t>a))\\w$",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?&t)*+(?(DEFINE)(?<t>a))\\w$",[]))), - <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(?&t)*+(?(DEFINE)(?<t>a))\\w$",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?&t)*+(?(DEFINE)(?<t>a))\\w$",[]))), + <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(?&t)*+(?(DEFINE)(?<t>a))\\w$",[trim]))), <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(?&t)*+(?(DEFINE)(?<t>a))\\w$",[{parts, - 2}]))), - <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(?&t)*+(?(DEFINE)(?<t>a))\\w$",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaaaaX","^(?&t)*(?(DEFINE)(?<t>a))\\w$",[trim]))), + 2}]))), + <<"aaaaaa">> = iolist_to_binary(join(re:split("aaaaaa","^(?&t)*+(?(DEFINE)(?<t>a))\\w$",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaaaaX","^(?&t)*(?(DEFINE)(?<t>a))\\w$",[trim]))), <<"::">> = iolist_to_binary(join(re:split("aaaaaaX","^(?&t)*(?(DEFINE)(?<t>a))\\w$",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("aaaaaaX","^(?&t)*(?(DEFINE)(?<t>a))\\w$",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaaaa","^(?&t)*(?(DEFINE)(?<t>a))\\w$",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("aaaaaaX","^(?&t)*(?(DEFINE)(?<t>a))\\w$",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaaaa","^(?&t)*(?(DEFINE)(?<t>a))\\w$",[trim]))), <<"::">> = iolist_to_binary(join(re:split("aaaaaa","^(?&t)*(?(DEFINE)(?<t>a))\\w$",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("aaaaaa","^(?&t)*(?(DEFINE)(?<t>a))\\w$",[]))), - <<":a:X">> = iolist_to_binary(join(re:split("aaaaX","^(a)*+(\\w)",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("aaaaaa","^(?&t)*(?(DEFINE)(?<t>a))\\w$",[]))), + <<":a:X">> = iolist_to_binary(join(re:split("aaaaX","^(a)*+(\\w)",[trim]))), <<":a:X:">> = iolist_to_binary(join(re:split("aaaaX","^(a)*+(\\w)",[{parts, - 2}]))), - <<":a:X:">> = iolist_to_binary(join(re:split("aaaaX","^(a)*+(\\w)",[]))), - <<"::Y:Z">> = iolist_to_binary(join(re:split("YZ","^(a)*+(\\w)",[trim]))), + 2}]))), + <<":a:X:">> = iolist_to_binary(join(re:split("aaaaX","^(a)*+(\\w)",[]))), + <<"::Y:Z">> = iolist_to_binary(join(re:split("YZ","^(a)*+(\\w)",[trim]))), <<"::Y:Z">> = iolist_to_binary(join(re:split("YZ","^(a)*+(\\w)",[{parts, - 2}]))), - <<"::Y:Z">> = iolist_to_binary(join(re:split("YZ","^(a)*+(\\w)",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a)*+(\\w)",[trim]))), + 2}]))), + <<"::Y:Z">> = iolist_to_binary(join(re:split("YZ","^(a)*+(\\w)",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a)*+(\\w)",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a)*+(\\w)",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a)*+(\\w)",[]))), - <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(a)*+(\\w)",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a)*+(\\w)",[]))), + <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(a)*+(\\w)",[trim]))), <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(a)*+(\\w)",[{parts, - 2}]))), - <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(a)*+(\\w)",[]))), - <<":X">> = iolist_to_binary(join(re:split("aaaaX","^(?:a)*+(\\w)",[trim]))), + 2}]))), + <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(a)*+(\\w)",[]))), + <<":X">> = iolist_to_binary(join(re:split("aaaaX","^(?:a)*+(\\w)",[trim]))), <<":X:">> = iolist_to_binary(join(re:split("aaaaX","^(?:a)*+(\\w)",[{parts, - 2}]))), - <<":X:">> = iolist_to_binary(join(re:split("aaaaX","^(?:a)*+(\\w)",[]))), - <<":Y:Z">> = iolist_to_binary(join(re:split("YZ","^(?:a)*+(\\w)",[trim]))), + 2}]))), + <<":X:">> = iolist_to_binary(join(re:split("aaaaX","^(?:a)*+(\\w)",[]))), + <<":Y:Z">> = iolist_to_binary(join(re:split("YZ","^(?:a)*+(\\w)",[trim]))), <<":Y:Z">> = iolist_to_binary(join(re:split("YZ","^(?:a)*+(\\w)",[{parts, - 2}]))), - <<":Y:Z">> = iolist_to_binary(join(re:split("YZ","^(?:a)*+(\\w)",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:a)*+(\\w)",[trim]))), + 2}]))), + <<":Y:Z">> = iolist_to_binary(join(re:split("YZ","^(?:a)*+(\\w)",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:a)*+(\\w)",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:a)*+(\\w)",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:a)*+(\\w)",[]))), - <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(?:a)*+(\\w)",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:a)*+(\\w)",[]))), + <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(?:a)*+(\\w)",[trim]))), <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(?:a)*+(\\w)",[{parts, - 2}]))), - <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(?:a)*+(\\w)",[]))), - <<":a:X">> = iolist_to_binary(join(re:split("aaaaX","^(a)++(\\w)",[trim]))), + 2}]))), + <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(?:a)*+(\\w)",[]))), + <<":a:X">> = iolist_to_binary(join(re:split("aaaaX","^(a)++(\\w)",[trim]))), <<":a:X:">> = iolist_to_binary(join(re:split("aaaaX","^(a)++(\\w)",[{parts, - 2}]))), - <<":a:X:">> = iolist_to_binary(join(re:split("aaaaX","^(a)++(\\w)",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a)++(\\w)",[trim]))), + 2}]))), + <<":a:X:">> = iolist_to_binary(join(re:split("aaaaX","^(a)++(\\w)",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a)++(\\w)",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a)++(\\w)",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a)++(\\w)",[]))), - <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(a)++(\\w)",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a)++(\\w)",[]))), + <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(a)++(\\w)",[trim]))), <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(a)++(\\w)",[{parts, - 2}]))), - <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(a)++(\\w)",[]))), - <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(a)++(\\w)",[trim]))), + 2}]))), + <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(a)++(\\w)",[]))), + <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(a)++(\\w)",[trim]))), <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(a)++(\\w)",[{parts, - 2}]))), - <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(a)++(\\w)",[]))), - <<":X">> = iolist_to_binary(join(re:split("aaaaX","^(?:a)++(\\w)",[trim]))), + 2}]))), + <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(a)++(\\w)",[]))), + <<":X">> = iolist_to_binary(join(re:split("aaaaX","^(?:a)++(\\w)",[trim]))), <<":X:">> = iolist_to_binary(join(re:split("aaaaX","^(?:a)++(\\w)",[{parts, - 2}]))), - <<":X:">> = iolist_to_binary(join(re:split("aaaaX","^(?:a)++(\\w)",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:a)++(\\w)",[trim]))), + 2}]))), + <<":X:">> = iolist_to_binary(join(re:split("aaaaX","^(?:a)++(\\w)",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:a)++(\\w)",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:a)++(\\w)",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:a)++(\\w)",[]))), - <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(?:a)++(\\w)",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:a)++(\\w)",[]))), + <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(?:a)++(\\w)",[trim]))), <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(?:a)++(\\w)",[{parts, - 2}]))), - <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(?:a)++(\\w)",[]))), - <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(?:a)++(\\w)",[trim]))), + 2}]))), + <<"aaaa">> = iolist_to_binary(join(re:split("aaaa","^(?:a)++(\\w)",[]))), + <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(?:a)++(\\w)",[trim]))), <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(?:a)++(\\w)",[{parts, - 2}]))), - <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(?:a)++(\\w)",[]))), - <<":a:a:aaX">> = iolist_to_binary(join(re:split("aaaaX","^(a)?+(\\w)",[trim]))), + 2}]))), + <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(?:a)++(\\w)",[]))), + <<":a:a:aaX">> = iolist_to_binary(join(re:split("aaaaX","^(a)?+(\\w)",[trim]))), <<":a:a:aaX">> = iolist_to_binary(join(re:split("aaaaX","^(a)?+(\\w)",[{parts, - 2}]))), - <<":a:a:aaX">> = iolist_to_binary(join(re:split("aaaaX","^(a)?+(\\w)",[]))), - <<"::Y:Z">> = iolist_to_binary(join(re:split("YZ","^(a)?+(\\w)",[trim]))), + 2}]))), + <<":a:a:aaX">> = iolist_to_binary(join(re:split("aaaaX","^(a)?+(\\w)",[]))), + <<"::Y:Z">> = iolist_to_binary(join(re:split("YZ","^(a)?+(\\w)",[trim]))), <<"::Y:Z">> = iolist_to_binary(join(re:split("YZ","^(a)?+(\\w)",[{parts, - 2}]))), - <<"::Y:Z">> = iolist_to_binary(join(re:split("YZ","^(a)?+(\\w)",[]))), - <<":a:aaX">> = iolist_to_binary(join(re:split("aaaaX","^(?:a)?+(\\w)",[trim]))), + 2}]))), + <<"::Y:Z">> = iolist_to_binary(join(re:split("YZ","^(a)?+(\\w)",[]))), + <<":a:aaX">> = iolist_to_binary(join(re:split("aaaaX","^(?:a)?+(\\w)",[trim]))), <<":a:aaX">> = iolist_to_binary(join(re:split("aaaaX","^(?:a)?+(\\w)",[{parts, - 2}]))), - <<":a:aaX">> = iolist_to_binary(join(re:split("aaaaX","^(?:a)?+(\\w)",[]))), - <<":Y:Z">> = iolist_to_binary(join(re:split("YZ","^(?:a)?+(\\w)",[trim]))), + 2}]))), + <<":a:aaX">> = iolist_to_binary(join(re:split("aaaaX","^(?:a)?+(\\w)",[]))), + <<":Y:Z">> = iolist_to_binary(join(re:split("YZ","^(?:a)?+(\\w)",[trim]))), <<":Y:Z">> = iolist_to_binary(join(re:split("YZ","^(?:a)?+(\\w)",[{parts, - 2}]))), - <<":Y:Z">> = iolist_to_binary(join(re:split("YZ","^(?:a)?+(\\w)",[]))), - <<":a:X">> = iolist_to_binary(join(re:split("aaaaX","^(a){2,}+(\\w)",[trim]))), + 2}]))), + <<":Y:Z">> = iolist_to_binary(join(re:split("YZ","^(?:a)?+(\\w)",[]))), + <<":a:X">> = iolist_to_binary(join(re:split("aaaaX","^(a){2,}+(\\w)",[trim]))), <<":a:X:">> = iolist_to_binary(join(re:split("aaaaX","^(a){2,}+(\\w)",[{parts, - 2}]))), - <<":a:X:">> = iolist_to_binary(join(re:split("aaaaX","^(a){2,}+(\\w)",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a){2,}+(\\w)",[trim]))), + 2}]))), + <<":a:X:">> = iolist_to_binary(join(re:split("aaaaX","^(a){2,}+(\\w)",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a){2,}+(\\w)",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a){2,}+(\\w)",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a){2,}+(\\w)",[]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a){2,}+(\\w)",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(a){2,}+(\\w)",[]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a){2,}+(\\w)",[trim]))), <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a){2,}+(\\w)",[{parts, - 2}]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a){2,}+(\\w)",[]))), - <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(a){2,}+(\\w)",[trim]))), + 2}]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(a){2,}+(\\w)",[]))), + <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(a){2,}+(\\w)",[trim]))), <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(a){2,}+(\\w)",[{parts, - 2}]))), - <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(a){2,}+(\\w)",[]))), - <<":X">> = iolist_to_binary(join(re:split("aaaaX","^(?:a){2,}+(\\w)",[trim]))), + 2}]))), + <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(a){2,}+(\\w)",[]))), + <<":X">> = iolist_to_binary(join(re:split("aaaaX","^(?:a){2,}+(\\w)",[trim]))), <<":X:">> = iolist_to_binary(join(re:split("aaaaX","^(?:a){2,}+(\\w)",[{parts, - 2}]))), - <<":X:">> = iolist_to_binary(join(re:split("aaaaX","^(?:a){2,}+(\\w)",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:a){2,}+(\\w)",[trim]))), + 2}]))), + <<":X:">> = iolist_to_binary(join(re:split("aaaaX","^(?:a){2,}+(\\w)",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:a){2,}+(\\w)",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:a){2,}+(\\w)",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:a){2,}+(\\w)",[]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(?:a){2,}+(\\w)",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","^(?:a){2,}+(\\w)",[]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(?:a){2,}+(\\w)",[trim]))), <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(?:a){2,}+(\\w)",[{parts, - 2}]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(?:a){2,}+(\\w)",[]))), - <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(?:a){2,}+(\\w)",[trim]))), + 2}]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","^(?:a){2,}+(\\w)",[]))), + <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(?:a){2,}+(\\w)",[trim]))), <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(?:a){2,}+(\\w)",[{parts, - 2}]))), - <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(?:a){2,}+(\\w)",[]))), - <<"">> = iolist_to_binary(join(re:split("b","(a|)*(?1)b",[trim]))), + 2}]))), + <<"YZ">> = iolist_to_binary(join(re:split("YZ","^(?:a){2,}+(\\w)",[]))), + <<"">> = iolist_to_binary(join(re:split("b","(a|)*(?1)b",[trim]))), <<"::">> = iolist_to_binary(join(re:split("b","(a|)*(?1)b",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("b","(a|)*(?1)b",[]))), - <<"">> = iolist_to_binary(join(re:split("ab","(a|)*(?1)b",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("b","(a|)*(?1)b",[]))), + <<"">> = iolist_to_binary(join(re:split("ab","(a|)*(?1)b",[trim]))), <<"::">> = iolist_to_binary(join(re:split("ab","(a|)*(?1)b",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("ab","(a|)*(?1)b",[]))), - <<"">> = iolist_to_binary(join(re:split("aab","(a|)*(?1)b",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("ab","(a|)*(?1)b",[]))), + <<"">> = iolist_to_binary(join(re:split("aab","(a|)*(?1)b",[trim]))), <<"::">> = iolist_to_binary(join(re:split("aab","(a|)*(?1)b",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("aab","(a|)*(?1)b",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(a)++(?1)b",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("aab","(a|)*(?1)b",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(a)++(?1)b",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(a)++(?1)b",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(a)++(?1)b",[]))), - <<"ab">> = iolist_to_binary(join(re:split("ab","(a)++(?1)b",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(a)++(?1)b",[]))), + <<"ab">> = iolist_to_binary(join(re:split("ab","(a)++(?1)b",[trim]))), <<"ab">> = iolist_to_binary(join(re:split("ab","(a)++(?1)b",[{parts, - 2}]))), - <<"ab">> = iolist_to_binary(join(re:split("ab","(a)++(?1)b",[]))), - <<"aab">> = iolist_to_binary(join(re:split("aab","(a)++(?1)b",[trim]))), + 2}]))), + <<"ab">> = iolist_to_binary(join(re:split("ab","(a)++(?1)b",[]))), + <<"aab">> = iolist_to_binary(join(re:split("aab","(a)++(?1)b",[trim]))), <<"aab">> = iolist_to_binary(join(re:split("aab","(a)++(?1)b",[{parts, - 2}]))), - <<"aab">> = iolist_to_binary(join(re:split("aab","(a)++(?1)b",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(a)*+(?1)b",[trim]))), + 2}]))), + <<"aab">> = iolist_to_binary(join(re:split("aab","(a)++(?1)b",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(a)*+(?1)b",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(a)*+(?1)b",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(a)*+(?1)b",[]))), - <<"ab">> = iolist_to_binary(join(re:split("ab","(a)*+(?1)b",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(a)*+(?1)b",[]))), + <<"ab">> = iolist_to_binary(join(re:split("ab","(a)*+(?1)b",[trim]))), <<"ab">> = iolist_to_binary(join(re:split("ab","(a)*+(?1)b",[{parts, - 2}]))), - <<"ab">> = iolist_to_binary(join(re:split("ab","(a)*+(?1)b",[]))), - <<"aab">> = iolist_to_binary(join(re:split("aab","(a)*+(?1)b",[trim]))), + 2}]))), + <<"ab">> = iolist_to_binary(join(re:split("ab","(a)*+(?1)b",[]))), + <<"aab">> = iolist_to_binary(join(re:split("aab","(a)*+(?1)b",[trim]))), <<"aab">> = iolist_to_binary(join(re:split("aab","(a)*+(?1)b",[{parts, - 2}]))), - <<"aab">> = iolist_to_binary(join(re:split("aab","(a)*+(?1)b",[]))), - <<"">> = iolist_to_binary(join(re:split("b","(?1)(?:(b)){0}",[trim]))), + 2}]))), + <<"aab">> = iolist_to_binary(join(re:split("aab","(a)*+(?1)b",[]))), + <<"">> = iolist_to_binary(join(re:split("b","(?1)(?:(b)){0}",[trim]))), <<"::">> = iolist_to_binary(join(re:split("b","(?1)(?:(b)){0}",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("b","(?1)(?:(b)){0}",[]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("b","(?1)(?:(b)){0}",[]))), <<":foo(bar(baz)+baz(bop)):(bar(baz)+baz(bop)):bar(baz)+baz(bop)">> = iolist_to_binary(join(re:split("foo(bar(baz)+baz(bop))","(foo ( \\( ((?:(?> [^()]+ )|(?2))*) \\) ) )",[extended, - trim]))), + trim]))), <<":foo(bar(baz)+baz(bop)):(bar(baz)+baz(bop)):bar(baz)+baz(bop):">> = iolist_to_binary(join(re:split("foo(bar(baz)+baz(bop))","(foo ( \\( ((?:(?> [^()]+ )|(?2))*) \\) ) )",[extended, {parts, - 2}]))), - <<":foo(bar(baz)+baz(bop)):(bar(baz)+baz(bop)):bar(baz)+baz(bop):">> = iolist_to_binary(join(re:split("foo(bar(baz)+baz(bop))","(foo ( \\( ((?:(?> [^()]+ )|(?2))*) \\) ) )",[extended]))), + 2}]))), + <<":foo(bar(baz)+baz(bop)):(bar(baz)+baz(bop)):bar(baz)+baz(bop):">> = iolist_to_binary(join(re:split("foo(bar(baz)+baz(bop))","(foo ( \\( ((?:(?> [^()]+ )|(?2))*) \\) ) )",[extended]))), <<":AB:B">> = iolist_to_binary(join(re:split("AB","(A (A|B(*ACCEPT)|C) D)(E)",[extended, - trim]))), + trim]))), <<":AB:B::">> = iolist_to_binary(join(re:split("AB","(A (A|B(*ACCEPT)|C) D)(E)",[extended, {parts, - 2}]))), - <<":AB:B::">> = iolist_to_binary(join(re:split("AB","(A (A|B(*ACCEPT)|C) D)(E)",[extended]))), + 2}]))), + <<":AB:B::">> = iolist_to_binary(join(re:split("AB","(A (A|B(*ACCEPT)|C) D)(E)",[extended]))), ok. run46() -> - <<":a">> = iolist_to_binary(join(re:split("ba","\\A.*?(a|bc)",[trim]))), + <<":a">> = iolist_to_binary(join(re:split("ba","\\A.*?(a|bc)",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("ba","\\A.*?(a|bc)",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("ba","\\A.*?(a|bc)",[]))), - <<"">> = iolist_to_binary(join(re:split("ba","\\A.*?(?:a|bc)++",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("ba","\\A.*?(a|bc)",[]))), + <<"">> = iolist_to_binary(join(re:split("ba","\\A.*?(?:a|bc)++",[trim]))), <<":">> = iolist_to_binary(join(re:split("ba","\\A.*?(?:a|bc)++",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ba","\\A.*?(?:a|bc)++",[]))), - <<":a">> = iolist_to_binary(join(re:split("ba","\\A.*?(a|bc)++",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ba","\\A.*?(?:a|bc)++",[]))), + <<":a">> = iolist_to_binary(join(re:split("ba","\\A.*?(a|bc)++",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("ba","\\A.*?(a|bc)++",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("ba","\\A.*?(a|bc)++",[]))), - <<"">> = iolist_to_binary(join(re:split("ba","\\A.*?(?:a|bc|d)",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("ba","\\A.*?(a|bc)++",[]))), + <<"">> = iolist_to_binary(join(re:split("ba","\\A.*?(?:a|bc|d)",[trim]))), <<":">> = iolist_to_binary(join(re:split("ba","\\A.*?(?:a|bc|d)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ba","\\A.*?(?:a|bc|d)",[]))), - <<":b:eetle">> = iolist_to_binary(join(re:split("beetle","(?:(b))++",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ba","\\A.*?(?:a|bc|d)",[]))), + <<":b:eetle">> = iolist_to_binary(join(re:split("beetle","(?:(b))++",[trim]))), <<":b:eetle">> = iolist_to_binary(join(re:split("beetle","(?:(b))++",[{parts, - 2}]))), - <<":b:eetle">> = iolist_to_binary(join(re:split("beetle","(?:(b))++",[]))), - <<":a">> = iolist_to_binary(join(re:split("a","(?(?=(a(*ACCEPT)z))a)",[trim]))), + 2}]))), + <<":b:eetle">> = iolist_to_binary(join(re:split("beetle","(?:(b))++",[]))), + <<":a">> = iolist_to_binary(join(re:split("a","(?(?=(a(*ACCEPT)z))a)",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("a","(?(?=(a(*ACCEPT)z))a)",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("a","(?(?=(a(*ACCEPT)z))a)",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaaab","^(a)(?1)+ab",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("a","(?(?=(a(*ACCEPT)z))a)",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaaab","^(a)(?1)+ab",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aaaab","^(a)(?1)+ab",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aaaab","^(a)(?1)+ab",[]))), - <<"aaaab">> = iolist_to_binary(join(re:split("aaaab","^(a)(?1)++ab",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aaaab","^(a)(?1)+ab",[]))), + <<"aaaab">> = iolist_to_binary(join(re:split("aaaab","^(a)(?1)++ab",[trim]))), <<"aaaab">> = iolist_to_binary(join(re:split("aaaab","^(a)(?1)++ab",[{parts, - 2}]))), - <<"aaaab">> = iolist_to_binary(join(re:split("aaaab","^(a)(?1)++ab",[]))), - <<"::ckgammon">> = iolist_to_binary(join(re:split("backgammon","(?(DEFINE)(a))?b(?1)",[trim]))), + 2}]))), + <<"aaaab">> = iolist_to_binary(join(re:split("aaaab","^(a)(?1)++ab",[]))), + <<"::ckgammon">> = iolist_to_binary(join(re:split("backgammon","(?(DEFINE)(a))?b(?1)",[trim]))), <<"::ckgammon">> = iolist_to_binary(join(re:split("backgammon","(?(DEFINE)(a))?b(?1)",[{parts, - 2}]))), - <<"::ckgammon">> = iolist_to_binary(join(re:split("backgammon","(?(DEFINE)(a))?b(?1)",[]))), + 2}]))), + <<"::ckgammon">> = iolist_to_binary(join(re:split("backgammon","(?(DEFINE)(a))?b(?1)",[]))), <<": def">> = iolist_to_binary(join(re:split("abc -def","^\\N+",[trim]))), +def","^\\N+",[trim]))), <<": def">> = iolist_to_binary(join(re:split("abc -def","^\\N+",[{parts,2}]))), +def","^\\N+",[{parts,2}]))), <<": def">> = iolist_to_binary(join(re:split("abc -def","^\\N+",[]))), +def","^\\N+",[]))), <<": def">> = iolist_to_binary(join(re:split("abc -def","^\\N{1,}",[trim]))), +def","^\\N{1,}",[trim]))), <<": def">> = iolist_to_binary(join(re:split("abc -def","^\\N{1,}",[{parts,2}]))), +def","^\\N{1,}",[{parts,2}]))), <<": def">> = iolist_to_binary(join(re:split("abc -def","^\\N{1,}",[]))), - <<":cde">> = iolist_to_binary(join(re:split("aaaabcde","(?(R)a+|(?R)b)",[trim]))), +def","^\\N{1,}",[]))), + <<":cde">> = iolist_to_binary(join(re:split("aaaabcde","(?(R)a+|(?R)b)",[trim]))), <<":cde">> = iolist_to_binary(join(re:split("aaaabcde","(?(R)a+|(?R)b)",[{parts, - 2}]))), - <<":cde">> = iolist_to_binary(join(re:split("aaaabcde","(?(R)a+|(?R)b)",[]))), - <<":aaaa:cde">> = iolist_to_binary(join(re:split("aaaabcde","(?(R)a+|((?R))b)",[trim]))), + 2}]))), + <<":cde">> = iolist_to_binary(join(re:split("aaaabcde","(?(R)a+|(?R)b)",[]))), + <<":aaaa:cde">> = iolist_to_binary(join(re:split("aaaabcde","(?(R)a+|((?R))b)",[trim]))), <<":aaaa:cde">> = iolist_to_binary(join(re:split("aaaabcde","(?(R)a+|((?R))b)",[{parts, - 2}]))), - <<":aaaa:cde">> = iolist_to_binary(join(re:split("aaaabcde","(?(R)a+|((?R))b)",[]))), - <<":aaaab:cde">> = iolist_to_binary(join(re:split("aaaabcde","((?(R)a+|(?1)b))",[trim]))), + 2}]))), + <<":aaaa:cde">> = iolist_to_binary(join(re:split("aaaabcde","(?(R)a+|((?R))b)",[]))), + <<":aaaab:cde">> = iolist_to_binary(join(re:split("aaaabcde","((?(R)a+|(?1)b))",[trim]))), <<":aaaab:cde">> = iolist_to_binary(join(re:split("aaaabcde","((?(R)a+|(?1)b))",[{parts, - 2}]))), - <<":aaaab:cde">> = iolist_to_binary(join(re:split("aaaabcde","((?(R)a+|(?1)b))",[]))), - <<":aaaab:cde">> = iolist_to_binary(join(re:split("aaaabcde","((?(R1)a+|(?1)b))",[trim]))), + 2}]))), + <<":aaaab:cde">> = iolist_to_binary(join(re:split("aaaabcde","((?(R)a+|(?1)b))",[]))), + <<":aaaab:cde">> = iolist_to_binary(join(re:split("aaaabcde","((?(R1)a+|(?1)b))",[trim]))), <<":aaaab:cde">> = iolist_to_binary(join(re:split("aaaabcde","((?(R1)a+|(?1)b))",[{parts, - 2}]))), - <<":aaaab:cde">> = iolist_to_binary(join(re:split("aaaabcde","((?(R1)a+|(?1)b))",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaa","((?(R)a|(?1)))*",[trim]))), + 2}]))), + <<":aaaab:cde">> = iolist_to_binary(join(re:split("aaaabcde","((?(R1)a+|(?1)b))",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaa","((?(R)a|(?1)))*",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aaa","((?(R)a|(?1)))*",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aaa","((?(R)a|(?1)))*",[]))), - <<":a">> = iolist_to_binary(join(re:split("aaa","((?(R)a|(?1)))+",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aaa","((?(R)a|(?1)))*",[]))), + <<":a">> = iolist_to_binary(join(re:split("aaa","((?(R)a|(?1)))+",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("aaa","((?(R)a|(?1)))+",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("aaa","((?(R)a|(?1)))+",[]))), - <<"">> = iolist_to_binary(join(re:split("a","(?>(?&t)c|(?&t))(?(DEFINE)(?<t>a|b(*PRUNE)c))",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("aaa","((?(R)a|(?1)))+",[]))), + <<"">> = iolist_to_binary(join(re:split("a","(?>(?&t)c|(?&t))(?(DEFINE)(?<t>a|b(*PRUNE)c))",[trim]))), <<"::">> = iolist_to_binary(join(re:split("a","(?>(?&t)c|(?&t))(?(DEFINE)(?<t>a|b(*PRUNE)c))",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("a","(?>(?&t)c|(?&t))(?(DEFINE)(?<t>a|b(*PRUNE)c))",[]))), - <<"b">> = iolist_to_binary(join(re:split("ba","(?>(?&t)c|(?&t))(?(DEFINE)(?<t>a|b(*PRUNE)c))",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("a","(?>(?&t)c|(?&t))(?(DEFINE)(?<t>a|b(*PRUNE)c))",[]))), + <<"b">> = iolist_to_binary(join(re:split("ba","(?>(?&t)c|(?&t))(?(DEFINE)(?<t>a|b(*PRUNE)c))",[trim]))), <<"b::">> = iolist_to_binary(join(re:split("ba","(?>(?&t)c|(?&t))(?(DEFINE)(?<t>a|b(*PRUNE)c))",[{parts, - 2}]))), - <<"b::">> = iolist_to_binary(join(re:split("ba","(?>(?&t)c|(?&t))(?(DEFINE)(?<t>a|b(*PRUNE)c))",[]))), - <<"bb">> = iolist_to_binary(join(re:split("bba","(?>(?&t)c|(?&t))(?(DEFINE)(?<t>a|b(*PRUNE)c))",[trim]))), + 2}]))), + <<"b::">> = iolist_to_binary(join(re:split("ba","(?>(?&t)c|(?&t))(?(DEFINE)(?<t>a|b(*PRUNE)c))",[]))), + <<"bb">> = iolist_to_binary(join(re:split("bba","(?>(?&t)c|(?&t))(?(DEFINE)(?<t>a|b(*PRUNE)c))",[trim]))), <<"bb::">> = iolist_to_binary(join(re:split("bba","(?>(?&t)c|(?&t))(?(DEFINE)(?<t>a|b(*PRUNE)c))",[{parts, - 2}]))), - <<"bb::">> = iolist_to_binary(join(re:split("bba","(?>(?&t)c|(?&t))(?(DEFINE)(?<t>a|b(*PRUNE)c))",[]))), + 2}]))), + <<"bb::">> = iolist_to_binary(join(re:split("bba","(?>(?&t)c|(?&t))(?(DEFINE)(?<t>a|b(*PRUNE)c))",[]))), ok. run47() -> <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (a(*THEN)b) c",[extended, - trim]))), + trim]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (a(*THEN)b) c",[extended, {parts, - 2}]))), - <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (a(*THEN)b) c",[extended]))), + 2}]))), + <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (a(*THEN)b) c",[extended]))), <<":ab">> = iolist_to_binary(join(re:split("aabc","^.*? (a(*THEN)b|(*F)) c",[extended, - trim]))), + trim]))), <<":ab:">> = iolist_to_binary(join(re:split("aabc","^.*? (a(*THEN)b|(*F)) c",[extended, {parts, - 2}]))), - <<":ab:">> = iolist_to_binary(join(re:split("aabc","^.*? (a(*THEN)b|(*F)) c",[extended]))), + 2}]))), + <<":ab:">> = iolist_to_binary(join(re:split("aabc","^.*? (a(*THEN)b|(*F)) c",[extended]))), <<":ab:ab">> = iolist_to_binary(join(re:split("aabc","^.*? ( (a(*THEN)b) | (*F) ) c",[extended, - trim]))), + trim]))), <<":ab:ab:">> = iolist_to_binary(join(re:split("aabc","^.*? ( (a(*THEN)b) | (*F) ) c",[extended, {parts, - 2}]))), - <<":ab:ab:">> = iolist_to_binary(join(re:split("aabc","^.*? ( (a(*THEN)b) | (*F) ) c",[extended]))), + 2}]))), + <<":ab:ab:">> = iolist_to_binary(join(re:split("aabc","^.*? ( (a(*THEN)b) | (*F) ) c",[extended]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? ( (a(*THEN)b) ) c",[extended, - trim]))), + trim]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? ( (a(*THEN)b) ) c",[extended, {parts, - 2}]))), - <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? ( (a(*THEN)b) ) c",[extended]))), + 2}]))), + <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? ( (a(*THEN)b) ) c",[extended]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?:a(*THEN)b) c",[extended, - trim]))), + trim]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?:a(*THEN)b) c",[extended, {parts, - 2}]))), - <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?:a(*THEN)b) c",[extended]))), + 2}]))), + <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?:a(*THEN)b) c",[extended]))), <<"">> = iolist_to_binary(join(re:split("aabc","^.*? (?:a(*THEN)b|(*F)) c",[extended, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("aabc","^.*? (?:a(*THEN)b|(*F)) c",[extended, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aabc","^.*? (?:a(*THEN)b|(*F)) c",[extended]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aabc","^.*? (?:a(*THEN)b|(*F)) c",[extended]))), <<"">> = iolist_to_binary(join(re:split("aabc","^.*? (?: (?:a(*THEN)b) | (*F) ) c",[extended, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("aabc","^.*? (?: (?:a(*THEN)b) | (*F) ) c",[extended, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aabc","^.*? (?: (?:a(*THEN)b) | (*F) ) c",[extended]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aabc","^.*? (?: (?:a(*THEN)b) | (*F) ) c",[extended]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?: (?:a(*THEN)b) ) c",[extended, - trim]))), + trim]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?: (?:a(*THEN)b) ) c",[extended, {parts, - 2}]))), - <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?: (?:a(*THEN)b) ) c",[extended]))), + 2}]))), + <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?: (?:a(*THEN)b) ) c",[extended]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?>a(*THEN)b) c",[extended, - trim]))), + trim]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?>a(*THEN)b) c",[extended, {parts, - 2}]))), - <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?>a(*THEN)b) c",[extended]))), + 2}]))), + <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?>a(*THEN)b) c",[extended]))), <<"">> = iolist_to_binary(join(re:split("aabc","^.*? (?>a(*THEN)b|(*F)) c",[extended, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("aabc","^.*? (?>a(*THEN)b|(*F)) c",[extended, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aabc","^.*? (?>a(*THEN)b|(*F)) c",[extended]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aabc","^.*? (?>a(*THEN)b|(*F)) c",[extended]))), <<"">> = iolist_to_binary(join(re:split("aabc","^.*? (?> (?>a(*THEN)b) | (*F) ) c",[extended, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("aabc","^.*? (?> (?>a(*THEN)b) | (*F) ) c",[extended, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aabc","^.*? (?> (?>a(*THEN)b) | (*F) ) c",[extended]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aabc","^.*? (?> (?>a(*THEN)b) | (*F) ) c",[extended]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?> (?>a(*THEN)b) ) c",[extended, - trim]))), + trim]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?> (?>a(*THEN)b) ) c",[extended, {parts, - 2}]))), - <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?> (?>a(*THEN)b) ) c",[extended]))), + 2}]))), + <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?> (?>a(*THEN)b) ) c",[extended]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (a(*THEN)b)++ c",[extended, - trim]))), + trim]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (a(*THEN)b)++ c",[extended, {parts, - 2}]))), - <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (a(*THEN)b)++ c",[extended]))), + 2}]))), + <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (a(*THEN)b)++ c",[extended]))), <<":ab">> = iolist_to_binary(join(re:split("aabc","^.*? (a(*THEN)b|(*F))++ c",[extended, - trim]))), + trim]))), <<":ab:">> = iolist_to_binary(join(re:split("aabc","^.*? (a(*THEN)b|(*F))++ c",[extended, {parts, - 2}]))), - <<":ab:">> = iolist_to_binary(join(re:split("aabc","^.*? (a(*THEN)b|(*F))++ c",[extended]))), + 2}]))), + <<":ab:">> = iolist_to_binary(join(re:split("aabc","^.*? (a(*THEN)b|(*F))++ c",[extended]))), <<":ab:ab">> = iolist_to_binary(join(re:split("aabc","^.*? ( (a(*THEN)b)++ | (*F) )++ c",[extended, - trim]))), + trim]))), <<":ab:ab:">> = iolist_to_binary(join(re:split("aabc","^.*? ( (a(*THEN)b)++ | (*F) )++ c",[extended, {parts, - 2}]))), - <<":ab:ab:">> = iolist_to_binary(join(re:split("aabc","^.*? ( (a(*THEN)b)++ | (*F) )++ c",[extended]))), + 2}]))), + <<":ab:ab:">> = iolist_to_binary(join(re:split("aabc","^.*? ( (a(*THEN)b)++ | (*F) )++ c",[extended]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? ( (a(*THEN)b)++ )++ c",[extended, - trim]))), + trim]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? ( (a(*THEN)b)++ )++ c",[extended, {parts, - 2}]))), - <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? ( (a(*THEN)b)++ )++ c",[extended]))), + 2}]))), + <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? ( (a(*THEN)b)++ )++ c",[extended]))), ok. run48() -> <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?:a(*THEN)b)++ c",[extended, - trim]))), + trim]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?:a(*THEN)b)++ c",[extended, {parts, - 2}]))), - <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?:a(*THEN)b)++ c",[extended]))), + 2}]))), + <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?:a(*THEN)b)++ c",[extended]))), <<"">> = iolist_to_binary(join(re:split("aabc","^.*? (?:a(*THEN)b|(*F))++ c",[extended, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("aabc","^.*? (?:a(*THEN)b|(*F))++ c",[extended, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aabc","^.*? (?:a(*THEN)b|(*F))++ c",[extended]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aabc","^.*? (?:a(*THEN)b|(*F))++ c",[extended]))), <<"">> = iolist_to_binary(join(re:split("aabc","^.*? (?: (?:a(*THEN)b)++ | (*F) )++ c",[extended, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("aabc","^.*? (?: (?:a(*THEN)b)++ | (*F) )++ c",[extended, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aabc","^.*? (?: (?:a(*THEN)b)++ | (*F) )++ c",[extended]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aabc","^.*? (?: (?:a(*THEN)b)++ | (*F) )++ c",[extended]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?: (?:a(*THEN)b)++ )++ c",[extended, - trim]))), + trim]))), <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?: (?:a(*THEN)b)++ )++ c",[extended, {parts, - 2}]))), - <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?: (?:a(*THEN)b)++ )++ c",[extended]))), - <<"">> = iolist_to_binary(join(re:split("ac","^(?(?=a(*THEN)b)ab|ac)",[trim]))), + 2}]))), + <<"aabc">> = iolist_to_binary(join(re:split("aabc","^.*? (?: (?:a(*THEN)b)++ )++ c",[extended]))), + <<"">> = iolist_to_binary(join(re:split("ac","^(?(?=a(*THEN)b)ab|ac)",[trim]))), <<":">> = iolist_to_binary(join(re:split("ac","^(?(?=a(*THEN)b)ab|ac)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ac","^(?(?=a(*THEN)b)ab|ac)",[]))), - <<"ba">> = iolist_to_binary(join(re:split("ba","^.*?(?(?=a)a|b(*THEN)c)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ac","^(?(?=a(*THEN)b)ab|ac)",[]))), + <<"ba">> = iolist_to_binary(join(re:split("ba","^.*?(?(?=a)a|b(*THEN)c)",[trim]))), <<"ba">> = iolist_to_binary(join(re:split("ba","^.*?(?(?=a)a|b(*THEN)c)",[{parts, - 2}]))), - <<"ba">> = iolist_to_binary(join(re:split("ba","^.*?(?(?=a)a|b(*THEN)c)",[]))), - <<"">> = iolist_to_binary(join(re:split("ba","^.*?(?:(?(?=a)a|b(*THEN)c)|d)",[trim]))), + 2}]))), + <<"ba">> = iolist_to_binary(join(re:split("ba","^.*?(?(?=a)a|b(*THEN)c)",[]))), + <<"">> = iolist_to_binary(join(re:split("ba","^.*?(?:(?(?=a)a|b(*THEN)c)|d)",[trim]))), <<":">> = iolist_to_binary(join(re:split("ba","^.*?(?:(?(?=a)a|b(*THEN)c)|d)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ba","^.*?(?:(?(?=a)a|b(*THEN)c)|d)",[]))), - <<"ac">> = iolist_to_binary(join(re:split("ac","^.*?(?(?=a)a(*THEN)b|c)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ba","^.*?(?:(?(?=a)a|b(*THEN)c)|d)",[]))), + <<"ac">> = iolist_to_binary(join(re:split("ac","^.*?(?(?=a)a(*THEN)b|c)",[trim]))), <<"ac">> = iolist_to_binary(join(re:split("ac","^.*?(?(?=a)a(*THEN)b|c)",[{parts, - 2}]))), - <<"ac">> = iolist_to_binary(join(re:split("ac","^.*?(?(?=a)a(*THEN)b|c)",[]))), - <<":abc">> = iolist_to_binary(join(re:split("aabc","^.*(?=a(*THEN)b)",[trim]))), + 2}]))), + <<"ac">> = iolist_to_binary(join(re:split("ac","^.*?(?(?=a)a(*THEN)b|c)",[]))), + <<":abc">> = iolist_to_binary(join(re:split("aabc","^.*(?=a(*THEN)b)",[trim]))), <<":abc">> = iolist_to_binary(join(re:split("aabc","^.*(?=a(*THEN)b)",[{parts, - 2}]))), - <<":abc">> = iolist_to_binary(join(re:split("aabc","^.*(?=a(*THEN)b)",[]))), - <<"xa:d">> = iolist_to_binary(join(re:split("xacd","(?<=a(*ACCEPT)b)c",[trim]))), + 2}]))), + <<":abc">> = iolist_to_binary(join(re:split("aabc","^.*(?=a(*THEN)b)",[]))), + <<"xa:d">> = iolist_to_binary(join(re:split("xacd","(?<=a(*ACCEPT)b)c",[trim]))), <<"xa:d">> = iolist_to_binary(join(re:split("xacd","(?<=a(*ACCEPT)b)c",[{parts, - 2}]))), - <<"xa:d">> = iolist_to_binary(join(re:split("xacd","(?<=a(*ACCEPT)b)c",[]))), - <<"xa:a:d">> = iolist_to_binary(join(re:split("xacd","(?<=(a(*ACCEPT)b))c",[trim]))), + 2}]))), + <<"xa:d">> = iolist_to_binary(join(re:split("xacd","(?<=a(*ACCEPT)b)c",[]))), + <<"xa:a:d">> = iolist_to_binary(join(re:split("xacd","(?<=(a(*ACCEPT)b))c",[trim]))), <<"xa:a:d">> = iolist_to_binary(join(re:split("xacd","(?<=(a(*ACCEPT)b))c",[{parts, - 2}]))), - <<"xa:a:d">> = iolist_to_binary(join(re:split("xacd","(?<=(a(*ACCEPT)b))c",[]))), - <<"xab:ab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=(a(*COMMIT)b))c",[trim]))), + 2}]))), + <<"xa:a:d">> = iolist_to_binary(join(re:split("xacd","(?<=(a(*ACCEPT)b))c",[]))), + <<"xab:ab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=(a(*COMMIT)b))c",[trim]))), <<"xab:ab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=(a(*COMMIT)b))c",[{parts, - 2}]))), - <<"xab:ab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=(a(*COMMIT)b))c",[]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=(a(*COMMIT)b))c",[trim]))), + 2}]))), + <<"xab:ab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=(a(*COMMIT)b))c",[]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=(a(*COMMIT)b))c",[trim]))), <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=(a(*COMMIT)b))c",[{parts, - 2}]))), - <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=(a(*COMMIT)b))c",[]))), - <<"xacd">> = iolist_to_binary(join(re:split("xacd","(?<=(a(*COMMIT)b))c",[trim]))), + 2}]))), + <<"** Failers">> = iolist_to_binary(join(re:split("** Failers","(?<=(a(*COMMIT)b))c",[]))), + <<"xacd">> = iolist_to_binary(join(re:split("xacd","(?<=(a(*COMMIT)b))c",[trim]))), <<"xacd">> = iolist_to_binary(join(re:split("xacd","(?<=(a(*COMMIT)b))c",[{parts, - 2}]))), - <<"xacd">> = iolist_to_binary(join(re:split("xacd","(?<=(a(*COMMIT)b))c",[]))), - <<"x:d">> = iolist_to_binary(join(re:split("xcd","(?<!a(*FAIL)b)c",[trim]))), + 2}]))), + <<"xacd">> = iolist_to_binary(join(re:split("xacd","(?<=(a(*COMMIT)b))c",[]))), + <<"x:d">> = iolist_to_binary(join(re:split("xcd","(?<!a(*FAIL)b)c",[trim]))), <<"x:d">> = iolist_to_binary(join(re:split("xcd","(?<!a(*FAIL)b)c",[{parts, - 2}]))), - <<"x:d">> = iolist_to_binary(join(re:split("xcd","(?<!a(*FAIL)b)c",[]))), - <<"a:d">> = iolist_to_binary(join(re:split("acd","(?<!a(*FAIL)b)c",[trim]))), + 2}]))), + <<"x:d">> = iolist_to_binary(join(re:split("xcd","(?<!a(*FAIL)b)c",[]))), + <<"a:d">> = iolist_to_binary(join(re:split("acd","(?<!a(*FAIL)b)c",[trim]))), <<"a:d">> = iolist_to_binary(join(re:split("acd","(?<!a(*FAIL)b)c",[{parts, - 2}]))), - <<"a:d">> = iolist_to_binary(join(re:split("acd","(?<!a(*FAIL)b)c",[]))), - <<"xab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=a(*PRUNE)b)c",[trim]))), + 2}]))), + <<"a:d">> = iolist_to_binary(join(re:split("acd","(?<!a(*FAIL)b)c",[]))), + <<"xab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=a(*PRUNE)b)c",[trim]))), <<"xab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=a(*PRUNE)b)c",[{parts, - 2}]))), - <<"xab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=a(*PRUNE)b)c",[]))), - <<"xab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=a(*SKIP)b)c",[trim]))), + 2}]))), + <<"xab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=a(*PRUNE)b)c",[]))), + <<"xab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=a(*SKIP)b)c",[trim]))), <<"xab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=a(*SKIP)b)c",[{parts, - 2}]))), - <<"xab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=a(*SKIP)b)c",[]))), - <<"xab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=a(*THEN)b)c",[trim]))), + 2}]))), + <<"xab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=a(*SKIP)b)c",[]))), + <<"xab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=a(*THEN)b)c",[trim]))), <<"xab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=a(*THEN)b)c",[{parts, - 2}]))), - <<"xab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=a(*THEN)b)c",[]))), + 2}]))), + <<"xab:d">> = iolist_to_binary(join(re:split("xabcd","(?<=a(*THEN)b)c",[]))), ok. run49() -> - <<":a:d">> = iolist_to_binary(join(re:split("abcd","(a)(?2){2}(.)",[trim]))), + <<":a:d">> = iolist_to_binary(join(re:split("abcd","(a)(?2){2}(.)",[trim]))), <<":a:d:">> = iolist_to_binary(join(re:split("abcd","(a)(?2){2}(.)",[{parts, - 2}]))), - <<":a:d:">> = iolist_to_binary(join(re:split("abcd","(a)(?2){2}(.)",[]))), - <<"hello world ">> = iolist_to_binary(join(re:split("hello world test","(another)?(\\1?)test",[trim]))), + 2}]))), + <<":a:d:">> = iolist_to_binary(join(re:split("abcd","(a)(?2){2}(.)",[]))), + <<"hello world ">> = iolist_to_binary(join(re:split("hello world test","(another)?(\\1?)test",[trim]))), <<"hello world :::">> = iolist_to_binary(join(re:split("hello world test","(another)?(\\1?)test",[{parts, - 2}]))), - <<"hello world :::">> = iolist_to_binary(join(re:split("hello world test","(another)?(\\1?)test",[]))), - <<"hello world test">> = iolist_to_binary(join(re:split("hello world test","(another)?(\\1+)test",[trim]))), + 2}]))), + <<"hello world :::">> = iolist_to_binary(join(re:split("hello world test","(another)?(\\1?)test",[]))), + <<"hello world test">> = iolist_to_binary(join(re:split("hello world test","(another)?(\\1+)test",[trim]))), <<"hello world test">> = iolist_to_binary(join(re:split("hello world test","(another)?(\\1+)test",[{parts, - 2}]))), - <<"hello world test">> = iolist_to_binary(join(re:split("hello world test","(another)?(\\1+)test",[]))), - <<"">> = iolist_to_binary(join(re:split("aac","(a(*COMMIT)b){0}a(?1)|aac",[trim]))), + 2}]))), + <<"hello world test">> = iolist_to_binary(join(re:split("hello world test","(another)?(\\1+)test",[]))), + <<"">> = iolist_to_binary(join(re:split("aac","(a(*COMMIT)b){0}a(?1)|aac",[trim]))), <<"::">> = iolist_to_binary(join(re:split("aac","(a(*COMMIT)b){0}a(?1)|aac",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("aac","(a(*COMMIT)b){0}a(?1)|aac",[]))), - <<"">> = iolist_to_binary(join(re:split("aac","((?:a?)*)*c",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("aac","(a(*COMMIT)b){0}a(?1)|aac",[]))), + <<"">> = iolist_to_binary(join(re:split("aac","((?:a?)*)*c",[trim]))), <<"::">> = iolist_to_binary(join(re:split("aac","((?:a?)*)*c",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("aac","((?:a?)*)*c",[]))), - <<"">> = iolist_to_binary(join(re:split("aac","((?>a?)*)*c",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("aac","((?:a?)*)*c",[]))), + <<"">> = iolist_to_binary(join(re:split("aac","((?>a?)*)*c",[trim]))), <<"::">> = iolist_to_binary(join(re:split("aac","((?>a?)*)*c",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("aac","((?>a?)*)*c",[]))), - <<"a">> = iolist_to_binary(join(re:split("aba","(?>.*?a)(?<=ba)",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("aac","((?>a?)*)*c",[]))), + <<"a">> = iolist_to_binary(join(re:split("aba","(?>.*?a)(?<=ba)",[trim]))), <<"a:">> = iolist_to_binary(join(re:split("aba","(?>.*?a)(?<=ba)",[{parts, - 2}]))), - <<"a:">> = iolist_to_binary(join(re:split("aba","(?>.*?a)(?<=ba)",[]))), - <<"">> = iolist_to_binary(join(re:split("aba","(?:.*?a)(?<=ba)",[trim]))), + 2}]))), + <<"a:">> = iolist_to_binary(join(re:split("aba","(?>.*?a)(?<=ba)",[]))), + <<"">> = iolist_to_binary(join(re:split("aba","(?:.*?a)(?<=ba)",[trim]))), <<":">> = iolist_to_binary(join(re:split("aba","(?:.*?a)(?<=ba)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aba","(?:.*?a)(?<=ba)",[]))), - <<"a">> = iolist_to_binary(join(re:split("aab",".*?a(*PRUNE)b",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aba","(?:.*?a)(?<=ba)",[]))), + <<"a">> = iolist_to_binary(join(re:split("aab",".*?a(*PRUNE)b",[trim]))), <<"a:">> = iolist_to_binary(join(re:split("aab",".*?a(*PRUNE)b",[{parts, - 2}]))), - <<"a:">> = iolist_to_binary(join(re:split("aab",".*?a(*PRUNE)b",[]))), + 2}]))), + <<"a:">> = iolist_to_binary(join(re:split("aab",".*?a(*PRUNE)b",[]))), <<"a">> = iolist_to_binary(join(re:split("aab",".*?a(*PRUNE)b",[dotall, - trim]))), + trim]))), <<"a:">> = iolist_to_binary(join(re:split("aab",".*?a(*PRUNE)b",[dotall, {parts, - 2}]))), - <<"a:">> = iolist_to_binary(join(re:split("aab",".*?a(*PRUNE)b",[dotall]))), + 2}]))), + <<"a:">> = iolist_to_binary(join(re:split("aab",".*?a(*PRUNE)b",[dotall]))), <<"aab">> = iolist_to_binary(join(re:split("aab","^a(*PRUNE)b",[dotall, - trim]))), + trim]))), <<"aab">> = iolist_to_binary(join(re:split("aab","^a(*PRUNE)b",[dotall, {parts, - 2}]))), - <<"aab">> = iolist_to_binary(join(re:split("aab","^a(*PRUNE)b",[dotall]))), - <<"a">> = iolist_to_binary(join(re:split("aab",".*?a(*SKIP)b",[trim]))), + 2}]))), + <<"aab">> = iolist_to_binary(join(re:split("aab","^a(*PRUNE)b",[dotall]))), + <<"a">> = iolist_to_binary(join(re:split("aab",".*?a(*SKIP)b",[trim]))), <<"a:">> = iolist_to_binary(join(re:split("aab",".*?a(*SKIP)b",[{parts, - 2}]))), - <<"a:">> = iolist_to_binary(join(re:split("aab",".*?a(*SKIP)b",[]))), + 2}]))), + <<"a:">> = iolist_to_binary(join(re:split("aab",".*?a(*SKIP)b",[]))), <<"a">> = iolist_to_binary(join(re:split("aab","(?>.*?a)b",[dotall, - trim]))), + trim]))), <<"a:">> = iolist_to_binary(join(re:split("aab","(?>.*?a)b",[dotall, {parts, - 2}]))), - <<"a:">> = iolist_to_binary(join(re:split("aab","(?>.*?a)b",[dotall]))), + 2}]))), + <<"a:">> = iolist_to_binary(join(re:split("aab","(?>.*?a)b",[dotall]))), ok. run50() -> - <<"a">> = iolist_to_binary(join(re:split("aab","(?>.*?a)b",[trim]))), + <<"a">> = iolist_to_binary(join(re:split("aab","(?>.*?a)b",[trim]))), <<"a:">> = iolist_to_binary(join(re:split("aab","(?>.*?a)b",[{parts, - 2}]))), - <<"a:">> = iolist_to_binary(join(re:split("aab","(?>.*?a)b",[]))), + 2}]))), + <<"a:">> = iolist_to_binary(join(re:split("aab","(?>.*?a)b",[]))), <<"aab">> = iolist_to_binary(join(re:split("aab","(?>^a)b",[dotall, - trim]))), + trim]))), <<"aab">> = iolist_to_binary(join(re:split("aab","(?>^a)b",[dotall, {parts, - 2}]))), - <<"aab">> = iolist_to_binary(join(re:split("aab","(?>^a)b",[dotall]))), - <<"alphabetabcd:abcd">> = iolist_to_binary(join(re:split("alphabetabcd","(?>.*?)(?<=(abcd)|(wxyz))",[trim]))), + 2}]))), + <<"aab">> = iolist_to_binary(join(re:split("aab","(?>^a)b",[dotall]))), + <<"alphabetabcd:abcd">> = iolist_to_binary(join(re:split("alphabetabcd","(?>.*?)(?<=(abcd)|(wxyz))",[trim]))), <<"alphabetabcd:abcd::">> = iolist_to_binary(join(re:split("alphabetabcd","(?>.*?)(?<=(abcd)|(wxyz))",[{parts, - 2}]))), - <<"alphabetabcd:abcd::">> = iolist_to_binary(join(re:split("alphabetabcd","(?>.*?)(?<=(abcd)|(wxyz))",[]))), - <<"endingwxyz::wxyz">> = iolist_to_binary(join(re:split("endingwxyz","(?>.*?)(?<=(abcd)|(wxyz))",[trim]))), + 2}]))), + <<"alphabetabcd:abcd::">> = iolist_to_binary(join(re:split("alphabetabcd","(?>.*?)(?<=(abcd)|(wxyz))",[]))), + <<"endingwxyz::wxyz">> = iolist_to_binary(join(re:split("endingwxyz","(?>.*?)(?<=(abcd)|(wxyz))",[trim]))), <<"endingwxyz::wxyz:">> = iolist_to_binary(join(re:split("endingwxyz","(?>.*?)(?<=(abcd)|(wxyz))",[{parts, - 2}]))), - <<"endingwxyz::wxyz:">> = iolist_to_binary(join(re:split("endingwxyz","(?>.*?)(?<=(abcd)|(wxyz))",[]))), - <<":abcd">> = iolist_to_binary(join(re:split("alphabetabcd","(?>.*)(?<=(abcd)|(wxyz))",[trim]))), + 2}]))), + <<"endingwxyz::wxyz:">> = iolist_to_binary(join(re:split("endingwxyz","(?>.*?)(?<=(abcd)|(wxyz))",[]))), + <<":abcd">> = iolist_to_binary(join(re:split("alphabetabcd","(?>.*)(?<=(abcd)|(wxyz))",[trim]))), <<":abcd::">> = iolist_to_binary(join(re:split("alphabetabcd","(?>.*)(?<=(abcd)|(wxyz))",[{parts, - 2}]))), - <<":abcd::">> = iolist_to_binary(join(re:split("alphabetabcd","(?>.*)(?<=(abcd)|(wxyz))",[]))), - <<"::wxyz">> = iolist_to_binary(join(re:split("endingwxyz","(?>.*)(?<=(abcd)|(wxyz))",[trim]))), + 2}]))), + <<":abcd::">> = iolist_to_binary(join(re:split("alphabetabcd","(?>.*)(?<=(abcd)|(wxyz))",[]))), + <<"::wxyz">> = iolist_to_binary(join(re:split("endingwxyz","(?>.*)(?<=(abcd)|(wxyz))",[trim]))), <<"::wxyz:">> = iolist_to_binary(join(re:split("endingwxyz","(?>.*)(?<=(abcd)|(wxyz))",[{parts, - 2}]))), - <<"::wxyz:">> = iolist_to_binary(join(re:split("endingwxyz","(?>.*)(?<=(abcd)|(wxyz))",[]))), - <<"abcdfooxyz">> = iolist_to_binary(join(re:split("abcdfooxyz","(?>.*)foo",[trim]))), + 2}]))), + <<"::wxyz:">> = iolist_to_binary(join(re:split("endingwxyz","(?>.*)(?<=(abcd)|(wxyz))",[]))), + <<"abcdfooxyz">> = iolist_to_binary(join(re:split("abcdfooxyz","(?>.*)foo",[trim]))), <<"abcdfooxyz">> = iolist_to_binary(join(re:split("abcdfooxyz","(?>.*)foo",[{parts, - 2}]))), - <<"abcdfooxyz">> = iolist_to_binary(join(re:split("abcdfooxyz","(?>.*)foo",[]))), - <<"abcd:xyz">> = iolist_to_binary(join(re:split("abcdfooxyz","(?>.*?)foo",[trim]))), + 2}]))), + <<"abcdfooxyz">> = iolist_to_binary(join(re:split("abcdfooxyz","(?>.*)foo",[]))), + <<"abcd:xyz">> = iolist_to_binary(join(re:split("abcdfooxyz","(?>.*?)foo",[trim]))), <<"abcd:xyz">> = iolist_to_binary(join(re:split("abcdfooxyz","(?>.*?)foo",[{parts, - 2}]))), - <<"abcd:xyz">> = iolist_to_binary(join(re:split("abcdfooxyz","(?>.*?)foo",[]))), - <<"">> = iolist_to_binary(join(re:split("ac","(?:(a(*PRUNE)b)){0}(?:(?1)|ac)",[trim]))), + 2}]))), + <<"abcd:xyz">> = iolist_to_binary(join(re:split("abcdfooxyz","(?>.*?)foo",[]))), + <<"">> = iolist_to_binary(join(re:split("ac","(?:(a(*PRUNE)b)){0}(?:(?1)|ac)",[trim]))), <<"::">> = iolist_to_binary(join(re:split("ac","(?:(a(*PRUNE)b)){0}(?:(?1)|ac)",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("ac","(?:(a(*PRUNE)b)){0}(?:(?1)|ac)",[]))), - <<"">> = iolist_to_binary(join(re:split("ac","(?:(a(*SKIP)b)){0}(?:(?1)|ac)",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("ac","(?:(a(*PRUNE)b)){0}(?:(?1)|ac)",[]))), + <<"">> = iolist_to_binary(join(re:split("ac","(?:(a(*SKIP)b)){0}(?:(?1)|ac)",[trim]))), <<"::">> = iolist_to_binary(join(re:split("ac","(?:(a(*SKIP)b)){0}(?:(?1)|ac)",[{parts, - 2}]))), - <<"::">> = iolist_to_binary(join(re:split("ac","(?:(a(*SKIP)b)){0}(?:(?1)|ac)",[]))), - <<"aa">> = iolist_to_binary(join(re:split("aa","(?<=(*SKIP)ac)a",[trim]))), + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("ac","(?:(a(*SKIP)b)){0}(?:(?1)|ac)",[]))), + <<"aa">> = iolist_to_binary(join(re:split("aa","(?<=(*SKIP)ac)a",[trim]))), <<"aa">> = iolist_to_binary(join(re:split("aa","(?<=(*SKIP)ac)a",[{parts, - 2}]))), - <<"aa">> = iolist_to_binary(join(re:split("aa","(?<=(*SKIP)ac)a",[]))), - <<"aa">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*PRUNE)b|a+c",[trim]))), + 2}]))), + <<"aa">> = iolist_to_binary(join(re:split("aa","(?<=(*SKIP)ac)a",[]))), + <<"aa">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*PRUNE)b|a+c",[trim]))), <<"aa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*PRUNE)b|a+c",[{parts, - 2}]))), - <<"aa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*PRUNE)b|a+c",[]))), - <<"aa">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP)(*PRUNE)b|a+c",[trim]))), + 2}]))), + <<"aa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*PRUNE)b|a+c",[]))), + <<"aa">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP)(*PRUNE)b|a+c",[trim]))), <<"aa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP)(*PRUNE)b|a+c",[{parts, - 2}]))), - <<"aa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP)(*PRUNE)b|a+c",[]))), - <<"aa">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP:N)(*PRUNE)b|a+c",[trim]))), + 2}]))), + <<"aa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP)(*PRUNE)b|a+c",[]))), + <<"aa">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP:N)(*PRUNE)b|a+c",[trim]))), <<"aa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP:N)(*PRUNE)b|a+c",[{parts, - 2}]))), - <<"aa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP:N)(*PRUNE)b|a+c",[]))), - <<"aa">> = iolist_to_binary(join(re:split("aaaaaac","aaaa(*:N)a(*SKIP:N)(*PRUNE)b|a+c",[trim]))), + 2}]))), + <<"aa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP:N)(*PRUNE)b|a+c",[]))), + <<"aa">> = iolist_to_binary(join(re:split("aaaaaac","aaaa(*:N)a(*SKIP:N)(*PRUNE)b|a+c",[trim]))), <<"aa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaa(*:N)a(*SKIP:N)(*PRUNE)b|a+c",[{parts, - 2}]))), - <<"aa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaa(*:N)a(*SKIP:N)(*PRUNE)b|a+c",[]))), - <<"aa">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*THEN)(*PRUNE)b|a+c",[trim]))), + 2}]))), + <<"aa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaa(*:N)a(*SKIP:N)(*PRUNE)b|a+c",[]))), + <<"aa">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*THEN)(*PRUNE)b|a+c",[trim]))), <<"aa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*THEN)(*PRUNE)b|a+c",[{parts, - 2}]))), - <<"aa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*THEN)(*PRUNE)b|a+c",[]))), + 2}]))), + <<"aa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*THEN)(*PRUNE)b|a+c",[]))), ok. run51() -> - <<"aaaaa">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP)b|a+c",[trim]))), + <<"aaaaa">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP)b|a+c",[trim]))), <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP)b|a+c",[{parts, - 2}]))), - <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP)b|a+c",[]))), - <<"aaaaa">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*PRUNE)(*SKIP)b|a+c",[trim]))), + 2}]))), + <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP)b|a+c",[]))), + <<"aaaaa">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*PRUNE)(*SKIP)b|a+c",[trim]))), <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*PRUNE)(*SKIP)b|a+c",[{parts, - 2}]))), - <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*PRUNE)(*SKIP)b|a+c",[]))), - <<"aaaaa">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*THEN)(*SKIP)b|a+c",[trim]))), + 2}]))), + <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*PRUNE)(*SKIP)b|a+c",[]))), + <<"aaaaa">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*THEN)(*SKIP)b|a+c",[trim]))), <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*THEN)(*SKIP)b|a+c",[{parts, - 2}]))), - <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*THEN)(*SKIP)b|a+c",[]))), - <<"aaaaa">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*COMMIT)(*SKIP)b|a+c",[trim]))), + 2}]))), + <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*THEN)(*SKIP)b|a+c",[]))), + <<"aaaaa">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*COMMIT)(*SKIP)b|a+c",[trim]))), <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*COMMIT)(*SKIP)b|a+c",[{parts, - 2}]))), - <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*COMMIT)(*SKIP)b|a+c",[]))), - <<"aaaaaac">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*COMMIT)b|a+c",[trim]))), + 2}]))), + <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*COMMIT)(*SKIP)b|a+c",[]))), + <<"aaaaaac">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*COMMIT)b|a+c",[trim]))), <<"aaaaaac">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*COMMIT)b|a+c",[{parts, - 2}]))), - <<"aaaaaac">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*COMMIT)b|a+c",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*THEN)b|a+c",[trim]))), + 2}]))), + <<"aaaaaac">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*COMMIT)b|a+c",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*THEN)b|a+c",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*THEN)b|a+c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*THEN)b|a+c",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP)(*THEN)b|a+c",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*THEN)b|a+c",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP)(*THEN)b|a+c",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP)(*THEN)b|a+c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP)(*THEN)b|a+c",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*PRUNE)(*THEN)b|a+c",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*SKIP)(*THEN)b|a+c",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*PRUNE)(*THEN)b|a+c",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*PRUNE)(*THEN)b|a+c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*PRUNE)(*THEN)b|a+c",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*COMMIT)(*THEN)b|a+c",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*PRUNE)(*THEN)b|a+c",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*COMMIT)(*THEN)b|a+c",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*COMMIT)(*THEN)b|a+c",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*COMMIT)(*THEN)b|a+c",[]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaaaac","aaaaa(*COMMIT)(*THEN)b|a+c",[]))), ok. run52() -> - <<"aaaaa">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:m)(*PRUNE:m)(*SKIP:m)m|a+",[trim]))), + <<"aaaaa">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:m)(*PRUNE:m)(*SKIP:m)m|a+",[trim]))), <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:m)(*PRUNE:m)(*SKIP:m)m|a+",[{parts, - 2}]))), - <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:m)(*PRUNE:m)(*SKIP:m)m|a+",[]))), - <<"aaaaa">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:m)(*MARK:m)(*PRUNE)(*SKIP:m)m|a+",[trim]))), + 2}]))), + <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:m)(*PRUNE:m)(*SKIP:m)m|a+",[]))), + <<"aaaaa">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:m)(*MARK:m)(*PRUNE)(*SKIP:m)m|a+",[trim]))), <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:m)(*MARK:m)(*PRUNE)(*SKIP:m)m|a+",[{parts, - 2}]))), - <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:m)(*MARK:m)(*PRUNE)(*SKIP:m)m|a+",[]))), - <<"aa">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:n)(*PRUNE:m)(*SKIP:m)m|a+",[trim]))), + 2}]))), + <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:m)(*MARK:m)(*PRUNE)(*SKIP:m)m|a+",[]))), + <<"aa">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:n)(*PRUNE:m)(*SKIP:m)m|a+",[trim]))), <<"aa:">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:n)(*PRUNE:m)(*SKIP:m)m|a+",[{parts, - 2}]))), - <<"aa:">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:n)(*PRUNE:m)(*SKIP:m)m|a+",[]))), - <<"aaaaa">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:n)(*MARK:m)(*PRUNE)(*SKIP:m)m|a+",[trim]))), + 2}]))), + <<"aa:">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:n)(*PRUNE:m)(*SKIP:m)m|a+",[]))), + <<"aaaaa">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:n)(*MARK:m)(*PRUNE)(*SKIP:m)m|a+",[trim]))), <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:n)(*MARK:m)(*PRUNE)(*SKIP:m)m|a+",[{parts, - 2}]))), - <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:n)(*MARK:m)(*PRUNE)(*SKIP:m)m|a+",[]))), - <<"aa">> = iolist_to_binary(join(re:split("aaaac","a(*MARK:A)aa(*PRUNE:A)a(*SKIP:A)b|a+c",[trim]))), + 2}]))), + <<"aaaaa:">> = iolist_to_binary(join(re:split("aaaaaa","aaaaa(*:n)(*MARK:m)(*PRUNE)(*SKIP:m)m|a+",[]))), + <<"aa">> = iolist_to_binary(join(re:split("aaaac","a(*MARK:A)aa(*PRUNE:A)a(*SKIP:A)b|a+c",[trim]))), <<"aa:">> = iolist_to_binary(join(re:split("aaaac","a(*MARK:A)aa(*PRUNE:A)a(*SKIP:A)b|a+c",[{parts, - 2}]))), - <<"aa:">> = iolist_to_binary(join(re:split("aaaac","a(*MARK:A)aa(*PRUNE:A)a(*SKIP:A)b|a+c",[]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaaac","a(*MARK:A)aa(*MARK:A)a(*SKIP:A)b|a+c",[trim]))), + 2}]))), + <<"aa:">> = iolist_to_binary(join(re:split("aaaac","a(*MARK:A)aa(*PRUNE:A)a(*SKIP:A)b|a+c",[]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaaac","a(*MARK:A)aa(*MARK:A)a(*SKIP:A)b|a+c",[trim]))), <<"aaa:">> = iolist_to_binary(join(re:split("aaaac","a(*MARK:A)aa(*MARK:A)a(*SKIP:A)b|a+c",[{parts, - 2}]))), - <<"aaa:">> = iolist_to_binary(join(re:split("aaaac","a(*MARK:A)aa(*MARK:A)a(*SKIP:A)b|a+c",[]))), - <<"aa">> = iolist_to_binary(join(re:split("aaaac","aaa(*PRUNE:A)a(*SKIP:A)b|a+c",[trim]))), + 2}]))), + <<"aaa:">> = iolist_to_binary(join(re:split("aaaac","a(*MARK:A)aa(*MARK:A)a(*SKIP:A)b|a+c",[]))), + <<"aa">> = iolist_to_binary(join(re:split("aaaac","aaa(*PRUNE:A)a(*SKIP:A)b|a+c",[trim]))), <<"aa:">> = iolist_to_binary(join(re:split("aaaac","aaa(*PRUNE:A)a(*SKIP:A)b|a+c",[{parts, - 2}]))), - <<"aa:">> = iolist_to_binary(join(re:split("aaaac","aaa(*PRUNE:A)a(*SKIP:A)b|a+c",[]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaaac","aaa(*MARK:A)a(*SKIP:A)b|a+c",[trim]))), + 2}]))), + <<"aa:">> = iolist_to_binary(join(re:split("aaaac","aaa(*PRUNE:A)a(*SKIP:A)b|a+c",[]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaaac","aaa(*MARK:A)a(*SKIP:A)b|a+c",[trim]))), <<"aaa:">> = iolist_to_binary(join(re:split("aaaac","aaa(*MARK:A)a(*SKIP:A)b|a+c",[{parts, - 2}]))), - <<"aaa:">> = iolist_to_binary(join(re:split("aaaac","aaa(*MARK:A)a(*SKIP:A)b|a+c",[]))), - <<":a">> = iolist_to_binary(join(re:split("ba",".?(a|b(*THEN)c)",[trim]))), + 2}]))), + <<"aaa:">> = iolist_to_binary(join(re:split("aaaac","aaa(*MARK:A)a(*SKIP:A)b|a+c",[]))), + <<":a">> = iolist_to_binary(join(re:split("ba",".?(a|b(*THEN)c)",[trim]))), <<":a:">> = iolist_to_binary(join(re:split("ba",".?(a|b(*THEN)c)",[{parts, - 2}]))), - <<":a:">> = iolist_to_binary(join(re:split("ba",".?(a|b(*THEN)c)",[]))), - <<":ab">> = iolist_to_binary(join(re:split("abc","(a(*COMMIT)b)c|abd",[trim]))), + 2}]))), + <<":a:">> = iolist_to_binary(join(re:split("ba",".?(a|b(*THEN)c)",[]))), + <<":ab">> = iolist_to_binary(join(re:split("abc","(a(*COMMIT)b)c|abd",[trim]))), <<":ab:">> = iolist_to_binary(join(re:split("abc","(a(*COMMIT)b)c|abd",[{parts, - 2}]))), - <<":ab:">> = iolist_to_binary(join(re:split("abc","(a(*COMMIT)b)c|abd",[]))), - <<"abd">> = iolist_to_binary(join(re:split("abd","(a(*COMMIT)b)c|abd",[trim]))), + 2}]))), + <<":ab:">> = iolist_to_binary(join(re:split("abc","(a(*COMMIT)b)c|abd",[]))), + <<"abd">> = iolist_to_binary(join(re:split("abd","(a(*COMMIT)b)c|abd",[trim]))), <<"abd">> = iolist_to_binary(join(re:split("abd","(a(*COMMIT)b)c|abd",[{parts, - 2}]))), - <<"abd">> = iolist_to_binary(join(re:split("abd","(a(*COMMIT)b)c|abd",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","(?=a(*COMMIT)b)abc|abd",[trim]))), + 2}]))), + <<"abd">> = iolist_to_binary(join(re:split("abd","(a(*COMMIT)b)c|abd",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","(?=a(*COMMIT)b)abc|abd",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","(?=a(*COMMIT)b)abc|abd",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","(?=a(*COMMIT)b)abc|abd",[]))), - <<"">> = iolist_to_binary(join(re:split("abd","(?=a(*COMMIT)b)abc|abd",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","(?=a(*COMMIT)b)abc|abd",[]))), + <<"">> = iolist_to_binary(join(re:split("abd","(?=a(*COMMIT)b)abc|abd",[trim]))), <<":">> = iolist_to_binary(join(re:split("abd","(?=a(*COMMIT)b)abc|abd",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abd","(?=a(*COMMIT)b)abc|abd",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","(?>a(*COMMIT)b)c|abd",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abd","(?=a(*COMMIT)b)abc|abd",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","(?>a(*COMMIT)b)c|abd",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","(?>a(*COMMIT)b)c|abd",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","(?>a(*COMMIT)b)c|abd",[]))), - <<"">> = iolist_to_binary(join(re:split("abd","(?>a(*COMMIT)b)c|abd",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","(?>a(*COMMIT)b)c|abd",[]))), + <<"">> = iolist_to_binary(join(re:split("abd","(?>a(*COMMIT)b)c|abd",[trim]))), <<":">> = iolist_to_binary(join(re:split("abd","(?>a(*COMMIT)b)c|abd",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abd","(?>a(*COMMIT)b)c|abd",[]))), - <<"abd">> = iolist_to_binary(join(re:split("abd","a(?=b(*COMMIT)c)[^d]|abd",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abd","(?>a(*COMMIT)b)c|abd",[]))), + <<"abd">> = iolist_to_binary(join(re:split("abd","a(?=b(*COMMIT)c)[^d]|abd",[trim]))), <<"abd">> = iolist_to_binary(join(re:split("abd","a(?=b(*COMMIT)c)[^d]|abd",[{parts, - 2}]))), - <<"abd">> = iolist_to_binary(join(re:split("abd","a(?=b(*COMMIT)c)[^d]|abd",[]))), - <<":c">> = iolist_to_binary(join(re:split("abc","a(?=b(*COMMIT)c)[^d]|abd",[trim]))), + 2}]))), + <<"abd">> = iolist_to_binary(join(re:split("abd","a(?=b(*COMMIT)c)[^d]|abd",[]))), + <<":c">> = iolist_to_binary(join(re:split("abc","a(?=b(*COMMIT)c)[^d]|abd",[trim]))), <<":c">> = iolist_to_binary(join(re:split("abc","a(?=b(*COMMIT)c)[^d]|abd",[{parts, - 2}]))), - <<":c">> = iolist_to_binary(join(re:split("abc","a(?=b(*COMMIT)c)[^d]|abd",[]))), - <<"">> = iolist_to_binary(join(re:split("abd","a(?=bc).|abd",[trim]))), + 2}]))), + <<":c">> = iolist_to_binary(join(re:split("abc","a(?=b(*COMMIT)c)[^d]|abd",[]))), + <<"">> = iolist_to_binary(join(re:split("abd","a(?=bc).|abd",[trim]))), <<":">> = iolist_to_binary(join(re:split("abd","a(?=bc).|abd",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abd","a(?=bc).|abd",[]))), - <<":c">> = iolist_to_binary(join(re:split("abc","a(?=bc).|abd",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abd","a(?=bc).|abd",[]))), + <<":c">> = iolist_to_binary(join(re:split("abc","a(?=bc).|abd",[trim]))), <<":c">> = iolist_to_binary(join(re:split("abc","a(?=bc).|abd",[{parts, - 2}]))), - <<":c">> = iolist_to_binary(join(re:split("abc","a(?=bc).|abd",[]))), - <<"abceabd">> = iolist_to_binary(join(re:split("abceabd","a(?>b(*COMMIT)c)d|abd",[trim]))), + 2}]))), + <<":c">> = iolist_to_binary(join(re:split("abc","a(?=bc).|abd",[]))), + <<"abceabd">> = iolist_to_binary(join(re:split("abceabd","a(?>b(*COMMIT)c)d|abd",[trim]))), <<"abceabd">> = iolist_to_binary(join(re:split("abceabd","a(?>b(*COMMIT)c)d|abd",[{parts, - 2}]))), - <<"abceabd">> = iolist_to_binary(join(re:split("abceabd","a(?>b(*COMMIT)c)d|abd",[]))), - <<"abce">> = iolist_to_binary(join(re:split("abceabd","a(?>bc)d|abd",[trim]))), + 2}]))), + <<"abceabd">> = iolist_to_binary(join(re:split("abceabd","a(?>b(*COMMIT)c)d|abd",[]))), + <<"abce">> = iolist_to_binary(join(re:split("abceabd","a(?>bc)d|abd",[trim]))), <<"abce:">> = iolist_to_binary(join(re:split("abceabd","a(?>bc)d|abd",[{parts, - 2}]))), - <<"abce:">> = iolist_to_binary(join(re:split("abceabd","a(?>bc)d|abd",[]))), - <<"">> = iolist_to_binary(join(re:split("abd","(?>a(*COMMIT)b)c|abd",[trim]))), + 2}]))), + <<"abce:">> = iolist_to_binary(join(re:split("abceabd","a(?>bc)d|abd",[]))), + <<"">> = iolist_to_binary(join(re:split("abd","(?>a(*COMMIT)b)c|abd",[trim]))), <<":">> = iolist_to_binary(join(re:split("abd","(?>a(*COMMIT)b)c|abd",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abd","(?>a(*COMMIT)b)c|abd",[]))), - <<"abd">> = iolist_to_binary(join(re:split("abd","(?>a(*COMMIT)c)d|abd",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abd","(?>a(*COMMIT)b)c|abd",[]))), + <<"abd">> = iolist_to_binary(join(re:split("abd","(?>a(*COMMIT)c)d|abd",[trim]))), <<"abd">> = iolist_to_binary(join(re:split("abd","(?>a(*COMMIT)c)d|abd",[{parts, - 2}]))), - <<"abd">> = iolist_to_binary(join(re:split("abd","(?>a(*COMMIT)c)d|abd",[]))), - <<"::c">> = iolist_to_binary(join(re:split("ac","((?=a(*COMMIT)b)ab|ac){0}(?:(?1)|a(c))",[trim]))), + 2}]))), + <<"abd">> = iolist_to_binary(join(re:split("abd","(?>a(*COMMIT)c)d|abd",[]))), + <<"::c">> = iolist_to_binary(join(re:split("ac","((?=a(*COMMIT)b)ab|ac){0}(?:(?1)|a(c))",[trim]))), <<"::c:">> = iolist_to_binary(join(re:split("ac","((?=a(*COMMIT)b)ab|ac){0}(?:(?1)|a(c))",[{parts, - 2}]))), - <<"::c:">> = iolist_to_binary(join(re:split("ac","((?=a(*COMMIT)b)ab|ac){0}(?:(?1)|a(c))",[]))), + 2}]))), + <<"::c:">> = iolist_to_binary(join(re:split("ac","((?=a(*COMMIT)b)ab|ac){0}(?:(?1)|a(c))",[]))), ok. run53() -> - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a)?(?(1)a|b)+$",[trim]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a)?(?(1)a|b)+$",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a)?(?(1)a|b)+$",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a)?(?(1)a|b)+$",[]))), - <<"a">> = iolist_to_binary(join(re:split("a","^(a)?(?(1)a|b)+$",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^(a)?(?(1)a|b)+$",[]))), + <<"a">> = iolist_to_binary(join(re:split("a","^(a)?(?(1)a|b)+$",[trim]))), <<"a">> = iolist_to_binary(join(re:split("a","^(a)?(?(1)a|b)+$",[{parts, - 2}]))), - <<"a">> = iolist_to_binary(join(re:split("a","^(a)?(?(1)a|b)+$",[]))), - <<"a">> = iolist_to_binary(join(re:split("ab","(?=a\\Kb)ab",[trim]))), + 2}]))), + <<"a">> = iolist_to_binary(join(re:split("a","^(a)?(?(1)a|b)+$",[]))), + <<"a">> = iolist_to_binary(join(re:split("ab","(?=a\\Kb)ab",[trim]))), <<"a:">> = iolist_to_binary(join(re:split("ab","(?=a\\Kb)ab",[{parts, - 2}]))), - <<"a:">> = iolist_to_binary(join(re:split("ab","(?=a\\Kb)ab",[]))), - <<"">> = iolist_to_binary(join(re:split("ac","(?!a\\Kb)ac",[trim]))), + 2}]))), + <<"a:">> = iolist_to_binary(join(re:split("ab","(?=a\\Kb)ab",[]))), + <<"">> = iolist_to_binary(join(re:split("ac","(?!a\\Kb)ac",[trim]))), <<":">> = iolist_to_binary(join(re:split("ac","(?!a\\Kb)ac",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ac","(?!a\\Kb)ac",[]))), - <<"ab">> = iolist_to_binary(join(re:split("abcd","^abc(?<=b\\Kc)d",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ac","(?!a\\Kb)ac",[]))), + <<"ab">> = iolist_to_binary(join(re:split("abcd","^abc(?<=b\\Kc)d",[trim]))), <<"ab:">> = iolist_to_binary(join(re:split("abcd","^abc(?<=b\\Kc)d",[{parts, - 2}]))), - <<"ab:">> = iolist_to_binary(join(re:split("abcd","^abc(?<=b\\Kc)d",[]))), - <<"">> = iolist_to_binary(join(re:split("abcd","^abc(?<!b\\Kq)d",[trim]))), + 2}]))), + <<"ab:">> = iolist_to_binary(join(re:split("abcd","^abc(?<=b\\Kc)d",[]))), + <<"">> = iolist_to_binary(join(re:split("abcd","^abc(?<!b\\Kq)d",[trim]))), <<":">> = iolist_to_binary(join(re:split("abcd","^abc(?<!b\\Kq)d",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abcd","^abc(?<!b\\Kq)d",[]))), - <<":abcd">> = iolist_to_binary(join(re:split("abcd","^((abc|abcx)(*THEN)y|abcd)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abcd","^abc(?<!b\\Kq)d",[]))), + <<":abcd">> = iolist_to_binary(join(re:split("abcd","^((abc|abcx)(*THEN)y|abcd)",[trim]))), <<":abcd::">> = iolist_to_binary(join(re:split("abcd","^((abc|abcx)(*THEN)y|abcd)",[{parts, - 2}]))), - <<":abcd::">> = iolist_to_binary(join(re:split("abcd","^((abc|abcx)(*THEN)y|abcd)",[]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^((abc|abcx)(*THEN)y|abcd)",[trim]))), + 2}]))), + <<":abcd::">> = iolist_to_binary(join(re:split("abcd","^((abc|abcx)(*THEN)y|abcd)",[]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^((abc|abcx)(*THEN)y|abcd)",[trim]))), <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^((abc|abcx)(*THEN)y|abcd)",[{parts, - 2}]))), - <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^((abc|abcx)(*THEN)y|abcd)",[]))), - <<"abcxy">> = iolist_to_binary(join(re:split("abcxy","^((abc|abcx)(*THEN)y|abcd)",[trim]))), + 2}]))), + <<"*** Failers">> = iolist_to_binary(join(re:split("*** Failers","^((abc|abcx)(*THEN)y|abcd)",[]))), + <<"abcxy">> = iolist_to_binary(join(re:split("abcxy","^((abc|abcx)(*THEN)y|abcd)",[trim]))), <<"abcxy">> = iolist_to_binary(join(re:split("abcxy","^((abc|abcx)(*THEN)y|abcd)",[{parts, - 2}]))), - <<"abcxy">> = iolist_to_binary(join(re:split("abcxy","^((abc|abcx)(*THEN)y|abcd)",[]))), - <<"yes">> = iolist_to_binary(join(re:split("yes","^((yes|no)(*THEN)(*F))?",[trim]))), + 2}]))), + <<"abcxy">> = iolist_to_binary(join(re:split("abcxy","^((abc|abcx)(*THEN)y|abcd)",[]))), + <<"yes">> = iolist_to_binary(join(re:split("yes","^((yes|no)(*THEN)(*F))?",[trim]))), <<"yes">> = iolist_to_binary(join(re:split("yes","^((yes|no)(*THEN)(*F))?",[{parts, - 2}]))), - <<"yes">> = iolist_to_binary(join(re:split("yes","^((yes|no)(*THEN)(*F))?",[]))), - <<"ac">> = iolist_to_binary(join(re:split("ac","(?=a(*COMMIT)b|ac)ac|ac",[trim]))), + 2}]))), + <<"yes">> = iolist_to_binary(join(re:split("yes","^((yes|no)(*THEN)(*F))?",[]))), + <<"ac">> = iolist_to_binary(join(re:split("ac","(?=a(*COMMIT)b|ac)ac|ac",[trim]))), <<"ac">> = iolist_to_binary(join(re:split("ac","(?=a(*COMMIT)b|ac)ac|ac",[{parts, - 2}]))), - <<"ac">> = iolist_to_binary(join(re:split("ac","(?=a(*COMMIT)b|ac)ac|ac",[]))), + 2}]))), + <<"ac">> = iolist_to_binary(join(re:split("ac","(?=a(*COMMIT)b|ac)ac|ac",[]))), <<"ac">> = iolist_to_binary(join(re:split("ac","(?=a(*COMMIT)b|(ac)) ac | (a)c",[extended, - trim]))), + trim]))), <<"ac">> = iolist_to_binary(join(re:split("ac","(?=a(*COMMIT)b|(ac)) ac | (a)c",[extended, {parts, - 2}]))), - <<"ac">> = iolist_to_binary(join(re:split("ac","(?=a(*COMMIT)b|(ac)) ac | (a)c",[extended]))), - <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*THEN)a)bn|bnn)",[trim]))), + 2}]))), + <<"ac">> = iolist_to_binary(join(re:split("ac","(?=a(*COMMIT)b|(ac)) ac | (a)c",[extended]))), + <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*THEN)a)bn|bnn)",[trim]))), <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*THEN)a)bn|bnn)",[{parts, - 2}]))), - <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*THEN)a)bn|bnn)",[]))), - <<":n">> = iolist_to_binary(join(re:split("bnn","(?!b(*SKIP)a)bn|bnn",[trim]))), + 2}]))), + <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*THEN)a)bn|bnn)",[]))), + <<":n">> = iolist_to_binary(join(re:split("bnn","(?!b(*SKIP)a)bn|bnn",[trim]))), <<":n">> = iolist_to_binary(join(re:split("bnn","(?!b(*SKIP)a)bn|bnn",[{parts, - 2}]))), - <<":n">> = iolist_to_binary(join(re:split("bnn","(?!b(*SKIP)a)bn|bnn",[]))), - <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*SKIP)a)bn|bnn)",[trim]))), + 2}]))), + <<":n">> = iolist_to_binary(join(re:split("bnn","(?!b(*SKIP)a)bn|bnn",[]))), + <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*SKIP)a)bn|bnn)",[trim]))), <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*SKIP)a)bn|bnn)",[{parts, - 2}]))), - <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*SKIP)a)bn|bnn)",[]))), - <<":n">> = iolist_to_binary(join(re:split("bnn","(?!b(*PRUNE)a)bn|bnn",[trim]))), + 2}]))), + <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*SKIP)a)bn|bnn)",[]))), + <<":n">> = iolist_to_binary(join(re:split("bnn","(?!b(*PRUNE)a)bn|bnn",[trim]))), <<":n">> = iolist_to_binary(join(re:split("bnn","(?!b(*PRUNE)a)bn|bnn",[{parts, - 2}]))), - <<":n">> = iolist_to_binary(join(re:split("bnn","(?!b(*PRUNE)a)bn|bnn",[]))), - <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*PRUNE)a)bn|bnn)",[trim]))), + 2}]))), + <<":n">> = iolist_to_binary(join(re:split("bnn","(?!b(*PRUNE)a)bn|bnn",[]))), + <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*PRUNE)a)bn|bnn)",[trim]))), <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*PRUNE)a)bn|bnn)",[{parts, - 2}]))), - <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*PRUNE)a)bn|bnn)",[]))), - <<":n">> = iolist_to_binary(join(re:split("bnn","(?!b(*COMMIT)a)bn|bnn",[trim]))), + 2}]))), + <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*PRUNE)a)bn|bnn)",[]))), + <<":n">> = iolist_to_binary(join(re:split("bnn","(?!b(*COMMIT)a)bn|bnn",[trim]))), <<":n">> = iolist_to_binary(join(re:split("bnn","(?!b(*COMMIT)a)bn|bnn",[{parts, - 2}]))), - <<":n">> = iolist_to_binary(join(re:split("bnn","(?!b(*COMMIT)a)bn|bnn",[]))), - <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*COMMIT)a)bn|bnn)",[trim]))), + 2}]))), + <<":n">> = iolist_to_binary(join(re:split("bnn","(?!b(*COMMIT)a)bn|bnn",[]))), + <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*COMMIT)a)bn|bnn)",[trim]))), <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*COMMIT)a)bn|bnn)",[{parts, - 2}]))), - <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*COMMIT)a)bn|bnn)",[]))), - <<"bnn">> = iolist_to_binary(join(re:split("bnn","(?=b(*SKIP)a)bn|bnn",[trim]))), + 2}]))), + <<":n">> = iolist_to_binary(join(re:split("bnn","(?(?!b(*COMMIT)a)bn|bnn)",[]))), + <<"bnn">> = iolist_to_binary(join(re:split("bnn","(?=b(*SKIP)a)bn|bnn",[trim]))), <<"bnn">> = iolist_to_binary(join(re:split("bnn","(?=b(*SKIP)a)bn|bnn",[{parts, - 2}]))), - <<"bnn">> = iolist_to_binary(join(re:split("bnn","(?=b(*SKIP)a)bn|bnn",[]))), - <<"">> = iolist_to_binary(join(re:split("bnn","(?=b(*THEN)a)bn|bnn",[trim]))), + 2}]))), + <<"bnn">> = iolist_to_binary(join(re:split("bnn","(?=b(*SKIP)a)bn|bnn",[]))), + <<"">> = iolist_to_binary(join(re:split("bnn","(?=b(*THEN)a)bn|bnn",[trim]))), <<":">> = iolist_to_binary(join(re:split("bnn","(?=b(*THEN)a)bn|bnn",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("bnn","(?=b(*THEN)a)bn|bnn",[]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("bnn","(?=b(*THEN)a)bn|bnn",[]))), ok. run54() -> - <<":d">> = iolist_to_binary(join(re:split("acd","(?!a(*SKIP)b)..",[trim]))), + <<":d">> = iolist_to_binary(join(re:split("acd","(?!a(*SKIP)b)..",[trim]))), <<":d">> = iolist_to_binary(join(re:split("acd","(?!a(*SKIP)b)..",[{parts, - 2}]))), - <<":d">> = iolist_to_binary(join(re:split("acd","(?!a(*SKIP)b)..",[]))), - <<"ac">> = iolist_to_binary(join(re:split("ac","^(?(?!a(*SKIP)b))",[trim]))), + 2}]))), + <<":d">> = iolist_to_binary(join(re:split("acd","(?!a(*SKIP)b)..",[]))), + <<"ac">> = iolist_to_binary(join(re:split("ac","^(?(?!a(*SKIP)b))",[trim]))), <<"ac">> = iolist_to_binary(join(re:split("ac","^(?(?!a(*SKIP)b))",[{parts, - 2}]))), - <<"ac">> = iolist_to_binary(join(re:split("ac","^(?(?!a(*SKIP)b))",[]))), - <<":d">> = iolist_to_binary(join(re:split("acd","^(?!a(*PRUNE)b)..",[trim]))), + 2}]))), + <<"ac">> = iolist_to_binary(join(re:split("ac","^(?(?!a(*SKIP)b))",[]))), + <<":d">> = iolist_to_binary(join(re:split("acd","^(?!a(*PRUNE)b)..",[trim]))), <<":d">> = iolist_to_binary(join(re:split("acd","^(?!a(*PRUNE)b)..",[{parts, - 2}]))), - <<":d">> = iolist_to_binary(join(re:split("acd","^(?!a(*PRUNE)b)..",[]))), - <<":d">> = iolist_to_binary(join(re:split("acd","(?!a(*PRUNE)b)..",[trim]))), + 2}]))), + <<":d">> = iolist_to_binary(join(re:split("acd","^(?!a(*PRUNE)b)..",[]))), + <<":d">> = iolist_to_binary(join(re:split("acd","(?!a(*PRUNE)b)..",[trim]))), <<":d">> = iolist_to_binary(join(re:split("acd","(?!a(*PRUNE)b)..",[{parts, - 2}]))), - <<":d">> = iolist_to_binary(join(re:split("acd","(?!a(*PRUNE)b)..",[]))), - <<"">> = iolist_to_binary(join(re:split("ba","\\A.*?(?:a|bc)",[trim]))), + 2}]))), + <<":d">> = iolist_to_binary(join(re:split("acd","(?!a(*PRUNE)b)..",[]))), + <<"">> = iolist_to_binary(join(re:split("ba","\\A.*?(?:a|bc)",[trim]))), <<":">> = iolist_to_binary(join(re:split("ba","\\A.*?(?:a|bc)",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ba","\\A.*?(?:a|bc)",[]))), - <<":CD">> = iolist_to_binary(join(re:split("CD","^(A(*THEN)B|C(*THEN)D)",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ba","\\A.*?(?:a|bc)",[]))), + <<":CD">> = iolist_to_binary(join(re:split("CD","^(A(*THEN)B|C(*THEN)D)",[trim]))), <<":CD:">> = iolist_to_binary(join(re:split("CD","^(A(*THEN)B|C(*THEN)D)",[{parts, - 2}]))), - <<":CD:">> = iolist_to_binary(join(re:split("CD","^(A(*THEN)B|C(*THEN)D)",[]))), - <<"">> = iolist_to_binary(join(re:split("1234","^\\d*\\w{4}",[trim]))), + 2}]))), + <<":CD:">> = iolist_to_binary(join(re:split("CD","^(A(*THEN)B|C(*THEN)D)",[]))), + <<"">> = iolist_to_binary(join(re:split("1234","^\\d*\\w{4}",[trim]))), <<":">> = iolist_to_binary(join(re:split("1234","^\\d*\\w{4}",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("1234","^\\d*\\w{4}",[]))), - <<"123">> = iolist_to_binary(join(re:split("123","^\\d*\\w{4}",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("1234","^\\d*\\w{4}",[]))), + <<"123">> = iolist_to_binary(join(re:split("123","^\\d*\\w{4}",[trim]))), <<"123">> = iolist_to_binary(join(re:split("123","^\\d*\\w{4}",[{parts, - 2}]))), - <<"123">> = iolist_to_binary(join(re:split("123","^\\d*\\w{4}",[]))), - <<"">> = iolist_to_binary(join(re:split("aaaa","^[^b]*\\w{4}",[trim]))), + 2}]))), + <<"123">> = iolist_to_binary(join(re:split("123","^\\d*\\w{4}",[]))), + <<"">> = iolist_to_binary(join(re:split("aaaa","^[^b]*\\w{4}",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaa","^[^b]*\\w{4}",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaa","^[^b]*\\w{4}",[]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","^[^b]*\\w{4}",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaa","^[^b]*\\w{4}",[]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","^[^b]*\\w{4}",[trim]))), <<"aaa">> = iolist_to_binary(join(re:split("aaa","^[^b]*\\w{4}",[{parts, - 2}]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","^[^b]*\\w{4}",[]))), + 2}]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","^[^b]*\\w{4}",[]))), <<"">> = iolist_to_binary(join(re:split("aaaa","^[^b]*\\w{4}",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("aaaa","^[^b]*\\w{4}",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaa","^[^b]*\\w{4}",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaa","^[^b]*\\w{4}",[caseless]))), <<"aaa">> = iolist_to_binary(join(re:split("aaa","^[^b]*\\w{4}",[caseless, - trim]))), + trim]))), <<"aaa">> = iolist_to_binary(join(re:split("aaa","^[^b]*\\w{4}",[caseless, {parts, - 2}]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","^[^b]*\\w{4}",[caseless]))), - <<"">> = iolist_to_binary(join(re:split("aaaa","^a*\\w{4}",[trim]))), + 2}]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","^[^b]*\\w{4}",[caseless]))), + <<"">> = iolist_to_binary(join(re:split("aaaa","^a*\\w{4}",[trim]))), <<":">> = iolist_to_binary(join(re:split("aaaa","^a*\\w{4}",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaa","^a*\\w{4}",[]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","^a*\\w{4}",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaa","^a*\\w{4}",[]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","^a*\\w{4}",[trim]))), <<"aaa">> = iolist_to_binary(join(re:split("aaa","^a*\\w{4}",[{parts, - 2}]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","^a*\\w{4}",[]))), + 2}]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","^a*\\w{4}",[]))), <<"">> = iolist_to_binary(join(re:split("aaaa","^a*\\w{4}",[caseless, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("aaaa","^a*\\w{4}",[caseless, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaa","^a*\\w{4}",[caseless]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaa","^a*\\w{4}",[caseless]))), <<"aaa">> = iolist_to_binary(join(re:split("aaa","^a*\\w{4}",[caseless, - trim]))), + trim]))), <<"aaa">> = iolist_to_binary(join(re:split("aaa","^a*\\w{4}",[caseless, {parts, - 2}]))), - <<"aaa">> = iolist_to_binary(join(re:split("aaa","^a*\\w{4}",[caseless]))), - <<":1:non-sp1:non-sp2">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[trim]))), + 2}]))), + <<"aaa">> = iolist_to_binary(join(re:split("aaa","^a*\\w{4}",[caseless]))), + <<":1:non-sp1:non-sp2">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[trim]))), <<":1:non-sp1:non-sp2:">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[{parts, - 2}]))), - <<":1:non-sp1:non-sp2:">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[]))), - <<"AZ">> = iolist_to_binary(join(re:split("AZ","^A\\xZ",[trim]))), + 2}]))), + <<":1:non-sp1:non-sp2:">> = iolist_to_binary(join(re:split("1 IN SOA non-sp1 non-sp2(","^(\\d+)\\s+IN\\s+SOA\\s+(\\S+)\\s+(\\S+)\\s*\\(\\s*$",[]))), + <<"AZ">> = iolist_to_binary(join(re:split("AZ","^A\\xZ",[trim]))), <<"AZ">> = iolist_to_binary(join(re:split("AZ","^A\\xZ",[{parts, - 2}]))), - <<"AZ">> = iolist_to_binary(join(re:split("AZ","^A\\xZ",[]))), - <<"">> = iolist_to_binary(join(re:split("ASB","^A\\o{123}B",[trim]))), + 2}]))), + <<"AZ">> = iolist_to_binary(join(re:split("AZ","^A\\xZ",[]))), + <<"">> = iolist_to_binary(join(re:split("ASB","^A\\o{123}B",[trim]))), <<":">> = iolist_to_binary(join(re:split("ASB","^A\\o{123}B",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("ASB","^A\\o{123}B",[]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("ASB","^A\\o{123}B",[]))), <<"">> = iolist_to_binary(join(re:split("aaaab"," ^ a + + b $ ",[extended, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("aaaab"," ^ a + + b $ ",[extended, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaab"," ^ a + + b $ ",[extended]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaab"," ^ a + + b $ ",[extended]))), <<"">> = iolist_to_binary(join(re:split("aaaab"," ^ a + #comment - + b $ ",[extended,trim]))), + + b $ ",[extended,trim]))), <<":">> = iolist_to_binary(join(re:split("aaaab"," ^ a + #comment - + b $ ",[extended,{parts,2}]))), + + b $ ",[extended,{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("aaaab"," ^ a + #comment - + b $ ",[extended]))), + + b $ ",[extended]))), <<"">> = iolist_to_binary(join(re:split("aaaab"," ^ a + #comment #comment - + b $ ",[extended,trim]))), + + b $ ",[extended,trim]))), <<":">> = iolist_to_binary(join(re:split("aaaab"," ^ a + #comment #comment - + b $ ",[extended,{parts,2}]))), + + b $ ",[extended,{parts,2}]))), <<":">> = iolist_to_binary(join(re:split("aaaab"," ^ a + #comment #comment - + b $ ",[extended]))), + + b $ ",[extended]))), ok. run55() -> <<"">> = iolist_to_binary(join(re:split("aaaab"," ^ (?> a + ) b $ ",[extended, - trim]))), + trim]))), <<":">> = iolist_to_binary(join(re:split("aaaab"," ^ (?> a + ) b $ ",[extended, {parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aaaab"," ^ (?> a + ) b $ ",[extended]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aaaab"," ^ (?> a + ) b $ ",[extended]))), <<":aaaa">> = iolist_to_binary(join(re:split("aaaab"," ^ ( a + ) + + \\w $ ",[extended, - trim]))), + trim]))), <<":aaaa:">> = iolist_to_binary(join(re:split("aaaab"," ^ ( a + ) + + \\w $ ",[extended, {parts, - 2}]))), - <<":aaaa:">> = iolist_to_binary(join(re:split("aaaab"," ^ ( a + ) + + \\w $ ",[extended]))), - <<"acb">> = iolist_to_binary(join(re:split("acb","(?:x|(?:(xx|yy)+|x|x|x|x|x)|a|a|a)bc",[trim]))), + 2}]))), + <<":aaaa:">> = iolist_to_binary(join(re:split("aaaab"," ^ ( a + ) + + \\w $ ",[extended]))), + <<"acb">> = iolist_to_binary(join(re:split("acb","(?:x|(?:(xx|yy)+|x|x|x|x|x)|a|a|a)bc",[trim]))), <<"acb">> = iolist_to_binary(join(re:split("acb","(?:x|(?:(xx|yy)+|x|x|x|x|x)|a|a|a)bc",[{parts, - 2}]))), - <<"acb">> = iolist_to_binary(join(re:split("acb","(?:x|(?:(xx|yy)+|x|x|x|x|x)|a|a|a)bc",[]))), - <<":\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A(?:[^\\\"]++|\\\"(?:[^\\\"]*+|\\\"\\\")*+\\\")++",[trim]))), + 2}]))), + <<"acb">> = iolist_to_binary(join(re:split("acb","(?:x|(?:(xx|yy)+|x|x|x|x|x)|a|a|a)bc",[]))), + <<":\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A(?:[^\\\"]++|\\\"(?:[^\\\"]*+|\\\"\\\")*+\\\")++",[trim]))), <<":\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A(?:[^\\\"]++|\\\"(?:[^\\\"]*+|\\\"\\\")*+\\\")++",[{parts, - 2}]))), - <<":\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A(?:[^\\\"]++|\\\"(?:[^\\\"]*+|\\\"\\\")*+\\\")++",[]))), - <<":\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A(?:[^\\\"]++|\\\"(?:[^\\\"]++|\\\"\\\")*+\\\")++",[trim]))), + 2}]))), + <<":\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A(?:[^\\\"]++|\\\"(?:[^\\\"]*+|\\\"\\\")*+\\\")++",[]))), + <<":\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A(?:[^\\\"]++|\\\"(?:[^\\\"]++|\\\"\\\")*+\\\")++",[trim]))), <<":\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A(?:[^\\\"]++|\\\"(?:[^\\\"]++|\\\"\\\")*+\\\")++",[{parts, - 2}]))), - <<":\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A(?:[^\\\"]++|\\\"(?:[^\\\"]++|\\\"\\\")*+\\\")++",[]))), - <<":\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A(?:[^\\\"]++|\\\"(?:[^\\\"]++|\\\"\\\")++\\\")++",[trim]))), + 2}]))), + <<":\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A(?:[^\\\"]++|\\\"(?:[^\\\"]++|\\\"\\\")*+\\\")++",[]))), + <<":\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A(?:[^\\\"]++|\\\"(?:[^\\\"]++|\\\"\\\")++\\\")++",[trim]))), <<":\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A(?:[^\\\"]++|\\\"(?:[^\\\"]++|\\\"\\\")++\\\")++",[{parts, - 2}]))), - <<":\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A(?:[^\\\"]++|\\\"(?:[^\\\"]++|\\\"\\\")++\\\")++",[]))), - <<": AFTER ::\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A([^\\\"1]++|[\\\"2]([^\\\"3]*+|[\\\"4][\\\"5])*+[\\\"6])++",[trim]))), + 2}]))), + <<":\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A(?:[^\\\"]++|\\\"(?:[^\\\"]++|\\\"\\\")++\\\")++",[]))), + <<": AFTER ::\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A([^\\\"1]++|[\\\"2]([^\\\"3]*+|[\\\"4][\\\"5])*+[\\\"6])++",[trim]))), <<": AFTER ::\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A([^\\\"1]++|[\\\"2]([^\\\"3]*+|[\\\"4][\\\"5])*+[\\\"6])++",[{parts, - 2}]))), - <<": AFTER ::\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A([^\\\"1]++|[\\\"2]([^\\\"3]*+|[\\\"4][\\\"5])*+[\\\"6])++",[]))), - <<":t test">> = iolist_to_binary(join(re:split("test test","^\\w+(?>\\s*)(?<=\\w)",[trim]))), + 2}]))), + <<": AFTER ::\"NOT MATCHED">> = iolist_to_binary(join(re:split("NON QUOTED \"QUOT\"\"ED\" AFTER \"NOT MATCHED","\\A([^\\\"1]++|[\\\"2]([^\\\"3]*+|[\\\"4][\\\"5])*+[\\\"6])++",[]))), + <<":t test">> = iolist_to_binary(join(re:split("test test","^\\w+(?>\\s*)(?<=\\w)",[trim]))), <<":t test">> = iolist_to_binary(join(re:split("test test","^\\w+(?>\\s*)(?<=\\w)",[{parts, - 2}]))), - <<":t test">> = iolist_to_binary(join(re:split("test test","^\\w+(?>\\s*)(?<=\\w)",[]))), - <<":a">> = iolist_to_binary(join(re:split("acl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[trim]))), + 2}]))), + <<":t test">> = iolist_to_binary(join(re:split("test test","^\\w+(?>\\s*)(?<=\\w)",[]))), + <<":a">> = iolist_to_binary(join(re:split("acl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[trim]))), <<":a::">> = iolist_to_binary(join(re:split("acl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[{parts, - 2}]))), - <<":a::">> = iolist_to_binary(join(re:split("acl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[]))), - <<"::b">> = iolist_to_binary(join(re:split("bdl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[trim]))), + 2}]))), + <<":a::">> = iolist_to_binary(join(re:split("acl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[]))), + <<"::b">> = iolist_to_binary(join(re:split("bdl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[trim]))), <<"::b:">> = iolist_to_binary(join(re:split("bdl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[{parts, - 2}]))), - <<"::b:">> = iolist_to_binary(join(re:split("bdl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[]))), - <<"a">> = iolist_to_binary(join(re:split("adl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[trim]))), + 2}]))), + <<"::b:">> = iolist_to_binary(join(re:split("bdl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[]))), + <<"a">> = iolist_to_binary(join(re:split("adl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[trim]))), <<"a:::">> = iolist_to_binary(join(re:split("adl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[{parts, - 2}]))), - <<"a:::">> = iolist_to_binary(join(re:split("adl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[]))), - <<"bc">> = iolist_to_binary(join(re:split("bcl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[trim]))), + 2}]))), + <<"a:::">> = iolist_to_binary(join(re:split("adl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[]))), + <<"bc">> = iolist_to_binary(join(re:split("bcl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[trim]))), <<"bc:::">> = iolist_to_binary(join(re:split("bcl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[{parts, - 2}]))), - <<"bc:::">> = iolist_to_binary(join(re:split("bcl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[]))), - <<"">> = iolist_to_binary(join(re:split("abc","\\sabc",[trim]))), + 2}]))), + <<"bc:::">> = iolist_to_binary(join(re:split("bcl","(?P<Name>a)?(?P<Name2>b)?(?(<Name>)c|d)*l",[]))), + <<"">> = iolist_to_binary(join(re:split("abc","\\sabc",[trim]))), <<":">> = iolist_to_binary(join(re:split("abc","\\sabc",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("abc","\\sabc",[]))), - <<"">> = iolist_to_binary(join(re:split("aa]]","[\\Qa]\\E]+",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("abc","\\sabc",[]))), + <<"">> = iolist_to_binary(join(re:split("aa]]","[\\Qa]\\E]+",[trim]))), <<":">> = iolist_to_binary(join(re:split("aa]]","[\\Qa]\\E]+",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aa]]","[\\Qa]\\E]+",[]))), - <<"">> = iolist_to_binary(join(re:split("aa]]","[\\Q]a\\E]+",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aa]]","[\\Qa]\\E]+",[]))), + <<"">> = iolist_to_binary(join(re:split("aa]]","[\\Q]a\\E]+",[trim]))), <<":">> = iolist_to_binary(join(re:split("aa]]","[\\Q]a\\E]+",[{parts, - 2}]))), - <<":">> = iolist_to_binary(join(re:split("aa]]","[\\Q]a\\E]+",[]))), - <<"1::::::2::::::3::::::4:abcd:abcd">> = iolist_to_binary(join(re:split("1234abcd","(?:((abcd))|(((?:(?:(?:(?:abc|(?:abcdef))))b)abcdefghi)abc)|((*ACCEPT)))",[trim]))), + 2}]))), + <<":">> = iolist_to_binary(join(re:split("aa]]","[\\Q]a\\E]+",[]))), + <<"1::::::2::::::3::::::4:abcd:abcd">> = iolist_to_binary(join(re:split("1234abcd","(?:((abcd))|(((?:(?:(?:(?:abc|(?:abcdef))))b)abcdefghi)abc)|((*ACCEPT)))",[trim]))), <<"1::::::234abcd">> = iolist_to_binary(join(re:split("1234abcd","(?:((abcd))|(((?:(?:(?:(?:abc|(?:abcdef))))b)abcdefghi)abc)|((*ACCEPT)))",[{parts, - 2}]))), - <<"1::::::2::::::3::::::4:abcd:abcd::::">> = iolist_to_binary(join(re:split("1234abcd","(?:((abcd))|(((?:(?:(?:(?:abc|(?:abcdef))))b)abcdefghi)abc)|((*ACCEPT)))",[]))), + 2}]))), + <<"1::::::2::::::3::::::4:abcd:abcd::::">> = iolist_to_binary(join(re:split("1234abcd","(?:((abcd))|(((?:(?:(?:(?:abc|(?:abcdef))))b)abcdefghi)abc)|((*ACCEPT)))",[]))), ok. run56() -> - <<"b:a:c">> = iolist_to_binary(join(re:split("baaaaaaaaac","(?1)(?#?'){8}(a)",[trim]))), + <<"b:a:c">> = iolist_to_binary(join(re:split("baaaaaaaaac","(?1)(?#?'){8}(a)",[trim]))), <<"b:a:c">> = iolist_to_binary(join(re:split("baaaaaaaaac","(?1)(?#?'){8}(a)",[{parts, - 2}]))), - <<"b:a:c">> = iolist_to_binary(join(re:split("baaaaaaaaac","(?1)(?#?'){8}(a)",[]))), - <<"a::b::c::d">> = iolist_to_binary(join(re:split("abcd","(?|(\\k'Pm')|(?'Pm'))",[trim]))), + 2}]))), + <<"b:a:c">> = iolist_to_binary(join(re:split("baaaaaaaaac","(?1)(?#?'){8}(a)",[]))), + <<"a::b::c::d">> = iolist_to_binary(join(re:split("abcd","(?|(\\k'Pm')|(?'Pm'))",[trim]))), <<"a::bcd">> = iolist_to_binary(join(re:split("abcd","(?|(\\k'Pm')|(?'Pm'))",[{parts, - 2}]))), - <<"a::b::c::d::">> = iolist_to_binary(join(re:split("abcd","(?|(\\k'Pm')|(?'Pm'))",[]))), - <<" :Fred:099">> = iolist_to_binary(join(re:split(" Fred:099","(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[,;:])(?=.{8,16})(?!.*[\\s])",[trim]))), + 2}]))), + <<"a::b::c::d::">> = iolist_to_binary(join(re:split("abcd","(?|(\\k'Pm')|(?'Pm'))",[]))), + <<" :Fred:099">> = iolist_to_binary(join(re:split(" Fred:099","(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[,;:])(?=.{8,16})(?!.*[\\s])",[trim]))), <<" :Fred:099">> = iolist_to_binary(join(re:split(" Fred:099","(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[,;:])(?=.{8,16})(?!.*[\\s])",[{parts, - 2}]))), - <<" :Fred:099">> = iolist_to_binary(join(re:split(" Fred:099","(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[,;:])(?=.{8,16})(?!.*[\\s])",[]))), - <<" ">> = iolist_to_binary(join(re:split(" X","(?=.*X)X$",[trim]))), + 2}]))), + <<" :Fred:099">> = iolist_to_binary(join(re:split(" Fred:099","(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[,;:])(?=.{8,16})(?!.*[\\s])",[]))), + <<" ">> = iolist_to_binary(join(re:split(" X","(?=.*X)X$",[trim]))), <<" :">> = iolist_to_binary(join(re:split(" X","(?=.*X)X$",[{parts, - 2}]))), - <<" :">> = iolist_to_binary(join(re:split(" X","(?=.*X)X$",[]))), + 2}]))), + <<" :">> = iolist_to_binary(join(re:split(" X","(?=.*X)X$",[]))), + <<">:::<">> = iolist_to_binary(join(re:split(">XXX<","X+(?#comment)?",[trim]))), + <<">:XX<">> = iolist_to_binary(join(re:split(">XXX<","X+(?#comment)?",[{parts, + 2}]))), + <<">:::<">> = iolist_to_binary(join(re:split(">XXX<","X+(?#comment)?",[]))), + <<":pokus">> = iolist_to_binary(join(re:split("pokus."," (?<word> \\w+ )* \\. ",[extended, + caseless, + trim]))), + <<":pokus:">> = iolist_to_binary(join(re:split("pokus."," (?<word> \\w+ )* \\. ",[extended, + caseless, + {parts, + 2}]))), + <<":pokus:">> = iolist_to_binary(join(re:split("pokus."," (?<word> \\w+ )* \\. ",[extended, + caseless]))), + <<"">> = iolist_to_binary(join(re:split("pokus.","(?(DEFINE) (?<word> \\w+ ) ) (?&word)* \\.",[extended, + caseless, + trim]))), + <<"::">> = iolist_to_binary(join(re:split("pokus.","(?(DEFINE) (?<word> \\w+ ) ) (?&word)* \\.",[extended, + caseless, + {parts, + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("pokus.","(?(DEFINE) (?<word> \\w+ ) ) (?&word)* \\.",[extended, + caseless]))), + <<"::pokus">> = iolist_to_binary(join(re:split("pokus.","(?(DEFINE) (?<word> \\w+ ) ) ( (?&word)* ) \\.",[extended, + caseless, + trim]))), + <<"::pokus:">> = iolist_to_binary(join(re:split("pokus.","(?(DEFINE) (?<word> \\w+ ) ) ( (?&word)* ) \\.",[extended, + caseless, + {parts, + 2}]))), + <<"::pokus:">> = iolist_to_binary(join(re:split("pokus.","(?(DEFINE) (?<word> \\w+ ) ) ( (?&word)* ) \\.",[extended, + caseless]))), + <<"">> = iolist_to_binary(join(re:split("pokus.","(?&word)* (?(DEFINE) (?<word> \\w+ ) ) \\.",[extended, + caseless, + trim]))), + <<"::">> = iolist_to_binary(join(re:split("pokus.","(?&word)* (?(DEFINE) (?<word> \\w+ ) ) \\.",[extended, + caseless, + {parts, + 2}]))), + <<"::">> = iolist_to_binary(join(re:split("pokus.","(?&word)* (?(DEFINE) (?<word> \\w+ ) ) \\.",[extended, + caseless]))), + <<":hokus">> = iolist_to_binary(join(re:split("pokus.hokus","(?&word)* \\. (?<word> \\w+ )",[extended, + caseless, + trim]))), + <<":hokus:">> = iolist_to_binary(join(re:split("pokus.hokus","(?&word)* \\. (?<word> \\w+ )",[extended, + caseless, + {parts, + 2}]))), + <<":hokus:">> = iolist_to_binary(join(re:split("pokus.hokus","(?&word)* \\. (?<word> \\w+ )",[extended, + caseless]))), ok. diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk index 07224afdc9..c2f586fef5 100644 --- a/lib/stdlib/vsn.mk +++ b/lib/stdlib/vsn.mk @@ -1 +1 @@ -STDLIB_VSN = 3.9 +STDLIB_VSN = 3.9.2 diff --git a/lib/tools/doc/src/notes.xml b/lib/tools/doc/src/notes.xml index fd41e2cbeb..09ae5ef04a 100644 --- a/lib/tools/doc/src/notes.xml +++ b/lib/tools/doc/src/notes.xml @@ -74,6 +74,21 @@ </section> +<section><title>Tools 3.1.0.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p><c>cover</c> would fail to start if two processes + tried to start it at the exact same time.</p> + <p> + Own Id: OTP-15813 Aux Id: ERL-943 </p> + </item> + </list> + </section> + +</section> + <section><title>Tools 3.1</title> <section><title>Fixed Bugs and Malfunctions</title> @@ -171,6 +186,21 @@ </section> +<section><title>Tools 2.11.2.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p><c>cover</c> would fail to start if two processes + tried to start it at the exact same time.</p> + <p> + Own Id: OTP-15813 Aux Id: ERL-943 </p> + </item> + </list> + </section> + +</section> + <section><title>Tools 2.11.2.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl index 8fe866cb69..2b3af417b6 100644 --- a/lib/tools/src/cover.erl +++ b/lib/tools/src/cover.erl @@ -196,6 +196,8 @@ start() -> receive {?SERVER,started} -> {ok,Pid}; + {?SERVER,{error,Error}} -> + {error,Error}; {'DOWN', Ref, _Type, _Object, Info} -> {error,Info} end, @@ -645,19 +647,31 @@ remote_reply(MainNode,Reply) -> %%%---------------------------------------------------------------------- init_main(Starter) -> - register(?SERVER,self()), - ?COVER_MAPPING_TABLE = ets:new(?COVER_MAPPING_TABLE, - [ordered_set, public, named_table]), - ?COVER_CLAUSE_TABLE = ets:new(?COVER_CLAUSE_TABLE, [set, public, - named_table]), - ?BINARY_TABLE = ets:new(?BINARY_TABLE, [set, public, named_table]), - ?COLLECTION_TABLE = ets:new(?COLLECTION_TABLE, [set, public, - named_table]), - ?COLLECTION_CLAUSE_TABLE = ets:new(?COLLECTION_CLAUSE_TABLE, [set, public, - named_table]), - ok = net_kernel:monitor_nodes(true), - Starter ! {?SERVER,started}, - main_process_loop(#main_state{}). + try register(?SERVER,self()) of + true -> + ?COVER_MAPPING_TABLE = ets:new(?COVER_MAPPING_TABLE, + [ordered_set, public, named_table]), + ?COVER_CLAUSE_TABLE = ets:new(?COVER_CLAUSE_TABLE, [set, public, + named_table]), + ?BINARY_TABLE = ets:new(?BINARY_TABLE, [set, public, named_table]), + ?COLLECTION_TABLE = ets:new(?COLLECTION_TABLE, [set, public, + named_table]), + ?COLLECTION_CLAUSE_TABLE = ets:new(?COLLECTION_CLAUSE_TABLE, + [set, public, named_table]), + ok = net_kernel:monitor_nodes(true), + Starter ! {?SERVER,started}, + main_process_loop(#main_state{}) + catch + error:badarg -> + %% The server's already registered; either report that it's already + %% started or try again if it died before we could find its pid. + case whereis(?SERVER) of + undefined -> + init_main(Starter); + Pid -> + Starter ! {?SERVER, {error, {already_started, Pid}}} + end + end. main_process_loop(State) -> receive diff --git a/lib/tools/test/cover_SUITE.erl b/lib/tools/test/cover_SUITE.erl index ee58fd7a10..462767f430 100644 --- a/lib/tools/test/cover_SUITE.erl +++ b/lib/tools/test/cover_SUITE.erl @@ -37,7 +37,7 @@ all() -> dont_reconnect_after_stop, stop_node_after_disconnect, export_import, otp_5031, otp_6115, otp_8270, otp_10979_hanging_node, otp_14817, - local_only], + local_only, startup_race], case whereis(cover_server) of undefined -> [coverage,StartStop ++ NoStartStop]; @@ -1775,7 +1775,32 @@ local_only(Config) -> {ok,Name} = test_server:start_node(?FUNCTION_NAME, slave, []), {error,local_only} = cover:start([Name]), test_server:stop_node(Name), + ok. +%% ERL-943; We should not crash on startup when multiple servers race to +%% register the server name. +startup_race(Config) when is_list(Config) -> + PidRefs = [spawn_monitor(fun() -> + case cover:start() of + {error, {already_started, _Pid}} -> + ok; + {ok, _Pid} -> + ok + end + end) || _<- lists:seq(1,8)], + startup_race_1(PidRefs). + +startup_race_1([{Pid, Ref} | PidRefs]) -> + receive + {'DOWN', Ref, process, Pid, normal} -> + startup_race_1(PidRefs); + {'DOWN', Ref, process, Pid, _Other} -> + ct:fail("Cover server crashed on startup.") + after 5000 -> + ct:fail("Timed out.") + end; +startup_race_1([]) -> + cover:stop(), ok. %%--Auxiliary------------------------------------------------------------ diff --git a/lib/wx/c_src/Makefile.in b/lib/wx/c_src/Makefile.in index 8ec64bea7e..daadf5e785 100644 --- a/lib/wx/c_src/Makefile.in +++ b/lib/wx/c_src/Makefile.in @@ -70,7 +70,7 @@ else RC_FILE = endif -WX_OBJECTS = $(GENERAL_O) $(GENERATED_O) $(RC_FILE) +WX_OBJECTS = $(GENERATED_O) $(GENERAL_O) $(RC_FILE) OBJECTS = $(WX_OBJECTS) $(GL_OBJECTS) @@ -86,7 +86,7 @@ LD = $(CXX) LDFLAGS = @LDFLAGS@ RESCOMP = @WX_RESCOMP@ -ifeq (@WX_HAVE_STATIC_LIBS@,true) +ifeq (@WX_HAVE_STATIC_LIBS@,true) OPT_WX_LIBS = @WX_LIBS_STATIC@ DEBUG_WX_LIBS = @DEBUG_WX_LIBS_STATIC@ else @@ -97,14 +97,16 @@ endif ifeq ($(TYPE),debug) WX_CFLAGS = @DEBUG_WX_CFLAGS@ CFLAGS = @DEBUG_CFLAGS@ -WX_CXX_FLAGS = @DEBUG_WX_CXXFLAGS@ +WX_CXX_FLAGS = @DEBUG_WX_CXXFLAGS@ CXX_FLAGS = @DEBUG_CXXFLAGS@ +CXX_NO_OPT_FLAGS = @DEBUG_CXXFLAGS@ WX_LIBS = $(DEBUG_WX_LIBS) else WX_CFLAGS = @WX_CFLAGS@ CFLAGS = @CFLAGS@ -WX_CXX_FLAGS = @WX_CXXFLAGS@ +WX_CXX_FLAGS = @WX_CXXFLAGS@ CXX_FLAGS = @CXXFLAGS@ +CXX_NO_OPT_FLAGS = @CXXNOOPTFLAGS@ WX_LIBS = $(OPT_WX_LIBS) endif @@ -113,6 +115,7 @@ GL_LIBS = @GL_LIBS@ CC_O = $(V_CC) -c $(CFLAGS) $(WX_CFLAGS) $(COMMON_CFLAGS) OBJC_CC_O = $(OBJC_CC) -c $(CFLAGS) $(OBJC_CFLAGS) $(WX_CFLAGS) $(COMMON_CFLAGS) CXX_O = $(V_CXX) -c $(CXX_FLAGS) $(WX_CXX_FLAGS) $(COMMON_CFLAGS) +CXX_O_NO_OPT = $(V_CXX) -c $(CXX_NO_OPT_FLAGS) $(WX_CXX_FLAGS) $(COMMON_CFLAGS) # Targets @@ -152,6 +155,10 @@ $(SYS_TYPE)/wxe_ps_init.o: wxe_ps_init.c $(V_at)mkdir -p $(SYS_TYPE) $(cc_verbose)$(OBJC_CC_O) $< -o $@ +$(SYS_TYPE)/wxe_funcs.o: gen/wxe_funcs.cpp + $(V_at)mkdir -p $(SYS_TYPE) + $(CXX_O_NO_OPT) $< -o $@ + $(SYS_TYPE)/%.o: gen/%.cpp $(V_at)mkdir -p $(SYS_TYPE) $(CXX_O) $< -o $@ diff --git a/lib/wx/c_src/wxe_driver.c b/lib/wx/c_src/wxe_driver.c index c9d299e0df..b94ec2f32d 100644 --- a/lib/wx/c_src/wxe_driver.c +++ b/lib/wx/c_src/wxe_driver.c @@ -214,7 +214,7 @@ standard_outputv(ErlDrvData drv_data, ErlIOVec* ev) if(binref == NULL) { /* realloc */ max = sd->max_bins + DEF_BINS; - driver_realloc(sd->bin, sizeof(WXEBinRef)*max); + sd->bin = driver_realloc(sd->bin, sizeof(WXEBinRef)*max); for(i=sd->max_bins; i < max; i++) { sd->bin[i].from = 0; } diff --git a/lib/wx/configure.in b/lib/wx/configure.in index dbe237cd74..f35e6cdbd0 100644 --- a/lib/wx/configure.in +++ b/lib/wx/configure.in @@ -30,17 +30,11 @@ if test -f ./CONF_INFO; then fi if test -z "$ERL_TOP" || test ! -d $ERL_TOP ; then - AC_CONFIG_AUX_DIRS(autoconf) - WX_BUILDING_INSIDE_ERLSRC=false + AC_MSG_ERROR([ERL_TOP is not set]) else erl_top=${ERL_TOP} - if test -d $erl_top/erts/autoconf; then - AC_CONFIG_AUX_DIRS($erl_top/erts/autoconf) - WX_BUILDING_INSIDE_ERLSRC=true - else - AC_CONFIG_AUX_DIRS(autoconf) - WX_BUILDING_INSIDE_ERLSRC=false - fi + AC_CONFIG_AUX_DIRS($erl_top/erts/autoconf) + WX_BUILDING_INSIDE_ERLSRC=true fi if test "X$host" != "Xfree_source" -a "X$host" != "Xwin32"; then @@ -67,6 +61,20 @@ AC_PROG_CXX AC_PROG_RANLIB AC_PROG_CPP +AC_LANG_PUSH([C++]) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM( + [[#ifndef __cplusplus + #error "broken C++" + #endif]])],, + [CXX=;]) +AC_LANG_POP([C++]) +if test "X$CXX" = X ; then + echo "Can not find C++ compiler" >> ./CONF_INFO + WXERL_CAN_BUILD_DRIVER=false + AC_MSG_WARN([Can not find C++ compiler]) +fi +WXERL_CAN_BUILD_DRIVER=false + AC_MSG_NOTICE(Building for [$host_os]) WXERL_CAN_BUILD_DRIVER=true @@ -139,13 +147,9 @@ case $host_os in if test X$APPLE_CC = X -o X$APPLE_CXX = X; then AC_MSG_RESULT([no]) dnl Complete failure, we cannot build Cocoa code - if test X"$WX_BUILDING_INSIDE_ERLSRC" != X"true" ; then - AC_MSG_ERROR([Can not find compiler to compile Cocoa applications]) - else - echo "Can not find compiler to compile Cocoa applications" > ./CONF_INFO - WXERL_CAN_BUILD_DRIVER=false - AC_MSG_WARN([Can not find compiler to compile Cocoa applications]) - fi + echo "Can not find compiler to compile Cocoa applications" >> ./CONF_INFO + WXERL_CAN_BUILD_DRIVER=false + AC_MSG_WARN([Can not find compiler to compile Cocoa applications]) WXERL_CAN_BUILD_DRIVER=false else dnl We think we found an Apple compiler and will add @@ -225,11 +229,21 @@ case $host_os in ;; *) DEBUG_CFLAGS="-g -Wall -fPIC $CFLAGS -DDEBUG" - CFLAGS="-g -Wall -O2 -fPIC $CFLAGS -fomit-frame-pointer -fno-strict-aliasing" + CFLAGS="-Wall -fPIC $CFLAGS -fomit-frame-pointer -fno-strict-aliasing" ;; esac -dnl +dnl +dnl Use -O1 -fno-move-loop-invariants for wxe_funcs.cpp to reduce +dnl compilation time +dnl + +if test "x$GCC" = xyes -a X"$host_os" != X"win32" ; then + CXXNOOPT="-O1" + LM_TRY_ENABLE_CFLAG([-fno-move-loop-invariants], [CXXNOOPT]) +fi + +dnl dnl Opengl tests dnl @@ -251,11 +265,13 @@ if test X"$host_os" != X"win32" ; then AC_CHECK_HEADERS([GL/gl.h]) if test X"$ac_cv_header_GL_gl_h" != Xyes ; then AC_MSG_WARN([No OpenGL headers found, wx will NOT be usable]) + echo "No OpenGL headers found, wx will NOT be usable" >> ./CONF_INFO + WXERL_CAN_BUILD_DRIVER=false CPPFLAGS="$saved_CPPFLAGS" - else + else GL_LIBS="-L/usr/local/lib $GL_LIBS" fi - else + else GL_LIBS="-L/usr/X11R6/lib $GL_LIBS" fi fi @@ -270,6 +286,8 @@ if test X"$host_os" != X"win32" ; then test X"$ac_cv_header_OpenGL_glu_h" != Xyes then AC_MSG_WARN([No GLU headers found, wx will NOT be usable]) + echo "No GLU headers (glu.h) found, wx will NOT be usable" >> ./CONF_INFO + WXERL_CAN_BUILD_DRIVER=false fi else AC_CHECK_HEADERS([gl/glu.h],[],[],[#include <windows.h>]) @@ -280,47 +298,17 @@ AC_SUBST(GL_LIBS) DEBUG_CXXFLAGS="$CXXFLAGS $DEBUG_CFLAGS $CPPFLAGS" DEBUG_CFLAGS="$DEBUG_CFLAGS $CPPFLAGS $C_ONLY_FLAGS" -CXXFLAGS="$CXXFLAGS $CFLAGS $CPPFLAGS" +CXXNOOPTFLAGS="$CXXFLAGS $CFLAGS $CPPFLAGS $CXXNOOPT" +CXXFLAGS="$CXXFLAGS $CFLAGS $CPPFLAGS" CFLAGS="$CFLAGS $CPPFLAGS $C_ONLY_FLAGS" AC_SUBST(DEBUG_CFLAGS) AC_SUBST(DEBUG_CXXFLAGS) - -if test X"$WX_BUILDING_INSIDE_ERLSRC" != X"true" ; then - AC_MSG_CHECKING(for erl) - if test X$ERL != X; then - AC_MSG_RESULT([yes; using $ERL]) - else - type erl >/dev/null 2>&1 - if test $? -eq 0 ; then - ERL=erl - AC_MSG_RESULT([yes]) - else - AC_MSG_ERROR([Cannot find erl in path]) - fi - fi - AC_MSG_CHECKING(for erlc) - if test X$ERLC != X; then - AC_MSG_RESULT([yes; using $ERLC]) - else - type erlc >/dev/null 2>&1 - if test $? -eq 0 ; then - ERLC=erlc - AC_MSG_RESULT([yes]) - else - AC_MSG_ERROR([Cannot find erlc in path]) - fi - fi - ERLANG_ROOT_DIR=`erl -noshell -eval 'io:format("~s~n",[[code:root_dir()]])' -s erlang halt` - AC_MSG_NOTICE(ERL ROOT DIR: [$ERLANG_ROOT_DIR]) - ERLWX_VSN=`grep WX_VSN $srcdir/vsn.mk | sed 's/^.*=[ ]*//'` -else - ERLC=erlc - ERL=erl - ERLANG_ROOT_DIR=$ERL_TOP - AC_SUBST(ERLC) -fi +ERLC=erlc +ERL=erl +ERLANG_ROOT_DIR=$ERL_TOP +AC_SUBST(ERLC) AC_SUBST(WX_BUILDING_INSIDE_ERLSRC) AC_SUBST(ERLANG_ROOT_DIR) @@ -329,7 +317,7 @@ dnl dnl Check for wxwidgets dnl if test "$cross_compiling" = "yes"; then - echo "Cross compilation of the wx driver is not supported yet, wx will NOT be usable" > ./CONF_INFO + echo "Cross compilation of the wx driver is not supported yet, wx will NOT be usable" >> ./CONF_INFO WXERL_CAN_BUILD_DRIVER=false elif test X"$MIXED_CYGWIN_VC" = X"no" -a X"$MIXED_MSYS_VC" = X"no"; then WX_VERSION=`wx-config --version` @@ -398,13 +386,9 @@ define(wx_warn_text,[ wxWidgets version is $reqwx or above.]) if test "$wxWin" != 1; then - if test X"$WX_BUILDING_INSIDE_ERLSRC" != X"true" ; then - AC_MSG_ERROR([wx_warn_text]) - else - echo "wxWidgets not found, wx will NOT be usable" > ./CONF_INFO - WXERL_CAN_BUILD_DRIVER=false - AC_MSG_WARN([wx_warn_text]) - fi + echo "wxWidgets not found, wx will NOT be usable" >> ./CONF_INFO + WXERL_CAN_BUILD_DRIVER=false + AC_MSG_WARN([wx_warn_text]) fi else AC_MSG_CHECKING(for wxWidgets in standard locations) @@ -502,13 +486,9 @@ else if test -z "$WX_LIBS_STATIC"; then AC_MSG_RESULT([failed]) - if test X"$WX_BUILDING_INSIDE_ERLSRC" != X"true" ; then - AC_MSG_ERROR([Cannot find core lib version for wxWidgets]) - else - echo "No usable wxWidgets not found, wx will not be useable" > ./CONF_INFO - WXERL_CAN_BUILD_DRIVER=false - AC_MSG_WARN([Cannot find core lib version for wxWidgets]) - fi + echo "No usable wxWidgets not found, wx will not be useable" >> ./CONF_INFO + WXERL_CAN_BUILD_DRIVER=false + AC_MSG_WARN([Cannot find core lib version for wxWidgets]) fi WX_HAVE_STATIC_LIBS=true AC_SUBST(WX_CFLAGS) @@ -550,8 +530,8 @@ AC_MSG_RESULT($HAVE_GL_SUPPORT) AC_SUBST(HAVE_GL_SUPPORT) if test X"$HAVE_GL_SUPPORT" != X"yes" ; then - echo "wxWidgets don't have gl support, wx will NOT be useable" > ./CONF_INFO - WXERL_CAN_BUILD_DRIVER=false + echo "wxWidgets don't have gl support, wx will NOT be useable" >> ./CONF_INFO + WXERL_CAN_BUILD_DRIVER=false fi dnl Check for GLintptr @@ -610,7 +590,7 @@ dnl AC_CHECK_HEADERS([wx/stc/stc.h], [], [WXERL_CAN_BUILD_DRIVER=false - echo "wxWidgets don't have wxStyledTextControl (stc.h), wx will NOT be useable" > ./CONF_INFO + echo "wxWidgets don't have wxStyledTextControl (stc.h), wx will NOT be useable" >> ./CONF_INFO AC_MSG_WARN([Can not find wx/stc/stc.h $CXXFLAGS]) ], [#ifdef WIN32 @@ -670,9 +650,9 @@ AC_LANG_POP(C++) AC_MSG_RESULT($CAN_LINK_WX) if test X"$CAN_LINK_WX" != X"yes" ; then - echo "Can not link the wx driver, wx will NOT be useable" > ./CONF_INFO - WXERL_CAN_BUILD_DRIVER=false - AC_MSG_WARN([Can not link wx program are all developer packages installed?]) + echo "Can not link the wx driver, wx will NOT be useable" >> ./CONF_INFO + WXERL_CAN_BUILD_DRIVER=false + AC_MSG_WARN([Can not link wx program are all developer packages installed?]) fi fi dnl - if test "$WXERL_CAN_BUILD_DRIVER" != "false" @@ -721,6 +701,7 @@ esac AC_SUBST(SO_EXT) AC_SUBST(RUN_ERL) +AC_SUBST(CXXNOOPTFLAGS) if test X"$WX_BUILDING_INSIDE_ERLSRC" != X"true" ; then @@ -737,12 +718,6 @@ CONFIG_STATUS=$WXERL_SYS_TYPE/config.status dnl -if test X"$WX_BUILDING_INSIDE_ERLSRC" != X"true" ; then - if test X"$WXERL_CAN_BUILD_DRIVER" != X"true" ; then - AC_MSG_ERROR([Cannot build wxErlang driver, see ./CONF_INFO for information]) - fi -fi - AC_CONFIG_FILES([ config.mk c_src/Makefile @@ -750,20 +725,9 @@ AC_CONFIG_FILES([ AC_OUTPUT -if test X"$WX_BUILDING_INSIDE_ERLSRC" != X"true" ; then - AC_MSG_NOTICE() - AC_MSG_NOTICE(--------------------------------------------------) - AC_MSG_NOTICE(Using erlang compiler: [$ERLC]) - AC_MSG_NOTICE(wxErlang Install in: [$ERLANG_ROOT_DIR/lib/wx-$ERLWX_VSN]) - AC_MSG_NOTICE(Erlang driver in priv/[$WXERL_SYS_TYPE]/wxe_driver[$SO_EXT]) - AC_MSG_NOTICE(--------------------------------------------------) -fi - -if test X"$WX_BUILDING_INSIDE_ERLSRC" = X"true" ; then - CORES=`ls core* 2>/dev/null` - if test X"$CORES" != X"" ; then - echo "Configure dumped core files" > ignore_core_files - fi +CORES=`ls core* 2>/dev/null` +if test X"$CORES" != X"" ; then + echo "Configure dumped core files" > ignore_core_files fi diff --git a/lib/xmerl/doc/src/notes.xml b/lib/xmerl/doc/src/notes.xml index 37973d0dba..9fb4a430e5 100644 --- a/lib/xmerl/doc/src/notes.xml +++ b/lib/xmerl/doc/src/notes.xml @@ -47,6 +47,23 @@ </section> +<section><title>Xmerl 1.3.20.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + <c>xmerl_sax_parser</c> crashed during charset detection + when the xml declarations attribute values was missing + the closing quotation (' or ").</p> + <p> + Own Id: OTP-15826</p> + </item> + </list> + </section> + +</section> + <section><title>Xmerl 1.3.20</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 fe836fd8cd..2767d02552 100644 --- a/lib/xmerl/src/xmerl_sax_parser.erl +++ b/lib/xmerl/src/xmerl_sax_parser.erl @@ -369,8 +369,8 @@ parse_eq(_, State) -> %%---------------------------------------------------------------------- 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(<<C, Rest/binary>>, State) when C == $'; C == $" -> + parse_value_1(Rest, C, [], State); parse_value(_, State) -> ?fatal_error(State, "\', \" or whitespace expected"). @@ -383,10 +383,12 @@ parse_value(_, State) -> %% Rest = binary() %% Description: Parsing an attribute value from the stream. %%---------------------------------------------------------------------- -parse_value_1(<<Stop, Rest/binary>>, Stop, Acc) -> +parse_value_1(<<Stop, Rest/binary>>, Stop, Acc, _State) -> {lists:reverse(Acc), Rest}; -parse_value_1(<<C, Rest/binary>>, Stop, Acc) -> - parse_value_1(Rest, Stop, [C |Acc]). +parse_value_1(<<C, Rest/binary>>, Stop, Acc, State) -> + parse_value_1(Rest, Stop, [C |Acc], State); +parse_value_1(_, _Stop, _Acc, State) -> + ?fatal_error(State, "end of input and no \' or \" found"). %%====================================================================== %% Default functions |