aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/common_test/doc/src/ct.xml36
-rw-r--r--lib/common_test/src/ct.erl35
-rw-r--r--lib/common_test/src/ct_config.erl1
-rw-r--r--lib/common_test/src/ct_default_gl.erl1
-rw-r--r--lib/common_test/src/ct_event.erl1
-rw-r--r--lib/common_test/src/ct_gen_conn.erl9
-rw-r--r--lib/common_test/src/ct_hooks_lock.erl1
-rw-r--r--lib/common_test/src/ct_logs.erl20
-rw-r--r--lib/common_test/src/ct_master.erl2
-rw-r--r--lib/common_test/src/ct_master_event.erl1
-rw-r--r--lib/common_test/src/ct_master_logs.erl1
-rw-r--r--lib/common_test/src/ct_repeat.erl2
-rw-r--r--lib/common_test/src/ct_run.erl14
-rw-r--r--lib/common_test/src/ct_slave.erl1
-rw-r--r--lib/common_test/src/ct_telnet_client.erl1
-rw-r--r--lib/common_test/src/ct_util.erl68
-rw-r--r--lib/common_test/src/ct_webtool.erl1
-rw-r--r--lib/common_test/src/ct_webtool_sup.erl1
-rw-r--r--lib/common_test/src/cth_log_redirect.erl1
-rw-r--r--lib/common_test/src/test_server.erl10
-rw-r--r--lib/common_test/src/test_server_ctrl.erl37
-rw-r--r--lib/common_test/src/test_server_gl.erl1
-rw-r--r--lib/common_test/src/test_server_io.erl9
-rw-r--r--lib/common_test/src/test_server_node.erl1
-rw-r--r--lib/common_test/src/test_server_sup.erl3
-rw-r--r--lib/common_test/src/vts.erl2
-rw-r--r--lib/common_test/test/Makefile3
-rw-r--r--lib/common_test/test/ct_auto_clean_SUITE.erl262
-rw-r--r--lib/common_test/test/ct_auto_clean_SUITE_data/ac_SUITE.erl181
-rw-r--r--lib/common_test/test/ct_auto_clean_SUITE_data/cth_auto_clean.erl214
-rw-r--r--lib/common_test/test/test_server_test_lib.erl2
-rw-r--r--lib/compiler/src/beam_asm.erl2
-rw-r--r--lib/compiler/src/beam_dead.erl20
-rw-r--r--lib/compiler/src/core_lint.erl6
-rw-r--r--lib/compiler/src/sys_core_fold.erl10
-rw-r--r--lib/compiler/src/v3_codegen.erl460
-rw-r--r--lib/compiler/src/v3_core.erl4
-rw-r--r--lib/compiler/src/v3_kernel.erl17
-rw-r--r--lib/compiler/src/v3_kernel.hrl2
-rw-r--r--lib/compiler/test/bs_match_SUITE.erl18
-rw-r--r--lib/compiler/test/match_SUITE.erl74
-rw-r--r--lib/compiler/test/receive_SUITE.erl8
-rw-r--r--lib/crypto/c_src/crypto.c60
-rw-r--r--lib/crypto/doc/src/crypto.xml55
-rw-r--r--lib/crypto/src/crypto.erl52
-rw-r--r--lib/crypto/test/engine_SUITE.erl131
-rw-r--r--lib/debugger/src/dbg_wx_win.erl2
-rw-r--r--lib/diameter/doc/src/seealso.ent2
-rw-r--r--lib/diameter/src/diameter.appup.src6
-rw-r--r--lib/diameter/vsn.mk2
-rw-r--r--lib/inets/src/http_client/httpc.erl4
-rw-r--r--lib/inets/test/httpc_SUITE.erl31
-rw-r--r--lib/kernel/doc/src/file.xml236
-rw-r--r--lib/kernel/doc/src/net_kernel.xml7
-rw-r--r--lib/kernel/src/Makefile17
-rw-r--r--lib/kernel/src/file.erl61
-rw-r--r--lib/kernel/src/file_int.hrl33
-rw-r--r--lib/kernel/src/file_io_server.erl52
-rw-r--r--lib/kernel/src/file_server.erl137
-rw-r--r--lib/kernel/src/inet_int.hrl8
-rw-r--r--lib/kernel/src/kernel.app.src7
-rw-r--r--lib/kernel/src/raw_file_io.erl75
-rw-r--r--lib/kernel/src/raw_file_io_compressed.erl134
-rw-r--r--lib/kernel/src/raw_file_io_deflate.erl159
-rw-r--r--lib/kernel/src/raw_file_io_delayed.erl320
-rw-r--r--lib/kernel/src/raw_file_io_inflate.erl261
-rw-r--r--lib/kernel/src/raw_file_io_list.erl128
-rw-r--r--lib/kernel/src/raw_file_io_raw.erl25
-rw-r--r--lib/kernel/test/disk_log_SUITE.erl119
-rw-r--r--lib/kernel/test/erl_prim_loader_SUITE.erl3
-rw-r--r--lib/kernel/test/file_SUITE.erl220
-rw-r--r--lib/kernel/test/file_name_SUITE.erl26
-rw-r--r--lib/kernel/test/kernel_bench.spec1
-rw-r--r--lib/kernel/test/prim_file_SUITE.erl731
-rw-r--r--lib/kernel/test/sendfile_SUITE.erl138
-rw-r--r--lib/mnesia/src/mnesia.erl3
-rw-r--r--lib/mnesia/src/mnesia_log.erl4
-rw-r--r--lib/public_key/doc/src/public_key.xml112
-rw-r--r--lib/public_key/src/pubkey_cert.erl152
-rw-r--r--lib/public_key/src/public_key.erl27
-rw-r--r--lib/public_key/test/public_key_SUITE.erl60
-rw-r--r--lib/runtime_tools/doc/src/LTTng.xml34
-rw-r--r--lib/runtime_tools/examples/efile_drv.d105
-rw-r--r--lib/runtime_tools/examples/efile_drv.systemtap113
-rw-r--r--lib/sasl/src/systools_make.erl2
-rw-r--r--lib/ssl/doc/src/ssl_distribution.xml128
-rw-r--r--lib/ssl/src/dtls_connection.erl9
-rw-r--r--lib/ssl/src/dtls_handshake.erl2
-rw-r--r--lib/ssl/src/inet_tls_dist.erl64
-rw-r--r--lib/ssl/src/ssl.erl39
-rw-r--r--lib/ssl/src/ssl_cipher.erl1409
-rw-r--r--lib/ssl/src/ssl_connection.erl183
-rw-r--r--lib/ssl/src/ssl_dist_sup.erl65
-rw-r--r--lib/ssl/src/ssl_handshake.erl45
-rw-r--r--lib/ssl/src/tls_connection.erl9
-rw-r--r--lib/ssl/src/tls_handshake.erl2
-rw-r--r--lib/ssl/test/Makefile6
-rw-r--r--lib/ssl/test/ssl.spec3
-rw-r--r--lib/ssl/test/ssl_bench.spec2
-rw-r--r--lib/ssl/test/ssl_bench_SUITE.erl64
-rw-r--r--lib/ssl/test/ssl_bench_test_lib.erl75
-rw-r--r--lib/ssl/test/ssl_dist_SUITE.erl323
-rw-r--r--lib/ssl/test/ssl_dist_bench_SUITE.erl481
-rw-r--r--lib/ssl/test/ssl_dist_test_lib.erl343
-rw-r--r--lib/ssl/test/ssl_dist_test_lib.hrl26
-rw-r--r--lib/ssl/test/ssl_test_lib.erl68
-rw-r--r--lib/stdlib/doc/src/unicode.xml9
-rw-r--r--lib/stdlib/src/base64.erl597
-rw-r--r--lib/stdlib/src/gen.erl1
-rw-r--r--lib/stdlib/test/base64_SUITE.erl109
-rw-r--r--lib/stdlib/test/filelib_SUITE.erl22
-rw-r--r--lib/stdlib/test/qlc_SUITE.erl23
-rw-r--r--lib/stdlib/test/stdlib_bench_SUITE.erl107
-rw-r--r--lib/tools/test/fprof_SUITE.erl4
114 files changed, 6692 insertions, 2924 deletions
diff --git a/lib/common_test/doc/src/ct.xml b/lib/common_test/doc/src/ct.xml
index 1a3cfdb0c5..afd8741cd1 100644
--- a/lib/common_test/doc/src/ct.xml
+++ b/lib/common_test/doc/src/ct.xml
@@ -1060,6 +1060,42 @@
</desc>
</func>
+ <func>
+ <name>remaining_test_procs() -&gt; {TestProcs,SharedGL,OtherGLs}</name>
+ <fsummary>>This function will return the identity of test- and group
+ leader processes that are still running at the time of this call.</fsummary>
+ <type>
+ <v>TestProcs = [{pid(),GL}]</v>
+ <v>GL = pid()</v>
+ <v>SharedGL = pid()</v>
+ <v>OtherGLs = [pid()]</v>
+ </type>
+ <desc><marker id="remaining_test_procs-0"/>
+ <p>This function will return the identity of test- and group
+ leader processes that are still running at the time of this call.
+ <c>TestProcs</c> are processes in the system that have a Common Test IO
+ process as group leader. <c>SharedGL</c> is the central Common Test
+ IO process, responsible for printing to log files for configuration
+ functions and sequentially executing test cases. <c>OtherGLs</c> are
+ Common Test IO processes that print to log files for test cases
+ in parallel test case groups.</p>
+ <p>The process information returned by this function may be
+ used to locate and terminate remaining processes after tests have
+ finished executing. The function would typically by called from
+ Common Test Hook functions.</p>
+ <p>Note that processes that execute configuration functions or
+ test cases are never included in <c>TestProcs</c>. It is therefore safe
+ to use post configuration hook functions (such as post_end_per_suite,
+ post_end_per_group, post_end_per_testcase) to terminate all processes
+ in <c>TestProcs</c> that have the current group leader process as its group
+ leader.</p>
+ <p>Note also that the shared group leader (<c>SharedGL</c>) must never be
+ terminated by the user, only by Common Test. Group leader processes
+ for parallel test case groups (<c>OtherGLs</c>) may however be terminated
+ in post_end_per_group hook functions.</p>
+ </desc>
+ </func>
+
<func>
<name>remove_config(Callback, Config) -&gt; ok</name>
<fsummary>Removes configuration variables (together with
diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl
index 875301a8b2..69e371a30f 100644
--- a/lib/common_test/src/ct.erl
+++ b/lib/common_test/src/ct.erl
@@ -89,6 +89,8 @@
-export([get_target_name/1]).
-export([parse_table/1, listenv/1]).
+-export([remaining_test_procs/0]).
+
%%----------------------------------------------------------------------
%% Exported types
%%----------------------------------------------------------------------
@@ -1474,3 +1476,36 @@ continue() ->
%%% in order to let the test case proceed.</p>
continue(TestCase) ->
test_server:continue(TestCase).
+
+
+%%%-----------------------------------------------------------------
+%%% @spec remaining_test_procs() -> {TestProcs,SharedGL,OtherGLs}
+%%% TestProcs = [{pid(),GL}]
+%%% GL = SharedGL = pid()
+%%% OtherGLs = [pid()]
+%%%
+%%% @doc <p>This function will return the identity of test- and group
+%%% leader processes that are still running at the time of this call.
+%%% TestProcs are processes in the system that have a Common Test IO
+%%% process as group leader. SharedGL is the central Common Test
+%%% IO process, responsible for printing to log files for configuration
+%%% functions and sequentially executing test cases. OtherGLs are
+%%% Common Test IO processes that print to log files for test cases
+%%% in parallel test case groups.</p>
+%%% <p>The process information returned by this function may be
+%%% used to locate and terminate remaining processes after tests have
+%%% finished executing. The function would typically by called from
+%%% Common Test Hook functions.</p>
+%%% <p>Note that processes that execute configuration functions or
+%%% test cases are never included in TestProcs. It is therefore safe
+%%% to use post configuration hook functions (such as post_end_per_suite,
+%%% post_end_per_group, post_end_per_testcase) to terminate all processes
+%%% in TestProcs that have the current group leader process as its group
+%%% leader.</p>
+%%% <p>Note also that the shared group leader (SharedGL) must never be
+%%% terminated by the user, only by Common Test. Group leader processes
+%%% for parallel test case groups (OtherGLs) may however be terminated
+%%% in post_end_per_group hook functions.</p>
+%%%
+remaining_test_procs() ->
+ ct_util:remaining_test_procs().
diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl
index b3f983dd46..6c87b11f8d 100644
--- a/lib/common_test/src/ct_config.erl
+++ b/lib/common_test/src/ct_config.erl
@@ -81,6 +81,7 @@ start(Mode) ->
do_start(Parent) ->
process_flag(trap_exit,true),
+ ct_util:mark_process(),
register(ct_config_server,self()),
ct_util:create_table(?attr_table,bag,#ct_conf.key),
{ok,StartDir} = file:get_cwd(),
diff --git a/lib/common_test/src/ct_default_gl.erl b/lib/common_test/src/ct_default_gl.erl
index d1b52e5f4f..9ae430c546 100644
--- a/lib/common_test/src/ct_default_gl.erl
+++ b/lib/common_test/src/ct_default_gl.erl
@@ -55,6 +55,7 @@ stop() ->
init([ParentGL]) ->
register(?MODULE, self()),
+ ct_util:mark_process(),
{ok,#{parent_gl_pid => ParentGL,
parent_gl_monitor => erlang:monitor(process,ParentGL)}}.
diff --git a/lib/common_test/src/ct_event.erl b/lib/common_test/src/ct_event.erl
index 1a0ee4f3cd..8b5bba7600 100644
--- a/lib/common_test/src/ct_event.erl
+++ b/lib/common_test/src/ct_event.erl
@@ -137,6 +137,7 @@ is_alive() ->
%% this function is called to initialize the event handler.
%%--------------------------------------------------------------------
init(RecvPids) ->
+ ct_util:mark_process(),
%% RecvPids = [{RecvTag,Pid}]
{ok,#state{receivers=RecvPids}}.
diff --git a/lib/common_test/src/ct_gen_conn.erl b/lib/common_test/src/ct_gen_conn.erl
index badb7c52ae..456bfd8bd1 100644
--- a/lib/common_test/src/ct_gen_conn.erl
+++ b/lib/common_test/src/ct_gen_conn.erl
@@ -186,9 +186,11 @@ end_log() ->
do_within_time(Fun,Timeout) ->
Self = self(),
Silent = get(silent),
- TmpPid = spawn_link(fun() -> put(silent,Silent),
- R = Fun(),
- Self ! {self(),R}
+ TmpPid = spawn_link(fun() ->
+ ct_util:mark_process(),
+ put(silent,Silent),
+ R = Fun(),
+ Self ! {self(),R}
end),
ConnPid = get(conn_pid),
receive
@@ -301,6 +303,7 @@ return({To,Ref},Result) ->
init_gen(Parent,Opts) ->
process_flag(trap_exit,true),
+ ct_util:mark_process(),
put(silent,false),
try (Opts#gen_opts.callback):init(Opts#gen_opts.name,
Opts#gen_opts.address,
diff --git a/lib/common_test/src/ct_hooks_lock.erl b/lib/common_test/src/ct_hooks_lock.erl
index fea298e535..a82be288e1 100644
--- a/lib/common_test/src/ct_hooks_lock.erl
+++ b/lib/common_test/src/ct_hooks_lock.erl
@@ -78,6 +78,7 @@ release() ->
%% @doc Initiates the server
init(Id) ->
+ ct_util:mark_process(),
{ok, #state{ id = Id }}.
%% @doc Handling call messages
diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl
index 028c265420..9861b1e521 100644
--- a/lib/common_test/src/ct_logs.erl
+++ b/lib/common_test/src/ct_logs.erl
@@ -666,6 +666,7 @@ log_timestamp({MS,S,US}) ->
logger(Parent, Mode, Verbosity) ->
register(?MODULE,self()),
+ ct_util:mark_process(),
%%! Below is a temporary workaround for the limitation of
%%! max one test run per second.
%%! --->
@@ -1004,6 +1005,7 @@ print_to_log(async, FromPid, Category, TCGL, Content, EscChars, State) ->
if FromPid /= TCGL ->
IoFun = create_io_fun(FromPid, CtLogFd, EscChars),
fun() ->
+ ct_util:mark_process(),
test_server:permit_io(TCGL, self()),
%% Since asynchronous io gets can get buffered if
@@ -1035,6 +1037,7 @@ print_to_log(async, FromPid, Category, TCGL, Content, EscChars, State) ->
end;
true ->
fun() ->
+ ct_util:mark_process(),
unexpected_io(FromPid, Category, ?MAX_IMPORTANCE,
Content, CtLogFd, EscChars)
end
@@ -3002,6 +3005,7 @@ simulate() ->
S = self(),
Pid = spawn(fun() ->
register(?MODULE,self()),
+ ct_util:mark_process(),
S ! {self(),started},
simulate_logger_loop()
end),
@@ -3129,8 +3133,8 @@ locate_priv_file(FileName) ->
filename:join(get(ct_run_dir), FileName);
_ ->
%% executed on other process than ct_logs
- {ok,RunDir} = get_log_dir(true),
- filename:join(RunDir, FileName)
+ {ok,LogDir} = get_log_dir(true),
+ filename:join(LogDir, FileName)
end,
case filelib:is_file(PrivResultFile) of
true ->
@@ -3212,6 +3216,10 @@ get_ts_html_wrapper(TestName, Logdir, PrintLabel, Cwd, TableCols, Encoding) ->
?all_runs_name), Cwd),
TestIndex = make_relative(filename:join(filename:dirname(CtLogdir),
?index_name), Cwd),
+ LatestTest = make_relative(filename:join(filename:dirname(CtLogdir),
+ ?suitelog_name++".latest.html"),
+ Cwd),
+
case Basic of
true ->
TileFile = filename:join(filename:join(CTPath,"priv"),"tile1.jpg"),
@@ -3238,7 +3246,9 @@ get_ts_html_wrapper(TestName, Logdir, PrintLabel, Cwd, TableCols, Encoding) ->
"<a href=\"", uri(AllRuns),
"\">Test run history\n</a> | ",
"<a href=\"", uri(TestIndex),
- "\">Top level test index\n</a>\n</p>\n",
+ "\">Top level test index\n</a> | ",
+ "<a href=\"", uri(LatestTest),
+ "\">Latest test result</a>\n</p>\n",
Copyright,"</center>\n</body>\n</html>\n"]};
_ ->
Copyright =
@@ -3285,7 +3295,9 @@ get_ts_html_wrapper(TestName, Logdir, PrintLabel, Cwd, TableCols, Encoding) ->
"<a href=\"", uri(AllRuns),
"\">Test run history\n</a> | ",
"<a href=\"", uri(TestIndex),
- "\">Top level test index\n</a>\n</p>\n",
+ "\">Top level test index\n</a> | ",
+ "<a href=\"", uri(LatestTest),
+ "\">Latest test result</a>\n</p>\n",
Copyright,"</center>\n</body>\n</html>\n"]}
end.
diff --git a/lib/common_test/src/ct_master.erl b/lib/common_test/src/ct_master.erl
index 44d3fb8f64..e2ea525cdd 100644
--- a/lib/common_test/src/ct_master.erl
+++ b/lib/common_test/src/ct_master.erl
@@ -346,6 +346,7 @@ init_master(Parent,NodeOptsList,EvHandlers,MasterLogDir,LogDirs,
case whereis(ct_master) of
undefined ->
register(ct_master,self()),
+ ct_util:mark_process(),
ok;
_Pid ->
io:format("~nWarning: ct_master already running!~n"),
@@ -690,6 +691,7 @@ refresh_logs([],Refreshed) ->
init_node_ctrl(MasterPid,Cookie,Opts) ->
%% make sure tests proceed even if connection to master is lost
process_flag(trap_exit, true),
+ ct_util:mark_process(),
MasterNode = node(MasterPid),
group_leader(whereis(user),self()),
io:format("~n********** node_ctrl process ~w started on ~w **********~n",
diff --git a/lib/common_test/src/ct_master_event.erl b/lib/common_test/src/ct_master_event.erl
index d535d1274e..bd4d1efc92 100644
--- a/lib/common_test/src/ct_master_event.erl
+++ b/lib/common_test/src/ct_master_event.erl
@@ -116,6 +116,7 @@ sync_notify(Event) ->
%% this function is called to initialize the event handler.
%%--------------------------------------------------------------------
init(_) ->
+ ct_util:mark_process(),
ct_master_logs:log("CT Master Event Handler started","",[]),
{ok,#state{}}.
diff --git a/lib/common_test/src/ct_master_logs.erl b/lib/common_test/src/ct_master_logs.erl
index fd92f73f63..1308720823 100644
--- a/lib/common_test/src/ct_master_logs.erl
+++ b/lib/common_test/src/ct_master_logs.erl
@@ -88,6 +88,7 @@ stop() ->
init(Parent,LogDir,Nodes) ->
register(?MODULE,self()),
+ ct_util:mark_process(),
Time = calendar:local_time(),
RunDir = make_dirname(Time),
RunDirAbs = filename:join(LogDir,RunDir),
diff --git a/lib/common_test/src/ct_repeat.erl b/lib/common_test/src/ct_repeat.erl
index c043c9846c..177ef37d1f 100644
--- a/lib/common_test/src/ct_repeat.erl
+++ b/lib/common_test/src/ct_repeat.erl
@@ -70,6 +70,7 @@ loop_test(If,Args) when is_list(Args) ->
CtrlPid = self(),
spawn(
fun() ->
+ ct_util:mark_process(),
stop_after(CtrlPid,Secs,ForceStop)
end)
end,
@@ -134,6 +135,7 @@ spawn_tester(script,Ctrl,Args) ->
spawn_tester(func,Ctrl,Opts) ->
Tester = fun() ->
+ ct_util:mark_process(),
case catch ct_run:run_test2(Opts) of
{'EXIT',Reason} ->
exit(Reason);
diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl
index 9436236719..8c401cf3f5 100644
--- a/lib/common_test/src/ct_run.erl
+++ b/lib/common_test/src/ct_run.erl
@@ -250,6 +250,8 @@ finish(Tracing, ExitStatus, Args) ->
end.
script_start1(Parent, Args) ->
+ %% tag this process
+ ct_util:mark_process(),
%% read general start flags
Label = get_start_opt(label, fun([Lbl]) -> Lbl end, Args),
Profile = get_start_opt(profile, fun([Prof]) -> Prof end, Args),
@@ -956,7 +958,10 @@ run_test(StartOpts) when is_list(StartOpts) ->
-spec run_test1_fun(_) -> fun(() -> no_return()).
run_test1_fun(StartOpts) ->
- fun() -> run_test1(StartOpts) end.
+ fun() ->
+ ct_util:mark_process(),
+ run_test1(StartOpts)
+ end.
run_test1(StartOpts) when is_list(StartOpts) ->
case proplists:get_value(refresh_logs, StartOpts) of
@@ -1447,7 +1452,10 @@ run_testspec(TestSpec) ->
-spec run_testspec1_fun(_) -> fun(() -> no_return()).
run_testspec1_fun(TestSpec) ->
- fun() -> run_testspec1(TestSpec) end.
+ fun() ->
+ ct_util:mark_process(),
+ run_testspec1(TestSpec)
+ end.
run_testspec1(TestSpec) ->
{ok,Cwd} = file:get_cwd(),
@@ -1906,10 +1914,12 @@ possibly_spawn(true, Tests, Skip, Opts) ->
CTUtilSrv = whereis(ct_util_server),
Supervisor =
fun() ->
+ ct_util:mark_process(),
process_flag(trap_exit, true),
link(CTUtilSrv),
TestRun =
fun() ->
+ ct_util:mark_process(),
TestResult = (catch do_run_test(Tests, Skip, Opts)),
case TestResult of
{EType,_} = Error when EType == user_error;
diff --git a/lib/common_test/src/ct_slave.erl b/lib/common_test/src/ct_slave.erl
index 61e6446df8..0c16ad5980 100644
--- a/lib/common_test/src/ct_slave.erl
+++ b/lib/common_test/src/ct_slave.erl
@@ -318,6 +318,7 @@ monitor_master(MasterNode) ->
% code of the masterdeath-waiter process
monitor_master_int(MasterNode) ->
+ ct_util:mark_process(),
erlang:monitor_node(MasterNode, true),
receive
{nodedown, MasterNode}->
diff --git a/lib/common_test/src/ct_telnet_client.erl b/lib/common_test/src/ct_telnet_client.erl
index c8d217cd2a..76e4b9ea70 100644
--- a/lib/common_test/src/ct_telnet_client.erl
+++ b/lib/common_test/src/ct_telnet_client.erl
@@ -118,6 +118,7 @@ get_data(Pid) ->
%%%-----------------------------------------------------------------
%%% Internal functions
init(Parent, Server, Port, Timeout, KeepAlive, NoDelay, ConnName) ->
+ ct_util:mark_process(),
case gen_tcp:connect(Server, Port, [list,{packet,0},{nodelay,NoDelay}], Timeout) of
{ok,Sock} ->
dbg("~tp connected to: ~tp (port: ~w, keep_alive: ~w)\n",
diff --git a/lib/common_test/src/ct_util.erl b/lib/common_test/src/ct_util.erl
index 3c0fead5b2..10a06d5c88 100644
--- a/lib/common_test/src/ct_util.erl
+++ b/lib/common_test/src/ct_util.erl
@@ -65,6 +65,9 @@
-export([warn_duplicates/1]).
+-export([mark_process/0, mark_process/1, is_marked/1, is_marked/2,
+ remaining_test_procs/0]).
+
-export([get_profile_data/0, get_profile_data/1,
get_profile_data/2, open_url/3]).
@@ -126,6 +129,7 @@ start(Mode, LogDir, Verbosity) ->
do_start(Parent, Mode, LogDir, Verbosity) ->
process_flag(trap_exit,true),
register(ct_util_server,self()),
+ mark_process(),
create_table(?conn_table,#conn.handle),
create_table(?board_table,2),
create_table(?suite_table,#suite_data.key),
@@ -934,6 +938,70 @@ warn_duplicates(Suites) ->
%%% @spec
%%%
%%% @doc
+mark_process() ->
+ mark_process(system).
+
+mark_process(Type) ->
+ put(ct_process_type, Type).
+
+is_marked(Pid) ->
+ is_marked(Pid, system).
+
+is_marked(Pid, Type) ->
+ case process_info(Pid, dictionary) of
+ {dictionary,List} ->
+ Type == proplists:get_value(ct_process_type, List);
+ undefined ->
+ false
+ end.
+
+remaining_test_procs() ->
+ Procs = processes(),
+ {SharedGL,OtherGLs,Procs2} =
+ lists:foldl(
+ fun(Pid, ProcTypes = {Shared,Other,Procs1}) ->
+ case is_marked(Pid, group_leader) of
+ true ->
+ if not is_pid(Shared) ->
+ case test_server_io:get_gl(true) of
+ Pid ->
+ {Pid,Other,
+ lists:delete(Pid,Procs1)};
+ _ ->
+ {Shared,[Pid|Other],Procs1}
+ end;
+ true -> % SharedGL already found
+ {Shared,[Pid|Other],Procs1}
+ end;
+ false ->
+ case is_marked(Pid) of
+ true ->
+ {Shared,Other,lists:delete(Pid,Procs1)};
+ false ->
+ ProcTypes
+ end
+ end
+ end, {undefined,[],Procs}, Procs),
+
+ AllGLs = [SharedGL | OtherGLs],
+ TestProcs =
+ lists:flatmap(fun(Pid) ->
+ case process_info(Pid, group_leader) of
+ {group_leader,GL} ->
+ case lists:member(GL, AllGLs) of
+ true -> [{Pid,GL}];
+ false -> []
+ end;
+ undefined ->
+ []
+ end
+ end, Procs2),
+ {TestProcs, SharedGL, OtherGLs}.
+
+%%%-----------------------------------------------------------------
+%%% @spec
+%%%
+%%% @doc
get_profile_data() ->
get_profile_data(all).
diff --git a/lib/common_test/src/ct_webtool.erl b/lib/common_test/src/ct_webtool.erl
index 9016aca899..82aa78fc4b 100644
--- a/lib/common_test/src/ct_webtool.erl
+++ b/lib/common_test/src/ct_webtool.erl
@@ -343,6 +343,7 @@ code_change(_,State,_)->
% Start the gen_server
%----------------------------------------------------------------------
init({Path,Config})->
+ ct_util:mark_process(),
case filelib:is_dir(Path) of
true ->
{ok, Table} = get_tool_files_data(),
diff --git a/lib/common_test/src/ct_webtool_sup.erl b/lib/common_test/src/ct_webtool_sup.erl
index c02ec69d04..6c6dbde0a6 100644
--- a/lib/common_test/src/ct_webtool_sup.erl
+++ b/lib/common_test/src/ct_webtool_sup.erl
@@ -46,6 +46,7 @@ stop(Pid)->
%% {error, Reason}
%%----------------------------------------------------------------------
init(_StartArgs) ->
+ ct_util:mark_process(),
%%Child1 =
%%Child2 ={webcover_backend,{webcover_backend,start_link,[]},permanent,2000,worker,[webcover_backend]},
%%{ok,{{simple_one_for_one,5,10},[Child1]}}.
diff --git a/lib/common_test/src/cth_log_redirect.erl b/lib/common_test/src/cth_log_redirect.erl
index 1c55e17686..b05f0bd28b 100644
--- a/lib/common_test/src/cth_log_redirect.erl
+++ b/lib/common_test/src/cth_log_redirect.erl
@@ -56,6 +56,7 @@ id(_Opts) ->
?MODULE.
init(?MODULE, _Opts) ->
+ ct_util:mark_process(),
error_logger:add_report_handler(?MODULE),
tc_log_async.
diff --git a/lib/common_test/src/test_server.erl b/lib/common_test/src/test_server.erl
index 35a73e6d2e..7250041e13 100644
--- a/lib/common_test/src/test_server.erl
+++ b/lib/common_test/src/test_server.erl
@@ -415,6 +415,7 @@ run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData) ->
St = #st{ref=Ref,pid=Pid,mf={Mod,Func},last_known_loc=unknown,
status=starting,ret_val=[],comment="",timeout=infinity,
config=hd(Args)},
+ ct_util:mark_process(),
run_test_case_msgloop(St).
%% Ugly bug (pre R5A):
@@ -785,6 +786,7 @@ spawn_fw_call(Mod,IPTC={init_per_testcase,Func},CurrConf,Pid,
Why,Loc,SendTo) ->
FwCall =
fun() ->
+ ct_util:mark_process(),
Skip = {skip,{failed,{Mod,init_per_testcase,Why}}},
%% if init_per_testcase fails, the test case
%% should be skipped
@@ -815,6 +817,7 @@ spawn_fw_call(Mod,EPTC={end_per_testcase,Func},EndConf,Pid,
Why,_Loc,SendTo) ->
FwCall =
fun() ->
+ ct_util:mark_process(),
{RetVal,Report} =
case proplists:get_value(tc_status, EndConf) of
undefined ->
@@ -864,6 +867,7 @@ spawn_fw_call(Mod,EPTC={end_per_testcase,Func},EndConf,Pid,
spawn_fw_call(FwMod,FwFunc,_,_Pid,{framework_error,FwError},_,SendTo) ->
FwCall =
fun() ->
+ ct_util:mark_process(),
test_server_sup:framework_call(report, [framework_error,
{{FwMod,FwFunc},
FwError}]),
@@ -880,6 +884,7 @@ spawn_fw_call(FwMod,FwFunc,_,_Pid,{framework_error,FwError},_,SendTo) ->
spawn_link(FwCall);
spawn_fw_call(Mod,Func,CurrConf,Pid,Error,Loc,SendTo) ->
+ ct_util:mark_process(),
{Func1,EndTCFunc} = case Func of
CF when CF == init_per_suite; CF == end_per_suite;
CF == init_per_group; CF == end_per_group ->
@@ -918,6 +923,7 @@ start_job_proxy() ->
%% The io_reply_proxy is not the most satisfying solution but it works...
io_reply_proxy(ReplyTo) ->
+ ct_util:mark_process(),
receive
IoReply when is_tuple(IoReply),
element(1, IoReply) == io_reply ->
@@ -927,6 +933,7 @@ io_reply_proxy(ReplyTo) ->
end.
job_proxy_msgloop() ->
+ ct_util:mark_process(),
receive
%%
@@ -1804,6 +1811,7 @@ break(CBM, TestCase, Comment) ->
spawn_break_process(Pid, PName) ->
spawn(fun() ->
register(PName, self()),
+ ct_util:mark_process(),
receive
continue -> continue(Pid);
cancel -> ok
@@ -2001,6 +2009,7 @@ time_ms_apply(Func, TCPid, MultAndScale) ->
user_timetrap_supervisor(Func, Spawner, TCPid, GL, T0, MultAndScale) ->
process_flag(trap_exit, true),
+ ct_util:mark_process(),
Spawner ! {self(),infinity},
MonRef = monitor(process, TCPid),
UserTTSup = self(),
@@ -2571,6 +2580,7 @@ run_on_shielded_node(Fun, CArgs) when is_function(Fun), is_list(CArgs) ->
-spec start_job_proxy_fun(_, _) -> fun(() -> no_return()).
start_job_proxy_fun(Master, Fun) ->
fun () ->
+ ct_util:mark_process(),
_ = start_job_proxy(),
receive
Ref ->
diff --git a/lib/common_test/src/test_server_ctrl.erl b/lib/common_test/src/test_server_ctrl.erl
index c70ea4ef9d..3a454a1e84 100644
--- a/lib/common_test/src/test_server_ctrl.erl
+++ b/lib/common_test/src/test_server_ctrl.erl
@@ -89,6 +89,7 @@
-define(logdir_ext, ".logs").
-define(data_dir_suffix, "_data/").
-define(suitelog_name, "suite.log").
+-define(suitelog_latest_name, "suite.log.latest").
-define(coverlog_name, "cover.html").
-define(raw_coverlog_name, "cover.log").
-define(cross_coverlog_name, "cross_cover.html").
@@ -1126,6 +1127,7 @@ init_tester(Mod, Func, Args, Dir, Name, {_,_,MinLev}=Levels,
RejectIoReqs, CreatePrivDir, TCCallback, ExtraTools) ->
process_flag(trap_exit, true),
_ = test_server_io:start_link(),
+ put(app, common_test),
put(test_server_name, Name),
put(test_server_dir, Dir),
put(test_server_total_time, 0),
@@ -1150,6 +1152,12 @@ init_tester(Mod, Func, Args, Dir, Name, {_,_,MinLev}=Levels,
end,
%% before first print, read and set logging options
+ FWLogDir =
+ case test_server_sup:framework_call(get_log_dir, [], []) of
+ {ok,FwDir} -> FwDir;
+ _ -> filename:dirname(Dir)
+ end,
+ put(test_server_framework_logdir, FWLogDir),
LogOpts = test_server_sup:framework_call(get_logopts, [], []),
put(test_server_logopts, LogOpts),
@@ -1712,6 +1720,12 @@ start_log_file() ->
test_server_io:set_fd(html, Html),
test_server_io:set_fd(unexpected_io, Unexpected),
+ %% we must assume the redirection file (to the latest suite index) can
+ %% be stored on the level above the log directory of the current test
+ TopDir = filename:dirname(get(test_server_framework_logdir)),
+ RedirectLink = filename:join(TopDir, ?suitelog_latest_name ++ ?html_ext),
+ make_html_link(RedirectLink, HtmlName, redirect),
+
make_html_link(filename:absname(?last_test ++ ?html_ext),
HtmlName, filename:basename(Dir)),
LinkName = filename:join(Dir, ?last_link),
@@ -1740,11 +1754,18 @@ make_html_link(LinkName, Target, Explanation) ->
false ->
"file:" ++ uri_encode(Target)
end,
- H = [html_header(Explanation),
- "<h1>Last test</h1>\n"
- "<a href=\"",Href,"\">",Explanation,"</a>\n"
- "</body>\n</html>\n"],
+ H = if Explanation == redirect ->
+ Meta = ["<meta http-equiv=\"refresh\" "
+ "content=\"0; url=", Href, "\" />\n"],
+ [html_header("redirect", Meta), "</html>\n"];
+ true ->
+ [html_header(Explanation),
+ "<h1>Last test</h1>\n"
+ "<a href=\"",Href,"\">",Explanation,"</a>\n"
+ "</body>\n</html>\n"]
+ end,
ok = write_html_file(LinkName, H).
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% start_minor_log_file(Mod, Func, ParallelTC) -> AbsName
@@ -3705,6 +3726,7 @@ run_test_case(Ref, Num, Mod, Func, Args, RunInit, TimetrapData, Mode) ->
spawn_link(
fun() ->
process_flag(trap_exit, true),
+ ct_util:mark_process(),
_ = [put(Key, Val) || {Key,Val} <- Dictionary],
set_io_buffering({tc,Main}),
run_test_case1(Ref, Num, Mod, Func, Args, RunInit,
@@ -5658,6 +5680,13 @@ html_header(Title) ->
"<body bgcolor=\"white\" text=\"black\" "
"link=\"blue\" vlink=\"purple\" alink=\"red\">\n"].
+html_header(Title, Meta) ->
+ ["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n"
+ "<!-- autogenerated by '", atom_to_list(?MODULE), "'. -->\n"
+ "<html>\n"
+ "<head>\n"
+ "<title>", Title, "</title>\n"] ++ Meta ++ ["</head>\n"].
+
open_html_file(File) ->
open_utf8_file(File).
diff --git a/lib/common_test/src/test_server_gl.erl b/lib/common_test/src/test_server_gl.erl
index ce7682d101..24dd5cd54c 100644
--- a/lib/common_test/src/test_server_gl.erl
+++ b/lib/common_test/src/test_server_gl.erl
@@ -132,6 +132,7 @@ set_props(GL, PropList) ->
%%% Internal functions.
init([TSIO]) ->
+ ct_util:mark_process(group_leader),
EscChars = case application:get_env(test_server, esc_chars) of
{ok,ECBool} -> ECBool;
_ -> true
diff --git a/lib/common_test/src/test_server_io.erl b/lib/common_test/src/test_server_io.erl
index 062e3bd8ff..ef31521950 100644
--- a/lib/common_test/src/test_server_io.erl
+++ b/lib/common_test/src/test_server_io.erl
@@ -184,6 +184,7 @@ reset_state() ->
init([]) ->
process_flag(trap_exit, true),
+ ct_util:mark_process(),
Empty = gb_trees:empty(),
{ok,Shared} = test_server_gl:start_link(self()),
{ok,#st{fds=Empty,shared_gl=Shared,gls=gb_sets:empty(),
@@ -262,7 +263,7 @@ handle_call(reset_state, From, #st{phase=stopping,pending_ops=Ops}=St) ->
{Result,NewSt1}
end,
{noreply,St#st{pending_ops=[{From,Op}|Ops]}};
-handle_call(reset_state, _From, #st{fds=Fds,tags=Tags,gls=Gls,
+handle_call(reset_state, _From, #st{fds=Fds,tags=Tags,shared_gl=Shared0,gls=Gls,
offline_buffer=OfflineBuff}) ->
%% close open log files
lists:foreach(fun(Tag) ->
@@ -273,6 +274,7 @@ handle_call(reset_state, _From, #st{fds=Fds,tags=Tags,gls=Gls,
file:close(Fd)
end
end, Tags),
+ test_server_gl:stop(Shared0),
GlList = gb_sets:to_list(Gls),
_ = [test_server_gl:stop(GL) || GL <- GlList],
timer:sleep(100),
@@ -320,7 +322,7 @@ handle_call(finish, From, St) ->
handle_info({'EXIT',Pid,normal}, #st{gls=Gls0,stopping=From}=St) ->
Gls = gb_sets:delete_any(Pid, Gls0),
- case gb_sets:is_empty(Gls) andalso stopping =/= undefined of
+ case gb_sets:is_empty(Gls) andalso From =/= undefined of
true ->
%% No more group leaders left.
gen_server:reply(From, ok),
@@ -329,6 +331,9 @@ handle_info({'EXIT',Pid,normal}, #st{gls=Gls0,stopping=From}=St) ->
%% Wait for more group leaders to finish.
{noreply,St#st{gls=Gls,phase=stopping}}
end;
+handle_info({'EXIT',Pid,killed}, #st{gls=Gls0}=St) ->
+ %% forced termination of group leader
+ {noreply,St#st{gls=gb_sets:delete_any(Pid, Gls0)}};
handle_info({'EXIT',_Pid,Reason}, _St) ->
exit(Reason);
handle_info(stop_group_leaders, #st{gls=Gls}=St) ->
diff --git a/lib/common_test/src/test_server_node.erl b/lib/common_test/src/test_server_node.erl
index f0f9cea6e0..b2d4f199c3 100644
--- a/lib/common_test/src/test_server_node.erl
+++ b/lib/common_test/src/test_server_node.erl
@@ -749,6 +749,7 @@ unpack(Bin) ->
id(I) -> I.
print_data(Port) ->
+ ct_util:mark_process(),
receive
{Port, {data, Bytes}} ->
io:put_chars(Bytes),
diff --git a/lib/common_test/src/test_server_sup.erl b/lib/common_test/src/test_server_sup.erl
index 21f4be22fe..6ddbf1ad27 100644
--- a/lib/common_test/src/test_server_sup.erl
+++ b/lib/common_test/src/test_server_sup.erl
@@ -56,6 +56,7 @@ timetrap(Timeout0, Scale, Pid) ->
timetrap(Timeout0, ReportTVal, Scale, Pid) ->
process_flag(priority, max),
+ ct_util:mark_process(),
Timeout = if not Scale -> Timeout0;
true -> test_server:timetrap_scale_factor() * Timeout0
end,
@@ -773,6 +774,7 @@ framework_call(Callback,Func,Args,DefaultReturn) ->
false ->
ok
end,
+ ct_util:mark_process(),
try apply(Mod,Func,Args) of
Result ->
Result
@@ -850,6 +852,7 @@ util_start() ->
undefined ->
spawn_link(fun() ->
register(?MODULE, self()),
+ put(app, common_test),
util_loop(#util_state{starter=Starter})
end),
ok;
diff --git a/lib/common_test/src/vts.erl b/lib/common_test/src/vts.erl
index 99a109cfe8..83fcde2f48 100644
--- a/lib/common_test/src/vts.erl
+++ b/lib/common_test/src/vts.erl
@@ -157,6 +157,7 @@ test_info(_VtsPid,Type,Data) ->
init(Parent) ->
register(?MODULE,self()),
process_flag(trap_exit,true),
+ ct_util:mark_process(),
Parent ! {self(),started},
{ok,Cwd} = file:get_cwd(),
InitState = #state{start_dir=Cwd},
@@ -284,6 +285,7 @@ run_test1(State=#state{tests=Tests,current_log_dir=LogDir,
logopts=LogOpts}) ->
Self=self(),
RunTest = fun() ->
+ ct_util:mark_process(),
case ct_run:do_run(Tests,[],LogDir,LogOpts) of
{error,_Reason} ->
aborted();
diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile
index 0d9149f489..ecd1f727a2 100644
--- a/lib/common_test/test/Makefile
+++ b/lib/common_test/test/Makefile
@@ -73,7 +73,8 @@ MODULES= \
ct_log_SUITE \
ct_SUITE \
ct_keep_logs_SUITE \
- ct_unicode_SUITE
+ ct_unicode_SUITE \
+ ct_auto_clean_SUITE
ERL_FILES= $(MODULES:%=%.erl)
HRL_FILES= test_server_test_lib.hrl
diff --git a/lib/common_test/test/ct_auto_clean_SUITE.erl b/lib/common_test/test/ct_auto_clean_SUITE.erl
new file mode 100644
index 0000000000..fd81430d0d
--- /dev/null
+++ b/lib/common_test/test/ct_auto_clean_SUITE.erl
@@ -0,0 +1,262 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-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_auto_clean_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+
+-define(eh, ct_test_support_eh).
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config0) -> Config1 | {skip,Reason}
+%%
+%% Config0 = Config1 = [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Reason = term()
+%% The reason for skipping the suite.
+%%
+%% Description: Since Common Test starts another Test Server
+%% instance, the tests need to be performed on a separate node (or
+%% there will be clashes with logging processes etc).
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ DataDir = ?config(data_dir, Config),
+ CTHs = filelib:wildcard(filename:join(DataDir,"cth_*.erl")),
+ ct:pal("CTHs: ~p",[CTHs]),
+ [ct:pal("Compiling ~p: ~p",
+ [FileName,compile:file(FileName,[{outdir,DataDir},debug_info])]) ||
+ FileName <- CTHs],
+ ct_test_support:init_per_suite([{path_dirs,[DataDir]} | Config]).
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> void()
+%%
+%% Config = [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Cleanup after the suite.
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ ct_test_support:end_per_suite(Config).
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config0) -> Config1 |
+%% {skip,Reason}
+%% TestCase = atom()
+%% Name of the test case that is about to run.
+%% Config0 = Config1 = [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Reason = term()
+%% The reason for skipping the test case.
+%%
+%% Description: Initialization before each test case.
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_testcase(TestCase, Config) ->
+ ct_test_support:init_per_testcase(TestCase, Config).
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> void()
+%%
+%% TestCase = atom()
+%% Name of the test case that is finished.
+%% Config = [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Cleanup after each test case.
+%%--------------------------------------------------------------------
+end_per_testcase(TestCase, Config) ->
+ ct_test_support:end_per_testcase(TestCase, Config).
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> Descr | TestCases | {skip,Reason}
+%%
+%% Clause = doc | suite
+%% Indicates expected return value.
+%% Descr = [string()] | []
+%% String that describes the test suite.
+%% TestCases = [TestCase]
+%% TestCase = atom()
+%% Name of a test case.
+%% Reason = term()
+%% The reason for skipping the test suite.
+%%
+%% Description: Returns a description of the test suite (doc) and a
+%% list of all test cases in the suite (suite).
+%%--------------------------------------------------------------------
+suite() -> [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [clean].
+
+groups() ->
+ [].
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% TEST CASES
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% Function: TestCase(Arg) -> Descr | Spec | ok | exit() | {skip,Reason}
+%%
+%% Arg = doc | suite | Config
+%% Indicates expected behaviour and return value.
+%% Config = [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Descr = [string()] | []
+%% String that describes the test case.
+%% Spec = [tuple()] | []
+%% A test specification.
+%% Reason = term()
+%% The reason for skipping the test case.
+%%
+%% Description: Test case function. Returns a description of the test
+%% case (doc), then returns a test specification (suite),
+%% or performs the actual test (Config).
+%%--------------------------------------------------------------------
+
+%%%-----------------------------------------------------------------
+%%%
+
+clean(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+
+ ACSuite = filename:join(DataDir, "ac_SUITE"),
+ Opts0 = ct_test_support:get_opts(Config),
+ Opts = eh_opts(Config) ++ Opts0 ++ [{suite,ACSuite},
+ {ct_hooks,[cth_auto_clean]}],
+
+ ERPid = ct_test_support:start_event_receiver(Config),
+
+ ok = ct_test_support:run(Opts, Config),
+
+ Events = ct_test_support:get_events(ERPid, Config),
+ ct_test_support:log_events(?FUNCTION_NAME,
+ ct_test_support:reformat(Events, ?eh),
+ ?config(priv_dir, Config),
+ Opts),
+ TestEvents = events_to_check(?FUNCTION_NAME),
+ ok = ct_test_support:verify_events(TestEvents, Events, Config).
+
+
+%%%-----------------------------------------------------------------
+%%% HELP FUNCTIONS
+%%%-----------------------------------------------------------------
+
+eh_opts(Config) ->
+ Level = ?config(trace_level, Config),
+ [{event_handler,{?eh,[{cbm,ct_test_support},{trace_level,Level}]}}].
+
+events_to_check(Test) ->
+ %% 2 tests (ct:run_test + script_start) is default
+ events_to_check(Test, 2).
+
+events_to_check(_, 0) ->
+ [];
+events_to_check(Test, N) ->
+ events(Test) ++ events_to_check(Test, N-1).
+
+events(clean) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,start_info,{1,1,9}},
+
+ {?eh,tc_start,{ac_SUITE,init_per_suite}},
+ {?eh,tc_done,{ac_SUITE,init_per_suite,ok}},
+
+ {?eh,tc_start,{ac_SUITE,tc1}},
+ {?eh,tc_done,{ac_SUITE,tc1,ok}},
+
+ {?eh,test_stats,{1,0,{0,0}}},
+
+ {?eh,tc_start,{ac_SUITE,tc2}},
+ {?eh,tc_done,{ac_SUITE,tc2,ok}},
+
+ {?eh,test_stats,{2,0,{0,0}}},
+
+ [{?eh,tc_start,{ac_SUITE,{init_per_group,s1,[]}}},
+ {?eh,tc_done,{ac_SUITE,{init_per_group,s1,[]},ok}},
+
+ {?eh,tc_start,{ac_SUITE,stc1}},
+ {?eh,tc_done,{ac_SUITE,stc1,ok}},
+
+ {?eh,test_stats,{3,0,{0,0}}},
+
+ {?eh,tc_start,{ac_SUITE,stc2}},
+ {?eh,tc_done,{ac_SUITE,stc2,ok}},
+
+ {?eh,test_stats,{4,0,{0,0}}},
+
+ {?eh,tc_start,{ac_SUITE,{end_per_group,s1,[]}}},
+ {?eh,tc_done,{ac_SUITE,{end_per_group,s1,[]},ok}}],
+
+ {parallel,
+ [{?eh,tc_start,{ac_SUITE,{init_per_group,p1,[parallel]}}},
+ {?eh,tc_done,{ac_SUITE,{init_per_group,p1,[parallel]},ok}},
+
+ {?eh,tc_start,{ac_SUITE,ptc1}},
+ {?eh,tc_start,{ac_SUITE,ptc2}},
+ {?eh,tc_done,{ac_SUITE,ptc1,ok}},
+ {?eh,test_stats,{5,0,{0,0}}},
+ {?eh,tc_done,{ac_SUITE,ptc2,ok}},
+ {?eh,test_stats,{6,0,{0,0}}},
+
+ {?eh,tc_start,{ac_SUITE,{end_per_group,p1,[parallel]}}},
+ {?eh,tc_done,{ac_SUITE,{end_per_group,p1,[parallel]},ok}}]},
+
+ [{?eh,tc_start,{ac_SUITE,{init_per_group,s2,[]}}},
+ {?eh,tc_done,{ac_SUITE,{init_per_group,s2,[]},ok}},
+
+ {?eh,tc_start,{ac_SUITE,stc1}},
+ {?eh,tc_done,{ac_SUITE,stc1,ok}},
+
+ {?eh,test_stats,{7,0,{0,0}}},
+
+ {?eh,tc_start,{ac_SUITE,stc2}},
+ {?eh,tc_done,{ac_SUITE,stc2,ok}},
+
+ {?eh,test_stats,{8,0,{0,0}}},
+
+ {?eh,tc_start,{ac_SUITE,{end_per_group,s2,[]}}},
+ {?eh,tc_done,{ac_SUITE,{end_per_group,s2,[]},ok}}],
+
+ {?eh,tc_start,{ac_SUITE,tc1}},
+ {?eh,tc_done,{ac_SUITE,tc1,ok}},
+
+ {?eh,test_stats,{9,0,{0,0}}},
+
+ {?eh,tc_start,{ac_SUITE,end_per_suite}},
+ {?eh,tc_done,{ac_SUITE,end_per_suite,ok}},
+
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ].
diff --git a/lib/common_test/test/ct_auto_clean_SUITE_data/ac_SUITE.erl b/lib/common_test/test/ct_auto_clean_SUITE_data/ac_SUITE.erl
new file mode 100644
index 0000000000..dae7c1e22c
--- /dev/null
+++ b/lib/common_test/test/ct_auto_clean_SUITE_data/ac_SUITE.erl
@@ -0,0 +1,181 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-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(ac_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%--------------------------------------------------------------------
+%% @spec suite() -> Info
+%% Info = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+suite() ->
+ [{timetrap,{seconds,30}}].
+
+%%--------------------------------------------------------------------
+%% @spec init_per_suite(Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ start_processes(),
+ Config.
+
+%%--------------------------------------------------------------------
+%% @spec end_per_suite(Config0) -> term() | {save_config,Config1}
+%% Config0 = Config1 = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ start_processes(),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec init_per_group(GroupName, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% GroupName = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_group(_GroupName, Config) ->
+ start_processes(),
+ Config.
+
+%%--------------------------------------------------------------------
+%% @spec end_per_group(GroupName, Config0) ->
+%% term() | {save_config,Config1}
+%% GroupName = atom()
+%% Config0 = Config1 = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+end_per_group(_GroupName, _Config) ->
+ start_processes(),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec init_per_testcase(TestCase, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ start_processes(),
+ Config.
+
+%%--------------------------------------------------------------------
+%% @spec end_per_testcase(TestCase, Config0) ->
+%% term() | {save_config,Config1} | {fail,Reason}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, _Config) ->
+ start_processes(),
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec groups() -> [Group]
+%% Group = {GroupName,Properties,GroupsAndTestCases}
+%% GroupName = atom()
+%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
+%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase]
+%% TestCase = atom()
+%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}}
+%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
+%% repeat_until_any_ok | repeat_until_any_fail
+%% N = integer() | forever
+%% @end
+%%--------------------------------------------------------------------
+groups() ->
+ [{s1,[],[stc1,stc2]},
+ {p1,[parallel],[ptc1,ptc2]},
+ {s2,[],[stc1,stc2]}].
+
+%%! What about nested groups??
+
+%%--------------------------------------------------------------------
+%% @spec all() -> GroupsAndTestCases | {skip,Reason}
+%% GroupsAndTestCases = [{group,GroupName} | TestCase]
+%% GroupName = atom()
+%% TestCase = atom()
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+all() ->
+ [
+ [tc1,tc2],
+ {group,s1},
+ {group,p1},
+ {group,s2},
+ tc1
+ ].
+
+tc1(_Config) ->
+ start_processes(),
+ ok.
+
+tc2(_Config) ->
+ start_processes(),
+ ok.
+
+stc1(_Config) ->
+ start_processes(),
+ ok.
+
+stc2(_Config) ->
+ start_processes(),
+ ok.
+
+ptc1(_Config) ->
+ start_processes(),
+ ok.
+
+ptc2(_Config) ->
+ start_processes(),
+ ok.
+
+
+%%%-----------------------------------------------------------------
+%%%
+
+start_processes() ->
+ Init = fun() ->
+ process_flag(trap_exit, true),
+ do_spawn(fun() -> receive _ -> ok end end),
+ receive _ ->
+ ok
+ end
+ end,
+ do_spawn(Init).
+
+do_spawn(Fun) ->
+ Pid = spawn(Fun),
+ ct:log("Process ~w started with group leader ~w",
+ [Pid,element(2, process_info(Pid, group_leader))]),
+ Pid.
diff --git a/lib/common_test/test/ct_auto_clean_SUITE_data/cth_auto_clean.erl b/lib/common_test/test/ct_auto_clean_SUITE_data/cth_auto_clean.erl
new file mode 100644
index 0000000000..137c81969d
--- /dev/null
+++ b/lib/common_test/test/ct_auto_clean_SUITE_data/cth_auto_clean.erl
@@ -0,0 +1,214 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-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(cth_auto_clean).
+
+%% CTH Callbacks
+-export([id/1, init/2,
+ pre_init_per_suite/3, post_init_per_suite/4,
+ pre_end_per_suite/3, post_end_per_suite/4,
+ pre_init_per_group/4, post_init_per_group/5,
+ pre_end_per_group/4, post_end_per_group/5,
+ pre_init_per_testcase/4, post_init_per_testcase/5,
+ pre_end_per_testcase/4, post_end_per_testcase/5]).
+
+id(_Opts) ->
+ ?MODULE.
+
+init(?MODULE, _Opts) ->
+ ok.
+
+pre_init_per_suite(_Suite, Config, State) ->
+ identify(?FUNCTION_NAME),
+ SharedGL = test_server_io:get_gl(true),
+ SharedGL = find_and_kill(),
+ do_until(fun() -> ct:remaining_test_procs() end, {[],SharedGL,[]}),
+ %% get status of processes at startup, to be compared with end result
+ {Config, [{all_procs,processes()} | State]}.
+
+post_init_per_suite(_Suite, _Config, Return, State) ->
+ identify(?FUNCTION_NAME),
+ SharedGL = find_and_kill(),
+ do_until(fun() -> ct:remaining_test_procs() end, {[],SharedGL,[]}),
+ {Return, State}.
+
+pre_end_per_suite(_Suite, Config, State) ->
+ identify(?FUNCTION_NAME),
+ SharedGL = find_and_kill(),
+ do_until(fun() -> ct:remaining_test_procs() end, {[],SharedGL,[]}),
+ {Config, State}.
+
+post_end_per_suite(_Suite, _Config, Return, State) ->
+ identify(?FUNCTION_NAME),
+ SharedGL = find_and_kill(),
+ do_until(fun() -> ct:remaining_test_procs() end, {[],SharedGL,[]}),
+ AllProcs = processes(),
+ Remaining = AllProcs--proplists:get_value(all_procs, State),
+ ct:pal("Final remaining processes = ~p", [Remaining]),
+ %% only the end_per_suite process shoud remain at this point!
+ Remaining = [self()],
+ {Return, State}.
+
+pre_init_per_group(_Suite, _Group, Config, State) ->
+ identify(?FUNCTION_NAME),
+ SharedGL = find_and_kill(procs_and_gls),
+ do_until(fun() -> ct:remaining_test_procs() end, {[],SharedGL,[]}),
+ {Config, State}.
+
+post_init_per_group(_Suite, _Group, _Config, Result, State) ->
+ identify(?FUNCTION_NAME),
+ SharedGL = find_and_kill(procs_and_gls),
+ do_until(fun() -> ct:remaining_test_procs() end, {[],SharedGL,[]}),
+ {Result, State}.
+
+pre_init_per_testcase(_Suite, _TC, Config, State) ->
+ identify(?FUNCTION_NAME),
+ ThisGL = group_leader(),
+ find_and_kill(proc, ThisGL),
+ case proplists:get_value(tc_group_properties, Config) of
+ [{name,_},parallel] ->
+ timer:sleep(1000);
+ _ ->
+ do_until(fun() -> element(1,ct:remaining_test_procs()) end, [])
+ end,
+ {Config, State}.
+
+post_init_per_testcase(_Suite, _TC, Config, Return, State) ->
+ identify(?FUNCTION_NAME),
+ ThisGL = group_leader(),
+ find_and_kill(proc, ThisGL),
+ case proplists:get_value(tc_group_properties, Config) of
+ [{name,_},parallel] ->
+ timer:sleep(1000);
+ _ ->
+ do_until(fun() -> element(1,ct:remaining_test_procs()) end, [])
+ end,
+ {Return, State}.
+
+pre_end_per_testcase(_Suite, _TC, Config, State) ->
+ identify(?FUNCTION_NAME),
+ ThisGL = group_leader(),
+ find_and_kill(proc, ThisGL),
+ case proplists:get_value(tc_group_properties, Config) of
+ [{name,_},parallel] ->
+ timer:sleep(1000);
+ _ ->
+ do_until(fun() -> element(1,ct:remaining_test_procs()) end, [])
+ end,
+ {Config, State}.
+
+post_end_per_testcase(_Suite, _TC, Config, Result, State) ->
+ identify(?FUNCTION_NAME),
+ ThisGL = group_leader(),
+ find_and_kill(proc, ThisGL),
+ case proplists:get_value(tc_group_properties, Config) of
+ [{name,_},parallel] ->
+ timer:sleep(1000);
+ _ ->
+ do_until(fun() -> element(1,ct:remaining_test_procs()) end, [])
+ end,
+ {Result, State}.
+
+pre_end_per_group(_Suite, _Group, Config, State) ->
+ identify(?FUNCTION_NAME),
+ SharedGL = find_and_kill(procs_and_gls),
+ do_until(fun() -> ct:remaining_test_procs() end, {[],SharedGL,[]}),
+ {Config, State}.
+
+post_end_per_group(_Suite, _Group, _Config, Return, State) ->
+ identify(?FUNCTION_NAME),
+ SharedGL = find_and_kill(procs_and_gls),
+ do_until(fun() -> ct:remaining_test_procs() end, {[],SharedGL,[]}),
+ {Return, State}.
+
+
+%%%-----------------------------------------------------------------
+%%% HELP FUNCTIONS
+%%%-----------------------------------------------------------------
+
+identify(Func) ->
+ ct:pal("********** THIS IS ~w on ~w", [Func, self()]),
+ ok.
+
+find_and_kill() ->
+ find_and_kill(procs).
+
+find_and_kill(procs) ->
+ {Procs,SharedGL,_ParallelGLs} = ct:remaining_test_procs(),
+ ct:pal("Remaining test processes = ~p", [pi(Procs)]),
+ [pkill(P, kill) || {P,_GL} <- Procs],
+ SharedGL;
+
+find_and_kill(procs_and_gls) ->
+ {Procs,SharedGL,GLs} = ct:remaining_test_procs(),
+ ct:pal("Remaining test processes = ~p", [pi(Procs)]),
+ [pkill(P, kill) || {P,_GL} <- Procs],
+ ct:pal("Remaining group leaders = ~p", [pi(GLs)]),
+ [pkill(GL, kill) || GL <- GLs, GL /= SharedGL],
+ SharedGL.
+
+find_and_kill(proc, ProcGL) ->
+ {Procs,SharedGL,GLs} = ct:remaining_test_procs(),
+ ct:pal("Remaining test processes = ~p", [pi(Procs++GLs)]),
+ [pkill(P, kill) || {P,GL} <- Procs, GL == ProcGL],
+ SharedGL.
+
+pi([{P,_GL}|Ps]) ->
+ pi([P|Ps]);
+pi([P|Ps]) ->
+ case node() == node(P) of
+ true ->
+ {_,GL} = process_info(P,group_leader),
+ {_,CF} = process_info(P,current_function),
+ {_,IC} = process_info(P,initial_call),
+ {_,D} = process_info(P,dictionary),
+ Shared = test_server_io:get_gl(true),
+ User = whereis(user),
+ if (GL /= P) and (GL /= Shared) and (GL /= User) ->
+ [{P,GL,CF,IC,D} | pi([GL|Ps])];
+ true ->
+ [{P,GL,CF,IC,D} | pi(Ps)]
+ end;
+ false ->
+ pi(Ps)
+ end;
+pi([]) ->
+ [].
+
+do_until(Fun, Until) ->
+ io:format("Will do until ~p~n", [Until]),
+ do_until(Fun, Until, 1000).
+
+do_until(_, Until, 0) ->
+ io:format("Couldn't get ~p~n", [Until]),
+ exit({not_reached,Until});
+
+do_until(Fun, Until, N) ->
+ case Fun() of
+ Until ->
+ ok;
+ _Tmp ->
+ do_until(Fun, Until, N-1)
+ end.
+
+pkill(P, How) ->
+ ct:pal("KILLING ~w NOW!", [P]),
+ exit(P, How).
+
diff --git a/lib/common_test/test/test_server_test_lib.erl b/lib/common_test/test/test_server_test_lib.erl
index e3d987a2ea..9ee946af0b 100644
--- a/lib/common_test/test/test_server_test_lib.erl
+++ b/lib/common_test/test/test_server_test_lib.erl
@@ -121,7 +121,7 @@ parse_suite(FileName) ->
end.
fline(Fd) ->
- case prim_file:read_line(Fd) of
+ case file:read_line(Fd) of
eof -> eof;
{ok, Line} -> Line
end.
diff --git a/lib/compiler/src/beam_asm.erl b/lib/compiler/src/beam_asm.erl
index 3dff51d7f6..453e00fce3 100644
--- a/lib/compiler/src/beam_asm.erl
+++ b/lib/compiler/src/beam_asm.erl
@@ -24,7 +24,7 @@
-export([module/4]).
-export([encode/2]).
--export_type([fail/0,label/0,reg/0,src/0,module_code/0,function_name/0]).
+-export_type([fail/0,label/0,reg/0,reg_num/0,src/0,module_code/0,function_name/0]).
-import(lists, [map/2,member/2,keymember/3,duplicate/2,splitwith/2]).
-include("beam_opcodes.hrl").
diff --git a/lib/compiler/src/beam_dead.erl b/lib/compiler/src/beam_dead.erl
index d379fdc4eb..1b152a2d6f 100644
--- a/lib/compiler/src/beam_dead.erl
+++ b/lib/compiler/src/beam_dead.erl
@@ -272,7 +272,8 @@ backward([{jump,{f,To0}},{move,Src,Reg}=Move|Is], D, Acc) ->
end;
backward([{jump,{f,To}}=J|[{bif,Op,{f,BifFail},Ops,Reg}|Is]=Is0], D, Acc) ->
try replace_comp_op(To, Reg, Op, Ops, D) of
- I -> backward(Is, D, I++Acc)
+ {Test,Jump} ->
+ backward([Jump,Test|Is], D, Acc)
catch
throw:not_possible ->
case To =:= BifFail of
@@ -446,7 +447,7 @@ prune_redundant([], _) -> [].
replace_comp_op(To, Reg, Op, Ops, D) ->
False = comp_op_find_shortcut(To, Reg, {atom,false}, D),
True = comp_op_find_shortcut(To, Reg, {atom,true}, D),
- [bif_to_test(Op, Ops, False),{jump,{f,True}}].
+ {bif_to_test(Op, Ops, False),{jump,{f,True}}}.
comp_op_find_shortcut(To0, Reg, Val, D) ->
case shortcut_select_label(To0, Reg, Val, D) of
@@ -483,15 +484,22 @@ not_possible() -> throw(not_possible).
%% F1: is_eq_exact F2 Reg Lit2 F1: is_eq_exact F2 Reg Lit2
%% L2: .... L2:
%%
-combine_eqs(To, [Reg,{Type,_}=Lit1]=Ops, D, [{label,L1}|_])
- when Type =:= atom; Type =:= integer ->
+combine_eqs(To, [Reg,{Type,_}=Lit1]=Ops, D, Acc)
+ when Type =:= atom; Type =:= integer ->
+ Next = case Acc of
+ [{label,Lbl}|_] -> Lbl;
+ [{jump,{f,Lbl}}|_] -> Lbl
+ end,
case beam_utils:code_at(To, D) of
[{test,is_eq_exact,{f,F2},[Reg,{Type,_}=Lit2]},
{label,L2}|_] when Lit1 =/= Lit2 ->
- {select,select_val,Reg,{f,F2},[Lit1,{f,L1},Lit2,{f,L2}]};
+ {select,select_val,Reg,{f,F2},[Lit1,{f,Next},Lit2,{f,L2}]};
+ [{test,is_eq_exact,{f,F2},[Reg,{Type,_}=Lit2]},
+ {jump,{f,L2}}|_] when Lit1 =/= Lit2 ->
+ {select,select_val,Reg,{f,F2},[Lit1,{f,Next},Lit2,{f,L2}]};
[{select,select_val,Reg,{f,F2},[{Type,_}|_]=List0}|_] ->
List = remove_from_list(Lit1, List0),
- {select,select_val,Reg,{f,F2},[Lit1,{f,L1}|List]};
+ {select,select_val,Reg,{f,F2},[Lit1,{f,Next}|List]};
_Is ->
{test,is_eq_exact,{f,To},Ops}
end;
diff --git a/lib/compiler/src/core_lint.erl b/lib/compiler/src/core_lint.erl
index 7d3513c0ba..6e2114be56 100644
--- a/lib/compiler/src/core_lint.erl
+++ b/lib/compiler/src/core_lint.erl
@@ -353,12 +353,6 @@ expr(#c_case{arg=Arg,clauses=Cs}, Def, Rt, St0) ->
Pc = case_patcount(Cs),
St1 = body(Arg, Def, Pc, St0),
clauses(Cs, Def, Pc, Rt, St1);
-expr(#c_receive{clauses=Cs,timeout=#c_literal{val=infinity},
- action=#c_literal{}},
- Def, Rt, St) ->
- %% If the timeout is 'infinity', the after code can never
- %% be reached. We don't care if the return count is wrong.
- clauses(Cs, Def, 1, Rt, St);
expr(#c_receive{clauses=Cs,timeout=T,action=A}, Def, Rt, St0) ->
St1 = expr(T, Def, 1, St0),
St2 = body(A, Def, Rt, St1),
diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl
index 6da68f1f4e..f8a467d6a9 100644
--- a/lib/compiler/src/sys_core_fold.erl
+++ b/lib/compiler/src/sys_core_fold.erl
@@ -2624,9 +2624,13 @@ delay_build_expr_1(#c_receive{clauses=Cs0,
timeout=Timeout,
action=A0}=Rec, TypeSig) ->
Cs = delay_build_cs(Cs0, TypeSig),
- A = case Timeout of
- #c_literal{val=infinity} -> A0;
- _ -> delay_build_expr(A0, TypeSig)
+ A = case {Timeout,A0} of
+ {#c_literal{val=infinity},#c_literal{}} ->
+ {_Type,Arity} = TypeSig,
+ Es = lists:duplicate(Arity, A0),
+ core_lib:make_values(Es);
+ _ ->
+ delay_build_expr(A0, TypeSig)
end,
Rec#c_receive{clauses=Cs,action=A};
delay_build_expr_1(#c_seq{body=B0}=Seq, TypeSig) ->
diff --git a/lib/compiler/src/v3_codegen.erl b/lib/compiler/src/v3_codegen.erl
index 9bcd6987bf..74d64e1456 100644
--- a/lib/compiler/src/v3_codegen.erl
+++ b/lib/compiler/src/v3_codegen.erl
@@ -51,7 +51,7 @@ set_kanno(Kthing, Anno) -> setelement(2, Kthing, Anno).
%% Stack/register state record.
-record(sr, {reg=[], %Register table
stk=[], %Stack table
- res=[]}). %Reserved regs: [{reserved,I,V}]
+ res=[]}). %Registers to reserve
%% Internal records.
-record(cg_need_heap, {anno=[] :: term(),
@@ -77,10 +77,15 @@ functions(Forms, AtomMod) ->
function(#k_fdef{anno=#k{a=Anno},func=Name,arity=Arity,
vars=As,body=Kb}, AtomMod, St0) ->
try
- %% Annotate kernel records with variable usage.
#k_match{} = Kb, %Assertion.
+
+ %% Try to suppress the stack frame unless it is
+ %% really needed.
+ Body0 = avoid_stack_frame(Kb),
+
+ %% Annotate kernel records with variable usage.
Vdb0 = init_vars(As),
- {Body,_,Vdb} = body(Kb, 1, Vdb0),
+ {Body,_,Vdb} = body(Body0, 1, Vdb0),
%% Generate the BEAM assembly code.
{Asm,EntryLabel,St} = cg_fun(Body, As, Vdb, AtomMod,
@@ -94,6 +99,112 @@ function(#k_fdef{anno=#k{a=Anno},func=Name,arity=Arity,
erlang:raise(Class, Error, Stack)
end.
+
+%% avoid_stack_frame(Kernel) -> Kernel'
+%% If possible, avoid setting up a stack frame. Functions
+%% that only do matching, calls to guard BIFs, and tail-recursive
+%% calls don't need a stack frame.
+
+avoid_stack_frame(#k_match{body=Body}=M) ->
+ try
+ M#k_match{body=avoid_stack_frame_1(Body)}
+ catch
+ impossible ->
+ M
+ end.
+
+avoid_stack_frame_1(#k_alt{first=First0,then=Then0}=Alt) ->
+ First = avoid_stack_frame_1(First0),
+ Then = avoid_stack_frame_1(Then0),
+ Alt#k_alt{first=First,then=Then};
+avoid_stack_frame_1(#k_bif{op=Op}=Bif) ->
+ case Op of
+ #k_internal{} ->
+ %% Most internal BIFs clobber the X registers.
+ throw(impossible);
+ _ ->
+ Bif
+ end;
+avoid_stack_frame_1(#k_break{anno=Anno,args=Args}) ->
+ #k_guard_break{anno=Anno,args=Args};
+avoid_stack_frame_1(#k_guard_break{}=Break) ->
+ Break;
+avoid_stack_frame_1(#k_enter{}=Enter) ->
+ %% Tail-recursive calls don't need a stack frame.
+ Enter;
+avoid_stack_frame_1(#k_guard{clauses=Cs0}=Guard) ->
+ Cs = avoid_stack_frame_list(Cs0),
+ Guard#k_guard{clauses=Cs};
+avoid_stack_frame_1(#k_guard_clause{guard=G0,body=B0}=C) ->
+ G = avoid_stack_frame_1(G0),
+ B = avoid_stack_frame_1(B0),
+ C#k_guard_clause{guard=G,body=B};
+avoid_stack_frame_1(#k_match{anno=A,vars=Vs,body=B0,ret=Ret}) ->
+ %% Use #k_guard_match{} instead to avoid saving the X registers
+ %% to the stack before matching.
+ B = avoid_stack_frame_1(B0),
+ #k_guard_match{anno=A,vars=Vs,body=B,ret=Ret};
+avoid_stack_frame_1(#k_guard_match{body=B0}=M) ->
+ B = avoid_stack_frame_1(B0),
+ M#k_guard_match{body=B};
+avoid_stack_frame_1(#k_protected{arg=Arg0}=Prot) ->
+ Arg = avoid_stack_frame_1(Arg0),
+ Prot#k_protected{arg=Arg};
+avoid_stack_frame_1(#k_put{}=Put) ->
+ Put;
+avoid_stack_frame_1(#k_return{}=Ret) ->
+ Ret;
+avoid_stack_frame_1(#k_select{var=#k_var{anno=Vanno},types=Types0}=Select) ->
+ case member(reuse_for_context, Vanno) of
+ false ->
+ Types = avoid_stack_frame_list(Types0),
+ Select#k_select{types=Types};
+ true ->
+ %% Including binary patterns that overwrite the register containing
+ %% the binary with the match context may not be safe. For example,
+ %% bs_match_SUITE:bin_tail_e/1 with inlining will be rejected by
+ %% beam_validator.
+ %%
+ %% Essentially the following code is produced:
+ %%
+ %% bs_match {x,0} => {x,0}
+ %% ...
+ %% bs_match {x,0} => {x,1} %% ILLEGAL
+ %%
+ %% A bs_match instruction will only accept a match context as the
+ %% source operand if the source and destination registers are the
+ %% the same (as in the first bs_match instruction above).
+ %% The second bs_match instruction is therefore illegal.
+ %%
+ %% This situation is avoided if there is a stack frame:
+ %%
+ %% move {x,0} => {y,0}
+ %% bs_match {x,0} => {x,0}
+ %% ...
+ %% bs_match {y,0} => {x,1} %% LEGAL
+ %%
+ throw(impossible)
+ end;
+avoid_stack_frame_1(#k_seq{arg=A0,body=B0}=Seq) ->
+ A = avoid_stack_frame_1(A0),
+ B = avoid_stack_frame_1(B0),
+ Seq#k_seq{arg=A,body=B};
+avoid_stack_frame_1(#k_test{}=Test) ->
+ Test;
+avoid_stack_frame_1(#k_type_clause{values=Values0}=TC) ->
+ Values = avoid_stack_frame_list(Values0),
+ TC#k_type_clause{values=Values};
+avoid_stack_frame_1(#k_val_clause{body=B0}=VC) ->
+ B = avoid_stack_frame_1(B0),
+ VC#k_val_clause{body=B};
+avoid_stack_frame_1(_Body) ->
+ throw(impossible).
+
+avoid_stack_frame_list([H|T]) ->
+ [avoid_stack_frame_1(H)|avoid_stack_frame_list(T)];
+avoid_stack_frame_list([]) -> [].
+
+
%% This pass creates beam format annotated with variable lifetime
%% information. Each thing is given an index and for each variable we
%% store the first and last index for its occurrence. The variable
@@ -219,10 +330,8 @@ expr(#k_put{anno=A}=Put, I, _Vdb) ->
Put#k_put{anno=#l{i=I,a=A#k.a}};
expr(#k_break{anno=A}=Break, I, _Vdb) ->
Break#k_break{anno=#l{i=I,a=A#k.a}};
-expr(#k_guard_break{anno=A}=Break, I, Vdb) ->
- Locked = [V || {V,_,_} <- Vdb],
- L = #l{i=I,a=A#k.a},
- Break#k_guard_break{anno=L,locked=Locked};
+expr(#k_guard_break{anno=A}=Break, I, _Vdb) ->
+ Break#k_guard_break{anno=#l{i=I,a=A#k.a}};
expr(#k_return{anno=A}=Ret, I, _Vdb) ->
Ret#k_return{anno=#l{i=I,a=A#k.a}}.
@@ -246,14 +355,9 @@ match(#k_alt{anno=A,first=Kf,then=Kt}, Ls, I, Vdb0) ->
F = match(Kf, Ls, I+1, Vdb1),
T = match(Kt, Ls, I+1, Vdb1),
#k_alt{anno=[],first=F,then=T};
-match(#k_select{anno=A,var=V,types=Kts}=Select, Ls0, I, Vdb0) ->
- Vanno = get_kanno(V),
- Ls1 = case member(no_usage, Vanno) of
- false -> add_element(V#k_var.name, Ls0);
- true -> Ls0
- end,
- Vdb1 = use_vars(union(A#k.us, Ls1), I, Vdb0),
- Ts = [type_clause(Tc, Ls1, I+1, Vdb1) || Tc <- Kts],
+match(#k_select{anno=A,types=Kts}=Select, Ls, I, Vdb0) ->
+ Vdb1 = use_vars(union(A#k.us, Ls), I, Vdb0),
+ Ts = [type_clause(Tc, Ls, I+1, Vdb1) || Tc <- Kts],
Select#k_select{anno=[],types=Ts};
match(#k_guard{anno=A,clauses=Kcs}, Ls, I, Vdb0) ->
Vdb1 = use_vars(union(A#k.us, Ls), I, Vdb0),
@@ -388,8 +492,8 @@ cg(#k_return{anno=Le,args=Rs}, Vdb, Bef, St) ->
return_cg(Rs, Le, Vdb, Bef, St);
cg(#k_break{anno=Le,args=Bs}, Vdb, Bef, St) ->
break_cg(Bs, Le, Vdb, Bef, St);
-cg(#k_guard_break{anno=Le,args=Bs,locked=N}, Vdb, Bef, St) ->
- guard_break_cg(Bs, N, Le, Vdb, Bef, St);
+cg(#k_guard_break{anno=Le,args=Bs}, Vdb, Bef, St) ->
+ guard_break_cg(Bs, Le, Vdb, Bef, St);
cg(#cg_need_heap{h=H}, _Vdb, Bef, St) ->
{[{test_heap,H,max_reg(Bef#sr.reg)}],Bef,St}.
@@ -487,7 +591,10 @@ match_cg(M, Rs, Le, Vdb, Bef, St0) ->
guard_match_cg(M, Rs, Le, Vdb, Bef, St0) ->
I = Le#l.i,
{B,St1} = new_label(St0),
- #cg{bfail=Fail} = St1,
+ Fail = case St0 of
+ #cg{bfail=0,ultimate_failure=Fail0} -> Fail0;
+ #cg{bfail=Fail0} -> Fail0
+ end,
{Mis,Aft,St2} = match_cg(M, Fail, Bef, St1#cg{break=B}),
%% Update the register descriptors for the return registers.
Reg = guard_match_regs(Aft#sr.reg, Rs),
@@ -593,9 +700,6 @@ bsm_rename_ctx(#k_protected{arg=Ts0}=Prot, Old, New, _InProt) ->
InProt = true,
Ts = bsm_rename_ctx_list(Ts0, Old, New, InProt),
bsm_forget_var(Prot#k_protected{arg=Ts}, Old);
-bsm_rename_ctx(#k_match{body=Ms0}=Match, Old, New, InProt) ->
- Ms = bsm_rename_ctx(Ms0, Old, New, InProt),
- Match#k_match{body=Ms};
bsm_rename_ctx(#k_guard_match{body=Ms0}=Match, Old, New, InProt) ->
Ms = bsm_rename_ctx(Ms0, Old, New, InProt),
Match#k_guard_match{body=Ms};
@@ -612,9 +716,8 @@ bsm_rename_ctx(#cg_block{es=Es0}=Block, Old, New, true) ->
%% inside the block.
Es = bsm_rename_ctx_list(Es0, Old, New, true),
bsm_forget_var(Block#cg_block{es=Es}, Old);
-bsm_rename_ctx(#k_guard_break{locked=Locked0}=Break, Old, _New, _InProt) ->
- Locked = Locked0 -- [Old],
- bsm_forget_var(Break#k_guard_break{locked=Locked}, Old).
+bsm_rename_ctx(#k_guard_break{}=Break, Old, _New, _InProt) ->
+ bsm_forget_var(Break, Old).
bsm_rename_ctx_list([C|Cs], Old, New, InProt) ->
[bsm_rename_ctx(C, Old, New, InProt)|
@@ -679,129 +782,158 @@ basic_block([Ke|Kes], Acc) ->
no_block -> {reverse(Acc, [Ke]),Kes}
end.
-%% #k_put{} instructions that may garbage collect are not allowed in basic blocks.
-
-collect_block(#k_put{arg=#k_binary{}}) ->
- no_block;
-collect_block(#k_put{arg=#k_map{}}) ->
- no_block;
-collect_block(#k_put{}) ->
- include;
-collect_block(#k_call{op=#k_var{}=Var,args=As}) ->
- {block_end,As++[Var]};
+collect_block(#k_put{arg=Arg}) ->
+ %% #k_put{} instructions that may garbage collect are not allowed
+ %% in basic blocks.
+ case Arg of
+ #k_binary{} -> no_block;
+ #k_map{} -> no_block;
+ _ -> include
+ end;
collect_block(#k_call{op=Func,args=As}) ->
{block_end,As++func_vars(Func)};
-collect_block(#k_enter{op=#k_var{}=Var,args=As}) ->
- {block_end,As++[Var]};
collect_block(#k_enter{op=Func,args=As}) ->
{block_end,As++func_vars(Func)};
collect_block(#k_return{args=Rs}) ->
{block_end,Rs};
collect_block(#k_break{args=Bs}) ->
{block_end,Bs};
-collect_block(_) -> no_block.
+collect_block(_) -> no_block.
+func_vars(#k_var{}=Var) ->
+ [Var];
func_vars(#k_remote{mod=M,name=F})
when is_record(M, k_var); is_record(F, k_var) ->
[M,F];
func_vars(_) -> [].
-%% cg_basic_block([Kexpr], FirstI, LastI, As, Vdb, StackReg, State) ->
+%% cg_basic_block([Kexpr], FirstI, LastI, Arguments, Vdb, StackReg, State) ->
%% {[Ainstr],StackReg,State}.
+%%
+%% Do a specialized code generation for a basic block of #put{}
+%% instructions (that don't do any garbage collection) followed by a
+%% call, break, or return.
+%%
+%% 'Arguments' is a list of the variables that must be loaded into
+%% consecutive X registers before the last instruction in the block.
+%% The point of this specialized code generation is to try put the
+%% all of the variables in 'Arguments' into the correct X register
+%% to begin with, instead of putting them into the first available
+%% X register and having to move them to the correct X register
+%% later.
+%%
+%% To achieve that, we attempt to reserve the X registers that the
+%% variables in 'Arguments' will need to be in when the block ends.
+%%
+%% To make it more likely that reservations will be successful, we
+%% will try to save variables that need to be saved to the stack as
+%% early as possible (if an X register needed by a variable in
+%% Arguments is occupied by another variable, the value in the
+%% X register can be evicted if it is saved on the stack).
+%%
+%% We will take care not to increase the size of stack frame compared
+%% to what the standard code generator would have done (that is, to
+%% save all X registers at the last possible moment). We will do that
+%% by extending the stack frame to the minimal size needed to save
+%% all that needs to be saved using extend_stack/4, and use
+%% save_carefully/4 during code generation to only save the variables
+%% that can be saved without growing the stack frame.
cg_basic_block(Kes, Fb, Lf, As, Vdb, Bef, St0) ->
- Res = make_reservation(As, 0),
- Regs0 = reserve(Res, Bef#sr.reg, Bef#sr.stk),
- Stk = extend_stack(Bef, Lf, Lf+1, Vdb),
- Int0 = Bef#sr{reg=Regs0,stk=Stk,res=Res},
- X0_v0 = x0_vars(As, Fb, Lf, Vdb),
- {Keis,{Aft,_,St1}} =
+ Int0 = reserve_arg_regs(As, Bef),
+ Int = extend_stack(Int0, Lf, Lf+1, Vdb),
+ {Keis,{Aft,St1}} =
flatmapfoldl(fun(Ke, St) -> cg_basic_block(Ke, St, Lf, Vdb) end,
- {Int0,X0_v0,St0}, need_heap(Kes, Fb)),
+ {Int,St0}, need_heap(Kes, Fb)),
{Keis,Aft,St1}.
-cg_basic_block(#cg_need_heap{}=Ke, {Inta,X0v,Sta}, _Lf, Vdb) ->
- {Keis,Intb,Stb} = cg(Ke, Vdb, Inta, Sta),
- {Keis, {Intb,X0v,Stb}};
-cg_basic_block(Ke, {Inta,X0_v1,Sta}, Lf, Vdb) ->
+cg_basic_block(#cg_need_heap{}=Ke, {Bef,St0}, _Lf, Vdb) ->
+ {Keis,Aft,St1} = cg(Ke, Vdb, Bef, St0),
+ {Keis,{Aft,St1}};
+cg_basic_block(Ke, {Bef,St0}, Lf, Vdb) ->
#l{i=I} = get_kanno(Ke),
- {Sis,Intb} = save_carefully(Inta, I, Lf+1, Vdb),
- {X0_v2,Intc} = allocate_x0(X0_v1, I, Intb),
- Intd = reserve(Intc),
- {Keis,Inte,Stb} = cg(Ke, Vdb, Intd, Sta),
- {Sis ++ Keis, {Inte,X0_v2,Stb}}.
-make_reservation([], _) -> [];
-make_reservation([#k_var{name=V}|As], I) -> [{I,V}|make_reservation(As, I+1)];
-make_reservation([A|As], I) -> [{I,A}|make_reservation(As, I+1)].
-
-reserve(Sr) -> Sr#sr{reg=reserve(Sr#sr.res, Sr#sr.reg, Sr#sr.stk)}.
-
-reserve([{I,V}|Rs], [free|Regs], Stk) -> [{reserved,I,V}|reserve(Rs, Regs, Stk)];
-reserve([{I,V}|Rs], [{I,V}|Regs], Stk) -> [{I,V}|reserve(Rs, Regs, Stk)];
-reserve([{I,V}|Rs], [{I,Var}|Regs], Stk) ->
+ %% Save all we can to increase the possibility that reserving
+ %% registers will succeed.
+ {Sis,Int0} = save_carefully(Bef, I, Lf+1, Vdb),
+ Int1 = reserve(Int0),
+ {Keis,Aft,St1} = cg(Ke, Vdb, Int1, St0),
+ {Sis ++ Keis,{Aft,St1}}.
+
+%% reserve_arg_regs([Argument], Bef) -> Aft.
+%% Try to reserve the X registers for all arguments. All registers
+%% that we wish to reserve will be saved in Bef#sr.res.
+
+reserve_arg_regs(As, Bef) ->
+ Res = reserve_arg_regs_1(As, 0),
+ reserve(Bef#sr{res=Res}).
+
+reserve_arg_regs_1([#k_var{name=V}|As], I) ->
+ [{I,V}|reserve_arg_regs_1(As, I+1)];
+reserve_arg_regs_1([A|As], I) ->
+ [{I,A}|reserve_arg_regs_1(As, I+1)];
+reserve_arg_regs_1([], _) -> [].
+
+%% reserve(Bef) -> Aft.
+%% Try to reserve more registers. The registers we wish to reserve
+%% are found in Bef#sr.res.
+
+reserve(#sr{reg=Regs,stk=Stk,res=Res}=Sr) ->
+ Sr#sr{reg=reserve_1(Res, Regs, Stk)}.
+
+reserve_1([{I,V}|Rs], [free|Regs], Stk) ->
+ [{reserved,I,V}|reserve_1(Rs, Regs, Stk)];
+reserve_1([{I,V}|Rs], [{I,V}|Regs], Stk) ->
+ [{I,V}|reserve_1(Rs, Regs, Stk)];
+reserve_1([{I,V}|Rs], [{I,Var}|Regs], Stk) ->
case on_stack(Var, Stk) of
- true -> [{reserved,I,V}|reserve(Rs, Regs, Stk)];
- false -> [{I,Var}|reserve(Rs, Regs, Stk)]
+ true -> [{reserved,I,V}|reserve_1(Rs, Regs, Stk)];
+ false -> [{I,Var}|reserve_1(Rs, Regs, Stk)]
end;
-reserve([{I,V}|Rs], [{reserved,I,_}|Regs], Stk) ->
- [{reserved,I,V}|reserve(Rs, Regs, Stk)];
-%reserve([{I,V}|Rs], [Other|Regs], Stk) -> [Other|reserve(Rs, Regs, Stk)];
-reserve([{I,V}|Rs], [], Stk) -> [{reserved,I,V}|reserve(Rs, [], Stk)];
-reserve([], Regs, _) -> Regs.
-
-extend_stack(Bef, Fb, Lf, Vdb) ->
- Stk0 = clear_dead_stk(Bef#sr.stk, Fb, Vdb),
- Saves = [V || {V,F,L} <- Vdb,
- F < Fb,
- L >= Lf,
- not on_stack(V, Stk0)],
- Stk1 = foldl(fun (V, Stk) -> put_stack(V, Stk) end, Stk0, Saves),
- Bef#sr.stk ++ lists:duplicate(length(Stk1) - length(Bef#sr.stk), free).
-
-save_carefully(Bef, Fb, Lf, Vdb) ->
- Stk = Bef#sr.stk,
- %% New variables that are in use but not on stack.
- New = [VFL || {V,F,L} = VFL <- Vdb,
- F < Fb,
- L >= Lf,
- not on_stack(V, Stk)],
- Saves = [V || {V,_,_} <- keysort(2, New)],
- save_carefully(Saves, Bef, []).
-
-save_carefully([], Bef, Acc) -> {reverse(Acc),Bef};
-save_carefully([V|Vs], Bef, Acc) ->
- case put_stack_carefully(V, Bef#sr.stk) of
- error -> {reverse(Acc),Bef};
+reserve_1([{I,V}|Rs], [{reserved,I,_}|Regs], Stk) ->
+ [{reserved,I,V}|reserve_1(Rs, Regs, Stk)];
+reserve_1([{I,V}|Rs], [], Stk) ->
+ [{reserved,I,V}|reserve_1(Rs, [], Stk)];
+reserve_1([], Regs, _) -> Regs.
+
+%% extend_stack(Bef, FirstBefore, LastFrom, Vdb) -> Aft.
+%% Extend the stack enough to fit all variables alive past LastFrom
+%% and not already on the stack.
+
+extend_stack(#sr{stk=Stk0}=Bef, Fb, Lf, Vdb) ->
+ Stk1 = clear_dead_stk(Stk0, Fb, Vdb),
+ New = new_not_on_stack(Stk1, Fb, Lf, Vdb),
+ Stk2 = foldl(fun ({V,_,_}, Stk) -> put_stack(V, Stk) end, Stk1, New),
+ Stk = Stk0 ++ lists:duplicate(length(Stk2) - length(Stk0), free),
+ Bef#sr{stk=Stk}.
+
+%% save_carefully(Bef, FirstBefore, LastFrom, Vdb) -> {[SaveVar],Aft}.
+%% Save variables which are used past current point and which are not
+%% already on the stack, but only if the variables can be saved without
+%% growing the stack frame.
+
+save_carefully(#sr{stk=Stk}=Bef, Fb, Lf, Vdb) ->
+ New0 = new_not_on_stack(Stk, Fb, Lf, Vdb),
+ New = keysort(2, New0),
+ save_carefully_1(New, Bef, []).
+
+save_carefully_1([{V,_,_}|Vs], #sr{reg=Regs,stk=Stk0}=Bef, Acc) ->
+ case put_stack_carefully(V, Stk0) of
+ error ->
+ {reverse(Acc),Bef};
Stk1 ->
- SrcReg = fetch_reg(V, Bef#sr.reg),
+ SrcReg = fetch_reg(V, Regs),
Move = {move,SrcReg,fetch_stack(V, Stk1)},
{x,_} = SrcReg, %Assertion - must be X register.
- save_carefully(Vs, Bef#sr{stk=Stk1}, [Move|Acc])
- end.
+ save_carefully_1(Vs, Bef#sr{stk=Stk1}, [Move|Acc])
+ end;
+save_carefully_1([], Bef, Acc) ->
+ {reverse(Acc),Bef}.
-x0_vars([], _Fb, _Lf, _Vdb) -> [];
-x0_vars([#k_var{name=V}|_], Fb, _Lf, Vdb) ->
- {V,F,_L} = VFL = vdb_find(V, Vdb),
- x0_vars1([VFL], Fb, F, Vdb);
-x0_vars([X0|_], Fb, Lf, Vdb) ->
- x0_vars1([{X0,Lf,Lf}], Fb, Lf, Vdb).
-
-x0_vars1(X0, Fb, Xf, Vdb) ->
- Vs0 = [VFL || {_V,F,L}=VFL <- Vdb,
- F >= Fb,
- L < Xf],
- Vs1 = keysort(3, Vs0),
- keysort(2, X0++Vs1).
-
-allocate_x0([], _, Bef) -> {[],Bef#sr{res=[]}};
-allocate_x0([{_,_,L}|Vs], I, Bef) when L =< I ->
- allocate_x0(Vs, I, Bef);
-allocate_x0([{V,_F,_L}=VFL|Vs], _, Bef) ->
- {[VFL|Vs],Bef#sr{res=reserve_x0(V, Bef#sr.res)}}.
-
-reserve_x0(V, [_|Res]) -> [{0,V}|Res];
-reserve_x0(V, []) -> [{0,V}].
+%% top_level_block([Instruction], Bef, MaxRegs, St) -> [Instruction].
+%% For the top-level block, allocate a stack frame a necessary,
+%% adjust Y register numbering and instructions that return
+%% from the function.
top_level_block(Keis, #sr{stk=[]}, _MaxRegs, #cg{need_frame=false}) ->
Keis;
@@ -1349,8 +1481,6 @@ guard_clause_cg(#k_guard_clause{anno=#l{vdb=Vdb},guard=G,body=B}, Fail, Bef, St0
guard_cg(#k_protected{arg=Ts,ret=Rs,anno=#l{i=I,vdb=Pdb}}, Fail, _Vdb, Bef, St) ->
protected_cg(Ts, Rs, Fail, I, Pdb, Bef, St);
-guard_cg(#cg_block{es=Ts,anno=#l{i=I,vdb=Bdb}}, Fail, _Vdb, Bef, St) ->
- guard_cg_list(Ts, Fail, I, Bdb, Bef, St);
guard_cg(#k_test{anno=#l{i=I},op=Test0,args=As,inverted=Inverted},
Fail, Vdb, Bef, St0) ->
#k_remote{mod=#k_atom{val=erlang},name=#k_atom{val=Test}} = Test0,
@@ -1368,6 +1498,18 @@ guard_cg(G, _Fail, Vdb, Bef, St) ->
%%ok = io:fwrite("cg ~w: ~p~n", [?LINE,{Aft}]),
{Gis,Aft,St1}.
+%% guard_cg_list([Kexpr], Fail, I, Vdb, StackReg, St) ->
+%% {[Ainstr],StackReg,St}.
+
+guard_cg_list(Kes, Fail, I, Vdb, Bef, St0) ->
+ {Keis,{Aft,St1}} =
+ flatmapfoldl(fun (Ke, {Inta,Sta}) ->
+ {Keis,Intb,Stb} =
+ guard_cg(Ke, Fail, Vdb, Inta, Sta),
+ {Keis,{Intb,Stb}}
+ end, {Bef,St0}, need_heap(Kes, I)),
+ {Keis,Aft,St1}.
+
%% protected_cg([Kexpr], [Ret], Fail, I, Vdb, Bef, St) -> {[Ainstr],Aft,St}.
%% Do a protected. Protecteds without return values are just done
%% for effect, the return value is not checked, success passes on to
@@ -1424,18 +1566,6 @@ test_cg(Test, As, Fail, I, Vdb, Bef, St) ->
Aft = clear_dead(Bef, I, Vdb),
{[beam_utils:bif_to_test(Test, Args, {f,Fail})],Aft,St}.
-%% guard_cg_list([Kexpr], Fail, I, Vdb, StackReg, St) ->
-%% {[Ainstr],StackReg,St}.
-
-guard_cg_list(Kes, Fail, I, Vdb, Bef, St0) ->
- {Keis,{Aft,St1}} =
- flatmapfoldl(fun (Ke, {Inta,Sta}) ->
- {Keis,Intb,Stb} =
- guard_cg(Ke, Fail, Vdb, Inta, Sta),
- {Keis,{Intb,Stb}}
- end, {Bef,St0}, need_heap(Kes, I)),
- {Keis,Aft,St1}.
-
%% match_fmf(Fun, LastFail, State, [Clause]) -> {Is,Aft,State}.
%% This is a special flatmapfoldl for match code gen where we
%% generate a "failure" label for each clause. The last clause uses
@@ -2202,13 +2332,12 @@ break_cg(Bs, Le, Vdb, Bef, St) ->
{Ms ++ [{jump,{f,St#cg.break}}],
Int#sr{reg=clear_regs(Int#sr.reg)},St}.
-guard_break_cg(Bs, Locked, #l{i=I}, Vdb, #sr{reg=Reg0}=Bef, St) ->
- RegLocked = get_locked_regs(Reg0, Locked),
- #sr{reg=Reg1} = Int = clear_dead(Bef#sr{reg=RegLocked}, I, Vdb),
+guard_break_cg(Bs, #l{i=I}, Vdb, #sr{reg=Reg0}=Bef, St) ->
+ #sr{reg=Reg1} = Int = clear_dead(Bef, I, Vdb),
Reg2 = trim_free(Reg1),
NumLocked = length(Reg2),
Moves0 = gen_moves(Bs, Bef, NumLocked, []),
- Moves = order_moves(Moves0, find_scratch_reg(RegLocked)),
+ Moves = order_moves(Moves0, find_scratch_reg(Reg0)),
{BreakVars,_} = mapfoldl(fun(_, RegNum) ->
{{RegNum,gbreakvar},RegNum+1}
end, length(Reg2), Bs),
@@ -2216,20 +2345,6 @@ guard_break_cg(Bs, Locked, #l{i=I}, Vdb, #sr{reg=Reg0}=Bef, St) ->
Aft = Int#sr{reg=Reg},
{Moves ++ [{jump,{f,St#cg.break}}],Aft,St}.
-get_locked_regs([R|Rs0], Preserve) ->
- case {get_locked_regs(Rs0, Preserve),R} of
- {[],{_,V}} ->
- case lists:member(V, Preserve) of
- true -> [R];
- false -> []
- end;
- {[],_} ->
- [];
- {Rs,_} ->
- [R|Rs]
- end;
-get_locked_regs([], _) -> [].
-
%% cg_reg_arg(Arg0, Info) -> Arg
%% cg_reg_args([Arg0], Info) -> [Arg]
%% Convert argument[s] into registers. Literal values are returned unchanged.
@@ -2370,21 +2485,21 @@ break_up_cycle1(Dst, [M|Path], LastMove) ->
%% clear_dead(Sr, Until, Vdb) -> Aft.
%% Remove all variables in Sr which have died AT ALL so far.
-clear_dead(Sr, Until, Vdb) ->
- Sr#sr{reg=clear_dead_reg(Sr, Until, Vdb),
- stk=clear_dead_stk(Sr#sr.stk, Until, Vdb)}.
+clear_dead(#sr{stk=Stk}=Sr0, Until, Vdb) ->
+ Sr = Sr0#sr{reg=clear_dead_reg(Sr0, Until, Vdb),
+ stk=clear_dead_stk(Stk, Until, Vdb)},
+ reserve(Sr).
clear_dead_reg(Sr, Until, Vdb) ->
- Reg = [case R of
- {_I,V} = IV ->
- case vdb_find(V, Vdb) of
- {V,_,L} when L > Until -> IV;
- _ -> free %Remove anything else
- end;
- {reserved,_I,_V} = Reserved -> Reserved;
- free -> free
- end || R <- Sr#sr.reg],
- reserve(Sr#sr.res, Reg, Sr#sr.stk).
+ [case R of
+ {_I,V} = IV ->
+ case vdb_find(V, Vdb) of
+ {V,_,L} when L > Until -> IV;
+ _ -> free %Remove anything else
+ end;
+ {reserved,_I,_V}=Reserved -> Reserved;
+ free -> free
+ end || R <- Sr#sr.reg].
clear_dead_stk(Stk, Until, Vdb) ->
[case S of
@@ -2456,16 +2571,25 @@ adjust_stack(Bef, Fb, Lf, Vdb) ->
save_stack(Stk0, Fb, Lf, Vdb) ->
%% New variables that are in use but not on stack.
- New = [VFL || {V,F,L} = VFL <- Vdb,
- F < Fb,
- L >= Lf,
- not on_stack(V, Stk0)],
+ New = new_not_on_stack(Stk0, Fb, Lf, Vdb),
+
%% Add new variables that are not just dropped immediately.
%% N.B. foldr works backwards from the end!!
Saves = [V || {V,_,_} <- keysort(3, New)],
Stk1 = foldr(fun (V, Stk) -> put_stack(V, Stk) end, Stk0, Saves),
{Stk1,Saves}.
+%% new_not_on_stack(Stack, FirstBefore, LastFrom, Vdb) ->
+%% [{Variable,First,Last}]
+%% Return information about all variables that are used past current
+%% point and that are not already on the stack.
+
+new_not_on_stack(Stk, Fb, Lf, Vdb) ->
+ [VFL || {V,F,L} = VFL <- Vdb,
+ F < Fb,
+ L >= Lf,
+ not on_stack(V, Stk)].
+
%% saves([SaveVar], Reg, Stk) -> [{move,Reg,Stk}].
%% Generate move instructions to save variables onto stack. The
%% stack/reg info used is that after the new stack has been made.
diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl
index cc8ea475d2..6029b91cdc 100644
--- a/lib/compiler/src/v3_core.erl
+++ b/lib/compiler/src/v3_core.erl
@@ -2482,9 +2482,11 @@ cexpr(#icase{anno=A,args=Largs,clauses=Lcs,fc=Lfc}, As, St0) ->
cexpr(#ireceive1{anno=A,clauses=Lcs}, As, St0) ->
Exp = intersection(A#a.ns, As), %Exports
{Ccs,St1} = cclauses(Lcs, Exp, St0),
+ True = #c_literal{val=true},
+ Action = core_lib:make_values(lists:duplicate(1+length(Exp), True)),
{#c_receive{anno=A#a.anno,
clauses=Ccs,
- timeout=#c_literal{val=infinity},action=#c_literal{val=true}},
+ timeout=#c_literal{val=infinity},action=Action},
Exp,A#a.us,St1};
cexpr(#ireceive2{anno=A,clauses=Lcs,timeout=Lto,action=Les}, As, St0) ->
Exp = intersection(A#a.ns, As), %Exports
diff --git a/lib/compiler/src/v3_kernel.erl b/lib/compiler/src/v3_kernel.erl
index 3eea058153..23625b1f2e 100644
--- a/lib/compiler/src/v3_kernel.erl
+++ b/lib/compiler/src/v3_kernel.erl
@@ -108,6 +108,7 @@ copy_anno(Kdst, Ksrc) ->
-record(iclause, {anno=[],isub,osub,pats,guard,body}).
-record(ireceive_accept, {anno=[],arg}).
-record(ireceive_next, {anno=[],arg}).
+-record(ignored, {anno=[]}).
-type warning() :: term(). % XXX: REFINE
@@ -489,7 +490,7 @@ make_alt(First0, Then0) ->
Then1 = pre_seq(droplast(Then0), last(Then0)),
First2 = make_protected(First1),
Then2 = make_protected(Then1),
- Body = #k_atom{val=ignored},
+ Body = #ignored{},
First3 = #k_guard_clause{guard=First2,body=Body},
Then3 = #k_guard_clause{guard=Then2,body=Body},
First = #k_guard{clauses=[First3]},
@@ -2225,7 +2226,9 @@ ubody(E, return, St0) ->
{Ea,Pa,St1} = force_atomic(E, St0),
ubody(pre_seq(Pa, #ivalues{args=[Ea]}), return, St1)
end;
-ubody(E, {break,_Rs} = Break, St0) ->
+ubody(#ignored{}, {break,_} = Break, St) ->
+ ubody(#ivalues{args=[]}, Break, St);
+ubody(E, {break,[_]} = Break, St0) ->
%%ok = io:fwrite("ubody ~w:~p~n", [?LINE,{E,Br}]),
%% Exiting expressions need no trailing break.
case is_exit_expr(E) of
@@ -2233,6 +2236,16 @@ ubody(E, {break,_Rs} = Break, St0) ->
false ->
{Ea,Pa,St1} = force_atomic(E, St0),
ubody(pre_seq(Pa, #ivalues{args=[Ea]}), Break, St1)
+ end;
+ubody(E, {break,Rs}=Break, St0) ->
+ case is_exit_expr(E) of
+ true ->
+ uexpr(E, return, St0);
+ false ->
+ {Vs,St1} = new_vars(length(Rs), St0),
+ Iset = #iset{vars=Vs,arg=E},
+ PreSeq = pre_seq([Iset], #ivalues{args=Vs}),
+ ubody(PreSeq, Break, St1)
end.
iletrec_funs(#iletrec{defs=Fs}, St0) ->
diff --git a/lib/compiler/src/v3_kernel.hrl b/lib/compiler/src/v3_kernel.hrl
index 87011b7680..7cd30b25a8 100644
--- a/lib/compiler/src/v3_kernel.hrl
+++ b/lib/compiler/src/v3_kernel.hrl
@@ -79,7 +79,7 @@
-record(k_guard_clause, {anno=[],guard,body}).
-record(k_break, {anno=[],args=[]}).
--record(k_guard_break, {anno=[],args=[],locked=[]}).
+-record(k_guard_break, {anno=[],args=[]}).
-record(k_return, {anno=[],args=[]}).
%%k_get_anno(Thing) -> element(2, Thing).
diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl
index e6fa80e143..7e1a432511 100644
--- a/lib/compiler/test/bs_match_SUITE.erl
+++ b/lib/compiler/test/bs_match_SUITE.erl
@@ -40,7 +40,7 @@
map_and_binary/1,unsafe_branch_caching/1,
bad_literals/1,good_literals/1,constant_propagation/1,
parse_xml/1,get_payload/1,escape/1,num_slots_different/1,
- check_bitstring_list/1]).
+ check_bitstring_list/1,guard/1]).
-export([coverage_id/1,coverage_external_ignore/2]).
@@ -73,7 +73,7 @@ groups() ->
map_and_binary,unsafe_branch_caching,
bad_literals,good_literals,constant_propagation,parse_xml,
get_payload,escape,num_slots_different,
- check_bitstring_list]}].
+ check_bitstring_list,guard]}].
init_per_suite(Config) ->
@@ -1587,6 +1587,20 @@ check_bitstring_list(<<>>, []) ->
check_bitstring_list(_, _) ->
false.
+guard(_Config) ->
+ Tuple = id({a,b}),
+ ok = guard_1(<<1,2,3>>, {1,2,3}),
+ ok = guard_2(<<42>>, #{}),
+ ok.
+
+%% Cover handling of #k_put{} in v3_codegen:bsm_rename_ctx/4.
+guard_1(<<A,B,C>>, Tuple) when Tuple =:= {A,B,C} ->
+ ok.
+
+%% Cover handling of #k_call{} in v3_codegen:bsm_rename_ctx/4.
+guard_2(<<_>>, Healing) when Healing#{[] => Healing} =:= #{[] => #{}} ->
+ ok.
+
check(F, R) ->
R = F().
diff --git a/lib/compiler/test/match_SUITE.erl b/lib/compiler/test/match_SUITE.erl
index 35d2e8e91a..4b26a8dcdc 100644
--- a/lib/compiler/test/match_SUITE.erl
+++ b/lib/compiler/test/match_SUITE.erl
@@ -24,7 +24,8 @@
pmatch/1,mixed/1,aliases/1,non_matching_aliases/1,
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]).
+ coverage/1,grab_bag/1,literal_binary/1,
+ unary_op/1]).
-include_lib("common_test/include/ct.hrl").
@@ -40,7 +41,7 @@ groups() ->
match_in_call,untuplify,
shortcut_boolean,letify_guard,selectify,deselectify,
underscore,match_map,map_vars_used,coverage,
- grab_bag,literal_binary]}].
+ grab_bag,literal_binary,unary_op]}].
init_per_suite(Config) ->
@@ -662,5 +663,74 @@ literal_binary_match(_, <<"x">>) -> 2;
literal_binary_match(_, <<"y">>) -> 3;
literal_binary_match(_, _) -> fail.
+unary_op(Config) ->
+ %% ERL-514. This test case only verifies that the code
+ %% calculates the correct result, not that the generated
+ %% code is optimial.
+
+ {non_associative,30} = unary_op_1('&'),
+ {non_associative,300} = unary_op_1('^'),
+ {non_associative,300} = unary_op_1('not'),
+ {non_associative,300} = unary_op_1('+'),
+ {non_associative,300} = unary_op_1('-'),
+ {non_associative,300} = unary_op_1('~~~'),
+ {non_associative,300} = unary_op_1('!'),
+ {non_associative,320} = unary_op_1('@'),
+
+ error = unary_op_1(Config),
+ error = unary_op_1(abc),
+ error = unary_op_1(42),
+
+ ok.
+
+unary_op_1(Vop@1) ->
+ %% If all optimizations are working as they should, there should
+ %% be no stack frame and all '=:=' tests should be coalesced into
+ %% a single select_val instruction.
+
+ case Vop@1 =:= '&' of
+ true ->
+ {non_associative,30};
+ false ->
+ case
+ case Vop@1 =:= '^' of
+ true ->
+ true;
+ false ->
+ case Vop@1 =:= 'not' of
+ true ->
+ true;
+ false ->
+ case Vop@1 =:= '+' of
+ true ->
+ true;
+ false ->
+ case Vop@1 =:= '-' of
+ true ->
+ true;
+ false ->
+ case Vop@1 =:= '~~~' of
+ true ->
+ true;
+ false ->
+ Vop@1 =:= '!'
+ end
+ end
+ end
+ end
+ end
+ of
+ true ->
+ {non_associative,300};
+ false ->
+ case Vop@1 =:= '@' of
+ true ->
+ {non_associative,320};
+ false ->
+ error
+ end
+ end
+ end.
+
id(I) -> I.
diff --git a/lib/compiler/test/receive_SUITE.erl b/lib/compiler/test/receive_SUITE.erl
index 5f838b99bd..5e386790c0 100644
--- a/lib/compiler/test/receive_SUITE.erl
+++ b/lib/compiler/test/receive_SUITE.erl
@@ -264,6 +264,10 @@ export(Config) when is_list(Config) ->
self() ! {result,Ref,42},
42 = export_1(Ref),
{error,timeout} = export_1(Ref),
+
+ self() ! {result,Ref},
+ {ok,Ref} = export_2(),
+
ok.
export_1(Reference) ->
@@ -280,6 +284,10 @@ export_1(Reference) ->
id({build,self()}),
Result.
+export_2() ->
+ receive {result,Result} -> ok end,
+ {ok,Result}.
+
wait(Config) when is_list(Config) ->
self() ! <<42>>,
<<42>> = wait_1(r, 1, 2),
diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c
index f05bfa10b3..6957d25774 100644
--- a/lib/crypto/c_src/crypto.c
+++ b/lib/crypto/c_src/crypto.c
@@ -587,7 +587,7 @@ static ErlNifFunc nif_funcs[] = {
{"engine_finish_nif", 1, engine_finish_nif},
{"engine_free_nif", 1, engine_free_nif},
{"engine_load_dynamic_nif", 0, engine_load_dynamic_nif},
- {"engine_ctrl_cmd_strings_nif", 2, engine_ctrl_cmd_strings_nif},
+ {"engine_ctrl_cmd_strings_nif", 3, engine_ctrl_cmd_strings_nif},
{"engine_register_nif", 2, engine_register_nif},
{"engine_unregister_nif", 2, engine_unregister_nif},
{"engine_add_nif", 1, engine_add_nif},
@@ -4825,9 +4825,10 @@ static ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NI
} else if (argv[0] == atom_ecdsa) {
#if defined(HAVE_EC)
- EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey);
- if (ec) {
- /* Example of result:
+ /* not yet implemented
+ EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey);
+ if (ec) {
+ / * Example of result:
{
Curve = {Field, Prime, Point, Order, CoFactor} =
{
@@ -4841,7 +4842,7 @@ static ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NI
CoFactor = <<1>>
},
Key = <<151,...,62>>
- }
+ }
or
{
Curve =
@@ -4852,16 +4853,13 @@ static ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NI
},
Key
}
- */
+ * /
EVP_PKEY_free(pkey);
- return atom_notsup;
- }
-#else
- EVP_PKEY_free(pkey);
- return atom_notsup;
+ return enif_make_list_from_array(env, ..., ...);
+ */
#endif
}
-
+
if (pkey) EVP_PKEY_free(pkey);
return enif_make_badarg(env);
}
@@ -4886,7 +4884,6 @@ static ERL_NIF_TERM engine_by_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TER
#ifdef HAS_ENGINE_SUPPORT
ERL_NIF_TERM ret;
ErlNifBinary engine_id_bin;
- unsigned int engine_id_len = 0;
char *engine_id;
ENGINE *engine;
struct engine_ctx *ctx;
@@ -4896,14 +4893,14 @@ static ERL_NIF_TERM engine_by_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TER
PRINTF_ERR0("engine_by_id_nif Leaved: badarg");
return enif_make_badarg(env);
} else {
- engine_id_len = engine_id_bin.size+1;
- engine_id = enif_alloc(engine_id_len);
- (void) memcpy(engine_id, engine_id_bin.data, engine_id_len);
- engine_id[engine_id_len-1] = '\0';
+ engine_id = enif_alloc(engine_id_bin.size+1);
+ (void) memcpy(engine_id, engine_id_bin.data, engine_id_bin.size);
+ engine_id[engine_id_bin.size] = '\0';
}
engine = ENGINE_by_id(engine_id);
if(!engine) {
+ enif_free(engine_id);
PRINTF_ERR0("engine_by_id_nif Leaved: {error, bad_engine_id}");
return enif_make_tuple2(env, atom_error, atom_bad_engine_id);
}
@@ -4997,7 +4994,7 @@ static ERL_NIF_TERM engine_ctrl_cmd_strings_nif(ErlNifEnv* env, int argc, const
unsigned int cmds_len = 0;
char **cmds = NULL;
struct engine_ctx *ctx;
- int i;
+ int i, optional = 0;
// Get Engine
if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
@@ -5021,11 +5018,16 @@ static ERL_NIF_TERM engine_ctrl_cmd_strings_nif(ErlNifEnv* env, int argc, const
}
}
+ if(!enif_get_int(env, argv[2], &optional)) {
+ PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Parameter optional not an integer");
+ return enif_make_badarg(env);
+ }
+
for(i = 0; i < cmds_len; i+=2) {
PRINTF_ERR2("Cmd: %s:%s\r\n",
cmds[i] ? cmds[i] : "(NULL)",
cmds[i+1] ? cmds[i+1] : "(NULL)");
- if(!ENGINE_ctrl_cmd_string(ctx->engine, cmds[i], cmds[i+1], 0)) {
+ if(!ENGINE_ctrl_cmd_string(ctx->engine, cmds[i], cmds[i+1], optional)) {
PRINTF_ERR2("Command failed: %s:%s\r\n",
cmds[i] ? cmds[i] : "(NULL)",
cmds[i+1] ? cmds[i+1] : "(NULL)");
@@ -5034,11 +5036,12 @@ static ERL_NIF_TERM engine_ctrl_cmd_strings_nif(ErlNifEnv* env, int argc, const
PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: {error, ctrl_cmd_failed}");
goto error;
}
-}
+ }
error:
for(i = 0; cmds != NULL && cmds[i] != NULL; i++)
- enif_free(cmds[i]);
+ enif_free(cmds[i]);
+ enif_free(cmds);
return ret;
#else
return atom_notsup;
@@ -5377,7 +5380,6 @@ static int get_engine_load_cmd_list(ErlNifEnv* env, const ERL_NIF_TERM term, cha
ErlNifBinary tmpbin;
int arity;
char* tmpstr;
- int tmplen = 0;
if(!enif_is_empty_list(env, term)) {
if(!enif_get_list_cell(env, term, &head, &tail)) {
@@ -5392,10 +5394,9 @@ static int get_engine_load_cmd_list(ErlNifEnv* env, const ERL_NIF_TERM term, cha
cmds[i] = NULL;
return -1;
} else {
- tmplen = tmpbin.size+1;
- tmpstr = enif_alloc(tmplen);
- (void) memcpy(tmpstr, tmpbin.data, tmplen);
- tmpstr[tmplen-1] = '\0';
+ tmpstr = enif_alloc(tmpbin.size+1);
+ (void) memcpy(tmpstr, tmpbin.data, tmpbin.size);
+ tmpstr[tmpbin.size] = '\0';
cmds[i++] = tmpstr;
}
if(!enif_inspect_binary(env, tmp_tuple[1], &tmpbin)) {
@@ -5405,10 +5406,9 @@ static int get_engine_load_cmd_list(ErlNifEnv* env, const ERL_NIF_TERM term, cha
if(tmpbin.size == 0)
cmds[i++] = NULL;
else {
- tmplen = tmpbin.size+1;
- tmpstr = enif_alloc(tmplen);
- (void) memcpy(tmpstr, tmpbin.data, tmplen);
- tmpstr[tmplen-1] = '\0';
+ tmpstr = enif_alloc(tmpbin.size+1);
+ (void) memcpy(tmpstr, tmpbin.data, tmpbin.size);
+ tmpstr[tmpbin.size] = '\0';
cmds[i++] = tmpstr;
}
}
diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml
index c681ead797..3a5efd0bea 100644
--- a/lib/crypto/doc/src/crypto.xml
+++ b/lib/crypto/doc/src/crypto.xml
@@ -629,10 +629,6 @@
<p>Fetches the corresponding public key from a private key stored in an Engine.
The key must be of the type indicated by the Type parameter.
</p>
- <p>
- May throw exception notsup in case there is
- no engine support in the underlying OpenSSL implementation.
- </p>
</desc>
</func>
@@ -1185,6 +1181,57 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</desc>
</func>
+ <func>
+ <name>engine_ctrl_cmd_string(Engine, CmdName, CmdArg) -> Result</name>
+ <fsummary>Sends ctrl commands to an OpenSSL engine</fsummary>
+ <type>
+ <v>Engine = term()</v>
+ <v>CmdName = unicode:chardata()</v>
+ <v>CmdArg = unicode:chardata()</v>
+ <v>Result = ok | {error, Reason::term()}</v>
+ </type>
+ <desc>
+ <p>
+ Sends ctrl commands to the OpenSSL engine given by <c>Engine</c>.
+ This function is the same as calling <c>engine_ctrl_cmd_string/4</c> with
+ <c>Optional</c> set to <c>false</c>.
+ </p>
+ <p>
+ The function throws a badarg if the parameters are in wrong format.
+ It may also throw the exception notsup in case there is
+ no engine support in the underlying OpenSSL implementation.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name>engine_ctrl_cmd_string(Engine, CmdName, CmdArg, Optional) -> Result</name>
+ <fsummary>Sends ctrl commands to an OpenSSL engine</fsummary>
+ <type>
+ <v>Engine = term()</v>
+ <v>CmdName = unicode:chardata()</v>
+ <v>CmdArg = unicode:chardata()</v>
+ <v>Optional = boolean()</v>
+ <v>Result = ok | {error, Reason::term()}</v>
+ </type>
+ <desc>
+ <p>
+ Sends ctrl commands to the OpenSSL engine given by <c>Engine</c>.
+ <c>Optional</c> is a boolean argument that can relax the semantics of the function.
+ If set to <c>true</c> it will only return failure if the ENGINE supported the given
+ command name but failed while executing it, if the ENGINE doesn't support the command
+ name it will simply return success without doing anything. In this case we assume
+ the user is only supplying commands specific to the given ENGINE so we set this to
+ <c>false</c>.
+ </p>
+ <p>
+ The function throws a badarg if the parameters are in wrong format.
+ It may also throw the exception notsup in case there is
+ no engine support in the underlying OpenSSL implementation.
+ </p>
+ </desc>
+ </func>
+
</funcs>
<!-- Maybe put this in the users guide -->
diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl
index 91e154280f..df259d5419 100644
--- a/lib/crypto/src/crypto.erl
+++ b/lib/crypto/src/crypto.erl
@@ -52,7 +52,9 @@
engine_load/3,
engine_load/4,
engine_unload/1,
- engine_list/0
+ engine_list/0,
+ engine_ctrl_cmd_string/3,
+ engine_ctrl_cmd_string/4
]).
-export_type([engine_ref/0,
@@ -687,7 +689,7 @@ engine_load(EngineId, PreCmds, PostCmds, EngineMethods) when is_list(PreCmds),
engine_load_1(Engine, PreCmds, PostCmds, EngineMethods) ->
try
- ok = engine_nif_wrapper(engine_ctrl_cmd_strings_nif(Engine, ensure_bin_cmds(PreCmds))),
+ ok = engine_nif_wrapper(engine_ctrl_cmd_strings_nif(Engine, ensure_bin_cmds(PreCmds), 0)),
ok = engine_nif_wrapper(engine_add_nif(Engine)),
ok = engine_nif_wrapper(engine_init_nif(Engine)),
engine_load_2(Engine, PostCmds, EngineMethods),
@@ -701,7 +703,7 @@ engine_load_1(Engine, PreCmds, PostCmds, EngineMethods) ->
engine_load_2(Engine, PostCmds, EngineMethods) ->
try
- ok = engine_nif_wrapper(engine_ctrl_cmd_strings_nif(Engine, ensure_bin_cmds(PostCmds))),
+ ok = engine_nif_wrapper(engine_ctrl_cmd_strings_nif(Engine, ensure_bin_cmds(PostCmds), 0)),
[ok = engine_nif_wrapper(engine_register_nif(Engine, engine_method_atom_to_int(Method))) ||
Method <- EngineMethods],
ok
@@ -767,6 +769,35 @@ engine_list(Engine0, IdList) ->
end
end.
+%%----------------------------------------------------------------------
+%% Function: engine_ctrl_cmd_string/3
+%%----------------------------------------------------------------------
+-spec engine_ctrl_cmd_string(Engine::term(),
+ CmdName::unicode:chardata(),
+ CmdArg::unicode:chardata()) ->
+ ok | {error, Reason::term()}.
+engine_ctrl_cmd_string(Engine, CmdName, CmdArg) ->
+ engine_ctrl_cmd_string(Engine, CmdName, CmdArg, false).
+
+%%----------------------------------------------------------------------
+%% Function: engine_ctrl_cmd_string/4
+%%----------------------------------------------------------------------
+-spec engine_ctrl_cmd_string(Engine::term(),
+ CmdName::unicode:chardata(),
+ CmdArg::unicode:chardata(),
+ Optional::boolean()) ->
+ ok | {error, Reason::term()}.
+engine_ctrl_cmd_string(Engine, CmdName, CmdArg, Optional) ->
+ case engine_ctrl_cmd_strings_nif(Engine,
+ ensure_bin_cmds([{CmdName, CmdArg}]),
+ bool_to_int(Optional)) of
+ ok ->
+ ok;
+ notsup ->
+ erlang:error(notsup);
+ {error, Error} ->
+ {error, Error}
+ end.
%%--------------------------------------------------------------------
%%% On load
@@ -1100,9 +1131,17 @@ ec_curve(X) ->
privkey_to_pubkey(Alg, EngineMap) when Alg == rsa; Alg == dss; Alg == ecdsa ->
- case notsup_to_error(privkey_to_pubkey_nif(Alg, format_pkey(Alg,EngineMap))) of
+ try privkey_to_pubkey_nif(Alg, format_pkey(Alg,EngineMap))
+ of
[_|_]=L -> map_ensure_bin_as_int(L);
X -> X
+ catch
+ error:badarg when Alg==ecdsa ->
+ {error, notsup};
+ error:badarg ->
+ {error, not_found};
+ error:notsup ->
+ {error, notsup}
end.
privkey_to_pubkey_nif(_Alg, _EngineMap) -> ?nif_stub.
@@ -1258,7 +1297,7 @@ engine_init_nif(_Engine) -> ?nif_stub.
engine_finish_nif(_Engine) -> ?nif_stub.
engine_free_nif(_Engine) -> ?nif_stub.
engine_load_dynamic_nif() -> ?nif_stub.
-engine_ctrl_cmd_strings_nif(_Engine, _Cmds) -> ?nif_stub.
+engine_ctrl_cmd_strings_nif(_Engine, _Cmds, _Optional) -> ?nif_stub.
engine_add_nif(_Engine) -> ?nif_stub.
engine_remove_nif(_Engine) -> ?nif_stub.
engine_register_nif(_Engine, _EngineMethod) -> ?nif_stub.
@@ -1301,6 +1340,9 @@ engine_methods_convert_to_bitmask(engine_method_none, _BitMask) ->
engine_methods_convert_to_bitmask([M |Ms], BitMask) ->
engine_methods_convert_to_bitmask(Ms, BitMask bor engine_method_atom_to_int(M)).
+bool_to_int(true) -> 1;
+bool_to_int(false) -> 0.
+
engine_method_atom_to_int(engine_method_rsa) -> 16#0001;
engine_method_atom_to_int(engine_method_dsa) -> 16#0002;
engine_method_atom_to_int(engine_method_dh) -> 16#0004;
diff --git a/lib/crypto/test/engine_SUITE.erl b/lib/crypto/test/engine_SUITE.erl
index 5967331d8e..f206f967c7 100644
--- a/lib/crypto/test/engine_SUITE.erl
+++ b/lib/crypto/test/engine_SUITE.erl
@@ -44,6 +44,8 @@ all() ->
pre_command_fail_bad_value,
pre_command_fail_bad_key,
failed_engine_init,
+ ctrl_cmd_string,
+ ctrl_cmd_string_optional,
{group, engine_stored_key}
].
@@ -354,6 +356,67 @@ failed_engine_init(Config) when is_list(Config) ->
{skip, "Engine not supported on this OpenSSL version"}
end.
+
+ctrl_cmd_string()->
+ [{doc, "Test that a not known optional ctrl comand do not fail"}].
+ctrl_cmd_string(Config) when is_list(Config) ->
+ try
+ case crypto:get_test_engine() of
+ {error, notexist} ->
+ {skip, "OTP Test engine not found"};
+ {ok, Engine} ->
+ case crypto:engine_load(<<"dynamic">>,
+ [{<<"SO_PATH">>, Engine},
+ {<<"ID">>, <<"MD5">>},
+ <<"LOAD">>],
+ []) of
+ {ok, E} ->
+ case crypto:engine_ctrl_cmd_string(E, <<"TEST">>, <<"17">>) of
+ ok ->
+ ct:fail(fail_ctrl_cmd_should_fail);
+ {error,ctrl_cmd_failed} ->
+ ok
+ end,
+ ok = crypto:engine_unload(E);
+ {error, bad_engine_id} ->
+ {skip, "Dynamic Engine not supported"}
+ end
+ end
+ catch
+ error:notsup ->
+ {skip, "Engine not supported on this OpenSSL version"}
+ end.
+
+ctrl_cmd_string_optional()->
+ [{doc, "Test that a not known optional ctrl comand do not fail"}].
+ctrl_cmd_string_optional(Config) when is_list(Config) ->
+ try
+ case crypto:get_test_engine() of
+ {error, notexist} ->
+ {skip, "OTP Test engine not found"};
+ {ok, Engine} ->
+ case crypto:engine_load(<<"dynamic">>,
+ [{<<"SO_PATH">>, Engine},
+ {<<"ID">>, <<"MD5">>},
+ <<"LOAD">>],
+ []) of
+ {ok, E} ->
+ case crypto:engine_ctrl_cmd_string(E, <<"TEST">>, <<"17">>, true) of
+ ok ->
+ ok;
+ _ ->
+ ct:fail(fail_ctrl_cmd_string)
+ end,
+ ok = crypto:engine_unload(E);
+ {error, bad_engine_id} ->
+ {skip, "Dynamic Engine not supported"}
+ end
+ end
+ catch
+ error:notsup ->
+ {skip, "Engine not supported on this OpenSSL version"}
+ end.
+
%%%----------------------------------------------------------------
%%% Pub/priv key storage tests. Thoose are for testing the crypto.erl
%%% support for using priv/pub keys stored in an engine.
@@ -432,65 +495,93 @@ pub_encrypt_priv_decrypt_rsa_pwd(Config) ->
get_pub_from_priv_key_rsa(Config) ->
Priv = #{engine => engine_ref(Config),
key_id => key_id(Config, "rsa_private_key.pem")},
- try crypto:privkey_to_pubkey(rsa, Priv) of
+ case crypto:privkey_to_pubkey(rsa, Priv) of
+ {error, not_found} ->
+ {fail, "Key not found"};
+ {error, notsup} ->
+ {skip, "RSA not supported"};
+ {error, Error} ->
+ {fail, {wrong_error,Error}};
Pub ->
ct:log("rsa Pub = ~p",[Pub]),
sign_verify(rsa, sha, Priv, Pub)
- catch
- error:notsup -> {skip, "RSA not implemented"}
end.
get_pub_from_priv_key_rsa_pwd(Config) ->
Priv = #{engine => engine_ref(Config),
key_id => key_id(Config, "rsa_private_key_pwd.pem"),
password => "password"},
- try crypto:privkey_to_pubkey(rsa, Priv) of
+ case crypto:privkey_to_pubkey(rsa, Priv) of
+ {error, not_found} ->
+ {fail, "Key not found"};
+ {error, notsup} ->
+ {skip, "RSA not supported"};
+ {error, Error} ->
+ {fail, {wrong_error,Error}};
Pub ->
ct:log("rsa Pub = ~p",[Pub]),
sign_verify(rsa, sha, Priv, Pub)
- catch
- error:notsup -> {skip, "RSA not supported"}
end.
get_pub_from_priv_key_rsa_pwd_no_pwd(Config) ->
Priv = #{engine => engine_ref(Config),
key_id => key_id(Config, "rsa_private_key_pwd.pem")},
- try crypto:privkey_to_pubkey(rsa, Priv) of
- _ -> {fail, "PWD prot pubkey fetch succeded although no pwd!"}
- catch
- error:badarg -> ok
+ case crypto:privkey_to_pubkey(rsa, Priv) of
+ {error, not_found} ->
+ ok;
+ {error, notsup} ->
+ {skip, "RSA not supported"};
+ {error, Error} ->
+ {fail, {wrong_error,Error}};
+ Pub ->
+ ct:log("rsa Pub = ~p",[Pub]),
+ {fail, "PWD prot pubkey fetch succeded although no pwd!"}
end.
get_pub_from_priv_key_rsa_pwd_bad_pwd(Config) ->
Priv = #{engine => engine_ref(Config),
key_id => key_id(Config, "rsa_private_key_pwd.pem"),
password => "Bad password"},
- try crypto:privkey_to_pubkey(rsa, Priv) of
- _ -> {fail, "PWD prot pubkey fetch succeded with bad pwd!"}
- catch
- error:badarg -> ok
+ case crypto:privkey_to_pubkey(rsa, Priv) of
+ {error, not_found} ->
+ ok;
+ {error, notsup} ->
+ {skip, "RSA not supported"};
+ {error, Error} ->
+ {fail, {wrong_error,Error}};
+ Pub ->
+ ct:log("rsa Pub = ~p",[Pub]),
+ {fail, "PWD prot pubkey fetch succeded with bad pwd!"}
end.
get_pub_from_priv_key_dsa(Config) ->
Priv = #{engine => engine_ref(Config),
key_id => key_id(Config, "dsa_private_key.pem")},
- try crypto:privkey_to_pubkey(dss, Priv) of
+ case crypto:privkey_to_pubkey(dss, Priv) of
+ {error, not_found} ->
+ {fail, "Key not found"};
+ {error, notsup} ->
+ {skip, "DSA not supported"};
+ {error, Error} ->
+ {fail, {wrong_error,Error}};
Pub ->
ct:log("dsa Pub = ~p",[Pub]),
sign_verify(dss, sha, Priv, Pub)
- catch
- error:notsup -> {skip, "DSA not supported"}
end.
get_pub_from_priv_key_ecdsa(Config) ->
Priv = #{engine => engine_ref(Config),
key_id => key_id(Config, "ecdsa_private_key.pem")},
- try crypto:privkey_to_pubkey(ecdsa, Priv) of
+ case crypto:privkey_to_pubkey(ecdsa, Priv) of
+ {error, not_found} ->
+ {fail, "Key not found"};
+ {error, notsup} ->
+ {skip, "ECDSA not supported"};
+ {error, Error} ->
+ {fail, {wrong_error,Error}};
Pub ->
ct:log("ecdsa Pub = ~p",[Pub]),
sign_verify(ecdsa, sha, Priv, Pub)
- catch
- error:notsup -> {skip, "ECDSA not supported"}
end.
%%%================================================================
diff --git a/lib/debugger/src/dbg_wx_win.erl b/lib/debugger/src/dbg_wx_win.erl
index 9f59915476..f1298154ab 100644
--- a/lib/debugger/src/dbg_wx_win.erl
+++ b/lib/debugger/src/dbg_wx_win.erl
@@ -299,7 +299,7 @@ open_help(_Parent, HelpHtmlFile) ->
%%--------------------------------------------------------------------
to_string(Atom) when is_atom(Atom) ->
- io_lib:format("~tw", [Atom]);
+ atom_to_list(Atom);
to_string(Integer) when is_integer(Integer) ->
integer_to_list(Integer);
to_string([]) -> "";
diff --git a/lib/diameter/doc/src/seealso.ent b/lib/diameter/doc/src/seealso.ent
index c5a53670d0..72d74c103c 100644
--- a/lib/diameter/doc/src/seealso.ent
+++ b/lib/diameter/doc/src/seealso.ent
@@ -79,7 +79,7 @@ significant.
<!ENTITY app_handle_answer '<seealso marker="diameter_app#Mod:handle_answer-4">handle_answer/4</seealso>'>
<!ENTITY app_handle_request '<seealso marker="diameter_app#Mod:handle_request-3">handle_request/3</seealso>'>
<!ENTITY app_handle_error '<seealso marker="diameter_app#Mod:handle_error-4">handle_error/4</seealso>'>
-<!ENTITY app_peer_down '<seealso marker="diameter_app#Mod:peer_down-3">peer_up/3</seealso>'>
+<!ENTITY app_peer_down '<seealso marker="diameter_app#Mod:peer_down-3">peer_down/3</seealso>'>
<!ENTITY app_peer_up '<seealso marker="diameter_app#Mod:peer_up-3">peer_up/3</seealso>'>
<!ENTITY app_pick_peer '<seealso marker="diameter_app#Mod:pick_peer-4">pick_peer/4</seealso>'>
<!ENTITY app_prepare_retransmit '<seealso marker="diameter_app#Mod:prepare_retransmit-3">prepare_retransmit/3</seealso>'>
diff --git a/lib/diameter/src/diameter.appup.src b/lib/diameter/src/diameter.appup.src
index d0e58e8410..7da59f8b25 100644
--- a/lib/diameter/src/diameter.appup.src
+++ b/lib/diameter/src/diameter.appup.src
@@ -56,7 +56,8 @@
{"2.0", [{restart_application, diameter}]}, %% 20.0
{"2.1", [{load_module, diameter_gen}, %% 20.1
{update, diameter_reg, {advanced, "2.1"}}]},
- {"2.1.1", [{load_module, diameter_gen}]}
+ {"2.1.1", [{load_module, diameter_gen}]}, %% 20.1.2
+ {"2.1.2", []} %% 20.1.3
],
[
{"0.9", [{restart_application, diameter}]},
@@ -93,6 +94,7 @@
{"1.12.2", [{restart_application, diameter}]},
{"2.0", [{restart_application, diameter}]},
{"2.1", [{restart_application, diameter}]},
- {"2.1.1", [{load_module, diameter_gen}]}
+ {"2.1.1", [{load_module, diameter_gen}]},
+ {"2.1.2", []}
]
}.
diff --git a/lib/diameter/vsn.mk b/lib/diameter/vsn.mk
index bfb260ed8f..0c852d75cd 100644
--- a/lib/diameter/vsn.mk
+++ b/lib/diameter/vsn.mk
@@ -17,5 +17,5 @@
# %CopyrightEnd%
APPLICATION = diameter
-DIAMETER_VSN = 2.1.2
+DIAMETER_VSN = 2.1.3
APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN)
diff --git a/lib/inets/src/http_client/httpc.erl b/lib/inets/src/http_client/httpc.erl
index 2efe2c2858..821eb7f02f 100644
--- a/lib/inets/src/http_client/httpc.erl
+++ b/lib/inets/src/http_client/httpc.erl
@@ -319,7 +319,7 @@ store_cookies(SetCookieHeaders, Url, Profile)
Scheme = scheme_to_atom(maps:get(scheme, URI, '')),
Host = maps:get(host, URI, ""),
Port = maps:get(port, URI, default_port(Scheme)),
- Path = maps:get(path, URI, ""),
+ Path = uri_string:recompose(#{path => maps:get(path, URI, "")}),
%% Since the Address part is not actually used
%% by the manager when storing cookies, we dont
%% care about ipv6-host-with-brackets.
@@ -539,7 +539,7 @@ handle_request(Method, Url,
Host = http_util:maybe_add_brackets(maps:get(host, URI, ""), BracketedHost),
Port = maps:get(port, URI, default_port(Scheme)),
Host2 = http_request:normalize_host(Scheme, Host, Port),
- Path = maps:get(path, URI, ""),
+ Path = uri_string:recompose(#{path => maps:get(path, URI, "")}),
Query = add_question_mark(maps:get(query, URI, "")),
HeadersRecord = header_record(NewHeaders, Host2, HTTPOptions),
diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl
index e0e0c6b6e5..1e912e7640 100644
--- a/lib/inets/test/httpc_SUITE.erl
+++ b/lib/inets/test/httpc_SUITE.erl
@@ -58,6 +58,9 @@ all() ->
groups() ->
[
{http, [], real_requests()},
+ %% process_leak_on_keepalive is depending on stream_fun_server_close
+ %% and it shall be the last test case in the suite otherwise cookie
+ %% will fail.
{sim_http, [], only_simulated() ++ [process_leak_on_keepalive]},
{https, [], real_requests()},
{sim_https, [], only_simulated()},
@@ -133,6 +136,7 @@ only_simulated() ->
redirect_port_in_host_header,
relaxed,
multipart_chunks,
+ get_space,
stream_fun_server_close
].
@@ -254,6 +258,16 @@ get_query_string(Config) when is_list(Config) ->
{ok, {{_,200,_}, [_ | _], Body = [_ | _]}} = httpc:request(get, Request, [], []),
inets_test_lib:check_body(Body).
+
+%%--------------------------------------------------------------------
+get_space() ->
+ [{"Test http get request with '%20' in the path of the URL."}].
+get_space(Config) when is_list(Config) ->
+ Request = {url(group_name(Config), "/space%20.html", Config), []},
+ {ok, {{_,200,_}, [_ | _], Body = [_ | _]}} = httpc:request(get, Request, [], []),
+
+ inets_test_lib:check_body(Body).
+
%%--------------------------------------------------------------------
post() ->
[{"Test http post request against local server. We do in this case "
@@ -1084,8 +1098,6 @@ remote_socket_close_async(Config) when is_list(Config) ->
%%-------------------------------------------------------------------------
process_leak_on_keepalive(Config) ->
- {ok, ClosedSocket} = gen_tcp:listen(6666, [{active, false}]),
- ok = gen_tcp:close(ClosedSocket),
Request = {url(group_name(Config), "/dummy.html", Config), []},
HttpcHandlers0 = supervisor:which_children(httpc_handler_sup),
{ok, {{_, 200, _}, _, Body}} = httpc:request(get, Request, [], []),
@@ -1097,11 +1109,10 @@ process_leak_on_keepalive(Config) ->
ordsets:to_list(
ordsets:subtract(ordsets:from_list(HttpcHandlers1),
ordsets:from_list(HttpcHandlers0))),
- sys:replace_state(
- Pid, fun (State) ->
- Session = element(3, State),
- setelement(3, State, Session#session{socket=ClosedSocket})
- end),
+ State = sys:get_state(Pid),
+ #session{socket=Socket} = element(3, State),
+ gen_tcp:close(Socket),
+
{ok, {{_, 200, _}, _, Body}} = httpc:request(get, Request, [], []),
%% bad handler with the closed socket should get replaced by
%% the new one, so children count should stay the same
@@ -1745,6 +1756,12 @@ content_length([_Head | Tail]) ->
handle_uri("GET","/dummy.html?foo=bar",_,_,_,_) ->
"HTTP/1.0 200 OK\r\n\r\nTEST";
+handle_uri("GET","/space%20.html",_,_,_,_) ->
+ Body = "<HTML><BODY>foobar</BODY></HTML>",
+ "HTTP/1.1 200 OK\r\n" ++
+ "Content-Length:" ++ integer_to_list(length(Body)) ++ "\r\n\r\n" ++
+ Body;
+
handle_uri(_,"/just_close.html",_,_,_,_) ->
close;
handle_uri(_,"/no_content.html",_,_,_,_) ->
diff --git a/lib/kernel/doc/src/file.xml b/lib/kernel/doc/src/file.xml
index 2ab35b9b05..58abb35428 100644
--- a/lib/kernel/doc/src/file.xml
+++ b/lib/kernel/doc/src/file.xml
@@ -33,11 +33,14 @@
<description>
<p>This module provides an interface to the file system.</p>
- <p>On operating systems with thread support,
- file operations can be performed in threads of their own, allowing
- other Erlang processes to continue executing in parallel with
- the file operations. See command-line flag
- <c>+A</c> in <seealso marker="erts:erl"><c>erl(1)</c></seealso>.</p>
+ <warning>
+ <p>File operations are only guaranteed to appear atomic when going
+ through the same file server. A NIF or other OS process may observe
+ intermediate steps on certain operations on some operating systems,
+ eg. renaming an existing file on Windows, or
+ <seealso marker="#write_file_info/2"><c>write_file_info/2</c>
+ </seealso> on any OS at the time of writing.</p>
+ </warning>
<p>Regarding filename encoding, the Erlang VM can operate in
two modes. The current mode can be queried using function
@@ -1438,8 +1441,12 @@ f.txt: {person, "kalle", 25}.
which is 1970-01-01 00:00 UTC.</p></item>
</taglist>
<p>Default is <c>{time, local}</c>.</p>
- <p>If the option <c>raw</c> is set, the file server is not called
- and only information about local files is returned.</p>
+ <p>If the option <c>raw</c> is set, the file server is not called and
+ only information about local files is returned. Note that this will
+ break this module's atomicity guarantees as it can race with a
+ concurrent call to
+ <seealso marker="#write_file_info/2"><c>write_file_info/1,2</c>
+ </seealso></p>
<note>
<p>As file times are stored in POSIX time on most OS, it is faster to
query file information with option <c>posix</c>.</p>
@@ -1687,8 +1694,12 @@ f.txt: {person, "kalle", 25}.
except that if <c><anno>Name</anno></c> is a symbolic link, information
about the link is returned in the <c>file_info</c> record and
the <c>type</c> field of the record is set to <c>symlink</c>.</p>
- <p>If the option <c>raw</c> is set, the file server is not called
- and only information about local files is returned.</p>
+ <p>If the option <c>raw</c> is set, the file server is not called and
+ only information about local files is returned. Note that this will
+ break this module's atomicity guarantees as it can race with a
+ concurrent call to
+ <seealso marker="#write_file_info/2"><c>write_file_info/1,2</c>
+ </seealso></p>
<p>If <c><anno>Name</anno></c> is not a symbolic link, this function returns
the same result as <c>read_file_info/1</c>.
On platforms that do not support symbolic links, this function
@@ -1826,24 +1837,16 @@ f.txt: {person, "kalle", 25}.
<p>The file used must be opened using the <c>raw</c> flag, and the process
calling <c>sendfile</c> must be the controlling process of the socket.
See <seealso marker="gen_tcp#controlling_process-2"><c>gen_tcp:controlling_process/2</c></seealso>.</p>
- <p>If the OS used does not support <c>sendfile</c>, an Erlang fallback
- using
- <seealso marker="#read/2"><c>read/2</c></seealso> and
- <seealso marker="gen_tcp#send/2"><c>gen_tcp:send/2</c></seealso> is used.</p>
+ <p>If the OS used does not support non-blocking <c>sendfile</c>, an
+ Erlang fallback using <seealso marker="#read/2"><c>read/2</c></seealso>
+ and <seealso marker="gen_tcp#send/2"><c>gen_tcp:send/2</c></seealso> is
+ used.</p>
<p>The option list can contain the following options:</p>
<taglist>
<tag><c>chunk_size</c></tag>
<item><p>The chunk size used by the Erlang fallback to send
data. If using the fallback, set this to a value
that comfortably fits in the systems memory. Default is 20 MB.</p></item>
- <tag><c>use_threads</c></tag>
- <item><p>Instructs the emulator to use the <c>async</c> thread pool for the
- <c>sendfile</c> system call. This can be useful if the OS you are running
- on does not properly support non-blocking <c>sendfile</c> calls. Notice that
- using <c>async</c> threads potentially makes your system vulnerable to slow
- client attacks. If set to <c>true</c> and no <c>async</c> threads are available,
- the <c>sendfile</c> call returns <c>{error,einval}</c>.
- Introduced in Erlang/OTP 17.0. Default is <c>false</c>.</p></item>
</taglist>
</desc>
</func>
@@ -2148,144 +2151,77 @@ f.txt: {person, "kalle", 25}.
<section>
<title>Performance</title>
- <p>Some operating system file operations, for example, a
- <c>sync/1</c> or <c>close/1</c> on a huge file, can block their
- calling thread for seconds. If this affects the emulator main
- thread, the response time is no longer in the order of
- milliseconds, depending on the definition of "soft" in soft
- real-time system.</p>
- <p>If the device driver thread pool is active, file operations are
- done through those threads instead, so the emulator can go on
- executing Erlang processes. Unfortunately, the time for serving a
- file operation increases because of the extra scheduling required
- from the operating system.</p>
- <p>If the device driver thread pool is disabled or of size 0, large
- file reads and writes are segmented into many smaller, which
- enable the emulator to serve other processes during the file
- operation. This has the same effect as when using the thread
- pool, but with larger overhead. Other file operations, for
- example, <c>sync/1</c> or <c>close/1</c> on a huge file, still are
- a problem.</p>
- <p>For increased performance, raw files are recommended. Raw files
- use the file system of the host machine of the node.</p>
+ <p>For increased performance, raw files are recommended.</p>
+ <p>A normal file is really a process so it can be used as an I/O
+ device (see <seealso marker="stdlib:io"><c>io</c></seealso>).
+ Therefore, when data is written to a normal file, the sending of the
+ data to the file process, copies all data that are not binaries. Opening
+ the file in binary mode and writing binaries is therefore recommended.
+ If the file is opened on another node, or if the file server runs as
+ slave to the file server of another node, also binaries are copied.</p>
<note>
- <p>
- For normal files (non-raw), the file server is used to find the files,
- and if the node is running its file server as slave to the file server
- of another node, and the other node runs on some other host machine,
- they can have different file systems.
- However, this is seldom a problem.</p>
+ <p>Raw files use the file system of the host machine of the node.
+ For normal files (non-raw), the file server is used to find the files,
+ and if the node is running its file server as slave to the file server
+ of another node, and the other node runs on some other host machine,
+ they can have different file systems.
+ However, this is seldom a problem.</p>
</note>
- <p>A normal file is really a process so it can be used as an I/O
- device (see
- <seealso marker="stdlib:io"><c>io</c></seealso>).
- Therefore, when data is written to a
- normal file, the sending of the data to the file process, copies
- all data that are not binaries. Opening the file in binary mode
- and writing binaries is therefore recommended. If the file is
- opened on another node, or if the file server runs as slave to
- the file server of another node, also binaries are copied.</p>
- <p>Caching data to reduce the number of file operations, or rather
- the number of calls to the file driver, generally increases
- performance. The following function writes 4 MBytes in 23
- seconds when tested:</p>
+ <p><seealso marker="#open/2"><c>open/2</c></seealso> can be given the
+ options <c>delayed_write</c> and <c>read_ahead</c> to turn on caching,
+ which will reduce the number of operating system calls and greatly
+ improve performance for small reads and writes. However, the overhead
+ won't disappear completely and it's best to keep the number of file
+ operations to a minimum. As a contrived example, the following function
+ writes 4MB in 2.5 seconds when tested:</p>
+
<code type="none"><![CDATA[
-create_file_slow(Name, N) when integer(N), N >= 0 ->
- {ok, FD} = file:open(Name, [raw, write, delayed_write, binary]),
- ok = create_file_slow(FD, 0, N),
- ok = ?FILE_MODULE:close(FD),
- ok.
-
-create_file_slow(FD, M, M) ->
+create_file_slow(Name) ->
+ {ok, Fd} = file:open(Name, [raw, write, delayed_write, binary]),
+ create_file_slow_1(Fd, 4 bsl 20),
+ file:close(Fd).
+
+create_file_slow_1(_Fd, 0) ->
ok;
-create_file_slow(FD, M, N) ->
- ok = file:write(FD, <<M:32/unsigned>>),
- create_file_slow(FD, M+1, N).]]></code>
+create_file_slow_1(Fd, M) ->
+ ok = file:write(Fd, <<0>>),
+ create_file_slow_1(Fd, M - 1).]]></code>
+
+ <p>The following functionally equivalent code writes 128 bytes per call
+ to <seealso marker="#write/2"><c>write/2</c></seealso> and so does the
+ same work in 0.08 seconds, which is roughly 30 times faster:</p>
- <p>The following, functionally equivalent, function collects 1024
- entries into a list of 128 32-byte binaries before each call to
- <seealso marker="#write/2"><c>write/2</c></seealso> and so
- does the same work in 0.52 seconds,
- which is 44 times faster:</p>
<code type="none"><![CDATA[
-create_file(Name, N) when integer(N), N >= 0 ->
- {ok, FD} = file:open(Name, [raw, write, delayed_write, binary]),
- ok = create_file(FD, 0, N),
- ok = ?FILE_MODULE:close(FD),
+create_file(Name) ->
+ {ok, Fd} = file:open(Name, [raw, write, delayed_write, binary]),
+ create_file_1(Fd, 4 bsl 20),
+ file:close(Fd),
ok.
-
-create_file(FD, M, M) ->
+
+create_file_1(_Fd, 0) ->
ok;
-create_file(FD, M, N) when M + 1024 =&lt; N ->
- create_file(FD, M, M + 1024, []),
- create_file(FD, M + 1024, N);
-create_file(FD, M, N) ->
- create_file(FD, M, N, []).
-
-create_file(FD, M, M, R) ->
- ok = file:write(FD, R);
-create_file(FD, M, N0, R) when M + 8 =&lt; N0 ->
- N1 = N0-1, N2 = N0-2, N3 = N0-3, N4 = N0-4,
- N5 = N0-5, N6 = N0-6, N7 = N0-7, N8 = N0-8,
- create_file(FD, M, N8,
- [<<N8:32/unsigned, N7:32/unsigned,
- N6:32/unsigned, N5:32/unsigned,
- N4:32/unsigned, N3:32/unsigned,
- N2:32/unsigned, N1:32/unsigned>> | R]);
-create_file(FD, M, N0, R) ->
- N1 = N0-1,
- create_file(FD, M, N1, [<<N1:32/unsigned>> | R]).]]></code>
+create_file_1(Fd, M) when M >= 128 ->
+ ok = file:write(Fd, <<0:(128)/unit:8>>),
+ create_file_1(Fd, M - 128);
+create_file_1(Fd, M) ->
+ ok = file:write(Fd, <<0:(M)/unit:8>>),
+ create_file_1(Fd, M - 1).]]></code>
- <note>
- <p>Trust only your own benchmarks. If the list length in
- <c>create_file/2</c> above is increased, it runs slightly
- faster, but consumes more memory and causes more memory
- fragmentation. How much this affects your application is
- something that this simple benchmark cannot predict.</p>
- <p>If the size of each binary is increased to 64 bytes, it
- also runs slightly faster, but the code is then twice as clumsy.
- In the current implementation, binaries larger than 64 bytes are
- stored in memory common to all processes and not copied when
- sent between processes, while these smaller binaries are stored
- on the process heap and copied when sent like any other term.</p>
- <p>So, with a binary size of 68 bytes, <c>create_file/2</c> runs
- 30 percent slower than with 64 bytes, and causes much more
- memory fragmentation. Notice that if the binaries were to be sent
- between processes (for example, a non-raw file), the results
- would probably be completely different.</p>
- </note>
- <p>A raw file is really a port. When writing data to a port, it is
- efficient to write a list of binaries. It is not needed to
- flatten a deep list before writing. On Unix hosts, scatter output,
- which writes a set of buffers in one operation, is used when
- possible. In this way <c>write(FD, [Bin1, Bin2 | Bin3])</c>
- writes the contents of the binaries without copying the data
- at all, except for perhaps deep down in the operating system
- kernel.</p>
- <p>For raw files, <c>pwrite/2</c> and <c>pread/2</c> are
- efficiently implemented. The file driver is called only once for
- the whole operation, and the list iteration is done in the file
- driver.</p>
- <p>The options <c>delayed_write</c> and <c>read_ahead</c> to
- <seealso marker="#open/2"><c>open/2</c></seealso>
- make the file driver cache data to reduce
- the number of operating system calls. The function
- <c>create_file/2</c> in the recent example takes 60 seconds
- without option <c>delayed_write</c>, which is 2.6
- times slower.</p>
- <p>As a bad example, <c>create_file_slow/2</c>
- without options <c>raw</c>, <c>binary</c>, and <c>delayed_write</c>,
- meaning it calls <c>open(Name, [write])</c>, needs
- 1 min 20 seconds for the job, which is 3.5 times slower than
- the first example, and 150 times slower than the optimized
- <c>create_file/2</c>.</p>
- <warning>
- <p>If an error occurs when accessing an open file with module
- <seealso marker="stdlib:io"><c>io</c></seealso>,
- the process handling the file exits. The dead
- file process can hang if a process tries to access it later.
- This will be fixed in a future release.</p>
- </warning>
+ <p>When writing data it's generally more efficient to write a list of
+ binaries rather than a list of integers. It is not needed to
+ flatten a deep list before writing. On Unix hosts, scatter output,
+ which writes a set of buffers in one operation, is used when
+ possible. In this way <c>write(FD, [Bin1, Bin2 | Bin3])</c>
+ writes the contents of the binaries without copying the data
+ at all, except for perhaps deep down in the operating system
+ kernel.</p>
+ <warning>
+ <p>If an error occurs when accessing an open file with module
+ <seealso marker="stdlib:io"><c>io</c></seealso>, the process
+ handling the file exits. The dead file process can hang if a process
+ tries to access it later. This will be fixed in a future release.
+ </p>
+ </warning>
</section>
<section>
diff --git a/lib/kernel/doc/src/net_kernel.xml b/lib/kernel/doc/src/net_kernel.xml
index 0b94fc0fa6..94c8fb9e20 100644
--- a/lib/kernel/doc/src/net_kernel.xml
+++ b/lib/kernel/doc/src/net_kernel.xml
@@ -230,7 +230,12 @@ $ <input>erl -sname foobar</input></pre>
<item>
<p>The tuple <c>{nodedown_reason, Reason}</c> is included in
<c>InfoList</c> in <c>nodedown</c> messages.</p>
- <p><c>Reason</c> can be any of the following:</p>
+ <p>
+ <c>Reason</c> can, depending on which
+ distribution module or process that is used be any term,
+ but for the standard TCP distribution module it is
+ any of the following:
+ </p>
<taglist>
<tag><c>connection_setup_failed</c></tag>
<item><p>The connection setup failed (after <c>nodeup</c>
diff --git a/lib/kernel/src/Makefile b/lib/kernel/src/Makefile
index 5946620f0f..4a713b2a99 100644
--- a/lib/kernel/src/Makefile
+++ b/lib/kernel/src/Makefile
@@ -120,6 +120,13 @@ MODULES = \
user \
user_drv \
user_sup \
+ raw_file_io \
+ raw_file_io_compressed \
+ raw_file_io_inflate \
+ raw_file_io_deflate \
+ raw_file_io_delayed \
+ raw_file_io_list \
+ raw_file_io_raw \
wrap_log_reader
HRL_FILES= ../include/file.hrl ../include/inet.hrl ../include/inet_sctp.hrl \
@@ -226,7 +233,8 @@ $(EBIN)/disk_log_server.beam: disk_log.hrl
$(EBIN)/dist_util.beam: ../include/dist_util.hrl ../include/dist.hrl
$(EBIN)/erl_boot_server.beam: inet_boot.hrl
$(EBIN)/erl_epmd.beam: inet_int.hrl erl_epmd.hrl
-$(EBIN)/file.beam: ../include/file.hrl
+$(EBIN)/file.beam: ../include/file.hrl file_int.hrl
+$(EBIN)/file_io_server.beam: ../include/file.hrl file_int.hrl
$(EBIN)/gen_tcp.beam: inet_int.hrl
$(EBIN)/gen_udp.beam: inet_int.hrl
$(EBIN)/gen_sctp.beam: ../include/inet_sctp.hrl
@@ -254,3 +262,10 @@ $(EBIN)/net_kernel.beam: ../include/net_address.hrl
$(EBIN)/os.beam: ../include/file.hrl
$(EBIN)/ram_file.beam: ../include/file.hrl
$(EBIN)/wrap_log_reader.beam: disk_log.hrl ../include/file.hrl
+$(EBIN)/raw_file_io.beam: ../include/file.hrl file_int.hrl
+$(EBIN)/raw_file_io_compressed.beam: ../include/file.hrl file_int.hrl
+$(EBIN)/raw_file_io_inflate.beam: ../include/file.hrl file_int.hrl
+$(EBIN)/raw_file_io_deflate.beam: ../include/file.hrl file_int.hrl
+$(EBIN)/raw_file_io_delayed.beam: ../include/file.hrl file_int.hrl
+$(EBIN)/raw_file_io_list.beam: ../include/file.hrl file_int.hrl
+$(EBIN)/raw_file_io_raw.beam: ../include/file.hrl file_int.hrl
diff --git a/lib/kernel/src/file.erl b/lib/kernel/src/file.erl
index 933f2d5f65..d05199897f 100644
--- a/lib/kernel/src/file.erl
+++ b/lib/kernel/src/file.erl
@@ -72,7 +72,7 @@
io_device/0, name/0, name_all/0, posix/0]).
%%% Includes and defines
--include("file.hrl").
+-include("file_int.hrl").
-define(FILE_IO_SERVER_TABLE, file_io_servers).
@@ -454,41 +454,23 @@ raw_write_file_info(Name, #file_info{} = Info) ->
Reason :: posix() | badarg | system_limit.
open(Item, ModeList) when is_list(ModeList) ->
- case lists:member(raw, ModeList) of
- %% Raw file, use ?PRIM_FILE to handle this file
- true ->
+ case {lists:member(raw, ModeList), lists:member(ram, ModeList)} of
+ {false, false} ->
+ %% File server file
Args = [file_name(Item) | ModeList],
case check_args(Args) of
ok ->
[FileName | _] = Args,
- %% We rely on the returned Handle (in {ok, Handle})
- %% being a pid() or a #file_descriptor{}
- ?PRIM_FILE:open(FileName, ModeList);
+ call(open, [FileName, ModeList]);
Error ->
Error
- end;
- false ->
- case lists:member(ram, ModeList) of
- %% RAM file, use ?RAM_FILE to handle this file
- true ->
- case check_args(ModeList) of
- ok ->
- ?RAM_FILE:open(Item, ModeList);
- Error ->
- Error
- end;
- %% File server file
- false ->
- Args = [file_name(Item) | ModeList],
- case check_args(Args) of
- ok ->
- [FileName | _] = Args,
- call(open, [FileName, ModeList]);
- Error ->
- Error
- end
- end
+ end;
+ {true, _Either} ->
+ raw_file_io:open(file_name(Item), ModeList);
+ {false, true} ->
+ ram_file:open(Item, ModeList)
end;
+
%% Old obsolete mode specification in atom or 2-tuple format
open(Item, Mode) ->
open(Item, mode_list(Mode)).
@@ -1254,15 +1236,18 @@ sendfile(File, _Sock, _Offet, _Bytes, _Opts) when is_pid(File) ->
sendfile(File, Sock, Offset, Bytes, []) ->
sendfile(File, Sock, Offset, Bytes, ?MAX_CHUNK_SIZE, [], [], []);
sendfile(File, Sock, Offset, Bytes, Opts) ->
- ChunkSize0 = proplists:get_value(chunk_size, Opts, ?MAX_CHUNK_SIZE),
- ChunkSize = if ChunkSize0 > ?MAX_CHUNK_SIZE ->
- ?MAX_CHUNK_SIZE;
- true -> ChunkSize0
- end,
- %% Support for headers, trailers and options has been removed because the
- %% Darwin and BSD API for using it does not play nice with
- %% non-blocking sockets. See unix_efile.c for more info.
- sendfile(File, Sock, Offset, Bytes, ChunkSize, [], [], Opts).
+ try proplists:get_value(chunk_size, Opts, ?MAX_CHUNK_SIZE) of
+ ChunkSize0 when is_integer(ChunkSize0) ->
+ ChunkSize = erlang:min(ChunkSize0, ?MAX_CHUNK_SIZE),
+ %% Support for headers, trailers and options has been removed
+ %% because the Darwin and BSD API for using it does not play nice
+ %% with non-blocking sockets. See unix_efile.c for more info.
+ sendfile(File, Sock, Offset, Bytes, ChunkSize, [], [], Opts);
+ _Other ->
+ {error, badarg}
+ catch
+ error:_ -> {error, badarg}
+ end.
%% sendfile/2
-spec sendfile(Filename, Socket) ->
diff --git a/lib/kernel/src/file_int.hrl b/lib/kernel/src/file_int.hrl
new file mode 100644
index 0000000000..bafc330c04
--- /dev/null
+++ b/lib/kernel/src/file_int.hrl
@@ -0,0 +1,33 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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%
+%%
+
+%%
+%% Internal definitions for the 'file' module and friends.
+%%
+
+-ifndef(FILE_INTERNAL_HRL_).
+-define(FILE_INTERNAL_HRL_, 1).
+
+-include("file.hrl").
+
+-define(CALL_FD(Fd, Method, Args),
+ apply(Fd#file_descriptor.module, Method, [Fd | Args])).
+
+-endif.
diff --git a/lib/kernel/src/file_io_server.erl b/lib/kernel/src/file_io_server.erl
index deb7b315b1..2b35d2acfb 100644
--- a/lib/kernel/src/file_io_server.erl
+++ b/lib/kernel/src/file_io_server.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2000-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.
@@ -28,7 +28,8 @@
-record(state, {handle,owner,mref,buf,read_mode,unic}).
--define(PRIM_FILE, prim_file).
+-include("file_int.hrl").
+
-define(READ_SIZE_LIST, 128).
-define(READ_SIZE_BINARY, (8*1024)).
@@ -68,7 +69,7 @@ do_start(Spawn, Owner, FileName, ModeList) ->
%% process_flag(trap_exit, true),
case parse_options(ModeList) of
{ReadMode, UnicodeMode, Opts} ->
- case ?PRIM_FILE:open(FileName, Opts) of
+ case raw_file_io:open(FileName, [raw | Opts]) of
{error, Reason} = Error ->
Self ! {Ref, Error},
exit(Reason);
@@ -205,7 +206,7 @@ io_reply(From, ReplyAs, Reply) ->
file_request({advise,Offset,Length,Advise},
#state{handle=Handle}=State) ->
- case ?PRIM_FILE:advise(Handle, Offset, Length, Advise) of
+ case ?CALL_FD(Handle, advise, [Offset, Length, Advise]) of
{error,Reason}=Reply ->
{stop,Reason,Reply,State};
Reply ->
@@ -213,7 +214,7 @@ file_request({advise,Offset,Length,Advise},
end;
file_request({allocate, Offset, Length},
#state{handle = Handle} = State) ->
- Reply = ?PRIM_FILE:allocate(Handle, Offset, Length),
+ Reply = ?CALL_FD(Handle, allocate, [Offset, Length]),
{reply, Reply, State};
file_request({pread,At,Sz}, State)
when At =:= cur;
@@ -256,7 +257,7 @@ file_request({pwrite,At,Data},
end;
file_request(datasync,
#state{handle=Handle}=State) ->
- case ?PRIM_FILE:datasync(Handle) of
+ case ?CALL_FD(Handle, datasync, []) of
{error,Reason}=Reply ->
{stop,Reason,Reply,State};
Reply ->
@@ -264,7 +265,7 @@ file_request(datasync,
end;
file_request(sync,
#state{handle=Handle}=State) ->
- case ?PRIM_FILE:sync(Handle) of
+ case ?CALL_FD(Handle, sync, []) of
{error,Reason}=Reply ->
{stop,Reason,Reply,State};
Reply ->
@@ -272,7 +273,7 @@ file_request(sync,
end;
file_request(close,
#state{handle=Handle}=State) ->
- case ?PRIM_FILE:close(Handle) of
+ case ?CALL_FD(Handle, close, []) of
{error,Reason}=Reply ->
{stop,Reason,Reply,State#state{buf= <<>>}};
Reply ->
@@ -288,7 +289,7 @@ file_request({position,At},
end;
file_request(truncate,
#state{handle=Handle}=State) ->
- case ?PRIM_FILE:truncate(Handle) of
+ case ?CALL_FD(Handle, truncate, []) of
{error,Reason}=Reply ->
{stop,Reason,Reply,State#state{buf= <<>>}};
Reply ->
@@ -398,7 +399,7 @@ io_request_loop([Request|Tail],
%%
put_chars(Chars, latin1, #state{handle=Handle, unic=latin1}=State) ->
NewState = State#state{buf = <<>>},
- case ?PRIM_FILE:write(Handle, Chars) of
+ case ?CALL_FD(Handle, write, [Chars]) of
{error,Reason}=Reply ->
{stop,Reason,Reply,NewState};
Reply ->
@@ -408,7 +409,7 @@ put_chars(Chars, InEncoding, #state{handle=Handle, unic=OutEncoding}=State) ->
NewState = State#state{buf = <<>>},
case unicode:characters_to_binary(Chars,InEncoding,OutEncoding) of
Bin when is_binary(Bin) ->
- case ?PRIM_FILE:write(Handle, Bin) of
+ case ?CALL_FD(Handle, write, [Bin]) of
{error,Reason}=Reply ->
{stop,Reason,Reply,NewState};
Reply ->
@@ -422,7 +423,7 @@ put_chars(Chars, InEncoding, #state{handle=Handle, unic=OutEncoding}=State) ->
get_line(S, {<<>>, Cont}, OutEnc,
#state{handle=Handle, read_mode=Mode, unic=InEnc}=State) ->
- case ?PRIM_FILE:read(Handle, read_size(Mode)) of
+ case ?CALL_FD(Handle, read, [read_size(Mode)]) of
{ok,Bin} ->
get_line(S, convert_enc([Cont, Bin], InEnc, OutEnc), OutEnc, State);
eof ->
@@ -472,7 +473,7 @@ get_chars(N, OutEnc,#state{handle=Handle,buf=Buf,read_mode=ReadMode,unic=latin1}
BufSize = byte_size(Buf),
NeedSize = N-BufSize,
Size = erlang:max(NeedSize, ?READ_SIZE_BINARY),
- case ?PRIM_FILE:read(Handle, Size) of
+ case ?CALL_FD(Handle, read, [Size]) of
{ok, B} ->
if BufSize+byte_size(B) < N ->
std_reply(cat(Buf, B, ReadMode,latin1,OutEnc), State);
@@ -504,7 +505,7 @@ get_chars(N, OutEnc,#state{handle=Handle,buf=Buf,read_mode=ReadMode,unic=InEncod
%% Need more, Try to read 4*needed in bytes...
NeedSize = (N - BufCount) * 4,
Size = erlang:max(NeedSize, ?READ_SIZE_BINARY),
- case ?PRIM_FILE:read(Handle, Size) of
+ case ?CALL_FD(Handle, read, [Size]) of
{ok, B} ->
NewBuf = list_to_binary([Buf,B]),
{NewCount,NewSplit} = count_and_find(NewBuf,N,InEncoding),
@@ -544,7 +545,7 @@ get_chars(Mod, Func, XtraArg, OutEnc, #state{buf=Buf}=State) ->
get_chars_empty(Mod, Func, XtraArg, S, latin1,
#state{handle=Handle,read_mode=ReadMode, unic=latin1}=State) ->
- case ?PRIM_FILE:read(Handle, read_size(ReadMode)) of
+ case ?CALL_FD(Handle, read, [read_size(ReadMode)]) of
{ok,Bin} ->
get_chars_apply(Mod, Func, XtraArg, S, latin1, State, Bin);
eof ->
@@ -554,7 +555,7 @@ get_chars_empty(Mod, Func, XtraArg, S, latin1,
end;
get_chars_empty(Mod, Func, XtraArg, S, OutEnc,
#state{handle=Handle,read_mode=ReadMode}=State) ->
- case ?PRIM_FILE:read(Handle, read_size(ReadMode)) of
+ case ?CALL_FD(Handle, read, [read_size(ReadMode)]) of
{ok,Bin} ->
get_chars_apply(Mod, Func, XtraArg, S, OutEnc, State, Bin);
eof ->
@@ -564,7 +565,7 @@ get_chars_empty(Mod, Func, XtraArg, S, OutEnc,
end.
get_chars_notempty(Mod, Func, XtraArg, S, OutEnc,
#state{handle=Handle,read_mode=ReadMode,buf = B}=State) ->
- case ?PRIM_FILE:read(Handle, read_size(ReadMode)) of
+ case ?CALL_FD(Handle, read, [read_size(ReadMode)]) of
{ok,Bin} ->
get_chars_apply(Mod, Func, XtraArg, S, OutEnc, State, list_to_binary([B,Bin]));
eof ->
@@ -918,13 +919,10 @@ cbv({utf32,little},_) ->
%% Compensates ?PRIM_FILE:position/2 for the number of bytes
%% we have buffered
position(Handle, At, Buf) ->
- ?PRIM_FILE:position(
- Handle,
- case At of
- cur ->
- {cur, -byte_size(Buf)};
- {cur, Offs} ->
- {cur, Offs-byte_size(Buf)};
- _ ->
- At
- end).
+ SeekTo =
+ case At of
+ {cur, Offs} -> {cur, Offs-byte_size(Buf)};
+ cur -> {cur, -byte_size(Buf)};
+ _ -> At
+ end,
+ ?CALL_FD(Handle, position, [SeekTo]).
diff --git a/lib/kernel/src/file_server.erl b/lib/kernel/src/file_server.erl
index 6e8f64d932..ecc1ffbdd6 100644
--- a/lib/kernel/src/file_server.erl
+++ b/lib/kernel/src/file_server.erl
@@ -63,7 +63,7 @@ stop() ->
%%% Callback functions from gen_server
%%%----------------------------------------------------------------------
--type state() :: port(). % Internal type
+-type state() :: term(). % Internal type
%%----------------------------------------------------------------------
%% Func: init/1
@@ -77,14 +77,8 @@ stop() ->
init([]) ->
process_flag(trap_exit, true),
- case ?PRIM_FILE:start() of
- {ok, Handle} ->
- ?FILE_IO_SERVER_TABLE =
- ets:new(?FILE_IO_SERVER_TABLE, [named_table]),
- {ok, Handle};
- {error, Reason} ->
- {stop, Reason}
- end.
+ ?FILE_IO_SERVER_TABLE = ets:new(?FILE_IO_SERVER_TABLE, [named_table]),
+ {ok, undefined}.
%%----------------------------------------------------------------------
%% Func: handle_call/3
@@ -101,7 +95,7 @@ init([]) ->
{'reply', 'eof' | 'ok' | {'error', term()} | {'ok', term()}, state()} |
{'stop', 'normal', 'stopped', state()}.
-handle_call({open, Name, ModeList}, {Pid, _Tag} = _From, Handle)
+handle_call({open, Name, ModeList}, {Pid, _Tag} = _From, State)
when is_list(ModeList) ->
Child = ?FILE_IO_SERVER:start_link(Pid, Name, ModeList),
case Child of
@@ -110,78 +104,78 @@ handle_call({open, Name, ModeList}, {Pid, _Tag} = _From, Handle)
_ ->
ok
end,
- {reply, Child, Handle};
+ {reply, Child, State};
-handle_call({open, _Name, _Mode}, _From, Handle) ->
- {reply, {error, einval}, Handle};
+handle_call({open, _Name, _Mode}, _From, State) ->
+ {reply, {error, einval}, State};
-handle_call({read_file, Name}, _From, Handle) ->
- {reply, ?PRIM_FILE:read_file(Name), Handle};
+handle_call({read_file, Name}, _From, State) ->
+ {reply, ?PRIM_FILE:read_file(Name), State};
-handle_call({write_file, Name, Bin}, _From, Handle) ->
- {reply, ?PRIM_FILE:write_file(Name, Bin), Handle};
+handle_call({write_file, Name, Bin}, _From, State) ->
+ {reply, ?PRIM_FILE:write_file(Name, Bin), State};
-handle_call({set_cwd, Name}, _From, Handle) ->
- {reply, ?PRIM_FILE:set_cwd(Handle, Name), Handle};
+handle_call({set_cwd, Name}, _From, State) ->
+ {reply, ?PRIM_FILE:set_cwd(Name), State};
-handle_call({delete, Name}, _From, Handle) ->
- {reply, ?PRIM_FILE:delete(Handle, Name), Handle};
+handle_call({delete, Name}, _From, State) ->
+ {reply, ?PRIM_FILE:delete(Name), State};
-handle_call({rename, Fr, To}, _From, Handle) ->
- {reply, ?PRIM_FILE:rename(Handle, Fr, To), Handle};
+handle_call({rename, Fr, To}, _From, State) ->
+ {reply, ?PRIM_FILE:rename(Fr, To), State};
-handle_call({make_dir, Name}, _From, Handle) ->
- {reply, ?PRIM_FILE:make_dir(Handle, Name), Handle};
+handle_call({make_dir, Name}, _From, State) ->
+ {reply, ?PRIM_FILE:make_dir(Name), State};
-handle_call({del_dir, Name}, _From, Handle) ->
- {reply, ?PRIM_FILE:del_dir(Handle, Name), Handle};
+handle_call({del_dir, Name}, _From, State) ->
+ {reply, ?PRIM_FILE:del_dir(Name), State};
-handle_call({list_dir, Name}, _From, Handle) ->
- {reply, ?PRIM_FILE:list_dir(Handle, Name), Handle};
-handle_call({list_dir_all, Name}, _From, Handle) ->
- {reply, ?PRIM_FILE:list_dir_all(Handle, Name), Handle};
+handle_call({list_dir, Name}, _From, State) ->
+ {reply, ?PRIM_FILE:list_dir(Name), State};
+handle_call({list_dir_all, Name}, _From, State) ->
+ {reply, ?PRIM_FILE:list_dir_all(Name), State};
-handle_call(get_cwd, _From, Handle) ->
- {reply, ?PRIM_FILE:get_cwd(Handle), Handle};
-handle_call({get_cwd}, _From, Handle) ->
- {reply, ?PRIM_FILE:get_cwd(Handle), Handle};
-handle_call({get_cwd, Name}, _From, Handle) ->
- {reply, ?PRIM_FILE:get_cwd(Handle, Name), Handle};
+handle_call(get_cwd, _From, State) ->
+ {reply, ?PRIM_FILE:get_cwd(), State};
+handle_call({get_cwd}, _From, State) ->
+ {reply, ?PRIM_FILE:get_cwd(), State};
+handle_call({get_cwd, Name}, _From, State) ->
+ {reply, ?PRIM_FILE:get_cwd(Name), State};
-handle_call({read_file_info, Name}, _From, Handle) ->
- {reply, ?PRIM_FILE:read_file_info(Handle, Name), Handle};
+handle_call({read_file_info, Name}, _From, State) ->
+ {reply, ?PRIM_FILE:read_file_info(Name), State};
-handle_call({read_file_info, Name, Opts}, _From, Handle) ->
- {reply, ?PRIM_FILE:read_file_info(Handle, Name, Opts), Handle};
+handle_call({read_file_info, Name, Opts}, _From, State) ->
+ {reply, ?PRIM_FILE:read_file_info(Name, Opts), State};
-handle_call({altname, Name}, _From, Handle) ->
- {reply, ?PRIM_FILE:altname(Handle, Name), Handle};
+handle_call({altname, Name}, _From, State) ->
+ {reply, ?PRIM_FILE:altname(Name), State};
-handle_call({write_file_info, Name, Info}, _From, Handle) ->
- {reply, ?PRIM_FILE:write_file_info(Handle, Name, Info), Handle};
+handle_call({write_file_info, Name, Info}, _From, State) ->
+ {reply, ?PRIM_FILE:write_file_info(Name, Info), State};
-handle_call({write_file_info, Name, Info, Opts}, _From, Handle) ->
- {reply, ?PRIM_FILE:write_file_info(Handle, Name, Info, Opts), Handle};
+handle_call({write_file_info, Name, Info, Opts}, _From, State) ->
+ {reply, ?PRIM_FILE:write_file_info(Name, Info, Opts), State};
-handle_call({read_link_info, Name}, _From, Handle) ->
- {reply, ?PRIM_FILE:read_link_info(Handle, Name), Handle};
+handle_call({read_link_info, Name}, _From, State) ->
+ {reply, ?PRIM_FILE:read_link_info(Name), State};
-handle_call({read_link_info, Name, Opts}, _From, Handle) ->
- {reply, ?PRIM_FILE:read_link_info(Handle, Name, Opts), Handle};
+handle_call({read_link_info, Name, Opts}, _From, State) ->
+ {reply, ?PRIM_FILE:read_link_info(Name, Opts), State};
-handle_call({read_link, Name}, _From, Handle) ->
- {reply, ?PRIM_FILE:read_link(Handle, Name), Handle};
-handle_call({read_link_all, Name}, _From, Handle) ->
- {reply, ?PRIM_FILE:read_link_all(Handle, Name), Handle};
+handle_call({read_link, Name}, _From, State) ->
+ {reply, ?PRIM_FILE:read_link(Name), State};
+handle_call({read_link_all, Name}, _From, State) ->
+ {reply, ?PRIM_FILE:read_link_all(Name), State};
-handle_call({make_link, Old, New}, _From, Handle) ->
- {reply, ?PRIM_FILE:make_link(Handle, Old, New), Handle};
+handle_call({make_link, Old, New}, _From, State) ->
+ {reply, ?PRIM_FILE:make_link(Old, New), State};
-handle_call({make_symlink, Old, New}, _From, Handle) ->
- {reply, ?PRIM_FILE:make_symlink(Handle, Old, New), Handle};
+handle_call({make_symlink, Old, New}, _From, State) ->
+ {reply, ?PRIM_FILE:make_symlink(Old, New), State};
handle_call({copy, SourceName, SourceOpts, DestName, DestOpts, Length},
- _From, Handle) ->
+ _From, State) ->
Reply =
case ?PRIM_FILE:open(SourceName, [read, binary | SourceOpts]) of
{ok, Source} ->
@@ -201,14 +195,14 @@ handle_call({copy, SourceName, SourceOpts, DestName, DestOpts, Length},
{error, _} = Error ->
Error
end,
- {reply, Reply, Handle};
+ {reply, Reply, State};
-handle_call(stop, _From, Handle) ->
- {stop, normal, stopped, Handle};
+handle_call(stop, _From, State) ->
+ {stop, normal, stopped, State};
-handle_call(Request, From, Handle) ->
+handle_call(Request, From, State) ->
error_logger:error_msg("handle_call(~tp, ~tp, _)", [Request, From]),
- {noreply, Handle}.
+ {noreply, State}.
%%----------------------------------------------------------------------
%% Func: handle_cast/2
@@ -233,14 +227,9 @@ handle_cast(Msg, State) ->
-spec handle_info(term(), state()) ->
{'noreply', state()} | {'stop', 'normal', state()}.
-handle_info({'EXIT', Pid, _Reason}, Handle) when is_pid(Pid) ->
+handle_info({'EXIT', Pid, _Reason}, State) when is_pid(Pid) ->
ets:delete(?FILE_IO_SERVER_TABLE, Pid),
- {noreply, Handle};
-
-handle_info({'EXIT', Handle, _Reason}, Handle) ->
- error_logger:error_msg("Port controlling ~w terminated in ~w",
- [?FILE_SERVER, ?MODULE]),
- {stop, normal, Handle};
+ {noreply, State};
handle_info(Info, State) ->
error_logger:error_msg("handle_Info(~tp, _)", [Info]),
@@ -254,8 +243,8 @@ handle_info(Info, State) ->
-spec terminate(term(), state()) -> 'ok'.
-terminate(_Reason, Handle) ->
- ?PRIM_FILE:stop(Handle).
+terminate(_Reason, _State) ->
+ ok.
%%----------------------------------------------------------------------
%% Func: code_change/3
diff --git a/lib/kernel/src/inet_int.hrl b/lib/kernel/src/inet_int.hrl
index bc5b67f7bf..357e27826c 100644
--- a/lib/kernel/src/inet_int.hrl
+++ b/lib/kernel/src/inet_int.hrl
@@ -100,6 +100,8 @@
-define(TCP_REQ_RECV, 42).
-define(TCP_REQ_UNRECV, 43).
-define(TCP_REQ_SHUTDOWN, 44).
+-define(TCP_REQ_SENDFILE, 45).
+
%% UDP and SCTP requests
-define(PACKET_REQ_RECV, 60).
%%-define(SCTP_REQ_LISTEN, 61). MERGED
@@ -319,6 +321,12 @@
[((X) bsr 24) band 16#ff, ((X) bsr 16) band 16#ff,
((X) bsr 8) band 16#ff, (X) band 16#ff]).
+-define(int64(X),
+ [((X) bsr 56) band 16#ff, ((X) bsr 48) band 16#ff,
+ ((X) bsr 40) band 16#ff, ((X) bsr 32) band 16#ff,
+ ((X) bsr 24) band 16#ff, ((X) bsr 16) band 16#ff,
+ ((X) bsr 8) band 16#ff, (X) band 16#ff]).
+
-define(intAID(X), % For SCTP AssocID
?int32(X)).
diff --git a/lib/kernel/src/kernel.app.src b/lib/kernel/src/kernel.app.src
index 080b11fc4d..e4852a6e75 100644
--- a/lib/kernel/src/kernel.app.src
+++ b/lib/kernel/src/kernel.app.src
@@ -88,6 +88,13 @@
inet_udp,
inet_sctp,
pg2,
+ raw_file_io,
+ raw_file_io_compressed,
+ raw_file_io_deflate,
+ raw_file_io_delayed,
+ raw_file_io_inflate,
+ raw_file_io_list,
+ raw_file_io_raw,
seq_trace,
standard_error,
wrap_log_reader]},
diff --git a/lib/kernel/src/raw_file_io.erl b/lib/kernel/src/raw_file_io.erl
new file mode 100644
index 0000000000..e3c07c8f78
--- /dev/null
+++ b/lib/kernel/src/raw_file_io.erl
@@ -0,0 +1,75 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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(raw_file_io).
+
+-export([open/2]).
+
+open(Filename, Modes) ->
+ %% Layers are applied in this order, and the listed modules will call this
+ %% function again as necessary. eg. a raw compressed delayed file in list
+ %% mode will walk through [_list -> _compressed -> _delayed -> _raw].
+ ModuleOrder = [{raw_file_io_list, fun match_list/1},
+ {raw_file_io_compressed, fun match_compressed/1},
+ {raw_file_io_delayed, fun match_delayed/1},
+ {raw_file_io_raw, fun match_raw/1}],
+ open_1(ModuleOrder, Filename, add_implicit_modes(Modes)).
+open_1([], _Filename, _Modes) ->
+ error(badarg);
+open_1([{Module, Match} | Rest], Filename, Modes) ->
+ case lists:any(Match, Modes) of
+ true ->
+ {Options, ChildModes} =
+ lists:partition(fun(Mode) -> Match(Mode) end, Modes),
+ Module:open_layer(Filename, ChildModes, Options);
+ false ->
+ open_1(Rest, Filename, Modes)
+ end.
+
+%% 'read' and 'list' mode are enabled unless disabled by another option, so
+%% we'll explicitly add them to avoid duplicating this logic in child layers.
+add_implicit_modes(Modes0) ->
+ Modes1 = add_unless_matched(Modes0, fun match_writable/1, read),
+ add_unless_matched(Modes1, fun match_binary/1, list).
+add_unless_matched(Modes, Match, Default) ->
+ case lists:any(Match, Modes) of
+ false -> [Default | Modes];
+ true -> Modes
+ end.
+
+match_list(list) -> true;
+match_list(_Other) -> false.
+
+match_compressed(compressed) -> true;
+match_compressed(_Other) -> false.
+
+match_delayed({delayed_write, _Size, _Timeout}) -> true;
+match_delayed(delayed_write) -> true;
+match_delayed(_Other) -> false.
+
+match_raw(raw) -> true;
+match_raw(_Other) -> false.
+
+match_writable(write) -> true;
+match_writable(append) -> true;
+match_writable(exclusive) -> true;
+match_writable(_Other) -> false.
+
+match_binary(binary) -> true;
+match_binary(_Other) -> false.
diff --git a/lib/kernel/src/raw_file_io_compressed.erl b/lib/kernel/src/raw_file_io_compressed.erl
new file mode 100644
index 0000000000..d5ab042d25
--- /dev/null
+++ b/lib/kernel/src/raw_file_io_compressed.erl
@@ -0,0 +1,134 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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(raw_file_io_compressed).
+
+-export([close/1, sync/1, datasync/1, truncate/1, advise/4, allocate/3,
+ position/2, write/2, pwrite/2, pwrite/3,
+ read_line/1, read/2, pread/2, pread/3]).
+
+%% OTP internal.
+-export([ipread_s32bu_p32bu/3, sendfile/8]).
+
+-export([open_layer/3]).
+
+-include("file_int.hrl").
+
+open_layer(Filename, Modes, Options) ->
+ IsAppend = lists:member(append, Modes),
+ IsDeflate = lists:member(write, Modes),
+ IsInflate = lists:member(read, Modes),
+ if
+ IsDeflate, IsInflate; IsAppend ->
+ {error, einval};
+ IsDeflate, not IsInflate ->
+ start_server_module(raw_file_io_deflate, Filename, Modes, Options);
+ IsInflate ->
+ start_server_module(raw_file_io_inflate, Filename, Modes, Options)
+ end.
+
+start_server_module(Module, Filename, Modes, Options) ->
+ Secret = make_ref(),
+ case gen_statem:start(Module, {self(), Secret, Options}, []) of
+ {ok, Pid} -> open_next_layer(Pid, Secret, Filename, Modes);
+ Other -> Other
+ end.
+
+open_next_layer(Pid, Secret, Filename, Modes) ->
+ case gen_statem:call(Pid, {'$open', Secret, Filename, Modes}, infinity) of
+ ok ->
+ PublicFd = #file_descriptor{
+ module = raw_file_io_compressed, data = {self(), Pid} },
+ {ok, PublicFd};
+ Other -> Other
+ end.
+
+close(Fd) ->
+ wrap_call(Fd, [close]).
+
+sync(Fd) ->
+ wrap_call(Fd, [sync]).
+datasync(Fd) ->
+ wrap_call(Fd, [datasync]).
+
+truncate(Fd) ->
+ wrap_call(Fd, [truncate]).
+
+advise(Fd, Offset, Length, Advise) ->
+ wrap_call(Fd, [advise, Offset, Length, Advise]).
+allocate(Fd, Offset, Length) ->
+ wrap_call(Fd, [allocate, Offset, Length]).
+
+position(Fd, Mark) ->
+ wrap_call(Fd, [position, Mark]).
+
+write(Fd, IOData) ->
+ try
+ CompactedData = erlang:iolist_to_iovec(IOData),
+ wrap_call(Fd, [write, CompactedData])
+ catch
+ error:badarg -> {error, badarg}
+ end.
+
+pwrite(Fd, Offset, IOData) ->
+ try
+ CompactedData = erlang:iolist_to_iovec(IOData),
+ wrap_call(Fd, [pwrite, Offset, CompactedData])
+ catch
+ error:badarg -> {error, badarg}
+ end.
+pwrite(Fd, LocBytes) ->
+ try
+ CompactedLocBytes =
+ [ {Offset, erlang:iolist_to_iovec(IOData)} ||
+ {Offset, IOData} <- LocBytes ],
+ wrap_call(Fd, [pwrite, CompactedLocBytes])
+ catch
+ error:badarg -> {error, badarg}
+ end.
+
+read_line(Fd) ->
+ wrap_call(Fd, [read_line]).
+read(Fd, Size) ->
+ wrap_call(Fd, [read, Size]).
+pread(Fd, Offset, Size) ->
+ wrap_call(Fd, [pread, Offset, Size]).
+pread(Fd, LocNums) ->
+ wrap_call(Fd, [pread, LocNums]).
+
+ipread_s32bu_p32bu(Fd, Offset, MaxSize) ->
+ wrap_call(Fd, [ipread_s32bu_p32bu, Offset, MaxSize]).
+
+sendfile(_,_,_,_,_,_,_,_) ->
+ {error, enotsup}.
+
+wrap_call(Fd, Command) ->
+ {_Owner, Pid} = get_fd_data(Fd),
+ try gen_statem:call(Pid, Command, infinity) of
+ Result -> Result
+ catch
+ exit:{noproc, _StackTrace} -> {error, einval}
+ end.
+
+get_fd_data(#file_descriptor{ data = Data }) ->
+ {Owner, _ServerPid} = Data,
+ case self() of
+ Owner -> Data;
+ _ -> error(not_on_controlling_process)
+ end.
diff --git a/lib/kernel/src/raw_file_io_deflate.erl b/lib/kernel/src/raw_file_io_deflate.erl
new file mode 100644
index 0000000000..acfc546743
--- /dev/null
+++ b/lib/kernel/src/raw_file_io_deflate.erl
@@ -0,0 +1,159 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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(raw_file_io_deflate).
+
+-behavior(gen_statem).
+
+-export([init/1, callback_mode/0, terminate/3]).
+-export([opening/3, opened/3]).
+
+-include("file_int.hrl").
+
+-define(GZIP_WBITS, 16 + 15).
+
+callback_mode() -> state_functions.
+
+init({Owner, Secret, [compressed]}) ->
+ Monitor = monitor(process, Owner),
+ Z = zlib:open(),
+ ok = zlib:deflateInit(Z, default, deflated, ?GZIP_WBITS, 8, default),
+ Data =
+ #{ owner => Owner,
+ monitor => Monitor,
+ secret => Secret,
+ position => 0,
+ zlib => Z },
+ {ok, opening, Data}.
+
+opening({call, From}, {'$open', Secret, Filename, Modes}, #{ secret := Secret } = Data) ->
+ case raw_file_io:open(Filename, Modes) of
+ {ok, PrivateFd} ->
+ NewData = Data#{ handle => PrivateFd },
+ {next_state, opened, NewData, [{reply, From, ok}]};
+ Other ->
+ {stop_and_reply, normal, [{reply, From, Other}]}
+ end;
+opening(_Event, _Contents, _Data) ->
+ {keep_state_and_data, [postpone]}.
+
+%%
+
+opened(info, {'DOWN', Monitor, process, _Owner, Reason}, #{ monitor := Monitor } = Data) ->
+ if
+ Reason =/= kill -> flush_deflate_state(Data);
+ Reason =:= kill -> ignored
+ end,
+ {stop, shutdown};
+
+opened(info, _Message, _Data) ->
+ keep_state_and_data;
+
+opened({call, {Owner, _Tag} = From}, [close], #{ owner := Owner } = Data) ->
+ #{ handle := PrivateFd } = Data,
+ Response =
+ case flush_deflate_state(Data) of
+ ok -> ?CALL_FD(PrivateFd, close, []);
+ Other -> Other
+ end,
+ {stop_and_reply, normal, [{reply, From, Response}]};
+
+opened({call, {Owner, _Tag} = From}, [position, Mark], #{ owner := Owner } = Data) ->
+ case position(Data, Mark) of
+ {ok, NewData, Result} ->
+ Response = {ok, Result},
+ {keep_state, NewData, [{reply, From, Response}]};
+ Other ->
+ {keep_state_and_data, [{reply, From, Other}]}
+ end;
+
+opened({call, {Owner, _Tag} = From}, [write, IOVec], #{ owner := Owner } = Data) ->
+ case write(Data, IOVec) of
+ {ok, NewData} -> {keep_state, NewData, [{reply, From, ok}]};
+ Other -> {keep_state_and_data, [{reply, From, Other}]}
+ end;
+
+opened({call, {Owner, _Tag} = From}, [read, _Size], #{ owner := Owner }) ->
+ Response = {error, ebadf},
+ {keep_state_and_data, [{reply, From, Response}]};
+
+opened({call, {Owner, _Tag} = From}, [read_line], #{ owner := Owner }) ->
+ Response = {error, ebadf},
+ {keep_state_and_data, [{reply, From, Response}]};
+
+opened({call, {Owner, _Tag} = From}, _Command, #{ owner := Owner }) ->
+ Response = {error, enotsup},
+ {keep_state_and_data, [{reply, From, Response}]};
+
+opened({call, _From}, _Command, _Data) ->
+ %% The client functions filter this out, so we'll crash if the user does
+ %% anything stupid on purpose.
+ {shutdown, protocol_violation};
+
+opened(_Event, _Request, _Data) ->
+ keep_state_and_data.
+
+write(Data, IOVec) ->
+ #{ handle := PrivateFd, position := Position, zlib := Z } = Data,
+ UncompressedSize = iolist_size(IOVec),
+ case ?CALL_FD(PrivateFd, write, [zlib:deflate(Z, IOVec)]) of
+ ok -> {ok, Data#{ position := (Position + UncompressedSize) }};
+ Other -> Other
+ end.
+
+%%
+%% We support "seeking" forward as long as it isn't relative to EOF.
+%%
+%% Seeking is a bit of a misnomer as it's really just compressing zeroes until
+%% we reach the desired point, but it has always behaved like this.
+%%
+
+position(Data, Mark) when is_atom(Mark) ->
+ position(Data, {Mark, 0});
+position(Data, Offset) when is_integer(Offset) ->
+ position(Data, {bof, Offset});
+position(Data, {bof, Offset}) when is_integer(Offset) ->
+ position_1(Data, Offset);
+position(Data, {cur, Offset}) when is_integer(Offset) ->
+ #{ position := Position } = Data,
+ position_1(Data, Position + Offset);
+position(_Data, {eof, Offset}) when is_integer(Offset) ->
+ {error, einval};
+position(_Data, _Any) ->
+ {error, badarg}.
+
+position_1(#{ position := Desired } = Data, Desired) ->
+ {ok, Data, Desired};
+position_1(#{ position := Current } = Data, Desired) when Current < Desired ->
+ BytesToWrite = min(Desired - Current, 4 bsl 20),
+ case write(Data, <<0:(BytesToWrite)/unit:8>>) of
+ {ok, NewData} -> position_1(NewData, Desired);
+ Other -> Other
+ end;
+position_1(#{ position := Current }, Desired) when Current > Desired ->
+ {error, einval}.
+
+flush_deflate_state(#{ handle := PrivateFd, zlib := Z }) ->
+ case ?CALL_FD(PrivateFd, write, [zlib:deflate(Z, [], finish)]) of
+ ok -> ok;
+ Other -> Other
+ end.
+
+terminate(_Reason, _State, _Data) ->
+ ok.
diff --git a/lib/kernel/src/raw_file_io_delayed.erl b/lib/kernel/src/raw_file_io_delayed.erl
new file mode 100644
index 0000000000..d2ad7550a1
--- /dev/null
+++ b/lib/kernel/src/raw_file_io_delayed.erl
@@ -0,0 +1,320 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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(raw_file_io_delayed).
+
+-behavior(gen_statem).
+
+-export([close/1, sync/1, datasync/1, truncate/1, advise/4, allocate/3,
+ position/2, write/2, pwrite/2, pwrite/3,
+ read_line/1, read/2, pread/2, pread/3]).
+
+%% OTP internal.
+-export([ipread_s32bu_p32bu/3, sendfile/8]).
+
+-export([open_layer/3]).
+
+-export([init/1, callback_mode/0, terminate/3]).
+-export([opening/3, opened/3]).
+
+-include("file_int.hrl").
+
+open_layer(Filename, Modes, Options) ->
+ Secret = make_ref(),
+ case gen_statem:start(?MODULE, {self(), Secret, Options}, []) of
+ {ok, Pid} ->
+ gen_statem:call(Pid, {'$open', Secret, Filename, Modes}, infinity);
+ Other ->
+ Other
+ end.
+
+callback_mode() -> state_functions.
+
+init({Owner, Secret, Options}) ->
+ Monitor = monitor(process, Owner),
+ Defaults =
+ #{ owner => Owner,
+ monitor => Monitor,
+ secret => Secret,
+ timer => none,
+ pid => self(),
+ buffer => prim_buffer:new(),
+ delay_size => 64 bsl 10,
+ delay_time => 2000 },
+ Data = fill_delay_values(Defaults, Options),
+ {ok, opening, Data}.
+
+fill_delay_values(Data, []) ->
+ Data;
+fill_delay_values(Data, [{delayed_write, Size, Time} | Options]) ->
+ fill_delay_values(Data#{ delay_size => Size, delay_time => Time }, Options);
+fill_delay_values(Data, [_ | Options]) ->
+ fill_delay_values(Data, Options).
+
+opening({call, From}, {'$open', Secret, Filename, Modes}, #{ secret := Secret } = Data) ->
+ case raw_file_io:open(Filename, Modes) of
+ {ok, PrivateFd} ->
+ PublicData = maps:with([owner, buffer, delay_size, pid], Data),
+ PublicFd = #file_descriptor{ module = ?MODULE, data = PublicData },
+
+ NewData = Data#{ handle => PrivateFd },
+ Response = {ok, PublicFd},
+ {next_state, opened, NewData, [{reply, From, Response}]};
+ Other ->
+ {stop_and_reply, normal, [{reply, From, Other}]}
+ end;
+opening(_Event, _Contents, _Data) ->
+ {keep_state_and_data, [postpone]}.
+
+%%
+
+opened(info, {'$timed_out', Secret}, #{ secret := Secret } = Data) ->
+ %% If the user writes something at this exact moment, the flush will fail
+ %% and the timer won't reset on the next write since the buffer won't be
+ %% empty (Unless we collided on a flush). We therefore reset the timeout to
+ %% ensure that data won't sit idle for extended periods of time.
+ case try_flush_write_buffer(Data) of
+ busy -> gen_statem:cast(self(), '$reset_timeout');
+ ok -> ok
+ end,
+ {keep_state, Data#{ timer => none }, []};
+
+opened(info, {'DOWN', Monitor, process, _Owner, Reason}, #{ monitor := Monitor } = Data) ->
+ if
+ Reason =/= kill -> try_flush_write_buffer(Data);
+ Reason =:= kill -> ignored
+ end,
+ {stop, shutdown};
+
+opened(info, _Message, _Data) ->
+ keep_state_and_data;
+
+opened({call, {Owner, _Tag} = From}, [close], #{ owner := Owner } = Data) ->
+ case flush_write_buffer(Data) of
+ ok ->
+ #{ handle := PrivateFd } = Data,
+ Response = ?CALL_FD(PrivateFd, close, []),
+ {stop_and_reply, normal, [{reply, From, Response}]};
+ Other ->
+ {stop_and_reply, normal, [{reply, From, Other}]}
+ end;
+
+opened({call, {Owner, _Tag} = From}, '$wait', #{ owner := Owner }) ->
+ %% Used in write/2 to synchronize writes on lock conflicts.
+ {keep_state_and_data, [{reply, From, ok}]};
+
+opened({call, {Owner, _Tag} = From}, '$synchronous_flush', #{ owner := Owner } = Data) ->
+ cancel_flush_timeout(Data),
+ Response = flush_write_buffer(Data),
+ {keep_state_and_data, [{reply, From, Response}]};
+
+opened({call, {Owner, _Tag} = From}, Command, #{ owner := Owner } = Data) ->
+ Response =
+ case flush_write_buffer(Data) of
+ ok -> dispatch_command(Data, Command);
+ Other -> Other
+ end,
+ {keep_state_and_data, [{reply, From, Response}]};
+
+opened({call, _From}, _Command, _Data) ->
+ %% The client functions filter this out, so we'll crash if the user does
+ %% anything stupid on purpose.
+ {shutdown, protocol_violation};
+
+opened(cast, '$reset_timeout', #{ delay_time := Timeout, secret := Secret } = Data) ->
+ cancel_flush_timeout(Data),
+ Timer = erlang:send_after(Timeout, self(), {'$timed_out', Secret}),
+ {keep_state, Data#{ timer => Timer }, []};
+
+opened(cast, _Message, _Data) ->
+ {keep_state_and_data, []}.
+
+dispatch_command(Data, [Function | Args]) ->
+ #{ handle := Handle } = Data,
+ Module = Handle#file_descriptor.module,
+ apply(Module, Function, [Handle | Args]).
+
+cancel_flush_timeout(#{ timer := none }) ->
+ ok;
+cancel_flush_timeout(#{ timer := Timer }) ->
+ _ = erlang:cancel_timer(Timer, [{async, true}]),
+ ok.
+
+try_flush_write_buffer(#{ buffer := Buffer, handle := PrivateFd }) ->
+ case prim_buffer:try_lock(Buffer) of
+ acquired ->
+ flush_write_buffer_1(Buffer, PrivateFd),
+ prim_buffer:unlock(Buffer),
+ ok;
+ busy ->
+ busy
+ end.
+
+%% This is only safe to use when there is no chance of conflict with the owner
+%% process, or in other words, "during synchronous calls outside of the locked
+%% section of write/2"
+flush_write_buffer(#{ buffer := Buffer, handle := PrivateFd }) ->
+ acquired = prim_buffer:try_lock(Buffer),
+ Result = flush_write_buffer_1(Buffer, PrivateFd),
+ prim_buffer:unlock(Buffer),
+ Result.
+
+flush_write_buffer_1(Buffer, PrivateFd) ->
+ case prim_buffer:size(Buffer) of
+ Size when Size > 0 ->
+ ?CALL_FD(PrivateFd, write, [prim_buffer:read_iovec(Buffer, Size)]);
+ 0 ->
+ ok
+ end.
+
+terminate(_Reason, _State, _Data) ->
+ ok.
+
+%% Client functions
+
+write(Fd, IOData) ->
+ try
+ enqueue_write(Fd, erlang:iolist_to_iovec(IOData))
+ catch
+ error:badarg -> {error, badarg}
+ end.
+enqueue_write(_Fd, []) ->
+ ok;
+enqueue_write(Fd, IOVec) ->
+ %% get_fd_data will reject everyone except the process that opened the Fd,
+ %% so we can't race with anyone except the wrapper process.
+ #{ delay_size := DelaySize,
+ buffer := Buffer,
+ pid := Pid } = get_fd_data(Fd),
+ case prim_buffer:try_lock(Buffer) of
+ acquired ->
+ %% (The wrapper process will exit without flushing if we're killed
+ %% while holding the lock).
+ enqueue_write_locked(Pid, Buffer, DelaySize, IOVec);
+ busy ->
+ %% This can only happen while we're processing a timeout in the
+ %% wrapper process, so we perform a bogus call to get a completion
+ %% notification before trying again.
+ gen_statem:call(Pid, '$wait'),
+ enqueue_write(Fd, IOVec)
+ end.
+enqueue_write_locked(Pid, Buffer, DelaySize, IOVec) ->
+ %% The synchronous operations (write, forced flush) are safe since we're
+ %% running on the only process that can fill the buffer; a timeout being
+ %% processed just before $synchronous_flush will cause the flush to nop,
+ %% and a timeout sneaking in just before a synchronous write won't do
+ %% anything since the buffer is guaranteed to be empty at that point.
+ BufSize = prim_buffer:size(Buffer),
+ case is_iovec_smaller_than(IOVec, DelaySize - BufSize) of
+ true when BufSize > 0 ->
+ prim_buffer:write(Buffer, IOVec),
+ prim_buffer:unlock(Buffer);
+ true ->
+ prim_buffer:write(Buffer, IOVec),
+ prim_buffer:unlock(Buffer),
+ gen_statem:cast(Pid, '$reset_timeout');
+ false when BufSize > 0 ->
+ prim_buffer:write(Buffer, IOVec),
+ prim_buffer:unlock(Buffer),
+ gen_statem:call(Pid, '$synchronous_flush');
+ false ->
+ prim_buffer:unlock(Buffer),
+ gen_statem:call(Pid, [write, IOVec])
+ end.
+
+%% iolist_size/1 will always look through the entire list to get a precise
+%% amount, which is pretty inefficient since we only need to know whether we've
+%% hit the buffer threshold or not.
+%%
+%% We only handle the binary case since write/2 forcibly translates input to
+%% erlang:iovec().
+is_iovec_smaller_than(IOVec, Max) ->
+ is_iovec_smaller_than_1(IOVec, Max, 0).
+is_iovec_smaller_than_1(_IOVec, Max, Acc) when Acc >= Max ->
+ false;
+is_iovec_smaller_than_1([], _Max, _Acc) ->
+ true;
+is_iovec_smaller_than_1([Binary | Rest], Max, Acc) when is_binary(Binary) ->
+ is_iovec_smaller_than_1(Rest, Max, Acc + byte_size(Binary)).
+
+close(Fd) ->
+ wrap_call(Fd, [close]).
+
+sync(Fd) ->
+ wrap_call(Fd, [sync]).
+datasync(Fd) ->
+ wrap_call(Fd, [datasync]).
+
+truncate(Fd) ->
+ wrap_call(Fd, [truncate]).
+
+advise(Fd, Offset, Length, Advise) ->
+ wrap_call(Fd, [advise, Offset, Length, Advise]).
+allocate(Fd, Offset, Length) ->
+ wrap_call(Fd, [allocate, Offset, Length]).
+
+position(Fd, Mark) ->
+ wrap_call(Fd, [position, Mark]).
+
+pwrite(Fd, Offset, IOData) ->
+ try
+ CompactedData = erlang:iolist_to_iovec(IOData),
+ wrap_call(Fd, [pwrite, Offset, CompactedData])
+ catch
+ error:badarg -> {error, badarg}
+ end.
+pwrite(Fd, LocBytes) ->
+ try
+ CompactedLocBytes =
+ [ {Offset, erlang:iolist_to_iovec(IOData)} ||
+ {Offset, IOData} <- LocBytes ],
+ wrap_call(Fd, [pwrite, CompactedLocBytes])
+ catch
+ error:badarg -> {error, badarg}
+ end.
+
+read_line(Fd) ->
+ wrap_call(Fd, [read_line]).
+read(Fd, Size) ->
+ wrap_call(Fd, [read, Size]).
+pread(Fd, Offset, Size) ->
+ wrap_call(Fd, [pread, Offset, Size]).
+pread(Fd, LocNums) ->
+ wrap_call(Fd, [pread, LocNums]).
+
+ipread_s32bu_p32bu(Fd, Offset, MaxSize) ->
+ wrap_call(Fd, [ipread_s32bu_p32bu, Offset, MaxSize]).
+
+sendfile(_,_,_,_,_,_,_,_) ->
+ {error, enotsup}.
+
+wrap_call(Fd, Command) ->
+ #{ pid := Pid } = get_fd_data(Fd),
+ try gen_statem:call(Pid, Command, infinity) of
+ Result -> Result
+ catch
+ exit:{noproc, _StackTrace} -> {error, einval}
+ end.
+
+get_fd_data(#file_descriptor{ data = Data }) ->
+ #{ owner := Owner } = Data,
+ case self() of
+ Owner -> Data;
+ _ -> error(not_on_controlling_process)
+ end.
diff --git a/lib/kernel/src/raw_file_io_inflate.erl b/lib/kernel/src/raw_file_io_inflate.erl
new file mode 100644
index 0000000000..7e9780310c
--- /dev/null
+++ b/lib/kernel/src/raw_file_io_inflate.erl
@@ -0,0 +1,261 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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(raw_file_io_inflate).
+
+-behavior(gen_statem).
+
+-export([init/1, callback_mode/0, terminate/3]).
+-export([opening/3, opened_gzip/3, opened_passthrough/3]).
+
+-include("file_int.hrl").
+
+-define(INFLATE_CHUNK_SIZE, (1 bsl 10)).
+-define(GZIP_WBITS, (16 + 15)).
+
+callback_mode() -> state_functions.
+
+init({Owner, Secret, [compressed]}) ->
+ Monitor = monitor(process, Owner),
+ %% We're using the undocumented inflateInit/3 to open the stream in
+ %% 'reset mode', which resets the inflate state at the end of every stream,
+ %% allowing us to read concatenated gzip files.
+ Z = zlib:open(),
+ ok = zlib:inflateInit(Z, ?GZIP_WBITS, reset),
+ Data =
+ #{ owner => Owner,
+ monitor => Monitor,
+ secret => Secret,
+ position => 0,
+ buffer => prim_buffer:new(),
+ zlib => Z },
+ {ok, opening, Data}.
+
+%% The old driver fell back to plain reads if the file didn't start with the
+%% magic gzip bytes.
+choose_decompression_state(PrivateFd) ->
+ State =
+ case ?CALL_FD(PrivateFd, read, [2]) of
+ {ok, <<16#1F, 16#8B>>} -> opened_gzip;
+ _Other -> opened_passthrough
+ end,
+ {ok, 0} = ?CALL_FD(PrivateFd, position, [0]),
+ State.
+
+opening({call, From}, {'$open', Secret, Filename, Modes}, #{ secret := Secret } = Data) ->
+ case raw_file_io:open(Filename, Modes) of
+ {ok, PrivateFd} ->
+ NextState = choose_decompression_state(PrivateFd),
+ NewData = Data#{ handle => PrivateFd },
+ {next_state, NextState, NewData, [{reply, From, ok}]};
+ Other ->
+ {stop_and_reply, normal, [{reply, From, Other}]}
+ end;
+opening(_Event, _Contents, _Data) ->
+ {keep_state_and_data, [postpone]}.
+
+internal_close(From, Data) ->
+ #{ handle := PrivateFd } = Data,
+ Response = ?CALL_FD(PrivateFd, close, []),
+ {stop_and_reply, normal, [{reply, From, Response}]}.
+
+opened_passthrough(info, {'DOWN', Monitor, process, _Owner, _Reason}, #{ monitor := Monitor }) ->
+ {stop, shutdown};
+
+opened_passthrough(info, _Message, _Data) ->
+ keep_state_and_data;
+
+opened_passthrough({call, {Owner, _Tag} = From}, [close], #{ owner := Owner } = Data) ->
+ internal_close(From, Data);
+
+opened_passthrough({call, {Owner, _Tag} = From}, [Method | Args], #{ owner := Owner } = Data) ->
+ #{ handle := PrivateFd } = Data,
+ Response = ?CALL_FD(PrivateFd, Method, Args),
+ {keep_state_and_data, [{reply, From, Response}]};
+
+opened_passthrough({call, _From}, _Command, _Data) ->
+ %% The client functions filter this out, so we'll crash if the user does
+ %% anything stupid on purpose.
+ {shutdown, protocol_violation};
+
+opened_passthrough(_Event, _Request, _Data) ->
+ keep_state_and_data.
+
+%%
+
+opened_gzip(info, {'DOWN', Monitor, process, _Owner, _Reason}, #{ monitor := Monitor }) ->
+ {stop, shutdown};
+
+opened_gzip(info, _Message, _Data) ->
+ keep_state_and_data;
+
+opened_gzip({call, {Owner, _Tag} = From}, [close], #{ owner := Owner } = Data) ->
+ internal_close(From, Data);
+
+opened_gzip({call, {Owner, _Tag} = From}, [position, Mark], #{ owner := Owner } = Data) ->
+ case position(Data, Mark) of
+ {ok, NewData, Result} ->
+ Response = {ok, Result},
+ {keep_state, NewData, [{reply, From, Response}]};
+ Other ->
+ {keep_state_and_data, [{reply, From, Other}]}
+ end;
+
+opened_gzip({call, {Owner, _Tag} = From}, [read, Size], #{ owner := Owner } = Data) ->
+ case read(Data, Size) of
+ {ok, NewData, Result} ->
+ Response = {ok, Result},
+ {keep_state, NewData, [{reply, From, Response}]};
+ Other ->
+ {keep_state_and_data, [{reply, From, Other}]}
+ end;
+
+opened_gzip({call, {Owner, _Tag} = From}, [read_line], #{ owner := Owner } = Data) ->
+ case read_line(Data) of
+ {ok, NewData, Result} ->
+ Response = {ok, Result},
+ {keep_state, NewData, [{reply, From, Response}]};
+ Other ->
+ {keep_state_and_data, [{reply, From, Other}]}
+ end;
+
+opened_gzip({call, {Owner, _Tag} = From}, [write, _IOData], #{ owner := Owner }) ->
+ Response = {error, ebadf},
+ {keep_state_and_data, [{reply, From, Response}]};
+
+opened_gzip({call, {Owner, _Tag} = From}, _Request, #{ owner := Owner }) ->
+ Response = {error, enotsup},
+ {keep_state_and_data, [{reply, From, Response}]};
+
+opened_gzip({call, _From}, _Request, _Data) ->
+ %% The client functions filter this out, so we'll crash if the user does
+ %% anything stupid on purpose.
+ {shutdown, protocol_violation};
+
+opened_gzip(_Event, _Request, _Data) ->
+ keep_state_and_data.
+
+%%
+
+read(#{ buffer := Buffer } = Data, Size) ->
+ try read_1(Data, Buffer, prim_buffer:size(Buffer), Size) of
+ Result -> Result
+ catch
+ error:badarg -> {error, badarg};
+ error:_ -> {error, eio}
+ end.
+read_1(Data, Buffer, BufferSize, ReadSize) when BufferSize >= ReadSize ->
+ #{ position := Position } = Data,
+ Decompressed = prim_buffer:read(Buffer, ReadSize),
+ {ok, Data#{ position => (Position + ReadSize) }, Decompressed};
+read_1(Data, Buffer, BufferSize, ReadSize) when BufferSize < ReadSize ->
+ #{ handle := PrivateFd } = Data,
+ case ?CALL_FD(PrivateFd, read, [?INFLATE_CHUNK_SIZE]) of
+ {ok, Compressed} ->
+ #{ zlib := Z } = Data,
+ Uncompressed = erlang:iolist_to_iovec(zlib:inflate(Z, Compressed)),
+ prim_buffer:write(Buffer, Uncompressed),
+ read_1(Data, Buffer, prim_buffer:size(Buffer), ReadSize);
+ eof when BufferSize > 0 ->
+ read_1(Data, Buffer, BufferSize, BufferSize);
+ Other ->
+ Other
+ end.
+
+read_line(#{ buffer := Buffer } = Data) ->
+ try read_line_1(Data, Buffer, prim_buffer:find_byte_index(Buffer, $\n)) of
+ {ok, NewData, Decompressed} -> {ok, NewData, Decompressed};
+ Other -> Other
+ catch
+ error:badarg -> {error, badarg};
+ error:_ -> {error, eio}
+ end.
+
+read_line_1(Data, Buffer, not_found) ->
+ #{ handle := PrivateFd, zlib := Z } = Data,
+ case ?CALL_FD(PrivateFd, read, [?INFLATE_CHUNK_SIZE]) of
+ {ok, Compressed} ->
+ Uncompressed = erlang:iolist_to_iovec(zlib:inflate(Z, Compressed)),
+ prim_buffer:write(Buffer, Uncompressed),
+ read_line_1(Data, Buffer, prim_buffer:find_byte_index(Buffer, $\n));
+ eof ->
+ case prim_buffer:size(Buffer) of
+ Size when Size > 0 -> {ok, prim_buffer:read(Buffer, Size)};
+ Size when Size =:= 0 -> eof
+ end;
+ Error ->
+ Error
+ end;
+read_line_1(Data, Buffer, {ok, LFIndex}) ->
+ %% Translate CRLF into just LF, completely ignoring which encoding is used,
+ %% but treat the file position as including CR.
+ #{ position := Position } = Data,
+ NewData = Data#{ position => (Position + LFIndex + 1) },
+ CRIndex = (LFIndex - 1),
+ TranslatedLine =
+ case prim_buffer:read(Buffer, LFIndex + 1) of
+ <<Line:CRIndex/binary, "\r\n">> -> <<Line/binary, "\n">>;
+ Line -> Line
+ end,
+ {ok, NewData, TranslatedLine}.
+
+%%
+%% We support seeking in both directions as long as it isn't relative to EOF.
+%%
+%% Seeking backwards is extremely inefficient since we have to seek to the very
+%% beginning and then decompress up to the desired point.
+%%
+
+position(Data, Mark) when is_atom(Mark) ->
+ position(Data, {Mark, 0});
+position(Data, Offset) when is_integer(Offset) ->
+ position(Data, {bof, Offset});
+position(Data, {bof, Offset}) when is_integer(Offset) ->
+ position_1(Data, Offset);
+position(Data, {cur, Offset}) when is_integer(Offset) ->
+ #{ position := Position } = Data,
+ position_1(Data, Position + Offset);
+position(_Data, {eof, Offset}) when is_integer(Offset) ->
+ {error, einval};
+position(_Data, _Other) ->
+ {error, badarg}.
+
+position_1(_Data, Desired) when Desired < 0 ->
+ {error, einval};
+position_1(#{ position := Desired } = Data, Desired) ->
+ {ok, Data, Desired};
+position_1(#{ position := Current } = Data, Desired) when Current < Desired ->
+ case read(Data, min(Desired - Current, ?INFLATE_CHUNK_SIZE)) of
+ {ok, NewData, _Data} -> position_1(NewData, Desired);
+ eof -> {ok, Data, Current};
+ Other -> Other
+ end;
+position_1(#{ position := Current } = Data, Desired) when Current > Desired ->
+ #{ handle := PrivateFd, buffer := Buffer, zlib := Z } = Data,
+ case ?CALL_FD(PrivateFd, position, [bof]) of
+ {ok, 0} ->
+ ok = zlib:inflateReset(Z),
+ prim_buffer:wipe(Buffer),
+ position_1(Data#{ position => 0 }, Desired);
+ Other ->
+ Other
+ end.
+
+terminate(_Reason, _State, _Data) ->
+ ok.
diff --git a/lib/kernel/src/raw_file_io_list.erl b/lib/kernel/src/raw_file_io_list.erl
new file mode 100644
index 0000000000..2e16e63f0e
--- /dev/null
+++ b/lib/kernel/src/raw_file_io_list.erl
@@ -0,0 +1,128 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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(raw_file_io_list).
+
+-export([close/1, sync/1, datasync/1, truncate/1, advise/4, allocate/3,
+ position/2, write/2, pwrite/2, pwrite/3,
+ read_line/1, read/2, pread/2, pread/3]).
+
+%% OTP internal.
+-export([ipread_s32bu_p32bu/3, sendfile/8]).
+
+-export([open_layer/3]).
+
+-include("file_int.hrl").
+
+open_layer(Filename, Modes, [list]) ->
+ case raw_file_io:open(Filename, [binary | Modes]) of
+ {ok, PrivateFd} -> {ok, make_public_fd(PrivateFd, Modes)};
+ Other -> Other
+ end.
+
+%% We can skip wrapping the file if it's write-only since only read operations
+%% are affected by list mode. Since raw_file_io fills in all implicit options
+%% for us, all we need to do is check whether 'read' is among them.
+make_public_fd(PrivateFd, Modes) ->
+ case lists:member(read, Modes) of
+ true -> #file_descriptor{ module = ?MODULE, data = PrivateFd };
+ false -> PrivateFd
+ end.
+
+close(Fd) ->
+ PrivateFd = Fd#file_descriptor.data,
+ ?CALL_FD(PrivateFd, close, []).
+
+sync(Fd) ->
+ PrivateFd = Fd#file_descriptor.data,
+ ?CALL_FD(PrivateFd, sync, []).
+datasync(Fd) ->
+ PrivateFd = Fd#file_descriptor.data,
+ ?CALL_FD(PrivateFd, datasync, []).
+
+truncate(Fd) ->
+ PrivateFd = Fd#file_descriptor.data,
+ ?CALL_FD(PrivateFd, truncate, []).
+
+advise(Fd, Offset, Length, Advise) ->
+ PrivateFd = Fd#file_descriptor.data,
+ ?CALL_FD(PrivateFd, advise, [Offset, Length, Advise]).
+allocate(Fd, Offset, Length) ->
+ PrivateFd = Fd#file_descriptor.data,
+ ?CALL_FD(PrivateFd, allocate, [Offset, Length]).
+
+position(Fd, Mark) ->
+ PrivateFd = Fd#file_descriptor.data,
+ ?CALL_FD(PrivateFd, position, [Mark]).
+
+write(Fd, IOData) ->
+ PrivateFd = Fd#file_descriptor.data,
+ ?CALL_FD(PrivateFd, write, [IOData]).
+
+pwrite(Fd, Offset, IOData) ->
+ PrivateFd = Fd#file_descriptor.data,
+ ?CALL_FD(PrivateFd, pwrite, [Offset, IOData]).
+pwrite(Fd, LocBytes) ->
+ PrivateFd = Fd#file_descriptor.data,
+ ?CALL_FD(PrivateFd, pwrite, [LocBytes]).
+
+read_line(Fd) ->
+ PrivateFd = Fd#file_descriptor.data,
+ case ?CALL_FD(PrivateFd, read_line, []) of
+ {ok, Binary} -> {ok, binary_to_list(Binary)};
+ Other -> Other
+ end.
+read(Fd, Size) ->
+ PrivateFd = Fd#file_descriptor.data,
+ case ?CALL_FD(PrivateFd, read, [Size]) of
+ {ok, Binary} -> {ok, binary_to_list(Binary)};
+ Other -> Other
+ end.
+pread(Fd, Offset, Size) ->
+ PrivateFd = Fd#file_descriptor.data,
+ case ?CALL_FD(PrivateFd, pread, [Offset, Size]) of
+ {ok, Binary} -> {ok, binary_to_list(Binary)};
+ Other -> Other
+ end.
+pread(Fd, LocNums) ->
+ PrivateFd = Fd#file_descriptor.data,
+ case ?CALL_FD(PrivateFd, pread, [LocNums]) of
+ {ok, LocResults} ->
+ TranslatedResults =
+ [ case Result of
+ Result when is_binary(Result) -> binary_to_list(Result);
+ eof -> eof
+ end || Result <- LocResults ],
+ {ok, TranslatedResults};
+ Other -> Other
+ end.
+
+ipread_s32bu_p32bu(Fd, Offset, MaxSize) ->
+ PrivateFd = Fd#file_descriptor.data,
+ case ?CALL_FD(PrivateFd, ipread_s32bu_p32bu, [Offset, MaxSize]) of
+ {ok, {Size, Pointer, Binary}} when is_binary(Binary) ->
+ {ok, {Size, Pointer, binary_to_list(Binary)}};
+ Other ->
+ Other
+ end.
+
+sendfile(Fd, Dest, Offset, Bytes, ChunkSize, Headers, Trailers, Flags) ->
+ Args = [Dest, Offset, Bytes, ChunkSize, Headers, Trailers, Flags],
+ PrivateFd = Fd#file_descriptor.data,
+ ?CALL_FD(PrivateFd, sendfile, Args).
diff --git a/lib/kernel/src/raw_file_io_raw.erl b/lib/kernel/src/raw_file_io_raw.erl
new file mode 100644
index 0000000000..9a9fe78eb1
--- /dev/null
+++ b/lib/kernel/src/raw_file_io_raw.erl
@@ -0,0 +1,25 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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(raw_file_io_raw).
+
+-export([open_layer/3]).
+
+open_layer(Filename, Modes, [raw]) ->
+ prim_file:open(Filename, [raw | Modes]).
diff --git a/lib/kernel/test/disk_log_SUITE.erl b/lib/kernel/test/disk_log_SUITE.erl
index fe2fc778f2..7beaadb1d6 100644
--- a/lib/kernel/test/disk_log_SUITE.erl
+++ b/lib/kernel/test/disk_log_SUITE.erl
@@ -89,8 +89,6 @@
dist_terminate/1, dist_accessible/1, dist_deadlock/1,
dist_open2/1, other_groups/1,
- evil/1,
-
otp_6278/1, otp_10131/1]).
-export([head_fun/1, hf/0, lserv/1,
@@ -123,7 +121,7 @@
[halt_int, wrap_int, halt_ext, wrap_ext, read_mode, head,
notif, new_idx_vsn, reopen, block, unblock, open, close,
error, chunk, truncate, many_users, info, change_size,
- change_attribute, distribution, evil, otp_6278, otp_10131]).
+ change_attribute, distribution, otp_6278, otp_10131]).
%% These test cases should be skipped if the VxWorks card is
%% configured without NFS cache.
@@ -149,7 +147,7 @@ all() ->
{group, open}, {group, close}, {group, error}, chunk,
truncate, many_users, {group, info},
{group, change_size}, change_attribute,
- {group, distribution}, evil, otp_6278, otp_10131].
+ {group, distribution}, otp_6278, otp_10131].
groups() ->
[{halt_int, [], [halt_int_inf, {group, halt_int_sz}]},
@@ -4676,119 +4674,6 @@ other_groups(Conf) when is_list(Conf) ->
ok.
--define(MAX, ?MAX_FWRITE_CACHE). % as in disk_log_1.erl
-%% Evil cases such as closed file descriptor port.
-evil(Conf) when is_list(Conf) ->
- Dir = ?privdir(Conf),
- File = filename:join(Dir, "n.LOG"),
- Log = n,
-
- %% Not a very thorough test.
-
- ok = setup_evil_filled_cache_wrap(Log, Dir),
- {error, {file_error,_,einval}} = disk_log:log(Log, apa),
- ok = disk_log:close(Log),
-
- ok = setup_evil_filled_cache_halt(Log, Dir),
- {error, {file_error,_,einval}} = disk_log:truncate(Log, apa),
- ok = stop_evil(Log),
-
- %% White box test.
- file:delete(File),
- Ports0 = erlang:ports(),
- {ok, Log} = disk_log:open([{name,Log},{file,File},{type,halt},
- {size,?MAX+50},{format,external}]),
- [Fd] = erlang:ports() -- Ports0,
- {B,_} = x_mk_bytes(30),
- ok = disk_log:blog(Log, <<0:(?MAX-1)/unit:8>>),
- exit(Fd, kill),
- {error, {file_error,_,einval}} = disk_log:blog_terms(Log, [B,B]),
- ok= disk_log:close(Log),
- file:delete(File),
-
- ok = setup_evil_wrap(Log, Dir),
- {error, {file_error,_,einval}} = disk_log:close(Log),
-
- ok = setup_evil_wrap(Log, Dir),
- {error, {file_error,_,einval}} = disk_log:log(Log, apa),
- ok = stop_evil(Log),
-
- ok = setup_evil_halt(Log, Dir),
- {error, {file_error,_,einval}} = disk_log:log(Log, apa),
- ok = stop_evil(Log),
-
- ok = setup_evil_wrap(Log, Dir),
- {error, {file_error,_,einval}} = disk_log:reopen(Log, apa),
- {error, {file_error,_,einval}} = disk_log:reopen(Log, apa),
- ok = stop_evil(Log),
-
- ok = setup_evil_wrap(Log, Dir),
- {error, {file_error,_,einval}} = disk_log:reopen(Log, apa),
- ok = stop_evil(Log),
-
- ok = setup_evil_wrap(Log, Dir),
- {error, {file_error,_,einval}} = disk_log:inc_wrap_file(Log),
- ok = stop_evil(Log),
-
- ok = setup_evil_wrap(Log, Dir),
- {error, {file_error,_,einval}} = disk_log:chunk(Log, start),
- ok = stop_evil(Log),
-
- ok = setup_evil_wrap(Log, Dir),
- {error, {file_error,_,einval}} = disk_log:truncate(Log),
- ok = stop_evil(Log),
-
- ok = setup_evil_wrap(Log, Dir),
- {error, {file_error,_,einval}} = disk_log:chunk_step(Log, start, 1),
- ok = stop_evil(Log),
-
- io:format("messages: ~p~n", [erlang:process_info(self(), messages)]),
- del(File, 2),
- file:delete(File),
- ok.
-
-setup_evil_wrap(Log, Dir) ->
- setup_evil(Log, [{type,wrap},{size,{100,2}}], Dir).
-
-setup_evil_halt(Log, Dir) ->
- setup_evil(Log, [{type,halt},{size,10000}], Dir).
-
-setup_evil(Log, Args, Dir) ->
- File = filename:join(Dir, lists:concat([Log, ".LOG"])),
- file:delete(File),
- del(File, 2),
- ok = disk_log:start(),
- Ports0 = erlang:ports(),
- {ok, Log} = disk_log:open([{name,Log},{file,File} | Args]),
- [Fd] = erlang:ports() -- Ports0,
- exit(Fd, kill),
- ok = disk_log:log_terms(n, [<<0:10/unit:8>>]),
- timer:sleep(2500), % TIMEOUT in disk_log_1.erl is 2000
- ok.
-
-stop_evil(Log) ->
- {error, _} = disk_log:close(Log),
- ok.
-
-setup_evil_filled_cache_wrap(Log, Dir) ->
- setup_evil_filled_cache(Log, [{type,wrap},{size,{?MAX,2}}], Dir).
-
-setup_evil_filled_cache_halt(Log, Dir) ->
- setup_evil_filled_cache(Log, [{type,halt},{size,infinity}], Dir).
-
-%% The cache is filled, and the file descriptor port gone.
-setup_evil_filled_cache(Log, Args, Dir) ->
- File = filename:join(Dir, lists:concat([Log, ".LOG"])),
- file:delete(File),
- del(File, 2),
- ok = disk_log:start(),
- Ports0 = erlang:ports(),
- {ok, Log} = disk_log:open([{name,Log},{file,File} | Args]),
- [Fd] = erlang:ports() -- Ports0,
- ok = disk_log:log_terms(n, [<<0:?MAX/unit:8>>]),
- exit(Fd, kill),
- ok.
-
%% OTP-6278. open/1 creates no status or crash report.
otp_6278(Conf) when is_list(Conf) ->
Dir = ?privdir(Conf),
diff --git a/lib/kernel/test/erl_prim_loader_SUITE.erl b/lib/kernel/test/erl_prim_loader_SUITE.erl
index b6417210b9..3502a4ad08 100644
--- a/lib/kernel/test/erl_prim_loader_SUITE.erl
+++ b/lib/kernel/test/erl_prim_loader_SUITE.erl
@@ -33,6 +33,7 @@
primary_archive/1, virtual_dir_in_archive/1,
get_modules/1]).
+-define(PRIM_FILE, prim_file).
%%-----------------------------------------------------------------
%% Test suite for erl_prim_loader. (Most code is run during system start/stop.)
@@ -461,7 +462,7 @@ primary_archive(Config) when is_list(Config) ->
%% Set primary archive
ExpectedEbins = [Archive, DictDir ++ "/ebin", DummyDir ++ "/ebin"],
io:format("ExpectedEbins: ~p\n", [ExpectedEbins]),
- {ok, FileInfo} = prim_file:read_file_info(Archive),
+ {ok, FileInfo} = ?PRIM_FILE:read_file_info(Archive),
{ok, Ebins} = rpc:call(Node, erl_prim_loader, set_primary_archive,
[Archive, ArchiveBin, FileInfo,
fun escript:parse_file/1]),
diff --git a/lib/kernel/test/file_SUITE.erl b/lib/kernel/test/file_SUITE.erl
index 119e1f24bb..0cb8087a76 100644
--- a/lib/kernel/test/file_SUITE.erl
+++ b/lib/kernel/test/file_SUITE.erl
@@ -39,6 +39,8 @@
-define(FILE_FIN_PER_TESTCASE(Config), Config).
-endif.
+-define(PRIM_FILE, prim_file).
+
-module(?FILE_SUITE).
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
@@ -97,6 +99,12 @@
-export([unicode_mode/1]).
+-export([volume_relative_paths/1]).
+
+-export([tiny_writes/1, tiny_writes_delayed/1,
+ large_writes/1, large_writes_delayed/1,
+ tiny_reads/1, tiny_reads_ahead/1]).
+
%% Debug exports
-export([create_file_slow/2, create_file/2, create_bin/2]).
-export([verify_file/2, verify_bin/3]).
@@ -107,6 +115,8 @@
-export([disc_free/1, memsize/0]).
-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+
-include_lib("kernel/include/file.hrl").
-define(THROW_ERROR(RES), throw({fail, ?LINE, RES})).
@@ -118,13 +128,13 @@ suite() ->
all() ->
[unicode, altname, read_write_file, {group, dirs},
- {group, files}, delete, rename, names, {group, errors},
- {group, compression}, {group, links}, copy,
+ {group, files}, delete, rename, names, volume_relative_paths,
+ {group, errors}, {group, compression}, {group, links}, copy,
delayed_write, read_ahead, segment_read, segment_write,
ipread, pid2name, interleaved_read_write, otp_5814, otp_10852,
large_file, large_write, read_line_1, read_line_2, read_line_3,
read_line_4, standard_io, old_io_protocol,
- unicode_mode
+ unicode_mode, {group, bench}
].
groups() ->
@@ -154,11 +164,19 @@ groups() ->
write_compressed, compress_errors, catenated_gzips,
compress_async_crash]},
{links, [],
- [make_link, read_link_info_for_non_link, symlinks]}].
+ [make_link, read_link_info_for_non_link, symlinks]},
+ {bench, [],
+ [tiny_writes, tiny_writes_delayed,
+ large_writes, large_writes_delayed,
+ tiny_reads, tiny_reads_ahead]}].
init_per_group(_GroupName, Config) ->
Config.
+end_per_group(bench, Config) ->
+ ScratchDir = proplists:get_value(priv_dir, Config),
+ file:delete(filename:join(ScratchDir, "benchmark_scratch_file")),
+ Config;
end_per_group(_GroupName, Config) ->
Config.
@@ -473,7 +491,7 @@ um_check_unicode(_Utf8Bin, {ok, _ListOrBin}, _, _UTF8_) ->
um_filename(Bin, Dir, Options) when is_binary(Bin) ->
um_filename(binary_to_list(Bin), Dir, Options);
um_filename(Str = [_|_], Dir, Options) ->
- Name = hd(string:tokens(Str, ":")),
+ Name = hd(string:lexemes(Str, ":")),
Enc = atom_to_list(proplists:get_value(encoding, Options, latin1)),
File = case lists:member(binary, Options) of
true ->
@@ -638,6 +656,10 @@ cur_dir_0(Config) when is_list(Config) ->
{ok,NewDirFiles} = ?FILE_MODULE:list_dir("."),
true = lists:member(UncommonName,NewDirFiles),
+ %% Ensure that we get the same result with a trailing slash; the
+ %% APIs used on Windows will choke on them if passed directly.
+ {ok,NewDirFiles} = ?FILE_MODULE:list_dir("./"),
+
%% Delete the directory and return to the old current directory
%% and check that the created file isn't there (too!)
expect({error, einval}, {error, eacces},
@@ -690,10 +712,15 @@ win_cur_dir_1(_Config) ->
%% Get the drive letter from the current directory,
%% and try to get current directory for that drive.
- [Drive,$:|_] = BaseDir,
- {ok,BaseDir} = ?FILE_MODULE:get_cwd([Drive,$:]),
+ [CurDrive,$:|_] = BaseDir,
+ {ok,BaseDir} = ?FILE_MODULE:get_cwd([CurDrive,$:]),
io:format("BaseDir = ~s\n", [BaseDir]),
+ %% We should error out on non-existent drives. Any reasonable system will
+ %% have at least one.
+ CurDirs = [?FILE_MODULE:get_cwd([Drive,$:]) || Drive <- lists:seq($A, $Z)],
+ lists:member({error,eaccess}, CurDirs),
+
%% Unfortunately, there is no way to move away from the
%% current drive as we can't use the "subst" command from
%% a SSH connection. We can't test any more.
@@ -831,7 +858,7 @@ no_untranslatable_names() ->
end.
start_node(Name, Args) ->
- [_,Host] = string:tokens(atom_to_list(node()), "@"),
+ [_,Host] = string:lexemes(atom_to_list(node()), "@"),
ct:log("Trying to start ~w@~s~n", [Name,Host]),
case test_server:start_node(Name, peer, [{args,Args}]) of
{error,Reason} ->
@@ -1019,6 +1046,23 @@ close(Config) when is_list(Config) ->
Val = ?FILE_MODULE:close(Fd1),
io:format("Second close gave: ~p",[Val]),
+ %% All operations on a closed raw file should EINVAL, even if they're not
+ %% supported on the current platform.
+ {ok,Fd2} = ?FILE_MODULE:open(Name, [read, write, raw]),
+ ok = ?FILE_MODULE:close(Fd2),
+
+ {error, einval} = ?FILE_MODULE:advise(Fd2, 5, 5, normal),
+ {error, einval} = ?FILE_MODULE:allocate(Fd2, 5, 5),
+ {error, einval} = ?FILE_MODULE:close(Fd2),
+ {error, einval} = ?FILE_MODULE:datasync(Fd2),
+ {error, einval} = ?FILE_MODULE:position(Fd2, 5),
+ {error, einval} = ?FILE_MODULE:pread(Fd2, 5, 1),
+ {error, einval} = ?FILE_MODULE:pwrite(Fd2, 5, "einval please"),
+ {error, einval} = ?FILE_MODULE:read(Fd2, 1),
+ {error, einval} = ?FILE_MODULE:sync(Fd2),
+ {error, einval} = ?FILE_MODULE:truncate(Fd2),
+ {error, einval} = ?FILE_MODULE:write(Fd2, "einval please"),
+
[] = flush(),
ok.
@@ -1132,8 +1176,8 @@ pread_write_test(File, Data) ->
end,
I = Size + 17,
ok = ?FILE_MODULE:pwrite(File, 0, Data),
- Res = ?FILE_MODULE:pread(File, 0, I),
- {ok, Data} = Res,
+ {ok, Data} = ?FILE_MODULE:pread(File, 0, I),
+ {ok, [Data]} = ?FILE_MODULE:pread(File, [{0, I}]),
eof = ?FILE_MODULE:pread(File, I, 1),
ok = ?FILE_MODULE:pwrite(File, [{0, Data}, {I, Data}]),
{ok, [Data, eof, Data]} =
@@ -2044,13 +2088,22 @@ names(Config) when is_list(Config) ->
ok = ?FILE_MODULE:close(Fd2),
{ok,Fd3} = ?FILE_MODULE:open(Name3,read),
ok = ?FILE_MODULE:close(Fd3),
+
+ %% Now try the same on raw files.
+ {ok,Fd4} = ?FILE_MODULE:open(Name2, [read, raw]),
+ ok = ?FILE_MODULE:close(Fd4),
+ {ok,Fd4f} = ?FILE_MODULE:open(lists:flatten(Name2), [read, raw]),
+ ok = ?FILE_MODULE:close(Fd4f),
+ {ok,Fd5} = ?FILE_MODULE:open(Name3, [read, raw]),
+ ok = ?FILE_MODULE:close(Fd5),
+
case length(Name1) > 255 of
true ->
io:format("Path too long for an atom:\n\n~p\n", [Name1]);
false ->
Name4 = list_to_atom(Name1),
- {ok,Fd4} = ?FILE_MODULE:open(Name4,read),
- ok = ?FILE_MODULE:close(Fd4)
+ {ok,Fd6} = ?FILE_MODULE:open(Name4,read),
+ ok = ?FILE_MODULE:close(Fd6)
end,
%% Try some path names
@@ -2074,6 +2127,22 @@ names(Config) when is_list(Config) ->
[] = flush(),
ok.
+volume_relative_paths(Config) when is_list(Config) ->
+ case os:type() of
+ {win32, _} ->
+ {ok, [Drive, $: | _]} = file:get_cwd(),
+ %% Relative to current device root.
+ {ok, RootInfo} = file:read_file_info([Drive, $:, $/]),
+ {ok, RootInfo} = file:read_file_info("/"),
+ %% Relative to current device directory.
+ {ok, DirContents} = file:list_dir([Drive, $:]),
+ {ok, DirContents} = file:list_dir("."),
+ [] = flush(),
+ ok;
+ _ ->
+ {skip, "This test is Windows-specific."}
+ end.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -2641,8 +2710,8 @@ altname(Config) when is_list(Config) ->
{skipped, "Altname not supported on this platform"};
{ok, "LONGAL~1"} ->
{ok, "A_FILE~1"} = ?FILE_MODULE:altname(Name),
- {ok, "C:/"} = ?FILE_MODULE:altname("C:/"),
- {ok, "C:\\"} = ?FILE_MODULE:altname("C:\\"),
+ {ok, "c:/"} = ?FILE_MODULE:altname("C:/"),
+ {ok, "c:/"} = ?FILE_MODULE:altname("C:\\"),
{error,enoent} = ?FILE_MODULE:altname(NonexName),
{ok, "short"} = ?FILE_MODULE:altname(ShortName),
ok
@@ -2923,20 +2992,22 @@ delayed_write(Config) when is_list(Config) ->
%%
%% Test caching and normal close of non-raw file
{ok, Fd1} =
- ?FILE_MODULE:open(File, [write, {delayed_write, Size+1, 2000}]),
+ ?FILE_MODULE:open(File, [write, {delayed_write, Size+1, 400}]),
ok = ?FILE_MODULE:write(Fd1, Data1),
- timer:sleep(1000), % Just in case the file system is slow
+ %% Wait for a reasonable amount of time to check whether the write was
+ %% practically instantaneous or actually delayed.
+ timer:sleep(100),
{ok, Fd2} = ?FILE_MODULE:open(File, [read]),
eof = ?FILE_MODULE:read(Fd2, 1),
ok = ?FILE_MODULE:write(Fd1, Data1), % Data flush on size
- timer:sleep(1000), % Just in case the file system is slow
+ timer:sleep(100),
{ok, Data1Data1} = ?FILE_MODULE:pread(Fd2, bof, 2*Size+1),
ok = ?FILE_MODULE:write(Fd1, Data1),
- timer:sleep(3000), % Wait until data flush on timeout
+ timer:sleep(500), % Wait until data flush on timeout
{ok, Data1Data1Data1} = ?FILE_MODULE:pread(Fd2, bof, 3*Size+1),
ok = ?FILE_MODULE:write(Fd1, Data1),
ok = ?FILE_MODULE:close(Fd1), % Data flush on close
- timer:sleep(1000), % Just in case the file system is slow
+ timer:sleep(100),
{ok, Data1Data1Data1Data1} = ?FILE_MODULE:pread(Fd2, bof, 4*Size+1),
ok = ?FILE_MODULE:close(Fd2),
%%
@@ -2970,7 +3041,7 @@ delayed_write(Config) when is_list(Config) ->
{'DOWN', Mref1, _, _, _} = Down1a ->
ct:fail(Down1a)
end,
- timer:sleep(1000), % Just in case the file system is slow
+ timer:sleep(100), % Just in case the file system is slow
{ok, Fd3} = ?FILE_MODULE:open(File, [read]),
eof = ?FILE_MODULE:read(Fd3, 1),
Child1 ! {Parent, continue, normal},
@@ -2980,7 +3051,7 @@ delayed_write(Config) when is_list(Config) ->
{'DOWN', Mref1, _, _, _} = Down1b ->
ct:fail(Down1b)
end,
- timer:sleep(1000), % Just in case the file system is slow
+ timer:sleep(100), % Just in case the file system is slow
{ok, Data1} = ?FILE_MODULE:pread(Fd3, bof, Size+1),
ok = ?FILE_MODULE:close(Fd3),
%%
@@ -2993,7 +3064,7 @@ delayed_write(Config) when is_list(Config) ->
{'DOWN', Mref2, _, _, _} = Down2a ->
ct:fail(Down2a)
end,
- timer:sleep(1000), % Just in case the file system is slow
+ timer:sleep(100), % Just in case the file system is slow
{ok, Fd4} = ?FILE_MODULE:open(File, [read]),
eof = ?FILE_MODULE:read(Fd4, 1),
Child2 ! {Parent, continue, kill},
@@ -3003,7 +3074,7 @@ delayed_write(Config) when is_list(Config) ->
{'DOWN', Mref2, _, _, _} = Down2b ->
ct:fail(Down2b)
end,
- timer:sleep(1000), % Just in case the file system is slow
+ timer:sleep(100), % Just in case the file system is slow
eof = ?FILE_MODULE:pread(Fd4, bof, 1),
ok = ?FILE_MODULE:close(Fd4),
%%
@@ -3095,6 +3166,16 @@ read_ahead(Config) when is_list(Config) ->
Data1Data2Data3 = Data1++Data2++Data3,
{ok, Data1Data2Data3} = ?FILE_MODULE:read(Fd5, 3*Size+1),
ok = ?FILE_MODULE:close(Fd5),
+
+ %% Ensure that a read that draws from both the buffer and the file won't
+ %% return anything wonky.
+ SplitData = << <<(I rem 256)>> || I <- lists:seq(1, 1024) >>,
+ file:write_file(File, SplitData),
+ {ok, Fd6} = ?FILE_MODULE:open(File, [raw, read, binary, {read_ahead, 256}]),
+ {ok, <<1>>} = file:read(Fd6, 1),
+ <<1, Shifted:512/binary, _Rest/binary>> = SplitData,
+ {ok, Shifted} = file:read(Fd6, 512),
+
%%
[] = flush(),
ok.
@@ -3699,6 +3780,83 @@ do_large_write(Name) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Benchmarks
+%%
+%% Note that we only measure the time it takes to run the isolated file
+%% operations and that the actual test runtime can differ significantly,
+%% especially on the write side as the files need to be truncated before
+%% writing.
+
+large_writes(Config) when is_list(Config) ->
+ Modes = [raw, binary],
+ OpCount = 4096,
+ Data = <<0:(64 bsl 10)/unit:8>>,
+ run_write_benchmark(Config, Modes, OpCount, Data).
+
+large_writes_delayed(Config) when is_list(Config) ->
+ %% Each write is exactly as large as the delay buffer, causing the writes
+ %% to pass through each time, giving us a decent idea of how much overhead
+ %% delayed_write adds.
+ Modes = [raw, binary, {delayed_write, 64 bsl 10, 2000}],
+ OpCount = 4096,
+ Data = <<0:(64 bsl 10)/unit:8>>,
+ run_write_benchmark(Config, Modes, OpCount, Data).
+
+tiny_writes(Config) when is_list(Config) ->
+ Modes = [raw, binary],
+ OpCount = 512 bsl 10,
+ Data = <<0>>,
+ run_write_benchmark(Config, Modes, OpCount, Data).
+
+tiny_writes_delayed(Config) when is_list(Config) ->
+ Modes = [raw, binary, {delayed_write, 512 bsl 10, 2000}],
+ OpCount = 512 bsl 10,
+ Data = <<0>>,
+ run_write_benchmark(Config, Modes, OpCount, Data).
+
+%% The read benchmarks assume that "benchmark_scratch_file" has been filled by
+%% the write benchmarks.
+
+tiny_reads(Config) when is_list(Config) ->
+ Modes = [raw, binary],
+ OpCount = 512 bsl 10,
+ run_read_benchmark(Config, Modes, OpCount, 1).
+
+tiny_reads_ahead(Config) when is_list(Config) ->
+ Modes = [raw, binary, {read_ahead, 512 bsl 10}],
+ OpCount = 512 bsl 10,
+ run_read_benchmark(Config, Modes, OpCount, 1).
+
+run_write_benchmark(Config, Modes, OpCount, Data) ->
+ run_benchmark(Config, [write | Modes], OpCount, fun file:write/2, Data).
+
+run_read_benchmark(Config, Modes, OpCount, OpSize) ->
+ run_benchmark(Config, [read | Modes], OpCount, fun file:read/2, OpSize).
+
+run_benchmark(Config, Modes, OpCount, Fun, Arg) ->
+ ScratchDir = proplists:get_value(priv_dir, Config),
+ Path = filename:join(ScratchDir, "benchmark_scratch_file"),
+ {ok, Fd} = file:open(Path, Modes),
+ submit_throughput_results(Fun, [Fd, Arg], OpCount).
+
+submit_throughput_results(Fun, Args, Times) ->
+ MSecs = measure_repeated_file_op(Fun, Args, Times, millisecond),
+ IOPS = trunc(Times * (1000 / MSecs)),
+ ct_event:notify(#event{ name = benchmark_data, data = [{value,IOPS}] }),
+ {comment, io_lib:format("~p IOPS, ~p ms", [IOPS, trunc(MSecs)])}.
+
+measure_repeated_file_op(Fun, Args, Times, Unit) ->
+ Start = os:perf_counter(Unit),
+ repeated_apply(Fun, Args, Times),
+ os:perf_counter(Unit) - Start.
+
+repeated_apply(_F, _Args, Times) when Times =< 0 ->
+ ok;
+repeated_apply(F, Args, Times) ->
+ erlang:apply(F, Args),
+ repeated_apply(F, Args, Times - 1).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
response_analysis(Module, Function, Arguments) ->
@@ -3934,7 +4092,7 @@ read_line_create_files(TestData) ->
read_line_remove_files(TestData) ->
[ file:delete(File) || {_Function,File,_,_} <- TestData ].
-%% read_line with prim_file.
+%% read_line with ?PRIM_FILE.
read_line_1(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
All = read_line_testdata(PrivDir),
@@ -4103,9 +4261,9 @@ read_line_create7(Filename) ->
file:close(F).
read_line_all(Filename) ->
- {ok,F} = prim_file:open(Filename,[read,binary]),
+ {ok,F} = ?PRIM_FILE:open(Filename,[read,binary]),
X=read_rl_lines(F),
- prim_file:close(F),
+ ?PRIM_FILE:close(F),
Bin = list_to_binary([B || {ok,B} <- X]),
Bin = re:replace(list_to_binary([element(2,file:read_file(Filename))]),
"\r\n","\n",[global,{return,binary}]),
@@ -4138,7 +4296,7 @@ read_line_all4(Filename) ->
{length(X),Bin}.
read_rl_lines(F) ->
- case prim_file:read_line(F) of
+ case ?PRIM_FILE:read_line(F) of
eof ->
[];
{error,X} ->
@@ -4158,9 +4316,9 @@ read_rl_lines2(F) ->
end.
read_line_all_alternating(Filename) ->
- {ok,F} = prim_file:open(Filename,[read,binary]),
+ {ok,F} = ?PRIM_FILE:open(Filename,[read,binary]),
X=read_rl_lines(F,true),
- prim_file:close(F),
+ ?PRIM_FILE:close(F),
Bin = list_to_binary([B || {ok,B} <- X]),
Bin = re:replace(list_to_binary([element(2,file:read_file(Filename))]),
"\r\n","\n",[global,{return,binary}]),
@@ -4194,8 +4352,8 @@ read_line_all_alternating4(Filename) ->
read_rl_lines(F,Alternate) ->
case begin
case Alternate of
- true -> prim_file:read(F,1);
- false -> prim_file:read_line(F)
+ true -> ?PRIM_FILE:read(F,1);
+ false -> ?PRIM_FILE:read_line(F)
end
end of
eof ->
diff --git a/lib/kernel/test/file_name_SUITE.erl b/lib/kernel/test/file_name_SUITE.erl
index f23529fec9..3afc647081 100644
--- a/lib/kernel/test/file_name_SUITE.erl
+++ b/lib/kernel/test/file_name_SUITE.erl
@@ -77,6 +77,7 @@
init_per_testcase/2, end_per_testcase/2]).
-export([normal/1,icky/1,very_icky/1,normalize/1,home_dir/1]).
+-define(PRIM_FILE, prim_file).
init_per_testcase(_Func, Config) ->
Config.
@@ -131,7 +132,7 @@ home_dir(Config) when is_list(Config) ->
os:putenv("HOME",NewHome),
{"HOME",Save};
_ ->
- rm_rf(prim_file,NewHome),
+ rm_rf(?PRIM_FILE,NewHome),
throw(unsupported_os)
end,
try
@@ -145,7 +146,7 @@ home_dir(Config) when is_list(Config) ->
_ ->
os:putenv(SaveOldName,SaveOldValue)
end,
- rm_rf(prim_file,NewHome)
+ rm_rf(?PRIM_FILE,NewHome)
end
catch
throw:need_unicode_mode ->
@@ -190,7 +191,7 @@ normal(Config) when is_list(Config) ->
try
Priv = proplists:get_value(priv_dir, Config),
file:set_cwd(Priv),
- ok = check_normal(prim_file),
+ ok = check_normal(?PRIM_FILE),
ok = check_normal(file),
%% If all is good, delete dir again (avoid hanging dir on windows)
rm_rf(file,"normal_dir"),
@@ -210,7 +211,7 @@ icky(Config) when is_list(Config) ->
try
Priv = proplists:get_value(priv_dir, Config),
file:set_cwd(Priv),
- ok = check_icky(prim_file),
+ ok = check_icky(?PRIM_FILE),
ok = check_icky(file),
%% If all is good, delete dir again (avoid hanging dir on windows)
rm_rf(file,"icky_dir"),
@@ -229,7 +230,7 @@ very_icky(Config) when is_list(Config) ->
try
Priv = proplists:get_value(priv_dir, Config),
file:set_cwd(Priv),
- case check_very_icky(prim_file) of
+ case check_very_icky(?PRIM_FILE) of
need_unicode_mode ->
{skipped,"VM needs to be started in Unicode filename mode"};
ok ->
@@ -292,11 +293,6 @@ check_normal(Mod) ->
ok
end,
[ begin
- {ok, FD} = Mod:open(Name,[read]),
- {ok, Content} = Mod:read(FD,1024),
- ok = file:close(FD)
- end || {regular,Name,Content} <- NormalDir ],
- [ begin
{ok, FD} = Mod:open(Name,[read,binary]),
BC = list_to_binary(Content),
{ok, BC} = Mod:read(FD,1024),
@@ -412,11 +408,6 @@ check_icky(Mod) ->
ok
end,
[ begin
- {ok, FD} = Mod:open(Name,[read]),
- {ok, Content} = Mod:read(FD,1024),
- ok = file:close(FD)
- end || {regular,Name,Content} <- IckyDir ],
- [ begin
{ok, FD} = Mod:open(Name,[read,binary]),
BC = list_to_binary([Content]),
{ok, BC} = Mod:read(FD,1024),
@@ -521,11 +512,6 @@ check_very_icky(Mod) ->
ok
end,
[ begin
- {ok, FD} = Mod:open(Name,[read]),
- {ok, Content} = Mod:read(FD,1024),
- ok = file:close(FD)
- end || {regular,Name,Content} <- VeryIckyDir ],
- [ begin
{ok, FD} = Mod:open(Name,[read,binary]),
BC = list_to_binary([Content]),
{ok, BC} = Mod:read(FD,1024),
diff --git a/lib/kernel/test/kernel_bench.spec b/lib/kernel/test/kernel_bench.spec
index 8de60dae31..4de133f21b 100644
--- a/lib/kernel/test/kernel_bench.spec
+++ b/lib/kernel/test/kernel_bench.spec
@@ -1 +1,2 @@
{groups,"../kernel_test",zlib_SUITE,[bench]}.
+{groups,"../kernel_test",file_SUITE,[bench]}.
diff --git a/lib/kernel/test/prim_file_SUITE.erl b/lib/kernel/test/prim_file_SUITE.erl
index 2f4330c217..db753679ea 100644
--- a/lib/kernel/test/prim_file_SUITE.erl
+++ b/lib/kernel/test/prim_file_SUITE.erl
@@ -21,38 +21,23 @@
-export([all/0, suite/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,
read_write_file/1, free_memory/0]).
--export([cur_dir_0a/1, cur_dir_0b/1,
- cur_dir_1a/1, cur_dir_1b/1,
- make_del_dir_a/1, make_del_dir_b/1,
- pos1/1, pos2/1]).
--export([close/1,
- delete_a/1, delete_b/1]).
--export([ open1/1, modes/1]).
--export([
- file_info_basic_file_a/1, file_info_basic_file_b/1,
- file_info_basic_directory_a/1, file_info_basic_directory_b/1,
- file_info_bad_a/1, file_info_bad_b/1,
- file_info_times_a/1, file_info_times_b/1,
- file_write_file_info_a/1, file_write_file_info_b/1,
- file_read_file_info_opts/1, file_write_file_info_opts/1,
- file_write_read_file_info_opts/1
- ]).
--export([rename_a/1, rename_b/1,
- access/1, truncate/1, datasync/1, sync/1,
+-export([cur_dir_0/1, cur_dir_1/1,
+ make_del_dir/1, pos1/1, pos2/1]).
+-export([close/1, delete/1]).
+-export([open1/1, modes/1]).
+-export([file_info_basic_file/1, file_info_basic_directory/1, file_info_bad/1,
+ file_info_times/1, file_write_file_info/1,
+ file_read_file_info_opts/1, file_write_file_info_opts/1,
+ file_write_read_file_info_opts/1]).
+-export([rename/1, access/1, truncate/1, datasync/1, sync/1,
read_write/1, pread_write/1, append/1, exclusive/1]).
--export([ e_delete/1, e_rename/1, e_make_dir/1, e_del_dir/1]).
+-export([e_delete/1, e_rename/1, e_make_dir/1, e_del_dir/1]).
--export([ read_not_really_compressed/1,
- read_compressed/1, write_compressed/1,
- compress_errors/1]).
-
--export([
- make_link_a/1, make_link_b/1,
- read_link_info_for_non_link/1,
- symlinks_a/1, symlinks_b/1,
- list_dir_limit/1,
- list_dir_error/1,
- list_dir/1]).
+-export([make_link/1, read_link_info_for_non_link/1,
+ symlinks/1,
+ list_dir_limit/1,
+ list_dir_error/1,
+ list_dir/1]).
-export([advise/1]).
-export([large_write/1]).
@@ -67,29 +52,16 @@
-define(PRIM_FILE, prim_file).
-%% Calls ?PRIM_FILE:F with arguments A and an optional handle H
-%% as first argument, unless the handle is [], i.e no handle.
-%% This is a macro to give the compiler and thereby
-%% the cross reference tool the possibility to interprete
-%% the call, since M, F, A (or [H | A]) can all be known at
-%% compile time.
--define(PRIM_FILE_call(F, H, A),
- case H of
- [] -> apply(?PRIM_FILE, F, A);
- _ -> apply(?PRIM_FILE, F, [H | A])
- end).
-
suite() -> [].
all() ->
[read_write_file, {group, dirs}, {group, files},
- delete_a, delete_b, rename_a, rename_b, {group, errors},
- {group, compression}, {group, links}, list_dir_limit, list_dir].
+ delete, rename, {group, errors}, {group, links},
+ list_dir_limit, list_dir].
groups() ->
[{dirs, [],
- [make_del_dir_a, make_del_dir_b, cur_dir_0a, cur_dir_0b,
- cur_dir_1a, cur_dir_1b]},
+ [make_del_dir, cur_dir_0, cur_dir_1]},
{files, [],
[{group, open}, {group, pos}, {group, file_info},
truncate, sync, datasync, advise, large_write, allocate]},
@@ -98,22 +70,14 @@ groups() ->
append, exclusive]},
{pos, [], [pos1, pos2]},
{file_info, [],
- [file_info_basic_file_a, file_info_basic_file_b,
- file_info_basic_directory_a,
- file_info_basic_directory_b, file_info_bad_a,
- file_info_bad_b, file_info_times_a, file_info_times_b,
- file_write_file_info_a, file_write_file_info_b,
- file_read_file_info_opts, file_write_file_info_opts,
- file_write_read_file_info_opts
+ [file_info_basic_file,file_info_basic_directory, file_info_bad,
+ file_info_times, file_write_file_info, file_read_file_info_opts,
+ file_write_file_info_opts, file_write_read_file_info_opts
]},
{errors, [],
[e_delete, e_rename, e_make_dir, e_del_dir]},
- {compression, [],
- [read_compressed, read_not_really_compressed,
- write_compressed, compress_errors]},
{links, [],
- [make_link_a, make_link_b, read_link_info_for_non_link,
- symlinks_a, symlinks_b, list_dir_error]}].
+ [make_link, read_link_info_for_non_link, symlinks, list_dir_error]}].
init_per_testcase(large_write, Config) ->
{ok, Started} = application:ensure_all_started(os_mon),
@@ -246,39 +210,27 @@ read_write_file(Config) when is_list(Config) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-make_del_dir_a(Config) when is_list(Config) ->
- make_del_dir(Config, [], "_a").
-
-make_del_dir_b(Config) when is_list(Config) ->
- {ok, Handle} = ?PRIM_FILE:start(),
- Result = make_del_dir(Config, Handle, "_b"),
- ok = ?PRIM_FILE:stop(Handle),
- %% Just to make sure the state of the server makes a difference
- {error, einval} = ?PRIM_FILE_call(get_cwd, Handle, []),
- Result.
-
-make_del_dir(Config, Handle, Suffix) ->
+make_del_dir(Config) when is_list(Config) ->
RootDir = proplists:get_value(priv_dir,Config),
NewDir = filename:join(RootDir,
atom_to_list(?MODULE)
- ++"_mk-dir"++Suffix),
- ok = ?PRIM_FILE_call(make_dir, Handle, [NewDir]),
- {error, eexist} = ?PRIM_FILE_call(make_dir, Handle, [NewDir]),
- ok = ?PRIM_FILE_call(del_dir, Handle, [NewDir]),
- {error, enoent} = ?PRIM_FILE_call(del_dir, Handle, [NewDir]),
+ ++"_mk-dir"),
+ ok = ?PRIM_FILE:make_dir(NewDir),
+ {error, eexist} = ?PRIM_FILE:make_dir(NewDir),
+ ok = ?PRIM_FILE:del_dir(NewDir),
+ {error, enoent} = ?PRIM_FILE:del_dir(NewDir),
%% Make sure we are not in a directory directly under test_server
%% as that would result in eacces errors when trying to delete '..',
%% because there are processes having that directory as current.
- ok = ?PRIM_FILE_call(make_dir, Handle, [NewDir]),
- {ok, CurrentDir} = ?PRIM_FILE_call(get_cwd, Handle, []),
+ ok = ?PRIM_FILE:make_dir(NewDir),
+ {ok, CurrentDir} = ?PRIM_FILE:get_cwd(),
case {os:type(), length(NewDir) >= 260 } of
{{win32,_}, true} ->
io:format("Skip set_cwd for windows path longer than 260 (MAX_PATH)\n", []),
io:format("\nNewDir = ~p\n", [NewDir]);
_ ->
- ok = ?PRIM_FILE_call(set_cwd, Handle, [NewDir])
+ ok = ?PRIM_FILE:set_cwd(NewDir)
end,
try
%% Check that we get an error when trying to create...
@@ -286,14 +238,14 @@ make_del_dir(Config, Handle, Suffix) ->
NewDir2 = filename:join(RootDir,
atom_to_list(?MODULE)
++"_mk-dir-noexist/foo"),
- {error, enoent} = ?PRIM_FILE_call(make_dir, Handle, [NewDir2]),
+ {error, enoent} = ?PRIM_FILE:make_dir(NewDir2),
%% a nameless directory
- {error, enoent} = ?PRIM_FILE_call(make_dir, Handle, [""]),
+ {error, enoent} = ?PRIM_FILE:make_dir(""),
%% a directory with illegal name
- {error, badarg} = ?PRIM_FILE_call(make_dir, Handle, ['mk-dir']),
+ {error, badarg} = ?PRIM_FILE:make_dir('mk-dir'),
%% a directory with illegal name, even if it's a (bad) list
- {error, badarg} = ?PRIM_FILE_call(make_dir, Handle, [[1,2,3,{}]]),
+ {error, badarg} = ?PRIM_FILE:make_dir([1,2,3,{}]),
%% Maybe this isn't an error, exactly, but worth mentioning anyway:
%% ok = ?PRIM_FILE:make_dir([$f,$o,$o,0,$b,$a,$r])),
@@ -306,125 +258,101 @@ make_del_dir(Config, Handle, Suffix) ->
%% Try deleting some bad directories
%% Deleting the parent directory to the current, sounds dangerous, huh?
%% Don't worry ;-) the parent directory should never be empty, right?
- case ?PRIM_FILE_call(del_dir, Handle, [".."]) of
+ case ?PRIM_FILE:del_dir("..") of
{error, eexist} -> ok;
{error, eacces} -> ok; %OpenBSD
{error, einval} -> ok %FreeBSD
end,
- {error, enoent} = ?PRIM_FILE_call(del_dir, Handle, [""]),
- {error, badarg} = ?PRIM_FILE_call(del_dir, Handle, [[3,2,1,{}]])
+ {error, enoent} = ?PRIM_FILE:del_dir(""),
+ {error, badarg} = ?PRIM_FILE:del_dir([3,2,1,{}])
after
- ok = ?PRIM_FILE_call(set_cwd, Handle, [CurrentDir])
+ ok = ?PRIM_FILE:set_cwd(CurrentDir)
end,
ok.
-cur_dir_0a(Config) when is_list(Config) ->
- cur_dir_0(Config, []).
-
-cur_dir_0b(Config) when is_list(Config) ->
- {ok, Handle} = ?PRIM_FILE:start(),
- Result = cur_dir_0(Config, Handle),
- ok = ?PRIM_FILE:stop(Handle),
- Result.
-
-cur_dir_0(Config, Handle) ->
+cur_dir_0(Config) when is_list(Config) ->
%% Find out the current dir, and cd to it ;-)
- {ok,BaseDir} = ?PRIM_FILE_call(get_cwd, Handle, []),
+ {ok,BaseDir} = ?PRIM_FILE:get_cwd(),
Dir1 = BaseDir ++ "", %% Check that it's a string
- ok = ?PRIM_FILE_call(set_cwd, Handle, [Dir1]),
- DirName = atom_to_list(?MODULE) ++
- case Handle of
- [] ->
- "_curdir";
- _ ->
- "_curdir_h"
- end,
+ ok = ?PRIM_FILE:set_cwd(Dir1),
+ DirName = atom_to_list(?MODULE) ++ "_curdir",
%% Make a new dir, and cd to that
RootDir = proplists:get_value(priv_dir,Config),
NewDir = filename:join(RootDir, DirName),
- ok = ?PRIM_FILE_call(make_dir, Handle, [NewDir]),
+ ok = ?PRIM_FILE:make_dir(NewDir),
case {os:type(), length(NewDir) >= 260} of
{{win32,_}, true} ->
io:format("Skip set_cwd for windows path longer than 260 (MAX_PATH):\n"),
io:format("\nNewDir = ~p\n", [NewDir]);
_ ->
io:format("cd to ~s",[NewDir]),
- ok = ?PRIM_FILE_call(set_cwd, Handle, [NewDir]),
+ ok = ?PRIM_FILE:set_cwd(NewDir),
%% Create a file in the new current directory, and check that it
%% really is created there
UncommonName = "uncommon.fil",
{ok,Fd} = ?PRIM_FILE:open(UncommonName, [read, write]),
ok = ?PRIM_FILE:close(Fd),
- {ok,NewDirFiles} = ?PRIM_FILE_call(list_dir, Handle, ["."]),
+ {ok,NewDirFiles} = ?PRIM_FILE:list_dir("."),
true = lists:member(UncommonName,NewDirFiles),
%% Delete the directory and return to the old current directory
%% and check that the created file isn't there (too!)
expect({error, einval}, {error, eacces}, {error, eexist},
- ?PRIM_FILE_call(del_dir, Handle, [NewDir])),
- ?PRIM_FILE_call(delete, Handle, [UncommonName]),
- {ok,[]} = ?PRIM_FILE_call(list_dir, Handle, ["."]),
- ok = ?PRIM_FILE_call(set_cwd, Handle, [Dir1]),
+ ?PRIM_FILE:del_dir(NewDir)),
+ ?PRIM_FILE:delete(UncommonName),
+ {ok,[]} = ?PRIM_FILE:list_dir("."),
+ ok = ?PRIM_FILE:set_cwd(Dir1),
io:format("cd back to ~s",[Dir1]),
- ok = ?PRIM_FILE_call(del_dir, Handle, [NewDir]),
- {error, enoent} = ?PRIM_FILE_call(set_cwd, Handle, [NewDir]),
- ok = ?PRIM_FILE_call(set_cwd, Handle, [Dir1]),
+ ok = ?PRIM_FILE:del_dir(NewDir),
+ {error, enoent} = ?PRIM_FILE:set_cwd(NewDir),
+ ok = ?PRIM_FILE:set_cwd(Dir1),
io:format("cd back to ~s",[Dir1]),
- {ok,OldDirFiles} = ?PRIM_FILE_call(list_dir, Handle, ["."]),
+ {ok,OldDirFiles} = ?PRIM_FILE:list_dir("."),
false = lists:member(UncommonName,OldDirFiles)
end,
%% Try doing some bad things
{error, badarg} =
- ?PRIM_FILE_call(set_cwd, Handle, [{foo,bar}]),
+ ?PRIM_FILE:set_cwd({foo,bar}),
{error, enoent} =
- ?PRIM_FILE_call(set_cwd, Handle, [""]),
+ ?PRIM_FILE:set_cwd(""),
{error, enoent} =
- ?PRIM_FILE_call(set_cwd, Handle, [".......a......"]),
+ ?PRIM_FILE:set_cwd(".......a......"),
{ok,BaseDir} =
- ?PRIM_FILE_call(get_cwd, Handle, []), %% Still there?
+ ?PRIM_FILE:get_cwd(), %% Still there?
%% On Windows, there should only be slashes, no backslashes,
%% in the return value of get_cwd().
%% (The test is harmless on Unix, because filenames usually
%% don't contain backslashes.)
- {ok, BaseDir} = ?PRIM_FILE_call(get_cwd, Handle, []),
+ {ok, BaseDir} = ?PRIM_FILE:get_cwd(),
false = lists:member($\\, BaseDir),
ok.
%% Tests ?PRIM_FILE:get_cwd/1.
-cur_dir_1a(Config) when is_list(Config) ->
- cur_dir_1(Config, []).
-
-cur_dir_1b(Config) when is_list(Config) ->
- {ok, Handle} = ?PRIM_FILE:start(),
- Result = cur_dir_1(Config, Handle),
- ok = ?PRIM_FILE:stop(Handle),
- Result.
-
-cur_dir_1(Config, Handle) ->
+cur_dir_1(Config) when is_list(Config) ->
case os:type() of
{win32, _} ->
- win_cur_dir_1(Config, Handle);
+ win_cur_dir_1(Config);
_ ->
{error, enotsup} =
- ?PRIM_FILE_call(get_cwd, Handle, ["d:"])
+ ?PRIM_FILE:get_cwd("d:")
end,
ok.
-win_cur_dir_1(_Config, Handle) ->
- {ok, BaseDir} = ?PRIM_FILE_call(get_cwd, Handle, []),
+win_cur_dir_1(_Config) ->
+ {ok, BaseDir} = ?PRIM_FILE:get_cwd(),
%% Get the drive letter from the current directory,
%% and try to get current directory for that drive.
[Drive, $:|_] = BaseDir,
- {ok, BaseDir} = ?PRIM_FILE_call(get_cwd, Handle, [[Drive, $:]]),
+ {ok, BaseDir} = ?PRIM_FILE:get_cwd([Drive, $:]),
io:format("BaseDir = ~s\n", [BaseDir]),
%% Unfortunately, there is no way to move away from the
@@ -446,12 +374,12 @@ open1(Config) when is_list(Config) ->
Name = filename:join(NewDir, "foo1.fil"),
{ok,Fd1} = ?PRIM_FILE:open(Name, [read, write]),
{ok,Fd2} = ?PRIM_FILE:open(Name, [read]),
- Str = "{a,tuple}.\n",
- Length = length(Str),
- ?PRIM_FILE:write(Fd1,Str),
+ Bin = list_to_binary("{a,tuple}.\n"),
+ Length = byte_size(Bin),
+ ?PRIM_FILE:write(Fd1,Bin),
{ok,0} = ?PRIM_FILE:position(Fd1,bof),
- {ok, Str} = ?PRIM_FILE:read(Fd1,Length),
- {ok, Str} = ?PRIM_FILE:read(Fd2,Length),
+ {ok, Bin} = ?PRIM_FILE:read(Fd1,Length),
+ {ok, Bin} = ?PRIM_FILE:read(Fd2,Length),
ok = ?PRIM_FILE:close(Fd2),
{ok,0} = ?PRIM_FILE:position(Fd1,bof),
ok = ?PRIM_FILE:truncate(Fd1),
@@ -471,13 +399,13 @@ modes(Config) when is_list(Config) ->
++"_open_modes"),
ok = ?PRIM_FILE:make_dir(NewDir),
Name1 = filename:join(NewDir, "foo1.fil"),
- Marker = "hello, world",
- Length = length(Marker),
+ Marker = <<"hello, world">>,
+ Length = byte_size(Marker),
%% write
{ok, Fd1} = ?PRIM_FILE:open(Name1, [write]),
ok = ?PRIM_FILE:write(Fd1, Marker),
- ok = ?PRIM_FILE:write(Fd1, ".\n"),
+ ok = ?PRIM_FILE:write(Fd1, <<".\n">>),
ok = ?PRIM_FILE:close(Fd1),
%% read
@@ -496,12 +424,6 @@ modes(Config) when is_list(Config) ->
{ok, Marker} = ?PRIM_FILE:read(Fd4, Length),
ok = ?PRIM_FILE:close(Fd4),
- %% read and binary
- BinaryMarker = list_to_binary(Marker),
- {ok, Fd5} = ?PRIM_FILE:open(Name1, [read, binary]),
- {ok, BinaryMarker} = ?PRIM_FILE:read(Fd5, Length),
- ok = ?PRIM_FILE:close(Fd5),
-
ok.
close(Config) when is_list(Config) ->
@@ -528,9 +450,9 @@ access(Config) when is_list(Config) ->
Name = filename:join(RootDir,
atom_to_list(?MODULE)
++"_access.fil"),
- Str = "ABCDEFGH",
+ Bin = <<"ABCDEFGH">>,
{ok,Fd1} = ?PRIM_FILE:open(Name, [write]),
- ?PRIM_FILE:write(Fd1,Str),
+ ?PRIM_FILE:write(Fd1,Bin),
ok = ?PRIM_FILE:close(Fd1),
%% Check that we can't write when in read only mode
{ok,Fd2} = ?PRIM_FILE:open(Name, [read]),
@@ -542,7 +464,7 @@ access(Config) when is_list(Config) ->
end,
ok = ?PRIM_FILE:close(Fd2),
{ok, Fd3} = ?PRIM_FILE:open(Name, [read]),
- {ok, Str} = ?PRIM_FILE:read(Fd3,length(Str)),
+ {ok, Bin} = ?PRIM_FILE:read(Fd3,byte_size(Bin)),
ok = ?PRIM_FILE:close(Fd3),
ok.
@@ -564,7 +486,7 @@ read_write(Config) when is_list(Config) ->
ok.
read_write_test(File) ->
- Marker = "hello, world",
+ Marker = <<"hello, world">>,
ok = ?PRIM_FILE:write(File, Marker),
{ok, 0} = ?PRIM_FILE:position(File, 0),
{ok, Marker} = ?PRIM_FILE:read(File, 100),
@@ -590,15 +512,15 @@ pread_write(Config) when is_list(Config) ->
ok.
pread_write_test(File) ->
- Marker = "hello, world",
- Len = length(Marker),
+ Marker = <<"hello, world">>,
+ Len = byte_size(Marker),
ok = ?PRIM_FILE:write(File, Marker),
{ok, Marker} = ?PRIM_FILE:pread(File, 0, 100),
eof = ?PRIM_FILE:pread(File, 100, 1),
ok = ?PRIM_FILE:pwrite(File, Len, Marker),
{ok, Marker} = ?PRIM_FILE:pread(File, Len, 100),
eof = ?PRIM_FILE:pread(File, 100, 1),
- MM = Marker ++ Marker,
+ MM = <<Marker/binary,Marker/binary>>,
{ok, MM} = ?PRIM_FILE:pread(File, 0, 100),
ok = ?PRIM_FILE:close(File),
ok.
@@ -655,24 +577,24 @@ pos1(Config) when is_list(Config) ->
atom_to_list(?MODULE)
++"_pos1.fil"),
{ok, Fd1} = ?PRIM_FILE:open(Name, [write]),
- ?PRIM_FILE:write(Fd1,"ABCDEFGH"),
+ ?PRIM_FILE:write(Fd1,<<"ABCDEFGH">>),
ok = ?PRIM_FILE:close(Fd1),
{ok, Fd2} = ?PRIM_FILE:open(Name, [read]),
%% Start pos is first char
io:format("Relative positions"),
- {ok, "A"} = ?PRIM_FILE:read(Fd2,1),
+ {ok, <<"A">>} = ?PRIM_FILE:read(Fd2,1),
{ok, 2} = ?PRIM_FILE:position(Fd2,{cur,1}),
- {ok, "C"} = ?PRIM_FILE:read(Fd2,1),
+ {ok, <<"C">>} = ?PRIM_FILE:read(Fd2,1),
{ok, 0} = ?PRIM_FILE:position(Fd2,{cur,-3}),
- {ok, "A"} = ?PRIM_FILE:read(Fd2,1),
+ {ok, <<"A">>} = ?PRIM_FILE:read(Fd2,1),
%% Backwards from first char should be an error
{ok,0} = ?PRIM_FILE:position(Fd2,{cur,-1}),
{error, einval} = ?PRIM_FILE:position(Fd2,{cur,-1}),
%% Reset position and move again
{ok, 0} = ?PRIM_FILE:position(Fd2,0),
{ok, 2} = ?PRIM_FILE:position(Fd2,{cur,2}),
- {ok, "C"} = ?PRIM_FILE:read(Fd2,1),
+ {ok, <<"C">>} = ?PRIM_FILE:read(Fd2,1),
%% Go a lot forwards
{ok, 13} = ?PRIM_FILE:position(Fd2,{cur,10}),
eof = ?PRIM_FILE:read(Fd2,1),
@@ -684,27 +606,27 @@ pos1(Config) when is_list(Config) ->
{ok, 8} = ?PRIM_FILE:position(Fd2,cur),
eof = ?PRIM_FILE:read(Fd2,1),
{ok, 7} = ?PRIM_FILE:position(Fd2,7),
- {ok, "H"} = ?PRIM_FILE:read(Fd2,1),
+ {ok, <<"H">>} = ?PRIM_FILE:read(Fd2,1),
{ok, 0} = ?PRIM_FILE:position(Fd2,0),
- {ok, "A"} = ?PRIM_FILE:read(Fd2,1),
+ {ok, <<"A">>} = ?PRIM_FILE:read(Fd2,1),
{ok, 3} = ?PRIM_FILE:position(Fd2,3),
- {ok, "D"} = ?PRIM_FILE:read(Fd2,1),
+ {ok, <<"D">>} = ?PRIM_FILE:read(Fd2,1),
{ok, 12} = ?PRIM_FILE:position(Fd2,12),
eof = ?PRIM_FILE:read(Fd2,1),
{ok, 3} = ?PRIM_FILE:position(Fd2,3),
- {ok, "D"} = ?PRIM_FILE:read(Fd2,1),
+ {ok, <<"D">>} = ?PRIM_FILE:read(Fd2,1),
%% Try the {bof,X} notation
{ok, 3} = ?PRIM_FILE:position(Fd2,{bof,3}),
- {ok, "D"} = ?PRIM_FILE:read(Fd2,1),
+ {ok, <<"D">>} = ?PRIM_FILE:read(Fd2,1),
%% Try eof positions
io:format("EOF positions"),
{ok, 8} = ?PRIM_FILE:position(Fd2,{eof,0}),
eof = ?PRIM_FILE:read(Fd2,1),
{ok, 7} = ?PRIM_FILE:position(Fd2,{eof,-1}),
- {ok, "H"} = ?PRIM_FILE:read(Fd2,1),
+ {ok, <<"H">>} = ?PRIM_FILE:read(Fd2,1),
{ok, 0} = ?PRIM_FILE:position(Fd2,{eof,-8}),
- {ok, "A"} = ?PRIM_FILE:read(Fd2,1),
+ {ok, <<"A">>} = ?PRIM_FILE:read(Fd2,1),
{error, einval} = ?PRIM_FILE:position(Fd2,{eof,-9}),
ok.
@@ -714,7 +636,7 @@ pos2(Config) when is_list(Config) ->
atom_to_list(?MODULE)
++"_pos2.fil"),
{ok,Fd1} = ?PRIM_FILE:open(Name, [write]),
- ?PRIM_FILE:write(Fd1,"ABCDEFGH"),
+ ?PRIM_FILE:write(Fd1,<<"ABCDEFGH">>),
ok = ?PRIM_FILE:close(Fd1),
{ok, Fd2} = ?PRIM_FILE:open(Name, [read]),
{error, einval} = ?PRIM_FILE:position(Fd2,-1),
@@ -722,35 +644,25 @@ pos2(Config) when is_list(Config) ->
%% Make sure that we still can search after an error.
{ok, 0} = ?PRIM_FILE:position(Fd2, 0),
{ok, 3} = ?PRIM_FILE:position(Fd2, {bof,3}),
- {ok, "D"} = ?PRIM_FILE:read(Fd2,1),
+ {ok, <<"D">>} = ?PRIM_FILE:read(Fd2,1),
io:format("DONE"),
ok.
-
-file_info_basic_file_a(Config) when is_list(Config) ->
- file_info_basic_file(Config, [], "_a").
-
-file_info_basic_file_b(Config) when is_list(Config) ->
- {ok, Handle} = ?PRIM_FILE:start(),
- Result = file_info_basic_file(Config, Handle, "_b"),
- ok = ?PRIM_FILE:stop(Handle),
- Result.
-
-file_info_basic_file(Config, Handle, Suffix) ->
+file_info_basic_file(Config) when is_list(Config) ->
RootDir = proplists:get_value(priv_dir, Config),
%% Create a short file.
Name = filename:join(RootDir,
atom_to_list(?MODULE)
- ++"_basic_test"++Suffix++".fil"),
+ ++"_basic_test"".fil"),
{ok,Fd1} = ?PRIM_FILE:open(Name, [write]),
?PRIM_FILE:write(Fd1, "foo bar"),
ok = ?PRIM_FILE:close(Fd1),
%% Test that the file has the expected attributes.
%% The times are tricky, so we will save them to a separate test case.
- {ok, FileInfo} = ?PRIM_FILE_call(read_file_info, Handle, [Name]),
+ {ok, FileInfo} = ?PRIM_FILE:read_file_info(Name),
#file_info{size = Size, type = Type, access = Access,
atime = AccessTime, mtime = ModifyTime} =
FileInfo,
@@ -768,39 +680,30 @@ file_info_basic_file(Config, Handle, Suffix) ->
ok.
-file_info_basic_directory_a(Config) when is_list(Config) ->
- file_info_basic_directory(Config, []).
-
-file_info_basic_directory_b(Config) when is_list(Config) ->
- {ok, Handle} = ?PRIM_FILE:start(),
- Result = file_info_basic_directory(Config, Handle),
- ok = ?PRIM_FILE:stop(Handle),
- Result.
-
-file_info_basic_directory(Config, Handle) ->
+file_info_basic_directory(Config) when is_list(Config) ->
%% Note: filename:join/1 removes any trailing slash,
%% which is essential for ?PRIM_FILE:read_file_info/1 to work on
%% platforms such as Windows95.
RootDir = filename:join([proplists:get_value(priv_dir, Config)]),
%% Test that the RootDir directory has the expected attributes.
- test_directory(RootDir, read_write, Handle),
+ test_directory(RootDir, read_write),
%% Note that on Windows file systems, "/" or "c:/" are *NOT* directories.
%% Therefore, test that ?PRIM_FILE:read_file_info/1 behaves
%% as if they were directories.
case os:type() of
{win32, _} ->
- test_directory("/", read_write, Handle),
- test_directory("c:/", read_write, Handle),
- test_directory("c:\\", read_write, Handle);
+ test_directory("/", read_write),
+ test_directory("c:/", read_write),
+ test_directory("c:\\", read_write);
_ ->
- test_directory("/", read, Handle)
+ test_directory("/", read)
end,
ok.
-test_directory(Name, ExpectedAccess, Handle) ->
- {ok, FileInfo} = ?PRIM_FILE_call(read_file_info, Handle, [Name]),
+test_directory(Name, ExpectedAccess) ->
+ {ok, FileInfo} = ?PRIM_FILE:read_file_info(Name),
#file_info{size = Size, type = Type, access = Access,
atime = AccessTime, mtime = ModifyTime} =
FileInfo,
@@ -824,45 +727,24 @@ all_integers([]) ->
%% Try something nonexistent.
-file_info_bad_a(Config) when is_list(Config) ->
- file_info_bad(Config, []).
-
-file_info_bad_b(Config) when is_list(Config) ->
- {ok, Handle} = ?PRIM_FILE:start(),
- Result = file_info_bad(Config, Handle),
- ok = ?PRIM_FILE:stop(Handle),
- Result.
-
-file_info_bad(Config, Handle) ->
+file_info_bad(Config) when is_list(Config) ->
RootDir = filename:join([proplists:get_value(priv_dir, Config)]),
- {error, enoent} =
- ?PRIM_FILE_call(
- read_file_info, Handle,
- [filename:join(RootDir,
- atom_to_list(?MODULE)++"_nonexistent")]),
+ NonExistent = filename:join(RootDir, atom_to_list(?MODULE)++"_nonexistent"),
+ {error, enoent} = ?PRIM_FILE:read_file_info(NonExistent),
ok.
%% Test that the file times behave as they should.
-file_info_times_a(Config) when is_list(Config) ->
- file_info_times(Config, [], "_a").
-
-file_info_times_b(Config) when is_list(Config) ->
- {ok, Handle} = ?PRIM_FILE:start(),
- Result = file_info_times(Config, Handle, "_b"),
- ok = ?PRIM_FILE:stop(Handle),
- Result.
-
-file_info_times(Config, Handle, Suffix) ->
+file_info_times(Config) when is_list(Config) ->
%% We have to try this twice, since if the test runs across the change
%% of a month the time diff calculations will fail. But it won't happen
%% if you run it twice in succession.
test_server:m_out_of_n(
1,2,
- fun() -> file_info_int(Config, Handle, Suffix) end),
+ fun() -> file_info_int(Config) end),
ok.
-file_info_int(Config, Handle, Suffix) ->
+file_info_int(Config) ->
%% Note: filename:join/1 removes any trailing slash,
%% which is essential for ?PRIM_FILE:read_file_info/1 to work on
%% platforms such as Windows95.
@@ -872,14 +754,14 @@ file_info_int(Config, Handle, Suffix) ->
Name = filename:join(RootDir,
atom_to_list(?MODULE)
- ++"_file_info"++Suffix++".fil"),
+ ++"_file_info.fil"),
{ok,Fd1} = ?PRIM_FILE:open(Name, [write]),
?PRIM_FILE:write(Fd1,"foo"),
%% check that the file got a modify date max a few seconds away from now
{ok, #file_info{type = regular,
atime = AccTime1, mtime = ModTime1}} =
- ?PRIM_FILE_call(read_file_info, Handle, [Name]),
+ ?PRIM_FILE:read_file_info(Name),
Now = erlang:localtime(),
io:format("Now ~p",[Now]),
io:format("Open file Acc ~p Mod ~p",[AccTime1,ModTime1]),
@@ -897,7 +779,7 @@ file_info_int(Config, Handle, Suffix) ->
ok = ?PRIM_FILE:close(Fd1),
{ok, #file_info{size = Size, type = regular, access = Access,
atime = AccTime2, mtime = ModTime2}} =
- ?PRIM_FILE_call(read_file_info, Handle, [Name]),
+ ?PRIM_FILE:read_file_info(Name),
io:format("Closed file Acc ~p Mod ~p",[AccTime2,ModTime2]),
true = time_dist(ModTime1, ModTime2) >= 0,
@@ -909,7 +791,7 @@ file_info_int(Config, Handle, Suffix) ->
{ok, #file_info{size = DSize, type = directory,
access = DAccess,
atime = AccTime3, mtime = ModTime3}} =
- ?PRIM_FILE_call(read_file_info, Handle, [RootDir]),
+ ?PRIM_FILE:read_file_info(RootDir),
%% this dir was modified only a few secs ago
io:format("Dir Acc ~p; Mod ~p; Now ~p",
[AccTime3, ModTime3, Now]),
@@ -936,16 +818,7 @@ filter_atime(Atime, Config) ->
%% Test the write_file_info/2 function.
-file_write_file_info_a(Config) when is_list(Config) ->
- file_write_file_info(Config, [], "_a").
-
-file_write_file_info_b(Config) when is_list(Config) ->
- {ok, Handle} = ?PRIM_FILE:start(),
- Result = file_write_file_info(Config, Handle, "_b"),
- ok = ?PRIM_FILE:stop(Handle),
- Result.
-
-file_write_file_info(Config, Handle, Suffix) ->
+file_write_file_info(Config) when is_list(Config) ->
RootDir = get_good_directory(Config),
io:format("RootDir = ~p", [RootDir]),
@@ -955,16 +828,16 @@ file_write_file_info(Config, Handle, Suffix) ->
Name = filename:join(RootDir,
atom_to_list(?MODULE)
- ++"_write_file_info_ro"++Suffix),
+ ++"_write_file_info_ro"),
ok = ?PRIM_FILE:write_file(Name, "hello"),
Time = {{1997, 01, 02}, {12, 35, 42}},
Info = #file_info{mode=8#400, atime=Time, mtime=Time, ctime=Time},
- ok = ?PRIM_FILE_call(write_file_info, Handle, [Name, Info]),
+ ok = ?PRIM_FILE:write_file_info(Name, Info),
%% Read back the times.
{ok, ActualInfo} =
- ?PRIM_FILE_call(read_file_info, Handle, [Name]),
+ ?PRIM_FILE:read_file_info(Name),
#file_info{mode=_Mode, atime=ActAtime, mtime=Time,
ctime=ActCtime} = ActualInfo,
FilteredAtime = filter_atime(Time, Config),
@@ -980,14 +853,11 @@ file_write_file_info(Config, Handle, Suffix) ->
{error, eacces} = ?PRIM_FILE:write_file(Name, "hello again"),
%% Make the file writable again.
-
- ?PRIM_FILE_call(write_file_info, Handle,
- [Name, #file_info{mode=8#600}]),
+ ?PRIM_FILE:write_file_info(Name, #file_info{mode=8#600}),
ok = ?PRIM_FILE:write_file(Name, "hello again"),
%% And unwritable.
- ?PRIM_FILE_call(write_file_info, Handle,
- [Name, #file_info{mode=8#400}]),
+ ?PRIM_FILE:write_file_info(Name, #file_info{mode=8#400}),
{error, eacces} = ?PRIM_FILE:write_file(Name, "hello again"),
%% Write the times again.
@@ -995,9 +865,9 @@ file_write_file_info(Config, Handle, Suffix) ->
NewTime = {{1997, 02, 15}, {13, 18, 20}},
NewInfo = #file_info{atime=NewTime, mtime=NewTime, ctime=NewTime},
- ok = ?PRIM_FILE_call(write_file_info, Handle, [Name, NewInfo]),
+ ok = ?PRIM_FILE:write_file_info(Name, NewInfo),
{ok, ActualInfo2} =
- ?PRIM_FILE_call(read_file_info, Handle, [Name]),
+ ?PRIM_FILE:read_file_info(Name),
#file_info{atime=NewActAtime, mtime=NewTime,
ctime=NewActCtime} = ActualInfo2,
NewFilteredAtime = filter_atime(NewTime, Config),
@@ -1012,14 +882,12 @@ file_write_file_info(Config, Handle, Suffix) ->
%% Make the file writeable again, so that we can remove the
%% test suites ... :-)
- ?PRIM_FILE_call(write_file_info, Handle,
- [Name, #file_info{mode=8#600}]),
+ ?PRIM_FILE:write_file_info(Name, #file_info{mode=8#600}),
ok.
%% Test the write_file_info/3 function.
file_write_file_info_opts(Config) when is_list(Config) ->
- {ok, Handle} = ?PRIM_FILE:start(),
RootDir = get_good_directory(Config),
io:format("RootDir = ~p", [RootDir]),
@@ -1028,7 +896,7 @@ file_write_file_info_opts(Config) when is_list(Config) ->
lists:foreach(fun
({FI, Opts}) ->
- ok = ?PRIM_FILE_call(write_file_info, Handle, [Name, FI, Opts])
+ ok = ?PRIM_FILE:write_file_info(Name, FI, Opts)
end, [
{#file_info{ mode=8#600, atime = Time, mtime = Time, ctime = Time}, Opts} ||
Opts <- [[{time, posix}]],
@@ -1038,7 +906,7 @@ file_write_file_info_opts(Config) when is_list(Config) ->
%% REM: determine date range dependent on time_t = Uint32 | Sint32 | Sint64 | Uint64
%% Determine time_t on os:type()?
lists:foreach(fun ({FI, Opts}) ->
- ok = ?PRIM_FILE_call(write_file_info, Handle, [Name, FI, Opts])
+ ok = ?PRIM_FILE:write_file_info(Name, FI, Opts)
end, [ {#file_info{ mode=8#400, atime = Time, mtime = Time, ctime = Time}, Opts} ||
Opts <- [[{time, universal}],[{time, local}]],
Time <- [
@@ -1050,11 +918,9 @@ file_write_file_info_opts(Config) when is_list(Config) ->
{{2037,2,3},{23,59,59}},
erlang:localtime()
]]),
- ok = ?PRIM_FILE:stop(Handle),
ok.
file_read_file_info_opts(Config) when is_list(Config) ->
- {ok, Handle} = ?PRIM_FILE:start(),
RootDir = get_good_directory(Config),
io:format("RootDir = ~p", [RootDir]),
@@ -1063,41 +929,38 @@ file_read_file_info_opts(Config) when is_list(Config) ->
lists:foreach(fun
(Opts) ->
- {ok,_} = ?PRIM_FILE_call(read_file_info, Handle, [Name, Opts])
+ {ok,_} = ?PRIM_FILE:read_file_info(Name, Opts)
end, [[{time, Type}] || Type <- [local, universal, posix]]),
- ok = ?PRIM_FILE:stop(Handle),
ok.
%% Test the write and read back *_file_info/3 functions.
file_write_read_file_info_opts(Config) when is_list(Config) ->
- {ok, Handle} = ?PRIM_FILE:start(),
RootDir = get_good_directory(Config),
io:format("RootDir = ~p", [RootDir]),
Name = filename:join(RootDir, atom_to_list(?MODULE) ++"_read_write_file_info_opts"),
ok = ?PRIM_FILE:write_file(Name, "hello_opts2"),
- ok = file_write_read_file_info_opts(Handle, Name, {{1989, 04, 28}, {19,30,22}}, [{time, local}]),
- ok = file_write_read_file_info_opts(Handle, Name, {{1989, 04, 28}, {19,30,22}}, [{time, universal}]),
+ ok = file_write_read_file_info_opts(Name, {{1989, 04, 28}, {19,30,22}}, [{time, local}]),
+ ok = file_write_read_file_info_opts(Name, {{1989, 04, 28}, {19,30,22}}, [{time, universal}]),
%% will not work on platforms with unsigned time_t
- %ok = file_write_read_file_info_opts(Handle, Name, {{1930, 04, 28}, {19,30,22}}, [{time, local}]),
- %ok = file_write_read_file_info_opts(Handle, Name, {{1930, 04, 28}, {19,30,22}}, [{time, universal}]),
- ok = file_write_read_file_info_opts(Handle, Name, 1, [{time, posix}]),
+ %ok = file_write_read_file_info_opts(Name, {{1930, 04, 28}, {19,30,22}}, [{time, local}]),
+ %ok = file_write_read_file_info_opts(Name, {{1930, 04, 28}, {19,30,22}}, [{time, universal}]),
+ ok = file_write_read_file_info_opts(Name, 1, [{time, posix}]),
%% will not work on platforms with unsigned time_t
- %ok = file_write_read_file_info_opts(Handle, Name, -1, [{time, posix}]),
- %ok = file_write_read_file_info_opts(Handle, Name, -300000, [{time, posix}]),
- ok = file_write_read_file_info_opts(Handle, Name, 300000, [{time, posix}]),
- ok = file_write_read_file_info_opts(Handle, Name, 0, [{time, posix}]),
+ %ok = file_write_read_file_info_opts(Name, -1, [{time, posix}]),
+ %ok = file_write_read_file_info_opts(Name, -300000, [{time, posix}]),
+ ok = file_write_read_file_info_opts(Name, 300000, [{time, posix}]),
+ ok = file_write_read_file_info_opts(Name, 0, [{time, posix}]),
- ok = ?PRIM_FILE:stop(Handle),
ok.
-file_write_read_file_info_opts(Handle, Name, Mtime, Opts) ->
- {ok, FI} = ?PRIM_FILE_call(read_file_info, Handle, [Name, Opts]),
+file_write_read_file_info_opts(Name, Mtime, Opts) ->
+ {ok, FI} = ?PRIM_FILE:read_file_info(Name, Opts),
FI2 = FI#file_info{ mtime = Mtime },
- ok = ?PRIM_FILE_call(write_file_info, Handle, [Name, FI2, Opts]),
- {ok, FI3} = ?PRIM_FILE_call(read_file_info, Handle, [Name, Opts]),
+ ok = ?PRIM_FILE:write_file_info(Name, FI2, Opts),
+ {ok, FI3} = ?PRIM_FILE:read_file_info(Name, Opts),
io:format("Expecting mtime = ~p, got ~p~n", [FI2#file_info.mtime, FI3#file_info.mtime]),
FI2 = FI3,
ok.
@@ -1175,8 +1038,8 @@ advise(Config) when is_list(Config) ->
atom_to_list(?MODULE)
++"_advise.fil"),
- Line1 = "Hello\n",
- Line2 = "World!\n",
+ Line1 = <<"Hello\n">>,
+ Line2 = <<"World!\n">>,
{ok, Fd} = ?PRIM_FILE:open(Advise, [write]),
ok = ?PRIM_FILE:advise(Fd, 0, 0, normal),
@@ -1226,7 +1089,7 @@ advise(Config) when is_list(Config) ->
{ok, Fd9} = ?PRIM_FILE:open(Advise, [read]),
Offset = 0,
%% same as a 0 length in some implementations
- Length = length(Line1) + length(Line2),
+ Length = byte_size(Line1) + byte_size(Line2),
ok = ?PRIM_FILE:advise(Fd9, Offset, Length, sequential),
{ok, Line1} = ?PRIM_FILE:read_line(Fd9),
{ok, Line2} = ?PRIM_FILE:read_line(Fd9),
@@ -1250,23 +1113,18 @@ do_large_write(Name) ->
Chunk = <<0:ChunkSize/unit:8>>,
Data = zip_data(lists:duplicate(Chunks, Chunk), Interleave),
Size = Chunks * ChunkSize + Chunks, % 4 G + 32
- Wordsize = erlang:system_info(wordsize),
- case prim_file:write_file(Name, Data) of
- ok when Wordsize =:= 8 ->
- {ok,#file_info{size=Size}} = file:read_file_info(Name),
- {ok,Fd} = prim_file:open(Name, [read]),
- check_large_write(Fd, ChunkSize, 0, Interleave);
- {error,einval} when Wordsize =:= 4 ->
- ok
- end.
+ ok = ?PRIM_FILE:write_file(Name, Data),
+ {ok,#file_info{size=Size}} = file:read_file_info(Name),
+ {ok,Fd} = ?PRIM_FILE:open(Name, [read]),
+ check_large_write(Fd, ChunkSize, 0, Interleave).
check_large_write(Fd, ChunkSize, Pos, [X|Interleave]) ->
Pos1 = Pos + ChunkSize,
- {ok,Pos1} = prim_file:position(Fd, {cur,ChunkSize}),
- {ok,[X]} = prim_file:read(Fd, 1),
+ {ok,Pos1} = ?PRIM_FILE:position(Fd, {cur,ChunkSize}),
+ {ok,<<X>>} = ?PRIM_FILE:read(Fd, 1),
check_large_write(Fd, ChunkSize, Pos1+1, Interleave);
check_large_write(Fd, _, _, []) ->
- eof = prim_file:read(Fd, 1),
+ eof = ?PRIM_FILE:read(Fd, 1),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1338,71 +1196,53 @@ allocate_and_assert(Fd, Offset, Length) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-delete_a(Config) when is_list(Config) ->
- delete(Config, [], "_a").
-
-delete_b(Config) when is_list(Config) ->
- {ok, Handle} = ?PRIM_FILE:start(),
- Result = delete(Config, Handle, "_b"),
- ok = ?PRIM_FILE:stop(Handle),
- Result.
-
-delete(Config, Handle, Suffix) ->
+delete(Config) when is_list(Config) ->
RootDir = proplists:get_value(priv_dir,Config),
Name = filename:join(RootDir,
atom_to_list(?MODULE)
- ++"_delete"++Suffix++".fil"),
+ ++"_delete.fil"),
{ok, Fd1} = ?PRIM_FILE:open(Name, [write]),
?PRIM_FILE:write(Fd1,"ok.\n"),
ok = ?PRIM_FILE:close(Fd1),
%% Check that the file is readable
{ok, Fd2} = ?PRIM_FILE:open(Name, [read]),
ok = ?PRIM_FILE:close(Fd2),
- ok = ?PRIM_FILE_call(delete, Handle, [Name]),
+ ok = ?PRIM_FILE:delete(Name),
%% Check that the file is not readable anymore
{error, _} = ?PRIM_FILE:open(Name, [read]),
%% Try deleting a nonexistent file
- {error, enoent} = ?PRIM_FILE_call(delete, Handle, [Name]),
+ {error, enoent} = ?PRIM_FILE:delete(Name),
ok.
-rename_a(Config) when is_list(Config) ->
- rename(Config, [], "_a").
-
-rename_b(Config) when is_list(Config) ->
- {ok, Handle} = ?PRIM_FILE:start(),
- Result = rename(Config, Handle, "_b"),
- ok = ?PRIM_FILE:stop(Handle),
- Result.
-
-rename(Config, Handle, Suffix) ->
+rename(Config) when is_list(Config) ->
RootDir = proplists:get_value(priv_dir,Config),
- FileName1 = atom_to_list(?MODULE)++"_rename"++Suffix++".fil",
- FileName2 = atom_to_list(?MODULE)++"_rename"++Suffix++".ful",
+ FileName1 = atom_to_list(?MODULE)++"_rename.fil",
+ FileName2 = atom_to_list(?MODULE)++"_rename.ful",
Name1 = filename:join(RootDir, FileName1),
Name2 = filename:join(RootDir, FileName2),
{ok,Fd1} = ?PRIM_FILE:open(Name1, [write]),
ok = ?PRIM_FILE:close(Fd1),
%% Rename, and check that it really changed name
- ok = ?PRIM_FILE_call(rename, Handle, [Name1, Name2]),
+ ok = ?PRIM_FILE:rename(Name1, Name2),
{error, _} = ?PRIM_FILE:open(Name1, [read]),
{ok, Fd2} = ?PRIM_FILE:open(Name2, [read]),
ok = ?PRIM_FILE:close(Fd2),
%% Try renaming something to itself
- ok = ?PRIM_FILE_call(rename, Handle, [Name2, Name2]),
+ ok = ?PRIM_FILE:rename(Name2, Name2),
%% Try renaming something that doesn't exist
{error, enoent} =
- ?PRIM_FILE_call(rename, Handle, [Name1, Name2]),
+ ?PRIM_FILE:rename(Name1, Name2),
%% Try renaming to something else than a string
{error, badarg} =
- ?PRIM_FILE_call(rename, Handle, [Name1, foobar]),
+ ?PRIM_FILE:rename(Name1, foobar),
%% Move between directories
DirName1 = filename:join(RootDir,
atom_to_list(?MODULE)
- ++"_rename_dir"++Suffix),
+ ++"_rename_dir"),
DirName2 = filename:join(RootDir,
atom_to_list(?MODULE)
- ++"_second_rename_dir"++Suffix),
+ ++"_second_rename_dir"),
Name1foo = filename:join(DirName1, "foo.fil"),
Name2foo = filename:join(DirName2, "foo.fil"),
Name2bar = filename:join(DirName2, "bar.dir"),
@@ -1410,21 +1250,21 @@ rename(Config, Handle, Suffix) ->
%% The name has to include the full file name, path is not enough
expect(
{error, eexist}, {error, eisdir},
- ?PRIM_FILE_call(rename, Handle, [Name2, DirName1])),
+ ?PRIM_FILE:rename(Name2, DirName1)),
ok =
- ?PRIM_FILE_call(rename, Handle, [Name2, Name1foo]),
+ ?PRIM_FILE:rename(Name2, Name1foo),
%% Now rename the directory
- ok = ?PRIM_FILE_call(rename, Handle, [DirName1, DirName2]),
+ ok = ?PRIM_FILE:rename(DirName1, DirName2),
%% And check that the file is there now
{ok,Fd3} = ?PRIM_FILE:open(Name2foo, [read]),
ok = ?PRIM_FILE:close(Fd3),
%% Try some dirty things now: move the directory into itself
{error, Msg1} =
- ?PRIM_FILE_call(rename, Handle, [DirName2, Name2bar]),
+ ?PRIM_FILE:rename(DirName2, Name2bar),
io:format("Errmsg1: ~p",[Msg1]),
%% move dir into a file in itself
{error, Msg2} =
- ?PRIM_FILE_call(rename, Handle, [DirName2, Name2foo]),
+ ?PRIM_FILE:rename(DirName2, Name2foo),
io:format("Errmsg2: ~p",[Msg2]),
ok.
@@ -1657,165 +1497,19 @@ e_del_dir(Config) when is_list(Config) ->
ok.
-%% Trying reading and positioning from a compressed file.
-
-read_compressed(Config) when is_list(Config) ->
- Data = proplists:get_value(data_dir, Config),
- Real = filename:join(Data, "realmen.html.gz"),
- {ok, Fd} = ?PRIM_FILE:open(Real, [read, compressed]),
- try_read_file(Fd).
-
-%% Trying reading and positioning from an uncompressed file,
-%% but with the compressed flag given.
-
-read_not_really_compressed(Config) when is_list(Config) ->
- Data = proplists:get_value(data_dir, Config),
- Priv = proplists:get_value(priv_dir, Config),
-
- %% The file realmen.html might have got CRs added (by WinZip).
- %% Remove them, or the file positions will not be correct.
-
- Real = filename:join(Data, "realmen.html"),
- RealPriv = filename:join(Priv,
- atom_to_list(?MODULE)++"_realmen.html"),
- {ok, RealDataBin} = ?PRIM_FILE:read_file(Real),
- RealData = remove_crs(binary_to_list(RealDataBin), []),
- ok = ?PRIM_FILE:write_file(RealPriv, RealData),
- {ok, Fd} = ?PRIM_FILE:open(RealPriv, [read, compressed]),
- try_read_file(Fd).
-
-remove_crs([$\r|Rest], Result) ->
- remove_crs(Rest, Result);
-remove_crs([C|Rest], Result) ->
- remove_crs(Rest, [C|Result]);
-remove_crs([], Result) ->
- lists:reverse(Result).
-
-try_read_file(Fd) ->
- %% Seek to the current position (nothing should happen).
-
- {ok, 0} = ?PRIM_FILE:position(Fd, 0),
- {ok, 0} = ?PRIM_FILE:position(Fd, {cur, 0}),
-
- %% Read a few lines from a compressed file.
-
- ShouldBe = "<TITLE>Real Programmers Don't Use PASCAL</TITLE>\n",
- {ok, ShouldBe} = ?PRIM_FILE:read(Fd, length(ShouldBe)),
-
- %% Now seek forward.
-
- {ok, 381} = ?PRIM_FILE:position(Fd, 381),
- Back = "Back in the good old days -- the \"Golden Era\" " ++
- "of computers, it was\n",
- {ok, Back} = ?PRIM_FILE:read(Fd, length(Back)),
-
- %% Try to search forward relative to the current position.
-
- {ok, CurPos} = ?PRIM_FILE:position(Fd, {cur, 0}),
- RealPos = 4273,
- {ok, RealPos} = ?PRIM_FILE:position(Fd, {cur, RealPos-CurPos}),
- RealProg = "<LI> Real Programmers aren't afraid to use GOTOs.\n",
- {ok, RealProg} = ?PRIM_FILE:read(Fd, length(RealProg)),
-
- %% Seek backward.
-
- AfterTitle = length("<TITLE>"),
- {ok, AfterTitle} = ?PRIM_FILE:position(Fd, AfterTitle),
- Title = "Real Programmers Don't Use PASCAL</TITLE>\n",
- {ok, Title} = ?PRIM_FILE:read(Fd, length(Title)),
-
- %% Done.
-
- ?PRIM_FILE:close(Fd),
- ok.
-
-write_compressed(Config) when is_list(Config) ->
- Priv = proplists:get_value(priv_dir, Config),
- MyFile = filename:join(Priv,
- atom_to_list(?MODULE)++"_test.gz"),
-
- %% Write a file.
-
- {ok, Fd} = ?PRIM_FILE:open(MyFile, [write, compressed]),
- {ok, 0} = ?PRIM_FILE:position(Fd, 0),
- Prefix = "hello\n",
- End = "end\n",
- ok = ?PRIM_FILE:write(Fd, Prefix),
- {ok, 143} = ?PRIM_FILE:position(Fd, 143),
- ok = ?PRIM_FILE:write(Fd, End),
- ok = ?PRIM_FILE:close(Fd),
-
- %% Read the file and verify the contents.
-
- {ok, Fd1} = ?PRIM_FILE:open(MyFile, [read, compressed]),
- {ok, Prefix} = ?PRIM_FILE:read(Fd1, length(Prefix)),
- Second = lists:duplicate(143-length(Prefix), 0) ++ End,
- {ok, Second} = ?PRIM_FILE:read(Fd1, length(Second)),
- ok = ?PRIM_FILE:close(Fd1),
-
- %% Ensure that the file is compressed.
-
- TotalSize = 143 + length(End),
- case ?PRIM_FILE:read_file_info(MyFile) of
- {ok, #file_info{size=Size}} when Size < TotalSize ->
- ok;
- {ok, #file_info{size=Size}} when Size == TotalSize ->
- ct:fail(file_not_compressed)
- end,
-
- %% Write again to ensure that the file is truncated.
-
- {ok, Fd2} = ?PRIM_FILE:open(MyFile, [write, compressed]),
- NewString = "aaaaaaaaaaa",
- ok = ?PRIM_FILE:write(Fd2, NewString),
- ok = ?PRIM_FILE:close(Fd2),
- {ok, Fd3} = ?PRIM_FILE:open(MyFile, [read, compressed]),
- {ok, NewString} = ?PRIM_FILE:read(Fd3, 1024),
- ok = ?PRIM_FILE:close(Fd3),
-
- ok.
-
-compress_errors(Config) when is_list(Config) ->
- Data = proplists:get_value(data_dir, Config),
- {error, enoent} = ?PRIM_FILE:open("non_existing__",
- [compressed, read]),
- {error, einval} = ?PRIM_FILE:open("non_existing__",
- [compressed, read, write]),
-
- %% Read a corrupted .gz file.
-
- Corrupted = filename:join(Data, "corrupted.gz"),
- {ok, Fd} = ?PRIM_FILE:open(Corrupted, [read, compressed]),
- {error, eio} = ?PRIM_FILE:read(Fd, 100),
- ?PRIM_FILE:close(Fd),
-
- ok.
-
-
-%% Test creating a hard link.
-make_link_a(Config) when is_list(Config) ->
- make_link(Config, [], "_a").
-
-%% Test creating a hard link.
-make_link_b(Config) when is_list(Config) ->
- {ok, Handle} = ?PRIM_FILE:start(),
- Result = make_link(Config, Handle, "_b"),
- ok = ?PRIM_FILE:stop(Handle),
- Result.
-
-make_link(Config, Handle, Suffix) ->
+make_link(Config) when is_list(Config) ->
RootDir = proplists:get_value(priv_dir, Config),
NewDir = filename:join(RootDir,
atom_to_list(?MODULE)
- ++"_make_link"++Suffix),
- ok = ?PRIM_FILE_call(make_dir, Handle, [NewDir]),
+ ++"_make_link"),
+ ok = ?PRIM_FILE:make_dir(NewDir),
Name = filename:join(NewDir, "a_file"),
ok = ?PRIM_FILE:write_file(Name, "some contents\n"),
Alias = filename:join(NewDir, "an_alias"),
Result =
- case ?PRIM_FILE_call(make_link, Handle, [Name, Alias]) of
+ case ?PRIM_FILE:make_link(Name, Alias) of
{error, enotsup} ->
{skipped, "Links not supported on this platform"};
ok ->
@@ -1826,12 +1520,12 @@ make_link(Config, Handle, Suffix) ->
%% since they are not used on symbolic links.
{ok, Info} =
- ?PRIM_FILE_call(read_link_info, Handle, [Name]),
+ ?PRIM_FILE:read_link_info(Name),
{ok, Info} =
- ?PRIM_FILE_call(read_link_info, Handle, [Alias]),
+ ?PRIM_FILE:read_link_info(Alias),
#file_info{links = 2, type = regular} = Info,
{error, eexist} =
- ?PRIM_FILE_call(make_link, Handle, [Name, Alias]),
+ ?PRIM_FILE:make_link(Name, Alias),
ok
end,
@@ -1843,30 +1537,19 @@ read_link_info_for_non_link(Config) when is_list(Config) ->
{ok, #file_info{type=directory}} = ?PRIM_FILE:read_link_info("."),
ok.
-%% Test operations on symbolic links (for Unix).
-symlinks_a(Config) when is_list(Config) ->
- symlinks(Config, [], "_a").
-
-%% Test operations on symbolic links (for Unix).
-symlinks_b(Config) when is_list(Config) ->
- {ok, Handle} = ?PRIM_FILE:start(),
- Result = symlinks(Config, Handle, "_b"),
- ok = ?PRIM_FILE:stop(Handle),
- Result.
-
-symlinks(Config, Handle, Suffix) ->
+symlinks(Config) when is_list(Config) ->
RootDir = proplists:get_value(priv_dir, Config),
NewDir = filename:join(RootDir,
atom_to_list(?MODULE)
- ++"_make_symlink"++Suffix),
- ok = ?PRIM_FILE_call(make_dir, Handle, [NewDir]),
+ ++"_make_symlink"),
+ ok = ?PRIM_FILE:make_dir(NewDir),
Name = filename:join(NewDir, "a_plain_file"),
ok = ?PRIM_FILE:write_file(Name, "some stupid content\n"),
Alias = filename:join(NewDir, "a_symlink_alias"),
Result =
- case ?PRIM_FILE_call(make_symlink, Handle, [Name, Alias]) of
+ case ?PRIM_FILE:make_symlink(Name, Alias) of
{error, enotsup} ->
{skipped, "Links not supported on this platform"};
{error, eperm} ->
@@ -1874,20 +1557,20 @@ symlinks(Config, Handle, Suffix) ->
{skipped, "Windows user not privileged to create links"};
ok ->
{ok, Info1} =
- ?PRIM_FILE_call(read_file_info, Handle, [Name]),
+ ?PRIM_FILE:read_file_info(Name),
{ok, Info1} =
- ?PRIM_FILE_call(read_file_info, Handle, [Alias]),
+ ?PRIM_FILE:read_file_info(Alias),
{ok, Info1} =
- ?PRIM_FILE_call(read_link_info, Handle, [Name]),
+ ?PRIM_FILE:read_link_info(Name),
#file_info{links = 1, type = regular} = Info1,
{ok, Info2} =
- ?PRIM_FILE_call(read_link_info, Handle, [Alias]),
+ ?PRIM_FILE:read_link_info(Alias),
#file_info{links=1, type=symlink} = Info2,
{ok, Name} =
- ?PRIM_FILE_call(read_link, Handle, [Alias]),
+ ?PRIM_FILE:read_link(Alias),
{ok, Name} =
- ?PRIM_FILE_call(read_link_all, Handle, [Alias]),
+ ?PRIM_FILE:read_link_all(Alias),
%% If all is good, delete dir again (avoid hanging dir on windows)
rm_rf(?PRIM_FILE,NewDir),
ok
@@ -1907,10 +1590,9 @@ list_dir_limit(Config) when is_list(Config) ->
RootDir = proplists:get_value(priv_dir, Config),
NewDir = filename:join(RootDir,
atom_to_list(?MODULE)++"_list_dir_limit"),
- {ok, Handle1} = ?PRIM_FILE:start(),
- ok = ?PRIM_FILE_call(make_dir, Handle1, [NewDir]),
+ ok = ?PRIM_FILE:make_dir(NewDir),
Ref = erlang:start_timer(MaxTime*1000, self(), []),
- Result = list_dir_limit_loop(NewDir, Handle1, Ref, MaxNumber, 0),
+ Result = list_dir_limit_loop(NewDir, Ref, MaxNumber, 0),
Time = case erlang:cancel_timer(Ref) of
false -> MaxTime;
T -> MaxTime - (T div 1000)
@@ -1920,21 +1602,18 @@ list_dir_limit(Config) when is_list(Config) ->
{error, _Reason, N} -> N;
_ -> 0
end,
- {ok, Handle2} = ?PRIM_FILE:start(),
- list_dir_limit_cleanup(NewDir, Handle2, Number, 0),
- ok = ?PRIM_FILE:stop(Handle1),
- ok = ?PRIM_FILE:stop(Handle2),
+ list_dir_limit_cleanup(NewDir, Number, 0),
{ok, Number} = Result,
{comment,
"Created " ++ integer_to_list(Number) ++ " files in "
++ integer_to_list(Time) ++ " seconds."}.
-list_dir_limit_loop(Dir, Handle, _Ref, N, Cnt) when Cnt >= N ->
- list_dir_check(Dir, Handle, Cnt);
-list_dir_limit_loop(Dir, Handle, Ref, N, Cnt) ->
+list_dir_limit_loop(Dir, _Ref, N, Cnt) when Cnt >= N ->
+ list_dir_check(Dir, Cnt);
+list_dir_limit_loop(Dir, Ref, N, Cnt) ->
receive
{timeout, Ref, []} ->
- list_dir_check(Dir, Handle, Cnt)
+ list_dir_check(Dir, Cnt)
after 0 ->
Name = integer_to_list(Cnt),
case ?PRIM_FILE:write_file(filename:join(Dir, Name), Name) of
@@ -1942,23 +1621,23 @@ list_dir_limit_loop(Dir, Handle, Ref, N, Cnt) ->
Next = Cnt + 1,
case Cnt rem 100 of
0 ->
- case list_dir_check(Dir, Handle, Next) of
+ case list_dir_check(Dir, Next) of
{ok, Next} ->
list_dir_limit_loop(
- Dir, Handle, Ref, N, Next);
+ Dir, Ref, N, Next);
Other ->
Other
end;
_ ->
- list_dir_limit_loop(Dir, Handle, Ref, N, Next)
+ list_dir_limit_loop(Dir, Ref, N, Next)
end;
{error, Reason} ->
{error, Reason, Cnt}
end
end.
-list_dir_check(Dir, Handle, Cnt) ->
- case ?PRIM_FILE:list_dir(Handle, Dir) of
+list_dir_check(Dir, Cnt) ->
+ case ?PRIM_FILE:list_dir(Dir) of
{ok, ListDir} ->
case length(ListDir) of
Cnt ->
@@ -1975,18 +1654,18 @@ list_dir_check(Dir, Handle, Cnt) ->
%% Deletes N files while ignoring errors, then continues deleting
%% as long as they exist.
-list_dir_limit_cleanup(Dir, Handle, N, Cnt) when Cnt >= N ->
+list_dir_limit_cleanup(Dir, N, Cnt) when Cnt >= N ->
Name = integer_to_list(Cnt),
- case ?PRIM_FILE:delete(Handle, filename:join(Dir, Name)) of
+ case ?PRIM_FILE:delete(filename:join(Dir, Name)) of
ok ->
- list_dir_limit_cleanup(Dir, Handle, N, Cnt+1);
+ list_dir_limit_cleanup(Dir, N, Cnt+1);
_ ->
ok
end;
-list_dir_limit_cleanup(Dir, Handle, N, Cnt) ->
+list_dir_limit_cleanup(Dir, N, Cnt) ->
Name = integer_to_list(Cnt),
- ?PRIM_FILE:delete(Handle, filename:join(Dir, Name)),
- list_dir_limit_cleanup(Dir, Handle, N, Cnt+1).
+ ?PRIM_FILE:delete(filename:join(Dir, Name)),
+ list_dir_limit_cleanup(Dir, N, Cnt+1).
%%%
%%% Test list_dir() on a non-existing pathname.
@@ -1995,7 +1674,7 @@ list_dir_limit_cleanup(Dir, Handle, N, Cnt) ->
list_dir_error(Config) ->
Priv = proplists:get_value(priv_dir, Config),
NonExisting = filename:join(Priv, "non-existing-dir"),
- {error,enoent} = prim_file:list_dir(NonExisting),
+ {error,enoent} = ?PRIM_FILE:list_dir(NonExisting),
ok.
%%%
@@ -2063,7 +1742,7 @@ do_run_large_file_test(Config, Run, Name0) ->
{'DOWN',Mref,_,_,_} -> ok;
{Tester,done} -> ok
end,
- prim_file:delete(Name)
+ ?PRIM_FILE:delete(Name)
end),
%% Run the test case.
diff --git a/lib/kernel/test/sendfile_SUITE.erl b/lib/kernel/test/sendfile_SUITE.erl
index bfa564c32c..0c0b1cbcb6 100644
--- a/lib/kernel/test/sendfile_SUITE.erl
+++ b/lib/kernel/test/sendfile_SUITE.erl
@@ -23,30 +23,41 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
--compile(export_all).
-
-all() -> [{group,async_threads},
- {group,no_async_threads}].
-
-groups() ->
- [{async_threads,[],tcs()},
- {no_async_threads,[],tcs()}].
-
-tcs() ->
- [t_sendfile_small
- ,t_sendfile_big_all
- ,t_sendfile_big_size
- ,t_sendfile_many_small
- ,t_sendfile_partial
- ,t_sendfile_offset
- ,t_sendfile_sendafter
- ,t_sendfile_recvafter
- ,t_sendfile_recvafter_remoteclose
- ,t_sendfile_sendduring
- ,t_sendfile_recvduring
- ,t_sendfile_closeduring
- ,t_sendfile_crashduring
- ].
+-export([all/0, init_per_suite/1, end_per_suite/1, init_per_testcase/2]).
+
+-export([sendfile_server/2, sendfile_do_recv/2, init/1, handle_event/2]).
+
+-export(
+ [t_sendfile_small/1,
+ t_sendfile_big_all/1,
+ t_sendfile_big_size/1,
+ t_sendfile_many_small/1,
+ t_sendfile_partial/1,
+ t_sendfile_offset/1,
+ t_sendfile_sendafter/1,
+ t_sendfile_recvafter/1,
+ t_sendfile_recvafter_remoteclose/1,
+ t_sendfile_sendduring/1,
+ t_sendfile_recvduring/1,
+ t_sendfile_closeduring/1,
+ t_sendfile_crashduring/1,
+ t_sendfile_arguments/1]).
+
+all() ->
+ [t_sendfile_small,
+ t_sendfile_big_all,
+ t_sendfile_big_size,
+ t_sendfile_many_small,
+ t_sendfile_partial,
+ t_sendfile_offset,
+ t_sendfile_sendafter,
+ t_sendfile_recvafter,
+ t_sendfile_recvafter_remoteclose,
+ t_sendfile_sendduring,
+ t_sendfile_recvduring,
+ t_sendfile_closeduring,
+ t_sendfile_crashduring,
+ t_sendfile_arguments].
init_per_suite(Config) ->
case {os:type(),os:version()} of
@@ -72,28 +83,18 @@ init_per_suite(Config) ->
end_per_suite(Config) ->
file:delete(proplists:get_value(big_file, Config)).
-init_per_group(async_threads,Config) ->
- case erlang:system_info(thread_pool_size) of
- 0 ->
- {skip,"No async threads"};
- _ ->
- [{sendfile_opts,[{use_threads,true}]}|Config]
- end;
-init_per_group(no_async_threads,Config) ->
- [{sendfile_opts,[{use_threads,false}]}|Config].
-
-end_per_group(_,_Config) ->
- ok.
-
init_per_testcase(TC,Config) when TC == t_sendfile_recvduring;
TC == t_sendfile_sendduring ->
Filename = proplists:get_value(small_file, Config),
Send = fun(Sock) ->
{_Size, Data} = sendfile_file_info(Filename),
- {ok,D} = file:open(Filename, [raw,binary,read]),
- prim_file:sendfile(D, Sock, 0, 0, 0,
- [],[],[]),
+ {ok,Fd} = file:open(Filename, [raw,binary,read]),
+ %% Determine whether the driver has native support by
+ %% hitting the raw module directly; file:sendfile/5 will
+ %% land in the fallback if it doesn't.
+ RawModule = Fd#file_descriptor.module,
+ {ok, _Ignored} = RawModule:sendfile(Fd,Sock,0,0,0,[],[],[]),
Data
end,
@@ -105,9 +106,8 @@ init_per_testcase(TC,Config) when TC == t_sendfile_recvduring;
ct:log("Error: ~p",[Error]),
{skip,"Not supported"}
end;
-init_per_testcase(_Tc,Config) ->
- Config ++ [{sendfile_opts,[{use_threads,false}]}].
-
+init_per_testcase(_TC,Config) ->
+ Config.
t_sendfile_small(Config) when is_list(Config) ->
Filename = proplists:get_value(small_file, Config),
@@ -124,7 +124,7 @@ t_sendfile_small(Config) when is_list(Config) ->
t_sendfile_many_small(Config) when is_list(Config) ->
Filename = proplists:get_value(small_file, Config),
FileOpts = proplists:get_value(file_opts, Config, []),
- SendfileOpts = proplists:get_value(sendfile_opts, Config),
+ SendfileOpts = proplists:get_value(sendfile_opts, Config, []),
error_logger:add_report_handler(?MODULE,[self()]),
@@ -151,7 +151,7 @@ t_sendfile_many_small(Config) when is_list(Config) ->
t_sendfile_big_all(Config) when is_list(Config) ->
Filename = proplists:get_value(big_file, Config),
- SendfileOpts = proplists:get_value(sendfile_opts, Config),
+ SendfileOpts = proplists:get_value(sendfile_opts, Config, []),
Send = fun(Sock) ->
{ok, #file_info{size = Size}} =
@@ -165,7 +165,7 @@ t_sendfile_big_all(Config) when is_list(Config) ->
t_sendfile_big_size(Config) ->
Filename = proplists:get_value(big_file, Config),
FileOpts = proplists:get_value(file_opts, Config, []),
- SendfileOpts = proplists:get_value(sendfile_opts, Config),
+ SendfileOpts = proplists:get_value(sendfile_opts, Config, []),
SendAll = fun(Sock) ->
{ok, #file_info{size = Size}} =
@@ -180,7 +180,7 @@ t_sendfile_big_size(Config) ->
t_sendfile_partial(Config) ->
Filename = proplists:get_value(small_file, Config),
FileOpts = proplists:get_value(file_opts, Config, []),
- SendfileOpts = proplists:get_value(sendfile_opts, Config),
+ SendfileOpts = proplists:get_value(sendfile_opts, Config, []),
SendSingle = fun(Sock) ->
{_Size, <<Data:5/binary,_/binary>>} =
@@ -217,7 +217,7 @@ t_sendfile_partial(Config) ->
t_sendfile_offset(Config) ->
Filename = proplists:get_value(small_file, Config),
FileOpts = proplists:get_value(file_opts, Config, []),
- SendfileOpts = proplists:get_value(sendfile_opts, Config),
+ SendfileOpts = proplists:get_value(sendfile_opts, Config, []),
Send = fun(Sock) ->
{_Size, <<_:5/binary,Data:3/binary,_/binary>> = AllData} =
@@ -233,7 +233,7 @@ t_sendfile_offset(Config) ->
t_sendfile_sendafter(Config) ->
Filename = proplists:get_value(small_file, Config),
- SendfileOpts = proplists:get_value(sendfile_opts, Config),
+ SendfileOpts = proplists:get_value(sendfile_opts, Config, []),
Send = fun(Sock) ->
{Size, Data} = sendfile_file_info(Filename),
@@ -246,7 +246,7 @@ t_sendfile_sendafter(Config) ->
t_sendfile_recvafter(Config) ->
Filename = proplists:get_value(small_file, Config),
- SendfileOpts = proplists:get_value(sendfile_opts, Config),
+ SendfileOpts = proplists:get_value(sendfile_opts, Config, []),
Send = fun(Sock) ->
{Size, Data} = sendfile_file_info(Filename),
@@ -279,7 +279,7 @@ t_sendfile_recvafter_remoteclose(Config) ->
t_sendfile_sendduring(Config) ->
Filename = proplists:get_value(big_file, Config),
- SendfileOpts = proplists:get_value(sendfile_opts, Config),
+ SendfileOpts = proplists:get_value(sendfile_opts, Config, []),
Send = fun(Sock) ->
{ok, #file_info{size = Size}} =
@@ -296,7 +296,7 @@ t_sendfile_sendduring(Config) ->
t_sendfile_recvduring(Config) ->
Filename = proplists:get_value(big_file, Config),
- SendfileOpts = proplists:get_value(sendfile_opts, Config),
+ SendfileOpts = proplists:get_value(sendfile_opts, Config, []),
Send = fun(Sock) ->
{ok, #file_info{size = Size}} =
@@ -315,7 +315,7 @@ t_sendfile_recvduring(Config) ->
t_sendfile_closeduring(Config) ->
Filename = proplists:get_value(big_file, Config),
- SendfileOpts = proplists:get_value(sendfile_opts, Config),
+ SendfileOpts = proplists:get_value(sendfile_opts, Config, []),
Send = fun(Sock,SFServPid) ->
spawn_link(fun() ->
@@ -345,7 +345,7 @@ t_sendfile_closeduring(Config) ->
t_sendfile_crashduring(Config) ->
Filename = proplists:get_value(big_file, Config),
- SendfileOpts = proplists:get_value(sendfile_opts, Config),
+ SendfileOpts = proplists:get_value(sendfile_opts, Config, []),
error_logger:add_report_handler(?MODULE,[self()]),
@@ -373,6 +373,36 @@ t_sendfile_crashduring(Config) ->
end
end.
+t_sendfile_arguments(Config) ->
+ Filename = proplists:get_value(small_file, Config),
+
+ {ok, Listener} = gen_tcp:listen(0,
+ [{packet, 0}, {active, false}, {reuseaddr, true}]),
+ {ok, Port} = inet:port(Listener),
+
+ ErrorCheck =
+ fun(Reason, Offset, Length, Opts) ->
+ {ok, Sender} = gen_tcp:connect({127, 0, 0, 1}, Port,
+ [{packet, 0}, {active, false}]),
+ {ok, Receiver} = gen_tcp:accept(Listener),
+ {ok, Fd} = file:open(Filename, [read, raw]),
+ {error, Reason} = file:sendfile(Fd, Sender, Offset, Length, Opts),
+ gen_tcp:close(Receiver),
+ gen_tcp:close(Sender),
+ file:close(Fd)
+ end,
+
+ ErrorCheck(einval, -1, 0, []),
+ ErrorCheck(einval, 0, -1, []),
+ ErrorCheck(badarg, gurka, 0, []),
+ ErrorCheck(badarg, 0, gurka, []),
+ ErrorCheck(badarg, 0, 0, gurka),
+ ErrorCheck(badarg, 0, 0, [{chunk_size, gurka}]),
+
+ gen_tcp:close(Listener),
+
+ ok.
+
%% Generic sendfile server code
sendfile_send(Send) ->
sendfile_send({127,0,0,1},Send).
diff --git a/lib/mnesia/src/mnesia.erl b/lib/mnesia/src/mnesia.erl
index 190fb2b56d..95f7d4afc1 100644
--- a/lib/mnesia/src/mnesia.erl
+++ b/lib/mnesia/src/mnesia.erl
@@ -151,7 +151,8 @@
{'snmp', SnmpStruct::term()} |
{'storage_properties', [{Backend::module(), [BackendProp::_]}]} |
{'type', 'set' | 'ordered_set' | 'bag'} |
- {'local_content', boolean()}.
+ {'local_content', boolean()} |
+ {'user_properties', proplists:proplist()}.
-type t_result(Res) :: {'atomic', Res} | {'aborted', Reason::term()}.
-type activity() :: 'ets' | 'async_dirty' | 'sync_dirty' | 'transaction' | 'sync_transaction' |
diff --git a/lib/mnesia/src/mnesia_log.erl b/lib/mnesia/src/mnesia_log.erl
index 55b1d6e419..a2de23a2a3 100644
--- a/lib/mnesia/src/mnesia_log.erl
+++ b/lib/mnesia/src/mnesia_log.erl
@@ -752,8 +752,8 @@ abort_write(B, What, Args, Reason) ->
Opaque = B#backup_args.opaque,
dbg_out("Failed to perform backup. M=~p:F=~tp:A=~tp -> ~tp~n",
[Mod, What, Args, Reason]),
- try apply(Mod, abort_write, [Opaque]) of
- {ok, _Res} -> throw({error, Reason})
+ try {ok, _Res} = apply(Mod, abort_write, [Opaque]) of
+ _ -> throw({error, Reason})
catch _:Other ->
error("Failed to abort backup. ~p:~tp~tp -> ~tp~n",
[Mod, abort_write, [Opaque], Other]),
diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml
index 5230cef496..dea35bc390 100644
--- a/lib/public_key/doc/src/public_key.xml
+++ b/lib/public_key/doc/src/public_key.xml
@@ -774,6 +774,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
<func>
<name>pkix_test_data(Options) -> Config </name>
+ <name>pkix_test_data([chain_opts()]) -> [conf_opt()]</name>
<fsummary>Creates certificate test data.</fsummary>
<type>
<v>Options = #{chain_type() := chain_opts()} </v>
@@ -781,30 +782,83 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
<v>chain_type() = server_chain | client_chain </v>
- <v>chain_opts() = #{chain_end() := [cert_opt()],
- intermediates => [[cert_opt()]]}</v>
- <d>A valid chain must have at least a ROOT and a peer cert</d>
-
- <v>chain_end() = root | peer </v>
-
+ <v>chain_opts() = #{root := [cert_opt()] | root_cert(),
+ peer := [cert_opt()],
+ intermediates => [[cert_opt()]]}</v>
+ <d>
+ A valid chain must have at least a ROOT and a peer cert.
+ The root cert can be given either as a cert pre-generated by
+ <seealso marker="#pkix_test_root_cert-2">
+ pkix_test_root_cert/2
+ </seealso>, or as root cert generation options.
+ </d>
+ <v>root_cert() = #{cert := der_encoded(), key := Key}</v>
+ <d>
+ A root certificate generated by
+ <seealso marker="#pkix_test_root_cert-2">
+ pkix_test_root_cert/2
+ </seealso>.
+ </d>
<v>cert_opt() = {Key, Value}</v>
<d>For available options see <seealso marker="#cert_opt"> cert_opt()</seealso> below.</d>
<v>Config = #{server_config := [conf_opt()],
client_config := [conf_opt()]}</v>
- <v>conf_opt() = {cert, der_encoded()} | {key, der_encoded()} |{cacerts, [der_encoded()]}</v>
- <d>This is a subset of the type <seealso marker="ssl:ssl#type-ssloption"> ssl:ssl_option()</seealso> </d>
+ <v>conf_opt() = {cert, der_encoded()} | {key, PrivateKey} |{cacerts, [der_encoded()]}</v>
+ <d>
+ This is a subset of the type
+ <seealso marker="ssl:ssl#type-ssloption"> ssl:ssl_option()</seealso>.
+ <c>PrivateKey</c> is what
+ <seealso marker="#generate_key-1">generate_key/1</seealso>
+ returns.
+ </d>
</type>
<desc>
- <p>Creates certificate test data to facilitate automated testing
- of applications using X509-certificates often through
- SSL/TLS. The test data can be used when you have control
- over both the client and the server in a test scenario.
+ <p>
+ Creates certificate configuration(s) consisting of certificate
+ and its private key plus CA certificate bundle, for a client
+ and a server, intended to facilitate automated testing
+ of applications using X509-certificates,
+ often through SSL/TLS. The test data can be used
+ when you have control over both the client and the server
+ in a test scenario.
+ </p>
+ <p>
+ When this function is called with a map containing
+ client and server chain specifications;
+ it generates both a client and a server certificate chain
+ where the <c>cacerts</c>
+ returned for the server contains the root cert the server
+ should trust and the intermediate certificates the server
+ should present to connecting clients.
+ The root cert the server should trust is the one used
+ as root of the client certificate chain.
+ Vice versa applies to the <c>cacerts</c> returned for the client.
+ The root cert(s) can either be pre-generated with
+ <seealso marker="#pkix_test_root_cert-2">
+ pkix_test_root_cert/2
+ </seealso>, or if options are specified; it is (they are)
+ generated.
+ </p>
+ <p>
+ When this function is called with a list of certificate options;
+ it generates a configuration with just one node certificate
+ where <c>cacerts</c> contains the root cert
+ and the intermediate certs that should be presented to a peer.
+ In this case the same root cert must be used for all peers.
+ This is useful in for example an Erlang distributed cluster
+ where any node, towards another node, acts either
+ as a server or as a client depending on who connects to whom.
+ The generated certificate contains a subject altname,
+ which is not needed in a client certificate,
+ but makes the certificate useful for both roles.
+ </p>
+ <p>
+ The <marker id="cert_opt"/><c>cert_opt()</c>
+ type consists of the following options:
</p>
-
- <p> The <marker id="cert_opt"/> cert_opt() type consists of the following options: </p>
<taglist>
<tag> {digest, digest_type()}</tag>
<item><p>Hash algorithm to be used for
@@ -851,6 +905,36 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</desc>
</func>
+ <func>
+ <name>pkix_test_root_cert(Name, Options) -> RootCert</name>
+ <fsummary>Generates a test data root cert.</fsummary>
+ <type>
+ <v>Name = string()</v>
+ <d>The root certificate name.</d>
+ <v>Options = [cert_opt()]</v>
+ <d>
+ For available options see
+ <seealso marker="#cert_opt">cert_opt()</seealso>
+ under
+ <seealso marker="#pkix_test_data-1">pkix_test_data/1</seealso>.
+ </d>
+ <v>RootCert = #{cert := der_encoded(), key := Key}</v>
+ <d>
+ A root certificate and key. The <c>Key</c> is generated by
+ <seealso marker="#generate_key-1">generate_key/1</seealso>.
+ </d>
+ </type>
+ <desc>
+ <p>
+ Generates a root certificate that can be used
+ in multiple calls to
+ <seealso marker="#pkix_test_data-1">pkix_test_data/1</seealso>
+ when you want the same root certificate for
+ several generated certificates.
+ </p>
+ </desc>
+ </func>
+
<func>
<name>pkix_verify(Cert, Key) -> boolean()</name>
<fsummary>Verifies PKIX x.509 certificate signature.</fsummary>
diff --git a/lib/public_key/src/pubkey_cert.erl b/lib/public_key/src/pubkey_cert.erl
index 76fd0f8133..c433a96585 100644
--- a/lib/public_key/src/pubkey_cert.erl
+++ b/lib/public_key/src/pubkey_cert.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-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.
@@ -33,11 +33,12 @@
is_fixed_dh_cert/1, verify_data/1, verify_fun/4,
select_extension/2, match_name/3,
extensions_list/1, cert_auth_key_id/1, time_str_2_gregorian_sec/1,
- gen_test_certs/1]).
+ gen_test_certs/1, root_cert/2]).
-define(NULL, 0).
--export_type([chain_opts/0, test_config/0]).
+-export_type([cert_opt/0, chain_opts/0, conf_opt/0,
+ test_config/0, test_root_cert/0]).
-type cert_opt() :: {digest, public_key:digest_type()} |
{key, public_key:key_params() | public_key:private_key()} |
@@ -46,9 +47,12 @@
-type chain_end() :: root | peer.
-type chain_opts() :: #{chain_end() := [cert_opt()], intermediates => [[cert_opt()]]}.
-type conf_opt() :: {cert, public_key:der_encoded()} |
- {key, public_key:der_encoded()} |
+ {key, public_key:private_key()} |
{cacerts, [public_key:der_encoded()]}.
--type test_config() :: #{server_config := [conf_opt()], client_config := [conf_opt()]}.
+-type test_config() ::
+ #{server_config := [conf_opt()], client_config := [conf_opt()]}.
+-type test_root_cert() ::
+ #{cert := binary(), key := public_key:private_key()}.
%%====================================================================
%% Internal application APIu
%%====================================================================
@@ -430,31 +434,94 @@ match_name(Fun, Name, PermittedName, [Head | Tail]) ->
false ->
match_name(Fun, Name, Head, Tail)
end.
+
%%%
--spec gen_test_certs(#{server_chain:= chain_opts(), client_chain:= chain_opts()}) -> test_config().
-
-%% Generates server and and client configuration for testing
+-spec gen_test_certs(#{server_chain:= chain_opts(),
+ client_chain:= chain_opts()} |
+ chain_opts()) ->
+ test_config() |
+ [conf_opt()].
+%%
+%% Generates server and and client configuration for testing
%% purposes. All certificate options have default values
-gen_test_certs(#{client_chain := #{root := ClientRootConf,
- intermediates := ClientCAs,
- peer := ClientPeer},
- server_chain :=
- #{root := ServerRootConf,
- intermediates := ServerCAs,
- peer := ServerPeer}}) ->
- SRootKey = gen_key(proplists:get_value(key, ServerRootConf, default_key_gen())),
- CRootKey = gen_key(proplists:get_value(key, ClientRootConf, default_key_gen())),
- ServerRoot = root_cert("server", SRootKey, ClientRootConf),
- ClientRoot = root_cert("client", CRootKey, ServerRootConf),
-
- [{ServerDERCert, ServerDERKey} | ServerCAsKeys] = config(server, ServerRoot,
- SRootKey, lists:reverse([ServerPeer | lists:reverse(ServerCAs)])),
- [{ClientDERCert, ClientDERKey} | ClientCAsKeys] = config(client, ClientRoot,
- CRootKey, lists:reverse([ClientPeer | lists:reverse(ClientCAs)])),
- ServerDERCA = ca_config(ClientRoot, ServerCAsKeys),
- ClientDERCA = ca_config(ServerRoot, ClientCAsKeys),
- #{server_config => [{cert, ServerDERCert}, {key, ServerDERKey}, {cacerts, ServerDERCA}],
- client_config => [{cert, ClientDERCert}, {key, ClientDERKey}, {cacerts, ClientDERCA}]}.
+gen_test_certs(
+ #{client_chain :=
+ #{root := ClientRoot,
+ intermediates := ClientCAs,
+ peer := ClientPeer},
+ server_chain :=
+ #{root := ServerRoot,
+ intermediates := ServerCAs,
+ peer := ServerPeer}}) ->
+ #{cert := ServerRootCert, key := ServerRootKey} =
+ case ServerRoot of
+ #{} ->
+ ServerRoot;
+ ServerRootConf when is_list(ServerRootConf) ->
+ root_cert("SERVER ROOT CA", ServerRootConf)
+ end,
+ #{cert := ClientRootCert, key := ClientRootKey} =
+ case ClientRoot of
+ #{} ->
+ ClientRoot;
+ ClientRootConf when is_list(ClientRootConf) ->
+ root_cert("CLIENT ROOT CA", ClientRootConf)
+ end,
+ [{ServerDERCert, ServerDERKey} | ServerCAsKeys] =
+ config(
+ server, ServerRootCert, ServerRootKey,
+ lists:reverse([ServerPeer | lists:reverse(ServerCAs)])),
+ [{ClientDERCert, ClientDERKey} | ClientCAsKeys] =
+ config(
+ client, ClientRootCert, ClientRootKey,
+ lists:reverse([ClientPeer | lists:reverse(ClientCAs)])),
+ ServerDERCA = ca_config(ClientRootCert, ServerCAsKeys),
+ ClientDERCA = ca_config(ServerRootCert, ClientCAsKeys),
+ #{server_config =>
+ [{cert, ServerDERCert}, {key, ServerDERKey},
+ {cacerts, ServerDERCA}],
+ client_config =>
+ [{cert, ClientDERCert}, {key, ClientDERKey},
+ {cacerts, ClientDERCA}]};
+%%
+%% Generates a node configuration for testing purposes,
+%% when using the node server cert also for the client.
+%% All certificate options have default values
+gen_test_certs(
+ #{root := Root, intermediates := CAs, peer := Peer}) ->
+ #{cert := RootCert, key := RootKey} =
+ case Root of
+ #{} ->
+ Root;
+ RootConf when is_list(RootConf) ->
+ root_cert("SERVER ROOT CA", RootConf)
+ end,
+ [{DERCert, DERKey} | CAsKeys] =
+ config(
+ server, RootCert, RootKey,
+ lists:reverse([Peer | lists:reverse(CAs)])),
+ DERCAs = ca_config(RootCert, CAsKeys),
+ [{cert, DERCert}, {key, DERKey}, {cacerts, DERCAs}].
+
+%%%
+-spec root_cert(string(), [cert_opt()]) -> test_root_cert().
+%%
+%% Generate a self-signed root cert
+root_cert(Name, Opts) ->
+ PrivKey = gen_key(proplists:get_value(key, Opts, default_key_gen())),
+ TBS = cert_template(),
+ Issuer = subject("root", Name),
+ OTPTBS =
+ TBS#'OTPTBSCertificate'{
+ signature = sign_algorithm(PrivKey, Opts),
+ issuer = Issuer,
+ validity = validity(Opts),
+ subject = Issuer,
+ subjectPublicKeyInfo = public_key(PrivKey),
+ extensions = extensions(undefined, ca, Opts)
+ },
+ #{cert => public_key:pkix_sign(OTPTBS, PrivKey),
+ key => PrivKey}.
%%--------------------------------------------------------------------
%%% Internal functions
@@ -1103,7 +1170,7 @@ missing_basic_constraints(OtpCert, SelfSigned, ValidationState, VerifyFun, UserS
UserState}
end.
- gen_key(KeyGen) ->
+gen_key(KeyGen) ->
case is_key(KeyGen) of
true ->
KeyGen;
@@ -1120,28 +1187,14 @@ is_key(#'ECPrivateKey'{}) ->
is_key(_) ->
false.
-root_cert(Role, PrivKey, Opts) ->
- TBS = cert_template(),
- Issuer = issuer("root", Role, " ROOT CA"),
- OTPTBS = TBS#'OTPTBSCertificate'{
- signature = sign_algorithm(PrivKey, Opts),
- issuer = Issuer,
- validity = validity(Opts),
- subject = Issuer,
- subjectPublicKeyInfo = public_key(PrivKey),
- extensions = extensions(Role, ca, Opts)
- },
- public_key:pkix_sign(OTPTBS, PrivKey).
cert_template() ->
#'OTPTBSCertificate'{
version = v3,
- serialNumber = trunc(rand:uniform()*100000000)*10000 + 1,
+ serialNumber = erlang:unique_integer([positive, monotonic]),
issuerUniqueID = asn1_NOVALUE,
subjectUniqueID = asn1_NOVALUE
}.
-issuer(Contact, Role, Name) ->
- subject(Contact, Role ++ Name).
subject(Contact, Name) ->
Opts = [{email, Contact ++ "@example.org"},
@@ -1176,9 +1229,11 @@ validity(Opts) ->
DefFrom0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())-1),
DefTo0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())+7),
{DefFrom, DefTo} = proplists:get_value(validity, Opts, {DefFrom0, DefTo0}),
- Format = fun({Y,M,D}) ->
- lists:flatten(io_lib:format("~w~2..0w~2..0w000000Z",[Y,M,D]))
- end,
+ Format =
+ fun({Y,M,D}) ->
+ lists:flatten(
+ io_lib:format("~4..0w~2..0w~2..0w000000Z",[Y,M,D]))
+ end,
#'Validity'{notBefore={generalTime, Format(DefFrom)},
notAfter ={generalTime, Format(DefTo)}}.
@@ -1240,7 +1295,6 @@ cert(Role, #'OTPCertificate'{tbsCertificate = #'OTPTBSCertificate'{subject = Iss
subject = subject(Contact, atom_to_list(Role) ++ Name),
subjectPublicKeyInfo = public_key(Key),
extensions = extensions(Role, Type, Opts)
-
},
public_key:pkix_sign(OTPTBS, PrivKey).
@@ -1297,7 +1351,7 @@ add_default_extensions(server, peer, Exts) ->
],
add_default_extensions(Default, Exts);
-add_default_extensions(_, peer, Exts) ->
+add_default_extensions(client, peer, Exts) ->
Exts.
add_default_extensions(Defaults0, Exts) ->
diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl
index 6788c1ee92..034126655c 100644
--- a/lib/public_key/src/public_key.erl
+++ b/lib/public_key/src/public_key.erl
@@ -59,7 +59,8 @@
pkix_crl_verify/2,
pkix_crl_issuer/1,
short_name_hash/1,
- pkix_test_data/1
+ pkix_test_data/1,
+ pkix_test_root_cert/2
]).
-export_type([public_key/0, private_key/0, pem_entry/0,
@@ -1033,10 +1034,12 @@ short_name_hash({rdnSequence, _Attributes} = Name) ->
%%--------------------------------------------------------------------
--spec pkix_test_data(#{chain_type() := pubkey_cert:chain_opts()}) ->
- pubkey_cert:test_config().
+-spec pkix_test_data(#{chain_type() := pubkey_cert:chain_opts()} |
+ pubkey_cert:chain_opts()) ->
+ pubkey_cert:test_config() |
+ [pubkey_cert:conf_opt()].
-%% Description: Generates OpenSSL-style hash of a name.
+%% Description: Generates cert(s) and ssl configuration
%%--------------------------------------------------------------------
pkix_test_data(#{client_chain := ClientChain0,
@@ -1045,7 +1048,21 @@ pkix_test_data(#{client_chain := ClientChain0,
ClientChain = maps:merge(Default, ClientChain0),
ServerChain = maps:merge(Default, ServerChain0),
pubkey_cert:gen_test_certs(#{client_chain => ClientChain,
- server_chain => ServerChain}).
+ server_chain => ServerChain});
+pkix_test_data(#{} = Chain) ->
+ Default = #{intermediates => []},
+ pubkey_cert:gen_test_certs(maps:merge(Default, Chain)).
+
+%%--------------------------------------------------------------------
+-spec pkix_test_root_cert(
+ Name :: string(), Opts :: [pubkey_cert:cert_opt()]) ->
+ pubkey_cert:test_root_cert().
+
+%% Description: Generates a root cert suitable for pkix_test_data/1
+%%--------------------------------------------------------------------
+
+pkix_test_root_cert(Name, Opts) ->
+ pubkey_cert:root_cert(Name, Opts).
%%--------------------------------------------------------------------
%%% Internal functions
diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl
index 9e5e288a1a..449d1fc040 100644
--- a/lib/public_key/test/public_key_SUITE.erl
+++ b/lib/public_key/test/public_key_SUITE.erl
@@ -64,7 +64,9 @@ all() ->
groups() ->
[{pem_decode_encode, [], [dsa_pem, rsa_pem, ec_pem, encrypted_pem,
dh_pem, cert_pem, pkcs7_pem, pkcs10_pem, ec_pem2,
- ec_pem_encode_generated, gen_ec_param]},
+ ec_pem_encode_generated,
+ gen_ec_param_prime_field, gen_ec_param_char_2_field
+ ]},
{ssh_public_key_decode_encode, [],
[ssh_rsa_public_key, ssh_dsa_public_key, ssh_ecdsa_public_key,
ssh_rfc4716_rsa_comment, ssh_rfc4716_dsa_comment,
@@ -105,18 +107,11 @@ init_per_testcase(pkix_test_data_all_default, Config) ->
init_common_per_testcase(Config)
end;
-init_per_testcase(gen_ec_param, Config) ->
- case crypto:ec_curves() of
- [] ->
- {skip, missing_ecc_support};
- Curves ->
- case lists:member(secp521r1, Curves) of
- true ->
- init_common_per_testcase(Config);
- false ->
- {skip, missing_ecc_secp52r1_support}
- end
- end;
+init_per_testcase(gen_ec_param_prime_field=TC, Config) ->
+ init_per_testcase_gen_ec_param(TC, secp521r1, Config);
+
+init_per_testcase(gen_ec_param_char_2_field=TC, Config) ->
+ init_per_testcase_gen_ec_param(TC, sect571r1, Config);
init_per_testcase(TestCase, Config) ->
case TestCase of
@@ -1218,12 +1213,19 @@ short_crl_issuer_hash(Config) when is_list(Config) ->
Issuer = public_key:pkix_crl_issuer(CrlDER),
CrlIssuerHash = public_key:short_name_hash(Issuer).
+
+%%--------------------------------------------------------------------
+gen_ec_param_prime_field() ->
+ [{doc, "Generate key with EC prime_field parameters"}].
+gen_ec_param_prime_field(Config) when is_list(Config) ->
+ Datadir = proplists:get_value(data_dir, Config),
+ do_gen_ec_param(filename:join(Datadir, "ec_key_param0.pem")).
+
%%--------------------------------------------------------------------
-gen_ec_param() ->
- [{doc, "Generate key with EC parameters"}].
-gen_ec_param(Config) when is_list(Config) ->
+gen_ec_param_char_2_field() ->
+ [{doc, "Generate key with EC characteristic_two_field parameters"}].
+gen_ec_param_char_2_field(Config) when is_list(Config) ->
Datadir = proplists:get_value(data_dir, Config),
- do_gen_ec_param(filename:join(Datadir, "ec_key_param0.pem")),
do_gen_ec_param(filename:join(Datadir, "ec_key_param1.pem")).
%%--------------------------------------------------------------------
@@ -1310,6 +1312,30 @@ do_gen_ec_param(File) ->
ct:fail({key_gen_fail, File})
end.
+init_per_testcase_gen_ec_param(TC, Curve, Config) ->
+ case crypto:ec_curves() of
+ [] ->
+ {skip, missing_ec_support};
+ Curves ->
+ case lists:member(Curve, Curves)
+ andalso crypto_supported_curve(Curve, Curves)
+ of
+ true ->
+ init_common_per_testcase(Config);
+ false ->
+ {skip, {missing_ec_support, Curve}}
+ end
+ end.
+
+
+crypto_supported_curve(Curve, Curves) ->
+ try crypto:generate_key(ecdh, Curve) of
+ {error,_} -> false; % Just in case crypto is changed in the future...
+ _-> true
+ catch
+ _:_-> false
+ end.
+
incorrect_countryname_pkix_cert() ->
<<48,130,5,186,48,130,4,162,160,3,2,1,2,2,7,7,250,61,63,6,140,137,48,13,6,9,42, 134,72,134,247,13,1,1,5,5,0,48,129,220,49,11,48,9,6,3,85,4,6,19,2,85,83,49, 16,48,14,6,3,85,4,8,19,7,65,114,105,122,111,110,97,49,19,48,17,6,3,85,4,7,19, 10,83,99,111,116,116,115,100,97,108,101,49,37,48,35,6,3,85,4,10,19,28,83,116, 97,114,102,105,101,108,100,32,84,101,99,104,110,111,108,111,103,105,101,115, 44,32,73,110,99,46,49,57,48,55,6,3,85,4,11,19,48,104,116,116,112,58,47,47,99, 101,114,116,105,102,105,99,97,116,101,115,46,115,116,97,114,102,105,101,108, 100,116,101,99,104,46,99,111,109,47,114,101,112,111,115,105,116,111,114,121, 49,49,48,47,6,3,85,4,3,19,40,83,116,97,114,102,105,101,108,100,32,83,101,99, 117,114,101,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117, 116,104,111,114,105,116,121,49,17,48,15,6,3,85,4,5,19,8,49,48,54,56,56,52,51, 53,48,30,23,13,49,48,49,48,50,51,48,49,51,50,48,53,90,23,13,49,50,49,48,50, 51,48,49,51,50,48,53,90,48,122,49,11,48,9,6,3,85,4,6,12,2,85,83,49,11,48,9,6, 3,85,4,8,12,2,65,90,49,19,48,17,6,3,85,4,7,12,10,83,99,111,116,116,115,100, 97,108,101,49,38,48,36,6,3,85,4,10,12,29,83,112,101,99,105,97,108,32,68,111, 109,97,105,110,32,83,101,114,118,105,99,101,115,44,32,73,110,99,46,49,33,48, 31,6,3,85,4,3,12,24,42,46,108,111,103,105,110,46,115,101,99,117,114,101,115, 101,114,118,101,114,46,110,101,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,185,136,240,80,141,36,124, 245,182,130,73,19,188,74,166,117,72,228,185,209,43,129,244,40,44,193,231,11, 209,12,234,88,43,142,1,162,48,122,17,95,230,105,171,131,12,147,46,204,36,80, 250,171,33,253,35,62,83,22,71,212,186,141,14,198,89,89,121,204,224,122,246, 127,110,188,229,162,67,95,6,74,231,127,99,131,7,240,85,102,203,251,50,58,58, 104,245,103,181,183,134,32,203,121,232,54,32,188,139,136,112,166,126,14,91, 223,153,172,164,14,61,38,163,208,215,186,210,136,213,143,70,147,173,109,217, 250,169,108,31,211,104,238,103,93,182,59,165,43,196,189,218,241,30,148,240, 109,90,69,176,194,52,116,173,151,135,239,10,209,179,129,192,102,75,11,25,168, 223,32,174,84,223,134,70,167,55,172,143,27,130,123,226,226,7,34,142,166,39, 48,246,96,231,150,84,220,106,133,193,55,95,159,227,24,249,64,36,1,142,171,16, 202,55,126,7,156,15,194,22,116,53,113,174,104,239,203,120,45,131,57,87,84, 163,184,27,83,57,199,91,200,34,43,98,61,180,144,76,65,170,177,2,3,1,0,1,163, 130,1,224,48,130,1,220,48,15,6,3,85,29,19,1,1,255,4,5,48,3,1,1,0,48,29,6,3, 85,29,37,4,22,48,20,6,8,43,6,1,5,5,7,3,1,6,8,43,6,1,5,5,7,3,2,48,14,6,3,85, 29,15,1,1,255,4,4,3,2,5,160,48,56,6,3,85,29,31,4,49,48,47,48,45,160,43,160, 41,134,39,104,116,116,112,58,47,47,99,114,108,46,115,116,97,114,102,105,101, 108,100,116,101,99,104,46,99,111,109,47,115,102,115,50,45,48,46,99,114,108, 48,83,6,3,85,29,32,4,76,48,74,48,72,6,11,96,134,72,1,134,253,110,1,7,23,2,48, 57,48,55,6,8,43,6,1,5,5,7,2,1,22,43,104,116,116,112,115,58,47,47,99,101,114, 116,115,46,115,116,97,114,102,105,101,108,100,116,101,99,104,46,99,111,109, 47,114,101,112,111,115,105,116,111,114,121,47,48,129,141,6,8,43,6,1,5,5,7,1, 1,4,129,128,48,126,48,42,6,8,43,6,1,5,5,7,48,1,134,30,104,116,116,112,58,47, 47,111,99,115,112,46,115,116,97,114,102,105,101,108,100,116,101,99,104,46,99, 111,109,47,48,80,6,8,43,6,1,5,5,7,48,2,134,68,104,116,116,112,58,47,47,99, 101,114,116,105,102,105,99,97,116,101,115,46,115,116,97,114,102,105,101,108, 100,116,101,99,104,46,99,111,109,47,114,101,112,111,115,105,116,111,114,121, 47,115,102,95,105,110,116,101,114,109,101,100,105,97,116,101,46,99,114,116, 48,31,6,3,85,29,35,4,24,48,22,128,20,73,75,82,39,209,27,188,242,161,33,106, 98,123,81,66,122,138,215,213,86,48,59,6,3,85,29,17,4,52,48,50,130,24,42,46, 108,111,103,105,110,46,115,101,99,117,114,101,115,101,114,118,101,114,46,110, 101,116,130,22,108,111,103,105,110,46,115,101,99,117,114,101,115,101,114,118, 101,114,46,110,101,116,48,29,6,3,85,29,14,4,22,4,20,138,233,191,208,157,203, 249,85,242,239,20,195,48,10,148,49,144,101,255,116,48,13,6,9,42,134,72,134, 247,13,1,1,5,5,0,3,130,1,1,0,82,31,121,162,49,50,143,26,167,202,143,61,71, 189,201,199,57,81,122,116,90,192,88,24,102,194,174,48,157,74,27,87,210,223, 253,93,3,91,150,109,120,1,110,27,11,200,198,141,222,246,14,200,71,105,41,138, 13,114,122,106,63,17,197,181,234,121,61,89,74,65,41,231,248,219,129,83,176, 219,55,107,55,211,112,98,38,49,69,77,96,221,108,123,152,12,210,159,157,141, 43,226,55,187,129,3,82,49,136,66,81,196,91,234,196,10,82,48,6,80,163,83,71, 127,102,177,93,209,129,26,104,2,84,24,255,248,161,3,244,169,234,92,122,110, 43,4,17,113,185,235,108,219,210,236,132,216,177,227,17,169,58,162,159,182, 162,93,160,229,200,9,163,229,110,121,240,168,232,14,91,214,188,196,109,210, 164,222,0,109,139,132,113,91,16,118,173,178,176,80,132,34,41,199,51,206,250, 224,132,60,115,192,94,107,163,219,212,226,225,65,169,148,108,213,46,174,173, 103,110,189,229,166,149,254,31,51,44,144,108,187,182,11,251,201,206,86,138, 208,59,51,86,132,235,81,225,88,34,190,8,184>>.
diff --git a/lib/runtime_tools/doc/src/LTTng.xml b/lib/runtime_tools/doc/src/LTTng.xml
index 392d54857c..93937b3fdc 100644
--- a/lib/runtime_tools/doc/src/LTTng.xml
+++ b/lib/runtime_tools/doc/src/LTTng.xml
@@ -134,7 +134,7 @@ $ make </code>
<p><em>port_open</em></p>
<list type="bulleted">
<item><c>pid : string</c> :: Process ID. Ex. <c>"&lt;0.131.0&gt;"</c></item>
- <item><c>driver : string</c> :: Driver name. Ex. <c>"efile"</c></item>
+ <item><c>driver : string</c> :: Driver name. Ex. <c>"tcp_inet"</c></item>
<item><c>port : string</c> :: Port ID. Ex. <c>"#Port&lt;0.1031&gt;"</c></item>
</list>
<p>
@@ -323,7 +323,7 @@ $ make </code>
<p><em>driver_init</em></p>
<list type="bulleted">
- <item><c>driver : string</c> :: Driver name. Ex. <c>"efile"</c></item>
+ <item><c>driver : string</c> :: Driver name. Ex. <c>"tcp_inet"</c></item>
<item><c>major : integer</c> :: Major version. Ex. <c>3</c></item>
<item><c>minor : integer</c> :: Minor version. Ex. <c>1</c></item>
<item><c>flags : integer</c> :: Flags. Ex. <c>1</c></item>
@@ -334,7 +334,7 @@ $ make </code>
<p><em>driver_start</em></p>
<list type="bulleted">
<item><c>pid : string</c> :: Process ID. Ex. <c>"&lt;0.131.0&gt;"</c></item>
- <item><c>driver : string</c> :: Driver name. Ex. <c>"efile"</c></item>
+ <item><c>driver : string</c> :: Driver name. Ex. <c>"tcp_inet"</c></item>
<item><c>port : string</c> :: Port ID. Ex. <c>"#Port&lt;0.1031&gt;"</c></item>
</list>
<p>Example:</p>
@@ -344,7 +344,7 @@ $ make </code>
<list type="bulleted">
<item><c>pid : string</c> :: Process ID. Ex. <c>"&lt;0.131.0&gt;"</c></item>
<item><c>port : string</c> :: Port ID. Ex. <c>"#Port&lt;0.1031&gt;"</c></item>
- <item><c>driver : string</c> :: Driver name. Ex. <c>"efile"</c></item>
+ <item><c>driver : string</c> :: Driver name. Ex. <c>"tcp_inet"</c></item>
<item><c>bytes : integer</c> :: Size of data returned. Ex. <c>82</c></item>
</list>
<p>Example:</p>
@@ -354,7 +354,7 @@ $ make </code>
<list type="bulleted">
<item><c>pid : string</c> :: Process ID. Ex. <c>"&lt;0.131.0&gt;"</c></item>
<item><c>port : string</c> :: Port ID. Ex. <c>"#Port&lt;0.1031&gt;"</c></item>
- <item><c>driver : string</c> :: Driver name. Ex. <c>"efile"</c></item>
+ <item><c>driver : string</c> :: Driver name. Ex. <c>"tcp_inet"</c></item>
<item><c>bytes : integer</c> :: Size of data returned. Ex. <c>82</c></item>
</list>
<p>Example:</p>
@@ -364,7 +364,7 @@ $ make </code>
<list type="bulleted">
<item><c>pid : string</c> :: Process ID. Ex. <c>"&lt;0.131.0&gt;"</c></item>
<item><c>port : string</c> :: Port ID. Ex. <c>"#Port&lt;0.1031&gt;"</c></item>
- <item><c>driver : string</c> :: Driver name. Ex. <c>"efile"</c></item>
+ <item><c>driver : string</c> :: Driver name. Ex. <c>"tcp_inet"</c></item>
</list>
<p>Example:</p>
<code type="none">driver_ready_input: { cpu_id = 5 }, { pid = "&lt;0.189.0&gt;", port = "#Port&lt;0.3637&gt;", driver = "inet_gethost 4 " }</code>
@@ -373,7 +373,7 @@ $ make </code>
<list type="bulleted">
<item><c>pid : string</c> :: Process ID. Ex. <c>"&lt;0.131.0&gt;"</c></item>
<item><c>port : string</c> :: Port ID. Ex. <c>"#Port&lt;0.1031&gt;"</c></item>
- <item><c>driver : string</c> :: Driver name. Ex. <c>"efile"</c></item>
+ <item><c>driver : string</c> :: Driver name. Ex. <c>"tcp_inet"</c></item>
</list>
<p>Example:</p>
<code type="none">driver_ready_output: { cpu_id = 5 }, { pid = "&lt;0.194.0&gt;", port = "#Port&lt;0.3663&gt;", driver = "tcp_inet" }</code>
@@ -382,14 +382,14 @@ $ make </code>
<list type="bulleted">
<item><c>pid : string</c> :: Process ID. Ex. <c>"&lt;0.131.0&gt;"</c></item>
<item><c>port : string</c> :: Port ID. Ex. <c>"#Port&lt;0.1031&gt;"</c></item>
- <item><c>driver : string</c> :: Driver name. Ex. <c>"efile"</c></item>
+ <item><c>driver : string</c> :: Driver name. Ex. <c>"tcp_inet"</c></item>
</list>
<p>Example:</p>
<code type="none">driver_timeout: { cpu_id = 5 }, { pid = "&lt;0.196.0&gt;", port = "#Port&lt;0.3664&gt;", driver = "tcp_inet" }</code>
<p><em>driver_stop_select</em></p>
<list type="bulleted">
- <item><c>driver : string</c> :: Driver name. Ex. <c>"efile"</c></item>
+ <item><c>driver : string</c> :: Driver name. Ex. <c>"tcp_inet"</c></item>
</list>
<p>Example:</p>
<code type="none">driver_stop_select: { cpu_id = 5 }, { driver = "unknown" }</code>
@@ -398,7 +398,7 @@ $ make </code>
<list type="bulleted">
<item><c>pid : string</c> :: Process ID. Ex. <c>"&lt;0.131.0&gt;"</c></item>
<item><c>port : string</c> :: Port ID. Ex. <c>"#Port&lt;0.1031&gt;"</c></item>
- <item><c>driver : string</c> :: Driver name. Ex. <c>"efile"</c></item>
+ <item><c>driver : string</c> :: Driver name. Ex. <c>"tcp_inet"</c></item>
</list>
<p>Example:</p>
<code type="none">driver_flush: { cpu_id = 7 }, { pid = "&lt;0.204.0&gt;", port = "#Port&lt;0.3686&gt;", driver = "tcp_inet" }</code>
@@ -407,32 +407,32 @@ $ make </code>
<list type="bulleted">
<item><c>pid : string</c> :: Process ID. Ex. <c>"&lt;0.131.0&gt;"</c></item>
<item><c>port : string</c> :: Port ID. Ex. <c>"#Port&lt;0.1031&gt;"</c></item>
- <item><c>driver : string</c> :: Driver name. Ex. <c>"efile"</c></item>
+ <item><c>driver : string</c> :: Driver name. Ex. <c>"tcp_inet"</c></item>
</list>
<p>Example:</p>
- <code type="none">driver_stop: { cpu_id = 5 }, { pid = "[]", port = "#Port&lt;0.3673&gt;", driver = "efile" }</code>
+ <code type="none">driver_stop: { cpu_id = 5 }, { pid = "[]", port = "#Port&lt;0.3673&gt;", driver = "tcp_inet" }</code>
<p><em>driver_process_exit</em></p>
<list type="bulleted">
<item><c>pid : string</c> :: Process ID. Ex. <c>"&lt;0.131.0&gt;"</c></item>
<item><c>port : string</c> :: Port ID. Ex. <c>"#Port&lt;0.1031&gt;"</c></item>
- <item><c>driver : string</c> :: Driver name. Ex. <c>"efile"</c></item>
+ <item><c>driver : string</c> :: Driver name. Ex. <c>"tcp_inet"</c></item>
</list>
<p><em>driver_ready_async</em></p>
<list type="bulleted">
<item><c>pid : string</c> :: Process ID. Ex. <c>"&lt;0.131.0&gt;"</c></item>
<item><c>port : string</c> :: Port ID. Ex. <c>"#Port&lt;0.1031&gt;"</c></item>
- <item><c>driver : string</c> :: Driver name. Ex. <c>"efile"</c></item>
+ <item><c>driver : string</c> :: Driver name. Ex. <c>"tcp_inet"</c></item>
</list>
<p>Example:</p>
- <code type="none">driver_ready_async: { cpu_id = 3 }, { pid = "&lt;0.181.0&gt;", port = "#Port&lt;0.3622&gt;", driver = "efile" }</code>
+ <code type="none">driver_ready_async: { cpu_id = 3 }, { pid = "&lt;0.181.0&gt;", port = "#Port&lt;0.3622&gt;", driver = "tcp_inet" }</code>
<p><em>driver_call</em></p>
<list type="bulleted">
<item><c>pid : string</c> :: Process ID. Ex. <c>"&lt;0.131.0&gt;"</c></item>
<item><c>port : string</c> :: Port ID. Ex. <c>"#Port&lt;0.1031&gt;"</c></item>
- <item><c>driver : string</c> :: Driver name. Ex. <c>"efile"</c></item>
+ <item><c>driver : string</c> :: Driver name. Ex. <c>"tcp_inet"</c></item>
<item><c>command : integer</c> :: Command integer. Ex. <c>1</c></item>
<item><c>bytes : integer</c> :: Size of data returned. Ex. <c>82</c></item>
</list>
@@ -443,7 +443,7 @@ $ make </code>
<list type="bulleted">
<item><c>pid : string</c> :: Process ID. Ex. <c>"&lt;0.131.0&gt;"</c></item>
<item><c>port : string</c> :: Port ID. Ex. <c>"#Port&lt;0.1031&gt;"</c></item>
- <item><c>driver : string</c> :: Driver name. Ex. <c>"efile"</c></item>
+ <item><c>driver : string</c> :: Driver name. Ex. <c>"tcp_inet"</c></item>
<item><c>command : integer</c> :: Command integer. Ex. <c>1</c></item>
<item><c>bytes : integer</c> :: Size of data returned. Ex. <c>82</c></item>
</list>
diff --git a/lib/runtime_tools/examples/efile_drv.d b/lib/runtime_tools/examples/efile_drv.d
deleted file mode 100644
index a470518dd9..0000000000
--- a/lib/runtime_tools/examples/efile_drv.d
+++ /dev/null
@@ -1,105 +0,0 @@
-/* example usage: dtrace -q -s /path/to/efile_drv.d */
-/*
- * %CopyrightBegin%
- *
- * Copyright Scott Lystig Fritchie 2011-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%
- */
-
-BEGIN
-{
- op_map[1] = "OPEN";
- op_map[2] = "READ";
- op_map[3] = "LSEEK";
- op_map[4] = "WRITE";
- op_map[5] = "FSTAT";
- op_map[6] = "PWD";
- op_map[7] = "READDIR";
- op_map[8] = "CHDIR";
- op_map[9] = "FSYNC";
- op_map[10] = "MKDIR";
- op_map[11] = "DELETE";
- op_map[12] = "RENAME";
- op_map[13] = "RMDIR";
- op_map[14] = "TRUNCATE";
- op_map[15] = "READ_FILE";
- op_map[16] = "WRITE_INFO";
- op_map[19] = "LSTAT";
- op_map[20] = "READLINK";
- op_map[21] = "LINK";
- op_map[22] = "SYMLINK";
- op_map[23] = "CLOSE";
- op_map[24] = "PWRITEV";
- op_map[25] = "PREADV";
- op_map[26] = "SETOPT";
- op_map[27] = "IPREAD";
- op_map[28] = "ALTNAME";
- op_map[29] = "READ_LINE";
- op_map[30] = "FDATASYNC";
- op_map[31] = "FADVISE";
-}
-
-erlang*:::aio_pool-add
-{
- printf("async I/O pool port %s queue len %d\n", copyinstr(arg0), arg1);
-}
-
-erlang*:::aio_pool-get
-{
- printf("async I/O pool port %s queue len %d\n", copyinstr(arg0), arg1);
-}
-
-erlang*:::efile_drv-entry
-{
- printf("efile_drv enter tag={%d,%d} %s%s | %s (%d) | args: %s %s , %d %d (port %s)\n",
- arg0, arg1,
- arg2 == NULL ? "" : "user tag ",
- arg2 == NULL ? "" : copyinstr(arg2),
- op_map[arg3], arg3,
- arg4 == NULL ? "" : copyinstr(arg4),
- arg5 == NULL ? "" : copyinstr(arg5), arg6, arg7,
- /* NOTE: port name in args[10] is experimental */
- (args[10] == NULL) ?
- "?" : copyinstr((user_addr_t) args[10]));
-}
-
-erlang*:::efile_drv-int*
-{
- printf("async I/O worker tag={%d,%d} | %s (%d) | %s\n",
- arg0, arg1, op_map[arg2], arg2, probename);
-}
-
-/* efile_drv-return error case */
-erlang*:::efile_drv-return
-/arg4 == 0/
-{
- printf("efile_drv return tag={%d,%d} %s%s | %s (%d) | errno %d\n",
- arg0, arg1,
- arg2 == NULL ? "" : "user tag ",
- arg2 == NULL ? "" : copyinstr(arg2),
- op_map[arg3], arg3,
- arg5);
-}
-
-/* efile_drv-return success case */
-erlang*:::efile_drv-return
-/arg4 != 0/
-{
- printf("efile_drv return tag={%d,%d} %s | %s (%d) ok\n",
- arg0, arg1,
- arg2 == NULL ? "" : copyinstr(arg2),
- op_map[arg3], arg3);
-}
diff --git a/lib/runtime_tools/examples/efile_drv.systemtap b/lib/runtime_tools/examples/efile_drv.systemtap
deleted file mode 100644
index 29c3637e10..0000000000
--- a/lib/runtime_tools/examples/efile_drv.systemtap
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Scott Lystig Fritchie and Andreas Schultz, 2011-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%
- */
-/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
- * Note that other variations of the virtual machine also have
- * different names, e.g. the debug build of the SMP-enabled VM
- * is "beam.debug.smp".
- *
- * To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
- */
-
-probe begin
-{
- op_map[1] = "OPEN";
- op_map[2] = "READ";
- op_map[3] = "LSEEK";
- op_map[4] = "WRITE";
- op_map[5] = "FSTAT";
- op_map[6] = "PWD";
- op_map[7] = "READDIR";
- op_map[8] = "CHDIR";
- op_map[9] = "FSYNC";
- op_map[10] = "MKDIR";
- op_map[11] = "DELETE";
- op_map[12] = "RENAME";
- op_map[13] = "RMDIR";
- op_map[14] = "TRUNCATE";
- op_map[15] = "READ_FILE";
- op_map[16] = "WRITE_INFO";
- op_map[19] = "LSTAT";
- op_map[20] = "READLINK";
- op_map[21] = "LINK";
- op_map[22] = "SYMLINK";
- op_map[23] = "CLOSE";
- op_map[24] = "PWRITEV";
- op_map[25] = "PREADV";
- op_map[26] = "SETOPT";
- op_map[27] = "IPREAD";
- op_map[28] = "ALTNAME";
- op_map[29] = "READ_LINE";
- op_map[30] = "FDATASYNC";
- op_map[31] = "FADVISE";
-}
-
-probe process("beam").mark("aio_pool-add")
-{
- printf("async I/O pool port %s queue len %d\n", user_string($arg1), $arg2);
-}
-
-probe process("beam").mark("aio_pool-get")
-{
- printf("async I/O pool port %s queue len %d\n", user_string($arg1), $arg2);
-}
-
-probe process("beam").mark("efile_drv-entry")
-{
- printf("efile_drv enter tag={%d,%d} %s%s | %s (%d) | args: %s %s , %d %d (port %s)\n",
- $arg1, $arg2,
- $arg3 == NULL ? "" : "user tag ",
- $arg3 == NULL ? "" : user_string($arg3),
- op_map[$arg4], $arg4,
- $arg5 == NULL ? "" : user_string($arg5),
- $arg6 == NULL ? "" : user_string($arg6), $arg7, $arg8,
- /* NOTE: port name in $arg[11] is experimental */
- user_string($arg11))
-}
-
-probe process("beam").mark("efile_drv-int*")
-{
- printf("async I/O worker tag={%d,%d} | %s (%d) | %s\n",
- $arg1, $arg2, op_map[$arg3], $arg3, probefunc());
-}
-
-probe process("beam").mark("efile_drv-return")
-{
- if ($arg5 == 0) {
- /* efile_drv-return error case */
- printf("efile_drv return tag={%d,%d} %s%s | %s (%d) | errno %d\n",
- $arg1, $arg2,
- $arg3 == NULL ? "" : "user tag ",
- $arg3 == NULL ? "" : user_string($arg3),
- op_map[$arg4], $arg4,
- $arg6);
- } else {
- /* efile_drv-return success case */
- printf("efile_drv return tag={%d,%d} %s | %s (%d) ok\n",
- $arg1, $arg2,
- $arg3 == NULL ? "" : user_string($arg3),
- op_map[$arg4], $arg4);
- }
-}
-
-global op_map;
diff --git a/lib/sasl/src/systools_make.erl b/lib/sasl/src/systools_make.erl
index 391b1fb5cc..9d960b7361 100644
--- a/lib/sasl/src/systools_make.erl
+++ b/lib/sasl/src/systools_make.erl
@@ -1501,7 +1501,7 @@ preloaded() ->
[erl_prim_loader,erl_tracer,erlang,
erts_code_purger,erts_dirty_process_code_checker,
erts_internal,erts_literal_area_collector,
- init,otp_ring0,prim_eval,prim_file,
+ init,otp_ring0,prim_buffer,prim_eval,prim_file,
prim_inet,prim_zip,zlib].
%%______________________________________________________________________
diff --git a/lib/ssl/doc/src/ssl_distribution.xml b/lib/ssl/doc/src/ssl_distribution.xml
index 61f88e3860..7f8a08f704 100644
--- a/lib/ssl/doc/src/ssl_distribution.xml
+++ b/lib/ssl/doc/src/ssl_distribution.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2000</year><year>2016</year>
+ <year>2000</year><year>2017</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -180,10 +180,96 @@ Eshell V5.0 (abort with ^G)
<section>
<title>Specifying SSL Options</title>
- <p>For SSL to work, at least
- a public key and a certificate must be specified for the server
- side. In the following example, the PEM-files consist of two
- entries, the server certificate and its private key.</p>
+
+ <p>
+ The SSL distribution options can be written into a file
+ that is consulted when the node is started. This file name
+ is then specified with the command line argument
+ <c>-ssl_dist_optfile</c>.
+ </p>
+ <p>
+ Any available SSL option can be specified in an options file,
+ but note that options that take a <c>fun()</c> has to use
+ the syntax <c>fun Mod:Func/Arity</c> since a function
+ body can not be compiled when consulting a file.
+ </p>
+ <p>
+ Do not tamper with the socket options
+ <c>list</c>, <c>binary</c>, <c>active</c>, <c>packet</c>,
+ <c>nodelay</c> and <c>deliver</c> since they are used
+ by the distribution protocol handler itself.
+ Other raw socket options such as <c>packet_size</c> may
+ interfere severely, so beware!
+ </p>
+ <p>
+ For SSL to work, at least a public key and a certificate
+ must be specified for the server side.
+ In the following example, the PEM file
+ <c>"/home/me/ssl/erlserver.pem"</c> contains both
+ the server certificate and its private key.
+ </p>
+ <p>
+ Create a file named for example
+ <c>"/home/me/ssl/[email protected]"</c>:
+ </p>
+ <code type="none"><![CDATA[
+[{server,
+ [{certfile, "/home/me/ssl/erlserver.pem"},
+ {secure_renegotiate, true}]},
+ {client,
+ [{secure_renegotiate, true}]}].]]>
+ </code>
+ <p>
+ And then start the node like this
+ (line breaks in the command are for readability,
+ and shall not be there when typed):
+ </p>
+ <code type="none"><![CDATA[
+$ erl -boot /home/me/ssl/start_ssl -proto_dist inet_tls
+ -ssl_dist_optfile "/home/me/ssl/[email protected]"
+ -sname ssl_test]]>
+ </code>
+ <p>
+ The options in the <c>{server, Opts}</c> tuple are used
+ when calling <c>ssl:ssl_accept/3</c>, and the options in the
+ <c>{client, Opts}</c> tuple are used when calling
+ <c>ssl:connect/4</c>.
+ </p>
+ <p>
+ For the client, the option
+ <c>{server_name_indication, atom_to_list(TargetNode)}</c>
+ is added when connecting.
+ This makes it possible to use the client option
+ <c>{verify, verify_peer}</c>,
+ and the client will verify that the certificate matches
+ the node name you are connecting to.
+ This only works if the the server certificate is issued
+ to the name <c>atom_to_list(TargetNode)</c>.
+ </p>
+ <p>
+ For the server it is also possible to use the option
+ <c>{verify, verify_peer}</c> and the server will only accept
+ client connections with certificates that are trusted by
+ a root certificate that the server knows.
+ A client that presents an untrusted certificate will be rejected.
+ This option is preferably combined with
+ <c>{fail_if_no_peer_cert, true}</c> or a client will
+ still be accepted if it does not present any certificate.
+ </p>
+ <p>
+ A node started in this way is fully functional, using SSL
+ as the distribution protocol.
+ </p>
+ </section>
+
+ <section>
+ <title>Specifying SSL Options (Legacy)</title>
+
+ <p>
+ As in the previous section the PEM file
+ <c>"/home/me/ssl/erlserver.pem"</c> contains both
+ the server certificate and its private key.
+ </p>
<p>On the <c>erl</c> command line you can specify options that the
SSL distribution adds when creating a socket.</p>
@@ -226,24 +312,26 @@ Eshell V5.0 (abort with ^G)
SSL options and their values. Argument <c>-ssl_dist_opt</c> can
be repeated any number of times.</p>
- <p>An example command line can now look as follows
+ <p>
+ An example command line doing the same as the example
+ in the previous section can now look as follows
(line breaks in the command are for readability,
- and are not be there when typed):</p>
- <code type="none">
+ and shall not be there when typed):
+ </p>
+ <code type="none"><![CDATA[
$ erl -boot /home/me/ssl/start_ssl -proto_dist inet_tls
- -ssl_dist_opt server_certfile "/home/me/ssl/erlserver.pem"
+ -ssl_dist_opt server_certfile "/home/me/ssl/erlserver.pem"
-ssl_dist_opt server_secure_renegotiate true client_secure_renegotiate true
-sname ssl_test
Erlang (BEAM) emulator version 5.0 [source]
-
+
Eshell V5.0 (abort with ^G)
-(ssl_test@myhost)1> </code>
- <p>A node started in this way is fully functional, using SSL
- as the distribution protocol.</p>
+(ssl_test@myhost)1>]]>
+ </code>
</section>
<section>
- <title>Setting up Environment to Always Use SSL</title>
+ <title>Setting up Environment to Always Use SSL (Legacy)</title>
<p>A convenient way to specify arguments to Erlang is to use environment
variable <c>ERL_FLAGS</c>. All the flags needed to
use the SSL distribution can be specified in that variable and are
@@ -285,15 +373,11 @@ Eshell V5.0 (abort with ^G)
variable.</p>
<p>An example command line with this option would look like this:</p>
- <code type="none">
+ <code type="none"><![CDATA[
$ erl -boot /home/me/ssl/start_ssl -proto_dist inet6_tls
- -ssl_dist_opt server_certfile "/home/me/ssl/erlserver.pem"
- -ssl_dist_opt server_secure_renegotiate true client_secure_renegotiate true
- -sname ssl_test
-Erlang (BEAM) emulator version 5.0 [source]
-
-Eshell V5.0 (abort with ^G)
-(ssl_test@myhost)1> </code>
+ -ssl_dist_optfile "/home/me/ssl/[email protected]"
+ -sname ssl_test]]>
+ </code>
<p>A node started in this way will only be able to communicate with
other nodes using SSL distribution over IPv6.</p>
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index 073cb4009b..e5760e7951 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -467,7 +467,8 @@ init(Type, Event, State) ->
error(enter, _, State) ->
{keep_state, State};
error({call, From}, {start, _Timeout}, {Error, State}) ->
- {stop_and_reply, normal, {reply, From, {error, Error}}, State};
+ ssl_connection:stop_and_reply(
+ normal, {reply, From, {error, Error}}, State);
error({call, _} = Call, Msg, State) ->
gen_handshake(?FUNCTION_NAME, Call, Msg, State);
error(_, _, _) ->
@@ -821,7 +822,7 @@ handle_info({Protocol, _, _, _, Data}, StateName,
next_event(StateName, Record, State);
#alert{} = Alert ->
ssl_connection:handle_normal_shutdown(Alert, StateName, State0),
- {stop, {shutdown, own_alert}}
+ ssl_connection:stop({shutdown, own_alert}, State0)
end;
handle_info({CloseTag, Socket}, StateName,
#state{socket = Socket,
@@ -846,7 +847,7 @@ handle_info({CloseTag, Socket}, StateName,
ok
end,
ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
- {stop, {shutdown, transport_closed}};
+ ssl_connection:stop({shutdown, transport_closed}, State);
true ->
%% Fixes non-delivery of final DTLS record in {active, once}.
%% Basically allows the application the opportunity to set {active, once} again
@@ -872,7 +873,7 @@ handle_state_timeout(flight_retransmission_timeout, StateName,
handle_alerts([], Result) ->
Result;
-handle_alerts(_, {stop,_} = Stop) ->
+handle_alerts(_, {stop, _, _} = Stop) ->
Stop;
handle_alerts([Alert | Alerts], {next_state, StateName, State}) ->
handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State));
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl
index 1d6f0a42c8..5e8f5c2ca0 100644
--- a/lib/ssl/src/dtls_handshake.erl
+++ b/lib/ssl/src/dtls_handshake.erl
@@ -189,7 +189,7 @@ handle_client_hello(Version,
no_suite ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY);
_ ->
- {KeyExAlg,_,_,_} = ssl_cipher:suite_definition(CipherSuite),
+ #{key_exchange := KeyExAlg} = ssl_cipher:suite_definition(CipherSuite),
case ssl_handshake:select_hashsign(ClientHashSigns, Cert, KeyExAlg,
SupportedHashSigns, TLSVersion) of
#alert{} = Alert ->
diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl
index 96782dcfc0..8e605bec65 100644
--- a/lib/ssl/src/inet_tls_dist.erl
+++ b/lib/ssl/src/inet_tls_dist.erl
@@ -324,12 +324,13 @@ do_accept(Driver, Kernel, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime) ->
timer = Timer,
this_flags = 0,
allowed = Allowed},
+ link(DistCtrl),
dist_util:handshake_other_started(trace(HSData));
{false,IP} ->
error_logger:error_msg(
"** Connection attempt from "
"disallowed IP ~w ** ~n", [IP]),
- ?shutdown(trace(no_node))
+ ?shutdown2(no_node, trace({disallowed, IP}))
end
end.
@@ -357,7 +358,11 @@ do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
ErlEpmd = net_kernel:epmd_module(),
case ErlEpmd:port_please(Name, Ip) of
{port, TcpPort, Version} ->
- Opts = trace(connect_options(get_ssl_options(client))),
+ Opts =
+ trace(
+ connect_options(
+ [{server_name_indication, atom_to_list(Node)}
+ |get_ssl_options(client)])),
dist_util:reset_timer(Timer),
case ssl:connect(
Address, TcpPort,
@@ -378,21 +383,26 @@ do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) ->
this_flags = 0,
other_version = Version,
request_type = Type},
+ link(DistCtrl),
dist_util:handshake_we_started(trace(HSData));
Other ->
%% Other Node may have closed since
%% port_please !
?shutdown2(
Node,
- trace({shutdown, {connect_failed, Other}}))
+ trace(
+ {ssl_connect_failed, Ip, TcpPort, Other}))
end;
Other ->
?shutdown2(
Node,
- trace({shutdown, {port_please_failed, Other}}))
+ trace(
+ {port_please_failed, ErlEpmd, Name, Ip, Other}))
end;
Other ->
- ?shutdown2(Node, trace({shutdown, {getaddr_failed, Other}}))
+ ?shutdown2(
+ Node,
+ trace({getaddr_failed, Driver, Address, Other}))
end.
close(Socket) ->
@@ -411,8 +421,9 @@ check_ip(Driver, SslSocket) ->
case get_ifs(SslSocket) of
{ok, IFs, IP} ->
check_ip(Driver, IFs, IP);
- _ ->
- ?shutdown(no_node)
+ Other ->
+ ?shutdown2(
+ no_node, trace({check_ip_failed, SslSocket, Other}))
end;
_ ->
true
@@ -441,23 +452,22 @@ get_ifs(#sslsocket{fd = {gen_tcp, Socket, _}}) ->
%% If Node is illegal terminate the connection setup!!
splitnode(Driver, Node, LongOrShortNames) ->
- case split_node(atom_to_list(Node), $@, []) of
- [Name|Tail] when Tail =/= [] ->
- Host = lists:append(Tail),
+ case string:split(atom_to_list(Node), "@") of
+ [Name, Host] when Host =/= [] ->
check_node(Driver, Name, Node, Host, LongOrShortNames);
[_] ->
error_logger:error_msg(
"** Nodename ~p illegal, no '@' character **~n",
[Node]),
- ?shutdown(Node);
+ ?shutdown2(Node, trace({illegal_node_n@me, Node}));
_ ->
error_logger:error_msg(
"** Nodename ~p illegal **~n", [Node]),
- ?shutdown(Node)
+ ?shutdown2(Node, trace({illegal_node_name, Node}))
end.
check_node(Driver, Name, Node, Host, LongOrShortNames) ->
- case split_node(Host, $., []) of
+ case string:split(Host, ".") of
[_] when LongOrShortNames == longnames ->
case Driver:parse_address(Host) of
{ok, _} ->
@@ -468,35 +478,28 @@ check_node(Driver, Name, Node, Host, LongOrShortNames) ->
"fully qualified hostnames **~n"
"** Hostname ~s is illegal **~n",
[Host]),
- ?shutdown(Node)
+ ?shutdown2(Node, trace({not_longnames, Host}))
end;
- [_, _ | _] when LongOrShortNames == shortnames ->
+ [_, _] when LongOrShortNames == shortnames ->
error_logger:error_msg(
"** System NOT running to use "
"fully qualified hostnames **~n"
"** Hostname ~s is illegal **~n",
[Host]),
- ?shutdown(Node);
+ ?shutdown2(Node, trace({not_shortnames, Host}));
_ ->
[Name, Host]
end.
split_node(Node) when is_atom(Node) ->
- case split_node(atom_to_list(Node), $@, []) of
- [_, Host] ->
+ case string:split(atom_to_list(Node), "@") of
+ [Name, Host] when Name =/= [], Host =/= [] ->
Host;
_ ->
false
end;
split_node(_) ->
false.
-%%
-split_node([Chr|T], Chr, Ack) ->
- [lists:reverse(Ack)|split_node(T, Chr, [])];
-split_node([H|T], Chr, Ack) ->
- split_node(T, Chr, [H|Ack]);
-split_node([], _, Ack) ->
- [lists:reverse(Ack)].
%% -------------------------------------------------------------------------
@@ -524,6 +527,17 @@ nodelay() ->
get_ssl_options(Type) ->
+ try ets:lookup(ssl_dist_opts, Type) of
+ [{Type, Opts}] ->
+ [{erl_dist, true} | Opts];
+ _ ->
+ get_ssl_dist_arguments(Type)
+ catch
+ error:badarg ->
+ get_ssl_dist_arguments(Type)
+ end.
+
+get_ssl_dist_arguments(Type) ->
case init:get_argument(ssl_dist_opt) of
{ok, Args} ->
[{erl_dist, true} | ssl_options(Type, lists:append(Args))];
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 4007e44a83..656ed94ea5 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -374,13 +374,12 @@ negotiated_protocol(#sslsocket{pid = Pid}) ->
ssl_connection:negotiated_protocol(Pid).
%%--------------------------------------------------------------------
--spec cipher_suites() -> [ssl_cipher:erl_cipher_suite()] | [string()].
+-spec cipher_suites() -> [ssl_cipher:old_erl_cipher_suite()] | [string()].
%%--------------------------------------------------------------------
cipher_suites() ->
cipher_suites(erlang).
%%--------------------------------------------------------------------
--spec cipher_suites(erlang | openssl | all) -> [ssl_cipher:erl_cipher_suite()] |
- [string()].
+-spec cipher_suites(erlang | openssl | all) -> [ssl_cipher:old_erl_cipher_suite() | string()].
%% Description: Returns all supported cipher suites.
%%--------------------------------------------------------------------
cipher_suites(erlang) ->
@@ -992,17 +991,21 @@ validate_option(next_protocols_advertised, Value) when is_list(Value) ->
Value;
validate_option(next_protocols_advertised, undefined) ->
undefined;
-validate_option(server_name_indication = Opt, Value) when is_list(Value) ->
+validate_option(server_name_indication, Value) when is_list(Value) ->
%% RFC 6066, Section 3: Currently, the only server names supported are
%% DNS hostnames
- case inet_parse:domain(Value) of
- false ->
- throw({error, {options, {{Opt, Value}}}});
- true ->
- Value
- end;
-validate_option(server_name_indication, undefined = Value) ->
+ %% case inet_parse:domain(Value) of
+ %% false ->
+ %% throw({error, {options, {{Opt, Value}}}});
+ %% true ->
+ %% Value
+ %% end;
+ %%
+ %% But the definition seems very diffuse, so let all strings through
+ %% and leave it up to public_key to decide...
Value;
+validate_option(server_name_indication, undefined) ->
+ undefined;
validate_option(server_name_indication, disable) ->
disable;
@@ -1149,9 +1152,8 @@ binary_cipher_suites(Version, []) ->
%% not require explicit configuration
ssl_cipher:filter_suites(ssl_cipher:suites(tls_version(Version)));
binary_cipher_suites(Version, [Tuple|_] = Ciphers0) when is_tuple(Tuple) ->
- Ciphers = [ssl_cipher:suite(C) || C <- Ciphers0],
+ Ciphers = [ssl_cipher:suite(tuple_to_map(C)) || C <- Ciphers0],
binary_cipher_suites(Version, Ciphers);
-
binary_cipher_suites(Version, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) ->
All = ssl_cipher:all_suites(tls_version(Version)),
case [Cipher || Cipher <- Ciphers0, lists:member(Cipher, All)] of
@@ -1171,6 +1173,17 @@ binary_cipher_suites(Version, Ciphers0) ->
Ciphers = [ssl_cipher:openssl_suite(C) || C <- string:lexemes(Ciphers0, ":")],
binary_cipher_suites(Version, Ciphers).
+tuple_to_map({Kex, Cipher, Mac}) ->
+ #{key_exchange => Kex,
+ cipher => Cipher,
+ mac => Mac,
+ prf => default_prf};
+tuple_to_map({Kex, Cipher, Mac, Prf}) ->
+ #{key_exchange => Kex,
+ cipher => Cipher,
+ mac => Mac,
+ prf => Prf}.
+
handle_eccs_option(Value, Version) when is_list(Value) ->
{_Major, Minor} = tls_version(Version),
try tls_v1:ecc_curves(Minor, Value) of
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index b6cd22dd13..b0e38fb9ad 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -44,20 +44,21 @@
is_stream_ciphersuite/1]).
-export_type([cipher_suite/0,
- erl_cipher_suite/0, openssl_cipher_suite/0,
+ erl_cipher_suite/0, old_erl_cipher_suite/0, openssl_cipher_suite/0,
hash/0, key_algo/0, sign_algo/0]).
--type cipher() :: null |rc4_128 | des_cbc | '3des_ede_cbc'
- | aes_128_cbc | aes_256_cbc | aes_128_gcm | aes_256_gcm | chacha20_poly1305.
+-type cipher() :: null |rc4_128 | des_cbc | '3des_ede_cbc' | aes_128_cbc | aes_256_cbc | aes_128_gcm | aes_256_gcm | chacha20_poly1305.
-type hash() :: null | md5 | sha | sha224 | sha256 | sha384 | sha512.
-type sign_algo() :: rsa | dsa | ecdsa.
--type key_algo() :: null | rsa | dhe_rsa | dhe_dss | ecdhe_ecdsa| ecdh_ecdsa | ecdh_rsa| srp_rsa| srp_dss |
- psk | dhe_psk | rsa_psk | dh_anon | ecdh_anon | srp_anon.
--type erl_cipher_suite() :: {key_algo(), cipher(), hash()} % Pre TLS 1.2
- %% TLS 1.2, internally PRE TLS 1.2 will use default_prf
- | {key_algo(), cipher(), hash(), hash() | default_prf}.
-
-
+-type key_algo() :: null | rsa | dhe_rsa | dhe_dss | ecdhe_ecdsa| ecdh_ecdsa | ecdh_rsa| srp_rsa| srp_dss | psk | dhe_psk | rsa_psk | dh_anon | ecdh_anon | srp_anon.
+-type erl_cipher_suite() :: #{key_exchange := key_algo(),
+ cipher := cipher(),
+ mac := hash(),
+ prf := hash() | default_prf %% Old cipher suites, version dependent
+ }.
+-type old_erl_cipher_suite() :: {key_algo(), cipher(), hash()} % Pre TLS 1.2
+ %% TLS 1.2, internally PRE TLS 1.2 will use default_prf
+ | {key_algo(), cipher(), hash(), hash() | default_prf}.
-type cipher_suite() :: binary().
-type cipher_enum() :: integer().
-type openssl_cipher_suite() :: string().
@@ -83,7 +84,8 @@ security_parameters(?TLS_NULL_WITH_NULL_NULL = CipherSuite, SecParams) ->
%% cipher values has been updated according to <CipherSuite>
%%-------------------------------------------------------------------
security_parameters(Version, CipherSuite, SecParams) ->
- { _, Cipher, Hash, PrfHashAlg} = suite_definition(CipherSuite),
+ #{cipher := Cipher, mac := Hash,
+ prf := PrfHashAlg} = suite_definition(CipherSuite),
SecParams#security_parameters{
cipher_suite = CipherSuite,
bulk_cipher_algorithm = bulk_cipher_algorithm(Cipher),
@@ -465,353 +467,740 @@ des_suites(_)->
%%-------------------------------------------------------------------
%% TLS v1.1 suites
suite_definition(?TLS_NULL_WITH_NULL_NULL) ->
- {null, null, null, null};
+ #{key_exchange => null,
+ cipher => null,
+ mac => null,
+ prf => null};
%% RFC 5746 - Not a real cipher suite used to signal empty "renegotiation_info" extension
%% to avoid handshake failure from old servers that do not ignore
%% hello extension data as they should.
suite_definition(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV) ->
- {null, null, null, null};
-%% suite_definition(?TLS_RSA_WITH_NULL_MD5) ->
-%% {rsa, null, md5, default_prf};
-%% suite_definition(?TLS_RSA_WITH_NULL_SHA) ->
-%% {rsa, null, sha, default_prf};
+ #{key_exchange => null,
+ cipher => null,
+ mac => null,
+ prf => null};
suite_definition(?TLS_RSA_WITH_RC4_128_MD5) ->
- {rsa, rc4_128, md5, default_prf};
+ #{key_exchange => rsa,
+ cipher => rc4_128,
+ mac => md5,
+ prf => default_prf};
suite_definition(?TLS_RSA_WITH_RC4_128_SHA) ->
- {rsa, rc4_128, sha, default_prf};
+ #{key_exchange => rsa,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_RSA_WITH_DES_CBC_SHA) ->
- {rsa, des_cbc, sha, default_prf};
+ #{key_exchange => rsa,
+ cipher => des_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_RSA_WITH_3DES_EDE_CBC_SHA) ->
- {rsa, '3des_ede_cbc', sha, default_prf};
+ #{key_exchange => rsa,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_DHE_DSS_WITH_DES_CBC_SHA) ->
- {dhe_dss, des_cbc, sha, default_prf};
+ #{key_exchange => dhe_dss,
+ cipher => des_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA) ->
- {dhe_dss, '3des_ede_cbc', sha, default_prf};
+ #{key_exchange => dhe_dss,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_DHE_RSA_WITH_DES_CBC_SHA) ->
- {dhe_rsa, des_cbc, sha, default_prf};
+ #{key_exchange => dhe_rsa,
+ cipher => des_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) ->
- {dhe_rsa, '3des_ede_cbc', sha, default_prf};
-
+ #{key_exchange => dhe_rsa,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
%%% TSL V1.1 AES suites
suite_definition(?TLS_RSA_WITH_AES_128_CBC_SHA) ->
- {rsa, aes_128_cbc, sha, default_prf};
+ #{key_exchange => rsa,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_DHE_DSS_WITH_AES_128_CBC_SHA) ->
- {dhe_dss, aes_128_cbc, sha, default_prf};
+ #{key_exchange => dhe_dss,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_DHE_RSA_WITH_AES_128_CBC_SHA) ->
- {dhe_rsa, aes_128_cbc, sha, default_prf};
+ #{key_exchange => dhe_rsa,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_RSA_WITH_AES_256_CBC_SHA) ->
- {rsa, aes_256_cbc, sha, default_prf};
+ #{key_exchange => rsa,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA) ->
- {dhe_dss, aes_256_cbc, sha, default_prf};
+ #{key_exchange => dhe_dss,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA) ->
- {dhe_rsa, aes_256_cbc, sha, default_prf};
-
+ #{key_exchange => dhe_rsa,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
%% TLS v1.2 suites
-
%% suite_definition(?TLS_RSA_WITH_NULL_SHA) ->
%% {rsa, null, sha, default_prf};
suite_definition(?TLS_RSA_WITH_AES_128_CBC_SHA256) ->
- {rsa, aes_128_cbc, sha256, default_prf};
+ #{key_exchange => rsa,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => default_prf};
suite_definition(?TLS_RSA_WITH_AES_256_CBC_SHA256) ->
- {rsa, aes_256_cbc, sha256, default_prf};
+ #{key_exchange => rsa,
+ cipher => aes_256_cbc,
+ mac => sha256,
+ prf => default_prf};
suite_definition(?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256) ->
- {dhe_dss, aes_128_cbc, sha256, default_prf};
+ #{key_exchange => dhe_dss,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => default_prf};
suite_definition(?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256) ->
- {dhe_rsa, aes_128_cbc, sha256, default_prf};
+ #{key_exchange => dhe_rsa,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => default_prf};
suite_definition(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256) ->
- {dhe_dss, aes_256_cbc, sha256, default_prf};
+ #{key_exchange => dhe_dss,
+ cipher => aes_256_cbc,
+ mac => sha256,
+ prf => default_prf};
suite_definition(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) ->
- {dhe_rsa, aes_256_cbc, sha256, default_prf};
-
+ #{key_exchange => dhe_rsa,
+ cipher => aes_256_cbc,
+ mac => sha256,
+ prf => default_prf};
%% not defined YET:
%% TLS_DH_DSS_WITH_AES_128_CBC_SHA256 DH_DSS AES_128_CBC SHA256
%% TLS_DH_RSA_WITH_AES_128_CBC_SHA256 DH_RSA AES_128_CBC SHA256
%% TLS_DH_DSS_WITH_AES_256_CBC_SHA256 DH_DSS AES_256_CBC SHA256
%% TLS_DH_RSA_WITH_AES_256_CBC_SHA256 DH_RSA AES_256_CBC SHA256
-
%%% DH-ANON deprecated by TLS spec and not available
%%% by default, but good for testing purposes.
suite_definition(?TLS_DH_anon_WITH_RC4_128_MD5) ->
- {dh_anon, rc4_128, md5, default_prf};
+ #{key_exchange => dh_anon,
+ cipher => rc4_128,
+ mac => md5,
+ prf => default_prf};
suite_definition(?TLS_DH_anon_WITH_DES_CBC_SHA) ->
- {dh_anon, des_cbc, sha, default_prf};
+ #{key_exchange => dh_anon,
+ cipher => des_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA) ->
- {dh_anon, '3des_ede_cbc', sha, default_prf};
+ #{key_exchange => dh_anon,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_DH_anon_WITH_AES_128_CBC_SHA) ->
- {dh_anon, aes_128_cbc, sha, default_prf};
+ #{key_exchange => dh_anon,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_DH_anon_WITH_AES_256_CBC_SHA) ->
- {dh_anon, aes_256_cbc, sha, default_prf};
+ #{key_exchange => dh_anon,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_DH_anon_WITH_AES_128_CBC_SHA256) ->
- {dh_anon, aes_128_cbc, sha256, default_prf};
+ #{key_exchange => dh_anon,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => default_prf};
suite_definition(?TLS_DH_anon_WITH_AES_256_CBC_SHA256) ->
- {dh_anon, aes_256_cbc, sha256, default_prf};
-
+ #{key_exchange => dh_anon,
+ cipher => aes_256_cbc,
+ mac => sha256,
+ prf => default_prf};
%%% PSK Cipher Suites RFC 4279
-
suite_definition(?TLS_PSK_WITH_RC4_128_SHA) ->
- {psk, rc4_128, sha, default_prf};
+ #{key_exchange => psk,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_PSK_WITH_3DES_EDE_CBC_SHA) ->
- {psk, '3des_ede_cbc', sha, default_prf};
+ #{key_exchange => psk,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_PSK_WITH_AES_128_CBC_SHA) ->
- {psk, aes_128_cbc, sha, default_prf};
+ #{key_exchange => psk,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_PSK_WITH_AES_256_CBC_SHA) ->
- {psk, aes_256_cbc, sha, default_prf};
+ #{key_exchange => psk,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_DHE_PSK_WITH_RC4_128_SHA) ->
- {dhe_psk, rc4_128, sha, default_prf};
+ #{key_exchange => dhe_psk,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA) ->
- {dhe_psk, '3des_ede_cbc', sha, default_prf};
+ #{key_exchange => dhe_psk,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_DHE_PSK_WITH_AES_128_CBC_SHA) ->
- {dhe_psk, aes_128_cbc, sha, default_prf};
+ #{key_exchange => dhe_psk,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_DHE_PSK_WITH_AES_256_CBC_SHA) ->
- {dhe_psk, aes_256_cbc, sha, default_prf};
+ #{key_exchange => dhe_psk,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_RSA_PSK_WITH_RC4_128_SHA) ->
- {rsa_psk, rc4_128, sha, default_prf};
+ #{key_exchange => rsa_psk,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA) ->
- {rsa_psk, '3des_ede_cbc', sha, default_prf};
+ #{key_exchange => rsa_psk,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_RSA_PSK_WITH_AES_128_CBC_SHA) ->
- {rsa_psk, aes_128_cbc, sha, default_prf};
+ #{key_exchange => rsa_psk,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_RSA_PSK_WITH_AES_256_CBC_SHA) ->
- {rsa_psk, aes_256_cbc, sha, default_prf};
-
+ #{key_exchange => rsa_psk,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
%%% PSK NULL Cipher Suites RFC 4785
-
suite_definition(?TLS_PSK_WITH_NULL_SHA) ->
- {psk, null, sha, default_prf};
+ #{key_exchange => psk,
+ cipher => null,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_DHE_PSK_WITH_NULL_SHA) ->
- {dhe_psk, null, sha, default_prf};
+ #{key_exchange => dhe_psk,
+ cipher => null,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_RSA_PSK_WITH_NULL_SHA) ->
- {rsa_psk, null, sha, default_prf};
-
+ #{key_exchange => rsa_psk,
+ cipher => null,
+ mac => sha,
+ prf => default_prf};
%%% TLS 1.2 PSK Cipher Suites RFC 5487
-
suite_definition(?TLS_PSK_WITH_AES_128_GCM_SHA256) ->
- {psk, aes_128_gcm, null, sha256};
+ #{key_exchange => psk,
+ cipher => aes_128_gcm,
+ mac => null,
+ prf => sha256};
suite_definition(?TLS_PSK_WITH_AES_256_GCM_SHA384) ->
- {psk, aes_256_gcm, null, sha384};
+ #{key_exchange => psk,
+ cipher => aes_256_gcm,
+ mac => null,
+ prf => sha384};
suite_definition(?TLS_DHE_PSK_WITH_AES_128_GCM_SHA256) ->
- {dhe_psk, aes_128_gcm, null, sha256};
+ #{key_exchange => dhe_psk,
+ cipher => aes_128_gcm,
+ mac => null,
+ prf => sha256};
suite_definition(?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384) ->
- {dhe_psk, aes_256_gcm, null, sha384};
+ #{key_exchange => dhe_psk,
+ cipher => aes_256_gcm,
+ mac => null,
+ prf => sha384};
suite_definition(?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256) ->
- {rsa_psk, aes_128_gcm, null, sha256};
+ #{key_exchange => rsa_psk,
+ cipher => aes_128_gcm,
+ mac => null,
+ prf => sha256};
suite_definition(?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384) ->
- {rsa_psk, aes_256_gcm, null, sha384};
-
+ #{key_exchange => rsa_psk,
+ cipher => aes_256_gcm,
+ mac => null,
+ prf => sha384};
suite_definition(?TLS_PSK_WITH_AES_128_CBC_SHA256) ->
- {psk, aes_128_cbc, sha256, default_prf};
+ #{key_exchange => psk,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => default_prf};
suite_definition(?TLS_PSK_WITH_AES_256_CBC_SHA384) ->
- {psk, aes_256_cbc, sha384, default_prf};
+ #{key_exchange => psk,
+ cipher => aes_256_cbc,
+ mac => sha384,
+ prf => default_prf};
suite_definition(?TLS_DHE_PSK_WITH_AES_128_CBC_SHA256) ->
- {dhe_psk, aes_128_cbc, sha256, default_prf};
+ #{key_exchange => dhe_psk,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => default_prf};
suite_definition(?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384) ->
- {dhe_psk, aes_256_cbc, sha384, default_prf};
+ #{key_exchange => dhe_psk,
+ cipher => aes_256_cbc,
+ mac => sha384,
+ prf => default_prf};
suite_definition(?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256) ->
- {rsa_psk, aes_128_cbc, sha256, default_prf};
+ #{key_exchange => rsa_psk,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => default_prf};
suite_definition(?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384) ->
- {rsa_psk, aes_256_cbc, sha384, default_prf};
-
+ #{key_exchange => rsa_psk,
+ cipher => aes_256_cbc,
+ mac => sha384,
+ prf => default_prf};
suite_definition(?TLS_PSK_WITH_NULL_SHA256) ->
- {psk, null, sha256, default_prf};
+ #{key_exchange => psk,
+ cipher => null,
+ mac => sha256,
+ prf => default_prf};
suite_definition(?TLS_PSK_WITH_NULL_SHA384) ->
- {psk, null, sha384, default_prf};
+ #{key_exchange => psk,
+ cipher => null,
+ mac => sha384,
+ prf => default_prf};
suite_definition(?TLS_DHE_PSK_WITH_NULL_SHA256) ->
- {dhe_psk, null, sha256, default_prf};
+ #{key_exchange => dhe_psk,
+ cipher => null,
+ mac => sha256,
+ prf => default_prf};
suite_definition(?TLS_DHE_PSK_WITH_NULL_SHA384) ->
- {dhe_psk, null, sha384, default_prf};
+ #{key_exchange => dhe_psk,
+ cipher => null,
+ mac => sha384,
+ prf => default_prf};
suite_definition(?TLS_RSA_PSK_WITH_NULL_SHA256) ->
- {rsa_psk, null, sha256, default_prf};
+ #{key_exchange => rsa_psk,
+ cipher => null,
+ mac => sha256,
+ prf => default_prf};
suite_definition(?TLS_RSA_PSK_WITH_NULL_SHA384) ->
- {rsa_psk, null, sha384, default_prf};
-
+ #{key_exchange => rsa_psk,
+ cipher => null,
+ mac => sha384,
+ prf => default_prf};
%%% ECDHE PSK Cipher Suites RFC 5489
-
suite_definition(?TLS_ECDHE_PSK_WITH_RC4_128_SHA) ->
- {ecdhe_psk, rc4_128, sha, default_prf};
+ #{key_exchange => ecdhe_psk,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA) ->
- {ecdhe_psk, '3des_ede_cbc', sha, default_prf};
+ #{key_exchange => ecdhe_psk,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA) ->
- {ecdhe_psk, aes_128_cbc, sha, default_prf};
+ #{key_exchange => ecdhe_psk,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA) ->
- {ecdhe_psk, aes_256_cbc, sha, default_prf};
+ #{key_exchange => ecdhe_psk,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256) ->
- {ecdhe_psk, aes_128_cbc, sha256, default_prf};
+ #{key_exchange => ecdhe_psk,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => default_prf};
suite_definition(?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384) ->
- {ecdhe_psk, aes_256_cbc, sha384, default_prf};
+ #{key_exchange => ecdhe_psk,
+ cipher => aes_256_cbc,
+ mac => sha384,
+ prf => default_prf};
suite_definition(?TLS_ECDHE_PSK_WITH_NULL_SHA256) ->
- {ecdhe_psk, null, sha256, default_prf};
+ #{key_exchange => ecdhe_psk,
+ cipher => null,
+ mac => sha256,
+ prf => default_prf};
suite_definition(?TLS_ECDHE_PSK_WITH_NULL_SHA384) ->
- {ecdhe_psk, null, sha384, default_prf};
-
+ #{key_exchange => ecdhe_psk,
+ cipher => null, mac => sha384,
+ prf => default_prf};
%%% ECDHE_PSK with AES-GCM and AES-CCM Cipher Suites, draft-ietf-tls-ecdhe-psk-aead-05
-
suite_definition(?TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256) ->
- {ecdhe_psk, aes_128_gcm, null, sha256};
+ #{key_exchange => ecdhe_psk,
+ cipher => aes_128_gcm,
+ mac => null,
+ prf => sha256};
suite_definition(?TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384) ->
- {ecdhe_psk, aes_256_gcm, null, sha384};
+ #{key_exchange => ecdhe_psk,
+ cipher => aes_256_gcm,
+ mac => null,
+ prf => sha384};
%% suite_definition(?TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256) ->
-%% {ecdhe_psk, aes_128_ccm, null, sha256};
+%% #{key_exchange => ecdhe_psk,
+%% cipher => aes_128_ccm,
+%% mac => null,
+%% prf =>sha256};
%% suite_definition(?TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256) ->
-%% {ecdhe_psk, aes_256_ccm, null, sha256};
-
+%% #{key_exchange => ecdhe_psk,
+%% cipher => aes_256_ccm,
+%% mac => null,
+%% prf => sha256};
%%% SRP Cipher Suites RFC 5054
-
suite_definition(?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) ->
- {srp_anon, '3des_ede_cbc', sha, default_prf};
+ #{key_exchange => srp_anon,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) ->
- {srp_rsa, '3des_ede_cbc', sha, default_prf};
+ #{key_exchange => srp_rsa,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA) ->
- {srp_dss, '3des_ede_cbc', sha, default_prf};
+ #{key_exchange => srp_dss,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_SRP_SHA_WITH_AES_128_CBC_SHA) ->
- {srp_anon, aes_128_cbc, sha, default_prf};
+ #{key_exchange => srp_anon,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) ->
- {srp_rsa, aes_128_cbc, sha, default_prf};
+ #{key_exchange => srp_rsa,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA) ->
- {srp_dss, aes_128_cbc, sha, default_prf};
+ #{key_exchange => srp_dss,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_SRP_SHA_WITH_AES_256_CBC_SHA) ->
- {srp_anon, aes_256_cbc, sha, default_prf};
+ #{key_exchange => srp_anon,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) ->
- {srp_rsa, aes_256_cbc, sha, default_prf};
+ #{key_exchange => srp_rsa,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA) ->
- {srp_dss, aes_256_cbc, sha, default_prf};
-
+ #{key_exchange => srp_dss,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
%% RFC 4492 EC TLS suites
suite_definition(?TLS_ECDH_ECDSA_WITH_NULL_SHA) ->
- {ecdh_ecdsa, null, sha, default_prf};
+ #{key_exchange => ecdh_ecdsa,
+ cipher => null,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDH_ECDSA_WITH_RC4_128_SHA) ->
- {ecdh_ecdsa, rc4_128, sha, default_prf};
+ #{key_exchange => ecdh_ecdsa,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA) ->
- {ecdh_ecdsa, '3des_ede_cbc', sha, default_prf};
+ #{key_exchange => ecdh_ecdsa,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA) ->
- {ecdh_ecdsa, aes_128_cbc, sha, default_prf};
+ #{key_exchange => ecdh_ecdsa,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA) ->
- {ecdh_ecdsa, aes_256_cbc, sha, default_prf};
-
+ #{key_exchange => ecdh_ecdsa,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDHE_ECDSA_WITH_NULL_SHA) ->
- {ecdhe_ecdsa, null, sha, default_prf};
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => null,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA) ->
- {ecdhe_ecdsa, rc4_128, sha, default_prf};
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA) ->
- {ecdhe_ecdsa, '3des_ede_cbc', sha, default_prf};
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA) ->
- {ecdhe_ecdsa, aes_128_cbc, sha, default_prf};
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) ->
- {ecdhe_ecdsa, aes_256_cbc, sha, default_prf};
-
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDH_RSA_WITH_NULL_SHA) ->
- {ecdh_rsa, null, sha, default_prf};
+ #{key_exchange => ecdh_rsa,
+ cipher => null,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDH_RSA_WITH_RC4_128_SHA) ->
- {ecdh_rsa, rc4_128, sha, default_prf};
+ #{key_exchange => ecdh_rsa,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA) ->
- {ecdh_rsa, '3des_ede_cbc', sha, default_prf};
+ #{key_exchange => ecdh_rsa,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA) ->
- {ecdh_rsa, aes_128_cbc, sha, default_prf};
+ #{key_exchange => ecdh_rsa,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA) ->
- {ecdh_rsa, aes_256_cbc, sha, default_prf};
-
+ #{key_exchange => ecdh_rsa,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDHE_RSA_WITH_NULL_SHA) ->
- {ecdhe_rsa, null, sha, default_prf};
+ #{key_exchange => ecdhe_rsa,
+ cipher => null,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDHE_RSA_WITH_RC4_128_SHA) ->
- {ecdhe_rsa, rc4_128, sha, default_prf};
+ #{key_exchange => ecdhe_rsa,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA) ->
- {ecdhe_rsa, '3des_ede_cbc', sha, default_prf};
+ #{key_exchange => ecdhe_rsa,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) ->
- {ecdhe_rsa, aes_128_cbc, sha, default_prf};
+ #{key_exchange => ecdhe_rsa,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) ->
- {ecdhe_rsa, aes_256_cbc, sha, default_prf};
-
+ #{key_exchange => ecdhe_rsa,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDH_anon_WITH_NULL_SHA) ->
- {ecdh_anon, null, sha, default_prf};
+ #{key_exchange => ecdh_anon,
+ cipher => null,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDH_anon_WITH_RC4_128_SHA) ->
- {ecdh_anon, rc4_128, sha, default_prf};
+ #{key_exchange => ecdh_anon,
+ cipher => rc4_128,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA) ->
- {ecdh_anon, '3des_ede_cbc', sha, default_prf};
+ #{key_exchange => ecdh_anon,
+ cipher => '3des_ede_cbc',
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDH_anon_WITH_AES_128_CBC_SHA) ->
- {ecdh_anon, aes_128_cbc, sha, default_prf};
+ #{key_exchange => ecdh_anon,
+ cipher => aes_128_cbc,
+ mac => sha,
+ prf => default_prf};
suite_definition(?TLS_ECDH_anon_WITH_AES_256_CBC_SHA) ->
- {ecdh_anon, aes_256_cbc, sha, default_prf};
-
+ #{key_exchange => ecdh_anon,
+ cipher => aes_256_cbc,
+ mac => sha,
+ prf => default_prf};
%% RFC 5289 EC TLS suites
suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256) ->
- {ecdhe_ecdsa, aes_128_cbc, sha256, sha256};
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => sha256};
suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384) ->
- {ecdhe_ecdsa, aes_256_cbc, sha384, sha384};
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_256_cbc,
+ mac => sha384,
+ prf => sha384};
suite_definition(?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256) ->
- {ecdh_ecdsa, aes_128_cbc, sha256, sha256};
+ #{key_exchange => ecdh_ecdsa,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => sha256};
suite_definition(?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384) ->
- {ecdh_ecdsa, aes_256_cbc, sha384, sha384};
+ #{key_exchange => ecdh_ecdsa,
+ cipher => aes_256_cbc,
+ mac => sha384,
+ prf => sha384};
suite_definition(?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) ->
- {ecdhe_rsa, aes_128_cbc, sha256, sha256};
+ #{key_exchange => ecdhe_rsa,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => sha256};
suite_definition(?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) ->
- {ecdhe_rsa, aes_256_cbc, sha384, sha384};
+ #{key_exchange => ecdhe_rsa,
+ cipher => aes_256_cbc,
+ mac => sha384,
+ prf => sha384};
suite_definition(?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256) ->
- {ecdh_rsa, aes_128_cbc, sha256, sha256};
+ #{key_exchange => ecdh_rsa,
+ cipher => aes_128_cbc,
+ mac => sha256,
+ prf => sha256};
suite_definition(?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384) ->
- {ecdh_rsa, aes_256_cbc, sha384, sha384};
-
+ #{key_exchange => ecdh_rsa,
+ cipher => aes_256_cbc,
+ mac => sha384,
+ prf => sha384};
%% RFC 5288 AES-GCM Cipher Suites
suite_definition(?TLS_RSA_WITH_AES_128_GCM_SHA256) ->
- {rsa, aes_128_gcm, null, sha256};
+ #{key_exchange => rsa,
+ cipher => aes_128_gcm,
+ mac => null,
+ prf => sha256};
suite_definition(?TLS_RSA_WITH_AES_256_GCM_SHA384) ->
- {rsa, aes_256_gcm, null, sha384};
+ #{key_exchange => rsa,
+ cipher => aes_256_gcm,
+ mac => null,
+ prf => sha384};
suite_definition(?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) ->
- {dhe_rsa, aes_128_gcm, null, sha256};
+ #{key_exchange => dhe_rsa,
+ cipher => aes_128_gcm,
+ mac => null,
+ prf => sha256};
suite_definition(?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384) ->
- {dhe_rsa, aes_256_gcm, null, sha384};
+ #{key_exchange => dhe_rsa,
+ cipher => aes_256_gcm,
+ mac => null,
+ prf => sha384};
suite_definition(?TLS_DH_RSA_WITH_AES_128_GCM_SHA256) ->
- {dh_rsa, aes_128_gcm, null, sha256};
+ #{key_exchange => dh_rsa,
+ cipher => aes_128_gcm,
+ mac => null,
+ prf => sha256};
suite_definition(?TLS_DH_RSA_WITH_AES_256_GCM_SHA384) ->
- {dh_rsa, aes_256_gcm, null, sha384};
+ #{key_exchange => dh_rsa,
+ cipher => aes_256_gcm,
+ mac => null,
+ prf => sha384};
suite_definition(?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256) ->
- {dhe_dss, aes_128_gcm, null, sha256};
+ #{key_exchange => dhe_dss,
+ cipher => aes_128_gcm,
+ mac => null,
+ prf => sha256};
suite_definition(?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384) ->
- {dhe_dss, aes_256_gcm, null, sha384};
+ #{key_exchange => dhe_dss,
+ cipher => aes_256_gcm,
+ mac => null,
+ prf => sha384};
suite_definition(?TLS_DH_DSS_WITH_AES_128_GCM_SHA256) ->
- {dh_dss, aes_128_gcm, null, sha256};
+ #{key_exchange => dh_dss,
+ cipher => aes_128_gcm,
+ mac => null,
+ prf => sha256};
suite_definition(?TLS_DH_DSS_WITH_AES_256_GCM_SHA384) ->
- {dh_dss, aes_256_gcm, null, sha384};
+ #{key_exchange => dh_dss,
+ cipher => aes_256_gcm,
+ mac => null,
+ prf => sha384};
suite_definition(?TLS_DH_anon_WITH_AES_128_GCM_SHA256) ->
- {dh_anon, aes_128_gcm, null, sha256};
+ #{key_exchange => dh_anon,
+ cipher => aes_128_gcm,
+ mac => null,
+ prf => sha256};
suite_definition(?TLS_DH_anon_WITH_AES_256_GCM_SHA384) ->
- {dh_anon, aes_256_gcm, null, sha384};
-
+ #{key_exchange => dh_anon,
+ cipher => aes_256_gcm,
+ mac => null,
+ prf => sha384};
%% RFC 5289 ECC AES-GCM Cipher Suites
suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) ->
- {ecdhe_ecdsa, aes_128_gcm, null, sha256};
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_128_gcm,
+ mac => null,
+ prf => sha256};
suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) ->
- {ecdhe_ecdsa, aes_256_gcm, null, sha384};
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => aes_256_gcm,
+ mac => null,
+ prf => sha384};
suite_definition(?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256) ->
- {ecdh_ecdsa, aes_128_gcm, null, sha256};
+ #{key_exchange => ecdh_ecdsa,
+ cipher => aes_128_gcm,
+ mac => null,
+ prf => sha256};
suite_definition(?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384) ->
- {ecdh_ecdsa, aes_256_gcm, null, sha384};
+ #{key_exchange => ecdh_ecdsa,
+ cipher => aes_256_gcm,
+ mac => null,
+ prf => sha384};
suite_definition(?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) ->
- {ecdhe_rsa, aes_128_gcm, null, sha256};
+ #{key_exchange => ecdhe_rsa,
+ cipher => aes_128_gcm,
+ mac => null,
+ prf => sha256};
suite_definition(?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) ->
- {ecdhe_rsa, aes_256_gcm, null, sha384};
+ #{key_exchange => ecdhe_rsa,
+ cipher => aes_256_gcm,
+ mac => null,
+ prf => sha384};
suite_definition(?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256) ->
- {ecdh_rsa, aes_128_gcm, null, sha256};
+ #{key_exchange => ecdh_rsa,
+ cipher => aes_128_gcm,
+ mac => null,
+ prf => sha256};
suite_definition(?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384) ->
- {ecdh_rsa, aes_256_gcm, null, sha384};
-
+ #{key_exchange => ecdh_rsa,
+ cipher => aes_256_gcm,
+ mac => null,
+ prf => sha384};
%% draft-agl-tls-chacha20poly1305-04 Chacha20/Poly1305 Suites
suite_definition(?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256) ->
- {ecdhe_rsa, chacha20_poly1305, null, sha256};
+ #{key_exchange => ecdhe_rsa,
+ cipher => chacha20_poly1305,
+ mac => null,
+ prf => sha256};
suite_definition(?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256) ->
- {ecdhe_ecdsa, chacha20_poly1305, null, sha256};
+ #{key_exchange => ecdhe_ecdsa,
+ cipher => chacha20_poly1305,
+ mac => null,
+ prf => sha256};
suite_definition(?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256) ->
- {dhe_rsa, chacha20_poly1305, null, sha256}.
+ #{key_exchange => dhe_rsa,
+ cipher => chacha20_poly1305,
+ mac => null,
+ prf => sha256}.
%%--------------------------------------------------------------------
--spec erl_suite_definition(cipher_suite()) -> erl_cipher_suite().
+-spec erl_suite_definition(cipher_suite() | erl_cipher_suite()) -> old_erl_cipher_suite().
%%
%% Description: Return erlang cipher suite definition. Filters last value
%% for now (compatibility reasons).
%%--------------------------------------------------------------------
-erl_suite_definition(S) ->
- case suite_definition(S) of
- {KeyExchange, Cipher, Hash, default_prf} ->
+erl_suite_definition(Bin) when is_binary(Bin) ->
+ erl_suite_definition(suite_definition(Bin));
+erl_suite_definition(#{key_exchange := KeyExchange, cipher := Cipher,
+ mac := Hash, prf := Prf}) ->
+ case Prf of
+ default_prf ->
{KeyExchange, Cipher, Hash};
- Suite ->
- Suite
+ _ ->
+ {KeyExchange, Cipher, Hash, Prf}
end.
%%--------------------------------------------------------------------
@@ -819,327 +1208,607 @@ erl_suite_definition(S) ->
%%
%% Description: Return TLS cipher suite definition.
%%--------------------------------------------------------------------
-
%% TLS v1.1 suites
-%%suite({rsa, null, md5}) ->
-%% ?TLS_RSA_WITH_NULL_MD5;
-%%suite({rsa, null, sha}) ->
-%% ?TLS_RSA_WITH_NULL_SHA;
-suite({rsa, rc4_128, md5}) ->
+suite(#{key_exchange := rsa,
+ cipher := rc4_128,
+ mac := md5}) ->
?TLS_RSA_WITH_RC4_128_MD5;
-suite({rsa, rc4_128, sha}) ->
+suite(#{key_exchange := rsa,
+ cipher := rc4_128,
+ mac := sha}) ->
?TLS_RSA_WITH_RC4_128_SHA;
-suite({rsa, des_cbc, sha}) ->
+suite(#{key_exchange := rsa,
+ cipher := des_cbc,
+ mac := sha}) ->
?TLS_RSA_WITH_DES_CBC_SHA;
-suite({rsa, '3des_ede_cbc', sha}) ->
+suite(#{key_exchange := rsa,
+ cipher :='3des_ede_cbc',
+ mac := sha}) ->
?TLS_RSA_WITH_3DES_EDE_CBC_SHA;
-suite({dhe_dss, des_cbc, sha}) ->
+suite(#{key_exchange := dhe_dss,
+ cipher:= des_cbc,
+ mac := sha}) ->
?TLS_DHE_DSS_WITH_DES_CBC_SHA;
-suite({dhe_dss, '3des_ede_cbc', sha}) ->
+suite(#{key_exchange := dhe_dss,
+ cipher:= '3des_ede_cbc',
+ mac := sha}) ->
?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA;
-suite({dhe_rsa, des_cbc, sha}) ->
+suite(#{key_exchange := dhe_rsa,
+ cipher:= des_cbc,
+ mac := sha}) ->
?TLS_DHE_RSA_WITH_DES_CBC_SHA;
-suite({dhe_rsa, '3des_ede_cbc', sha}) ->
+suite(#{key_exchange := dhe_rsa,
+ cipher:= '3des_ede_cbc',
+ mac := sha}) ->
?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA;
-suite({dh_anon, rc4_128, md5}) ->
+suite(#{key_exchange := dh_anon,
+ cipher:= rc4_128,
+ mac := md5}) ->
?TLS_DH_anon_WITH_RC4_128_MD5;
-suite({dh_anon, des_cbc, sha}) ->
+suite(#{key_exchange := dh_anon,
+ cipher:= des_cbc,
+ mac := sha}) ->
?TLS_DH_anon_WITH_DES_CBC_SHA;
-suite({dh_anon, '3des_ede_cbc', sha}) ->
+suite(#{key_exchange := dh_anon,
+ cipher:= '3des_ede_cbc',
+ mac := sha}) ->
?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA;
-
%%% TSL V1.1 AES suites
-suite({rsa, aes_128_cbc, sha}) ->
+suite(#{key_exchange := rsa,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
?TLS_RSA_WITH_AES_128_CBC_SHA;
-suite({dhe_dss, aes_128_cbc, sha}) ->
+suite(#{key_exchange := dhe_dss,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
?TLS_DHE_DSS_WITH_AES_128_CBC_SHA;
-suite({dhe_rsa, aes_128_cbc, sha}) ->
+suite(#{key_exchange := dhe_rsa,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
?TLS_DHE_RSA_WITH_AES_128_CBC_SHA;
-suite({dh_anon, aes_128_cbc, sha}) ->
+suite(#{key_exchange := dh_anon,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
?TLS_DH_anon_WITH_AES_128_CBC_SHA;
-suite({rsa, aes_256_cbc, sha}) ->
+suite(#{key_exchange := rsa,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
?TLS_RSA_WITH_AES_256_CBC_SHA;
-suite({dhe_dss, aes_256_cbc, sha}) ->
+suite(#{key_exchange := dhe_dss,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
?TLS_DHE_DSS_WITH_AES_256_CBC_SHA;
-suite({dhe_rsa, aes_256_cbc, sha}) ->
+suite(#{key_exchange := dhe_rsa,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
?TLS_DHE_RSA_WITH_AES_256_CBC_SHA;
-suite({dh_anon, aes_256_cbc, sha}) ->
+suite(#{key_exchange := dh_anon,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
?TLS_DH_anon_WITH_AES_256_CBC_SHA;
-
%% TLS v1.2 suites
-
-%% suite_definition(?TLS_RSA_WITH_NULL_SHA) ->
-%% {rsa, null, sha, sha256};
-suite({rsa, aes_128_cbc, sha256}) ->
+suite(#{key_exchange := rsa,
+ cipher := aes_128_cbc,
+ mac := sha256}) ->
?TLS_RSA_WITH_AES_128_CBC_SHA256;
-suite({rsa, aes_256_cbc, sha256}) ->
+suite(#{key_exchange := rsa,
+ cipher := aes_256_cbc,
+ mac := sha256}) ->
?TLS_RSA_WITH_AES_256_CBC_SHA256;
-suite({dhe_dss, aes_128_cbc, sha256}) ->
+suite(#{key_exchange := dhe_dss,
+ cipher := aes_128_cbc,
+ mac := sha256}) ->
?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256;
-suite({dhe_rsa, aes_128_cbc, sha256}) ->
+suite(#{key_exchange := dhe_rsa,
+ cipher := aes_128_cbc,
+ mac := sha256}) ->
?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256;
-suite({dhe_dss, aes_256_cbc, sha256}) ->
+suite(#{key_exchange := dhe_dss,
+ cipher := aes_256_cbc,
+ mac := sha256}) ->
?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256;
-suite({dhe_rsa, aes_256_cbc, sha256}) ->
+suite(#{key_exchange := dhe_rsa,
+ cipher := aes_256_cbc,
+ mac := sha256}) ->
?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256;
-suite({dh_anon, aes_128_cbc, sha256}) ->
+suite(#{key_exchange := dh_anon,
+ cipher := aes_128_cbc,
+ mac := sha256}) ->
?TLS_DH_anon_WITH_AES_128_CBC_SHA256;
-suite({dh_anon, aes_256_cbc, sha256}) ->
+suite(#{key_exchange := dh_anon,
+ cipher := aes_256_cbc,
+ mac := sha256}) ->
?TLS_DH_anon_WITH_AES_256_CBC_SHA256;
-
%%% PSK Cipher Suites RFC 4279
-
-suite({psk, rc4_128,sha}) ->
+suite(#{key_exchange := psk,
+ cipher := rc4_128,
+ mac := sha}) ->
?TLS_PSK_WITH_RC4_128_SHA;
-suite({psk, '3des_ede_cbc',sha}) ->
+suite(#{key_exchange := psk,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
?TLS_PSK_WITH_3DES_EDE_CBC_SHA;
-suite({psk, aes_128_cbc,sha}) ->
+suite(#{key_exchange := psk,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
?TLS_PSK_WITH_AES_128_CBC_SHA;
-suite({psk, aes_256_cbc,sha}) ->
+suite(#{key_exchange := psk,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
?TLS_PSK_WITH_AES_256_CBC_SHA;
-suite({dhe_psk, rc4_128,sha}) ->
+suite(#{key_exchange := dhe_psk,
+ cipher := rc4_128,
+ mac := sha}) ->
?TLS_DHE_PSK_WITH_RC4_128_SHA;
-suite({dhe_psk, '3des_ede_cbc',sha}) ->
+suite(#{key_exchange := dhe_psk,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
?TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA;
-suite({dhe_psk, aes_128_cbc,sha}) ->
+suite(#{key_exchange := dhe_psk,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
?TLS_DHE_PSK_WITH_AES_128_CBC_SHA;
-suite({dhe_psk, aes_256_cbc,sha}) ->
+suite(#{key_exchange := dhe_psk,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
?TLS_DHE_PSK_WITH_AES_256_CBC_SHA;
-suite({rsa_psk, rc4_128,sha}) ->
+suite(#{key_exchange := rsa_psk,
+ cipher := rc4_128,
+ mac := sha}) ->
?TLS_RSA_PSK_WITH_RC4_128_SHA;
-suite({rsa_psk, '3des_ede_cbc',sha}) ->
+suite(#{key_exchange := rsa_psk,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
?TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA;
-suite({rsa_psk, aes_128_cbc,sha}) ->
+suite(#{key_exchange := rsa_psk,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
?TLS_RSA_PSK_WITH_AES_128_CBC_SHA;
-suite({rsa_psk, aes_256_cbc,sha}) ->
+suite(#{key_exchange := rsa_psk,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
?TLS_RSA_PSK_WITH_AES_256_CBC_SHA;
-
%%% PSK NULL Cipher Suites RFC 4785
-
-suite({psk, null, sha}) ->
+suite(#{key_exchange := psk,
+ cipher := null,
+ mac := sha}) ->
?TLS_PSK_WITH_NULL_SHA;
-suite({dhe_psk, null, sha}) ->
+suite(#{key_exchange := dhe_psk,
+ cipher := null,
+ mac := sha}) ->
?TLS_DHE_PSK_WITH_NULL_SHA;
-suite({rsa_psk, null, sha}) ->
+suite(#{key_exchange := rsa_psk,
+ cipher := null,
+ mac := sha}) ->
?TLS_RSA_PSK_WITH_NULL_SHA;
-
%%% TLS 1.2 PSK Cipher Suites RFC 5487
-
-suite({psk, aes_128_gcm, null, sha256}) ->
+suite(#{key_exchange := psk,
+ cipher := aes_128_gcm,
+ mac := null,
+ prf := sha256}) ->
?TLS_PSK_WITH_AES_128_GCM_SHA256;
-suite({psk, aes_256_gcm, null, sha384}) ->
+suite(#{key_exchange := psk,
+ cipher := aes_256_gcm,
+ mac := null,
+ prf := sha384}) ->
?TLS_PSK_WITH_AES_256_GCM_SHA384;
-suite({dhe_psk, aes_128_gcm, null, sha256}) ->
+suite(#{key_exchange := dhe_psk,
+ cipher := aes_128_gcm,
+ mac := null,
+ prf := sha256}) ->
?TLS_DHE_PSK_WITH_AES_128_GCM_SHA256;
-suite({dhe_psk, aes_256_gcm, null, sha384}) ->
+suite(#{key_exchange := dhe_psk,
+ cipher := aes_256_gcm,
+ mac := null,
+ prf := sha384}) ->
?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384;
-suite({rsa_psk, aes_128_gcm, null, sha256}) ->
+suite(#{key_exchange := rsa_psk,
+ cipher := aes_128_gcm,
+ mac := null,
+ prf := sha256}) ->
?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256;
-suite({rsa_psk, aes_256_gcm, null, sha384}) ->
+suite(#{key_exchange := rsa_psk,
+ cipher := aes_256_gcm,
+ mac := null,
+ prf := sha384}) ->
?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384;
-
-suite({psk, aes_128_cbc, sha256}) ->
+suite(#{key_exchange := psk,
+ cipher := aes_128_cbc,
+ mac := sha256}) ->
?TLS_PSK_WITH_AES_128_CBC_SHA256;
-suite({psk, aes_256_cbc, sha384}) ->
+suite(#{key_exchange := psk,
+ cipher := aes_256_cbc,
+ mac := sha384}) ->
?TLS_PSK_WITH_AES_256_CBC_SHA384;
-suite({dhe_psk, aes_128_cbc, sha256}) ->
+suite(#{key_exchange := dhe_psk,
+ cipher := aes_128_cbc,
+ mac := sha256}) ->
?TLS_DHE_PSK_WITH_AES_128_CBC_SHA256;
-suite({dhe_psk, aes_256_cbc, sha384}) ->
+suite(#{key_exchange := dhe_psk,
+ cipher := aes_256_cbc,
+ mac := sha384}) ->
?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384;
-suite({rsa_psk, aes_128_cbc, sha256}) ->
+suite(#{key_exchange := rsa_psk,
+ cipher := aes_128_cbc,
+ mac := sha256}) ->
?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256;
-suite({rsa_psk, aes_256_cbc, sha384}) ->
+suite(#{key_exchange := rsa_psk,
+ cipher := aes_256_cbc,
+ mac := sha384}) ->
?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384;
-
-suite({psk, null, sha256}) ->
+suite(#{key_exchange := psk,
+ cipher := null,
+ mac := sha256}) ->
?TLS_PSK_WITH_NULL_SHA256;
-suite({psk, null, sha384}) ->
+suite(#{key_exchange := psk,
+ cipher := null,
+ mac := sha384}) ->
?TLS_PSK_WITH_NULL_SHA384;
-suite({dhe_psk, null, sha256}) ->
+suite(#{key_exchange := dhe_psk,
+ cipher := null,
+ mac := sha256}) ->
?TLS_DHE_PSK_WITH_NULL_SHA256;
-suite({dhe_psk, null, sha384}) ->
+suite(#{key_exchange := dhe_psk,
+ cipher := null,
+ mac := sha384}) ->
?TLS_DHE_PSK_WITH_NULL_SHA384;
-suite({rsa_psk, null, sha256}) ->
+suite(#{key_exchange := rsa_psk,
+ cipher := null,
+ mac := sha256}) ->
?TLS_RSA_PSK_WITH_NULL_SHA256;
-suite({rsa_psk, null, sha384}) ->
+suite(#{key_exchange := rsa_psk,
+ cipher := null,
+ mac := sha384}) ->
?TLS_RSA_PSK_WITH_NULL_SHA384;
-
%%% ECDHE PSK Cipher Suites RFC 5489
-
-suite({ecdhe_psk, rc4_128,sha}) ->
+suite(#{key_exchange := ecdhe_psk,
+ cipher := rc4_128,
+ mac := sha}) ->
?TLS_ECDHE_PSK_WITH_RC4_128_SHA;
-suite({ecdhe_psk, '3des_ede_cbc',sha}) ->
+suite(#{key_exchange := ecdhe_psk,
+ cipher :='3des_ede_cbc',
+ mac := sha}) ->
?TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA;
-suite({ecdhe_psk, aes_128_cbc,sha}) ->
+suite(#{key_exchange := ecdhe_psk,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA;
-suite({ecdhe_psk, aes_256_cbc,sha}) ->
+suite(#{key_exchange := ecdhe_psk,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA;
-suite({ecdhe_psk, aes_128_cbc, sha256}) ->
+suite(#{key_exchange := ecdhe_psk,
+ cipher := aes_128_cbc,
+ mac := sha256}) ->
?TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256;
-suite({ecdhe_psk, aes_256_cbc, sha384}) ->
+suite(#{key_exchange := ecdhe_psk,
+ cipher := aes_256_cbc,
+ mac := sha384}) ->
?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384;
-suite({ecdhe_psk, null, sha256}) ->
+suite(#{key_exchange := ecdhe_psk,
+ cipher := null,
+ mac := sha256}) ->
?TLS_ECDHE_PSK_WITH_NULL_SHA256;
-suite({ecdhe_psk, null, sha384}) ->
+suite(#{key_exchange := ecdhe_psk,
+ cipher := null,
+ mac := sha384}) ->
?TLS_ECDHE_PSK_WITH_NULL_SHA384;
-
%%% ECDHE_PSK with AES-GCM and AES-CCM Cipher Suites, draft-ietf-tls-ecdhe-psk-aead-05
-
-suite({ecdhe_psk, aes_128_gcm, null, sha256}) ->
+suite(#{key_exchange := ecdhe_psk,
+ cipher := aes_128_gcm,
+ mac := null,
+ prf := sha256}) ->
?TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256;
-suite({ecdhe_psk, aes_256_gcm, null, sha384}) ->
+suite(#{key_exchange := ecdhe_psk,
+ cipher := aes_256_gcm,
+ mac := null,
+ prf := sha384}) ->
?TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384;
-%% suite({ecdhe_psk, aes_128_ccm, null, sha256}) ->
-%% ?TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256;
-%% suite({ecdhe_psk, aes_256_ccm, null, sha256}) ->
-%% ?TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256;
-
+ %% suite(#{key_exchange := ecdhe_psk,
+ %% cipher := aes_128_ccm,
+ %% mac := null,
+ %% prf := sha256}) ->
+ %% ?TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256;
+ %% suite(#{key_exchange := ecdhe_psk,
+ %% cipher := aes_256_ccm,
+ %% mac := null,
+ %% prf := sha256}) ->
+ %% ?TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256;
%%% SRP Cipher Suites RFC 5054
-
-suite({srp_anon, '3des_ede_cbc', sha}) ->
+suite(#{key_exchange := srp_anon,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA;
-suite({srp_rsa, '3des_ede_cbc', sha}) ->
+suite(#{key_exchange := srp_rsa,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
?TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA;
-suite({srp_dss, '3des_ede_cbc', sha}) ->
+suite(#{key_exchange := srp_dss,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA;
-suite({srp_anon, aes_128_cbc, sha}) ->
+suite(#{key_exchange := srp_anon,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
?TLS_SRP_SHA_WITH_AES_128_CBC_SHA;
-suite({srp_rsa, aes_128_cbc, sha}) ->
+suite(#{key_exchange := srp_rsa,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
?TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA;
-suite({srp_dss, aes_128_cbc, sha}) ->
+suite(#{key_exchange := srp_dss,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
?TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA;
-suite({srp_anon, aes_256_cbc, sha}) ->
+suite(#{key_exchange := srp_anon,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
?TLS_SRP_SHA_WITH_AES_256_CBC_SHA;
-suite({srp_rsa, aes_256_cbc, sha}) ->
+suite(#{key_exchange := srp_rsa,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA;
-suite({srp_dss, aes_256_cbc, sha}) ->
+suite(#{key_exchange := srp_dss,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA;
-
%%% RFC 4492 EC TLS suites
-suite({ecdh_ecdsa, null, sha}) ->
+suite(#{key_exchange := ecdh_ecdsa,
+ cipher := null,
+ mac := sha}) ->
?TLS_ECDH_ECDSA_WITH_NULL_SHA;
-suite({ecdh_ecdsa, rc4_128, sha}) ->
+suite(#{key_exchange := ecdh_ecdsa,
+ cipher := rc4_128,
+ mac := sha}) ->
?TLS_ECDH_ECDSA_WITH_RC4_128_SHA;
-suite({ecdh_ecdsa, '3des_ede_cbc', sha}) ->
+suite(#{key_exchange := ecdh_ecdsa,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
?TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA;
-suite({ecdh_ecdsa, aes_128_cbc, sha}) ->
+suite(#{key_exchange := ecdh_ecdsa,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA;
-suite({ecdh_ecdsa, aes_256_cbc, sha}) ->
+suite(#{key_exchange := ecdh_ecdsa,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA;
-
-suite({ecdhe_ecdsa, null, sha}) ->
+suite(#{key_exchange := ecdhe_ecdsa,
+ cipher := null,
+ mac := sha}) ->
?TLS_ECDHE_ECDSA_WITH_NULL_SHA;
-suite({ecdhe_ecdsa, rc4_128, sha}) ->
+suite(#{key_exchange := ecdhe_ecdsa,
+ cipher := rc4_128,
+ mac := sha}) ->
?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA;
-suite({ecdhe_ecdsa, '3des_ede_cbc', sha}) ->
+suite(#{key_exchange := ecdhe_ecdsa,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
?TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA;
-suite({ecdhe_ecdsa, aes_128_cbc, sha}) ->
+suite(#{key_exchange := ecdhe_ecdsa,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA;
-suite({ecdhe_ecdsa, aes_256_cbc, sha}) ->
+suite(#{key_exchange := ecdhe_ecdsa,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA;
-
-suite({ecdh_rsa, null, sha}) ->
+suite(#{key_exchange := ecdh_rsa,
+ cipher := null,
+ mac := sha}) ->
?TLS_ECDH_RSA_WITH_NULL_SHA;
-suite({ecdh_rsa, rc4_128, sha}) ->
+suite(#{key_exchange := ecdh_rsa,
+ cipher := rc4_128,
+ mac := sha}) ->
?TLS_ECDH_RSA_WITH_RC4_128_SHA;
-suite({ecdh_rsa, '3des_ede_cbc', sha}) ->
+suite(#{key_exchange := ecdh_rsa,
+ cipher := '3des_ede_cbc', mac := sha}) ->
?TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA;
-suite({ecdh_rsa, aes_128_cbc, sha}) ->
+suite(#{key_exchange := ecdh_rsa,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA;
-suite({ecdh_rsa, aes_256_cbc, sha}) ->
+suite(#{key_exchange := ecdh_rsa,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA;
-
-suite({ecdhe_rsa, null, sha}) ->
+suite(#{key_exchange := ecdhe_rsa,
+ cipher := null,
+ mac := sha}) ->
?TLS_ECDHE_RSA_WITH_NULL_SHA;
-suite({ecdhe_rsa, rc4_128, sha}) ->
+suite(#{key_exchange := ecdhe_rsa,
+ cipher := rc4_128,
+ mac := sha}) ->
?TLS_ECDHE_RSA_WITH_RC4_128_SHA;
-suite({ecdhe_rsa, '3des_ede_cbc', sha}) ->
+suite(#{key_exchange := ecdhe_rsa,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
?TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA;
-suite({ecdhe_rsa, aes_128_cbc, sha}) ->
+suite(#{key_exchange := ecdhe_rsa,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA;
-suite({ecdhe_rsa, aes_256_cbc, sha}) ->
+suite(#{key_exchange := ecdhe_rsa,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA;
-
-suite({ecdh_anon, null, sha}) ->
+suite(#{key_exchange := ecdh_anon,
+ cipher := null,
+ mac := sha}) ->
?TLS_ECDH_anon_WITH_NULL_SHA;
-suite({ecdh_anon, rc4_128, sha}) ->
+suite(#{key_exchange := ecdh_anon,
+ cipher := rc4_128,
+ mac := sha}) ->
?TLS_ECDH_anon_WITH_RC4_128_SHA;
-suite({ecdh_anon, '3des_ede_cbc', sha}) ->
+suite(#{key_exchange := ecdh_anon,
+ cipher := '3des_ede_cbc',
+ mac := sha}) ->
?TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA;
-suite({ecdh_anon, aes_128_cbc, sha}) ->
+suite(#{key_exchange := ecdh_anon,
+ cipher := aes_128_cbc,
+ mac := sha}) ->
?TLS_ECDH_anon_WITH_AES_128_CBC_SHA;
-suite({ecdh_anon, aes_256_cbc, sha}) ->
+suite(#{key_exchange := ecdh_anon,
+ cipher := aes_256_cbc,
+ mac := sha}) ->
?TLS_ECDH_anon_WITH_AES_256_CBC_SHA;
-
%%% RFC 5289 EC TLS suites
-suite({ecdhe_ecdsa, aes_128_cbc, sha256, sha256}) ->
+suite(#{key_exchange := ecdhe_ecdsa,
+ cipher := aes_128_cbc,
+ mac:= sha256,
+ prf := sha256}) ->
?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256;
-suite({ecdhe_ecdsa, aes_256_cbc, sha384, sha384}) ->
+suite(#{key_exchange := ecdhe_ecdsa,
+ cipher := aes_256_cbc,
+ mac := sha384,
+ prf := sha384}) ->
?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384;
-suite({ecdh_ecdsa, aes_128_cbc, sha256, sha256}) ->
+suite(#{key_exchange := ecdh_ecdsa,
+ cipher := aes_128_cbc,
+ mac := sha256,
+ prf := sha256}) ->
?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256;
-suite({ecdh_ecdsa, aes_256_cbc, sha384, sha384}) ->
+suite(#{key_exchange := ecdh_ecdsa,
+ cipher := aes_256_cbc,
+ mac := sha384,
+ prf := sha384}) ->
?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384;
-suite({ecdhe_rsa, aes_128_cbc, sha256, sha256}) ->
+suite(#{key_exchange := ecdhe_rsa,
+ cipher := aes_128_cbc,
+ mac := sha256,
+ prf := sha256}) ->
?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256;
-suite({ecdhe_rsa, aes_256_cbc, sha384, sha384}) ->
+suite(#{key_exchange := ecdhe_rsa,
+ cipher := aes_256_cbc,
+ mac := sha384,
+ prf := sha384}) ->
?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384;
-suite({ecdh_rsa, aes_128_cbc, sha256, sha256}) ->
+suite(#{key_exchange := ecdh_rsa,
+ cipher := aes_128_cbc,
+ mac := sha256,
+ prf := sha256}) ->
?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256;
-suite({ecdh_rsa, aes_256_cbc, sha384, sha384}) ->
+suite(#{key_exchange := ecdh_rsa,
+ cipher := aes_256_cbc,
+ mac := sha384,
+ prf := sha384}) ->
?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384;
-
%% RFC 5288 AES-GCM Cipher Suites
-suite({rsa, aes_128_gcm, null, sha256}) ->
+suite(#{key_exchange := rsa,
+ cipher := aes_128_gcm,
+ mac := null,
+ prf := sha256}) ->
?TLS_RSA_WITH_AES_128_GCM_SHA256;
-suite({rsa, aes_256_gcm, null, sha384}) ->
+suite(#{key_exchange := rsa,
+ cipher := aes_256_gcm,
+ mac := null,
+ prf := sha384}) ->
?TLS_RSA_WITH_AES_256_GCM_SHA384;
-suite({dhe_rsa, aes_128_gcm, null, sha256}) ->
+suite(#{key_exchange := dhe_rsa,
+ cipher := aes_128_gcm,
+ mac := null,
+ prf := sha256}) ->
?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256;
-suite({dhe_rsa, aes_256_gcm, null, sha384}) ->
+suite(#{key_exchange := dhe_rsa,
+ cipher := aes_256_gcm,
+ mac := null,
+ prf := sha384}) ->
?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384;
-suite({dh_rsa, aes_128_gcm, null, sha256}) ->
+suite(#{key_exchange := dh_rsa,
+ cipher := aes_128_gcm,
+ mac := null,
+ prf := sha256}) ->
?TLS_DH_RSA_WITH_AES_128_GCM_SHA256;
-suite({dh_rsa, aes_256_gcm, null, sha384}) ->
+suite(#{key_exchange := dh_rsa,
+ cipher := aes_256_gcm,
+ mac := null,
+ prf := sha384}) ->
?TLS_DH_RSA_WITH_AES_256_GCM_SHA384;
-suite({dhe_dss, aes_128_gcm, null, sha256}) ->
+suite(#{key_exchange := dhe_dss,
+ cipher := aes_128_gcm,
+ mac := null,
+ prf := sha256}) ->
?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256;
-suite({dhe_dss, aes_256_gcm, null, sha384}) ->
+suite(#{key_exchange := dhe_dss,
+ cipher := aes_256_gcm,
+ mac := null,
+ prf := sha384}) ->
?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384;
-suite({dh_dss, aes_128_gcm, null, sha256}) ->
+suite(#{key_exchange := dh_dss,
+ cipher := aes_128_gcm,
+ mac := null,
+ prf := sha256}) ->
?TLS_DH_DSS_WITH_AES_128_GCM_SHA256;
-suite({dh_dss, aes_256_gcm, null, sha384}) ->
+suite(#{key_exchange := dh_dss,
+ cipher := aes_256_gcm,
+ mac := null,
+ prf := sha384}) ->
?TLS_DH_DSS_WITH_AES_256_GCM_SHA384;
-suite({dh_anon, aes_128_gcm, null, sha256}) ->
+suite(#{key_exchange := dh_anon,
+ cipher := aes_128_gcm,
+ mac := null,
+ prf := sha256}) ->
?TLS_DH_anon_WITH_AES_128_GCM_SHA256;
-suite({dh_anon, aes_256_gcm, null, sha384}) ->
+suite(#{key_exchange := dh_anon,
+ cipher := aes_256_gcm,
+ mac := null,
+ prf := sha384}) ->
?TLS_DH_anon_WITH_AES_256_GCM_SHA384;
-
%% RFC 5289 ECC AES-GCM Cipher Suites
-suite({ecdhe_ecdsa, aes_128_gcm, null, sha256}) ->
+suite(#{key_exchange := ecdhe_ecdsa,
+ cipher := aes_128_gcm,
+ mac := null,
+ prf := sha256}) ->
?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256;
-suite({ecdhe_ecdsa, aes_256_gcm, null, sha384}) ->
+suite(#{key_exchange := ecdhe_ecdsa,
+ cipher := aes_256_gcm,
+ mac := null,
+ prf := sha384}) ->
?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384;
-suite({ecdh_ecdsa, aes_128_gcm, null, sha256}) ->
+suite(#{key_exchange := ecdh_ecdsa,
+ cipher := aes_128_gcm,
+ mac := null,
+ prf := sha256}) ->
?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256;
-suite({ecdh_ecdsa, aes_256_gcm, null, sha384}) ->
+suite(#{key_exchange := ecdh_ecdsa,
+ cipher := aes_256_gcm,
+ mac := null,
+ prf := sha384}) ->
?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384;
-suite({ecdhe_rsa, aes_128_gcm, null, sha256}) ->
+suite(#{key_exchange := ecdhe_rsa,
+ cipher := aes_128_gcm,
+ mac := null,
+ prf := sha256}) ->
?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256;
-suite({ecdhe_rsa, aes_256_gcm, null, sha384}) ->
+suite(#{key_exchange := ecdhe_rsa,
+ cipher := aes_256_gcm,
+ mac := null,
+ prf := sha384}) ->
?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384;
-suite({ecdh_rsa, aes_128_gcm, null, sha256}) ->
+suite(#{key_exchange := ecdh_rsa,
+ cipher := aes_128_gcm,
+ mac := null,
+ prf := sha256}) ->
?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256;
-suite({ecdh_rsa, aes_256_gcm, null, sha384}) ->
+suite(#{key_exchange := ecdh_rsa,
+ cipher := aes_256_gcm,
+ mac := null,
+ prf := sha384}) ->
?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384;
-
-
%% draft-agl-tls-chacha20poly1305-04 Chacha20/Poly1305 Suites
-suite({ecdhe_rsa, chacha20_poly1305, null, sha256}) ->
+suite(#{key_exchange := ecdhe_rsa,
+ cipher := chacha20_poly1305,
+ mac := null,
+ prf := sha256}) ->
?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256;
-suite({ecdhe_ecdsa, chacha20_poly1305, null, sha256}) ->
+suite(#{key_exchange := ecdhe_ecdsa,
+ cipher := chacha20_poly1305,
+ mac := null,
+ prf := sha256}) ->
?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256;
-suite({dhe_rsa, chacha20_poly1305, null, sha256}) ->
+suite(#{key_exchange := dhe_rsa,
+ cipher := chacha20_poly1305,
+ mac := null,
+ prf := sha256}) ->
?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256.
%%--------------------------------------------------------------------
@@ -1516,14 +2185,13 @@ filter(DerCert, Ciphers) ->
%%
%% Description: Filter suites for algorithms supported by crypto.
%%-------------------------------------------------------------------
-filter_suites(Suites = [Value|_]) when is_tuple(Value) ->
+filter_suites(Suites = [Value|_]) when is_map(Value) ->
Algos = crypto:supports(),
Hashs = proplists:get_value(hashs, Algos),
- lists:filter(fun({KeyExchange, Cipher, Hash}) ->
- is_acceptable_keyexchange(KeyExchange, proplists:get_value(public_keys, Algos)) andalso
- is_acceptable_cipher(Cipher, proplists:get_value(ciphers, Algos)) andalso
- is_acceptable_hash(Hash, proplists:get_value(hashs, Algos));
- ({KeyExchange, Cipher, Hash, Prf}) ->
+ lists:filter(fun(#{key_exchange := KeyExchange,
+ cipher := Cipher,
+ mac := Hash,
+ prf := Prf}) ->
is_acceptable_keyexchange(KeyExchange, proplists:get_value(public_keys, Algos)) andalso
is_acceptable_cipher(Cipher, proplists:get_value(ciphers, Algos)) andalso
is_acceptable_hash(Hash, Hashs) andalso
@@ -1534,9 +2202,12 @@ filter_suites(Suites) ->
Algos = crypto:supports(),
Hashs = proplists:get_value(hashs, Algos),
lists:filter(fun(Suite) ->
- {KeyExchange, Cipher, Hash, Prf} = ssl_cipher:suite_definition(Suite),
+ #{key_exchange := KeyExchange,
+ cipher := Cipher,
+ mac := Hash,
+ prf := Prf} = suite_definition(Suite),
is_acceptable_keyexchange(KeyExchange, proplists:get_value(public_keys, Algos)) andalso
- is_acceptable_cipher(Cipher, proplists:get_value(ciphers, Algos)) andalso
+ is_acceptable_cipher(Cipher, proplists:get_value(ciphers, Algos)) andalso
is_acceptable_hash(Hash, Hashs) andalso
is_acceptable_prf(Prf, Hashs)
end, Suites).
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index b1efcbb857..d046145dff 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -49,7 +49,7 @@
%% Alert and close handling
-export([handle_own_alert/4, handle_alert/3,
- handle_normal_shutdown/3
+ handle_normal_shutdown/3, stop/2, stop_and_reply/3
]).
%% Data handling
@@ -316,7 +316,7 @@ handle_own_alert(Alert, Version, StateName,
catch _:_ ->
ok
end,
- {stop, {shutdown, own_alert}}.
+ stop({shutdown, own_alert}, State).
handle_normal_shutdown(Alert, _, #state{socket = Socket,
transport_cb = Transport,
@@ -340,24 +340,24 @@ handle_alert(#alert{level = ?FATAL} = Alert, StateName,
protocol_cb = Connection,
ssl_options = SslOpts, start_or_recv_from = From, host = Host,
port = Port, session = Session, user_application = {_Mon, Pid},
- role = Role, socket_options = Opts, tracker = Tracker}) ->
+ role = Role, socket_options = Opts, tracker = Tracker} = State) ->
invalidate_session(Role, Host, Port, Session),
log_alert(SslOpts#ssl_options.log_alert, Role, Connection:protocol_name(),
StateName, Alert#alert{role = opposite_role(Role)}),
alert_user(Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role, Connection),
- {stop, normal};
+ stop(normal, State);
handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
StateName, State) ->
handle_normal_shutdown(Alert, StateName, State),
- {stop, {shutdown, peer_close}};
+ stop({shutdown, peer_close}, State);
handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
#state{role = Role, ssl_options = SslOpts, protocol_cb = Connection, renegotiation = {true, internal}} = State) ->
log_alert(SslOpts#ssl_options.log_alert, Role,
Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
handle_normal_shutdown(Alert, StateName, State),
- {stop, {shutdown, peer_close}};
+ stop({shutdown, peer_close}, State);
handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
#state{role = Role,
@@ -404,7 +404,7 @@ write_application_data(Data0, {FromPid, _} = From,
ok when FromPid =:= self() ->
hibernate_after(connection, NewState, []);
Error when FromPid =:= self() ->
- {stop, {shutdown, Error}, NewState};
+ stop({shutdown, Error}, NewState);
ok ->
hibernate_after(connection, NewState, [{reply, From, ok}]);
Result ->
@@ -446,8 +446,8 @@ read_application_data(Data, #state{user_application = {_Mon, Pid},
Connection:next_record_if_active(State);
_ -> %% We have more data
read_application_data(<<>>, State)
- catch _:Reason ->
- death_row(State, Reason)
+ catch error:_ ->
+ death_row(State, disconnect)
end;
_ ->
SocketOpt =
@@ -479,7 +479,7 @@ read_application_data(Data, #state{user_application = {_Mon, Pid},
Connection:next_record_if_active(State0#state{user_data_buffer = Buffer});
{error,_Reason} -> %% Invalid packet in packet mode
deliver_packet_error(Transport, Socket, SOpts, Buffer1, Pid, RecvFrom, Tracker, Connection),
- stop_normal(State0)
+ stop(normal, State0)
end.
%%====================================================================
%% Help functions for tls|dtls_connection.erl
@@ -495,7 +495,7 @@ handle_session(#server_hello{cipher_suite = CipherSuite,
#state{session = #session{session_id = OldId},
negotiated_version = ReqVersion,
negotiated_protocol = CurrentProtocol} = State0) ->
- {KeyAlgorithm, _, _, _} =
+ #{key_exchange := KeyAlgorithm} =
ssl_cipher:suite_definition(CipherSuite),
PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm),
@@ -581,7 +581,7 @@ init({call, From}, {start, {Opts, EmOpts}, Timeout},
init({call, From}, {start, Timeout},
State#state{ssl_options = SslOpts, socket_options = new_emulated(EmOpts, SockOpts)}, Connection)
catch throw:Error ->
- {stop_and_reply, normal, {reply, From, {error, Error}}}
+ stop_and_reply(normal, {reply, From, {error, Error}}, State0)
end;
init({call, From}, Msg, State, Connection) ->
handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
@@ -966,7 +966,7 @@ connection({call, {FromPid, _} = From}, {application_data, Data},
catch throw:Error ->
case self() of
FromPid ->
- {stop, {shutdown, Error}};
+ stop({shutdown, Error}, State);
_ ->
hibernate_after(
?FUNCTION_NAME, State, [{reply, From, Error}])
@@ -1017,8 +1017,8 @@ connection(
ProtocolSpecific#{d_handle => DHandle}},
{Record, NewerState} = Connection:next_record_if_active(NewState),
Connection:next_event(connection, Record, NewerState, [{reply, From, ok}])
- catch _:Reason ->
- death_row(State, Reason)
+ catch error:_ ->
+ death_row(State, disconnect)
end;
connection({call, From}, Msg, State, Connection) ->
handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
@@ -1030,10 +1030,24 @@ connection(
_) ->
eat_msgs(Msg),
try send_dist_data(?FUNCTION_NAME, State, DHandle, [])
- catch _:Reason ->
- death_row(State, Reason)
+ catch error:_ ->
+ death_row(State, disconnect)
end;
connection(
+ info, {send, From, Ref, Data},
+ #state{
+ ssl_options = #ssl_options{erl_dist = true},
+ protocol_specific = #{d_handle := _}},
+ _) ->
+ %% This is for testing only!
+ %%
+ %% Needed by some OTP distribution
+ %% test suites...
+ From ! {Ref, ok},
+ {keep_state_and_data,
+ [{next_event, {call, {self(), undefined}},
+ {application_data, iolist_to_binary(Data)}}]};
+connection(
info, tick = Msg,
#state{
ssl_options = #ssl_options{erl_dist = true},
@@ -1058,20 +1072,22 @@ connection(Type, Msg, State, Connection) ->
%% or the socket may die too
death_row(
info, {'DOWN', MonitorRef, _, _, Reason},
- #state{user_application={MonitorRef,_Pid} = State},
+ #state{user_application={MonitorRef,_Pid}},
_) ->
- {stop, {shutdown, Reason}, State};
+ {stop, {shutdown, Reason}};
death_row(
- info, {'EXIT', Socket, Reason}, #state{socket = Socket} = State, _) ->
- {stop, {shutdown, Reason}, State};
+ info, {'EXIT', Socket, Reason}, #state{socket = Socket}, _) ->
+ {stop, {shutdown, Reason}};
death_row(state_timeout, Reason, _State, _Connection) ->
{stop, {shutdown,Reason}};
-death_row(_Type, _Msg, State, _Connection) ->
- {keep_state, State, [postpone]}.
+death_row(_Type, _Msg, _State, _Connection) ->
+ %% Waste all other events
+ keep_state_and_data.
%% State entry function
death_row(State, Reason) ->
- {next_state, death_row, State, [{state_timeout, 5000, Reason}]}.
+ {next_state, death_row, State,
+ [{state_timeout, 5000, Reason}]}.
%%--------------------------------------------------------------------
-spec downgrade(gen_statem:event_type(), term(),
@@ -1084,10 +1100,10 @@ downgrade(internal, #alert{description = ?CLOSE_NOTIFY},
tls_socket:setopts(Transport, Socket, [{active, false}, {packet, 0}, {mode, binary}]),
Transport:controlling_process(Socket, Pid),
gen_statem:reply(From, {ok, Socket}),
- stop_normal(State);
+ stop(normal, State);
downgrade(timeout, downgrade, #state{downgrade = {_, From}} = State, _) ->
gen_statem:reply(From, {error, timeout}),
- stop_normal(State);
+ stop(normal, State);
downgrade(Type, Event, State, Connection) ->
handle_common_event(Type, Event, ?FUNCTION_NAME, State, Connection).
@@ -1102,7 +1118,7 @@ handle_common_event(internal, {handshake, {#hello_request{} = Handshake, _}}, co
handle_common_event(internal, {handshake, {#hello_request{}, _}}, StateName, #state{role = client}, _)
when StateName =/= connection ->
{keep_state_and_data};
-handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName,
+handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName,
#state{tls_handshake_history = Hs0,
ssl_options = #ssl_options{v2_hello_compatible = V2HComp}} = State0,
Connection) ->
@@ -1121,8 +1137,8 @@ handle_common_event(timeout, hibernate, _, _, _) ->
{keep_state_and_data, [hibernate]};
handle_common_event(internal, {application_data, Data}, StateName, State0, Connection) ->
case read_application_data(Data, State0) of
- {stop, Reason, State} ->
- {stop, Reason, State};
+ {stop, _, _} = Stop->
+ Stop;
{Record, State} ->
Connection:next_event(StateName, Record, State)
end;
@@ -1151,8 +1167,9 @@ handle_call({close, _} = Close, From, StateName, State, Connection) ->
%% Run terminate before returning so that the reuseaddr
%% inet-option works properly
Result = Connection:terminate(Close, StateName, State#state{terminated = true}),
- {stop_and_reply, {shutdown, normal},
- {reply, From, Result}, State};
+ stop_and_reply(
+ {shutdown, normal},
+ {reply, From, Result}, State);
handle_call({shutdown, How0}, From, _,
#state{transport_cb = Transport,
negotiated_version = Version,
@@ -1173,7 +1190,7 @@ handle_call({shutdown, How0}, From, _,
{keep_state_and_data, [{reply, From, ok}]};
Error ->
gen_statem:reply(From, {error, Error}),
- stop_normal(State)
+ stop(normal, State)
end;
handle_call({recv, _N, _Timeout}, From, _,
#state{socket_options =
@@ -1253,33 +1270,50 @@ handle_info({ErrorTag, Socket, econnaborted}, StateName,
tracker = Tracker} = State) when StateName =/= connection ->
alert_user(Transport, Tracker,Socket,
StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, Connection),
- stop_normal(State);
+ stop(normal, State);
handle_info({ErrorTag, Socket, Reason}, StateName, #state{socket = Socket,
error_tag = ErrorTag} = State) ->
Report = io_lib:format("SSL: Socket error: ~p ~n", [Reason]),
error_logger:error_report(Report),
handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
- stop_normal(State);
+ stop(normal, State);
handle_info(
+ {'DOWN', MonitorRef, _, _, Reason}, _,
+ #state{
+ user_application = {MonitorRef, _Pid},
+ ssl_options = #ssl_options{erl_dist = true}}) ->
+ {stop, {shutdown, Reason}};
+handle_info(
{'DOWN', MonitorRef, _, _, _}, _,
- #state{user_application={MonitorRef,_Pid}} = State) ->
- stop_normal(State);
+ #state{user_application = {MonitorRef, _Pid}}) ->
+ {stop, normal};
+handle_info(
+ {'EXIT', Pid, _Reason}, StateName,
+ #state{user_application = {_MonitorRef, Pid}} = State) ->
+ %% It seems the user application has linked to us
+ %% - ignore that and let the monitor handle this
+ {next_state, StateName, State};
+
%%% So that terminate will be run when supervisor issues shutdown
handle_info({'EXIT', _Sup, shutdown}, _StateName, State) ->
- {stop, shutdown, State};
+ stop(shutdown, State);
handle_info({'EXIT', Socket, normal}, _StateName, #state{socket = Socket} = State) ->
%% Handle as transport close"
- {stop, {shutdown, transport_closed}, State};
+ stop({shutdown, transport_closed}, State);
handle_info({'EXIT', Socket, Reason}, _StateName, #state{socket = Socket} = State) ->
- {stop, {shutdown, Reason}, State};
+ stop({shutdown, Reason}, State);
+
handle_info(allow_renegotiate, StateName, State) ->
{next_state, StateName, State#state{allow_renegotiate = true}};
+
handle_info({cancel_start_or_recv, StartFrom}, StateName,
#state{renegotiation = {false, first}} = State) when StateName =/= connection ->
- {stop_and_reply, {shutdown, user_timeout},
- {reply, StartFrom, {error, timeout}}, State#state{timer = undefined}};
+ stop_and_reply(
+ {shutdown, user_timeout},
+ {reply, StartFrom, {error, timeout}},
+ State#state{timer = undefined});
handle_info({cancel_start_or_recv, RecvFrom}, StateName,
#state{start_or_recv_from = RecvFrom} = State) when RecvFrom =/= undefined ->
{next_state, StateName, State#state{start_or_recv_from = undefined,
@@ -1372,9 +1406,9 @@ connection_info(#state{sni_hostname = SNIHostname,
negotiated_version = {_,_} = Version,
ssl_options = Opts}) ->
RecordCB = record_cb(Connection),
- CipherSuiteDef = ssl_cipher:erl_suite_definition(CipherSuite),
- IsNamedCurveSuite = lists:member(element(1,CipherSuiteDef),
- [ecdh_ecdsa, ecdhe_ecdsa, ecdh_anon]),
+ CipherSuiteDef = #{key_exchange := KexAlg} = ssl_cipher:suite_definition(CipherSuite),
+ IsNamedCurveSuite = lists:member(KexAlg,
+ [ecdh_ecdsa, ecdhe_ecdsa, ecdh_anon]),
CurveInfo = case ECCCurve of
{namedCurve, Curve} when IsNamedCurveSuite ->
[{ecc, {named_curve, pubkey_cert_records:namedCurves(Curve)}}];
@@ -1383,7 +1417,7 @@ connection_info(#state{sni_hostname = SNIHostname,
end,
[{protocol, RecordCB:protocol_version(Version)},
{session_id, SessionId},
- {cipher_suite, CipherSuiteDef},
+ {cipher_suite, ssl_cipher:erl_suite_definition(CipherSuiteDef)},
{sni_hostname, SNIHostname} | CurveInfo] ++ ssl_options_list(Opts).
security_info(#state{connection_states = ConnectionStates}) ->
@@ -1451,7 +1485,7 @@ resumed_server_hello(#state{session = Session,
server_hello(ServerHello, State0, Connection) ->
CipherSuite = ServerHello#server_hello.cipher_suite,
- {KeyAlgorithm, _, _, _} = ssl_cipher:suite_definition(CipherSuite),
+ #{key_exchange := KeyAlgorithm} = ssl_cipher:suite_definition(CipherSuite),
State = Connection:queue_handshake(ServerHello, State0),
State#state{key_algorithm = KeyAlgorithm}.
@@ -1465,8 +1499,8 @@ handle_peer_cert(Role, PeerCert, PublicKeyInfo,
State1 = State0#state{session =
Session#session{peer_certificate = PeerCert},
public_key_info = PublicKeyInfo},
- {KeyAlg,_,_,_} = ssl_cipher:suite_definition(CipherSuite),
- State2 = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlg, State1),
+ #{key_exchange := KeyAlgorithm} = ssl_cipher:suite_definition(CipherSuite),
+ State2 = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1),
{Record, State} = Connection:next_record(State2),
Connection:next_event(certify, Record, State).
@@ -1548,24 +1582,24 @@ server_certify_and_key_exchange(State0, Connection) ->
certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS},
#state{private_key = Key, client_hello_version = {Major, Minor} = Version} = State, Connection) ->
-
+ FakeSecret = make_premaster_secret(Version, rsa),
%% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret
%% and fail handshake later.RFC 5246 section 7.4.7.1.
PremasterSecret =
try ssl_handshake:premaster_secret(EncPMS, Key) of
Secret when erlang:byte_size(Secret) == ?NUM_OF_PREMASTERSECRET_BYTES ->
case Secret of
- <<?BYTE(Major), ?BYTE(Minor), _/binary>> -> %% Correct
- Secret;
+ <<?BYTE(Major), ?BYTE(Minor), Rest/binary>> -> %% Correct
+ <<?BYTE(Major), ?BYTE(Minor), Rest/binary>>;
<<?BYTE(_), ?BYTE(_), Rest/binary>> -> %% Version mismatch
<<?BYTE(Major), ?BYTE(Minor), Rest/binary>>
end;
_ -> %% erlang:byte_size(Secret) =/= ?NUM_OF_PREMASTERSECRET_BYTES
- make_premaster_secret(Version, rsa)
+ FakeSecret
catch
#alert{description = ?DECRYPT_ERROR} ->
- make_premaster_secret(Version, rsa)
- end,
+ FakeSecret
+ end,
calculate_master_secret(PremasterSecret, State, Connection, certify, cipher);
certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey},
#state{diffie_hellman_params = #'DHParameter'{} = Params,
@@ -2423,8 +2457,8 @@ handle_active_option(_, connection = StateName0, To, Reply, #state{protocol_cb =
hibernate_after(StateName, State, [{reply, To, Reply}]);
{next_state, StateName, State, Actions} ->
hibernate_after(StateName, State, [{reply, To, Reply} | Actions]);
- {stop, Reason, State} ->
- {stop, Reason, State}
+ {stop, _, _} = Stop ->
+ Stop
end;
handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = <<>>} = State) ->
%% Active once already set
@@ -2433,8 +2467,8 @@ handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = <<>>} =
%% user_data_buffer =/= <<>>
handle_active_option(_, StateName0, To, Reply, #state{protocol_cb = Connection} = State0) ->
case read_application_data(<<>>, State0) of
- {stop, Reason, State} ->
- {stop, Reason, State};
+ {stop, _, _} = Stop ->
+ Stop;
{Record, State1} ->
%% Note: Renogotiation may cause StateName0 =/= StateName
case Connection:next_event(StateName0, Record, State1) of
@@ -2592,7 +2626,8 @@ send_or_reply(_, Pid, _From, Data) ->
send_user(Pid, Data).
send_user(Pid, Msg) ->
- Pid ! Msg.
+ Pid ! Msg,
+ ok.
alert_user(Transport, Tracker, Socket, connection, Opts, Pid, From, Alert, Role, Connection) ->
alert_user(Transport, Tracker, Socket, Opts#socket_options.active, Pid, From, Alert, Role, Connection);
@@ -2705,14 +2740,22 @@ eat_msgs(Msg) ->
after 0 -> ok
end.
-%% When running with erl_dist the stop reason 'normal'
-%% would be too silent and prevent cleanup
-stop_normal(State) ->
- Reason =
- case State of
- #state{ssl_options = #ssl_options{erl_dist = true}} ->
- {shutdown, normal};
- _ ->
- normal
- end,
- {stop, Reason, State}.
+%% When acting as distribution controller map the exit reason
+%% to follow the documented nodedown_reason for net_kernel
+stop(Reason, State) ->
+ {stop, erl_dist_stop_reason(Reason, State), State}.
+
+stop_and_reply(Reason, Replies, State) ->
+ {stop_and_reply, erl_dist_stop_reason(Reason, State), Replies, State}.
+
+erl_dist_stop_reason(
+ Reason, #state{ssl_options = #ssl_options{erl_dist = true}}) ->
+ case Reason of
+ normal ->
+ %% We can not exit with normal since that will not bring
+ %% down the rest of the distribution processes
+ {shutdown, normal};
+ _ -> Reason
+ end;
+erl_dist_stop_reason(Reason, _State) ->
+ Reason.
diff --git a/lib/ssl/src/ssl_dist_sup.erl b/lib/ssl/src/ssl_dist_sup.erl
index c241a9bced..bea67935d8 100644
--- a/lib/ssl/src/ssl_dist_sup.erl
+++ b/lib/ssl/src/ssl_dist_sup.erl
@@ -30,6 +30,9 @@
%% Supervisor callback
-export([init/1]).
+%% Debug
+-export([consult/1]).
+
%%%=========================================================================
%%% API
%%%=========================================================================
@@ -37,7 +40,18 @@
-spec start_link() -> {ok, pid()} | ignore | {error, term()}.
start_link() ->
- supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+ case init:get_argument(ssl_dist_optfile) of
+ {ok, [File]} ->
+ DistOpts = consult(File),
+ TabOpts = [set, protected, named_table],
+ Tab = ets:new(ssl_dist_opts, TabOpts),
+ true = ets:insert(Tab, DistOpts),
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []);
+ {ok, BadArg} ->
+ error({bad_ssl_dist_optfile, BadArg});
+ error ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, [])
+ end.
%%%=========================================================================
%%% Supervisor callback
@@ -68,3 +82,52 @@ ssl_connection_sup() ->
Modules = [ssl_connection_sup],
Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+consult(File) ->
+ case erl_prim_loader:get_file(File) of
+ {ok, Binary, _FullName} ->
+ Encoding =
+ case epp:read_encoding_from_binary(Binary) of
+ none -> latin1;
+ Enc -> Enc
+ end,
+ case unicode:characters_to_list(Binary, Encoding) of
+ {error, _String, Rest} ->
+ error(
+ {bad_ssl_dist_optfile, {encoding_error, Rest}});
+ {incomplete, _String, Rest} ->
+ error(
+ {bad_ssl_dist_optfile, {encoding_incomplete, Rest}});
+ String when is_list(String) ->
+ consult_string(String)
+ end;
+ error ->
+ error({bad_ssl_dist_optfile, File})
+ end.
+
+consult_string(String) ->
+ case erl_scan:string(String) of
+ {error, Info, Location} ->
+ error({bad_ssl_dist_optfile, {scan_error, Info, Location}});
+ {ok, Tokens, _EndLocation} ->
+ consult_tokens(Tokens)
+ end.
+
+consult_tokens(Tokens) ->
+ case erl_parse:parse_exprs(Tokens) of
+ {error, Info} ->
+ error({bad_ssl_dist_optfile, {parse_error, Info}});
+ {ok, [Expr]} ->
+ consult_expr(Expr);
+ {ok, Other} ->
+ error({bad_ssl_dist_optfile, {parse_error, Other}})
+ end.
+
+consult_expr(Expr) ->
+ {value, Value, Bs} = erl_eval:expr(Expr, erl_eval:new_bindings()),
+ case erl_eval:bindings(Bs) of
+ [] ->
+ Value;
+ Other ->
+ error({bad_ssl_dist_optfile, {bindings, Other}})
+ end.
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index 24f3a97b9b..61d61b53dd 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -1184,19 +1184,22 @@ certificate_types(_, {N, M}) when N >= 3 andalso M >= 3 ->
false ->
<<?BYTE(?RSA_SIGN), ?BYTE(?DSS_SIGN)>>
end;
-certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == rsa;
- KeyExchange == dh_rsa;
- KeyExchange == dhe_rsa;
- KeyExchange == ecdhe_rsa ->
+
+certificate_types(#{key_exchange := KeyExchange}, _) when KeyExchange == rsa;
+ KeyExchange == dh_rsa;
+ KeyExchange == dhe_rsa;
+ KeyExchange == ecdhe_rsa ->
<<?BYTE(?RSA_SIGN)>>;
-certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == dh_dss;
- KeyExchange == dhe_dss;
- KeyExchange == srp_dss ->
+
+certificate_types(#{key_exchange := KeyExchange}, _) when KeyExchange == dh_dss;
+ KeyExchange == dhe_dss;
+ KeyExchange == srp_dss ->
<<?BYTE(?DSS_SIGN)>>;
-certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == dh_ecdsa;
- KeyExchange == dhe_ecdsa;
- KeyExchange == ecdh_ecdsa;
- KeyExchange == ecdhe_ecdsa ->
+
+certificate_types(#{key_exchange := KeyExchange}, _) when KeyExchange == dh_ecdsa;
+ KeyExchange == dhe_ecdsa;
+ KeyExchange == ecdh_ecdsa;
+ KeyExchange == ecdhe_ecdsa ->
<<?BYTE(?ECDSA_SIGN)>>;
certificate_types(_, _) ->
<<?BYTE(?RSA_SIGN)>>.
@@ -2062,23 +2065,23 @@ handle_psk_identity(PSKIdentity, {Fun, UserState}) ->
filter_hashsigns([], [], _, Acc) ->
lists:reverse(Acc);
-filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns,
+filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns,
Acc) when KeyExchange == dhe_ecdsa;
KeyExchange == ecdhe_ecdsa ->
do_filter_hashsigns(ecdsa, Suite, Suites, Algos, HashSigns, Acc);
-filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns,
+filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns,
Acc) when KeyExchange == rsa;
KeyExchange == dhe_rsa;
KeyExchange == ecdhe_rsa;
KeyExchange == srp_rsa;
KeyExchange == rsa_psk ->
do_filter_hashsigns(rsa, Suite, Suites, Algos, HashSigns, Acc);
-filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns, Acc) when
+filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Acc) when
KeyExchange == dhe_dss;
KeyExchange == srp_dss ->
do_filter_hashsigns(dsa, Suite, Suites, Algos, HashSigns, Acc);
-filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns, Acc) when
+filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Acc) when
KeyExchange == dh_dss;
KeyExchange == dh_rsa;
KeyExchange == dh_ecdsa;
@@ -2088,7 +2091,7 @@ filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns, Acc
%% algorithm pair appearing in the hash_sign extension. The names
%% DH_DSS, DH_RSA, ECDH_ECDSA, and ECDH_RSA are historical.
filter_hashsigns(Suites, Algos, HashSigns, [Suite| Acc]);
-filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns, Acc) when
+filter_hashsigns([Suite | Suites], [#{key_exchange := KeyExchange} | Algos], HashSigns, Acc) when
KeyExchange == dh_anon;
KeyExchange == ecdh_anon;
KeyExchange == srp_anon;
@@ -2294,15 +2297,15 @@ handle_ecc_point_fmt_extension(_) ->
advertises_ec_ciphers([]) ->
false;
-advertises_ec_ciphers([{ecdh_ecdsa, _,_,_} | _]) ->
+advertises_ec_ciphers([#{key_exchange := ecdh_ecdsa} | _]) ->
true;
-advertises_ec_ciphers([{ecdhe_ecdsa, _,_,_} | _]) ->
+advertises_ec_ciphers([#{key_exchange := ecdhe_ecdsa} | _]) ->
true;
-advertises_ec_ciphers([{ecdh_rsa, _,_,_} | _]) ->
+advertises_ec_ciphers([#{key_exchange := ecdh_rsa} | _]) ->
true;
-advertises_ec_ciphers([{ecdhe_rsa, _,_,_} | _]) ->
+advertises_ec_ciphers([#{key_exchange := ecdhe_rsa} | _]) ->
true;
-advertises_ec_ciphers([{ecdh_anon, _,_,_} | _]) ->
+advertises_ec_ciphers([#{key_exchange := ecdh_anon} | _]) ->
true;
advertises_ec_ciphers([{ecdhe_psk, _,_,_} | _]) ->
true;
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index f1db75d5e9..406a095d2e 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -437,7 +437,8 @@ init(Type, Event, State) ->
%%--------------------------------------------------------------------
error({call, From}, {start, _Timeout}, {Error, State}) ->
- {stop_and_reply, normal, {reply, From, {error, Error}}, State};
+ ssl_connection:stop_and_reply(
+ normal, {reply, From, {error, Error}}, State);
error({call, _} = Call, Msg, State) ->
gen_handshake(?FUNCTION_NAME, Call, Msg, State);
error(_, _, _) ->
@@ -659,7 +660,7 @@ handle_info({Protocol, _, Data}, StateName,
next_event(StateName, Record, State);
#alert{} = Alert ->
ssl_connection:handle_normal_shutdown(Alert, StateName, State0),
- {stop, {shutdown, own_alert}}
+ ssl_connection:stop({shutdown, own_alert}, State0)
end;
handle_info({CloseTag, Socket}, StateName,
#state{socket = Socket, close_tag = CloseTag,
@@ -686,7 +687,7 @@ handle_info({CloseTag, Socket}, StateName,
end,
ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
- {stop, {shutdown, transport_closed}};
+ ssl_connection:stop({shutdown, transport_closed}, State);
true ->
%% Fixes non-delivery of final TLS record in {active, once}.
%% Basically allows the application the opportunity to set {active, once} again
@@ -698,7 +699,7 @@ handle_info(Msg, StateName, State) ->
handle_alerts([], Result) ->
Result;
-handle_alerts(_, {stop,_} = Stop) ->
+handle_alerts(_, {stop, _, _} = Stop) ->
Stop;
handle_alerts([Alert | Alerts], {next_state, StateName, State}) ->
handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State));
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index a38c5704a6..d59e817ffb 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -203,7 +203,7 @@ handle_client_hello(Version,
no_suite ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_ciphers);
_ ->
- {KeyExAlg,_,_,_} = ssl_cipher:suite_definition(CipherSuite),
+ #{key_exchange := KeyExAlg} = ssl_cipher:suite_definition(CipherSuite),
case ssl_handshake:select_hashsign(ClientHashSigns, Cert, KeyExAlg,
SupportedHashSigns, Version) of
#alert{} = Alert ->
diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile
index aa01552c39..9347b56f39 100644
--- a/lib/ssl/test/Makefile
+++ b/lib/ssl/test/Makefile
@@ -37,6 +37,8 @@ VSN=$(GS_VSN)
MODULES = \
ssl_test_lib \
+ ssl_bench_test_lib \
+ ssl_dist_test_lib \
ssl_alpn_handshake_SUITE \
ssl_basic_SUITE \
ssl_bench_SUITE \
@@ -44,6 +46,7 @@ MODULES = \
ssl_certificate_verify_SUITE\
ssl_crl_SUITE\
ssl_dist_SUITE \
+ ssl_dist_bench_SUITE \
ssl_engine_SUITE\
ssl_handshake_SUITE \
ssl_npn_hello_SUITE \
@@ -62,7 +65,8 @@ MODULES = \
ERL_FILES = $(MODULES:%=%.erl)
-HRL_FILES =
+HRL_FILES = \
+ ssl_dist_test_lib.hrl
HRL_FILES_SRC = \
ssl_api.hrl\
diff --git a/lib/ssl/test/ssl.spec b/lib/ssl/test/ssl.spec
index 0ad94e22bc..17b66aef40 100644
--- a/lib/ssl/test/ssl.spec
+++ b/lib/ssl/test/ssl.spec
@@ -3,3 +3,6 @@
ssl_bench_SUITE, [setup_sequential, setup_concurrent, payload_simple,
use_pem_cache, bypass_pem_cache],
"Benchmarks run separately"}.
+{skip_suites, "../ssl_test",
+ [ssl_dist_bench_SUITE],
+ "Benchmarks run separately"}.
diff --git a/lib/ssl/test/ssl_bench.spec b/lib/ssl/test/ssl_bench.spec
index d2f75b4203..8b746c5ca9 100644
--- a/lib/ssl/test/ssl_bench.spec
+++ b/lib/ssl/test/ssl_bench.spec
@@ -1 +1 @@
-{suites,"../ssl_test",[ssl_bench_SUITE]}.
+{suites,"../ssl_test",[ssl_bench_SUITE, ssl_dist_bench_SUITE]}.
diff --git a/lib/ssl/test/ssl_bench_SUITE.erl b/lib/ssl/test/ssl_bench_SUITE.erl
index ae2928b1c3..3fe6338d69 100644
--- a/lib/ssl/test/ssl_bench_SUITE.erl
+++ b/lib/ssl/test/ssl_bench_SUITE.erl
@@ -40,11 +40,11 @@ end_per_group(_GroupName, _Config) ->
ok.
init_per_suite(Config) ->
- try
- Server = setup(ssl, node()),
- [{server_node, Server}|Config]
- catch _:_ ->
- {skipped, "Benchmark machines only"}
+ case node() of
+ nonode@nohost ->
+ {skipped, "Node not distributed"};
+ _ ->
+ [{server_node, ssl_bench_test_lib:setup(perf_server)}|Config]
end.
end_per_suite(_Config) ->
@@ -132,10 +132,10 @@ bypass_pem_cache(_Config) ->
ssl() ->
- test(ssl, ?COUNT, node()).
+ test(ssl, ?COUNT).
-test(Type, Count, Host) ->
- Server = setup(Type, Host),
+test(Type, Count) ->
+ Server = ssl_bench_test_lib:setup(perf_server),
(do_test(Type, setup_connection, Count * 20, 1, Server)),
(do_test(Type, setup_connection, Count, 100, Server)),
(do_test(Type, payload, Count*300, 10, Server)),
@@ -294,47 +294,6 @@ msg() ->
"asdlkjsafsdfoierwlejsdlkfjsdf">>.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-setup(_Type, nonode@nohost) ->
- exit(dist_not_enabled);
-setup(Type, _This) ->
- Host = case os:getenv(?remote_host) of
- false ->
- {ok, This} = inet:gethostname(),
- This;
- RemHost ->
- RemHost
- end,
- Node = list_to_atom("perf_server@" ++ Host),
- SlaveArgs = case init:get_argument(pa) of
- {ok, PaPaths} ->
- lists:append([" -pa " ++ P || [P] <- PaPaths]);
- _ -> []
- end,
- %% io:format("Slave args: ~p~n",[SlaveArgs]),
- Prog =
- case os:find_executable("erl") of
- false -> "erl";
- P -> P
- end,
- io:format("Prog = ~p~n", [Prog]),
-
- case net_adm:ping(Node) of
- pong -> ok;
- pang ->
- {ok, Node} = slave:start(Host, perf_server, SlaveArgs, no_link, Prog)
- end,
- Path = code:get_path(),
- true = rpc:call(Node, code, set_path, [Path]),
- ok = rpc:call(Node, ?MODULE, setup_server, [Type, node()]),
- io:format("Client (~p) using ~s~n",[node(), code:which(ssl)]),
- (Node =:= node()) andalso restrict_schedulers(client),
- Node.
-
-setup_server(_Type, ClientNode) ->
- (ClientNode =:= node()) andalso restrict_schedulers(server),
- io:format("Server (~p) using ~s~n",[node(), code:which(ssl)]),
- ok.
-
ensure_all_started(App, Ack) ->
case application:start(App) of
@@ -358,13 +317,6 @@ setup_server_init(Type, Tc, Loop, PC) ->
unlink(Pid),
Res.
-restrict_schedulers(Type) ->
- %% We expect this to run on 8 core machine
- Extra0 = 1,
- Extra = if (Type =:= server) -> -Extra0; true -> Extra0 end,
- Scheds = erlang:system_info(schedulers),
- erlang:system_flag(schedulers_online, (Scheds div 2) + Extra).
-
tc(Fun, Mod, Line) ->
case timer:tc(Fun) of
{_,{'EXIT',Reason}} ->
diff --git a/lib/ssl/test/ssl_bench_test_lib.erl b/lib/ssl/test/ssl_bench_test_lib.erl
new file mode 100644
index 0000000000..e5cbb911bd
--- /dev/null
+++ b/lib/ssl/test/ssl_bench_test_lib.erl
@@ -0,0 +1,75 @@
+%%%-------------------------------------------------------------------
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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(ssl_bench_test_lib).
+
+%% API
+-export([setup/1]).
+
+%% Internal exports
+-export([setup_server/1]).
+
+-define(remote_host, "NETMARKS_REMOTE_HOST").
+
+setup(Name) ->
+ Host = case os:getenv(?remote_host) of
+ false ->
+ {ok, This} = inet:gethostname(),
+ This;
+ RemHost ->
+ RemHost
+ end,
+ Node = list_to_atom(atom_to_list(Name) ++ "@" ++ Host),
+ SlaveArgs = case init:get_argument(pa) of
+ {ok, PaPaths} ->
+ lists:append([" -pa " ++ P || [P] <- PaPaths]);
+ _ -> []
+ end,
+ %% io:format("Slave args: ~p~n",[SlaveArgs]),
+ Prog =
+ case os:find_executable("erl") of
+ false -> "erl";
+ P -> P
+ end,
+ io:format("Prog = ~p~n", [Prog]),
+
+ case net_adm:ping(Node) of
+ pong -> ok;
+ pang ->
+ {ok, Node} =
+ slave:start(Host, Name, SlaveArgs, no_link, Prog)
+ end,
+ Path = code:get_path(),
+ true = rpc:call(Node, code, set_path, [Path]),
+ ok = rpc:call(Node, ?MODULE, setup_server, [node()]),
+ io:format("Client (~p) using ~s~n",[node(), code:which(ssl)]),
+ (Node =:= node()) andalso restrict_schedulers(client),
+ Node.
+
+setup_server(ClientNode) ->
+ (ClientNode =:= node()) andalso restrict_schedulers(server),
+ io:format("Server (~p) using ~s~n",[node(), code:which(ssl)]),
+ ok.
+
+restrict_schedulers(Type) ->
+ %% We expect this to run on 8 core machine
+ Extra0 = 1,
+ Extra = if (Type =:= server) -> -Extra0; true -> Extra0 end,
+ Scheds = erlang:system_info(schedulers),
+ erlang:system_flag(schedulers_online, (Scheds div 2) + Extra).
diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl
index 8740e8c8f0..c822a52d1f 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-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2007-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.
@@ -22,6 +22,7 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("public_key/include/public_key.hrl").
+-include("ssl_dist_test_lib.hrl").
%% Note: This directive should only be used in test suites.
-compile(export_all).
@@ -30,12 +31,12 @@
-define(AWAIT_SSL_NODE_UP_TIMEOUT, 30000).
--record(node_handle,
- {connection_handler,
- socket,
- name,
- nodename}
- ).
+-import(ssl_dist_test_lib,
+ [tstsrvr_format/2, send_to_tstcntrl/1,
+ apply_on_ssl_node/4, apply_on_ssl_node/2,
+ stop_ssl_node/1]).
+start_ssl_node_name(Name, Args) ->
+ ssl_dist_test_lib:start_ssl_node(Name, Args).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -140,11 +141,14 @@ basic_test(NH1, NH2, _) ->
apply_on_ssl_node(
NH1,
fun () ->
- tstsrvr_format("Hi from ~p!~n", [node()]),
- send_to_tstcntrl({Ref, self()}),
+ tstsrvr_format(
+ "Hi from ~p!~n", [node()]),
+ send_to_tstcntrl(
+ {Ref, self()}),
receive
{From, ping} ->
- tstsrvr_format("Received ping ~p!~n", [node()]),
+ tstsrvr_format(
+ "Received ping ~p!~n", [node()]),
From ! {self(), pong}
end
end)
@@ -154,7 +158,8 @@ basic_test(NH1, NH2, _) ->
ok = apply_on_ssl_node(
NH2,
fun () ->
- tstsrvr_format("Hi from ~p!~n", [node()]),
+ tstsrvr_format(
+ "Hi from ~p!~n", [node()]),
SslPid ! {self(), ping},
receive
{SslPid, pong} ->
@@ -183,7 +188,8 @@ payload_test(NH1, NH2, _) ->
apply_on_ssl_node(
NH1,
fun () ->
- send_to_tstcntrl({Ref, self()}),
+ send_to_tstcntrl(
+ {Ref, self()}),
receive
{From, Msg} ->
From ! {self(), Msg}
@@ -616,12 +622,6 @@ gen_dist_test(Test, Config) ->
%% ssl_node side api
%%
-tstsrvr_format(Fmt, ArgList) ->
- send_to_tstsrvr({format, Fmt, ArgList}).
-
-send_to_tstcntrl(Message) ->
- send_to_tstsrvr({message, Message}).
-
try_setting_priority(TestFun, Config) ->
Prio = 1,
case gen_udp:open(0, [{priority,Prio}]) of
@@ -653,44 +653,6 @@ inet_ports() ->
%% test_server side api
%%
-apply_on_ssl_node(Node, M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
- Ref = make_ref(),
- send_to_ssl_node(Node, {apply, self(), Ref, M, F, A}),
- receive
- {Ref, Result} ->
- Result
- end.
-
-apply_on_ssl_node(Node, Fun) when is_function(Fun, 0) ->
- Ref = make_ref(),
- send_to_ssl_node(Node, {apply, self(), Ref, Fun}),
- receive
- {Ref, Result} ->
- Result
- end.
-
-stop_ssl_node(#node_handle{connection_handler = Handler,
- socket = Socket,
- name = Name}) ->
- ?t:format("Trying to stop ssl node ~s.~n", [Name]),
- Mon = erlang:monitor(process, Handler),
- unlink(Handler),
- case gen_tcp:send(Socket, term_to_binary(stop)) of
- ok ->
- receive
- {'DOWN', Mon, process, Handler, Reason} ->
- case Reason of
- normal ->
- ok;
- _ ->
- ct:pal("Down ~p ~n", [Reason])
- end
- end;
- Error ->
- erlang:demonitor(Mon, [flush]),
- ct:pal("Warning ~p ~n", [Error])
- end.
-
start_ssl_node(Config) ->
start_ssl_node(Config, "").
@@ -698,29 +660,8 @@ start_ssl_node(Config, XArgs) ->
Name = mk_node_name(Config),
SSL = proplists:get_value(ssl_opts, Config),
SSLDistOpts = setup_dist_opts(Config),
- start_ssl_node_raw(Name, SSL ++ " " ++ SSLDistOpts ++ XArgs).
-
-start_ssl_node_raw(Name, Args) ->
- {ok, LSock} = gen_tcp:listen(0,
- [binary, {packet, 4}, {active, false}]),
- {ok, ListenPort} = inet:port(LSock),
- CmdLine = mk_node_cmdline(ListenPort, Name, Args),
- ?t:format("Attempting to start ssl node ~ts: ~ts~n", [Name, CmdLine]),
- case open_port({spawn, CmdLine}, []) of
- Port when is_port(Port) ->
- unlink(Port),
- erlang:port_close(Port),
- case await_ssl_node_up(Name, LSock) of
- #node_handle{} = NodeHandle ->
- ?t:format("Ssl node ~s started.~n", [Name]),
- NodeName = list_to_atom(Name ++ "@" ++ host_name()),
- NodeHandle#node_handle{nodename = NodeName};
- Error ->
- exit({failed_to_start_node, Name, Error})
- end;
- Error ->
- exit({failed_to_start_node, Name, Error})
- end.
+ start_ssl_node_name(
+ Name, SSL ++ " " ++ SSLDistOpts ++ XArgs).
cache_crls_on_ssl_nodes(PrivDir, CANames, NHs) ->
[begin
@@ -739,11 +680,6 @@ cache_crls_on_ssl_nodes(PrivDir, CANames, NHs) ->
%% command line creation
%%
-host_name() ->
- [$@ | Host] = lists:dropwhile(fun ($@) -> false; (_) -> true end,
- atom_to_list(node())),
- Host.
-
mk_node_name(Config) ->
N = erlang:unique_integer([positive]),
Case = proplists:get_value(testcase, Config),
@@ -753,225 +689,6 @@ mk_node_name(Config) ->
++ "_"
++ integer_to_list(N).
-mk_node_cmdline(ListenPort, Name, Args) ->
- Static = "-detached -noinput",
- Pa = filename:dirname(code:which(?MODULE)),
- Prog = case catch init:get_argument(progname) of
- {ok,[[P]]} -> P;
- _ -> exit(no_progname_argument_found)
- end,
- NameSw = case net_kernel:longnames() of
- false -> "-sname ";
- _ -> "-name "
- end,
- {ok, Pwd} = file:get_cwd(),
- "\"" ++ Prog ++ "\" "
- ++ Static ++ " "
- ++ NameSw ++ " " ++ Name ++ " "
- ++ "-pa " ++ Pa ++ " "
- ++ "-run application start crypto -run application start public_key "
- ++ "-eval 'net_kernel:verbose(1)' "
- ++ "-run " ++ atom_to_list(?MODULE) ++ " cnct2tstsrvr "
- ++ host_name() ++ " "
- ++ integer_to_list(ListenPort) ++ " "
- ++ Args ++ " "
- ++ "-env ERL_CRASH_DUMP " ++ Pwd ++ "/erl_crash_dump." ++ Name ++ " "
- ++ "-kernel error_logger \"{file,\\\"" ++ Pwd ++ "/error_log." ++ Name ++ "\\\"}\" "
- ++ "-setcookie " ++ atom_to_list(erlang:get_cookie()).
-
-%%
-%% Connection handler test_server side
-%%
-
-await_ssl_node_up(Name, LSock) ->
- case gen_tcp:accept(LSock, ?AWAIT_SSL_NODE_UP_TIMEOUT) of
- timeout ->
- gen_tcp:close(LSock),
- ?t:format("Timeout waiting for ssl node ~s to come up~n",
- [Name]),
- timeout;
- {ok, Socket} ->
- gen_tcp:close(LSock),
- case gen_tcp:recv(Socket, 0) of
- {ok, Bin} ->
- check_ssl_node_up(Socket, Name, Bin);
- {error, closed} ->
- gen_tcp:close(Socket),
- exit({lost_connection_with_ssl_node_before_up, Name})
- end;
- {error, Error} ->
- gen_tcp:close(LSock),
- exit({accept_failed, Error})
- end.
-
-check_ssl_node_up(Socket, Name, Bin) ->
- case catch binary_to_term(Bin) of
- {'EXIT', _} ->
- gen_tcp:close(Socket),
- exit({bad_data_received_from_ssl_node, Name, Bin});
- {ssl_node_up, NodeName} ->
- case list_to_atom(Name++"@"++host_name()) of
- NodeName ->
- Parent = self(),
- Go = make_ref(),
- %% Spawn connection handler on test server side
- Pid = spawn_link(
- fun () ->
- receive Go -> ok end,
- tstsrvr_con_loop(Name, Socket, Parent)
- end),
- ok = gen_tcp:controlling_process(Socket, Pid),
- Pid ! Go,
- #node_handle{connection_handler = Pid,
- socket = Socket,
- name = Name};
- _ ->
- exit({unexpected_ssl_node_connected, NodeName})
- end;
- Msg ->
- exit({unexpected_msg_instead_of_ssl_node_up, Name, Msg})
- end.
-
-send_to_ssl_node(#node_handle{connection_handler = Hndlr}, Term) ->
- Hndlr ! {relay_to_ssl_node, term_to_binary(Term)},
- ok.
-
-tstsrvr_con_loop(Name, Socket, Parent) ->
- inet:setopts(Socket,[{active,once}]),
- receive
- {relay_to_ssl_node, Data} when is_binary(Data) ->
- case gen_tcp:send(Socket, Data) of
- ok ->
- ok;
- _Error ->
- gen_tcp:close(Socket),
- exit({failed_to_relay_data_to_ssl_node, Name, Data})
- end;
- {tcp, Socket, Bin} ->
- case catch binary_to_term(Bin) of
- {'EXIT', _} ->
- gen_tcp:close(Socket),
- exit({bad_data_received_from_ssl_node, Name, Bin});
- {format, FmtStr, ArgList} ->
- ?t:format(FmtStr, ArgList);
- {message, Msg} ->
- ?t:format("Got message ~p", [Msg]),
- Parent ! Msg;
- {apply_res, To, Ref, Res} ->
- To ! {Ref, Res};
- bye ->
- ?t:format("Ssl node ~s stopped.~n", [Name]),
- gen_tcp:close(Socket),
- exit(normal);
- Unknown ->
- exit({unexpected_message_from_ssl_node, Name, Unknown})
- end;
- {tcp_closed, Socket} ->
- gen_tcp:close(Socket),
- exit({lost_connection_with_ssl_node, Name})
- end,
- tstsrvr_con_loop(Name, Socket, Parent).
-
-%%
-%% Connection handler ssl_node side
-%%
-
-% cnct2tstsrvr() is called via command line arg -run ...
-cnct2tstsrvr([Host, Port]) when is_list(Host), is_list(Port) ->
- %% Spawn connection handler on ssl node side
- ConnHandler
- = spawn(fun () ->
- case catch gen_tcp:connect(Host,
- list_to_integer(Port),
- [binary,
- {packet, 4},
- {active, false}]) of
- {ok, Socket} ->
- notify_ssl_node_up(Socket),
- ets:new(test_server_info,
- [set,
- public,
- named_table,
- {keypos, 1}]),
- ets:insert(test_server_info,
- {test_server_handler, self()}),
- ssl_node_con_loop(Socket);
- Error ->
- halt("Failed to connect to test server " ++
- lists:flatten(io_lib:format("Host:~p ~n Port:~p~n Error:~p~n",
- [Host, Port, Error])))
- end
- end),
- spawn(fun () ->
- Mon = erlang:monitor(process, ConnHandler),
- receive
- {'DOWN', Mon, process, ConnHandler, Reason} ->
- receive after 1000 -> ok end,
- halt("test server connection handler terminated: " ++
- lists:flatten(io_lib:format("~p", [Reason])))
- end
- end).
-
-notify_ssl_node_up(Socket) ->
- case catch gen_tcp:send(Socket,
- term_to_binary({ssl_node_up, node()})) of
- ok -> ok;
- _ -> halt("Failed to notify test server that I'm up")
- end.
-
-send_to_tstsrvr(Term) ->
- case catch ets:lookup_element(test_server_info, test_server_handler, 2) of
- Hndlr when is_pid(Hndlr) ->
- Hndlr ! {relay_to_test_server, term_to_binary(Term)}, ok;
- _ ->
- receive after 200 -> ok end,
- send_to_tstsrvr(Term)
- end.
-
-ssl_node_con_loop(Socket) ->
- inet:setopts(Socket,[{active,once}]),
- receive
- {relay_to_test_server, Data} when is_binary(Data) ->
- case gen_tcp:send(Socket, Data) of
- ok ->
- ok;
- _Error ->
- gen_tcp:close(Socket),
- halt("Failed to relay data to test server")
- end;
- {tcp, Socket, Bin} ->
- case catch binary_to_term(Bin) of
- {'EXIT', _} ->
- gen_tcp:close(Socket),
- halt("test server sent me bad data");
- {apply, From, Ref, M, F, A} ->
- spawn_link(
- fun () ->
- send_to_tstsrvr({apply_res,
- From,
- Ref,
- (catch apply(M, F, A))})
- end);
- {apply, From, Ref, Fun} ->
- spawn_link(fun () ->
- send_to_tstsrvr({apply_res,
- From,
- Ref,
- (catch Fun())})
- end);
- stop ->
- gen_tcp:send(Socket, term_to_binary(bye)),
- gen_tcp:close(Socket),
- init:stop(),
- receive after infinity -> ok end;
- _Unknown ->
- halt("test server sent me an unexpected message")
- end;
- {tcp_closed, Socket} ->
- halt("Lost connection to test server")
- end,
- ssl_node_con_loop(Socket).
-
%%
%% Setup ssl dist info
%%
diff --git a/lib/ssl/test/ssl_dist_bench_SUITE.erl b/lib/ssl/test/ssl_dist_bench_SUITE.erl
new file mode 100644
index 0000000000..4d27564319
--- /dev/null
+++ b/lib/ssl/test/ssl_dist_bench_SUITE.erl
@@ -0,0 +1,481 @@
+%%%-------------------------------------------------------------------
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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(ssl_dist_bench_SUITE).
+
+-include_lib("common_test/include/ct_event.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+%% CT meta
+-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]).
+
+%% Test cases
+-export(
+ [setup/1,
+ roundtrip/1,
+ throughput_1024/1,
+ throughput_4096/1,
+ throughput_16384/1,
+ throughput_65536/1,
+ throughput_262144/1,
+ throughput_1048576/1]).
+
+%% Debug
+-export([payload/1]).
+
+%%%-------------------------------------------------------------------
+
+suite() -> [{ct_hooks, [{ts_install_cth, [{nodenames, 2}]}]}].
+
+all() -> [{group, ssl}, {group, plain}].
+
+groups() ->
+ [{ssl, all_groups()},
+ {plain, all_groups()},
+ %%
+ {setup, [{repeat, 1}], [setup]},
+ {roundtrip, [{repeat, 1}], [roundtrip]},
+ {throughput, [{repeat, 1}],
+ [throughput_1024,
+ throughput_4096,
+ throughput_16384,
+ throughput_65536,
+ throughput_262144,
+ throughput_1048576]}].
+
+all_groups() ->
+ [{group, setup},
+ {group, roundtrip},
+ {group, throughput}].
+
+init_per_suite(Config) ->
+ Digest = sha1,
+ ECCurve = secp521r1,
+ TLSVersion = 'tlsv1.2',
+ TLSCipher = {ecdhe_ecdsa,aes_128_cbc,sha256,sha256},
+ %%
+ Node = node(),
+ try
+ Node =/= nonode@nohost orelse
+ throw({skipped,"Node not distributed"}),
+ {supported, SSLVersions} =
+ lists:keyfind(supported, 1, ssl:versions()),
+ lists:member(TLSVersion, SSLVersions) orelse
+ throw(
+ {skipped,
+ "SSL does not support " ++ term_to_string(TLSVersion)}),
+ lists:member(ECCurve, ssl:eccs(TLSVersion)) orelse
+ throw(
+ {skipped,
+ "SSL does not support " ++ term_to_string(ECCurve)}),
+ lists:member(TLSCipher, ssl:cipher_suites()) orelse
+ throw(
+ {skipped,
+ "SSL does not support " ++ term_to_string(TLSCipher)})
+ of
+ _ ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ %%
+ [_, HostA] = string:split(atom_to_list(Node), "@"),
+ NodeAName = ?MODULE_STRING ++ "_node_a",
+ NodeAString = NodeAName ++ "@" ++ HostA,
+ NodeAConfFile = filename:join(PrivDir, NodeAString ++ ".conf"),
+ NodeA = list_to_atom(NodeAString),
+ %%
+ ServerNode = ssl_bench_test_lib:setup(dist_server),
+ [_, HostB] = string:split(atom_to_list(ServerNode), "@"),
+ NodeBName = ?MODULE_STRING ++ "_node_b",
+ NodeBString = NodeBName ++ "@" ++ HostB,
+ NodeBConfFile = filename:join(PrivDir, NodeBString ++ ".conf"),
+ NodeB = list_to_atom(NodeBString),
+ %%
+ CertOptions =
+ [{digest, Digest},
+ {key, {namedCurve, ECCurve}}],
+ RootCert =
+ public_key:pkix_test_root_cert(
+ ?MODULE_STRING ++ " ROOT CA", CertOptions),
+ SSLConf =
+ [{verify, verify_peer},
+ {versions, [TLSVersion]},
+ {ciphers, [TLSCipher]}],
+ %%
+ write_node_conf(
+ NodeAConfFile, NodeA,
+ [{fail_if_no_peer_cert, true} | SSLConf], SSLConf,
+ CertOptions, RootCert),
+ write_node_conf(
+ NodeBConfFile, NodeB,
+ [{fail_if_no_peer_cert, true} | SSLConf], SSLConf,
+ CertOptions, RootCert),
+ %%
+ [{node_a_name, NodeAName},
+ {node_a, NodeA},
+ {node_a_dist_args,
+ "-proto_dist inet_tls "
+ "-ssl_dist_optfile " ++ NodeAConfFile ++ " "},
+ {node_b_name, NodeBName},
+ {node_b, NodeB},
+ {node_b_dist_args,
+ "-proto_dist inet_tls "
+ "-ssl_dist_optfile " ++ NodeBConfFile ++ " "},
+ {server_node, ServerNode}
+ |Config]
+ catch
+ throw:Result ->
+ Result
+ end.
+
+end_per_suite(Config) ->
+ ServerNode = proplists:get_value(server_node, Config),
+ slave:stop(ServerNode).
+
+init_per_group(ssl, Config) ->
+ [{ssl_dist, true}, {ssl_dist_prefix, "SSL"}|Config];
+init_per_group(plain, Config) ->
+ [{ssl_dist, false}, {ssl_dist_prefix, "Plain"}|Config];
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, _Config) ->
+ ok.
+
+init_per_testcase(_Func, Conf) ->
+ Conf.
+
+end_per_testcase(_Func, _Conf) ->
+ ok.
+
+-define(COUNT, 400).
+
+%%%-------------------------------------------------------------------
+%%% CommonTest API helpers
+
+write_node_conf(
+ ConfFile, Node, ServerConf, ClientConf, CertOptions, RootCert) ->
+ Conf =
+ public_key:pkix_test_data(
+ #{root => RootCert,
+ peer =>
+ [{extensions,
+ [#'Extension'{
+ extnID = ?'id-ce-subjectAltName',
+ extnValue = [{dNSName, atom_to_list(Node)}],
+ critical = false}]} | CertOptions]}),
+ NodeConf =
+ [{server, ServerConf ++ Conf}, {client, ClientConf ++ Conf}],
+ {ok, Fd} = file:open(ConfFile, [write]),
+ ok = file:change_mode(ConfFile, 8#400),
+ io:format(Fd, "~p.~n", [NodeConf]),
+ ok = file:close(Fd).
+
+
+%%%-------------------------------------------------------------------
+%%% Test cases
+
+%%-----------------------
+%% Connection setup speed
+
+setup(Config) ->
+ run_nodepair_test(fun setup/5, Config).
+
+setup(A, B, Prefix, HA, HB) ->
+ Rounds = 10,
+ [] = ssl_apply(HA, erlang, nodes, []),
+ [] = ssl_apply(HB, erlang, nodes, []),
+ {SetupTime, CycleTime} =
+ ssl_apply(HA, fun () -> setup_runner(A, B, Rounds) end),
+ [] = ssl_apply(HA, erlang, nodes, []),
+ [] = ssl_apply(HB, erlang, nodes, []),
+ SetupSpeed = round((Rounds*1000000*1000) / SetupTime),
+ CycleSpeed = round((Rounds*1000000*1000) / CycleTime),
+ _ = report(Prefix++" Setup", SetupSpeed, "setups/1000s"),
+ report(Prefix++" Setup Cycle", CycleSpeed, "cycles/1000s").
+
+%% Runs on node A against rex in node B
+setup_runner(A, B, Rounds) ->
+ StartTime = start_time(),
+ SetupTime = setup_loop(A, B, 0, Rounds),
+ {microseconds(SetupTime), microseconds(elapsed_time(StartTime))}.
+
+setup_loop(_A, _B, T, 0) ->
+ T;
+setup_loop(A, B, T, N) ->
+ StartTime = start_time(),
+ [A] = rpc:block_call(B, erlang, nodes, []),
+ Time = elapsed_time(StartTime),
+ [B] = erlang:nodes(),
+ Mref = erlang:monitor(process, {rex,B}),
+ true = net_kernel:disconnect(B),
+ receive
+ {'DOWN',Mref,process,_,_} ->
+ [] = erlang:nodes(),
+ setup_loop(A, B, Time + T, N - 1)
+ end.
+
+
+%%----------------
+%% Roundtrip speed
+
+roundtrip(Config) ->
+ run_nodepair_test(fun roundtrip/5, Config).
+
+roundtrip(A, B, Prefix, HA, HB) ->
+ Rounds = 40000,
+ [] = ssl_apply(HA, erlang, nodes, []),
+ [] = ssl_apply(HB, erlang, nodes, []),
+ Time = ssl_apply(HA, fun () -> roundtrip_runner(A, B, Rounds) end),
+ [B] = ssl_apply(HA, erlang, nodes, []),
+ [A] = ssl_apply(HB, erlang, nodes, []),
+ Speed = round((Rounds*1000000) / Time),
+ report(Prefix++" Roundtrip", Speed, "pings/s").
+
+%% Runs on node A and spawns a server on node B
+roundtrip_runner(A, B, Rounds) ->
+ ClientPid = self(),
+ [A] = rpc:call(B, erlang, nodes, []),
+ ServerPid =
+ erlang:spawn(
+ B,
+ fun () -> roundtrip_server(ClientPid, Rounds) end),
+ ServerMon = erlang:monitor(process, ServerPid),
+ microseconds(
+ roundtrip_client(ServerPid, ServerMon, start_time(), Rounds)).
+
+roundtrip_server(_Pid, 0) ->
+ ok;
+roundtrip_server(Pid, N) ->
+ receive
+ N ->
+ Pid ! N,
+ roundtrip_server(Pid, N-1)
+ end.
+
+roundtrip_client(_Pid, Mon, StartTime, 0) ->
+ Time = elapsed_time(StartTime),
+ receive
+ {'DOWN', Mon, _, _, normal} ->
+ Time;
+ {'DOWN', Mon, _, _, Other} ->
+ exit(Other)
+ end;
+roundtrip_client(Pid, Mon, StartTime, N) ->
+ Pid ! N,
+ receive
+ N ->
+ roundtrip_client(Pid, Mon, StartTime, N - 1)
+ end.
+
+
+%%-----------------
+%% Throughput speed
+
+throughput_1024(Config) ->
+ run_nodepair_test(
+ fun (A, B, Prefix, HA, HB) ->
+ throughput(A, B, Prefix, HA, HB, 100000, 1024)
+ end, Config).
+
+throughput_4096(Config) ->
+ run_nodepair_test(
+ fun (A, B, Prefix, HA, HB) ->
+ throughput(A, B, Prefix, HA, HB, 50000, 4096)
+ end, Config).
+
+throughput_16384(Config) ->
+ run_nodepair_test(
+ fun (A, B, Prefix, HA, HB) ->
+ throughput(A, B, Prefix, HA, HB, 10000, 16384)
+ end, Config).
+
+throughput_65536(Config) ->
+ run_nodepair_test(
+ fun (A, B, Prefix, HA, HB) ->
+ throughput(A, B, Prefix, HA, HB, 2000, 65536)
+ end, Config).
+
+throughput_262144(Config) ->
+ run_nodepair_test(
+ fun (A, B, Prefix, HA, HB) ->
+ throughput(A, B, Prefix, HA, HB, 500, 262144)
+ end, Config).
+
+throughput_1048576(Config) ->
+ run_nodepair_test(
+ fun (A, B, Prefix, HA, HB) ->
+ throughput(A, B, Prefix, HA, HB, 200, 1048576)
+ end, Config).
+
+throughput(A, B, Prefix, HA, HB, Packets, Size) ->
+ [] = ssl_apply(HA, erlang, nodes, []),
+ [] = ssl_apply(HB, erlang, nodes, []),
+ Time =
+ ssl_apply(HA, fun () -> throughput_runner(A, B, Packets, Size) end),
+ [B] = ssl_apply(HA, erlang, nodes, []),
+ [A] = ssl_apply(HB, erlang, nodes, []),
+ Speed = round((Packets*Size*1000000) / (1024*Time)),
+ report(Prefix++" Throughput_"++integer_to_list(Size), Speed, "kB/s").
+
+%% Runs on node A and spawns a server on node B
+throughput_runner(A, B, Rounds, Size) ->
+ Payload = payload(Size),
+ ClientPid = self(),
+ [A] = rpc:call(B, erlang, nodes, []),
+ ServerPid =
+ erlang:spawn(
+ B,
+ fun () -> throughput_server(ClientPid, Rounds) end),
+ ServerMon = erlang:monitor(process, ServerPid),
+ microseconds(
+ throughput_client(
+ ServerPid, ServerMon, Payload, start_time(), Rounds)).
+
+throughput_server(_Pid, 0) ->
+ ok;
+throughput_server(Pid, N) ->
+ receive
+ [N|_] ->
+ throughput_server(Pid, N-1)
+ end.
+
+throughput_client(_Pid, Mon, _Payload, StartTime, 0) ->
+ receive
+ {'DOWN', Mon, _, _, normal} ->
+ elapsed_time(StartTime);
+ {'DOWN', Mon, _, _, Other} ->
+ exit(Other)
+ end;
+throughput_client(Pid, Mon, Payload, StartTime, N) ->
+ Pid ! [N|Payload],
+ throughput_client(Pid, Mon, Payload, StartTime, N - 1).
+
+%%%-------------------------------------------------------------------
+%%% Test cases helpers
+
+run_nodepair_test(TestFun, Config) ->
+ A = proplists:get_value(node_a, Config),
+ B = proplists:get_value(node_b, Config),
+ Prefix = proplists:get_value(ssl_dist_prefix, Config),
+ HA = start_ssl_node_a(Config),
+ HB = start_ssl_node_b(Config),
+ try TestFun(A, B, Prefix, HA, HB)
+ after
+ stop_ssl_node_a(HA),
+ stop_ssl_node_b(HB, Config),
+ ok
+ end.
+
+ssl_apply(Handle, M, F, Args) ->
+ case ssl_dist_test_lib:apply_on_ssl_node(Handle, M, F, Args) of
+ {'EXIT',Reason} ->
+ error(Reason);
+ Result ->
+ Result
+ end.
+
+ssl_apply(Handle, Fun) ->
+ case ssl_dist_test_lib:apply_on_ssl_node(Handle, Fun) of
+ {'EXIT',Reason} ->
+ error(Reason);
+ Result ->
+ Result
+ end.
+
+start_ssl_node_a(Config) ->
+ Name = proplists:get_value(node_a_name, Config),
+ Args = get_node_args(node_a_dist_args, Config),
+ ssl_dist_test_lib:start_ssl_node(Name, Args).
+
+start_ssl_node_b(Config) ->
+ Name = proplists:get_value(node_b_name, Config),
+ Args = get_node_args(node_b_dist_args, Config),
+ ServerNode = proplists:get_value(server_node, Config),
+ rpc:call(
+ ServerNode, ssl_dist_test_lib, start_ssl_node, [Name, Args]).
+
+stop_ssl_node_a(HA) ->
+ ssl_dist_test_lib:stop_ssl_node(HA).
+
+stop_ssl_node_b(HB, Config) ->
+ ServerNode = proplists:get_value(server_node, Config),
+ rpc:call(ServerNode, ssl_dist_test_lib, stop_ssl_node, [HB]).
+
+get_node_args(Tag, Config) ->
+ case proplists:get_value(ssl_dist, Config) of
+ true ->
+ proplists:get_value(Tag, Config);
+ false ->
+ ""
+ end.
+
+
+
+payload(Size) ->
+ iolist_to_binary(
+ [case Size bsr 8 of
+ 0 ->
+ [];
+ Blocks ->
+ payload(Blocks, create_binary(256))
+ end | create_binary(Size band 255)]).
+%%
+payload(0, _) ->
+ [];
+payload(Blocks, Block) ->
+ Half = payload(Blocks bsr 1, Block),
+ [Half, Half |
+ if
+ Blocks band 1 =:= 1 ->
+ Block;
+ true ->
+ []
+ end].
+
+create_binary(Size) ->
+ create_binary(Size, <<>>).
+%%
+create_binary(0, Bin) ->
+ Bin;
+create_binary(Size, Bin) ->
+ NextSize = Size - 1,
+ create_binary(NextSize, <<Bin/binary, NextSize>>).
+
+start_time() ->
+ erlang:system_time().
+
+elapsed_time(StartTime) ->
+ erlang:system_time() - StartTime.
+
+microseconds(Time) ->
+ erlang:convert_time_unit(Time, native, microsecond).
+
+report(Name, Value, Unit) ->
+ ct:pal("~s: ~w ~s", [Name, Value, Unit]),
+ ct_event:notify(
+ #event{
+ name = benchmark_data,
+ data = [{value, Value}, {suite, "ssl_dist"}, {name, Name}]}),
+ {comment, term_to_string(Value) ++ " " ++ Unit}.
+
+term_to_string(Term) ->
+ unicode:characters_to_list(
+ io_lib:write(Term, [{encoding, unicode}])).
diff --git a/lib/ssl/test/ssl_dist_test_lib.erl b/lib/ssl/test/ssl_dist_test_lib.erl
new file mode 100644
index 0000000000..1b9c853fc4
--- /dev/null
+++ b/lib/ssl/test/ssl_dist_test_lib.erl
@@ -0,0 +1,343 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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(ssl_dist_test_lib).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("public_key/include/public_key.hrl").
+-include("ssl_dist_test_lib.hrl").
+
+-export([tstsrvr_format/2, send_to_tstcntrl/1]).
+-export([apply_on_ssl_node/4, apply_on_ssl_node/2]).
+-export([stop_ssl_node/1, start_ssl_node/2]).
+%%
+-export([cnct2tstsrvr/1]).
+
+-define(AWAIT_SSL_NODE_UP_TIMEOUT, 30000).
+
+
+
+%% ssl_node side api
+%%
+
+tstsrvr_format(Fmt, ArgList) ->
+ send_to_tstsrvr({format, Fmt, ArgList}).
+
+send_to_tstcntrl(Message) ->
+ send_to_tstsrvr({message, Message}).
+
+
+%%
+%% test_server side api
+%%
+
+apply_on_ssl_node(
+ #node_handle{connection_handler = Hndlr} = Node,
+ M, F, A) when is_atom(M), is_atom(F), is_list(A) ->
+ Ref = erlang:monitor(process, Hndlr),
+ apply_on_ssl_node(Node, Ref, {apply, self(), Ref, M, F, A}).
+
+apply_on_ssl_node(
+ #node_handle{connection_handler = Hndlr} = Node,
+ Fun) when is_function(Fun, 0) ->
+ Ref = erlang:monitor(process, Hndlr),
+ apply_on_ssl_node(Node, Ref, {apply, self(), Ref, Fun}).
+
+apply_on_ssl_node(Node, Ref, Msg) ->
+ send_to_ssl_node(Node, Msg),
+ receive
+ {'DOWN', Ref, process, Hndlr, Reason} ->
+ exit({handler_died, Hndlr, Reason});
+ {Ref, Result} ->
+ Result
+ end.
+
+stop_ssl_node(#node_handle{connection_handler = Handler,
+ socket = Socket,
+ name = Name}) ->
+ ?t:format("Trying to stop ssl node ~s.~n", [Name]),
+ Mon = erlang:monitor(process, Handler),
+ unlink(Handler),
+ case gen_tcp:send(Socket, term_to_binary(stop)) of
+ ok ->
+ receive
+ {'DOWN', Mon, process, Handler, Reason} ->
+ case Reason of
+ normal ->
+ ok;
+ _ ->
+ ct:pal(
+ "stop_ssl_node/1 ~s Down ~p ~n",
+ [Name,Reason])
+ end
+ end;
+ Error ->
+ erlang:demonitor(Mon, [flush]),
+ ct:pal("stop_ssl_node/1 ~s Warning ~p ~n", [Name,Error])
+ end.
+
+start_ssl_node(Name, Args) ->
+ {ok, LSock} = gen_tcp:listen(0,
+ [binary, {packet, 4}, {active, false}]),
+ {ok, ListenPort} = inet:port(LSock),
+ CmdLine = mk_node_cmdline(ListenPort, Name, Args),
+ ?t:format("Attempting to start ssl node ~ts: ~ts~n", [Name, CmdLine]),
+ case open_port({spawn, CmdLine}, []) of
+ Port when is_port(Port) ->
+ unlink(Port),
+ erlang:port_close(Port),
+ case await_ssl_node_up(Name, LSock) of
+ #node_handle{} = NodeHandle ->
+ ?t:format("Ssl node ~s started.~n", [Name]),
+ NodeName = list_to_atom(Name ++ "@" ++ host_name()),
+ NodeHandle#node_handle{nodename = NodeName};
+ Error ->
+ exit({failed_to_start_node, Name, Error})
+ end;
+ Error ->
+ exit({failed_to_start_node, Name, Error})
+ end.
+
+host_name() ->
+ [_, Host] = string:split(atom_to_list(node()), "@"),
+ %% [$@ | Host] = lists:dropwhile(fun ($@) -> false; (_) -> true end,
+ %% atom_to_list(node())),
+ Host.
+
+mk_node_cmdline(ListenPort, Name, Args) ->
+ Static = "-detached -noinput",
+ Pa = filename:dirname(code:which(?MODULE)),
+ Prog = case catch init:get_argument(progname) of
+ {ok,[[P]]} -> P;
+ _ -> exit(no_progname_argument_found)
+ end,
+ NameSw = case net_kernel:longnames() of
+ false -> "-sname ";
+ _ -> "-name "
+ end,
+ {ok, Pwd} = file:get_cwd(),
+ "\"" ++ Prog ++ "\" "
+ ++ Static ++ " "
+ ++ NameSw ++ " " ++ Name ++ " "
+ ++ "-pa " ++ Pa ++ " "
+ ++ "-run application start crypto -run application start public_key "
+ ++ "-eval 'net_kernel:verbose(1)' "
+ ++ "-run " ++ atom_to_list(?MODULE) ++ " cnct2tstsrvr "
+ ++ host_name() ++ " "
+ ++ integer_to_list(ListenPort) ++ " "
+ ++ Args ++ " "
+ ++ "-env ERL_CRASH_DUMP " ++ Pwd ++ "/erl_crash_dump." ++ Name ++ " "
+ ++ "-kernel error_logger \"{file,\\\"" ++ Pwd ++ "/error_log." ++ Name ++ "\\\"}\" "
+ ++ "-setcookie " ++ atom_to_list(erlang:get_cookie()).
+
+%%
+%% Connection handler test_server side
+%%
+
+await_ssl_node_up(Name, LSock) ->
+ case gen_tcp:accept(LSock, ?AWAIT_SSL_NODE_UP_TIMEOUT) of
+ {ok, Socket} ->
+ gen_tcp:close(LSock),
+ case gen_tcp:recv(Socket, 0) of
+ {ok, Bin} ->
+ check_ssl_node_up(Socket, Name, Bin);
+ {error, closed} ->
+ gen_tcp:close(Socket),
+ exit({lost_connection_with_ssl_node_before_up, Name})
+ end;
+ {error, Error} ->
+ gen_tcp:close(LSock),
+ ?t:format("Accept failed for ssl node ~s: ~p~n", [Name,Error]),
+ exit({accept_failed, Error})
+ end.
+
+check_ssl_node_up(Socket, Name, Bin) ->
+ case catch binary_to_term(Bin) of
+ {'EXIT', _} ->
+ gen_tcp:close(Socket),
+ exit({bad_data_received_from_ssl_node, Name, Bin});
+ {ssl_node_up, NodeName} ->
+ case list_to_atom(Name++"@"++host_name()) of
+ NodeName ->
+ Parent = self(),
+ Go = make_ref(),
+ %% Spawn connection handler on test server side
+ Pid = spawn_link(
+ fun () ->
+ receive Go -> ok end,
+ process_flag(trap_exit, true),
+ tstsrvr_con_loop(Name, Socket, Parent)
+ end),
+ ok = gen_tcp:controlling_process(Socket, Pid),
+ Pid ! Go,
+ #node_handle{connection_handler = Pid,
+ socket = Socket,
+ name = Name};
+ _ ->
+ exit({unexpected_ssl_node_connected, NodeName})
+ end;
+ Msg ->
+ exit({unexpected_msg_instead_of_ssl_node_up, Name, Msg})
+ end.
+
+send_to_ssl_node(#node_handle{connection_handler = Hndlr}, Term) ->
+ Hndlr ! {relay_to_ssl_node, term_to_binary(Term)},
+ ok.
+
+tstsrvr_con_loop(Name, Socket, Parent) ->
+ ok = inet:setopts(Socket,[{active,once}]),
+ receive
+ {relay_to_ssl_node, Data} when is_binary(Data) ->
+ case gen_tcp:send(Socket, Data) of
+ ok ->
+ ok;
+ _Error ->
+ gen_tcp:close(Socket),
+ exit({failed_to_relay_data_to_ssl_node, Name, Data})
+ end;
+ {tcp, Socket, Bin} ->
+ try binary_to_term(Bin) of
+ {format, FmtStr, ArgList} ->
+ ?t:format(FmtStr, ArgList);
+ {message, Msg} ->
+ ?t:format("Got message ~p", [Msg]),
+ Parent ! Msg;
+ {apply_res, To, Ref, Res} ->
+ To ! {Ref, Res};
+ bye ->
+ {error, closed} = gen_tcp:recv(Socket, 0),
+ ?t:format("Ssl node ~s stopped.~n", [Name]),
+ gen_tcp:close(Socket),
+ exit(normal);
+ Unknown ->
+ exit({unexpected_message_from_ssl_node, Name, Unknown})
+ catch
+ error : _ ->
+ gen_tcp:close(Socket),
+ exit({bad_data_received_from_ssl_node, Name, Bin})
+ end;
+ {tcp_closed, Socket} ->
+ gen_tcp:close(Socket),
+ exit({lost_connection_with_ssl_node, Name});
+ {'EXIT', Parent, Reason} ->
+ exit({'EXIT', parent, Reason});
+ Unknown ->
+ exit({unknown, Unknown})
+ end,
+ tstsrvr_con_loop(Name, Socket, Parent).
+
+%%
+%% Connection handler ssl_node side
+%%
+
+% cnct2tstsrvr() is called via command line arg -run ...
+cnct2tstsrvr([Host, Port]) when is_list(Host), is_list(Port) ->
+ %% Spawn connection handler on ssl node side
+ ConnHandler
+ = spawn(fun () ->
+ case catch gen_tcp:connect(Host,
+ list_to_integer(Port),
+ [binary,
+ {packet, 4},
+ {active, false}]) of
+ {ok, Socket} ->
+ notify_ssl_node_up(Socket),
+ ets:new(test_server_info,
+ [set,
+ public,
+ named_table,
+ {keypos, 1}]),
+ ets:insert(test_server_info,
+ {test_server_handler, self()}),
+ ssl_node_con_loop(Socket);
+ Error ->
+ halt("Failed to connect to test server " ++
+ lists:flatten(io_lib:format("Host:~p ~n Port:~p~n Error:~p~n",
+ [Host, Port, Error])))
+ end
+ end),
+ spawn(fun () ->
+ Mon = erlang:monitor(process, ConnHandler),
+ receive
+ {'DOWN', Mon, process, ConnHandler, Reason} ->
+ receive after 1000 -> ok end,
+ halt("test server connection handler terminated: " ++
+ lists:flatten(io_lib:format("~p", [Reason])))
+ end
+ end).
+
+notify_ssl_node_up(Socket) ->
+ case catch gen_tcp:send(Socket,
+ term_to_binary({ssl_node_up, node()})) of
+ ok -> ok;
+ _ -> halt("Failed to notify test server that I'm up")
+ end.
+
+send_to_tstsrvr(Term) ->
+ case catch ets:lookup_element(test_server_info, test_server_handler, 2) of
+ Hndlr when is_pid(Hndlr) ->
+ Hndlr ! {relay_to_test_server, term_to_binary(Term)}, ok;
+ _ ->
+ receive after 200 -> ok end,
+ send_to_tstsrvr(Term)
+ end.
+
+ssl_node_con_loop(Socket) ->
+ inet:setopts(Socket,[{active,once}]),
+ receive
+ {relay_to_test_server, Data} when is_binary(Data) ->
+ case gen_tcp:send(Socket, Data) of
+ ok ->
+ ok;
+ _Error ->
+ gen_tcp:close(Socket),
+ halt("Failed to relay data to test server")
+ end;
+ {tcp, Socket, Bin} ->
+ case catch binary_to_term(Bin) of
+ {'EXIT', _} ->
+ gen_tcp:close(Socket),
+ halt("test server sent me bad data");
+ {apply, From, Ref, M, F, A} ->
+ spawn_link(
+ fun () ->
+ send_to_tstsrvr({apply_res,
+ From,
+ Ref,
+ (catch apply(M, F, A))})
+ end);
+ {apply, From, Ref, Fun} ->
+ spawn_link(fun () ->
+ send_to_tstsrvr({apply_res,
+ From,
+ Ref,
+ (catch Fun())})
+ end);
+ stop ->
+ gen_tcp:send(Socket, term_to_binary(bye)),
+ init:stop(),
+ receive after infinity -> ok end;
+ _Unknown ->
+ halt("test server sent me an unexpected message")
+ end;
+ {tcp_closed, Socket} ->
+ halt("Lost connection to test server")
+ end,
+ ssl_node_con_loop(Socket).
diff --git a/lib/ssl/test/ssl_dist_test_lib.hrl b/lib/ssl/test/ssl_dist_test_lib.hrl
new file mode 100644
index 0000000000..86b9b37026
--- /dev/null
+++ b/lib/ssl/test/ssl_dist_test_lib.hrl
@@ -0,0 +1,26 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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%
+%%
+
+-record(node_handle,
+ {connection_handler,
+ socket,
+ name,
+ nodename}
+ ).
diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl
index 4e7252f469..03c3ed9be3 100644
--- a/lib/ssl/test/ssl_test_lib.erl
+++ b/lib/ssl/test/ssl_test_lib.erl
@@ -1025,48 +1025,54 @@ string_regex_filter(_Str, _Search) ->
false.
anonymous_suites(Version) ->
- Suites = [ssl_cipher:erl_suite_definition(S) || S <- ssl_cipher:anonymous_suites(Version)],
- ssl_cipher:filter_suites(Suites).
+ [ssl_cipher:erl_suite_definition(S) || S <- ssl_cipher:filter_suites(ssl_cipher:anonymous_suites(Version))].
psk_suites(Version) ->
- Suites = [ssl_cipher:erl_suite_definition(S) || S <- ssl_cipher:psk_suites(Version)],
- ssl_cipher:filter_suites(Suites).
+ [ssl_cipher:erl_suite_definition(S) || S <- ssl_cipher:filter_suites(ssl_cipher:psk_suites(Version))].
psk_anon_suites(Version) ->
- Suites = [Suite || Suite <- psk_suites(Version), is_psk_anon_suite(Suite)],
- ssl_cipher:filter_suites(Suites).
+ [Suite || Suite <- psk_suites(Version), is_psk_anon_suite(Suite)].
srp_suites() ->
- Suites =
- [{srp_anon, '3des_ede_cbc', sha},
- {srp_rsa, '3des_ede_cbc', sha},
- {srp_anon, aes_128_cbc, sha},
- {srp_rsa, aes_128_cbc, sha},
- {srp_anon, aes_256_cbc, sha},
- {srp_rsa, aes_256_cbc, sha}],
- ssl_cipher:filter_suites(Suites).
-
+ [ssl_cipher:erl_suite_definition(Suite) ||
+ Suite <-
+ ssl_cipher:filter_suites([tuple_to_map(S) ||
+ S <- [{srp_anon,'3des_ede_cbc', sha},
+ {srp_rsa, '3des_ede_cbc', sha},
+ {srp_anon, aes_128_cbc, sha},
+ {srp_rsa, aes_128_cbc, sha},
+ {srp_anon, aes_256_cbc, sha},
+ {srp_rsa, aes_256_cbc, sha}]])].
srp_anon_suites() ->
- Suites =
- [{srp_anon, '3des_ede_cbc', sha},
- {srp_anon, aes_128_cbc, sha},
- {srp_anon, aes_256_cbc, sha}],
- ssl_cipher:filter_suites(Suites).
-
+ [ssl_cipher:erl_suite_definition(Suite) ||
+ Suite <-
+ ssl_cipher:filter_suites([tuple_to_map(S) ||
+ S <-[{srp_anon, '3des_ede_cbc', sha},
+ {srp_anon, aes_128_cbc, sha},
+ {srp_anon, aes_256_cbc, sha}]])].
srp_dss_suites() ->
- Suites =
- [{srp_dss, '3des_ede_cbc', sha},
- {srp_dss, aes_128_cbc, sha},
- {srp_dss, aes_256_cbc, sha}],
- ssl_cipher:filter_suites(Suites).
-
+ [ssl_cipher:erl_suite_definition(Suite) ||
+ Suite <-
+ ssl_cipher:filter_suites([tuple_to_map(S) ||
+ S <- [{srp_dss, '3des_ede_cbc', sha},
+ {srp_dss, aes_128_cbc, sha},
+ {srp_dss, aes_256_cbc, sha}]])].
rc4_suites(Version) ->
- Suites = [ssl_cipher:erl_suite_definition(S) || S <- ssl_cipher:rc4_suites(Version)],
- ssl_cipher:filter_suites(Suites).
+ [ssl_cipher:erl_suite_definition(S) || S <- ssl_cipher:filter_suites(ssl_cipher:rc4_suites(Version))].
des_suites(Version) ->
- Suites = ssl_cipher:des_suites(Version),
- ssl_cipher:filter_suites(Suites).
+ [ssl_cipher:erl_suite_definition(S) || S <- ssl_cipher:filter_suites(ssl_cipher:des_suites(Version))].
+
+tuple_to_map({Kex, Cipher, Mac}) ->
+ #{key_exchange => Kex,
+ cipher => Cipher,
+ mac => Mac,
+ prf => default_prf};
+tuple_to_map({Kex, Cipher, Mac, Prf}) ->
+ #{key_exchange => Kex,
+ cipher => Cipher,
+ mac => Mac,
+ prf => Prf}.
pem_to_der(File) ->
{ok, PemBin} = file:read_file(File),
diff --git a/lib/stdlib/doc/src/unicode.xml b/lib/stdlib/doc/src/unicode.xml
index e86f45431f..d822aca89c 100644
--- a/lib/stdlib/doc/src/unicode.xml
+++ b/lib/stdlib/doc/src/unicode.xml
@@ -239,8 +239,13 @@
<c><anno>InEncoding</anno></c>.</p>
</item>
</list>
- <p>Only when <c><anno>InEncoding</anno></c> is one of the UTF
- encodings, integers in the list are allowed to be &gt; 255.</p>
+ <p>
+ Note that integers in the list always represent code points
+ regardless of <c><anno>InEncoding</anno></c> passed. If
+ <c><anno>InEncoding</anno> latin1</c> is passed, only code
+ points &lt; 256 are allowed; otherwise, all valid unicode code
+ points are allowed.
+ </p>
<p>If <c><anno>InEncoding</anno></c> is <c>latin1</c>, parameter
<c><anno>Data</anno></c> corresponds to the <c>iodata()</c> type,
but for <c>unicode</c>, parameter <c><anno>Data</anno></c> can
diff --git a/lib/stdlib/src/base64.erl b/lib/stdlib/src/base64.erl
index c8cf6fdffe..6ea4147abf 100644
--- a/lib/stdlib/src/base64.erl
+++ b/lib/stdlib/src/base64.erl
@@ -24,22 +24,11 @@
-export([encode/1, decode/1, mime_decode/1,
encode_to_string/1, decode_to_string/1, mime_decode_to_string/1]).
-%%-------------------------------------------------------------------------
%% The following type is a subtype of string() for return values
%% of (some) functions of this module.
-%%-------------------------------------------------------------------------
-
-type ascii_string() :: [1..255].
-type ascii_binary() :: binary().
-%%-------------------------------------------------------------------------
-%% encode_to_string(ASCII) -> Base64String
-%% ASCII - string() | binary()
-%% Base64String - string()
-%%
-%% Description: Encodes a plain ASCII string (or binary) into base64.
-%%-------------------------------------------------------------------------
-
-spec encode_to_string(Data) -> Base64String when
Data :: ascii_string() | ascii_binary(),
Base64String :: ascii_string().
@@ -47,66 +36,67 @@
encode_to_string(Bin) when is_binary(Bin) ->
encode_to_string(binary_to_list(Bin));
encode_to_string(List) when is_list(List) ->
- encode_l(List).
-
-%%-------------------------------------------------------------------------
-%% encode(ASCII) -> Base64
-%% ASCII - string() | binary()
-%% Base64 - binary()
-%%
-%% Description: Encodes a plain ASCII string (or binary) into base64.
-%%-------------------------------------------------------------------------
+ encode_list_to_string(List).
-spec encode(Data) -> Base64 when
Data :: ascii_string() | ascii_binary(),
Base64 :: ascii_binary().
encode(Bin) when is_binary(Bin) ->
- encode_binary(Bin);
+ encode_binary(Bin, <<>>);
encode(List) when is_list(List) ->
- list_to_binary(encode_l(List)).
-
--spec encode_l(ascii_string()) -> ascii_string().
+ encode_list(List, <<>>).
-encode_l([]) ->
+encode_list_to_string([]) ->
[];
-encode_l([A]) ->
- [b64e(A bsr 2),
- b64e((A band 3) bsl 4), $=, $=];
-encode_l([A,B]) ->
- [b64e(A bsr 2),
- b64e(((A band 3) bsl 4) bor (B bsr 4)),
- b64e((B band 15) bsl 2), $=];
-encode_l([A,B,C|Ls]) ->
- BB = (A bsl 16) bor (B bsl 8) bor C,
+encode_list_to_string([B1]) ->
+ [b64e(B1 bsr 2),
+ b64e((B1 band 3) bsl 4), $=, $=];
+encode_list_to_string([B1,B2]) ->
+ [b64e(B1 bsr 2),
+ b64e(((B1 band 3) bsl 4) bor (B2 bsr 4)),
+ b64e((B2 band 15) bsl 2), $=];
+encode_list_to_string([B1,B2,B3|Ls]) ->
+ BB = (B1 bsl 16) bor (B2 bsl 8) bor B3,
[b64e(BB bsr 18),
b64e((BB bsr 12) band 63),
b64e((BB bsr 6) band 63),
- b64e(BB band 63) | encode_l(Ls)].
-
-encode_binary(Bin) ->
- Split = 3*(byte_size(Bin) div 3),
- <<Main0:Split/binary,Rest/binary>> = Bin,
- Main = << <<(b64e(C)):8>> || <<C:6>> <= Main0 >>,
- case Rest of
- <<A:6,B:6,C:4>> ->
- <<Main/binary,(b64e(A)):8,(b64e(B)):8,(b64e(C bsl 2)):8,$=:8>>;
- <<A:6,B:2>> ->
- <<Main/binary,(b64e(A)):8,(b64e(B bsl 4)):8,$=:8,$=:8>>;
- <<>> ->
- Main
- end.
+ b64e(BB band 63) | encode_list_to_string(Ls)].
-%%-------------------------------------------------------------------------
-%% mime_decode(Base64) -> ASCII
-%% decode(Base64) -> ASCII
-%% Base64 - string() | binary()
-%% ASCII - binary()
-%%
-%% Description: Decodes an base64 encoded string to plain ASCII.
-%% mime_decode strips away all characters not Base64 before converting,
-%% whereas decode crashes if an illegal character is found
-%%-------------------------------------------------------------------------
+encode_binary(<<>>, A) ->
+ A;
+encode_binary(<<B1:8>>, A) ->
+ <<A/bits,(b64e(B1 bsr 2)):8,(b64e((B1 band 3) bsl 4)):8,$=:8,$=:8>>;
+encode_binary(<<B1:8, B2:8>>, A) ->
+ <<A/bits,(b64e(B1 bsr 2)):8,
+ (b64e(((B1 band 3) bsl 4) bor (B2 bsr 4))):8,
+ (b64e((B2 band 15) bsl 2)):8, $=:8>>;
+encode_binary(<<B1:8, B2:8, B3:8, Ls/bits>>, A) ->
+ BB = (B1 bsl 16) bor (B2 bsl 8) bor B3,
+ encode_binary(Ls,
+ <<A/bits,(b64e(BB bsr 18)):8,
+ (b64e((BB bsr 12) band 63)):8,
+ (b64e((BB bsr 6) band 63)):8,
+ (b64e(BB band 63)):8>>).
+
+encode_list([], A) ->
+ A;
+encode_list([B1], A) ->
+ <<A/bits,(b64e(B1 bsr 2)):8,(b64e((B1 band 3) bsl 4)):8,$=:8,$=:8>>;
+encode_list([B1,B2], A) ->
+ <<A/bits,(b64e(B1 bsr 2)):8,
+ (b64e(((B1 band 3) bsl 4) bor (B2 bsr 4))):8,
+ (b64e((B2 band 15) bsl 2)):8, $=:8>>;
+encode_list([B1,B2,B3|Ls], A) ->
+ BB = (B1 bsl 16) bor (B2 bsl 8) bor B3,
+ encode_list(Ls,
+ <<A/bits,(b64e(BB bsr 18)):8,
+ (b64e((BB bsr 12) band 63)):8,
+ (b64e((BB bsr 6) band 63)):8,
+ (b64e(BB band 63)):8>>).
+
+%% mime_decode strips away all characters not Base64 before
+%% converting, whereas decode crashes if an illegal character is found
-spec decode(Base64) -> Data when
Base64 :: ascii_string() | ascii_binary(),
@@ -122,32 +112,13 @@ decode(List) when is_list(List) ->
Data :: ascii_binary().
mime_decode(Bin) when is_binary(Bin) ->
- mime_decode_binary(<<>>, Bin);
+ mime_decode_binary(Bin, <<>>);
mime_decode(List) when is_list(List) ->
- mime_decode(list_to_binary(List)).
+ mime_decode_list(List, <<>>).
--spec decode_l(ascii_string()) -> ascii_string().
-
-decode_l(List) ->
- L = strip_spaces(List, []),
- decode(L, []).
-
--spec mime_decode_l(ascii_string()) -> ascii_string().
-
-mime_decode_l(List) ->
- L = strip_illegal(List, [], 0),
- decode(L, []).
-
-%%-------------------------------------------------------------------------
-%% mime_decode_to_string(Base64) -> ASCII
-%% decode_to_string(Base64) -> ASCII
-%% Base64 - string() | binary()
-%% ASCII - binary()
-%%
-%% Description: Decodes an base64 encoded string to plain ASCII.
-%% mime_decode strips away all characters not Base64 before converting,
-%% whereas decode crashes if an illegal character is found
-%%-------------------------------------------------------------------------
+%% mime_decode_to_string strips away all characters not Base64 before
+%% converting, whereas decode_to_string crashes if an illegal
+%% character is found
-spec decode_to_string(Base64) -> DataString when
Base64 :: ascii_string() | ascii_binary(),
@@ -156,7 +127,7 @@ mime_decode_l(List) ->
decode_to_string(Bin) when is_binary(Bin) ->
decode_to_string(binary_to_list(Bin));
decode_to_string(List) when is_list(List) ->
- decode_l(List).
+ decode_list_to_string(List).
-spec mime_decode_to_string(Base64) -> DataString when
Base64 :: ascii_string() | ascii_binary(),
@@ -165,115 +136,195 @@ decode_to_string(List) when is_list(List) ->
mime_decode_to_string(Bin) when is_binary(Bin) ->
mime_decode_to_string(binary_to_list(Bin));
mime_decode_to_string(List) when is_list(List) ->
- mime_decode_l(List).
-
-%% One-based decode map.
--define(DECODE_MAP,
- {bad,bad,bad,bad,bad,bad,bad,bad,ws,ws,bad,bad,ws,bad,bad, %1-15
- bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, %16-31
- ws,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,62,bad,bad,bad,63, %32-47
- 52,53,54,55,56,57,58,59,60,61,bad,bad,bad,eq,bad,bad, %48-63
- bad,0,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,bad,bad,bad,bad,bad,
- bad,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
- 41,42,43,44,45,46,47,48,49,50,51,bad,bad,bad,bad,bad,
- bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
- bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
- bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
- bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
- bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
- bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
- bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
- bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad}).
+ mime_decode_list_to_string(List).
-decode_binary(<<C1:8, Cs/bits>>, A) ->
- case element(C1, ?DECODE_MAP) of
- ws -> decode_binary(Cs, A);
- B1 -> decode_binary(Cs, A, B1)
+%% Skipping pad character if not at end of string. Also liberal about
+%% excess padding and skipping of other illegal (non-base64 alphabet)
+%% characters. See section 3.3 of RFC4648
+mime_decode_list([0 | Cs], A) ->
+ mime_decode_list(Cs, A);
+mime_decode_list([C1 | Cs], A) ->
+ case b64d(C1) of
+ B1 when is_integer(B1) -> mime_decode_list(Cs, A, B1);
+ _ -> mime_decode_list(Cs, A) % eq is padding
end;
-decode_binary(<<>>, A) ->
+mime_decode_list([], A) ->
A.
-decode_binary(<<C2:8, Cs/bits>>, A, B1) ->
- case element(C2, ?DECODE_MAP) of
- ws -> decode_binary(Cs, A, B1);
- B2 -> decode_binary(Cs, A, B1, B2)
+mime_decode_list([0 | Cs], A, B1) ->
+ mime_decode_list(Cs, A, B1);
+mime_decode_list([C2 | Cs], A, B1) ->
+ case b64d(C2) of
+ B2 when is_integer(B2) ->
+ mime_decode_list(Cs, A, B1, B2);
+ _ -> mime_decode_list(Cs, A, B1) % eq is padding
end.
-decode_binary(<<C3:8, Cs/bits>>, A, B1, B2) ->
- case element(C3, ?DECODE_MAP) of
- ws -> decode_binary(Cs, A, B1, B2);
- B3 -> decode_binary(Cs, A, B1, B2, B3)
+mime_decode_list([0 | Cs], A, B1, B2) ->
+ mime_decode_list(Cs, A, B1, B2);
+mime_decode_list([C3 | Cs], A, B1, B2) ->
+ case b64d(C3) of
+ B3 when is_integer(B3) ->
+ mime_decode_list(Cs, A, B1, B2, B3);
+ eq=B3 ->
+ mime_decode_list_after_eq(Cs, A, B1, B2, B3);
+ _ -> mime_decode_list(Cs, A, B1, B2)
end.
-decode_binary(<<C4:8, Cs/bits>>, A, B1, B2, B3) ->
- case element(C4, ?DECODE_MAP) of
- ws -> decode_binary(Cs, A, B1, B2, B3);
- eq when B3 =:= eq -> only_ws_binary(Cs, <<A/binary,B1:6,(B2 bsr 4):2>>);
- eq -> only_ws_binary(Cs, <<A/binary,B1:6,B2:6,(B3 bsr 2):4>>);
- B4 -> decode_binary(Cs, <<A/binary,B1:6,B2:6,B3:6,B4:6>>)
+mime_decode_list([0 | Cs], A, B1, B2, B3) ->
+ mime_decode_list(Cs, A, B1, B2, B3);
+mime_decode_list([C4 | Cs], A, B1, B2, B3) ->
+ case b64d(C4) of
+ B4 when is_integer(B4) ->
+ mime_decode_list(Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>);
+ eq ->
+ mime_decode_list_after_eq(Cs, A, B1, B2, B3);
+ _ -> mime_decode_list(Cs, A, B1, B2, B3)
end.
-only_ws_binary(<<>>, A) ->
- A;
-only_ws_binary(<<C:8, Cs/bits>>, A) ->
- case element(C, ?DECODE_MAP) of
- ws -> only_ws_binary(Cs, A);
- _ -> erlang:error(function_clause)
+mime_decode_list_after_eq([0 | Cs], A, B1, B2, B3) ->
+ mime_decode_list_after_eq(Cs, A, B1, B2, B3);
+mime_decode_list_after_eq([C | Cs], A, B1, B2, B3) ->
+ case b64d(C) of
+ B when is_integer(B) ->
+ %% More valid data, skip the eq as invalid
+ case B3 of
+ eq -> mime_decode_list(Cs, A, B1, B2, B);
+ _ -> mime_decode_list(Cs, <<A/bits,B1:6,B2:6,B3:6,B:6>>)
+ end;
+ _ -> mime_decode_list_after_eq(Cs, A, B1, B2, B3)
+ end;
+mime_decode_list_after_eq([], A, B1, B2, eq) ->
+ <<A/bits,B1:6,(B2 bsr 4):2>>;
+mime_decode_list_after_eq([], A, B1, B2, B3) ->
+ <<A/bits,B1:6,B2:6,(B3 bsr 2):4>>.
+
+mime_decode_binary(<<0:8, Cs/bits>>, A) ->
+ mime_decode_binary(Cs, A);
+mime_decode_binary(<<C1:8, Cs/bits>>, A) ->
+ case b64d(C1) of
+ B1 when is_integer(B1) -> mime_decode_binary(Cs, A, B1);
+ _ -> mime_decode_binary(Cs, A) % eq is padding
+ end;
+mime_decode_binary(<<>>, A) ->
+ A.
+
+mime_decode_binary(<<0:8, Cs/bits>>, A, B1) ->
+ mime_decode_binary(Cs, A, B1);
+mime_decode_binary(<<C2:8, Cs/bits>>, A, B1) ->
+ case b64d(C2) of
+ B2 when is_integer(B2) ->
+ mime_decode_binary(Cs, A, B1, B2);
+ _ -> mime_decode_binary(Cs, A, B1) % eq is padding
end.
-%% Skipping pad character if not at end of string. Also liberal about
-%% excess padding and skipping of other illegal (non-base64 alphabet)
-%% characters. See section 3.3 of RFC4648
-mime_decode_binary(Result, <<0:8,T/bits>>) ->
- mime_decode_binary(Result, T);
-mime_decode_binary(Result0, <<C:8,T/bits>>) ->
- case element(C, ?DECODE_MAP) of
- Bits when is_integer(Bits) ->
- mime_decode_binary(<<Result0/bits,Bits:6>>, T);
+mime_decode_binary(<<0:8, Cs/bits>>, A, B1, B2) ->
+ mime_decode_binary(Cs, A, B1, B2);
+mime_decode_binary(<<C3:8, Cs/bits>>, A, B1, B2) ->
+ case b64d(C3) of
+ B3 when is_integer(B3) ->
+ mime_decode_binary(Cs, A, B1, B2, B3);
+ eq=B3 ->
+ mime_decode_binary_after_eq(Cs, A, B1, B2, B3);
+ _ -> mime_decode_binary(Cs, A, B1, B2)
+ end.
+
+mime_decode_binary(<<0:8, Cs/bits>>, A, B1, B2, B3) ->
+ mime_decode_binary(Cs, A, B1, B2, B3);
+mime_decode_binary(<<C4:8, Cs/bits>>, A, B1, B2, B3) ->
+ case b64d(C4) of
+ B4 when is_integer(B4) ->
+ mime_decode_binary(Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>);
eq ->
- mime_decode_binary_after_eq(Result0, T, false);
- _ ->
- mime_decode_binary(Result0, T)
+ mime_decode_binary_after_eq(Cs, A, B1, B2, B3);
+ _ -> mime_decode_binary(Cs, A, B1, B2, B3)
+ end.
+
+mime_decode_binary_after_eq(<<0:8, Cs/bits>>, A, B1, B2, B3) ->
+ mime_decode_binary_after_eq(Cs, A, B1, B2, B3);
+mime_decode_binary_after_eq(<<C:8, Cs/bits>>, A, B1, B2, B3) ->
+ case b64d(C) of
+ B when is_integer(B) ->
+ %% More valid data, skip the eq as invalid
+ case B3 of
+ eq -> mime_decode_binary(Cs, A, B1, B2, B);
+ _ -> mime_decode_binary(Cs, <<A/bits,B1:6,B2:6,B3:6,B:6>>)
+ end;
+ _ -> mime_decode_binary_after_eq(Cs, A, B1, B2, B3)
end;
-mime_decode_binary(Result, _) ->
- true = is_binary(Result),
- Result.
-
-mime_decode_binary_after_eq(Result, <<0:8,T/bits>>, Eq) ->
- mime_decode_binary_after_eq(Result, T, Eq);
-mime_decode_binary_after_eq(Result0, <<C:8,T/bits>>, Eq) ->
- case element(C, ?DECODE_MAP) of
- bad ->
- mime_decode_binary_after_eq(Result0, T, Eq);
- ws ->
- mime_decode_binary_after_eq(Result0, T, Eq);
+mime_decode_binary_after_eq(<<>>, A, B1, B2, eq) ->
+ <<A/bits,B1:6,(B2 bsr 4):2>>;
+mime_decode_binary_after_eq(<<>>, A, B1, B2, B3) ->
+ <<A/bits,B1:6,B2:6,(B3 bsr 2):4>>.
+
+mime_decode_list_to_string([0 | Cs]) ->
+ mime_decode_list_to_string(Cs);
+mime_decode_list_to_string([C1 | Cs]) ->
+ case b64d(C1) of
+ B1 when is_integer(B1) -> mime_decode_list_to_string(Cs, B1);
+ _ -> mime_decode_list_to_string(Cs) % eq is padding
+ end;
+mime_decode_list_to_string([]) ->
+ [].
+
+mime_decode_list_to_string([0 | Cs], B1) ->
+ mime_decode_list_to_string(Cs, B1);
+mime_decode_list_to_string([C2 | Cs], B1) ->
+ case b64d(C2) of
+ B2 when is_integer(B2) ->
+ mime_decode_list_to_string(Cs, B1, B2);
+ _ -> mime_decode_list_to_string(Cs, B1) % eq is padding
+ end.
+
+mime_decode_list_to_string([0 | Cs], B1, B2) ->
+ mime_decode_list_to_string(Cs, B1, B2);
+mime_decode_list_to_string([C3 | Cs], B1, B2) ->
+ case b64d(C3) of
+ B3 when is_integer(B3) ->
+ mime_decode_list_to_string(Cs, B1, B2, B3);
+ eq=B3 -> mime_decode_list_to_string_after_eq(Cs, B1, B2, B3);
+ _ -> mime_decode_list_to_string(Cs, B1, B2)
+ end.
+
+mime_decode_list_to_string([0 | Cs], B1, B2, B3) ->
+ mime_decode_list_to_string(Cs, B1, B2, B3);
+mime_decode_list_to_string([C4 | Cs], B1, B2, B3) ->
+ case b64d(C4) of
+ B4 when is_integer(B4) ->
+ Bits4x6 = (B1 bsl 18) bor (B2 bsl 12) bor (B3 bsl 6) bor B4,
+ Octet1 = Bits4x6 bsr 16,
+ Octet2 = (Bits4x6 bsr 8) band 16#ff,
+ Octet3 = Bits4x6 band 16#ff,
+ [Octet1, Octet2, Octet3 | mime_decode_list_to_string(Cs)];
eq ->
- mime_decode_binary_after_eq(Result0, T, true);
- Bits when is_integer(Bits) ->
+ mime_decode_list_to_string_after_eq(Cs, B1, B2, B3);
+ _ -> mime_decode_list_to_string(Cs, B1, B2, B3)
+ end.
+
+mime_decode_list_to_string_after_eq([0 | Cs], B1, B2, B3) ->
+ mime_decode_list_to_string_after_eq(Cs, B1, B2, B3);
+mime_decode_list_to_string_after_eq([C | Cs], B1, B2, B3) ->
+ case b64d(C) of
+ B when is_integer(B) ->
%% More valid data, skip the eq as invalid
- mime_decode_binary(<<Result0/bits,Bits:6>>, T)
+ case B3 of
+ eq -> mime_decode_list_to_string(Cs, B1, B2, B);
+ _ ->
+ Bits4x6 = (B1 bsl 18) bor (B2 bsl 12) bor (B3 bsl 6) bor B,
+ Octet1 = Bits4x6 bsr 16,
+ Octet2 = (Bits4x6 bsr 8) band 16#ff,
+ Octet3 = Bits4x6 band 16#ff,
+ [Octet1, Octet2, Octet3 | mime_decode_list_to_string(Cs)]
+ end;
+ _ -> mime_decode_list_to_string_after_eq(Cs, B1, B2, B3)
end;
-mime_decode_binary_after_eq(Result0, <<>>, Eq) ->
- %% No more valid data.
- case bit_size(Result0) rem 8 of
- 0 ->
- %% '====' is not uncommon.
- Result0;
- 4 when Eq ->
- %% enforce at least one more '=' only ignoring illegals and spacing
- Split = byte_size(Result0) - 1,
- <<Result:Split/bytes,_:4>> = Result0,
- Result;
- 2 ->
- %% remove 2 bits
- Split = byte_size(Result0) - 1,
- <<Result:Split/bytes,_:2>> = Result0,
- Result
- end.
+mime_decode_list_to_string_after_eq([], B1, B2, eq) ->
+ binary_to_list(<<B1:6,(B2 bsr 4):2>>);
+mime_decode_list_to_string_after_eq([], B1, B2, B3) ->
+ binary_to_list(<<B1:6,B2:6,(B3 bsr 2):4>>).
decode_list([C1 | Cs], A) ->
- case element(C1, ?DECODE_MAP) of
+ case b64d(C1) of
ws -> decode_list(Cs, A);
B1 -> decode_list(Cs, A, B1)
end;
@@ -281,122 +332,130 @@ decode_list([], A) ->
A.
decode_list([C2 | Cs], A, B1) ->
- case element(C2, ?DECODE_MAP) of
+ case b64d(C2) of
ws -> decode_list(Cs, A, B1);
B2 -> decode_list(Cs, A, B1, B2)
end.
decode_list([C3 | Cs], A, B1, B2) ->
- case element(C3, ?DECODE_MAP) of
+ case b64d(C3) of
ws -> decode_list(Cs, A, B1, B2);
B3 -> decode_list(Cs, A, B1, B2, B3)
end.
decode_list([C4 | Cs], A, B1, B2, B3) ->
- case element(C4, ?DECODE_MAP) of
+ case b64d(C4) of
ws -> decode_list(Cs, A, B1, B2, B3);
- eq when B3 =:= eq -> only_ws(Cs, <<A/binary,B1:6,(B2 bsr 4):2>>);
- eq -> only_ws(Cs, <<A/binary,B1:6,B2:6,(B3 bsr 2):4>>);
- B4 -> decode_list(Cs, <<A/binary,B1:6,B2:6,B3:6,B4:6>>)
+ eq when B3 =:= eq -> only_ws(Cs, <<A/bits,B1:6,(B2 bsr 4):2>>);
+ eq -> only_ws(Cs, <<A/bits,B1:6,B2:6,(B3 bsr 2):4>>);
+ B4 -> decode_list(Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>)
end.
-only_ws([], A) ->
- A;
-only_ws([C | Cs], A) ->
- case element(C, ?DECODE_MAP) of
- ws -> only_ws(Cs, A);
- _ -> erlang:error(function_clause)
- end.
+decode_binary(<<C1:8, Cs/bits>>, A) ->
+ case b64d(C1) of
+ ws -> decode_binary(Cs, A);
+ B1 -> decode_binary(Cs, A, B1)
+ end;
+decode_binary(<<>>, A) ->
+ A.
-decode([], A) -> A;
-decode([$=,$=,C2,C1|Cs], A) ->
- Bits2x6 = (b64d(C1) bsl 18) bor (b64d(C2) bsl 12),
- Octet1 = Bits2x6 bsr 16,
- decode(Cs, [Octet1|A]);
-decode([$=,C3,C2,C1|Cs], A) ->
- Bits3x6 = (b64d(C1) bsl 18) bor (b64d(C2) bsl 12)
- bor (b64d(C3) bsl 6),
- Octet1 = Bits3x6 bsr 16,
- Octet2 = (Bits3x6 bsr 8) band 16#ff,
- decode(Cs, [Octet1,Octet2|A]);
-decode([C4,C3,C2,C1| Cs], A) ->
- Bits4x6 = (b64d(C1) bsl 18) bor (b64d(C2) bsl 12)
- bor (b64d(C3) bsl 6) bor b64d(C4),
- Octet1 = Bits4x6 bsr 16,
- Octet2 = (Bits4x6 bsr 8) band 16#ff,
- Octet3 = Bits4x6 band 16#ff,
- decode(Cs, [Octet1,Octet2,Octet3|A]).
+decode_binary(<<C2:8, Cs/bits>>, A, B1) ->
+ case b64d(C2) of
+ ws -> decode_binary(Cs, A, B1);
+ B2 -> decode_binary(Cs, A, B1, B2)
+ end.
-%%%========================================================================
-%%% Internal functions
-%%%========================================================================
+decode_binary(<<C3:8, Cs/bits>>, A, B1, B2) ->
+ case b64d(C3) of
+ ws -> decode_binary(Cs, A, B1, B2);
+ B3 -> decode_binary(Cs, A, B1, B2, B3)
+ end.
-strip_spaces([], A) -> A;
-strip_spaces([$\s|Cs], A) -> strip_spaces(Cs, A);
-strip_spaces([$\t|Cs], A) -> strip_spaces(Cs, A);
-strip_spaces([$\r|Cs], A) -> strip_spaces(Cs, A);
-strip_spaces([$\n|Cs], A) -> strip_spaces(Cs, A);
-strip_spaces([C|Cs], A) -> strip_spaces(Cs, [C | A]).
+decode_binary(<<C4:8, Cs/bits>>, A, B1, B2, B3) ->
+ case b64d(C4) of
+ ws -> decode_binary(Cs, A, B1, B2, B3);
+ eq when B3 =:= eq -> only_ws_binary(Cs, <<A/bits,B1:6,(B2 bsr 4):2>>);
+ eq -> only_ws_binary(Cs, <<A/bits,B1:6,B2:6,(B3 bsr 2):4>>);
+ B4 -> decode_binary(Cs, <<A/bits,B1:6,B2:6,B3:6,B4:6>>)
+ end.
-%% Skipping pad character if not at end of string. Also liberal about
-%% excess padding and skipping of other illegal (non-base64 alphabet)
-%% characters. See section 3.3 of RFC4648
-strip_illegal([], A, _Cnt) ->
+only_ws_binary(<<>>, A) ->
A;
-strip_illegal([0|Cs], A, Cnt) ->
- strip_illegal(Cs, A, Cnt);
-strip_illegal([C|Cs], A, Cnt) ->
- case element(C, ?DECODE_MAP) of
- bad ->
- strip_illegal(Cs, A, Cnt);
- ws ->
- strip_illegal(Cs, A, Cnt);
- eq ->
- case {tail_contains_more(Cs, false), Cnt rem 4} of
- {{[], _}, 0} ->
- A; %% Ignore extra =
- {{[], true}, 2} ->
- [$=|[$=|A]]; %% 'XX=='
- {{[], _}, 3} ->
- [$=|A]; %% 'XXX='
- {{[H|T], _}, _} ->
- %% more data, skip equals
- strip_illegal(T, [H|A], Cnt+1)
- end;
- _ ->
- strip_illegal(Cs, [C|A], Cnt+1)
+only_ws_binary(<<C:8, Cs/bits>>, A) ->
+ case b64d(C) of
+ ws -> only_ws_binary(Cs, A)
end.
-%% Search the tail for more valid data and remember if we saw
-%% another equals along the way.
-tail_contains_more([], Eq) ->
- {[], Eq};
-tail_contains_more(<<>>, Eq) ->
- {<<>>, Eq};
-tail_contains_more([C|T]=More, Eq) ->
- case element(C, ?DECODE_MAP) of
- bad ->
- tail_contains_more(T, Eq);
- ws ->
- tail_contains_more(T, Eq);
- eq ->
- tail_contains_more(T, true);
- _ ->
- {More, Eq}
+decode_list_to_string([C1 | Cs]) ->
+ case b64d(C1) of
+ ws -> decode_list_to_string(Cs);
+ B1 -> decode_list_to_string(Cs, B1)
end;
-tail_contains_more(<<C:8,T/bits>> =More, Eq) ->
- case element(C, ?DECODE_MAP) of
- bad ->
- tail_contains_more(T, Eq);
- ws ->
- tail_contains_more(T, Eq);
- eq ->
- tail_contains_more(T, true);
- _ ->
- {More, Eq}
+decode_list_to_string([]) ->
+ [].
+
+decode_list_to_string([C2 | Cs], B1) ->
+ case b64d(C2) of
+ ws -> decode_list_to_string(Cs, B1);
+ B2 -> decode_list_to_string(Cs, B1, B2)
+ end.
+
+decode_list_to_string([C3 | Cs], B1, B2) ->
+ case b64d(C3) of
+ ws -> decode_list_to_string(Cs, B1, B2);
+ B3 -> decode_list_to_string(Cs, B1, B2, B3)
+ end.
+
+decode_list_to_string([C4 | Cs], B1, B2, B3) ->
+ case b64d(C4) of
+ ws ->
+ decode_list_to_string(Cs, B1, B2, B3);
+ eq when B3 =:= eq ->
+ only_ws(Cs, binary_to_list(<<B1:6,(B2 bsr 4):2>>));
+ eq ->
+ only_ws(Cs, binary_to_list(<<B1:6,B2:6,(B3 bsr 2):4>>));
+ B4 ->
+ Bits4x6 = (B1 bsl 18) bor (B2 bsl 12) bor (B3 bsl 6) bor B4,
+ Octet1 = Bits4x6 bsr 16,
+ Octet2 = (Bits4x6 bsr 8) band 16#ff,
+ Octet3 = Bits4x6 band 16#ff,
+ [Octet1, Octet2, Octet3 | decode_list_to_string(Cs)]
+ end.
+
+only_ws([], A) ->
+ A;
+only_ws([C | Cs], A) ->
+ case b64d(C) of
+ ws -> only_ws(Cs, A)
end.
-
+
+%%%========================================================================
+%%% Internal functions
+%%%========================================================================
+
%% accessors
+-compile({inline, [{b64d, 1}]}).
+%% One-based decode map.
+b64d(X) ->
+ element(X,
+ {bad,bad,bad,bad,bad,bad,bad,bad,ws,ws,bad,bad,ws,bad,bad, %1-15
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad, %16-31
+ ws,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,62,bad,bad,bad,63, %32-47
+ 52,53,54,55,56,57,58,59,60,61,bad,bad,bad,eq,bad,bad, %48-63
+ bad,0,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,bad,bad,bad,bad,bad,
+ bad,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
+ 41,42,43,44,45,46,47,48,49,50,51,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,
+ bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad,bad}).
+
+-compile({inline, [{b64e, 1}]}).
b64e(X) ->
element(X+1,
{$A, $B, $C, $D, $E, $F, $G, $H, $I, $J, $K, $L, $M, $N,
@@ -404,9 +463,3 @@ b64e(X) ->
$a, $b, $c, $d, $e, $f, $g, $h, $i, $j, $k, $l, $m, $n,
$o, $p, $q, $r, $s, $t, $u, $v, $w, $x, $y, $z,
$0, $1, $2, $3, $4, $5, $6, $7, $8, $9, $+, $/}).
-
-
-b64d(X) ->
- b64d_ok(element(X, ?DECODE_MAP)).
-
-b64d_ok(I) when is_integer(I) -> I.
diff --git a/lib/stdlib/src/gen.erl b/lib/stdlib/src/gen.erl
index 33af0aed8f..4b1d448487 100644
--- a/lib/stdlib/src/gen.erl
+++ b/lib/stdlib/src/gen.erl
@@ -49,6 +49,7 @@
| {'logfile', string()}.
-type option() :: {'timeout', timeout()}
| {'debug', [debug_flag()]}
+ | {'hibernate_after', timeout()}
| {'spawn_opt', [proc_lib:spawn_option()]}.
-type options() :: [option()].
diff --git a/lib/stdlib/test/base64_SUITE.erl b/lib/stdlib/test/base64_SUITE.erl
index 48b3f5f959..1fc4c3fc0e 100644
--- a/lib/stdlib/test/base64_SUITE.erl
+++ b/lib/stdlib/test/base64_SUITE.erl
@@ -97,10 +97,9 @@ base64_otp_5635(Config) when is_list(Config) ->
<<"===">> = base64:decode(base64:encode("===")),
ok.
%%-------------------------------------------------------------------------
-%% OTP-6279: Guard needed so that function fails in a correct
-%% way for faulty input, i.e. function_clause.
+%% OTP-6279: Make sure illegal characters are rejected when decoding.
base64_otp_6279(Config) when is_list(Config) ->
- {'EXIT',{function_clause, _}} = (catch base64:decode("dGVzda==a")),
+ {'EXIT',_} = (catch base64:decode("dGVzda==a")),
ok.
%%-------------------------------------------------------------------------
%% Encode and decode big binaries.
@@ -115,48 +114,61 @@ big(Config) when is_list(Config) ->
%%-------------------------------------------------------------------------
%% Make sure illegal characters are rejected when decoding.
illegal(Config) when is_list(Config) ->
- {'EXIT',{function_clause, _}} = (catch base64:decode("()")),
+ %% A few samples with different error reasons. Nothing can be
+ %% assumed about the reason for the crash.
+ {'EXIT',_} = (catch base64:decode("()")),
+ {'EXIT',_} = (catch base64:decode(<<19:8,20:8,21:8,22:8>>)),
+ {'EXIT',_} = (catch base64:decode([19,20,21,22])),
+ {'EXIT',_} = (catch base64:decode_to_string(<<19:8,20:8,21:8,22:8>>)),
+ {'EXIT',_} = (catch base64:decode_to_string([19,20,21,22])),
ok.
%%-------------------------------------------------------------------------
%% mime_decode and mime_decode_to_string have different implementations
-%% so test both with the same input separately. Both functions have
-%% the same implementation for binary/string arguments.
+%% so test both with the same input separately.
%%
%% Test base64:mime_decode/1.
mime_decode(Config) when is_list(Config) ->
+ MimeDecode = fun(In) ->
+ Out = base64:mime_decode(In),
+ Out = base64:mime_decode(binary_to_list(In))
+ end,
%% Test correct padding
- <<"one">> = base64:mime_decode(<<"b25l">>),
- <<"on">> = base64:mime_decode(<<"b24=">>),
- <<"o">> = base64:mime_decode(<<"bw==">>),
+ <<"one">> = MimeDecode(<<"b25l">>),
+ <<"on">> = MimeDecode(<<"b24=">>),
+ <<"o">> = MimeDecode(<<"bw==">>),
%% Test 1 extra padding
- <<"one">> = base64:mime_decode(<<"b25l= =">>),
- <<"on">> = base64:mime_decode(<<"b24== =">>),
- <<"o">> = base64:mime_decode(<<"bw=== =">>),
+ <<"one">> = MimeDecode(<<"b25l= =">>),
+ <<"on">> = MimeDecode(<<"b24== =">>),
+ <<"o">> = MimeDecode(<<"bw=== =">>),
%% Test 2 extra padding
- <<"one">> = base64:mime_decode(<<"b25l===">>),
- <<"on">> = base64:mime_decode(<<"b24====">>),
- <<"o">> = base64:mime_decode(<<"bw=====">>),
+ <<"one">> = MimeDecode(<<"b25l===">>),
+ <<"on">> = MimeDecode(<<"b24====">>),
+ <<"o">> = MimeDecode(<<"bw=====">>),
%% Test misc embedded padding
- <<"one">> = base64:mime_decode(<<"b2=5l===">>),
- <<"on">> = base64:mime_decode(<<"b=24====">>),
- <<"o">> = base64:mime_decode(<<"b=w=====">>),
+ <<"one">> = MimeDecode(<<"b2=5l===">>),
+ <<"on">> = MimeDecode(<<"b=24====">>),
+ <<"o">> = MimeDecode(<<"b=w=====">>),
%% Test misc white space and illegals with embedded padding
- <<"one">> = base64:mime_decode(<<" b~2=\r\n5()l===">>),
- <<"on">> = base64:mime_decode(<<"\tb =2\"¤4=¤= ==">>),
- <<"o">> = base64:mime_decode(<<"\nb=w=====">>),
+ <<"one">> = MimeDecode(<<" b~2=\r\n5()l===">>),
+ <<"on">> = MimeDecode(<<"\tb =2\"¤4=¤= ==">>),
+ <<"o">> = MimeDecode(<<"\nb=w=====">>),
%% Two pads
<<"Aladdin:open sesame">> =
- base64:mime_decode("QWxhZGRpbjpvc()GVuIHNlc2FtZQ=="),
+ MimeDecode(<<"QWxhZGRpbjpvc()GVuIHNlc2FtZQ==">>),
%% One pad to ignore, followed by more text
- <<"Hello World!!">> = base64:mime_decode(<<"SGVsb)(G8gV29ybGQ=h IQ= =">>),
+ <<"Hello World!!">> = MimeDecode(<<"SGVsb)(G8gV29ybGQ=h IQ= =">>),
%% No pad
<<"Aladdin:open sesam">> =
- base64:mime_decode("QWxhZGRpbjpvcG¤\")(VuIHNlc2Ft"),
+ MimeDecode(<<"QWxhZGRpbjpvcG¤\")(VuIHNlc2Ft">>),
%% Encoded base 64 strings may be divided by non base 64 chars.
%% In this cases whitespaces.
<<"0123456789!@#0^&*();:<>,. []{}">> =
- base64:mime_decode(
+ MimeDecode(
<<"MDEy MzQ1Njc4 \tOSFAIzBeJ \nio)(oKTs6 PD4sLi \r\nBbXXt9">>),
+ %% Zeroes
+ <<"012">> = MimeDecode(<<"\000M\000D\000E\000y=\000">>),
+ <<"o">> = MimeDecode(<<"bw==\000">>),
+ <<"o">> = MimeDecode(<<"bw=\000=">>),
ok.
%%-------------------------------------------------------------------------
@@ -165,39 +177,48 @@ mime_decode(Config) when is_list(Config) ->
%% Test base64:mime_decode_to_string/1.
mime_decode_to_string(Config) when is_list(Config) ->
+ MimeDecodeToString =
+ fun(In) ->
+ Out = base64:mime_decode_to_string(In),
+ Out = base64:mime_decode_to_string(binary_to_list(In))
+ end,
%% Test correct padding
- "one" = base64:mime_decode_to_string(<<"b25l">>),
- "on" = base64:mime_decode_to_string(<<"b24=">>),
- "o" = base64:mime_decode_to_string(<<"bw==">>),
+ "one" = MimeDecodeToString(<<"b25l">>),
+ "on" = MimeDecodeToString(<<"b24=">>),
+ "o" = MimeDecodeToString(<<"bw==">>),
%% Test 1 extra padding
- "one" = base64:mime_decode_to_string(<<"b25l= =">>),
- "on" = base64:mime_decode_to_string(<<"b24== =">>),
- "o" = base64:mime_decode_to_string(<<"bw=== =">>),
+ "one" = MimeDecodeToString(<<"b25l= =">>),
+ "on" = MimeDecodeToString(<<"b24== =">>),
+ "o" = MimeDecodeToString(<<"bw=== =">>),
%% Test 2 extra padding
- "one" = base64:mime_decode_to_string(<<"b25l===">>),
- "on" = base64:mime_decode_to_string(<<"b24====">>),
- "o" = base64:mime_decode_to_string(<<"bw=====">>),
+ "one" = MimeDecodeToString(<<"b25l===">>),
+ "on" = MimeDecodeToString(<<"b24====">>),
+ "o" = MimeDecodeToString(<<"bw=====">>),
%% Test misc embedded padding
- "one" = base64:mime_decode_to_string(<<"b2=5l===">>),
- "on" = base64:mime_decode_to_string(<<"b=24====">>),
- "o" = base64:mime_decode_to_string(<<"b=w=====">>),
+ "one" = MimeDecodeToString(<<"b2=5l===">>),
+ "on" = MimeDecodeToString(<<"b=24====">>),
+ "o" = MimeDecodeToString(<<"b=w=====">>),
%% Test misc white space and illegals with embedded padding
- "one" = base64:mime_decode_to_string(<<" b~2=\r\n5()l===">>),
- "on" = base64:mime_decode_to_string(<<"\tb =2\"¤4=¤= ==">>),
- "o" = base64:mime_decode_to_string(<<"\nb=w=====">>),
+ "one" = MimeDecodeToString(<<" b~2=\r\n5()l===">>),
+ "on" = MimeDecodeToString(<<"\tb =2\"¤4=¤= ==">>),
+ "o" = MimeDecodeToString(<<"\nb=w=====">>),
%% Two pads
"Aladdin:open sesame" =
- base64:mime_decode_to_string("QWxhZGRpbjpvc()GVuIHNlc2FtZQ=="),
+ MimeDecodeToString(<<"QWxhZGRpbjpvc()GVuIHNlc2FtZQ==">>),
%% One pad to ignore, followed by more text
- "Hello World!!" = base64:mime_decode_to_string(<<"SGVsb)(G8gV29ybGQ=h IQ= =">>),
+ "Hello World!!" = MimeDecodeToString(<<"SGVsb)(G8gV29ybGQ=h IQ= =">>),
%% No pad
"Aladdin:open sesam" =
- base64:mime_decode_to_string("QWxhZGRpbjpvcG¤\")(VuIHNlc2Ft"),
+ MimeDecodeToString(<<"QWxhZGRpbjpvcG¤\")(VuIHNlc2Ft">>),
%% Encoded base 64 strings may be divided by non base 64 chars.
%% In this cases whitespaces.
"0123456789!@#0^&*();:<>,. []{}" =
- base64:mime_decode_to_string(
+ MimeDecodeToString(
<<"MDEy MzQ1Njc4 \tOSFAIzBeJ \nio)(oKTs6 PD4sLi \r\nBbXXt9">>),
+ %% Zeroes
+ "012" = MimeDecodeToString(<<"\000M\000D\000E\000y=\000">>),
+ "o" = MimeDecodeToString(<<"bw==\000">>),
+ "o" = MimeDecodeToString(<<"bw=\000=">>),
ok.
%%-------------------------------------------------------------------------
diff --git a/lib/stdlib/test/filelib_SUITE.erl b/lib/stdlib/test/filelib_SUITE.erl
index 1236fe45f4..930cea347f 100644
--- a/lib/stdlib/test/filelib_SUITE.erl
+++ b/lib/stdlib/test/filelib_SUITE.erl
@@ -33,6 +33,8 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
+-define(PRIM_FILE, prim_file).
+
init_per_testcase(_Case, Config) ->
Config.
@@ -446,10 +448,10 @@ wildcard_symlink(Config) when is_list(Config) ->
erl_prim_loader)),
["sub","symlink"] =
basenames(Dir, filelib:wildcard(filename:join(Dir, "*"),
- prim_file)),
+ ?PRIM_FILE)),
["symlink"] =
basenames(Dir, filelib:wildcard(filename:join(Dir, "symlink"),
- prim_file)),
+ ?PRIM_FILE)),
ok = file:delete(AFile),
%% The symlink should still be visible even when its target
%% has been deleted.
@@ -465,10 +467,10 @@ wildcard_symlink(Config) when is_list(Config) ->
erl_prim_loader)),
["sub","symlink"] =
basenames(Dir, filelib:wildcard(filename:join(Dir, "*"),
- prim_file)),
+ ?PRIM_FILE)),
["symlink"] =
basenames(Dir, filelib:wildcard(filename:join(Dir, "symlink"),
- prim_file)),
+ ?PRIM_FILE)),
ok
end.
@@ -497,17 +499,17 @@ is_file_symlink(Config) ->
ok ->
true = filelib:is_dir(DirAlias),
true = filelib:is_dir(DirAlias, erl_prim_loader),
- true = filelib:is_dir(DirAlias, prim_file),
+ true = filelib:is_dir(DirAlias, ?PRIM_FILE),
true = filelib:is_file(DirAlias),
true = filelib:is_file(DirAlias, erl_prim_loader),
- true = filelib:is_file(DirAlias, prim_file),
+ true = filelib:is_file(DirAlias, ?PRIM_FILE),
ok = file:make_symlink(AFile,FileAlias),
true = filelib:is_file(FileAlias),
true = filelib:is_file(FileAlias, erl_prim_loader),
- true = filelib:is_file(FileAlias, prim_file),
+ true = filelib:is_file(FileAlias, ?PRIM_FILE),
true = filelib:is_regular(FileAlias),
true = filelib:is_regular(FileAlias, erl_prim_loader),
- true = filelib:is_regular(FileAlias, prim_file),
+ true = filelib:is_regular(FileAlias, ?PRIM_FILE),
ok
end.
@@ -528,11 +530,11 @@ file_props_symlink(Config) ->
{_,_} = LastMod = filelib:last_modified(AFile),
LastMod = filelib:last_modified(Alias),
LastMod = filelib:last_modified(Alias, erl_prim_loader),
- LastMod = filelib:last_modified(Alias, prim_file),
+ LastMod = filelib:last_modified(Alias, ?PRIM_FILE),
FileSize = filelib:file_size(AFile),
FileSize = filelib:file_size(Alias),
FileSize = filelib:file_size(Alias, erl_prim_loader),
- FileSize = filelib:file_size(Alias, prim_file)
+ FileSize = filelib:file_size(Alias, ?PRIM_FILE)
end.
find_source(Config) when is_list(Config) ->
diff --git a/lib/stdlib/test/qlc_SUITE.erl b/lib/stdlib/test/qlc_SUITE.erl
index 949142ec77..8f8a0f6e73 100644
--- a/lib/stdlib/test/qlc_SUITE.erl
+++ b/lib/stdlib/test/qlc_SUITE.erl
@@ -1695,28 +1695,7 @@ sort(Config) when is_list(Config) ->
[true || I <- lists:seq(1, 50000), not ets:insert(E, {I, I})],
H = qlc:q([{X,Y} || X <- [a,b], Y <- qlc:sort(ets:table(E))]),
100000 = length(qlc:e(H)),
- ets:delete(E)">>,
-
- begin
- TmpDir = ?privdir,
- [<<"TE = process_flag(trap_exit, true),
- E = ets:new(foo, []),
- [true || I <- lists:seq(1, 50000), not ets:insert(E, {I, I})],
- Ports = erlang:ports(),
- H = qlc:q([{X,Y} || X <- [a,b],
- begin
- [P] = erlang:ports() -- Ports,
- exit(P, port_exit),
- true
- end,
- Y <- qlc:sort(ets:table(E),
- [{tmpdir,\"">>,
- TmpDir, <<"\"}])]),
- {error, qlc, {file_error, _, _}} = (catch qlc:e(H)),
- receive {'EXIT', _, port_exit} -> ok end,
- ets:delete(E),
- process_flag(trap_exit, TE)">>]
- end
+ ets:delete(E)">>
],
run(Config, Ts),
diff --git a/lib/stdlib/test/stdlib_bench_SUITE.erl b/lib/stdlib/test/stdlib_bench_SUITE.erl
index 8670e7029c..2a9981bb9e 100644
--- a/lib/stdlib/test/stdlib_bench_SUITE.erl
+++ b/lib/stdlib/test/stdlib_bench_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2012-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.
@@ -28,13 +28,20 @@ suite() -> [{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}].
all() ->
- [{group,unicode}].
+ [{group,unicode}, {group,base64}].
groups() ->
[{unicode,[{repeat,5}],
[norm_nfc_list, norm_nfc_deep_l, norm_nfc_binary,
string_lexemes_list, string_lexemes_binary
- ]}].
+ ]},
+ {base64,[{repeat,5}],
+ [decode_binary, decode_binary_to_string,
+ decode_list, decode_list_to_string,
+ encode_binary, encode_binary_to_string,
+ encode_list, encode_list_to_string,
+ mime_binary_decode, mime_binary_decode_to_string,
+ mime_list_decode, mime_list_decode_to_string]}].
init_per_group(_GroupName, Config) ->
Config.
@@ -105,3 +112,97 @@ norm_data(Config) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+decode_binary(_Config) ->
+ test(decode, encoded_binary()).
+
+decode_binary_to_string(_Config) ->
+ test(decode_to_string, encoded_binary()).
+
+decode_list(_Config) ->
+ test(decode, encoded_list()).
+
+decode_list_to_string(_Config) ->
+ test(decode_to_string, encoded_list()).
+
+encode_binary(_Config) ->
+ test(encode, binary()).
+
+encode_binary_to_string(_Config) ->
+ test(encode_to_string, binary()).
+
+encode_list(_Config) ->
+ test(encode, list()).
+
+encode_list_to_string(_Config) ->
+ test(encode_to_string, list()).
+
+mime_binary_decode(_Config) ->
+ test(mime_decode, encoded_binary()).
+
+mime_binary_decode_to_string(_Config) ->
+ test(mime_decode_to_string, encoded_binary()).
+
+mime_list_decode(_Config) ->
+ test(mime_decode, encoded_list()).
+
+mime_list_decode_to_string(_Config) ->
+ test(mime_decode_to_string, encoded_list()).
+
+-define(SIZE, 10000).
+-define(N, 1000).
+
+encoded_binary() ->
+ list_to_binary(encoded_list()).
+
+encoded_list() ->
+ L = random_byte_list(round(?SIZE*0.75)),
+ base64:encode_to_string(L).
+
+binary() ->
+ list_to_binary(list()).
+
+list() ->
+ random_byte_list(?SIZE).
+
+test(Func, Data) ->
+ F = fun() -> loop(?N, Func, Data) end,
+ {Time, ok} = timer:tc(fun() -> lspawn(F) end),
+ report_base64(Time).
+
+loop(0, _F, _D) -> garbage_collect(), ok;
+loop(N, F, D) ->
+ _ = base64:F(D),
+ loop(N - 1, F, D).
+
+lspawn(Fun) ->
+ {Pid, Ref} = spawn_monitor(fun() -> exit(Fun()) end),
+ receive
+ {'DOWN', Ref, process, Pid, Rep} -> Rep
+ end.
+
+report_base64(Time) ->
+ Tps = round((?N*1000000)/Time),
+ ct_event:notify(#event{name = benchmark_data,
+ data = [{suite, "stdlib_base64"},
+ {value, Tps}]}),
+ Tps.
+
+%% Copied from base64_SUITE.erl.
+
+random_byte_list(N) ->
+ random_byte_list(N, []).
+
+random_byte_list(0, Acc) ->
+ Acc;
+random_byte_list(N, Acc) ->
+ random_byte_list(N-1, [rand:uniform(255)|Acc]).
+
+make_big_binary(N) ->
+ list_to_binary(mbb(N, [])).
+
+mbb(N, Acc) when N > 256 ->
+ B = list_to_binary(lists:seq(0, 255)),
+ mbb(N - 256, [B | Acc]);
+mbb(N, Acc) ->
+ B = list_to_binary(lists:seq(0, N-1)),
+ lists:reverse(Acc, B).
diff --git a/lib/tools/test/fprof_SUITE.erl b/lib/tools/test/fprof_SUITE.erl
index 8fd164a4b3..ae0e7253ad 100644
--- a/lib/tools/test/fprof_SUITE.erl
+++ b/lib/tools/test/fprof_SUITE.erl
@@ -51,7 +51,7 @@
suite() ->
[{ct_hooks,[ts_install_cth]},
- {timetrap,{seconds,60}}].
+ {timetrap,{seconds,240}}].
all() ->
case test_server:is_native(fprof_SUITE) of
@@ -571,7 +571,7 @@ seq_r(Start, Stop, Succ, R) ->
create_file_slow(Name, N) when is_integer(N), N >= 0 ->
{ok, FD} =
- file:open(Name, [raw, write, delayed_write, binary]),
+ file:open(Name, [raw, write, binary]),
if N > 256 ->
ok = file:write(FD,
lists:map(fun (X) -> <<X:32/unsigned>> end,