aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/common_test/doc/src/notes.xml60
-rw-r--r--lib/common_test/vsn.mk2
-rw-r--r--lib/compiler/doc/src/notes.xml32
-rwxr-xr-xlib/compiler/scripts/smoke1
-rw-r--r--lib/compiler/src/beam_validator.erl14
-rw-r--r--lib/compiler/test/beam_except_SUITE.erl13
-rw-r--r--lib/compiler/test/beam_validator_SUITE.erl32
-rw-r--r--lib/compiler/vsn.mk2
-rw-r--r--lib/crypto/c_src/Makefile.in3
-rw-r--r--lib/crypto/c_src/aes.c151
-rw-r--r--lib/crypto/c_src/aes.h3
-rw-r--r--lib/crypto/c_src/api_ng.c595
-rw-r--r--lib/crypto/c_src/api_ng.h1
-rw-r--r--lib/crypto/c_src/atoms.c2
-rw-r--r--lib/crypto/c_src/atoms.h1
-rw-r--r--lib/crypto/c_src/block.c149
-rw-r--r--lib/crypto/c_src/block.h28
-rw-r--r--lib/crypto/c_src/chacha20.c124
-rw-r--r--lib/crypto/c_src/chacha20.h29
-rw-r--r--lib/crypto/c_src/cipher.c9
-rw-r--r--lib/crypto/c_src/cipher.h5
-rw-r--r--lib/crypto/c_src/crypto.c15
-rw-r--r--lib/crypto/c_src/openssl_config.h8
-rw-r--r--lib/crypto/c_src/pkey.c5
-rw-r--r--lib/crypto/c_src/poly1305.c2
-rw-r--r--lib/crypto/c_src/rc4.c92
-rw-r--r--lib/crypto/c_src/rc4.h29
-rw-r--r--lib/crypto/doc/src/notes.xml32
-rw-r--r--lib/crypto/src/crypto.erl554
-rw-r--r--lib/crypto/test/crypto_SUITE.erl366
-rw-r--r--lib/crypto/vsn.mk2
-rw-r--r--lib/dialyzer/doc/src/notes.xml30
-rw-r--r--lib/dialyzer/vsn.mk2
-rw-r--r--lib/diameter/doc/src/notes.xml35
-rw-r--r--lib/diameter/vsn.mk2
-rw-r--r--lib/edoc/doc/src/notes.xml16
-rw-r--r--lib/edoc/vsn.mk2
-rw-r--r--lib/erl_docgen/doc/src/notes.xml26
-rw-r--r--lib/erl_docgen/vsn.mk2
-rw-r--r--lib/erl_interface/doc/src/ei.xml2
-rw-r--r--lib/erl_interface/doc/src/ei_connect.xml10
-rw-r--r--lib/erl_interface/doc/src/notes.xml37
-rw-r--r--lib/erl_interface/src/prog/ei_fake_prog.c11
-rw-r--r--lib/erl_interface/src/prog/erl_call.c54
-rw-r--r--lib/erl_interface/src/prog/erl_start.c2
-rw-r--r--lib/erl_interface/test/Makefile1
-rw-r--r--lib/erl_interface/test/erl_call_SUITE.erl96
-rw-r--r--lib/erl_interface/vsn.mk2
-rw-r--r--lib/ftp/doc/src/notes.xml18
-rw-r--r--lib/ftp/vsn.mk2
-rw-r--r--lib/hipe/doc/src/notes.xml15
-rw-r--r--lib/hipe/vsn.mk2
-rw-r--r--lib/inets/doc/src/notes.xml38
-rw-r--r--lib/inets/vsn.mk2
-rw-r--r--lib/kernel/doc/src/application.xml4
-rw-r--r--lib/kernel/doc/src/logger.xml4
-rw-r--r--lib/kernel/doc/src/notes.xml179
-rw-r--r--lib/kernel/src/kernel.appup.src6
-rw-r--r--lib/kernel/test/code_SUITE.erl10
-rw-r--r--lib/kernel/vsn.mk2
-rw-r--r--lib/mnesia/doc/src/notes.xml17
-rw-r--r--lib/mnesia/vsn.mk2
-rw-r--r--lib/observer/doc/src/notes.xml39
-rw-r--r--lib/observer/vsn.mk2
-rw-r--r--lib/odbc/doc/src/notes.xml17
-rw-r--r--lib/odbc/vsn.mk2
-rw-r--r--lib/public_key/doc/src/notes.xml15
-rw-r--r--lib/public_key/doc/src/public_key.xml2
-rw-r--r--lib/public_key/src/pubkey_pbe.erl30
-rw-r--r--lib/public_key/src/pubkey_pem.erl4
-rw-r--r--lib/public_key/test/pbe_SUITE.erl38
-rw-r--r--lib/public_key/test/pbe_SUITE_data/old_aes_128_cbc.pem (renamed from lib/public_key/test/pbe_SUITE_data/old_aes_128_cbc_enc_key.pem)0
-rw-r--r--lib/public_key/test/pbe_SUITE_data/old_aes_256_cbc.pem30
-rw-r--r--lib/public_key/vsn.mk2
-rw-r--r--lib/runtime_tools/doc/src/notes.xml15
-rw-r--r--lib/runtime_tools/vsn.mk2
-rw-r--r--lib/ssh/doc/src/notes.xml17
-rw-r--r--lib/ssh/src/ssh_transport.erl496
-rw-r--r--lib/ssh/vsn.mk2
-rw-r--r--lib/ssl/doc/src/notes.xml111
-rw-r--r--lib/ssl/doc/src/ssl.xml36
-rw-r--r--lib/ssl/src/ssl.erl106
-rw-r--r--lib/ssl/src/ssl_alert.erl14
-rw-r--r--lib/ssl/src/ssl_app.erl6
-rw-r--r--lib/ssl/src/ssl_cipher.erl14
-rw-r--r--lib/ssl/src/ssl_connection.erl3
-rw-r--r--lib/ssl/src/ssl_connection.hrl1
-rw-r--r--lib/ssl/src/ssl_handshake.erl5
-rw-r--r--lib/ssl/src/ssl_handshake.hrl4
-rw-r--r--lib/ssl/src/ssl_logger.erl5
-rw-r--r--lib/ssl/src/tls_connection.erl38
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl72
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl482
-rw-r--r--lib/ssl/src/tls_record_1_3.erl15
-rw-r--r--lib/ssl/src/tls_socket.erl10
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl264
-rw-r--r--lib/ssl/test/ssl_test_lib.erl14
-rw-r--r--lib/ssl/vsn.mk2
-rw-r--r--lib/stdlib/doc/src/beam_lib.xml39
-rw-r--r--lib/stdlib/doc/src/ets.xml16
-rw-r--r--lib/stdlib/doc/src/notes.xml51
-rw-r--r--lib/stdlib/src/beam_lib.erl52
-rw-r--r--lib/stdlib/src/stdlib.appup.src6
-rw-r--r--lib/stdlib/src/string.erl8
-rw-r--r--lib/stdlib/test/beam_lib_SUITE.erl68
-rw-r--r--lib/stdlib/test/ets_SUITE.erl599
-rw-r--r--lib/stdlib/test/ets_SUITE_data/visualize_throughput.html10
-rw-r--r--lib/stdlib/test/stdlib.spec3
-rw-r--r--lib/stdlib/test/stdlib_bench.spec1
-rw-r--r--lib/stdlib/test/string_SUITE.erl51
-rw-r--r--[-rwxr-xr-x]lib/stdlib/uc_spec/gen_unicode_mod.escript194
-rw-r--r--lib/stdlib/vsn.mk2
-rw-r--r--lib/syntax_tools/doc/src/notes.xml14
-rw-r--r--lib/syntax_tools/vsn.mk2
-rw-r--r--lib/tools/doc/src/notes.xml39
-rw-r--r--lib/tools/vsn.mk2
-rw-r--r--lib/wx/doc/src/notes.xml19
-rw-r--r--lib/wx/vsn.mk2
118 files changed, 3948 insertions, 2137 deletions
diff --git a/lib/common_test/doc/src/notes.xml b/lib/common_test/doc/src/notes.xml
index 2f53f1c29e..c8e0722a0f 100644
--- a/lib/common_test/doc/src/notes.xml
+++ b/lib/common_test/doc/src/notes.xml
@@ -33,6 +33,66 @@
<file>notes.xml</file>
</header>
+<section><title>Common_Test 1.17</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ A bug caused <c>ct:encrypt_config_file/3</c> and
+ <c>ct:decrypt_config_file/3</c> to fail with
+ <c>badmatch</c> if input parameter <c>KeyOrFile</c> was
+ <c>{key,string()}</c>. This is now corrected.</p>
+ <p>
+ Own Id: OTP-15540</p>
+ </item>
+ <item>
+ <p>
+ The status of a test case which failed with timetrap
+ timeout in <c>end_per_testcase</c> could not be modified
+ by returning <c>{fail,Reason}</c> from a
+ <c>post_end_per_testcase</c> hook function. This is now
+ corrected.</p>
+ <p>
+ Own Id: OTP-15584 Aux Id: ERIERL-282 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ A new variant of the <c>newline</c> option to
+ <c>ct_telnet:cmd/3</c> and <c>ct_telnet:send/3</c> is
+ added, which allows to specify a string to append as
+ newline indicator on a command. By default, the value is
+ "\n", but in some cases it is required to be "\r\n",
+ which this option allows.</p>
+ <p>
+ A faulty regular expression given as parameter to
+ <c>ct_telnet:expect/2,3</c> would earlier crash and look
+ like an internal error in common_test. A better error
+ indication is now given, but the test case will still
+ fail.</p>
+ <p>
+ Own Id: OTP-15229 Aux Id: ERIERL-203 </p>
+ </item>
+ <item>
+ <p>
+ Since the yang RFC allows more than one top element of
+ config data in an <c>edit-config</c> element,
+ <c>ct_netconfc:edit_config/3,4,5</c> can now take a list
+ of XML elements.</p>
+ <p>
+ Own Id: OTP-15298</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Common_Test 1.16.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/common_test/vsn.mk b/lib/common_test/vsn.mk
index fd5d4a57aa..23eb8d9656 100644
--- a/lib/common_test/vsn.mk
+++ b/lib/common_test/vsn.mk
@@ -1 +1 @@
-COMMON_TEST_VSN = 1.16.1
+COMMON_TEST_VSN = 1.17
diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml
index 02e6203137..d45dfef8f3 100644
--- a/lib/compiler/doc/src/notes.xml
+++ b/lib/compiler/doc/src/notes.xml
@@ -32,6 +32,38 @@
<p>This document describes the changes made to the Compiler
application.</p>
+<section><title>Compiler 7.3.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>An expression such as <c>(A / B) band 16#ff</c> would
+ crash the compiler.</p>
+ <p>
+ Own Id: OTP-15518 Aux Id: ERL-829 </p>
+ </item>
+ <item>
+ <p>There could be an incorrect warning when the
+ <c>tuple_calls</c> option was given. The generated code
+ would be correct. Here is an example of code that would
+ trigger the warning:</p>
+ <p><c>(list_to_atom("prefix_" ++
+ atom_to_list(suffix))):doit(X)</c>.</p>
+ <p>
+ Own Id: OTP-15552 Aux Id: ERL-838 </p>
+ </item>
+ <item>
+ <p>Optimize (again) Dialyzer's handling of
+ left-associative use of <c>andalso</c> and <c>orelse</c>
+ in guards.</p>
+ <p>
+ Own Id: OTP-15577 Aux Id: ERL-851, PR-2141, PR-1944 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Compiler 7.3.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/compiler/scripts/smoke b/lib/compiler/scripts/smoke
index 2429f104c0..ae31c923b8 100755
--- a/lib/compiler/scripts/smoke
+++ b/lib/compiler/scripts/smoke
@@ -54,6 +54,7 @@ setup_mix() ->
ElixirBin = filename:join([SmokeDir,"elixir","bin"]),
PATH = ElixirBin ++ ":" ++ os:getenv("PATH"),
os:putenv("PATH", PATH),
+ mix("local.hex --force"),
mix("local.rebar --force"),
ok.
diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl
index ab8caa1a0d..4fba3fa1c6 100644
--- a/lib/compiler/src/beam_validator.erl
+++ b/lib/compiler/src/beam_validator.erl
@@ -1595,8 +1595,11 @@ infer_types(_, #vst{}) ->
infer_types_1(#value{op={bif,'=:='},args=[LHS,RHS]}) ->
fun({atom,true}, S) ->
- Infer = infer_types(RHS, S),
- Infer(LHS, S);
+ %% Either side might contain something worth inferring, so we need
+ %% to check them both.
+ Infer_L = infer_types(RHS, S),
+ Infer_R = infer_types(LHS, S),
+ Infer_R(RHS, Infer_L(LHS, S));
(_, S) -> S
end;
infer_types_1(#value{op={bif,element},args=[{integer,Index}=Key,Tuple]}) ->
@@ -1772,8 +1775,11 @@ update_ne_types(LHS, RHS, Vst) ->
end.
update_eq_types(LHS, RHS, Vst0) ->
- Infer = infer_types(LHS, Vst0),
- Vst1 = Infer(RHS, Vst0),
+ %% Either side might contain something worth inferring, so we need
+ %% to check them both.
+ Infer_L = infer_types(RHS, Vst0),
+ Infer_R = infer_types(LHS, Vst0),
+ Vst1 = Infer_R(RHS, Infer_L(LHS, Vst0)),
T1 = get_term_type(LHS, Vst1),
T2 = get_term_type(RHS, Vst1),
diff --git a/lib/compiler/test/beam_except_SUITE.erl b/lib/compiler/test/beam_except_SUITE.erl
index 9380fe06c8..8e3b373d29 100644
--- a/lib/compiler/test/beam_except_SUITE.erl
+++ b/lib/compiler/test/beam_except_SUITE.erl
@@ -84,9 +84,16 @@ coverage(_) ->
{'EXIT',{function_clause,
[{?MODULE,fc,[y],[File,{line,2}]}|_]}} =
(catch fc(y)),
- {'EXIT',{function_clause,
- [{?MODULE,fc,[[a,b,c]],[File,{line,6}]}|_]}} =
- (catch fc([a,b,c])),
+ case ?MODULE of
+ beam_except_no_opt_SUITE ->
+ %% There will be a different stack fram in
+ %% unoptimized code.
+ ok;
+ _ ->
+ {'EXIT',{function_clause,
+ [{?MODULE,fc,[[a,b,c]],[File,{line,6}]}|_]}} =
+ (catch fc([a,b,c]))
+ end,
{'EXIT',{undef,[{erlang,error,[a,b,c],_}|_]}} =
(catch erlang:error(a, b, c)),
diff --git a/lib/compiler/test/beam_validator_SUITE.erl b/lib/compiler/test/beam_validator_SUITE.erl
index 8b39fce479..de5a3c2873 100644
--- a/lib/compiler/test/beam_validator_SUITE.erl
+++ b/lib/compiler/test/beam_validator_SUITE.erl
@@ -34,7 +34,8 @@
undef_label/1,illegal_instruction/1,failing_gc_guard_bif/1,
map_field_lists/1,cover_bin_opt/1,
val_dsetel/1,bad_tuples/1,bad_try_catch_nesting/1,
- receive_stacked/1,aliased_types/1,type_conflict/1]).
+ receive_stacked/1,aliased_types/1,type_conflict/1,
+ infer_on_eq/1]).
-include_lib("common_test/include/ct.hrl").
@@ -63,7 +64,8 @@ groups() ->
undef_label,illegal_instruction,failing_gc_guard_bif,
map_field_lists,cover_bin_opt,val_dsetel,
bad_tuples,bad_try_catch_nesting,
- receive_stacked,aliased_types,type_conflict]}].
+ receive_stacked,aliased_types,type_conflict,
+ infer_on_eq]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -651,6 +653,32 @@ type_conflict_1(C) ->
end,
{C#r.e1, TRes}.
+%% ERL-886; validation failed to infer types on both sides of '=:='
+
+infer_on_eq(Config) when is_list(Config) ->
+ {ok, gurka} = infer_on_eq_1(id({gurka})),
+ {ok, gaffel} = infer_on_eq_2(id({gaffel})),
+ {ok, elefant} = infer_on_eq_3(id({elefant})),
+ {ok, myra} = infer_on_eq_4(id({myra})),
+ ok.
+
+infer_on_eq_1(T) ->
+ 1 = erlang:tuple_size(T),
+ {ok, erlang:element(1, T)}.
+
+infer_on_eq_2(T) ->
+ Size = erlang:tuple_size(T),
+ Size = 1,
+ {ok, erlang:element(1, T)}.
+
+infer_on_eq_3(T) ->
+ true = 1 =:= erlang:tuple_size(T),
+ {ok, erlang:element(1, T)}.
+
+infer_on_eq_4(T) ->
+ true = erlang:tuple_size(T) =:= 1,
+ {ok, erlang:element(1, T)}.
+
%%%-------------------------------------------------------------------------
transform_remove(Remove, Module) ->
diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk
index efedb414ad..a523627384 100644
--- a/lib/compiler/vsn.mk
+++ b/lib/compiler/vsn.mk
@@ -1 +1 @@
-COMPILER_VSN = 7.3.1
+COMPILER_VSN = 7.3.2
diff --git a/lib/crypto/c_src/Makefile.in b/lib/crypto/c_src/Makefile.in
index e1e7f71538..b6a65d7488 100644
--- a/lib/crypto/c_src/Makefile.in
+++ b/lib/crypto/c_src/Makefile.in
@@ -77,9 +77,7 @@ CRYPTO_OBJS = $(OBJDIR)/crypto$(TYPEMARKER).o \
$(OBJDIR)/algorithms$(TYPEMARKER).o \
$(OBJDIR)/api_ng$(TYPEMARKER).o \
$(OBJDIR)/atoms$(TYPEMARKER).o \
- $(OBJDIR)/block$(TYPEMARKER).o \
$(OBJDIR)/bn$(TYPEMARKER).o \
- $(OBJDIR)/chacha20$(TYPEMARKER).o \
$(OBJDIR)/cipher$(TYPEMARKER).o \
$(OBJDIR)/cmac$(TYPEMARKER).o \
$(OBJDIR)/dh$(TYPEMARKER).o \
@@ -98,7 +96,6 @@ CRYPTO_OBJS = $(OBJDIR)/crypto$(TYPEMARKER).o \
$(OBJDIR)/pkey$(TYPEMARKER).o \
$(OBJDIR)/poly1305$(TYPEMARKER).o \
$(OBJDIR)/rand$(TYPEMARKER).o \
- $(OBJDIR)/rc4$(TYPEMARKER).o \
$(OBJDIR)/rsa$(TYPEMARKER).o \
$(OBJDIR)/srp$(TYPEMARKER).o
CALLBACK_OBJS = $(OBJDIR)/crypto_callback$(TYPEMARKER).o
diff --git a/lib/crypto/c_src/aes.c b/lib/crypto/c_src/aes.c
index ee2bb70fb7..4b01e629f9 100644
--- a/lib/crypto/c_src/aes.c
+++ b/lib/crypto/c_src/aes.c
@@ -166,156 +166,7 @@ ERL_NIF_TERM aes_ige_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv
}
-#ifdef HAVE_EVP_AES_CTR
-ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key, IVec) */
- ErlNifBinary key_bin, ivec_bin;
- struct evp_cipher_ctx *ctx = NULL;
- const EVP_CIPHER *cipher;
- ERL_NIF_TERM ret;
-
- ASSERT(argc == 2);
-
- if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin))
- goto bad_arg;
- if (!enif_inspect_binary(env, argv[1], &ivec_bin))
- goto bad_arg;
- if (ivec_bin.size != 16)
- goto bad_arg;
-
- switch (key_bin.size)
- {
- case 16:
- cipher = EVP_aes_128_ctr();
- break;
- case 24:
- cipher = EVP_aes_192_ctr();
- break;
- case 32:
- cipher = EVP_aes_256_ctr();
- break;
- default:
- goto bad_arg;
- }
-
- if ((ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx))) == NULL)
- goto err;
- if ((ctx->ctx = EVP_CIPHER_CTX_new()) == NULL)
- goto err;
-
- if (EVP_CipherInit_ex(ctx->ctx, cipher, NULL,
- key_bin.data, ivec_bin.data, 1) != 1)
- goto err;
-
- if (EVP_CIPHER_CTX_set_padding(ctx->ctx, 0) != 1)
- goto err;
-
- ret = enif_make_resource(env, ctx);
- goto done;
-
- bad_arg:
- return enif_make_badarg(env);
-
- err:
- ret = enif_make_badarg(env);
-
- done:
- if (ctx)
- enif_release_resource(ctx);
- return ret;
-}
-
-ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Context, Data) */
- struct evp_cipher_ctx *ctx = NULL, *new_ctx = NULL;
- ErlNifBinary data_bin;
- ERL_NIF_TERM ret, cipher_term;
- unsigned char *out;
- int outl = 0;
-
- ASSERT(argc == 2);
-
- if (!enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx))
- goto bad_arg;
- if (!enif_inspect_iolist_as_binary(env, argv[1], &data_bin))
- goto bad_arg;
- if (data_bin.size > INT_MAX)
- goto bad_arg;
-
- if ((new_ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx))) == NULL)
- goto err;
- if ((new_ctx->ctx = EVP_CIPHER_CTX_new()) == NULL)
- goto err;
-
- if (EVP_CIPHER_CTX_copy(new_ctx->ctx, ctx->ctx) != 1)
- goto err;
-
- if ((out = enif_make_new_binary(env, data_bin.size, &cipher_term)) == NULL)
- goto err;
-
- if (EVP_CipherUpdate(new_ctx->ctx, out, &outl, data_bin.data, (int)data_bin.size) != 1)
- goto err;
- ASSERT(outl >= 0 && (size_t)outl == data_bin.size);
-
- ret = enif_make_tuple2(env, enif_make_resource(env, new_ctx), cipher_term);
- CONSUME_REDS(env,data_bin);
- goto done;
-
- bad_arg:
- return enif_make_badarg(env);
-
- err:
- ret = enif_make_badarg(env);
-
- done:
- if (new_ctx)
- enif_release_resource(new_ctx);
- return ret;
-}
-
-#else /* if not HAVE_EVP_AES_CTR */
-
-ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key, IVec) */
- ASSERT(argc == 2);
-
- return aes_ctr_stream_init_compat(env, argv[0], argv[1]);
-}
-
-
-ERL_NIF_TERM aes_ctr_stream_init_compat(ErlNifEnv* env, const ERL_NIF_TERM key_term, const ERL_NIF_TERM iv_term)
-{
- ErlNifBinary key_bin, ivec_bin;
- ERL_NIF_TERM ecount_bin;
- unsigned char *outp;
-
- if (!enif_inspect_iolist_as_binary(env, key_term, &key_bin))
- goto bad_arg;
- if (key_bin.size != 16 && key_bin.size != 24 && key_bin.size != 32)
- goto bad_arg;
- if (!enif_inspect_binary(env, iv_term, &ivec_bin))
- goto bad_arg;
- if (ivec_bin.size != 16)
- goto bad_arg;
- if ((outp = enif_make_new_binary(env, AES_BLOCK_SIZE, &ecount_bin)) == NULL)
- goto err;
- memset(outp, 0, AES_BLOCK_SIZE);
-
- return enif_make_tuple4(env, key_term, iv_term, ecount_bin, enif_make_int(env, 0));
-
- bad_arg:
- err:
- return enif_make_badarg(env);
-}
-
-ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
- ASSERT(argc == 2);
-
- return aes_ctr_stream_encrypt_compat(env, argv[0], argv[1]);
-}
-
-
+#if !defined(HAVE_EVP_AES_CTR)
ERL_NIF_TERM aes_ctr_stream_encrypt_compat(ErlNifEnv* env, const ERL_NIF_TERM state_arg, const ERL_NIF_TERM data_arg)
{/* ({Key, IVec, ECount, Num}, Data) */
ErlNifBinary key_bin, ivec_bin, text_bin, ecount_bin;
diff --git a/lib/crypto/c_src/aes.h b/lib/crypto/c_src/aes.h
index 527d041410..c0b2b91f8d 100644
--- a/lib/crypto/c_src/aes.h
+++ b/lib/crypto/c_src/aes.h
@@ -27,10 +27,7 @@ ERL_NIF_TERM aes_cfb_8_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]
ERL_NIF_TERM aes_cfb_128_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM aes_ige_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
#if !defined(HAVE_EVP_AES_CTR)
-ERL_NIF_TERM aes_ctr_stream_init_compat(ErlNifEnv* env, const ERL_NIF_TERM key_term, const ERL_NIF_TERM iv_term);
ERL_NIF_TERM aes_ctr_stream_encrypt_compat(ErlNifEnv* env, const ERL_NIF_TERM state_arg, const ERL_NIF_TERM data_arg);
#endif
diff --git a/lib/crypto/c_src/api_ng.c b/lib/crypto/c_src/api_ng.c
index c4114d1626..6a833a0984 100644
--- a/lib/crypto/c_src/api_ng.c
+++ b/lib/crypto/c_src/api_ng.c
@@ -25,199 +25,532 @@
/*
* A unified set of functions for encryption/decryption.
*
- * EXPERIMENTAL!!
- *
*/
ERL_NIF_TERM ng_crypto_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM ng_crypto_one_shot(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-/* Try better error messages in new functions */
-#define ERROR_Term(Env, ReasonTerm) enif_make_tuple2((Env), atom_error, (ReasonTerm))
-#define ERROR_Str(Env, ReasonString) ERROR_Term((Env), enif_make_string((Env),(ReasonString),(ERL_NIF_LATIN1)))
+/* All nif functions return a valid value or throws an exception */
+#define EXCP(Env, Class, Str) enif_raise_exception((Env), \
+ enif_make_tuple2((Env), (Class), \
+ enif_make_string((Env),(Str),(ERL_NIF_LATIN1)) ))
-/* Initializes state for (de)encryption
- */
-ERL_NIF_TERM ng_crypto_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Cipher, Key, IVec, Encrypt) % if no IV for the Cipher, set IVec = <<>>
- */
- ErlNifBinary key_bin, ivec_bin;
- unsigned char *iv = NULL;
- struct evp_cipher_ctx *ctx;
- const struct cipher_type_t *cipherp;
- const EVP_CIPHER *cipher;
- ERL_NIF_TERM enc_flg_arg, ret;
- int enc;
- unsigned iv_len;
-
- enc_flg_arg = argv[argc-1];
- if (enc_flg_arg == atom_true)
- enc = 1;
- else if (enc_flg_arg == atom_false)
- enc = 0;
- else if (enc_flg_arg == atom_undefined)
+#define EXCP_NOTSUP(Env, Str) EXCP((Env), atom_notsup, (Str))
+#define EXCP_BADARG(Env, Str) EXCP((Env), atom_badarg, (Str))
+#define EXCP_ERROR(Env, Str) EXCP((Env), atom_error, (Str))
+
+
+#ifdef HAVE_ECB_IVEC_BUG
+ /* <= 0.9.8l returns faulty ivec length */
+# define GET_IV_LEN(Ciph) ((Ciph)->flags & ECB_BUG_0_9_8L) ? 0 : EVP_CIPHER_iv_length((Ciph)->cipher.p)
+#else
+# define GET_IV_LEN(Ciph) EVP_CIPHER_iv_length((Ciph)->cipher.p)
+#endif
+
+/*************************************************************************/
+/* Get the arguments for the initialization of the EVP_CIPHER_CTX. Check */
+/* them and initialize that context. */
+/*************************************************************************/
+static int get_init_args(ErlNifEnv* env,
+ struct evp_cipher_ctx *ctx_res,
+ const ERL_NIF_TERM cipher_arg,
+ const ERL_NIF_TERM key_arg,
+ const ERL_NIF_TERM ivec_arg,
+ const ERL_NIF_TERM encflg_arg,
+ const struct cipher_type_t **cipherp,
+ ERL_NIF_TERM *return_term)
+{
+ int ivec_len;
+ ErlNifBinary key_bin;
+ ErlNifBinary ivec_bin;
+ int encflg;
+
+ ctx_res->ctx = NULL; /* For testing if *ctx should be freed after errors */
+
+ /* Fetch the flag telling if we are going to encrypt (=true) or decrypt (=false) */
+ if (encflg_arg == atom_true)
+ encflg = 1;
+ else if (encflg_arg == atom_false)
+ encflg = 0;
+ else if (encflg_arg == atom_undefined)
/* For compat funcs in crypto.erl */
- enc = -1;
+ encflg = -1;
else
- return ERROR_Str(env, "Bad enc flag");
+ {
+ *return_term = EXCP_BADARG(env, "Bad enc flag");
+ goto err;
+ }
- if (!enif_inspect_binary(env, argv[1], &key_bin))
- return ERROR_Str(env, "Bad key");
+ /* Fetch the key */
+ if (!enif_inspect_iolist_as_binary(env, key_arg, &key_bin))
+ {
+ *return_term = EXCP_BADARG(env, "Bad key");
+ goto err;
+ }
- if (!(cipherp = get_cipher_type(argv[0], key_bin.size)))
- return ERROR_Str(env, "Unknown cipher or bad key size");
+ /* Fetch cipher type */
+ if (!enif_is_atom(env, cipher_arg))
+ {
+ *return_term = EXCP_BADARG(env, "Cipher id is not an atom");
+ goto err;
+ }
- if (FORBIDDEN_IN_FIPS(cipherp))
- return enif_raise_exception(env, atom_notsup);
+ if (!(*cipherp = get_cipher_type(cipher_arg, key_bin.size)))
+ {
+ if (!get_cipher_type_no_key(cipher_arg))
+ *return_term = EXCP_BADARG(env, "Unknown cipher");
+ else
+ *return_term = EXCP_BADARG(env, "Bad key size");
+ goto err;
+ }
- if (enc == -1)
- return atom_undefined;
+ if (FORBIDDEN_IN_FIPS(*cipherp))
+ {
+ *return_term = EXCP_NOTSUP(env, "Forbidden in FIPS");
+ goto err;
+ }
- if (!(cipher = cipherp->cipher.p)) {
+ /* Get ivec_len for this cipher (if we found one) */
#if !defined(HAVE_EVP_AES_CTR)
- if (cipherp->flags & AES_CTR_COMPAT)
- return aes_ctr_stream_init_compat(env, argv[1], argv[2]);
- else
+ /* This code is for historic OpenSSL where EVP_aes_*_ctr is not defined.... */
+ if ((*cipherp)->cipher.p) {
+ /* Not aes_ctr compatibility code since EVP_*
+ was defined and assigned to (*cipherp)->cipher.p */
+ ivec_len = GET_IV_LEN(*cipherp);
+ } else {
+ /* No EVP_* was found */
+ if ((*cipherp)->flags & AES_CTR_COMPAT)
+ /* Use aes_ctr compatibility code later */
+ ivec_len = 16;
+ else {
+ /* Unsupported crypto */
+ *return_term = EXCP_NOTSUP(env, "Cipher not supported in this libcrypto version");
+ goto err;
+ }
+ }
+#else
+ /* Normal code */
+ if (!((*cipherp)->cipher.p)) {
+ *return_term = EXCP_NOTSUP(env, "Cipher not supported in this libcrypto version");
+ goto err;
+ }
+ ivec_len = GET_IV_LEN(*cipherp);
#endif
- return enif_raise_exception(env, atom_notsup);
+
+ /* (*cipherp)->cipher.p != NULL and ivec_len has a value */
+
+ /* Fetch IV */
+ if (ivec_len && (ivec_arg != atom_undefined)) {
+ if (!enif_inspect_iolist_as_binary(env, ivec_arg, &ivec_bin))
+ {
+ *return_term = EXCP_BADARG(env, "Bad iv type");
+ goto err;
+ }
+
+ if (ivec_len != ivec_bin.size)
+ {
+ *return_term = EXCP_BADARG(env, "Bad iv size");
+ goto err;
+ }
}
-#ifdef HAVE_ECB_IVEC_BUG
- if (cipherp->flags & ECB_BUG_0_9_8L)
- iv_len = 0; /* <= 0.9.8l returns faulty ivec length */
- else
+ ctx_res->iv_len = ivec_len;
+
+#if !defined(HAVE_EVP_AES_CTR)
+ if (!((*cipherp)->cipher.p)
+ && ((*cipherp)->flags & AES_CTR_COMPAT)
+ ) {
+ /* Must use aes_ctr compatibility code */
+ ERL_NIF_TERM ecount_bin;
+ unsigned char *outp;
+ if ((outp = enif_make_new_binary(env, AES_BLOCK_SIZE, &ecount_bin)) == NULL) {
+ *return_term = EXCP_ERROR(env, "Can't allocate ecount_bin");
+ goto err;
+ }
+ memset(outp, 0, AES_BLOCK_SIZE);
+
+ ctx_res->env = enif_alloc_env();
+ if (!ctx_res->env) {
+ *return_term = EXCP_ERROR(env, "Can't allocate env");
+ goto err;
+ }
+ ctx_res->state =
+ enif_make_copy(ctx_res->env,
+ enif_make_tuple4(env, key_arg, ivec_arg, ecount_bin, enif_make_int(env, 0)));
+ goto success;
+ } else {
+ /* Flag for subsequent calls that no aes_ctr compatibility code should be called */
+ ctx_res->state = atom_undefined;
+ ctx_res->env = NULL;
+ }
#endif
- iv_len = EVP_CIPHER_iv_length(cipher);
- if (iv_len) {
- if (!enif_inspect_binary(env, argv[2], &ivec_bin))
- return ERROR_Str(env, "Bad iv type");
+ /* Initialize the EVP_CIPHER_CTX */
- if (iv_len != ivec_bin.size)
- return ERROR_Str(env, "Bad iv size");
+ ctx_res->ctx = EVP_CIPHER_CTX_new();
+ if (! ctx_res->ctx)
+ {
+ *return_term = EXCP_ERROR(env, "Can't allocate context");
+ goto err;
+ }
- iv = ivec_bin.data;
- }
+ if (!EVP_CipherInit_ex(ctx_res->ctx, (*cipherp)->cipher.p, NULL, NULL, NULL, encflg))
+ {
+ *return_term = EXCP_ERROR(env, "Can't initialize context, step 1");
+ goto err;
+ }
- if ((ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx))) == NULL)
- return ERROR_Str(env, "Can't allocate resource");
+ if (!EVP_CIPHER_CTX_set_key_length(ctx_res->ctx, (int)key_bin.size))
+ {
+ *return_term = EXCP_ERROR(env, "Can't initialize context, key_length");
+ goto err;
+ }
- ctx->ctx = EVP_CIPHER_CTX_new();
- if (! ctx->ctx)
- return ERROR_Str(env, "Can't allocate context");
- if (!EVP_CipherInit_ex(ctx->ctx, cipher, NULL, NULL, NULL, enc)) {
- enif_release_resource(ctx);
- return ERROR_Str(env, "Can't initialize context, step 1");
+ if (EVP_CIPHER_type((*cipherp)->cipher.p) == NID_rc2_cbc) {
+ if (key_bin.size > INT_MAX / 8) {
+ *return_term = EXCP_BADARG(env, "To large rc2_cbc key");
+ goto err;
+ }
+ if (!EVP_CIPHER_CTX_ctrl(ctx_res->ctx, EVP_CTRL_SET_RC2_KEY_BITS, (int)key_bin.size * 8, NULL)) {
+ *return_term = EXCP_ERROR(env, "ctrl rc2_cbc key");
+ goto err;
+ }
}
- if (!EVP_CIPHER_CTX_set_key_length(ctx->ctx, (int)key_bin.size)) {
- enif_release_resource(ctx);
- return ERROR_Str(env, "Can't initialize context, key_length");
- }
+ if (ivec_arg == atom_undefined || ivec_len == 0)
+ {
+ if (!EVP_CipherInit_ex(ctx_res->ctx, NULL, NULL, key_bin.data, NULL, -1)) {
+ *return_term = EXCP_ERROR(env, "Can't initialize key");
+ goto err;
+ }
+ }
+ else
+ if (!EVP_CipherInit_ex(ctx_res->ctx, NULL, NULL, key_bin.data, ivec_bin.data, -1))
+ {
+ *return_term = EXCP_ERROR(env, "Can't initialize key or iv");
+ goto err;
+ }
- if (EVP_CIPHER_type(cipher) == NID_rc2_cbc) {
- if (key_bin.size > INT_MAX / 8) {
- enif_release_resource(ctx);
- return ERROR_Str(env, "To large rc2_cbc key");
+ EVP_CIPHER_CTX_set_padding(ctx_res->ctx, 0);
+
+ *return_term = atom_ok;
+
+#if !defined(HAVE_EVP_AES_CTR)
+ success:
+#endif
+ return 1;
+
+ err:
+ if (ctx_res->ctx) EVP_CIPHER_CTX_free(ctx_res->ctx);
+ return 0;
+}
+
+/*************************************************************************/
+/* Get the arguments for the EVP_CipherUpdate function, and call it. */
+/*************************************************************************/
+
+static int get_update_args(ErlNifEnv* env,
+ struct evp_cipher_ctx *ctx_res,
+ const ERL_NIF_TERM indata_arg,
+ ERL_NIF_TERM *return_term)
+{
+ ErlNifBinary in_data_bin, out_data_bin;
+ int out_len, block_size;
+
+ if (!enif_inspect_binary(env, indata_arg, &in_data_bin) )
+ {
+ *return_term = EXCP_BADARG(env, "Bad 2:nd arg");
+ goto err;
}
- if (!EVP_CIPHER_CTX_ctrl(ctx->ctx, EVP_CTRL_SET_RC2_KEY_BITS, (int)key_bin.size * 8, NULL)) {
- enif_release_resource(ctx);
- return ERROR_Str(env, "ctrl rc2_cbc key");
+
+ ASSERT(in_data_bin.size <= INT_MAX);
+
+#if !defined(HAVE_EVP_AES_CTR)
+ if (ctx_res->state != atom_undefined) {
+ ERL_NIF_TERM state0, newstate_and_outdata;
+ const ERL_NIF_TERM *tuple_argv;
+ int tuple_argc;
+
+ state0 = enif_make_copy(env, ctx_res->state);
+
+ if (enif_get_tuple(env, state0, &tuple_argc, &tuple_argv) && (tuple_argc == 4)) {
+ /* A compatibility state term */
+ /* encrypt and decrypt is performed by calling the same function */
+ newstate_and_outdata = aes_ctr_stream_encrypt_compat(env, state0, indata_arg);
+
+ if (enif_get_tuple(env, newstate_and_outdata, &tuple_argc, &tuple_argv) && (tuple_argc == 2)) {
+ /* newstate_and_outdata = {NewState, OutData} */
+ ctx_res->state = enif_make_copy(ctx_res->env, tuple_argv[0]);
+ /* Return the OutData (from the newstate_and_outdata tuple) only: */
+ *return_term = tuple_argv[1];
+ }
}
+ } else
+#endif
+ {
+ block_size = EVP_CIPHER_CTX_block_size(ctx_res->ctx);
+
+ if (!enif_alloc_binary((size_t)in_data_bin.size+block_size, &out_data_bin))
+ {
+ *return_term = EXCP_ERROR(env, "Can't allocate outdata");
+ goto err;
+ }
+
+ if (!EVP_CipherUpdate(ctx_res->ctx, out_data_bin.data, &out_len, in_data_bin.data, in_data_bin.size))
+ {
+ *return_term = EXCP_ERROR(env, "Can't update");
+ goto err;
+ }
+
+ if (!enif_realloc_binary(&out_data_bin, (size_t)out_len))
+ {
+ *return_term = EXCP_ERROR(env, "Can't reallocate");
+ goto err;
+ }
+
+ CONSUME_REDS(env, in_data_bin);
+ /* return the result text as a binary: */
+ *return_term = enif_make_binary(env, &out_data_bin);
}
- if (!EVP_CipherInit_ex(ctx->ctx, NULL, NULL, key_bin.data, iv, enc)) {
- enif_release_resource(ctx);
- return ERROR_Str(env, "Can't initialize key and/or iv");
- }
+ /* success: */
+ return 1;
- EVP_CIPHER_CTX_set_padding(ctx->ctx, 0);
+ err:
+ return 0;
+}
- ret = enif_make_resource(env, ctx);
- enif_release_resource(ctx);
+/*************************************************************************/
+/* Initialize the state for (de/en)cryption */
+/*************************************************************************/
+
+ERL_NIF_TERM ng_crypto_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Cipher, Key, IVec, Encrypt) % if no IV for the Cipher, set IVec = <<>>
+ */
+ struct evp_cipher_ctx *ctx_res = NULL;
+ const struct cipher_type_t *cipherp;
+ ERL_NIF_TERM ret;
+ int encflg;
+
+ if (enif_is_atom(env, argv[0])) {
+ if ((ctx_res = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx))) == NULL)
+ return EXCP_ERROR(env, "Can't allocate resource");
+
+ if (!get_init_args(env, ctx_res, argv[0], argv[1], argv[2], argv[argc-1],
+ &cipherp, &ret))
+ /* Error msg in &ret */
+ goto ret;
+
+ ret = enif_make_resource(env, ctx_res);
+ if(ctx_res) enif_release_resource(ctx_res);
+
+ } else if (enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx_res)) {
+ /* Fetch the flag telling if we are going to encrypt (=true) or decrypt (=false) */
+ if (argv[3] == atom_true)
+ encflg = 1;
+ else if (argv[3] == atom_false)
+ encflg = 0;
+ else {
+ ret = EXCP_BADARG(env, "Bad enc flag");
+ goto ret;
+ }
+ if (ctx_res->ctx) {
+ /* It is *not* a ctx_res for the compatibility handling of non-EVP aes_ctr */
+ if (!EVP_CipherInit_ex(ctx_res->ctx, NULL, NULL, NULL, NULL, encflg)) {
+ ret = EXCP_ERROR(env, "Can't initialize encflag");
+ goto ret;
+ }
+ }
+ ret = argv[0];
+ } else {
+ ret = EXCP_BADARG(env, "Bad 1:st arg");
+ goto ret;
+ }
+
+ ret:
return ret;
}
-ERL_NIF_TERM ng_crypto_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Context, Data)
- (Context, Data, IV) */
- struct evp_cipher_ctx *ctx;
- ErlNifBinary in_data_bin, ivec_bin, out_data_bin;
- int out_len, block_size;
-#if !defined(HAVE_EVP_AES_CTR)
- const ERL_NIF_TERM *state_term;
- int state_arity;
+/*************************************************************************/
+/* Encrypt/decrypt */
+/*************************************************************************/
- if (enif_get_tuple(env, argv[0], &state_arity, &state_term) && (state_arity == 4)) {
- return aes_ctr_stream_encrypt_compat(env, argv[0], argv[1]);
- }
+#if !defined(HAVE_EVP_CIPHER_CTX_COPY)
+/*
+ The EVP_CIPHER_CTX_copy is not available in older cryptolibs although
+ the function is needed.
+ Instead of implement it in-place, we have a copy here as a compatibility
+ function
+*/
+
+int EVP_CIPHER_CTX_copy(EVP_CIPHER_CTX *out, const EVP_CIPHER_CTX *in);
+
+int EVP_CIPHER_CTX_copy(EVP_CIPHER_CTX *out, const EVP_CIPHER_CTX *in)
+{
+ if ((in == NULL) || (in->cipher == NULL))
+ {
+ return 0;
+ }
+#ifdef HAS_ENGINE_SUPPORT
+ /* Make sure it's safe to copy a cipher context using an ENGINE */
+ if (in->engine && !ENGINE_init(in->engine))
+ return 0;
#endif
- if (!enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx))
- return ERROR_Str(env, "Bad 1:st arg");
-
- if (!enif_inspect_binary(env, argv[1], &in_data_bin) )
- return ERROR_Str(env, "Bad 2:nd arg");
+ EVP_CIPHER_CTX_cleanup(out);
+ memcpy(out,in,sizeof *out);
- /* arg[1] was checked by the caller */
- ASSERT(in_data_bin.size =< INT_MAX);
+ if (in->cipher_data && in->cipher->ctx_size)
+ {
+ out->cipher_data=OPENSSL_malloc(in->cipher->ctx_size);
+ if (!out->cipher_data)
+ return 0;
+ memcpy(out->cipher_data,in->cipher_data,in->cipher->ctx_size);
+ }
- block_size = EVP_CIPHER_CTX_block_size(ctx->ctx);
- if (in_data_bin.size % (size_t)block_size != 0)
- return ERROR_Str(env, "Data not a multiple of block size");
+#if defined(EVP_CIPH_CUSTOM_COPY) && defined(EVP_CTRL_COPY)
+ if (in->cipher->flags & EVP_CIPH_CUSTOM_COPY)
+ return in->cipher->ctrl((EVP_CIPHER_CTX *)in, EVP_CTRL_COPY, 0, out);
+#endif
+ return 1;
+}
+/****** End of compatibility function ******/
+#endif
- if (argc==3) {
- if (!enif_inspect_iolist_as_binary(env, argv[2], &ivec_bin))
- return ERROR_Str(env, "Not binary IV");
-
- if (ivec_bin.size > INT_MAX)
- return ERROR_Str(env, "Too big IV");
-
- if (!EVP_CipherInit_ex(ctx->ctx, NULL, NULL, NULL, ivec_bin.data, -1))
- return ERROR_Str(env, "Can't set IV");
- }
- if (!enif_alloc_binary((size_t)in_data_bin.size+block_size, &out_data_bin))
- return ERROR_Str(env, "Can't allocate outdata");
+ERL_NIF_TERM ng_crypto_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context, Data [, IV]) */
+ struct evp_cipher_ctx *ctx_res;
+ ERL_NIF_TERM ret;
+
+ if (!enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx_res))
+ return EXCP_BADARG(env, "Bad 1:st arg");
+
+ if (argc == 3) {
+ struct evp_cipher_ctx ctx_res_copy;
+ ErlNifBinary ivec_bin;
- if (!EVP_CipherUpdate(ctx->ctx, out_data_bin.data, &out_len, in_data_bin.data, in_data_bin.size))
- return ERROR_Str(env, "Can't update");
+ memcpy(&ctx_res_copy, ctx_res, sizeof ctx_res_copy);
+#if !defined(HAVE_EVP_AES_CTR)
+ if (ctx_res_copy.state == atom_undefined)
+ /* Not going to use aes_ctr compat functions */
+#endif
+ {
+ ctx_res_copy.ctx = EVP_CIPHER_CTX_new();
- if (!enif_realloc_binary(&out_data_bin, (size_t)out_len))
- return ERROR_Str(env, "Can't reallocate");
+ if (!EVP_CIPHER_CTX_copy(ctx_res_copy.ctx, ctx_res->ctx)) {
+ ret = EXCP_ERROR(env, "Can't copy ctx_res");
+ goto err;
+ }
+ }
- CONSUME_REDS(env, in_data_bin);
- return enif_make_binary(env, &out_data_bin);
+ ctx_res = &ctx_res_copy;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[2], &ivec_bin))
+ {
+ ret = EXCP_BADARG(env, "Bad iv type");
+ goto err;
+ }
+
+ if (ctx_res_copy.iv_len != ivec_bin.size)
+ {
+ ret = EXCP_BADARG(env, "Bad iv size");
+ goto err;
+ }
+
+#if !defined(HAVE_EVP_AES_CTR)
+ if ((ctx_res_copy.state != atom_undefined) ) {
+ /* replace the iv in state with argv[2] */
+ ERL_NIF_TERM state0;
+ const ERL_NIF_TERM *tuple_argv;
+ int tuple_argc;
+ state0 = enif_make_copy(env, ctx_res_copy.state);
+ if (enif_get_tuple(env, state0, &tuple_argc, &tuple_argv) && (tuple_argc == 4)) {
+ /* A compatibility state term */
+ ctx_res_copy.state = enif_make_tuple4(env, tuple_argv[0], argv[2], tuple_argv[2], tuple_argv[3]);
+ }
+ } else
+#endif
+ if (!EVP_CipherInit_ex(ctx_res_copy.ctx, NULL, NULL, NULL, ivec_bin.data, -1))
+ {
+ ret = EXCP_ERROR(env, "Can't set iv");
+ goto err;
+ }
+
+ get_update_args(env, &ctx_res_copy, argv[1], &ret);
+ } else
+ get_update_args(env, ctx_res, argv[1], &ret);
+
+ err:
+ return ret; /* Both success and error */
}
ERL_NIF_TERM ng_crypto_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Context, Data)
- (Context, Data, IV) */
- int i;
+{/* (Context, Data [, IV]) */
ErlNifBinary data_bin;
- ERL_NIF_TERM new_argv[3];
- ASSERT(argc =< 3);
+ ASSERT(argc <= 3);
- if (!enif_inspect_iolist_as_binary(env, argv[1], &data_bin))
- return ERROR_Str(env, "iodata expected as data");
+ if (!enif_inspect_binary(env, argv[1], &data_bin))
+ return EXCP_BADARG(env, "expected binary as data");
if (data_bin.size > INT_MAX)
- return ERROR_Str(env, "to long data");
-
- for (i=0; i<argc; i++) new_argv[i] = argv[i];
- new_argv[1] = enif_make_binary(env, &data_bin);
+ return EXCP_BADARG(env, "to long data");
/* Run long jobs on a dirty scheduler to not block the current emulator thread */
if (data_bin.size > MAX_BYTES_TO_NIF) {
return enif_schedule_nif(env, "ng_crypto_update",
ERL_NIF_DIRTY_JOB_CPU_BOUND,
- ng_crypto_update, argc, new_argv);
+ ng_crypto_update, argc, argv);
}
- return ng_crypto_update(env, argc, new_argv);
+ return ng_crypto_update(env, argc, argv);
+}
+
+/*************************************************************************/
+/* One shot */
+/*************************************************************************/
+
+ERL_NIF_TERM ng_crypto_one_shot(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Cipher, Key, IVec, Data, Encrypt) */
+ struct evp_cipher_ctx ctx_res;
+ const struct cipher_type_t *cipherp;
+ ERL_NIF_TERM ret;
+
+ if (!get_init_args(env, &ctx_res, argv[0], argv[1], argv[2], argv[4], &cipherp, &ret))
+ goto ret;
+
+ get_update_args(env, &ctx_res, argv[3], &ret);
+
+ ret:
+ if (ctx_res.ctx)
+ EVP_CIPHER_CTX_free(ctx_res.ctx);
+ return ret;
}
+ERL_NIF_TERM ng_crypto_one_shot_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Cipher, Key, IVec, Data, Encrypt) % if no IV for the Cipher, set IVec = <<>>
+ */
+ ErlNifBinary data_bin;
+
+ ASSERT(argc == 5);
+
+ if (!enif_inspect_binary(env, argv[3], &data_bin))
+ return EXCP_BADARG(env, "expected binary as data");
+
+ if (data_bin.size > INT_MAX)
+ return EXCP_BADARG(env, "to long data");
+
+ /* Run long jobs on a dirty scheduler to not block the current emulator thread */
+ if (data_bin.size > MAX_BYTES_TO_NIF) {
+ return enif_schedule_nif(env, "ng_crypto_one_shot",
+ ERL_NIF_DIRTY_JOB_CPU_BOUND,
+ ng_crypto_one_shot, argc, argv);
+ }
+
+ return ng_crypto_one_shot(env, argc, argv);
+}
diff --git a/lib/crypto/c_src/api_ng.h b/lib/crypto/c_src/api_ng.h
index a3b40fe7fc..5c7d9af3c5 100644
--- a/lib/crypto/c_src/api_ng.h
+++ b/lib/crypto/c_src/api_ng.h
@@ -25,5 +25,6 @@
ERL_NIF_TERM ng_crypto_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM ng_crypto_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM ng_crypto_one_shot_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
#endif /* E_AES_H__ */
diff --git a/lib/crypto/c_src/atoms.c b/lib/crypto/c_src/atoms.c
index 798c26c9bb..114e3c1985 100644
--- a/lib/crypto/c_src/atoms.c
+++ b/lib/crypto/c_src/atoms.c
@@ -33,6 +33,7 @@ ERL_NIF_TERM atom_undefined;
ERL_NIF_TERM atom_ok;
ERL_NIF_TERM atom_none;
ERL_NIF_TERM atom_notsup;
+ERL_NIF_TERM atom_badarg;
ERL_NIF_TERM atom_digest;
#ifdef FIPS_SUPPORT
ERL_NIF_TERM atom_enabled;
@@ -150,6 +151,7 @@ int init_atoms(ErlNifEnv *env, const ERL_NIF_TERM fips_mode, const ERL_NIF_TERM
atom_ok = enif_make_atom(env,"ok");
atom_none = enif_make_atom(env,"none");
atom_notsup = enif_make_atom(env,"notsup");
+ atom_badarg = enif_make_atom(env,"badarg");
atom_digest = enif_make_atom(env,"digest");
atom_type = enif_make_atom(env,"type");
diff --git a/lib/crypto/c_src/atoms.h b/lib/crypto/c_src/atoms.h
index f8e9211459..fc46d838aa 100644
--- a/lib/crypto/c_src/atoms.h
+++ b/lib/crypto/c_src/atoms.h
@@ -37,6 +37,7 @@ extern ERL_NIF_TERM atom_undefined;
extern ERL_NIF_TERM atom_ok;
extern ERL_NIF_TERM atom_none;
extern ERL_NIF_TERM atom_notsup;
+extern ERL_NIF_TERM atom_badarg;
extern ERL_NIF_TERM atom_digest;
#ifdef FIPS_SUPPORT
extern ERL_NIF_TERM atom_enabled;
diff --git a/lib/crypto/c_src/block.c b/lib/crypto/c_src/block.c
deleted file mode 100644
index 0a4fd72623..0000000000
--- a/lib/crypto/c_src/block.c
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 2010-2018. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-
-#include "block.h"
-#include "aes.h"
-#include "cipher.h"
-
-ERL_NIF_TERM block_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type, Key, Ivec, Text, IsEncrypt) or (Type, Key, Text, IsEncrypt) */
- const struct cipher_type_t *cipherp;
- const EVP_CIPHER *cipher;
- ErlNifBinary key, ivec, text;
- EVP_CIPHER_CTX *ctx = NULL;
- ERL_NIF_TERM ret;
- unsigned char *out;
- int ivec_size, out_size = 0;
- int cipher_len;
-
- ASSERT(argc == 4 || argc == 5);
-
- if (!enif_inspect_iolist_as_binary(env, argv[1], &key))
- goto bad_arg;
- if (key.size > INT_MAX)
- goto bad_arg;
- if ((cipherp = get_cipher_type(argv[0], key.size)) == NULL)
- goto bad_arg;
- if (cipherp->flags & (NON_EVP_CIPHER | AEAD_CIPHER))
- goto bad_arg;
- if (!enif_inspect_iolist_as_binary(env, argv[argc - 2], &text))
- goto bad_arg;
- if (text.size > INT_MAX)
- goto bad_arg;
-
- if (FORBIDDEN_IN_FIPS(cipherp))
- return enif_raise_exception(env, atom_notsup);
- if ((cipher = cipherp->cipher.p) == NULL)
- return enif_raise_exception(env, atom_notsup);
-
- if (cipherp->flags & AES_CFBx) {
- if (argv[0] == atom_aes_cfb8
- && (key.size == 24 || key.size == 32)) {
- /* Why do EVP_CIPHER_CTX_set_key_length() fail on these key sizes?
- * Fall back on low level API
- */
- return aes_cfb_8_crypt(env, argc-1, argv+1);
- }
- else if (argv[0] == atom_aes_cfb128
- && (key.size == 24 || key.size == 32)) {
- /* Why do EVP_CIPHER_CTX_set_key_length() fail on these key sizes?
- * Fall back on low level API
- */
- return aes_cfb_128_crypt_nif(env, argc-1, argv+1);
- }
- }
-
- ivec_size = EVP_CIPHER_iv_length(cipher);
-
-#ifdef HAVE_ECB_IVEC_BUG
- if (cipherp->flags & ECB_BUG_0_9_8L)
- ivec_size = 0; /* 0.9.8l returns faulty ivec_size */
-#endif
-
- if (ivec_size < 0)
- goto bad_arg;
-
- if ((cipher_len = EVP_CIPHER_block_size(cipher)) < 0)
- goto bad_arg;
- if (text.size % (size_t)cipher_len != 0)
- goto bad_arg;
-
- if (ivec_size == 0) {
- if (argc != 4)
- goto bad_arg;
- } else {
- if (argc != 5)
- goto bad_arg;
- if (!enif_inspect_iolist_as_binary(env, argv[2], &ivec))
- goto bad_arg;
- if (ivec.size != (size_t)ivec_size)
- goto bad_arg;
- }
-
- if ((out = enif_make_new_binary(env, text.size, &ret)) == NULL)
- goto err;
- if ((ctx = EVP_CIPHER_CTX_new()) == NULL)
- goto err;
-
- if (!EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL,
- (argv[argc - 1] == atom_true)))
- goto err;
- if (!EVP_CIPHER_CTX_set_key_length(ctx, (int)key.size))
- goto err;
-
- if (EVP_CIPHER_type(cipher) == NID_rc2_cbc) {
- if (key.size > INT_MAX / 8)
- goto err;
- if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_SET_RC2_KEY_BITS, (int)key.size * 8, NULL))
- goto err;
- }
-
- if (!EVP_CipherInit_ex(ctx, NULL, NULL, key.data,
- ivec_size ? ivec.data : NULL, -1))
- goto err;
- if (!EVP_CIPHER_CTX_set_padding(ctx, 0))
- goto err;
-
- /* OpenSSL 0.9.8h asserts text.size > 0 */
- if (text.size > 0) {
- if (!EVP_CipherUpdate(ctx, out, &out_size, text.data, (int)text.size))
- goto err;
- if (ASSERT(out_size == text.size), 0)
- goto err;
- if (!EVP_CipherFinal_ex(ctx, out + out_size, &out_size))
- goto err;
- }
-
- ASSERT(out_size == 0);
- CONSUME_REDS(env, text);
- goto done;
-
- bad_arg:
- ret = enif_make_badarg(env);
- goto done;
-
- err:
- ret = enif_raise_exception(env, atom_notsup);
-
- done:
- if (ctx)
- EVP_CIPHER_CTX_free(ctx);
- return ret;
-}
diff --git a/lib/crypto/c_src/block.h b/lib/crypto/c_src/block.h
deleted file mode 100644
index cc5e78ce12..0000000000
--- a/lib/crypto/c_src/block.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 2010-2018. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-
-#ifndef E_BLOCK_H__
-#define E_BLOCK_H__ 1
-
-#include "common.h"
-
-ERL_NIF_TERM block_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-
-#endif /* E_BLOCK_H__ */
diff --git a/lib/crypto/c_src/chacha20.c b/lib/crypto/c_src/chacha20.c
deleted file mode 100644
index cfcc395dca..0000000000
--- a/lib/crypto/c_src/chacha20.c
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 2010-2018. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-
-#include "chacha20.h"
-#include "cipher.h"
-
-ERL_NIF_TERM chacha20_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key, IV) */
-#if defined(HAVE_CHACHA20)
- ErlNifBinary key_bin, ivec_bin;
- struct evp_cipher_ctx *ctx = NULL;
- const EVP_CIPHER *cipher;
- ERL_NIF_TERM ret;
-
- ASSERT(argc == 2);
-
- if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin))
- goto bad_arg;
- if (key_bin.size != 32)
- goto bad_arg;
- if (!enif_inspect_binary(env, argv[1], &ivec_bin))
- goto bad_arg;
- if (ivec_bin.size != 16)
- goto bad_arg;
-
- cipher = EVP_chacha20();
-
- if ((ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx))) == NULL)
- goto err;
- if ((ctx->ctx = EVP_CIPHER_CTX_new()) == NULL)
- goto err;
-
- if (EVP_CipherInit_ex(ctx->ctx, cipher, NULL,
- key_bin.data, ivec_bin.data, 1) != 1)
- goto err;
- if (EVP_CIPHER_CTX_set_padding(ctx->ctx, 0) != 1)
- goto err;
-
- ret = enif_make_resource(env, ctx);
- goto done;
-
- bad_arg:
- return enif_make_badarg(env);
-
- err:
- ret = enif_make_badarg(env);
-
- done:
- if (ctx)
- enif_release_resource(ctx);
- return ret;
-
-#else
- return enif_raise_exception(env, atom_notsup);
-#endif
-}
-
-ERL_NIF_TERM chacha20_stream_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (State, Data) */
-#if defined(HAVE_CHACHA20)
- struct evp_cipher_ctx *ctx = NULL, *new_ctx = NULL;
- ErlNifBinary data_bin;
- ERL_NIF_TERM ret, cipher_term;
- unsigned char *out;
- int outl = 0;
-
- ASSERT(argc == 2);
-
- if (!enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx))
- goto bad_arg;
- if (!enif_inspect_iolist_as_binary(env, argv[1], &data_bin))
- goto bad_arg;
- if (data_bin.size > INT_MAX)
- goto bad_arg;
-
- if ((new_ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx))) == NULL)
- goto err;
- if ((new_ctx->ctx = EVP_CIPHER_CTX_new()) == NULL)
- goto err;
-
- if (EVP_CIPHER_CTX_copy(new_ctx->ctx, ctx->ctx) != 1)
- goto err;
- if ((out = enif_make_new_binary(env, data_bin.size, &cipher_term)) == NULL)
- goto err;
- if (EVP_CipherUpdate(new_ctx->ctx, out, &outl, data_bin.data, (int)data_bin.size) != 1)
- goto err;
- ASSERT(outl >= 0 && (size_t)outl == data_bin.size);
-
- ret = enif_make_tuple2(env, enif_make_resource(env, new_ctx), cipher_term);
- CONSUME_REDS(env, data_bin);
- goto done;
-
- bad_arg:
- return enif_make_badarg(env);
-
- err:
- ret = enif_make_badarg(env);
-
- done:
- if (new_ctx)
- enif_release_resource(new_ctx);
- return ret;
-
-#else
- return enif_raise_exception(env, atom_notsup);
-#endif
-}
diff --git a/lib/crypto/c_src/chacha20.h b/lib/crypto/c_src/chacha20.h
deleted file mode 100644
index 7e2ccae2bb..0000000000
--- a/lib/crypto/c_src/chacha20.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 2010-2018. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-
-#ifndef E_CHACHA20_H__
-#define E_CHACHA20_H__ 1
-
-#include "common.h"
-
-ERL_NIF_TERM chacha20_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-ERL_NIF_TERM chacha20_stream_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-
-#endif /* E_CHACHA20_H__ */
diff --git a/lib/crypto/c_src/cipher.c b/lib/crypto/c_src/cipher.c
index c055a62654..5c57898c50 100644
--- a/lib/crypto/c_src/cipher.c
+++ b/lib/crypto/c_src/cipher.c
@@ -98,7 +98,9 @@ static struct cipher_type_t cipher_types[] =
{{"aes_128_ctr"}, {NULL}, 16, AES_CTR_COMPAT},
{{"aes_192_ctr"}, {NULL}, 24, AES_CTR_COMPAT},
{{"aes_256_ctr"}, {NULL}, 32, AES_CTR_COMPAT},
- {{"aes_ctr"}, {NULL}, 0, AES_CTR_COMPAT},
+ {{"aes_ctr"}, {NULL}, 16, AES_CTR_COMPAT},
+ {{"aes_ctr"}, {NULL}, 24, AES_CTR_COMPAT},
+ {{"aes_ctr"}, {NULL}, 32, AES_CTR_COMPAT},
#endif
#if defined(HAVE_CHACHA20)
@@ -162,6 +164,11 @@ static void evp_cipher_ctx_dtor(ErlNifEnv* env, struct evp_cipher_ctx* ctx) {
if (ctx->ctx)
EVP_CIPHER_CTX_free(ctx->ctx);
+
+#if !defined(HAVE_EVP_AES_CTR)
+ if (ctx->env)
+ enif_free_env(ctx->env);
+#endif
}
int init_cipher_ctx(ErlNifEnv *env) {
diff --git a/lib/crypto/c_src/cipher.h b/lib/crypto/c_src/cipher.h
index b0d9d324e1..b94873940f 100644
--- a/lib/crypto/c_src/cipher.h
+++ b/lib/crypto/c_src/cipher.h
@@ -59,6 +59,11 @@ struct cipher_type_t {
extern ErlNifResourceType* evp_cipher_ctx_rtype;
struct evp_cipher_ctx {
EVP_CIPHER_CTX* ctx;
+ int iv_len;
+#if !defined(HAVE_EVP_AES_CTR)
+ ErlNifEnv* env;
+ ERL_NIF_TERM state;
+#endif
};
ERL_NIF_TERM cipher_info_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c
index 261590d9a5..4aed06a489 100644
--- a/lib/crypto/c_src/crypto.c
+++ b/lib/crypto/c_src/crypto.c
@@ -29,9 +29,7 @@
#include "aes.h"
#include "algorithms.h"
#include "api_ng.h"
-#include "block.h"
#include "bn.h"
-#include "chacha20.h"
#include "cipher.h"
#include "cmac.h"
#include "dh.h"
@@ -50,7 +48,6 @@
#include "pkey.h"
#include "poly1305.h"
#include "rand.h"
-#include "rc4.h"
#include "rsa.h"
#include "srp.h"
@@ -80,22 +77,16 @@ static ErlNifFunc nif_funcs[] = {
{"hmac_final_nif", 2, hmac_final_nif, 0},
{"cmac_nif", 3, cmac_nif, 0},
{"cipher_info_nif", 1, cipher_info_nif, 0},
- {"block_crypt_nif", 5, block_crypt_nif, 0},
- {"block_crypt_nif", 4, block_crypt_nif, 0},
{"aes_ige_crypt_nif", 4, aes_ige_crypt_nif, 0},
- {"aes_ctr_stream_init", 2, aes_ctr_stream_init, 0},
- {"aes_ctr_stream_encrypt", 2, aes_ctr_stream_encrypt, 0},
- {"aes_ctr_stream_decrypt", 2, aes_ctr_stream_encrypt, 0},
{"ng_crypto_init_nif", 4, ng_crypto_init_nif, 0},
{"ng_crypto_update_nif", 2, ng_crypto_update_nif, 0},
{"ng_crypto_update_nif", 3, ng_crypto_update_nif, 0},
+ {"ng_crypto_one_shot_nif", 5, ng_crypto_one_shot_nif, 0},
{"strong_rand_bytes_nif", 1, strong_rand_bytes_nif, 0},
{"strong_rand_range_nif", 1, strong_rand_range_nif, 0},
{"rand_uniform_nif", 2, rand_uniform_nif, 0},
{"mod_exp_nif", 4, mod_exp_nif, 0},
{"do_exor", 2, do_exor, 0},
- {"rc4_set_key", 1, rc4_set_key, 0},
- {"rc4_encrypt_with_state", 2, rc4_encrypt_with_state, 0},
{"pkey_sign_nif", 5, pkey_sign_nif, 0},
{"pkey_verify_nif", 6, pkey_verify_nif, 0},
{"pkey_crypt_nif", 6, pkey_crypt_nif, 0},
@@ -117,10 +108,6 @@ static ErlNifFunc nif_funcs[] = {
{"aead_encrypt", 6, aead_encrypt, 0},
{"aead_decrypt", 6, aead_decrypt, 0},
- {"chacha20_stream_init", 2, chacha20_stream_init, 0},
- {"chacha20_stream_encrypt", 2, chacha20_stream_crypt, 0},
- {"chacha20_stream_decrypt", 2, chacha20_stream_crypt, 0},
-
{"poly1305_nif", 2, poly1305_nif, 0},
{"engine_by_id_nif", 1, engine_by_id_nif, 0},
diff --git a/lib/crypto/c_src/openssl_config.h b/lib/crypto/c_src/openssl_config.h
index 45144a0c25..46868cb987 100644
--- a/lib/crypto/c_src/openssl_config.h
+++ b/lib/crypto/c_src/openssl_config.h
@@ -109,6 +109,7 @@
#ifndef HAS_LIBRESSL
# if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
# define HAS_EVP_PKEY_CTX
+# define HAVE_EVP_CIPHER_CTX_COPY
# endif
#endif
@@ -203,12 +204,17 @@
#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
# ifndef HAS_LIBRESSL
-# define HAVE_CHACHA20
# define HAVE_CHACHA20_POLY1305
# define HAVE_RSA_OAEP_MD
# endif
#endif
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(1,1,0,'d')
+# ifndef HAS_LIBRESSL
+# define HAVE_CHACHA20
+# endif
+#endif
+
// OPENSSL_VERSION_NUMBER >= 1.1.1-pre8
#if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1)-7)
# ifndef HAS_LIBRESSL
diff --git a/lib/crypto/c_src/pkey.c b/lib/crypto/c_src/pkey.c
index 393358d173..638bb588fa 100644
--- a/lib/crypto/c_src/pkey.c
+++ b/lib/crypto/c_src/pkey.c
@@ -719,6 +719,11 @@ enif_get_atom(env,argv[1],buf,1024,ERL_NIF_LATIN1); printf("hash=%s ",buf);
if (pkey)
EVP_PKEY_free(pkey);
+#ifdef HAVE_EDDSA
+ if (mdctx)
+ EVP_MD_CTX_free(mdctx);
+#endif
+
return ret;
}
diff --git a/lib/crypto/c_src/poly1305.c b/lib/crypto/c_src/poly1305.c
index db3433dce3..76579c0a29 100644
--- a/lib/crypto/c_src/poly1305.c
+++ b/lib/crypto/c_src/poly1305.c
@@ -85,6 +85,6 @@ ERL_NIF_TERM poly1305_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
return ret;
#else
- return atom_notsup;
+ return enif_raise_exception(env, atom_notsup);
#endif
}
diff --git a/lib/crypto/c_src/rc4.c b/lib/crypto/c_src/rc4.c
deleted file mode 100644
index e423661097..0000000000
--- a/lib/crypto/c_src/rc4.c
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 2010-2018. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-
-#include "rc4.h"
-
-ERL_NIF_TERM rc4_set_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key) */
-#ifndef OPENSSL_NO_RC4
- ErlNifBinary key;
- ERL_NIF_TERM ret;
- RC4_KEY *rc4_key;
-
- CHECK_NO_FIPS_MODE();
-
- ASSERT(argc == 1);
-
- if (!enif_inspect_iolist_as_binary(env, argv[0], &key))
- goto bad_arg;
- if (key.size > INT_MAX)
- goto bad_arg;
-
- if ((rc4_key = (RC4_KEY*)enif_make_new_binary(env, sizeof(RC4_KEY), &ret)) == NULL)
- goto err;
-
- RC4_set_key(rc4_key, (int)key.size, key.data);
- return ret;
-
- bad_arg:
- err:
- return enif_make_badarg(env);
-
-#else
- return enif_raise_exception(env, atom_notsup);
-#endif
-}
-
-ERL_NIF_TERM rc4_encrypt_with_state(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (State, Data) */
-#ifndef OPENSSL_NO_RC4
- ErlNifBinary state, data;
- RC4_KEY* rc4_key;
- ERL_NIF_TERM new_state, new_data;
- unsigned char *outp;
-
- CHECK_NO_FIPS_MODE();
-
- ASSERT(argc == 2);
-
- if (!enif_inspect_iolist_as_binary(env, argv[0], &state))
- goto bad_arg;
- if (state.size != sizeof(RC4_KEY))
- goto bad_arg;
- if (!enif_inspect_iolist_as_binary(env, argv[1], &data))
- goto bad_arg;
-
- if ((rc4_key = (RC4_KEY*)enif_make_new_binary(env, sizeof(RC4_KEY), &new_state)) == NULL)
- goto err;
- if ((outp = enif_make_new_binary(env, data.size, &new_data)) == NULL)
- goto err;
-
- memcpy(rc4_key, state.data, sizeof(RC4_KEY));
- RC4(rc4_key, data.size, data.data, outp);
-
- CONSUME_REDS(env, data);
- return enif_make_tuple2(env, new_state, new_data);
-
- bad_arg:
- err:
- return enif_make_badarg(env);
-
-#else
- return enif_raise_exception(env, atom_notsup);
-#endif
-}
-
diff --git a/lib/crypto/c_src/rc4.h b/lib/crypto/c_src/rc4.h
deleted file mode 100644
index 28bf674253..0000000000
--- a/lib/crypto/c_src/rc4.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 2010-2018. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * %CopyrightEnd%
- */
-
-#ifndef E_RC4_H__
-#define E_RC4_H__ 1
-
-#include "common.h"
-
-ERL_NIF_TERM rc4_set_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-ERL_NIF_TERM rc4_encrypt_with_state(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-
-#endif /* E_RC4_H__ */
diff --git a/lib/crypto/doc/src/notes.xml b/lib/crypto/doc/src/notes.xml
index 0a3f68ade2..c0b302734e 100644
--- a/lib/crypto/doc/src/notes.xml
+++ b/lib/crypto/doc/src/notes.xml
@@ -31,6 +31,38 @@
</header>
<p>This document describes the changes made to the Crypto application.</p>
+<section><title>Crypto 4.4.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixes a bug that caused <c>crypto:sign</c> and
+ <c>crypto:verify</c> to return the error message
+ <c>badarg</c> instead of <c>notsup</c> in one case. That
+ case was when signing or verifying with eddsa keys (that
+ is, ed15519 or ed448), but only when FIPS was supported
+ and enabled.</p>
+ <p>
+ Own Id: OTP-15634</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Added a crypto benchmark test suite.</p>
+ <p>
+ Own Id: OTP-15447</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Crypto 4.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl
index 97a4a7a3f0..5cf34f8069 100644
--- a/lib/crypto/src/crypto.erl
+++ b/lib/crypto/src/crypto.erl
@@ -40,24 +40,27 @@
-export([rand_plugin_uniform/2]).
-export([rand_cache_plugin_next/1]).
-export([rand_uniform/2]).
--export([block_encrypt/3, block_decrypt/3, block_encrypt/4, block_decrypt/4]).
-export([next_iv/2, next_iv/3]).
--export([stream_init/2, stream_init/3, stream_encrypt/2, stream_decrypt/2]).
-export([public_encrypt/4, private_decrypt/4]).
-export([private_encrypt/4, public_decrypt/4]).
-export([privkey_to_pubkey/2]).
-export([ec_curve/1, ec_curves/0]).
-export([rand_seed/1]).
-%% Experiment
--export([crypto_init/4,
- crypto_update/2, crypto_update/3,
- %% Emulates old api:
- crypto_stream_init/2, crypto_stream_init/3,
- crypto_stream_encrypt/2,
- crypto_stream_decrypt/2,
- crypto_block_encrypt/3, crypto_block_encrypt/4,
- crypto_block_decrypt/3, crypto_block_decrypt/4
+%% Old interface. Now implemented with the New interface
+-export([stream_init/2, stream_init/3,
+ stream_encrypt/2,
+ stream_decrypt/2,
+ block_encrypt/3, block_encrypt/4,
+ block_decrypt/3, block_decrypt/4
+ ]).
+
+%% New interface
+-export([crypto_init/4, crypto_init/3,
+ crypto_update/2,
+ crypto_one_shot/5,
+ crypto_init_dyn_iv/3,
+ crypto_update_dyn_iv/3
]).
@@ -533,10 +536,17 @@ poly1305(Key, Data) ->
%%%================================================================
%%%
-%%% Encrypt/decrypt
+%%% Encrypt/decrypt, The "Old API"
%%%
%%%================================================================
+-define(COMPAT(CALL),
+ try CALL
+ catch
+ error:{E,_Reason} when E==notsup ; E==badarg ->
+ error(E)
+ end).
+
-spec cipher_info(Type) -> map() when Type :: block_cipher_with_iv()
| aead_cipher()
| block_cipher_without_iv().
@@ -544,7 +554,6 @@ cipher_info(Type) ->
cipher_info_nif(Type).
%%%---- Block ciphers
-
%%%----------------------------------------------------------------
-spec block_encrypt(Type::block_cipher_with_iv(), Key::key()|des3_key(), Ivec::binary(), PlainText::iodata()) -> binary();
(Type::aead_cipher(), Key::iodata(), Ivec::binary(), {AAD::binary(), PlainText::iodata()}) ->
@@ -556,11 +565,6 @@ cipher_info(Type) ->
block_encrypt(Type, Key, Ivec, Data) ->
do_block_encrypt(alias(Type), Key, Ivec, Data).
-do_block_encrypt(Type, Key0, Ivec, Data) when Type =:= des_ede3_cbc;
- Type =:= des_ede3_cfb ->
- Key = check_des3_key(Key0),
- block_crypt_nif(Type, Key, Ivec, Data, true);
-
do_block_encrypt(Type, Key, Ivec, PlainText) when Type =:= aes_ige256 ->
notsup_to_error(aes_ige_crypt_nif(Key, Ivec, PlainText, true));
@@ -577,14 +581,13 @@ do_block_encrypt(Type, Key, Ivec, Data) when Type =:= aes_gcm;
end;
do_block_encrypt(Type, Key, Ivec, PlainText) ->
- block_crypt_nif(Type, Key, Ivec, PlainText, true).
-
+ ?COMPAT(crypto_one_shot(Type, Key, Ivec, PlainText, true)).
-spec block_encrypt(Type::block_cipher_without_iv(), Key::key(), PlainText::iodata()) -> binary().
block_encrypt(Type, Key, PlainText) ->
- block_crypt_nif(alias(Type), Key, PlainText, true).
+ ?COMPAT(crypto_one_shot(Type, Key, <<>>, PlainText, true)).
%%%----------------------------------------------------------------
%%%----------------------------------------------------------------
@@ -595,11 +598,6 @@ block_encrypt(Type, Key, PlainText) ->
block_decrypt(Type, Key, Ivec, Data) ->
do_block_decrypt(alias(Type), Key, Ivec, Data).
-do_block_decrypt(Type, Key0, Ivec, Data) when Type =:= des_ede3_cbc;
- Type =:= des_ede3_cfb ->
- Key = check_des3_key(Key0),
- block_crypt_nif(Type, Key, Ivec, Data, false);
-
do_block_decrypt(aes_ige256, Key, Ivec, Data) ->
notsup_to_error(aes_ige_crypt_nif(Key, Ivec, Data, false));
@@ -609,14 +607,80 @@ do_block_decrypt(Type, Key, Ivec, {AAD, Data, Tag}) when Type =:= aes_gcm;
aead_decrypt(Type, Key, Ivec, AAD, Data, Tag);
do_block_decrypt(Type, Key, Ivec, Data) ->
- block_crypt_nif(Type, Key, Ivec, Data, false).
-
+ ?COMPAT(crypto_one_shot(Type, Key, Ivec, Data, false)).
-spec block_decrypt(Type::block_cipher_without_iv(), Key::key(), Data::iodata()) -> binary().
block_decrypt(Type, Key, Data) ->
- block_crypt_nif(alias(Type), Key, Data, false).
+ ?COMPAT(crypto_one_shot(Type, Key, <<>>, Data, false)).
+
+%%%-------- Stream ciphers API
+
+-opaque stream_state() :: {stream_cipher(),
+ crypto_state() | {crypto_state(),flg_undefined}
+ }.
+
+-type stream_cipher() :: stream_cipher_iv() | stream_cipher_no_iv() .
+-type stream_cipher_no_iv() :: rc4 .
+-type stream_cipher_iv() :: aes_ctr
+ | aes_128_ctr
+ | aes_192_ctr
+ | aes_256_ctr
+ | chacha20 .
+
+%%%---- stream_init
+-spec stream_init(Type, Key, IVec) -> State | no_return()
+ when Type :: stream_cipher_iv(),
+ Key :: iodata(),
+ IVec ::binary(),
+ State :: stream_state() .
+stream_init(Type, Key, IVec) when is_binary(IVec) ->
+ Ref = ?COMPAT(ng_crypto_init_nif(alias(Type),
+ iolist_to_binary(Key), iolist_to_binary(IVec),
+ undefined)
+ ),
+ {Type, {Ref,flg_undefined}}.
+
+
+-spec stream_init(Type, Key) -> State | no_return()
+ when Type :: stream_cipher_no_iv(),
+ Key :: iodata(),
+ State :: stream_state() .
+stream_init(rc4 = Type, Key) ->
+ Ref = ?COMPAT(ng_crypto_init_nif(alias(Type),
+ iolist_to_binary(Key), <<>>,
+ undefined)
+ ),
+ {Type, {Ref,flg_undefined}}.
+
+%%%---- stream_encrypt
+-spec stream_encrypt(State, PlainText) -> {NewState, CipherText} | no_return()
+ when State :: stream_state(),
+ PlainText :: iodata(),
+ NewState :: stream_state(),
+ CipherText :: iodata() .
+stream_encrypt(State, Data) ->
+ crypto_stream_emulate(State, Data, true).
+
+%%%---- stream_decrypt
+-spec stream_decrypt(State, CipherText) -> {NewState, PlainText} | no_return()
+ when State :: stream_state(),
+ CipherText :: iodata(),
+ NewState :: stream_state(),
+ PlainText :: iodata() .
+stream_decrypt(State, Data) ->
+ crypto_stream_emulate(State, Data, false).
+
+%%%-------- helpers
+crypto_stream_emulate({Cipher,{Ref0,flg_undefined}}, Data, EncryptFlag) when is_reference(Ref0) ->
+ ?COMPAT(begin
+ Ref = ng_crypto_init_nif(Ref0, <<>>, <<>>, EncryptFlag),
+ {{Cipher,Ref}, crypto_update(Ref, Data)}
+ end);
+
+crypto_stream_emulate({Cipher,Ref}, Data, _) when is_reference(Ref) ->
+ ?COMPAT({{Cipher,Ref}, crypto_update(Ref, Data)}).
%%%----------------------------------------------------------------
-spec next_iv(Type:: cbc_cipher(), Data) -> NextIVec when % Type :: cbc_cipher(), %des_cbc | des3_cbc | aes_cbc | aes_ige,
@@ -645,59 +709,155 @@ next_iv(des_cfb, Data, IVec) ->
next_iv(Type, Data, _Ivec) ->
next_iv(Type, Data).
-%%%---- Stream ciphers
+%%%================================================================
+%%%
+%%% Encrypt/decrypt, The "New API"
+%%%
+%%%================================================================
--opaque stream_state() :: {stream_cipher(), reference()}.
+-opaque crypto_state() :: reference() .
--type stream_cipher() :: stream_cipher_iv() | stream_cipher_no_iv() .
--type stream_cipher_no_iv() :: rc4 .
--type stream_cipher_iv() :: aes_ctr
- | aes_128_ctr
- | aes_192_ctr
- | aes_256_ctr
- | chacha20 .
--spec stream_init(Type, Key, IVec) -> State when Type :: stream_cipher_iv(),
- Key :: iodata(),
- IVec :: binary(),
- State :: stream_state() .
-stream_init(aes_ctr, Key, Ivec) ->
- {aes_ctr, aes_ctr_stream_init(Key, Ivec)};
-stream_init(aes_128_ctr, Key, Ivec) ->
- {aes_ctr, aes_ctr_stream_init(Key, Ivec)};
-stream_init(aes_192_ctr, Key, Ivec) ->
- {aes_ctr, aes_ctr_stream_init(Key, Ivec)};
-stream_init(aes_256_ctr, Key, Ivec) ->
- {aes_ctr, aes_ctr_stream_init(Key, Ivec)};
-stream_init(chacha20, Key, Ivec) ->
- {chacha20, chacha20_stream_init(Key,Ivec)}.
-
--spec stream_init(Type, Key) -> State when Type :: stream_cipher_no_iv(),
- Key :: iodata(),
- State :: stream_state() .
-stream_init(rc4, Key) ->
- {rc4, notsup_to_error(rc4_set_key(Key))}.
-
--spec stream_encrypt(State, PlainText) -> {NewState, CipherText}
- when State :: stream_state(),
- PlainText :: iodata(),
- NewState :: stream_state(),
- CipherText :: iodata() .
-stream_encrypt(State, Data0) ->
- Data = iolist_to_binary(Data0),
- MaxByts = max_bytes(),
- stream_crypt(fun do_stream_encrypt/2, State, Data, erlang:byte_size(Data), MaxByts, []).
+%%%----------------------------------------------------------------
+%%%
+%%% Create and initialize a new state for encryption or decryption
+%%%
--spec stream_decrypt(State, CipherText) -> {NewState, PlainText}
- when State :: stream_state(),
- CipherText :: iodata(),
- NewState :: stream_state(),
- PlainText :: iodata() .
-stream_decrypt(State, Data0) ->
- Data = iolist_to_binary(Data0),
- MaxByts = max_bytes(),
- stream_crypt(fun do_stream_decrypt/2, State, Data, erlang:byte_size(Data), MaxByts, []).
+-spec crypto_init(Cipher, Key, EncryptFlag) -> State | ng_crypto_error()
+ when Cipher :: block_cipher_without_iv()
+ | stream_cipher_no_iv(),
+ Key :: iodata(),
+ EncryptFlag :: boolean(),
+ State :: crypto_state() .
+crypto_init(Cipher, Key, EncryptFlag) ->
+ %% The IV is supposed to be supplied by calling crypto_update/3
+ ng_crypto_init_nif(alias(Cipher), iolist_to_binary(Key), <<>>, EncryptFlag).
+
+
+-spec crypto_init(Cipher, Key, IV, EncryptFlag) -> State | ng_crypto_error()
+ when Cipher :: stream_cipher_iv()
+ | block_cipher_with_iv(),
+ Key :: iodata(),
+ IV :: iodata(),
+ EncryptFlag :: boolean(),
+ State :: crypto_state() .
+crypto_init(Cipher, Key, IV, EncryptFlag) ->
+ ng_crypto_init_nif(alias(Cipher), iolist_to_binary(Key), iolist_to_binary(IV), EncryptFlag).
+
+
+
+%%%----------------------------------------------------------------
+-spec crypto_init_dyn_iv(Cipher, Key, EncryptFlag) -> State | ng_crypto_error()
+ when Cipher :: stream_cipher_iv()
+ | block_cipher_with_iv(),
+ Key :: iodata(),
+ EncryptFlag :: boolean(),
+ State :: crypto_state() .
+crypto_init_dyn_iv(Cipher, Key, EncryptFlag) ->
+ %% The IV is supposed to be supplied by calling crypto_update/3
+ ng_crypto_init_nif(alias(Cipher), iolist_to_binary(Key), undefined, EncryptFlag).
+
+%%%----------------------------------------------------------------
+%%%
+%%% Encrypt/decrypt a sequence of bytes. The sum of the sizes
+%%% of all blocks must be an integer multiple of the crypto's
+%%% blocksize.
+%%%
+
+-spec crypto_update(State, Data) -> Result | ng_crypto_error()
+ when State :: crypto_state(),
+ Data :: iodata(),
+ Result :: binary() .
+crypto_update(State, Data0) ->
+ case iolist_to_binary(Data0) of
+ <<>> ->
+ <<>>; % Known to fail on OpenSSL 0.9.8h
+ Data ->
+ ng_crypto_update_nif(State, Data)
+ end.
+
+
+%%%----------------------------------------------------------------
+-spec crypto_update_dyn_iv(State, Data, IV) -> Result | ng_crypto_error()
+ when State :: crypto_state(),
+ Data :: iodata(),
+ IV :: iodata(),
+ Result :: binary() .
+crypto_update_dyn_iv(State, Data0, IV) ->
+ %% When State is from State = crypto_init(Cipher, Key, undefined, EncryptFlag)
+ case iolist_to_binary(Data0) of
+ <<>> ->
+ <<>>; % Known to fail on OpenSSL 0.9.8h
+ Data ->
+ ng_crypto_update_nif(State, Data, iolist_to_binary(IV))
+ end.
+
+%%%----------------------------------------------------------------
+%%%
+%%% Encrypt/decrypt one set bytes.
+%%% The size must be an integer multiple of the crypto's blocksize.
+%%%
+
+-spec crypto_one_shot(Cipher, Key, IV, Data, EncryptFlag) -> Result | ng_crypto_error()
+ when Cipher :: stream_cipher()
+ | block_cipher_with_iv()
+ | block_cipher_without_iv(),
+ Key :: iodata(),
+ IV :: iodata() | undefined,
+ Data :: iodata(),
+ EncryptFlag :: boolean(),
+ Result :: binary() .
+crypto_one_shot(Cipher, Key, undefined, Data, EncryptFlag) ->
+ crypto_one_shot(Cipher, Key, <<>>, Data, EncryptFlag);
+
+crypto_one_shot(Cipher, Key, IV, Data0, EncryptFlag) ->
+ case iolist_to_binary(Data0) of
+ <<>> ->
+ <<>>; % Known to fail on OpenSSL 0.9.8h
+ Data ->
+ ng_crypto_one_shot_nif(alias(Cipher),
+ iolist_to_binary(Key), iolist_to_binary(IV), Data,
+ EncryptFlag)
+ end.
+
+%%%----------------------------------------------------------------
+%%% NIFs
+
+-type ng_crypto_error() :: no_return() .
+
+-spec ng_crypto_init_nif(atom(), binary(), binary()|undefined, boolean()|undefined ) -> crypto_state() | ng_crypto_error()
+ ; (crypto_state(), <<>>, <<>>, boolean()) -> crypto_state() | ng_crypto_error().
+ng_crypto_init_nif(_Cipher, _Key, _IVec, _EncryptFlg) -> ?nif_stub.
+
+
+-spec ng_crypto_update_nif(crypto_state(), binary()) -> binary() | ng_crypto_error() .
+ng_crypto_update_nif(_State, _Data) -> ?nif_stub.
+
+-spec ng_crypto_update_nif(crypto_state(), binary(), binary()) -> binary() | ng_crypto_error() .
+ng_crypto_update_nif(_State, _Data, _IV) -> ?nif_stub.
+
+
+-spec ng_crypto_one_shot_nif(atom(), binary(), binary(), binary(), boolean() ) -> binary() | ng_crypto_error().
+ng_crypto_one_shot_nif(_Cipher, _Key, _IVec, _Data, _EncryptFlg) -> ?nif_stub.
+
+%%%----------------------------------------------------------------
+%%% Cipher aliases
+%%%
+prepend_cipher_aliases(L) ->
+ [des3_cbc, des_ede3, des_ede3_cbf, des3_cbf, des3_cfb, aes_cbc128, aes_cbc256 | L].
+
+%%%---- des_ede3_cbc
+alias(des3_cbc) -> des_ede3_cbc;
+alias(des_ede3) -> des_ede3_cbc;
+%%%---- des_ede3_cfb
+alias(des_ede3_cbf) -> des_ede3_cfb;
+alias(des3_cbf) -> des_ede3_cfb;
+alias(des3_cfb) -> des_ede3_cfb;
+%%%---- aes_*_cbc
+alias(aes_cbc128) -> aes_128_cbc;
+alias(aes_cbc256) -> aes_256_cbc;
+alias(Alg) -> Alg.
%%%================================================================
%%%
@@ -1785,19 +1945,6 @@ poly1305_nif(_Key, _Data) -> ?nif_stub.
cipher_info_nif(_Type) -> ?nif_stub.
-block_crypt_nif(_Type, _Key, _Ivec, _Text, _IsEncrypt) -> ?nif_stub.
-block_crypt_nif(_Type, _Key, _Text, _IsEncrypt) -> ?nif_stub.
-
-check_des3_key(Key) ->
- case lists:map(fun erlang:iolist_to_binary/1, Key) of
- ValidKey = [B1, B2, B3] when byte_size(B1) =:= 8,
- byte_size(B2) =:= 8,
- byte_size(B3) =:= 8 ->
- ValidKey;
- _ ->
- error(badarg)
- end.
-
%%
%% AES - in Galois/Counter Mode (GCM)
%%
@@ -1814,59 +1961,7 @@ aead_decrypt(_Type, _Key, _Ivec, _AAD, _In, _Tag) -> ?nif_stub.
aes_ige_crypt_nif(_Key, _IVec, _Data, _IsEncrypt) -> ?nif_stub.
-
-%% Stream ciphers --------------------------------------------------------------------
-
-stream_crypt(Fun, State, Data, Size, MaxByts, []) when Size =< MaxByts ->
- Fun(State, Data);
-stream_crypt(Fun, State0, Data, Size, MaxByts, Acc) when Size =< MaxByts ->
- {State, Cipher} = Fun(State0, Data),
- {State, list_to_binary(lists:reverse([Cipher | Acc]))};
-stream_crypt(Fun, State0, Data, _, MaxByts, Acc) ->
- <<Increment:MaxByts/binary, Rest/binary>> = Data,
- {State, CipherText} = Fun(State0, Increment),
- stream_crypt(Fun, State, Rest, erlang:byte_size(Rest), MaxByts, [CipherText | Acc]).
-
-do_stream_encrypt({aes_ctr, State0}, Data) ->
- {State, Cipher} = aes_ctr_stream_encrypt(State0, Data),
- {{aes_ctr, State}, Cipher};
-do_stream_encrypt({rc4, State0}, Data) ->
- {State, Cipher} = rc4_encrypt_with_state(State0, Data),
- {{rc4, State}, Cipher};
-do_stream_encrypt({chacha20, State0}, Data) ->
- {State, Cipher} = chacha20_stream_encrypt(State0, Data),
- {{chacha20, State}, Cipher}.
-
-do_stream_decrypt({aes_ctr, State0}, Data) ->
- {State, Text} = aes_ctr_stream_decrypt(State0, Data),
- {{aes_ctr, State}, Text};
-do_stream_decrypt({rc4, State0}, Data) ->
- {State, Text} = rc4_encrypt_with_state(State0, Data),
- {{rc4, State}, Text};
-do_stream_decrypt({chacha20, State0}, Data) ->
- {State, Cipher} = chacha20_stream_decrypt(State0, Data),
- {{chacha20, State}, Cipher}.
-
-
-%%
-%% AES - in counter mode (CTR) with state maintained for multi-call streaming
-%%
-aes_ctr_stream_init(_Key, _IVec) -> ?nif_stub.
-aes_ctr_stream_encrypt(_State, _Data) -> ?nif_stub.
-aes_ctr_stream_decrypt(_State, _Cipher) -> ?nif_stub.
-
-%%
-%% RC4 - symmetric stream cipher
-%%
-rc4_set_key(_Key) -> ?nif_stub.
-rc4_encrypt_with_state(_State, _Data) -> ?nif_stub.
-
-%%
-%% CHACHA20 - stream cipher
-%%
-chacha20_stream_init(_Key, _IVec) -> ?nif_stub.
-chacha20_stream_encrypt(_State, _Data) -> ?nif_stub.
-chacha20_stream_decrypt(_State, _Data) -> ?nif_stub.
+%%%================================================================
%% Secure remote password -------------------------------------------------------------------
@@ -2232,176 +2327,3 @@ check_otp_test_engine(LibDir) ->
end.
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%%
-%%% Experimental NG
-%%%
-
-%%% -> {ok,State::ref()} | {error,Reason}
-
--opaque crypto_state() :: reference() | {any(),any(),any(),any()}.
-
-
-%%%----------------------------------------------------------------
-%%%
-%%% Create and initialize a new state for encryption or decryption
-%%%
-
--spec crypto_init(Cipher, Key, IV, EncryptFlag) -> {ok,State} | {error,term()} | undefined
- when Cipher :: stream_cipher()
- | block_cipher_with_iv()
- | block_cipher_without_iv() ,
- Key :: iodata(),
- IV :: binary(),
- EncryptFlag :: boolean() | undefined,
- State :: crypto_state() .
-
-crypto_init(Cipher, Key, IV, EncryptFlag) when is_atom(Cipher),
- is_binary(Key),
- is_binary(IV),
- is_atom(EncryptFlag) ->
- case ng_crypto_init_nif(alias(Cipher), Key, IV, EncryptFlag) of
- {error,Error} ->
- {error,Error};
- undefined -> % For compatibility function crypto_stream_init/3
- undefined;
- Ref when is_reference(Ref) ->
- {ok,Ref};
- State when is_tuple(State),
- size(State)==4 ->
- {ok,State} % compatibility with old cryptolibs < 1.0.1
- end.
-
-
-%%%----------------------------------------------------------------
-%%%
-%%% Encrypt/decrypt a sequence of bytes. The sum of the sizes
-%%% of all blocks must be an integer multiple of the crypto's
-%%% blocksize.
-%%%
-
--spec crypto_update(State, Data) -> {ok,Result} | {error,term()}
- when State :: crypto_state(),
- Data :: iodata(),
- Result :: binary() | {crypto_state(),binary()}.
-crypto_update(State, Data) ->
- mk_ret(ng_crypto_update_nif(State, Data)).
-
-%%%----------------------------------------------------------------
-%%%
-%%% Encrypt/decrypt a sequence of bytes but change the IV first.
-%%% Not applicable for all modes.
-%%%
-
--spec crypto_update(State, Data, IV) -> {ok,Result} | {error,term()}
- when State :: crypto_state(),
- Data :: iodata(),
- IV :: binary(),
- Result :: binary() | {crypto_state(),binary()}.
-crypto_update(State, Data, IV) ->
- mk_ret(ng_crypto_update_nif(State, Data, IV)).
-
-%%%----------------------------------------------------------------
-%%% Helpers
-mk_ret(R) -> mk_ret(R, []).
-
-mk_ret({error,Error}, _) ->
- {error,Error};
-mk_ret(Bin, Acc) when is_binary(Bin) ->
- {ok, iolist_to_binary(lists:reverse([Bin|Acc]))};
-mk_ret({State1,Bin}, Acc) when is_tuple(State1),
- size(State1) == 4,
- is_binary(Bin) ->
- %% compatibility with old cryptolibs < 1.0.1
- {ok, {State1, iolist_to_binary(lists:reverse([Bin|Acc]))}}.
-
-%%%----------------------------------------------------------------
-%%% NIFs
-ng_crypto_init_nif(_Cipher, _Key, _IVec, _EncryptFlg) -> ?nif_stub.
-ng_crypto_update_nif(_State, _Data) -> ?nif_stub.
-ng_crypto_update_nif(_State, _Data, _IV) -> ?nif_stub.
-
-%%%================================================================
-%%% Compatibility functions to be called by "old" api functions.
-
-%%%--------------------------------
-%%%---- block encrypt/decrypt
-crypto_block_encrypt(Cipher, Key, Data) -> crypto_block_encrypt(Cipher, Key, <<>>, Data).
-crypto_block_decrypt(Cipher, Key, Data) -> crypto_block_decrypt(Cipher, Key, <<>>, Data).
-
-crypto_block_encrypt(Cipher, Key, Ivec, Data) -> crypto_block(Cipher, Key, Ivec, Data, true).
-crypto_block_decrypt(Cipher, Key, Ivec, Data) -> crypto_block(Cipher, Key, Ivec, Data, false).
-
-%% AEAD: use old funcs
-
-%%%---- helper
-crypto_block(Cipher, Key, IV, Data, EncryptFlag) ->
- case crypto_init(Cipher, iolist_to_binary(Key), iolist_to_binary(IV), EncryptFlag) of
- {ok, Ref} ->
- case crypto_update(Ref, Data) of
- {ok, {_,Bin}} when is_binary(Bin) -> Bin;
- {ok, Bin} when is_binary(Bin) -> Bin;
- {error,_} -> error(badarg)
- end;
-
- {error,_} -> error(badarg)
- end.
-
-%%%--------------------------------
-%%%---- stream init, encrypt/decrypt
-
-crypto_stream_init(Cipher, Key) ->
- crypto_stream_init(Cipher, Key, <<>>).
-
-crypto_stream_init(Cipher, Key0, IV0) ->
- Key = iolist_to_binary(Key0),
- IV = iolist_to_binary(IV0),
- %% First check the argumensts:
- case crypto_init(Cipher, Key, IV, undefined) of
- undefined ->
- {Cipher, {Key, IV}};
- {error,_} ->
- {error,badarg}
- end.
-
-crypto_stream_encrypt(State, PlainText) ->
- crypto_stream_emulate(State, PlainText, true).
-
-crypto_stream_decrypt(State, CryptoText) ->
- crypto_stream_emulate(State, CryptoText, false).
-
-
-%%%---- helper
-crypto_stream_emulate({Cipher,{Key,IV}}, Data, EncryptFlag) ->
- case crypto_init(Cipher, Key, IV, EncryptFlag) of
- {ok,State} ->
- crypto_stream_emulate({Cipher,State}, Data, EncryptFlag);
- {error,_} ->
- error(badarg)
- end;
-crypto_stream_emulate({Cipher,State}, Data, _) ->
- case crypto_update(State, Data) of
- {ok, {State1,Bin}} when is_binary(Bin) -> {{Cipher,State1},Bin};
- {ok,Bin} when is_binary(Bin) -> {{Cipher,State},Bin};
- {error,_} -> error(badarg)
- end.
-
-
-%%%================================================================
-
-prepend_cipher_aliases(L) ->
- [des3_cbc, des_ede3, des_ede3_cbf, des3_cbf, des3_cfb, aes_cbc128, aes_cbc256 | L].
-
-
-%%%---- des_ede3_cbc
-alias(des3_cbc) -> des_ede3_cbc;
-alias(des_ede3) -> des_ede3_cbc;
-%%%---- des_ede3_cfb
-alias(des_ede3_cbf) -> des_ede3_cfb;
-alias(des3_cbf) -> des_ede3_cfb;
-alias(des3_cfb) -> des_ede3_cfb;
-%%%---- aes_*_cbc
-alias(aes_cbc128) -> aes_128_cbc;
-alias(aes_cbc256) -> aes_256_cbc;
-
-alias(Alg) -> Alg.
diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl
index 7257f4fb9f..7dbbde68e9 100644
--- a/lib/crypto/test/crypto_SUITE.erl
+++ b/lib/crypto/test/crypto_SUITE.erl
@@ -9,7 +9,7 @@
%%
%% 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
@@ -46,53 +46,67 @@ all() ->
].
groups() ->
- [{non_fips, [], [{group, md4},
+ [{non_fips, [], [
+ {group, blake2b},
+ {group, blake2s},
+ {group, dss},
+ {group, ecdsa},
+ {group, ed25519},
+ {group, ed448},
+ {group, rsa},
+
+ {group, md4},
{group, md5},
{group, ripemd160},
- {group, sha},
{group, sha224},
{group, sha256},
{group, sha384},
- {group, sha512},
{group, sha3_224},
{group, sha3_256},
{group, sha3_384},
{group, sha3_512},
- {group, blake2b},
- {group, blake2s},
- {group, rsa},
- {group, dss},
- {group, ecdsa},
- {group, ed25519},
- {group, ed448},
+ {group, sha512},
+ {group, sha},
+
{group, dh},
{group, ecdh},
{group, srp},
- {group, des_cbc},
- {group, des_cfb},
- {group, des3_cbc},
- {group, des3_cbf},
- {group, des3_cfb},
- {group, des_ede3},
- {group, blowfish_cbc},
- {group, blowfish_ecb},
- {group, blowfish_cfb64},
- {group, blowfish_ofb64},
- {group, aes_cbc128},
- {group, aes_cfb8},
- {group, aes_cfb128},
- {group, aes_cbc256},
- {group, aes_ige256},
- {group, rc2_cbc},
- {group, rc4},
- {group, aes_ctr},
+
+ {group, aes_cbc},
{group, aes_ccm},
{group, aes_gcm},
{group, chacha20_poly1305},
{group, chacha20},
+ {group, des3_cfb},
+ {group, aes_cbc128},
+ {group, aes_cbc256},
+ {group, aes_cfb128},
+ {group, aes_cfb8},
+ {group, aes_ctr},
+ {group, aes_ige256},
+ {group, blowfish_cbc},
+ {group, blowfish_cfb64},
+ {group, blowfish_ecb},
+ {group, blowfish_ofb64},
+ {group, des3_cbc},
+ {group, des3_cbf},
+ {group, des_cbc},
+ {group, des_cfb},
+ {group, des_ede3},
{group, poly1305},
- {group, aes_cbc}]},
- {fips, [], [{group, no_md4},
+ {group, rc2_cbc},
+ {group, rc4}
+ ]},
+ {fips, [], [
+ {group, no_blake2b},
+ {group, no_blake2s},
+ {group, dss},
+ {group, ecdsa},
+ {group, no_ed25519},
+ {group, no_ed448},
+ {group, rsa},
+
+ {group, no_md4},
{group, no_md5},
{group, no_ripemd160},
{group, sha},
@@ -100,37 +114,36 @@ groups() ->
{group, sha256},
{group, sha384},
{group, sha512},
- {group, rsa},
- {group, dss},
- {group, ecdsa},
- {group, no_ed25519},
- {group, no_ed448},
+
{group, dh},
{group, ecdh},
{group, no_srp},
- {group, no_des_cbc},
- {group, no_des_cfb},
- {group, des3_cbc},
- {group, des3_cbf},
+
+ {group, aes_cbc},
+ {group, aes_ccm},
+ {group, aes_gcm},
+ {group, no_chacha20_poly1305},
+ {group, no_chacha20},
{group, des3_cfb},
- {group, des_ede3},
- {group, no_blowfish_cbc},
- {group, no_blowfish_ecb},
- {group, no_blowfish_cfb64},
- {group, no_blowfish_ofb64},
{group, aes_cbc128},
- {group, no_aes_cfb8},
- {group, no_aes_cfb128},
{group, aes_cbc256},
+ {group, no_aes_cfb128},
+ {group, no_aes_cfb8},
+ {group, aes_ctr},
{group, no_aes_ige256},
+ {group, no_blowfish_cbc},
+ {group, no_blowfish_cfb64},
+ {group, no_blowfish_ecb},
+ {group, no_blowfish_ofb64},
+ {group, des3_cbc},
+ {group, des3_cbf},
+ {group, no_des_cbc},
+ {group, no_des_cfb},
+ {group, des_ede3},
+ {group, no_poly1305},
{group, no_rc2_cbc},
- {group, no_rc4},
- {group, aes_ctr},
- {group, aes_ccm},
- {group, aes_gcm},
- {group, no_chacha20_poly1305},
- {group, no_chacha20},
- {group, aes_cbc}]},
+ {group, no_rc4}
+ ]},
{md4, [], [hash]},
{md5, [], [hash, hmac]},
{ripemd160, [], [hash]},
@@ -145,6 +158,8 @@ groups() ->
{sha3_512, [], [hash, hmac]},
{blake2b, [], [hash, hmac]},
{blake2s, [], [hash, hmac]},
+ {no_blake2b, [], [no_hash, no_hmac]},
+ {no_blake2s, [], [no_hash, no_hmac]},
{rsa, [], [sign_verify,
public_encrypt,
private_encrypt,
@@ -166,31 +181,32 @@ groups() ->
compute_bug]},
{ecdh, [], [use_all_elliptic_curves, compute, generate]},
{srp, [], [generate_compute]},
- {des_cbc, [], [block]},
- {des_cfb, [], [block]},
- {des3_cbc,[], [block]},
- {des_ede3,[], [block]},
- {des3_cbf,[], [block]},
- {des3_cfb,[], [block]},
- {rc2_cbc,[], [block]},
- {aes_cbc128,[], [block, cmac]},
- {aes_cfb8,[], [block]},
- {aes_cfb128,[], [block]},
- {aes_cbc256,[], [block, cmac]},
- {aes_ecb,[], [block]},
+ {des_cbc, [], [block, api_ng, api_ng_one_shot, api_ng_tls]},
+ {des_cfb, [], [block, api_ng, api_ng_one_shot, api_ng_tls]},
+ {des3_cbc,[], [block, api_ng, api_ng_one_shot, api_ng_tls]},
+ {des_ede3,[], [block, api_ng, api_ng_one_shot, api_ng_tls]},
+ {des3_cbf,[], [block, api_ng, api_ng_one_shot, api_ng_tls]},
+ {des3_cfb,[], [block, api_ng, api_ng_one_shot, api_ng_tls]},
+ {rc2_cbc,[], [block, api_ng, api_ng_one_shot, api_ng_tls]},
+ {aes_cbc128,[], [block, api_ng, api_ng_one_shot, api_ng_tls, cmac]},
+ {aes_cfb8,[], [block, api_ng, api_ng_one_shot, api_ng_tls]},
+ {aes_cfb128,[], [block, api_ng, api_ng_one_shot, api_ng_tls]},
+ {aes_cbc256,[], [block, api_ng, api_ng_one_shot, api_ng_tls, cmac]},
+ {aes_ecb,[], [block, api_ng, api_ng_one_shot, api_ng_tls]},
{aes_ige256,[], [block]},
- {blowfish_cbc, [], [block]},
- {blowfish_ecb, [], [block]},
- {blowfish_cfb64, [], [block]},
- {blowfish_ofb64,[], [block]},
- {rc4, [], [stream]},
- {aes_ctr, [], [stream]},
+ {blowfish_cbc, [], [block, api_ng, api_ng_one_shot, api_ng_tls]},
+ {blowfish_ecb, [], [block, api_ng, api_ng_one_shot, api_ng_tls]},
+ {blowfish_cfb64, [], [block, api_ng, api_ng_one_shot, api_ng_tls]},
+ {blowfish_ofb64,[], [block, api_ng, api_ng_one_shot, api_ng_tls]},
+ {rc4, [], [stream, api_ng, api_ng_one_shot, api_ng_tls]},
+ {aes_ctr, [], [stream, api_ng, api_ng_one_shot, api_ng_tls]},
{aes_ccm, [], [aead]},
{aes_gcm, [], [aead]},
{chacha20_poly1305, [], [aead]},
- {chacha20, [], [stream]},
+ {chacha20, [], [stream, api_ng, api_ng_one_shot, api_ng_tls]},
{poly1305, [], [poly1305]},
- {aes_cbc, [], [block]},
+ {no_poly1305, [], [no_poly1305]},
+ {aes_cbc, [], [block, api_ng, api_ng_one_shot, api_ng_tls]},
{no_aes_cfb8,[], [no_support, no_block]},
{no_aes_cfb128,[], [no_support, no_block]},
{no_md4, [], [no_support, no_hash]},
@@ -412,11 +428,19 @@ poly1305(Config) ->
end, proplists:get_value(poly1305, Config)).
%%--------------------------------------------------------------------
+no_poly1305() ->
+ [{doc, "Test disabled poly1305 function"}].
+no_poly1305(Config) ->
+ Type = ?config(type, Config),
+ Key = <<133,214,190,120,87,85,109,51,127,68,82,254,66,213,6,168,1,
+ 3,128,138,251,13,178,253,74,191,246,175,65,73,245,27>>,
+ Txt = <<"Cryptographic Forum Research Group">>,
+ notsup(fun crypto:poly1305/2, [Key,Txt]).
+
+%%--------------------------------------------------------------------
block() ->
[{doc, "Test block ciphers"}].
block(Config) when is_list(Config) ->
- Fips = proplists:get_bool(fips, Config),
- Type = ?config(type, Config),
Blocks = lazy_eval(proplists:get_value(block, Config)),
lists:foreach(fun block_cipher/1, Blocks),
lists:foreach(fun block_cipher/1, block_iolistify(Blocks)),
@@ -439,6 +463,156 @@ no_block(Config) when is_list(Config) ->
notsup(fun crypto:block_encrypt/N, Args),
notsup(fun crypto:block_decrypt/N, Args).
%%--------------------------------------------------------------------
+api_ng() ->
+ [{doc, "Test new api"}].
+
+api_ng(Config) when is_list(Config) ->
+ Blocks = lazy_eval(proplists:get_value(block, Config, [])),
+ Streams = lazy_eval(proplists:get_value(stream, Config, [])),
+ lists:foreach(fun api_ng_cipher_increment/1, Blocks++Streams).
+
+
+api_ng_cipher_increment({Type, Key, PlainTexts}=_X) ->
+ ct:log("~p",[_X]),
+ api_ng_cipher_increment({Type, Key, <<>>, PlainTexts});
+
+api_ng_cipher_increment({Type, Key, IV, PlainTexts}=_X) ->
+ ct:log("~p",[_X]),
+ api_ng_cipher_increment({Type, Key, IV, PlainTexts, undefined});
+
+api_ng_cipher_increment({Type, Key, IV, PlainText0, ExpectedEncText}=_X) ->
+ ct:log("~p",[_X]),
+ PlainTexts = iolistify(PlainText0),
+ RefEnc = crypto:crypto_init(Type, Key, IV, true),
+ RefDec = crypto:crypto_init(Type, Key, IV, false),
+ EncTexts = api_ng_cipher_increment_loop(RefEnc, PlainTexts),
+ Enc = iolist_to_binary(EncTexts),
+ case ExpectedEncText of
+ undefined ->
+ ok;
+ Enc ->
+ ok;
+ _ ->
+ ct:log("encode~nIn: ~p~nExpected: ~p~nEnc: ~p~n", [{Type,Key,IV,PlainTexts}, ExpectedEncText, Enc]),
+ ct:fail("api_ng_cipher_increment (encode)",[])
+ end,
+ Plain = iolist_to_binary(PlainTexts),
+ case iolist_to_binary(api_ng_cipher_increment_loop(RefDec, EncTexts)) of
+ Plain ->
+ ok;
+ OtherPT ->
+ ct:log("decode~nIn: ~p~nExpected: ~p~nDec: ~p~n", [{Type,Key,IV,EncTexts}, Plain, OtherPT]),
+ ct:fail("api_ng_cipher_increment (encode)",[])
+ end.
+
+
+api_ng_cipher_increment_loop(Ref, InTexts) ->
+ lists:map(fun(Txt) ->
+ try crypto:crypto_update(Ref, Txt)
+ of
+ Bin when is_binary(Bin) ->
+ Bin
+ catch
+ error:Error ->
+ ct:pal("Txt = ~p",[Txt]),
+ ct:fail("~p",[Error])
+ end
+ end, InTexts).
+
+%%--------------------------------------------------------------------
+api_ng_one_shot() ->
+ [{doc, "Test new api"}].
+
+api_ng_one_shot(Config) when is_list(Config) ->
+ Blocks = lazy_eval(proplists:get_value(block, Config, [])),
+ Streams = lazy_eval(proplists:get_value(stream, Config, [])),
+ lists:foreach(fun do_api_ng_one_shot/1, Blocks++Streams).
+
+do_api_ng_one_shot({Type, Key, PlainTexts}=_X) ->
+ ct:log("~p",[_X]),
+ do_api_ng_one_shot({Type, Key, <<>>, PlainTexts});
+
+do_api_ng_one_shot({Type, Key, IV, PlainTexts}=_X) ->
+ ct:log("~p",[_X]),
+ do_api_ng_one_shot({Type, Key, IV, PlainTexts, undefined});
+
+do_api_ng_one_shot({Type, Key, IV, PlainText0, ExpectedEncText}=_X) ->
+ ct:log("~p",[_X]),
+ PlainText = iolist_to_binary(PlainText0),
+ EncTxt = crypto:crypto_one_shot(Type, Key, IV, PlainText, true),
+ case ExpectedEncText of
+ undefined ->
+ ok;
+ EncTxt ->
+ ok;
+ _ ->
+ ct:log("encode~nIn: ~p~nExpected: ~p~nEnc: ~p~n", [{Type,Key,IV,PlainText}, ExpectedEncText, EncTxt]),
+ ct:fail("api_ng_one_shot (encode)",[])
+ end,
+ case crypto:crypto_one_shot(Type, Key, IV, EncTxt, false) of
+ PlainText ->
+ ok;
+ OtherPT ->
+ ct:log("decode~nIn: ~p~nExpected: ~p~nDec: ~p~n", [{Type,Key,IV,EncTxt}, PlainText, OtherPT]),
+ ct:fail("api_ng_one_shot (decode)",[])
+ end.
+
+%%--------------------------------------------------------------------
+api_ng_tls() ->
+ [{doc, "Test special tls api"}].
+
+api_ng_tls(Config) when is_list(Config) ->
+ Blocks = lazy_eval(proplists:get_value(block, Config, [])),
+ Streams = lazy_eval(proplists:get_value(stream, Config, [])),
+ lists:foreach(fun do_api_ng_tls/1, Blocks++Streams).
+
+
+do_api_ng_tls({Type, Key, PlainTexts}=_X) ->
+ ct:log("~p",[_X]),
+ do_api_ng_tls({Type, Key, <<>>, PlainTexts});
+
+do_api_ng_tls({Type, Key, IV, PlainTexts}=_X) ->
+ ct:log("~p",[_X]),
+ do_api_ng_tls({Type, Key, IV, PlainTexts, undefined});
+
+do_api_ng_tls({Type, Key, IV, PlainText0, ExpectedEncText}=_X) ->
+ ct:log("~p",[_X]),
+ PlainText = iolist_to_binary(PlainText0),
+ Renc = crypto:crypto_init_dyn_iv(Type, Key, true),
+ Rdec = crypto:crypto_init_dyn_iv(Type, Key, false),
+ EncTxt = crypto:crypto_update_dyn_iv(Renc, PlainText, IV),
+ case ExpectedEncText of
+ undefined ->
+ ok;
+ EncTxt ->
+ %% Now check that the state is NOT updated:
+ case crypto:crypto_update_dyn_iv(Renc, PlainText, IV) of
+ EncTxt ->
+ ok;
+ EncTxt2 ->
+ ct:log("2nd encode~nIn: ~p~nExpected: ~p~nEnc: ~p~n", [{Type,Key,IV,PlainText}, EncTxt, EncTxt2]),
+ ct:fail("api_ng_tls (second encode)",[])
+ end;
+ OtherEnc ->
+ ct:log("1st encode~nIn: ~p~nExpected: ~p~nEnc: ~p~n", [{Type,Key,IV,PlainText}, ExpectedEncText, OtherEnc]),
+ ct:fail("api_ng_tls (encode)",[])
+ end,
+ case crypto:crypto_update_dyn_iv(Rdec, EncTxt, IV) of
+ PlainText ->
+ %% Now check that the state is NOT updated:
+ case crypto:crypto_update_dyn_iv(Rdec, EncTxt, IV) of
+ PlainText ->
+ ok;
+ PlainText2 ->
+ ct:log("2nd decode~nIn: ~p~nExpected: ~p~nDec: ~p~n", [{Type,Key,IV,EncTxt}, PlainText, PlainText2]),
+ ct:fail("api_ng_tls (second decode)",[])
+ end;
+ OtherPT ->
+ ct:log("1st decode~nIn: ~p~nExpected: ~p~nDec: ~p~n", [{Type,Key,IV,EncTxt}, PlainText, OtherPT]),
+ ct:fail("api_ng_tlst (decode)",[])
+ end.
+
+%%--------------------------------------------------------------------
no_aead() ->
[{doc, "Test disabled aead ciphers"}].
no_aead(Config) when is_list(Config) ->
@@ -774,6 +948,7 @@ cmac_check({Type, Key, Text, Size, CMac}) ->
ct:fail({{crypto, cmac, [Type, Key, Text, Size]}, {expected, ExpCMac}, {got, Other}})
end.
+
block_cipher({Type, Key, PlainText}) ->
Plain = iolist_to_binary(PlainText),
CipherText = crypto:block_encrypt(Type, Key, PlainText),
@@ -851,46 +1026,51 @@ block_cipher_increment(Type, Key, IV0, IV, [PlainText | PlainTexts], Plain, Ciph
stream_cipher({Type, Key, PlainText}) ->
Plain = iolist_to_binary(PlainText),
- State = crypto:stream_init(Type, Key),
- {_, CipherText} = crypto:stream_encrypt(State, PlainText),
- case crypto:stream_decrypt(State, CipherText) of
+ StateE = crypto:stream_init(Type, Key),
+ StateD = crypto:stream_init(Type, Key),
+ {_, CipherText} = crypto:stream_encrypt(StateE, PlainText),
+ case crypto:stream_decrypt(StateD, CipherText) of
{_, Plain} ->
ok;
Other ->
- ct:fail({{crypto, stream_decrypt, [State, CipherText]}, {expected, PlainText}, {got, Other}})
+ ct:fail({{crypto, stream_decrypt, [StateD, CipherText]}, {expected, PlainText}, {got, Other}})
end;
stream_cipher({Type, Key, IV, PlainText}) ->
Plain = iolist_to_binary(PlainText),
- State = crypto:stream_init(Type, Key, IV),
- {_, CipherText} = crypto:stream_encrypt(State, PlainText),
- case crypto:stream_decrypt(State, CipherText) of
+ StateE = crypto:stream_init(Type, Key, IV),
+ StateD = crypto:stream_init(Type, Key, IV),
+ {_, CipherText} = crypto:stream_encrypt(StateE, PlainText),
+ case crypto:stream_decrypt(StateD, CipherText) of
{_, Plain} ->
ok;
Other ->
- ct:fail({{crypto, stream_decrypt, [State, CipherText]}, {expected, PlainText}, {got, Other}})
+ ct:fail({{crypto, stream_decrypt, [StateD, CipherText]}, {expected, PlainText}, {got, Other}})
end;
stream_cipher({Type, Key, IV, PlainText, CipherText}) ->
Plain = iolist_to_binary(PlainText),
- State = crypto:stream_init(Type, Key, IV),
- case crypto:stream_encrypt(State, PlainText) of
+ StateE = crypto:stream_init(Type, Key, IV),
+ StateD = crypto:stream_init(Type, Key, IV),
+ case crypto:stream_encrypt(StateE, PlainText) of
{_, CipherText} ->
ok;
{_, Other0} ->
- ct:fail({{crypto, stream_encrypt, [State, Type, Key, IV, Plain]}, {expected, CipherText}, {got, Other0}})
+ ct:fail({{crypto, stream_encrypt, [StateE, Type, Key, IV, Plain]}, {expected, CipherText}, {got, Other0}})
end,
- case crypto:stream_decrypt(State, CipherText) of
+ case crypto:stream_decrypt(StateD, CipherText) of
{_, Plain} ->
ok;
Other1 ->
- ct:fail({{crypto, stream_decrypt, [State, CipherText]}, {expected, PlainText}, {got, Other1}})
+ ct:fail({{crypto, stream_decrypt, [StateD, CipherText]}, {expected, PlainText}, {got, Other1}})
end.
stream_cipher_incment({Type, Key, PlainTexts}) ->
- State = crypto:stream_init(Type, Key),
- stream_cipher_incment_loop(State, State, PlainTexts, [], iolist_to_binary(PlainTexts));
+ StateE = crypto:stream_init(Type, Key),
+ StateD = crypto:stream_init(Type, Key),
+ stream_cipher_incment_loop(StateE, StateD, PlainTexts, [], iolist_to_binary(PlainTexts));
stream_cipher_incment({Type, Key, IV, PlainTexts}) ->
- State = crypto:stream_init(Type, Key, IV),
- stream_cipher_incment_loop(State, State, PlainTexts, [], iolist_to_binary(PlainTexts));
+ StateE = crypto:stream_init(Type, Key, IV),
+ StateD = crypto:stream_init(Type, Key, IV),
+ stream_cipher_incment_loop(StateE, StateD, PlainTexts, [], iolist_to_binary(PlainTexts));
stream_cipher_incment({Type, Key, IV, PlainTexts, _CipherText}) ->
stream_cipher_incment({Type, Key, IV, PlainTexts}).
diff --git a/lib/crypto/vsn.mk b/lib/crypto/vsn.mk
index 6a91244715..deba17fb66 100644
--- a/lib/crypto/vsn.mk
+++ b/lib/crypto/vsn.mk
@@ -1 +1 @@
-CRYPTO_VSN = 4.4
+CRYPTO_VSN = 4.4.1
diff --git a/lib/dialyzer/doc/src/notes.xml b/lib/dialyzer/doc/src/notes.xml
index 3cf776e566..bc422c43a0 100644
--- a/lib/dialyzer/doc/src/notes.xml
+++ b/lib/dialyzer/doc/src/notes.xml
@@ -32,6 +32,36 @@
<p>This document describes the changes made to the Dialyzer
application.</p>
+<section><title>Dialyzer 3.3.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p> Fix a bug that caused Dialyzer to crash when
+ analyzing a contract with a module name differing from
+ the analyzed module's name. The bug was introduced in
+ Erlang/OTP 18. </p>
+ <p>
+ Own Id: OTP-15562 Aux Id: ERL-845 </p>
+ </item>
+ <item>
+ <p> Fix a bug in the handling of the <c>Key</c> argument
+ of <c>lists:{keysearch, keyfind, keymember}</c>. </p>
+ <p>
+ Own Id: OTP-15570</p>
+ </item>
+ <item>
+ <p>Optimize (again) Dialyzer's handling of
+ left-associative use of <c>andalso</c> and <c>orelse</c>
+ in guards.</p>
+ <p>
+ Own Id: OTP-15577 Aux Id: ERL-851, PR-2141, PR-1944 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Dialyzer 3.3.1</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/dialyzer/vsn.mk b/lib/dialyzer/vsn.mk
index 98ab533a58..7221993963 100644
--- a/lib/dialyzer/vsn.mk
+++ b/lib/dialyzer/vsn.mk
@@ -1 +1 @@
-DIALYZER_VSN = 3.3.1
+DIALYZER_VSN = 3.3.2
diff --git a/lib/diameter/doc/src/notes.xml b/lib/diameter/doc/src/notes.xml
index cc92bd99f0..5777225ae7 100644
--- a/lib/diameter/doc/src/notes.xml
+++ b/lib/diameter/doc/src/notes.xml
@@ -43,6 +43,41 @@ first.</p>
<!-- ===================================================================== -->
+<section><title>diameter 2.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix failure of incoming answer message with faulty
+ Experimental-Result-Code. Failure to decode the AVP
+ resulted in an uncaught exception, with no no
+ handle_answer/error callback as a consequence.</p>
+ <p>
+ Own Id: OTP-15569 Aux Id: ERIERL-302 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add spawn_opt MFA configuration to allow a callback to
+ spawn a handler process for an incoming Diameter request
+ on an an arbitrary node. Module diameter_dist provides a
+ route_session/2 that can be used to distribute requests
+ based on Session-Id, although this module is currently
+ only documented in the module itself and may change.</p>
+ <p>
+ Own Id: OTP-15398</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>diameter 2.1.6</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/diameter/vsn.mk b/lib/diameter/vsn.mk
index 8c75c9e55e..a900e8f28e 100644
--- a/lib/diameter/vsn.mk
+++ b/lib/diameter/vsn.mk
@@ -17,5 +17,5 @@
# %CopyrightEnd%
APPLICATION = diameter
-DIAMETER_VSN = 2.1.6
+DIAMETER_VSN = 2.2
APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN)
diff --git a/lib/edoc/doc/src/notes.xml b/lib/edoc/doc/src/notes.xml
index e818887eb8..145856bcaa 100644
--- a/lib/edoc/doc/src/notes.xml
+++ b/lib/edoc/doc/src/notes.xml
@@ -32,6 +32,22 @@
<p>This document describes the changes made to the EDoc
application.</p>
+<section><title>Edoc 0.10</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p> Print a helpful message explaining that adding
+ <c>{preprocess, true}</c> can help if reading a source
+ file fails. </p>
+ <p>
+ Own Id: OTP-15605 Aux Id: ERL-841 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Edoc 0.9.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/edoc/vsn.mk b/lib/edoc/vsn.mk
index 0b3636f030..b6e1422623 100644
--- a/lib/edoc/vsn.mk
+++ b/lib/edoc/vsn.mk
@@ -1 +1 @@
-EDOC_VSN = 0.9.4
+EDOC_VSN = 0.10
diff --git a/lib/erl_docgen/doc/src/notes.xml b/lib/erl_docgen/doc/src/notes.xml
index 97c842a324..54f0a36b27 100644
--- a/lib/erl_docgen/doc/src/notes.xml
+++ b/lib/erl_docgen/doc/src/notes.xml
@@ -31,7 +31,31 @@
</header>
<p>This document describes the changes made to the <em>erl_docgen</em> application.</p>
- <section><title>Erl_Docgen 0.8.1</title>
+ <section><title>Erl_Docgen 0.9</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The HTML reference documentation shows the OTP version
+ where modules and functions were first introduced.
+ Versions older than R13B04 are not shown.</p>
+ <p>
+ Own Id: OTP-15460</p>
+ </item>
+ <item>
+ <p>
+ Make html documentation of C functions with many
+ arguments more readable.</p>
+ <p>
+ Own Id: OTP-15637 Aux Id: PR-2160 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erl_Docgen 0.8.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/erl_docgen/vsn.mk b/lib/erl_docgen/vsn.mk
index 3b2f6db6a1..6321f229dd 100644
--- a/lib/erl_docgen/vsn.mk
+++ b/lib/erl_docgen/vsn.mk
@@ -1 +1 @@
-ERL_DOCGEN_VSN = 0.8.1
+ERL_DOCGEN_VSN = 0.9
diff --git a/lib/erl_interface/doc/src/ei.xml b/lib/erl_interface/doc/src/ei.xml
index 2bdb390644..f081ca926a 100644
--- a/lib/erl_interface/doc/src/ei.xml
+++ b/lib/erl_interface/doc/src/ei.xml
@@ -736,7 +736,7 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_init(void)</nametext></name>
+ <name since="OTP 21.3"><ret>int</ret><nametext>ei_init(void)</nametext></name>
<fsummary>Initialize the ei library.</fsummary>
<desc>
<p>Initialize the <c>ei</c> library. This function should be called once
diff --git a/lib/erl_interface/doc/src/ei_connect.xml b/lib/erl_interface/doc/src/ei_connect.xml
index df40973270..795f1249b3 100644
--- a/lib/erl_interface/doc/src/ei_connect.xml
+++ b/lib/erl_interface/doc/src/ei_connect.xml
@@ -412,7 +412,7 @@ typedef struct {
</func>
<func>
- <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_close_connection(int fd)</nametext></name>
+ <name since="OTP 21.3"><ret>int</ret><nametext>ei_close_connection(int fd)</nametext></name>
<fsummary>Close a connection.</fsummary>
<desc>
<p>Closes a previously opened connection or listen socket.</p>
@@ -472,9 +472,9 @@ fd = ei_xconnect(&ec, &addr, ALIVE);
<func>
<name since=""><ret>int</ret><nametext>ei_connect_init(ei_cnode* ec, const char* this_node_name, const char *cookie, short creation)</nametext></name>
- <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_connect_init_ussi(ei_cnode* ec, const char* this_node_name, const char *cookie, short creation, ei_socket_callbacks *cbs, int cbs_sz, void *setup_context)</nametext></name>
+ <name since="OTP 21.3"><ret>int</ret><nametext>ei_connect_init_ussi(ei_cnode* ec, const char* this_node_name, const char *cookie, short creation, ei_socket_callbacks *cbs, int cbs_sz, void *setup_context)</nametext></name>
<name since=""><ret>int</ret><nametext>ei_connect_xinit(ei_cnode* ec, const char *thishostname, const char *thisalivename, const char *thisnodename, Erl_IpAddr thisipaddr, const char *cookie, short creation)</nametext></name>
- <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname, const char *thisalivename, const char *thisnodename, Erl_IpAddr thisipaddr, const char *cookie, short creation, ei_socket_callbacks *cbs, int cbs_sz, void *setup_context)</nametext></name>
+ <name since="OTP 21.3"><ret>int</ret><nametext>ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname, const char *thisalivename, const char *thisnodename, Erl_IpAddr thisipaddr, const char *cookie, short creation, ei_socket_callbacks *cbs, int cbs_sz, void *setup_context)</nametext></name>
<fsummary>Initialize for a connection.</fsummary>
<desc>
<p>Initializes the <c>ec</c> structure, to
@@ -595,8 +595,8 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
- <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_listen(ei_cnode *ec, int *port, int backlog)</nametext></name>
- <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_xlisten(ei_cnode *ec, Erl_IpAddr adr, int *port, int backlog)</nametext></name>
+ <name since="OTP 21.3"><ret>int</ret><nametext>ei_listen(ei_cnode *ec, int *port, int backlog)</nametext></name>
+ <name since="OTP 21.3"><ret>int</ret><nametext>ei_xlisten(ei_cnode *ec, Erl_IpAddr adr, int *port, int backlog)</nametext></name>
<fsummary>Create a listen socket.</fsummary>
<desc>
<p>Used by a server process to setup a listen socket which
diff --git a/lib/erl_interface/doc/src/notes.xml b/lib/erl_interface/doc/src/notes.xml
index 07ddd82718..32d28b853b 100644
--- a/lib/erl_interface/doc/src/notes.xml
+++ b/lib/erl_interface/doc/src/notes.xml
@@ -31,6 +31,43 @@
</header>
<p>This document describes the changes made to the Erl_interface application.</p>
+<section><title>Erl_Interface 3.11.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed two bugs in the <c>erl_call</c> program. A missing
+ initialization (introduced in <c>erl_interface-3.11</c>)
+ which either caused a crash or failure to connect to or
+ start a node, and an incorrectly calculated timeout which
+ could cause failure to start an erlang node. These bugs
+ only caused failures on some platforms.</p>
+ <p>
+ Own Id: OTP-15676 Aux Id: OTP-15442, ERL-881 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erl_Interface 3.11</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Support for plugin of a <seealso
+ marker="ei_connect#ussi">user supplied socket
+ implementation</seealso> has been added.</p>
+ <p>
+ Own Id: OTP-15442 Aux Id: ERIERL-258 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erl_Interface 3.10.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/erl_interface/src/prog/ei_fake_prog.c b/lib/erl_interface/src/prog/ei_fake_prog.c
index c7a16dc7c4..158464b385 100644
--- a/lib/erl_interface/src/prog/ei_fake_prog.c
+++ b/lib/erl_interface/src/prog/ei_fake_prog.c
@@ -98,11 +98,18 @@ int main(void)
EI_ULONGLONG ulonglongx = 0;
#endif
erlang_char_encoding enc;
+ ei_socket_callbacks cbs;
intx = erl_errno;
+ ei_init();
+
+ ei_close_connection(intx);
+
ei_connect_init(&xec, charp, charp, creation);
+ ei_connect_init_ussi(&xec, charp, charp, creation, &cbs, sizeof(cbs), NULL);
ei_connect_xinit (&xec, charp, charp, charp, thisipaddr, charp, creation);
+ ei_connect_xinit_ussi(&xec, charp, charp, charp, thisipaddr, charp, creation, &cbs, sizeof(cbs), NULL);
ei_connect(&xec, charp);
ei_xconnect (&xec, thisipaddr, charp);
@@ -121,6 +128,8 @@ int main(void)
ei_publish(&xec, intx);
ei_accept(&xec, intx, &conp);
ei_unpublish(&xec);
+ ei_listen(&xec, intp, intx);
+ ei_xlisten(&xec, thisipaddr, intp, intx);
ei_thisnodename(&xec);
ei_thishostname(&xec);
@@ -187,7 +196,7 @@ int main(void)
ei_decode_char(charp, intp, charp);
ei_decode_string(charp, intp, charp);
ei_decode_atom(charp, intp, charp);
- ei_decode_atom_as(charp, intp, charp, MAXATOMLEN_UTF8, ERLANG_WHATEVER, &enc, &enc);
+ ei_decode_atom_as(charp, intp, charp, MAXATOMLEN_UTF8, ERLANG_UTF8, &enc, &enc);
ei_decode_binary(charp, intp, (void *)0, longp);
ei_decode_fun(charp, intp, &efun);
free_fun(&efun);
diff --git a/lib/erl_interface/src/prog/erl_call.c b/lib/erl_interface/src/prog/erl_call.c
index 52ad6885e8..ab91157035 100644
--- a/lib/erl_interface/src/prog/erl_call.c
+++ b/lib/erl_interface/src/prog/erl_call.c
@@ -88,10 +88,6 @@
#include "ei_resolve.h"
#include "erl_start.h" /* FIXME remove dependency */
-#ifdef __WIN32__
-static void initWinSock(void);
-#endif
-
/*
* Some nice global variables
* (I don't think "nice" is the right word actually... -gordon)
@@ -157,6 +153,8 @@ int erl_call(int argc, char **argv)
char* progname = argv[0];
ei_cnode ec;
+ ei_init();
+
/* Get the command line options */
while (i < argc) {
if (argv[i][0] != '-') {
@@ -317,14 +315,6 @@ int erl_call(int argc, char **argv)
struct in_addr h_ipadr;
char* ct;
-#ifdef __WIN32__
- /*
- * FIXME Extremly ugly, but needed to get ei_gethostbyname() below
- * to work.
- */
- initWinSock();
-#endif
-
/* gethostname requires len to be max(hostname) + 1 */
if (gethostname(h_hostname, EI_MAXHOSTNAMELEN+1) < 0) {
fprintf(stderr,"erl_call: failed to get host name: %d\n", errno);
@@ -857,46 +847,6 @@ static void usage(const char *progname) {
exit(0);
}
-
-/***************************************************************************
- *
- * OS specific functions
- *
- ***************************************************************************/
-
-#ifdef __WIN32__
-/*
- * FIXME This should not be here. This is a quick fix to make erl_call
- * work at all on Windows NT.
- */
-static void initWinSock(void)
-{
- WORD wVersionRequested;
- WSADATA wsaData;
- int err;
- static int initialized;
-
- wVersionRequested = MAKEWORD(1, 1);
- if (!initialized) {
- initialized = 1;
- err = WSAStartup(wVersionRequested, &wsaData);
-
- if (err != 0) {
- fprintf(stderr,"erl_call: "
- "Can't initialize windows sockets: %d\n", err);
- }
-
- if ( LOBYTE( wsaData.wVersion ) != 1 ||
- HIBYTE( wsaData.wVersion ) != 1 ) {
- fprintf(stderr,"erl_call: This version of "
- "windows sockets not supported\n");
- WSACleanup();
- }
- }
-}
-#endif
-
-
/***************************************************************************
*
* Utility functions
diff --git a/lib/erl_interface/src/prog/erl_start.c b/lib/erl_interface/src/prog/erl_start.c
index ba495ac818..b7aa451946 100644
--- a/lib/erl_interface/src/prog/erl_start.c
+++ b/lib/erl_interface/src/prog/erl_start.c
@@ -657,7 +657,7 @@ static int wait_for_erlang(int sockd, int magic, struct timeval *timeout)
gettimeofday(&now,NULL);
to.tv_sec = stop_time.tv_sec - now.tv_sec;
to.tv_usec = stop_time.tv_usec - now.tv_usec;
- while ((to.tv_usec <= 0) && (to.tv_sec >= 0)) {
+ while ((to.tv_usec < 0) && (to.tv_sec > 0)) {
to.tv_usec += 1000000;
to.tv_sec--;
}
diff --git a/lib/erl_interface/test/Makefile b/lib/erl_interface/test/Makefile
index 94f4b422d6..f8f2ef0156 100644
--- a/lib/erl_interface/test/Makefile
+++ b/lib/erl_interface/test/Makefile
@@ -33,6 +33,7 @@ MODULES= \
ei_format_SUITE \
ei_print_SUITE \
ei_tmo_SUITE \
+ erl_call_SUITE \
erl_connect_SUITE \
erl_global_SUITE \
erl_eterm_SUITE \
diff --git a/lib/erl_interface/test/erl_call_SUITE.erl b/lib/erl_interface/test/erl_call_SUITE.erl
new file mode 100644
index 0000000000..9e2b2e4251
--- /dev/null
+++ b/lib/erl_interface/test/erl_call_SUITE.erl
@@ -0,0 +1,96 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(erl_call_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+
+-export([all/0, smoke/1]).
+
+all() ->
+ [smoke].
+
+smoke(Config) when is_list(Config) ->
+ ErlCall = find_erl_call(),
+ NameSwitch = case net_kernel:longnames() of
+ true ->
+ "-name";
+ false ->
+ "-sname"
+ end,
+ Name = atom_to_list(?MODULE)
+ ++ "-"
+ ++ integer_to_list(erlang:system_time(microsecond)),
+
+ ArgsList = ["-s", "-a", "erlang node", NameSwitch, Name],
+ io:format("erl_call: \"~ts\"\n~nargs list: ~p~n", [ErlCall, ArgsList]),
+ CmdRes = get_smoke_port_res(open_port({spawn_executable, ErlCall},
+ [{args, ArgsList}, eof]), []),
+ io:format("CmdRes: ~p~n", [CmdRes]),
+
+ [_, Hostname] = string:lexemes(atom_to_list(node()), "@"),
+ NodeName = list_to_atom(Name ++ "@" ++ Hostname),
+ io:format("NodeName: ~p~n~n", [NodeName]),
+
+ pong = net_adm:ping(NodeName),
+ rpc:cast(NodeName, erlang, halt, []),
+ NodeName = list_to_atom(string:trim(CmdRes, both, "'")),
+ ok.
+
+%
+% Utility functions...
+%
+
+find_erl_call() ->
+ ErlCallName = case os:type() of
+ {win32, _} -> "erl_call.exe";
+ _ -> "erl_call"
+ end,
+ LibDir = code:lib_dir(erl_interface),
+ InstalledErlCall = filename:join([LibDir, "bin", ErlCallName]),
+ TargetDir = erlang:system_info(system_architecture),
+ TargetErlCall = filename:join([LibDir, "bin", TargetDir, ErlCallName]),
+
+ try
+ lists:foreach(fun (F) ->
+ io:format("Checking: \"~ts\"~n", [F]),
+ case file:read_file_info(F) of
+ {ok, _} ->
+ throw(F);
+ _ ->
+ ok
+ end
+ end,
+ [InstalledErlCall, TargetErlCall]),
+ exit({missing, erl_call})
+ catch
+ throw:ErlCall ->
+ ErlCall
+ end.
+
+get_smoke_port_res(Port, Acc) when is_port(Port) ->
+ receive
+ {Port, {data, Data}} ->
+ get_smoke_port_res(Port, [Acc|Data]);
+ {Port, eof} ->
+ lists:flatten(Acc)
+ end.
+
diff --git a/lib/erl_interface/vsn.mk b/lib/erl_interface/vsn.mk
index 06ef907d6c..dae6052d55 100644
--- a/lib/erl_interface/vsn.mk
+++ b/lib/erl_interface/vsn.mk
@@ -1,2 +1,2 @@
-EI_VSN = 3.10.4
+EI_VSN = 3.11.1
ERL_INTERFACE_VSN = $(EI_VSN)
diff --git a/lib/ftp/doc/src/notes.xml b/lib/ftp/doc/src/notes.xml
index 01c1f88cf1..61da079900 100644
--- a/lib/ftp/doc/src/notes.xml
+++ b/lib/ftp/doc/src/notes.xml
@@ -33,7 +33,23 @@
<file>notes.xml</file>
</header>
- <section><title>Ftp 1.0.1</title>
+ <section><title>Ftp 1.0.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed timing related bug that could make ftp functions
+ behave badly.</p>
+ <p>
+ Own Id: OTP-15659 Aux Id: ERIERL-316 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ftp 1.0.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/ftp/vsn.mk b/lib/ftp/vsn.mk
index d5d6c45b28..9f14658099 100644
--- a/lib/ftp/vsn.mk
+++ b/lib/ftp/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = ftp
-FTP_VSN = 1.0.1
+FTP_VSN = 1.0.2
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(FTP_VSN)$(PRE_VSN)"
diff --git a/lib/hipe/doc/src/notes.xml b/lib/hipe/doc/src/notes.xml
index e9cdf42018..9a803cb9df 100644
--- a/lib/hipe/doc/src/notes.xml
+++ b/lib/hipe/doc/src/notes.xml
@@ -31,6 +31,21 @@
</header>
<p>This document describes the changes made to HiPE.</p>
+<section><title>Hipe 3.18.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p> Fix a bug in the handling of the <c>Key</c> argument
+ of <c>lists:{keysearch, keyfind, keymember}</c>. </p>
+ <p>
+ Own Id: OTP-15570</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Hipe 3.18.2</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/hipe/vsn.mk b/lib/hipe/vsn.mk
index 12d621bf01..39565d721f 100644
--- a/lib/hipe/vsn.mk
+++ b/lib/hipe/vsn.mk
@@ -1 +1 @@
-HIPE_VSN = 3.18.2
+HIPE_VSN = 3.18.3
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index 31dae6317e..91dd9cd6ed 100644
--- a/lib/inets/doc/src/notes.xml
+++ b/lib/inets/doc/src/notes.xml
@@ -33,7 +33,43 @@
<file>notes.xml</file>
</header>
- <section><title>Inets 7.0.5</title>
+ <section><title>Inets 7.0.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix the internal handling of the option
+ erl_script_timeout in httpd. When httpd was started with
+ explicit erl_script_timeout, the value of the option was
+ converted to milliseconds before storage. Subsequent
+ calls to httpd:info/1 returned the input value multiplied
+ by 1000.</p>
+ <p>
+ This change fixes the handing of erl_script_timeout by
+ storing the timeout in seconds and converting to
+ milliseconds before usage.</p>
+ <p>
+ Own Id: OTP-15669 Aux Id: ERIERL-321 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Enhance documentation</p>
+ <p>
+ Own Id: OTP-15508 Aux Id: ERL-816 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Inets 7.0.5</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk
index 921161dce1..b7ddf39ebd 100644
--- a/lib/inets/vsn.mk
+++ b/lib/inets/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = inets
-INETS_VSN = 7.0.5
+INETS_VSN = 7.0.6
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)"
diff --git a/lib/kernel/doc/src/application.xml b/lib/kernel/doc/src/application.xml
index 5170502581..83a83ebad2 100644
--- a/lib/kernel/doc/src/application.xml
+++ b/lib/kernel/doc/src/application.xml
@@ -238,8 +238,8 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="set_env" arity="1" since="OTP @OTP-15642@"/>
- <name name="set_env" arity="2" since="OTP @OTP-15642@"/>
+ <name name="set_env" arity="1" since="OTP 21.3"/>
+ <name name="set_env" arity="2" since="OTP 21.3"/>
<fsummary>Sets the configuration parameters of multiple applications.</fsummary>
<desc>
<p>Sets the configuration <c><anno>Config</anno></c> for multiple
diff --git a/lib/kernel/doc/src/logger.xml b/lib/kernel/doc/src/logger.xml
index ebebcaa1ae..2a060ce1f9 100644
--- a/lib/kernel/doc/src/logger.xml
+++ b/lib/kernel/doc/src/logger.xml
@@ -1119,7 +1119,7 @@ logger:set_proxy_config(maps:merge(Old, Config)).
</func>
<func>
- <name name="timestamp" arity="0" since="OTP @OTP-15625@"/>
+ <name name="timestamp" arity="0" since="OTP 21.3"/>
<fsummary>Return a timestamp to insert in meta data for a log
event.</fsummary>
<desc>
@@ -1351,5 +1351,3 @@ logger:set_proxy_config(maps:merge(Old, Config)).
</p>
</section>
</erlref>
-
-
diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml
index 1f4d9be1f5..0c187eb19f 100644
--- a/lib/kernel/doc/src/notes.xml
+++ b/lib/kernel/doc/src/notes.xml
@@ -31,6 +31,185 @@
</header>
<p>This document describes the changes made to the Kernel application.</p>
+<section><title>Kernel 6.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ If for example the <c>/etc/hosts</c> did not come into
+ existence until after the kernel application had started,
+ its content was never read. This bug has now been
+ corrected.</p>
+ <p>
+ Own Id: OTP-14702 Aux Id: PR-2066 </p>
+ </item>
+ <item>
+ <p>
+ Fix bug where doing <c>seq_trace:reset_trace()</c> while
+ another process was doing a garbage collection could
+ cause the run-time system to segfault.</p>
+ <p>
+ Own Id: OTP-15490</p>
+ </item>
+ <item>
+ <p>
+ Fix <c>erl_epmd:port_please</c> spec to include
+ <c>atom()</c> and <c>string()</c>.</p>
+ <p>
+ Own Id: OTP-15557 Aux Id: PR-2117 </p>
+ </item>
+ <item>
+ <p>
+ The Logger handler logger_std_h now keeps track of the
+ inode of its log file in order to re-open the file if the
+ inode changes. This may happen, for instance, if the log
+ file is opened and saved by an editor.</p>
+ <p>
+ Own Id: OTP-15578 Aux Id: ERL-850 </p>
+ </item>
+ <item>
+ <p>
+ When user specific file modes are given to the logger
+ handler <c>logger_std_h</c>, they were earlier accepted
+ without any control. This is now changes, so Logger will
+ adjust the file modes as follows:</p>
+ <p>
+ - If <c>raw</c> is not found in the list, it is
+ added.<br/> - If none of <c>write</c>, <c>append</c> or
+ <c>exclusive</c> are found in the list, <c>append</c> is
+ added.<br/> - If none of <c>delayed_write</c> or
+ <c>{delayed_write,Size,Delay}</c> are found in the list,
+ <c>delayed_write</c> is added.</p>
+ <p>
+ Own Id: OTP-15602</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The standard logger handler, <c>logger_std_h</c>, now has
+ a new internal feature for log rotation. The rotation
+ scheme is as follows:</p>
+ <p>
+ The log file to which the handler currently writes always
+ has the same name, i.e. the name which is configured for
+ the handler. The archived files have the same name, but
+ with extension ".N", where N is an integer. The newest
+ archive has extension ".0", and the oldest archive has
+ the highest number. </p>
+ <p>
+ The size at which the log file is rotated, and the number
+ of archive files that are kept, is specified with the
+ handler specific configuration parameters
+ <c>max_no_bytes</c> and <c>max_no_files</c> respectively. </p>
+ <p>
+ Archives can be compressed, in which case they get a
+ ".gz" file extension after the integer. Compression is
+ specified with the handler specific configuration
+ parameter <c>compress_on_rotate</c>.</p>
+ <p>
+ Own Id: OTP-15479</p>
+ </item>
+ <item>
+ <p>
+ The new functions <c>logger:i/0</c> and <c>logger:i/1</c>
+ are added. These provide the same information as
+ <c>logger:get_config/0</c> and other
+ <c>logger:get_*_config</c> functions, but the information
+ is more logically sorted and more readable.</p>
+ <p>
+ Own Id: OTP-15600</p>
+ </item>
+ <item>
+ <p>
+ Logger is now protected against overload due to massive
+ amounts of log events from the emulator or from remote
+ nodes.</p>
+ <p>
+ Own Id: OTP-15601</p>
+ </item>
+ <item>
+ <p>
+ Logger now uses os:system_time/1 instead of
+ erlang:system_time/1 to generate log event timestamps.</p>
+ <p>
+ Own Id: OTP-15625</p>
+ </item>
+ <item>
+ <p>
+ Add functions <c>application:set_env/1,2</c> and
+ <c>application:set_env/2</c>. These take a list of
+ application configuration parameters, and the behaviour
+ is equivalent to calling <c>application:set_env/4</c>
+ individually for each application/key combination, except
+ it is more efficient.</p>
+ <p>
+ <c>set_env/1,2</c> warns about duplicated applications or
+ keys. The warning is also emitted during boot, if
+ applications or keys are duplicated within one
+ configuration file, e.g. sys.config.</p>
+ <p>
+ Own Id: OTP-15642 Aux Id: PR-2164 </p>
+ </item>
+ <item>
+ <p>
+ Handler specific configuration parameters for the
+ standard handler <c>logger_std_h</c> are changed to be
+ more intuitive and more similar to the disk_log handler.</p>
+ <p>
+ Earlier there was only one parameter, <c>type</c>, which
+ could have the values <c>standard_io</c>,
+ <c>standard_error</c>, <c>{file,FileName}</c> or
+ <c>{file,FileName,Modes}</c>.</p>
+ <p>
+ This is now changed, so the following parameters are
+ allowed:</p>
+ <p>
+ <c>type = standard_io | standard_error | file</c><br/>
+ <c>file = file:filename()</c><br/> <c>modes =
+ [file:mode()]</c></p>
+ <p>
+ All parameters are optional. <c>type</c> defaults to
+ <c>standard_io</c>, unless a file name is given, in which
+ case it defaults to <c>file</c>. If <c>type</c> is set to
+ <c>file</c>, the file name defaults to the same as the
+ handler id.</p>
+ <p>
+ The potential incompatibility is that
+ <c>logger:get_config/0</c> and
+ <c>logger:get_handler_config/1</c> now returns the new
+ parameters, even if the configuration was set with the
+ old variant, e.g. <c>#{type=>{file,FileName}}</c>.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15662</p>
+ </item>
+ <item>
+ <p>
+ The new configuration parameter <c>file_check</c> is
+ added to the Logger handler <c>logger_std_h</c>. This
+ parameter specifies how long (in milliseconds) the
+ handler may wait before checking if the log file still
+ exists and the inode is the same as when it was opened.</p>
+ <p>
+ The default value is 0, which means that this check is
+ done prior to each write operation. Setting a higher
+ number may improve performance, but adds the risk of
+ loosing log events.</p>
+ <p>
+ Own Id: OTP-15663</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Kernel 6.2.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/kernel/src/kernel.appup.src b/lib/kernel/src/kernel.appup.src
index 66fbcbf78d..8fa3f5c588 100644
--- a/lib/kernel/src/kernel.appup.src
+++ b/lib/kernel/src/kernel.appup.src
@@ -42,7 +42,8 @@
{<<"^6\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^6\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^6\\.2$">>,[restart_new_emulator]},
- {<<"^6\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}],
+ {<<"^6\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^6\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
[{<<"^5\\.3$">>,[restart_new_emulator]},
{<<"^5\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^5\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
@@ -58,4 +59,5 @@
{<<"^6\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^6\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^6\\.2$">>,[restart_new_emulator]},
- {<<"^6\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}]}.
+ {<<"^6\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^6\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
diff --git a/lib/kernel/test/code_SUITE.erl b/lib/kernel/test/code_SUITE.erl
index 64e0b9d8dd..99fecbe970 100644
--- a/lib/kernel/test/code_SUITE.erl
+++ b/lib/kernel/test/code_SUITE.erl
@@ -140,6 +140,11 @@ end_per_testcase(on_load_embedded, Config) ->
LinkName = proplists:get_value(link_name, Config),
_ = del_link(LinkName),
end_per_testcase(Config);
+end_per_testcase(upgrade, Config) ->
+ %% Make sure tracing is turned off even if the test times out.
+ erlang:trace_pattern({error_handler,undefined_function,3}, false, [global]),
+ erlang:trace(self(), false, [call]),
+ end_per_testcase(Config);
end_per_testcase(_Func, Config) ->
end_per_testcase(Config).
@@ -1556,6 +1561,11 @@ on_load_update_code_1(3, Mod) ->
%% Test -on_load while trace feature 'on_load' is enabled (OTP-14612)
on_load_trace_on_load(Config) ->
+ %% 'on_load' enables tracing for all newly loaded modules, so we make a dry
+ %% run to ensure that ancillary modules like 'merl' won't be loaded during
+ %% the actual test.
+ on_load_update(Config),
+
Papa = self(),
Tracer = spawn_link(fun F() -> receive M -> Papa ! M end, F() end),
{tracer,[]} = erlang:trace_info(self(),tracer),
diff --git a/lib/kernel/vsn.mk b/lib/kernel/vsn.mk
index 6e0eea7824..7bebe1ba70 100644
--- a/lib/kernel/vsn.mk
+++ b/lib/kernel/vsn.mk
@@ -1 +1 @@
-KERNEL_VSN = 6.2.1
+KERNEL_VSN = 6.3
diff --git a/lib/mnesia/doc/src/notes.xml b/lib/mnesia/doc/src/notes.xml
index 8fc3610bb6..01d1666b8d 100644
--- a/lib/mnesia/doc/src/notes.xml
+++ b/lib/mnesia/doc/src/notes.xml
@@ -39,7 +39,22 @@
thus constitutes one section in this document. The title of each
section is the version number of Mnesia.</p>
- <section><title>Mnesia 4.15.5</title>
+ <section><title>Mnesia 4.15.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Avoid overload warnings caused by a race condition.</p>
+ <p>
+ Own Id: OTP-15619 Aux Id: ERIERL-310 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Mnesia 4.15.5</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/mnesia/vsn.mk b/lib/mnesia/vsn.mk
index 1cfb35c774..781a4830a0 100644
--- a/lib/mnesia/vsn.mk
+++ b/lib/mnesia/vsn.mk
@@ -1 +1 @@
-MNESIA_VSN = 4.15.5
+MNESIA_VSN = 4.15.6
diff --git a/lib/observer/doc/src/notes.xml b/lib/observer/doc/src/notes.xml
index 22035af982..2d914f8c61 100644
--- a/lib/observer/doc/src/notes.xml
+++ b/lib/observer/doc/src/notes.xml
@@ -32,6 +32,45 @@
<p>This document describes the changes made to the Observer
application.</p>
+<section><title>Observer 2.9</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Since Logger was introduced in OTP-21.0, menu choice
+ <em>Log &gt; Toggle Log View</em> in observer would cause
+ a crash unless an <c>error_logger</c> event handler was
+ explicitly installed. This is now corrected.</p>
+ <p>
+ Own Id: OTP-15553 Aux Id: ERL-848 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Since persistent_term was introduced, observer would
+ sometimes crash when expanding a term from a process
+ state. This is now corrected.</p>
+ <p>
+ Own Id: OTP-15493 Aux Id: ERL-810 </p>
+ </item>
+ <item>
+ <p>
+ Add <c>OBSERVER_SCALE</c> environment variable for HiDPI
+ support.</p>
+ <p>
+ Own Id: OTP-15586 Aux Id: PR-2105 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Observer 2.8.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/observer/vsn.mk b/lib/observer/vsn.mk
index 5ce0aca589..0e9c8b302c 100644
--- a/lib/observer/vsn.mk
+++ b/lib/observer/vsn.mk
@@ -1 +1 @@
-OBSERVER_VSN = 2.8.2
+OBSERVER_VSN = 2.9
diff --git a/lib/odbc/doc/src/notes.xml b/lib/odbc/doc/src/notes.xml
index dba7663bb9..696fcaa479 100644
--- a/lib/odbc/doc/src/notes.xml
+++ b/lib/odbc/doc/src/notes.xml
@@ -32,7 +32,22 @@
<p>This document describes the changes made to the odbc application.
</p>
- <section><title>ODBC 2.12.2</title>
+ <section><title>ODBC 2.12.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Enhance error handling to avoid stack corruption</p>
+ <p>
+ Own Id: OTP-15667 Aux Id: ERL-808, PR-2065 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>ODBC 2.12.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/odbc/vsn.mk b/lib/odbc/vsn.mk
index bb21016fad..ff023e666b 100644
--- a/lib/odbc/vsn.mk
+++ b/lib/odbc/vsn.mk
@@ -1 +1 @@
-ODBC_VSN = 2.12.2
+ODBC_VSN = 2.12.3
diff --git a/lib/public_key/doc/src/notes.xml b/lib/public_key/doc/src/notes.xml
index b3e6023c41..f6bc0dc797 100644
--- a/lib/public_key/doc/src/notes.xml
+++ b/lib/public_key/doc/src/notes.xml
@@ -35,6 +35,21 @@
<file>notes.xml</file>
</header>
+<section><title>Public_Key 1.6.5</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add export of dialyzer type</p>
+ <p>
+ Own Id: OTP-15624</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Public_Key 1.6.4</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml
index 9fcedf6ef9..fb81ea68a4 100644
--- a/lib/public_key/doc/src/public_key.xml
+++ b/lib/public_key/doc/src/public_key.xml
@@ -423,7 +423,7 @@
<p>Available options:</p>
<taglist>
- <tag>{verify_fun, fun()}</tag>
+ <tag>{verify_fun, {fun(), InitialUserState::term()}</tag>
<item>
<p>The fun must be defined as:</p>
diff --git a/lib/public_key/src/pubkey_pbe.erl b/lib/public_key/src/pubkey_pbe.erl
index 806f7c5b0f..e6bcedd1b1 100644
--- a/lib/public_key/src/pubkey_pbe.erl
+++ b/lib/public_key/src/pubkey_pbe.erl
@@ -42,15 +42,14 @@
encode(Data, Password, "DES-CBC" = Cipher, KeyDevParams) ->
{Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
crypto:block_encrypt(des_cbc, Key, IV, pbe_pad(Data, KeyDevParams));
-
encode(Data, Password, "DES-EDE3-CBC" = Cipher, KeyDevParams) ->
{Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
<<Key1:8/binary, Key2:8/binary, Key3:8/binary>> = Key,
crypto:block_encrypt(des3_cbc, [Key1, Key2, Key3], IV, pbe_pad(Data));
-
encode(Data, Password, "RC2-CBC" = Cipher, KeyDevParams) ->
{Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
crypto:block_encrypt(rc2_cbc, Key, IV, pbe_pad(Data, KeyDevParams)).
+
%%--------------------------------------------------------------------
-spec decode(binary(), string(), string(), term()) -> binary().
%%
@@ -59,21 +58,20 @@ encode(Data, Password, "RC2-CBC" = Cipher, KeyDevParams) ->
decode(Data, Password,"DES-CBC"= Cipher, KeyDevParams) ->
{Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
crypto:block_decrypt(des_cbc, Key, IV, Data);
-
decode(Data, Password,"DES-EDE3-CBC" = Cipher, KeyDevParams) ->
{Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
<<Key1:8/binary, Key2:8/binary, Key3:8/binary>> = Key,
crypto:block_decrypt(des3_cbc, [Key1, Key2, Key3], IV, Data);
-
decode(Data, Password,"RC2-CBC"= Cipher, KeyDevParams) ->
{Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
crypto:block_decrypt(rc2_cbc, Key, IV, Data);
+decode(Data, Password,"AES-128-CBC"= Cipher, KeyDevParams) ->
+ {Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
+ crypto:block_decrypt(aes_cbc128, Key, IV, Data);
+decode(Data, Password,"AES-256-CBC"= Cipher, KeyDevParams) ->
+ {Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams),
+ crypto:block_decrypt(aes_cbc256, Key, IV, Data).
-decode(Data, Password,"AES-128-CBC"= Cipher, IV) ->
- %% PKCS5_SALT_LEN is 8 bytes
- <<Salt:8/binary,_/binary>> = IV,
- {Key, _} = password_to_key_and_iv(Password, Cipher, Salt),
- crypto:block_decrypt(aes_cbc128, Key, IV, Data).
%%--------------------------------------------------------------------
-spec pbdkdf1(string(), iodata(), integer(), atom()) -> binary().
@@ -131,13 +129,15 @@ password_to_key_and_iv(Password, _Cipher, {#'PBEParameter'{salt = Salt,
<<Key:8/binary, IV:8/binary, _/binary>>
= pbdkdf1(Password, Salt, Count, Hash),
{Key, IV};
-password_to_key_and_iv(Password, Cipher, Salt) ->
- KeyLen = derived_key_length(Cipher, undefined),
+password_to_key_and_iv(Password, Cipher, KeyDevParams) ->
+ %% PKCS5_SALT_LEN is 8 bytes
+ <<Salt:8/binary,_/binary>> = KeyDevParams,
+ KeyLen = derived_key_length(Cipher, undefined),
<<Key:KeyLen/binary, _/binary>> =
pem_encrypt(<<>>, Password, Salt, ceiling(KeyLen div 16), <<>>, md5),
%% Old PEM encryption does not use standard encryption method
- %% pbdkdf1 and uses then salt as IV
- {Key, Salt}.
+ %% pbdkdf1
+ {Key, KeyDevParams}.
pem_encrypt(_, _, _, 0, Acc, _) ->
Acc;
pem_encrypt(Prev, Password, Salt, Count, Acc, Hash) ->
@@ -267,7 +267,9 @@ derived_key_length(Cipher,_) when (Cipher == ?'des-EDE3-CBC') or
(Cipher == "DES-EDE3-CBC") ->
24;
derived_key_length(Cipher,_) when (Cipher == "AES-128-CBC") ->
- 16.
+ 16;
+derived_key_length(Cipher,_) when (Cipher == "AES-256-CBC") ->
+ 32.
cipher(#'PBES2-params_encryptionScheme'{algorithm = ?'desCBC'}) ->
"DES-CBC";
diff --git a/lib/public_key/src/pubkey_pem.erl b/lib/public_key/src/pubkey_pem.erl
index d7e5bc3ad8..0fd1453f7c 100644
--- a/lib/public_key/src/pubkey_pem.erl
+++ b/lib/public_key/src/pubkey_pem.erl
@@ -101,10 +101,10 @@ encode_pem_entry({'PrivateKeyInfo', Der, EncParams}) ->
EncDer = encode_encrypted_private_keyinfo(Der, EncParams),
StartStr = pem_start('EncryptedPrivateKeyInfo'),
[StartStr, "\n", b64encode_and_split(EncDer), "\n", pem_end(StartStr) ,"\n\n"];
-encode_pem_entry({Type, Der, {Cipher, Salt}}) ->
+encode_pem_entry({Type, Decrypted, {Cipher, Salt}}) ->
StartStr = pem_start(Type),
[StartStr,"\n", pem_decrypt(),"\n", pem_decrypt_info(Cipher, Salt),"\n\n",
- b64encode_and_split(Der), "\n", pem_end(StartStr) ,"\n\n"].
+ b64encode_and_split(Decrypted), "\n", pem_end(StartStr) ,"\n\n"].
decode_pem_entries([], Entries) ->
lists:reverse(Entries);
diff --git a/lib/public_key/test/pbe_SUITE.erl b/lib/public_key/test/pbe_SUITE.erl
index 523c9e2515..1136267411 100644
--- a/lib/public_key/test/pbe_SUITE.erl
+++ b/lib/public_key/test/pbe_SUITE.erl
@@ -37,7 +37,7 @@ all() ->
[
pbdkdf1,
pbdkdf2,
- old_enc,
+ old_pbe,
pbes1,
pbes2].
@@ -197,23 +197,11 @@ pbdkdf2(Config) when is_list(Config) ->
= pubkey_pbe:pbdkdf2("pass\0word",
"sa\0lt", 4096, 16, fun crypto:hmac/4, sha, 20).
-old_enc() ->
- [{doc,"Tests encode/decode RSA key encrypted with different ciphers using old PEM encryption scheme"}].
-old_enc(Config) when is_list(Config) ->
- Datadir = proplists:get_value(data_dir, Config),
- %% key generated with ssh-keygen -N hello_aes -f old_aes_128_cbc_enc_key.pem
- {ok, PemAesCbc} = file:read_file(filename:join(Datadir, "old_aes_128_cbc_enc_key.pem")),
-
- PemAesCbcEntry = public_key:pem_decode(PemAesCbc),
- ct:print("Pem entry: ~p" , [PemAesCbcEntry]),
- [{'RSAPrivateKey', _, {"AES-128-CBC",_}} = PubAesCbcEntry] = PemAesCbcEntry,
- #'RSAPrivateKey'{} = public_key:pem_entry_decode(PubAesCbcEntry, "hello_aes").
-
pbes1() ->
[{doc,"Tests encode/decode EncryptedPrivateKeyInfo encrypted with different ciphers using PBES1"}].
pbes1(Config) when is_list(Config) ->
decode_encode_key_file("pbes1_des_cbc_md5_enc_key.pem", "password", "DES-CBC", Config).
-
+
pbes2() ->
[{doc,"Tests encode/decode EncryptedPrivateKeyInfo encrypted with different ciphers using PBES2"}].
pbes2(Config) when is_list(Config) ->
@@ -225,13 +213,33 @@ pbes2(Config) when is_list(Config) ->
false ->
ok
end.
+old_pbe() ->
+ [{doc,"Tests encode/decode with old format used before PBE"}].
+old_pbe(Config) when is_list(Config) ->
+ Datadir = proplists:get_value(data_dir, Config),
+ % key generated with ssh-keygen -N hello_aes -f old_aes_128_cbc.pem
+ {ok, PemAes128Cbc} = file:read_file(filename:join(Datadir, "old_aes_128_cbc.pem")),
+
+ PemAes128CbcEntries = public_key:pem_decode(PemAes128Cbc),
+ ct:print("Pem entry: ~p" , [PemAes128CbcEntries]),
+ [{'RSAPrivateKey', _, {"AES-128-CBC",_}} = Aes128CbcEntry] = PemAes128CbcEntries,
+ #'RSAPrivateKey'{} = Key = public_key:pem_entry_decode(Aes128CbcEntry, "hello_aes"),
+
+ %% Converted with openssl rsa -in old_aes_128_cbc.pem -out old_aes_256_cbc.pem -aes256
+ {ok, PemAes256Cbc} = file:read_file(filename:join(Datadir, "old_aes_256_cbc.pem")),
+
+ PemAes256CbcEntries = public_key:pem_decode(PemAes256Cbc),
+ ct:print("Pem entry: ~p" , [PemAes256CbcEntries]),
+ [{'RSAPrivateKey', _, {"AES-256-CBC",_}} = Aes256CbcEntry] = PemAes256CbcEntries,
+ Key = public_key:pem_entry_decode(Aes256CbcEntry, "hello_aes").
+
decode_encode_key_file(File, Password, Cipher, Config) ->
Datadir = proplists:get_value(data_dir, Config),
{ok, PemKey} = file:read_file(filename:join(Datadir, File)),
PemEntry = public_key:pem_decode(PemKey),
- ct:print("Pem entry: ~p" , [PemEntry]),
+ ct:pal("Pem entry: ~p" , [PemEntry]),
[{Asn1Type, _, {Cipher,_} = CipherInfo} = PubEntry] = PemEntry,
#'RSAPrivateKey'{} = KeyInfo = public_key:pem_entry_decode(PubEntry, Password),
PemKey1 = public_key:pem_encode([public_key:pem_entry_encode(Asn1Type, KeyInfo, {CipherInfo, Password})]),
diff --git a/lib/public_key/test/pbe_SUITE_data/old_aes_128_cbc_enc_key.pem b/lib/public_key/test/pbe_SUITE_data/old_aes_128_cbc.pem
index 34c7543f30..34c7543f30 100644
--- a/lib/public_key/test/pbe_SUITE_data/old_aes_128_cbc_enc_key.pem
+++ b/lib/public_key/test/pbe_SUITE_data/old_aes_128_cbc.pem
diff --git a/lib/public_key/test/pbe_SUITE_data/old_aes_256_cbc.pem b/lib/public_key/test/pbe_SUITE_data/old_aes_256_cbc.pem
new file mode 100644
index 0000000000..e6aec2869d
--- /dev/null
+++ b/lib/public_key/test/pbe_SUITE_data/old_aes_256_cbc.pem
@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-256-CBC,ABDA22398E511E9465983E1A50706044
+
+XhIcPOb6pTWL++pgeeTH5rsx0tackhllVqyXyOfbYMBJnVFRhQ/V/1MDg3Jt4wD1
+Nerhcv5srHeiwmf+vwXwDFOzvFzLVM1jFMUJe/2XloYFX4TBiLZAF/zekQA3uPY6
+DKJuBuO5vVSZ0VlxGpu3jphIAwxbssfZkZmryCP3b1/oX5Y3Em/wEWW3RduaeWFu
+Z2nTsPH3yNmHkuqFF3cq0aZs+VxtjcuYo0gbkN5hNVgOoOVxIBzw7DsgBAkdXvr3
+LRCMGg7Y2pVthA963s0xkN37XtZEiYbydoLnzHlW/Kx5QSvED8/Fn94Lcdy5NsQN
+7nYWWgYZRH39Wnsi0BrTZv399U5rBe7DnpStKWPn2Sa8Bdu3CX2oLajM1cZjAp1X
+y6vuasK7SoZ8rWpcsHQpV1HyNBTl/uRU5nrYc/KGD8l2x7tdNidGx2Hey/6O4H5v
+rxL1TB4PlzDYwCsReuyLbEjyuZQ10j0SK/SFzikHuvD2IEpM/oUaPdVFqcW8/sjw
+VhcsupAf4EXfsi2dJBylmDjI2W7h9XwBDLzQN+69DtkBmvimE5CguTITf8MAHQQ6
+Q1vYF2ij7W675tj9ulksRPaMxSsI03luai/Jrieh0mPqGEenGEEC5QU0XPOvsfyw
+GMYaBUbdYrMpbHM3wFPxM2wRlXf4gX5BhZKRGZX7OaEs54pfglyQtTPuZmD0VcAp
+EWHq70G9mbuBlhbMX1rKAomuDmIvgLeLRUpAFf59Qr8KoJSLD1S8TJB3uEPk6i39
+4GnylbpmqS4gv/OIc6WTeOeUZTAD3A77HBwSlELPk3/s1d/MLyfciYClOBEuZ6NB
+FXEKCGCEC786zJA678gLEaa2XPNkEM+2gjzNFqtYMIn5ehAq/HRRsFvW+wkTbee4
+z+qe5HbVKAQ3EOfbidvYrDaGd7HvHVG8zosl+O61iIFs04lLEMDFXBIdvIgEncOK
+Rq3yXdpBKMg89aoZLniaPobSvuvdjNOMzW6EKlb5FKZduCiR68MEZ+rLHYHTwE3W
+Z5+TCbrbV2F6WQpq3zqnB14wGu8igEb5Veq+N2vMkx4iTMTUyCty1SwIjj4NidM1
+dJM7Ighdal6tQ6hIwbDfpIPsY4eGH/UrdVZ0SkxuDR2s76cZ8nFX3lJ/BNwTZLKo
+IqAC4NjUOv3ID+0Q6Lz+sxLCi5pLYUf0E+s4pgi1BYAOu+BF3GwxyqnqVoq/Fs5D
+LXxuY0946YM+WcrYzke4mq3MPx6QQYj04H5KJ2mzxtnbZJrfLF23PVRVhvgKSjyV
+I3/zgJ16fV2H/fb26oCpTNbb11pQvhorkLwdvpwtM+go7dJGebAi1762Nbj/CqnW
+fbBPxPRvNPZn6pEodJ/L/APhvGv1K7eC9THj66H7Kmeoq8Lz74idhywP9I3QS0ZO
+15ORbTDjuiRYNJPxxu79A3/tWMUlprJ9ljhI/0DXRB0M3UGic52D/32Q64I7eewy
+qRNS/3C3ejDShIRBDFTdDkM3s/42LySXJjmjU9bpZY4POQ3kOaJb3EzSvbzTyXzu
+3FiHvDQY+b8XwbxtE/kTMaAPQZ7TtWOao7SRi7J94MvCQ5/tbakFP2suM8psnigC
+-----END RSA PRIVATE KEY-----
diff --git a/lib/public_key/vsn.mk b/lib/public_key/vsn.mk
index 5e2643f0ea..11c06fb158 100644
--- a/lib/public_key/vsn.mk
+++ b/lib/public_key/vsn.mk
@@ -1 +1 @@
-PUBLIC_KEY_VSN = 1.6.4
+PUBLIC_KEY_VSN = 1.6.5
diff --git a/lib/runtime_tools/doc/src/notes.xml b/lib/runtime_tools/doc/src/notes.xml
index 810bb8207c..58a2a66c4b 100644
--- a/lib/runtime_tools/doc/src/notes.xml
+++ b/lib/runtime_tools/doc/src/notes.xml
@@ -32,6 +32,21 @@
<p>This document describes the changes made to the Runtime_Tools
application.</p>
+<section><title>Runtime_Tools 1.13.2</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Update of systemtap trace example scripts.</p>
+ <p>
+ Own Id: OTP-15670</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Runtime_Tools 1.13.1</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/runtime_tools/vsn.mk b/lib/runtime_tools/vsn.mk
index aa3d702997..fa2f338ec2 100644
--- a/lib/runtime_tools/vsn.mk
+++ b/lib/runtime_tools/vsn.mk
@@ -1 +1 @@
-RUNTIME_TOOLS_VSN = 1.13.1
+RUNTIME_TOOLS_VSN = 1.13.2
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index 0bc4baf5eb..17f14bdea2 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -30,6 +30,23 @@
<file>notes.xml</file>
</header>
+<section><title>Ssh 4.7.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ SSH sftp daemon now accepts an SSH_FXP_STAT message
+ encoded according to the wrong sftp version. Some clients
+ sends such messages.</p>
+ <p>
+ Own Id: OTP-15498 Aux Id: ERL-822, PR-2077 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.7.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl
index 9ff20454cd..1f4e281a30 100644
--- a/lib/ssh/src/ssh_transport.erl
+++ b/lib/ssh/src/ssh_transport.erl
@@ -162,14 +162,14 @@ supported_algorithms(cipher) ->
select_crypto_supported(
[
{'[email protected]', [{ciphers,chacha20}, {macs,poly1305}]},
- {'[email protected]', [{ciphers,{aes_gcm,256}}]},
- {'aes256-ctr', [{ciphers,{aes_ctr,256}}]},
- {'aes192-ctr', [{ciphers,{aes_ctr,192}}]},
- {'[email protected]', [{ciphers,{aes_gcm,128}}]},
- {'aes128-ctr', [{ciphers,{aes_ctr,128}}]},
- {'AEAD_AES_256_GCM', [{ciphers,{aes_gcm,256}}]},
- {'AEAD_AES_128_GCM', [{ciphers,{aes_gcm,128}}]},
- {'aes128-cbc', [{ciphers,aes_cbc128}]},
+ {'[email protected]', [{ciphers,aes_256_gcm}]},
+ {'aes256-ctr', [{ciphers,aes_256_ctr}]},
+ {'aes192-ctr', [{ciphers,aes_192_ctr}]},
+ {'[email protected]', [{ciphers,aes_128_gcm}]},
+ {'aes128-ctr', [{ciphers,aes_128_ctr}]},
+ {'AEAD_AES_256_GCM', [{ciphers,aes_256_gcm}]},
+ {'AEAD_AES_128_GCM', [{ciphers,aes_128_gcm}]},
+ {'aes128-cbc', [{ciphers,aes_128_cbc}]},
{'3des-cbc', [{ciphers,des3_cbc}]}
]
));
@@ -179,8 +179,8 @@ supported_algorithms(mac) ->
[{'hmac-sha2-256', [{macs,hmac}, {hashs,sha256}]},
{'hmac-sha2-512', [{macs,hmac}, {hashs,sha512}]},
{'hmac-sha1', [{macs,hmac}, {hashs,sha}]},
- {'AEAD_AES_128_GCM', [{ciphers,{aes_gcm,128}}]},
- {'AEAD_AES_256_GCM', [{ciphers,{aes_gcm,256}}]}
+ {'AEAD_AES_128_GCM', [{ciphers,aes_128_gcm}]},
+ {'AEAD_AES_256_GCM', [{ciphers,aes_256_gcm}]}
]
));
supported_algorithms(compression) ->
@@ -1256,11 +1256,6 @@ get_length(aead, EncryptedBuffer, Ssh) ->
end.
-pkt_type('AEAD_AES_128_GCM') -> aead;
-pkt_type('AEAD_AES_256_GCM') -> aead;
-pkt_type('[email protected]') -> aead;
-pkt_type(_) -> common.
-
payload(<<PacketLen:32, PaddingLen:8, PayloadAndPadding/binary>>) ->
PayloadLen = PacketLen - PaddingLen - 1,
<<Payload:PayloadLen/binary, _/binary>> = PayloadAndPadding,
@@ -1323,162 +1318,115 @@ verify(PlainText, HashAlg, Sig, Key, _) ->
%%% Unit: bytes
--record(cipher_data, {
- key_bytes,
- iv_bytes,
- block_bytes
- }).
+-record(cipher, {
+ impl,
+ key_bytes,
+ iv_bytes,
+ block_bytes,
+ pkt_type = common
+ }).
%%% Start of a more parameterized crypto handling.
cipher('AEAD_AES_128_GCM') ->
- #cipher_data{key_bytes = 16,
- iv_bytes = 12,
- block_bytes = 16};
+ #cipher{key_bytes = 16,
+ iv_bytes = 12,
+ block_bytes = 16,
+ pkt_type = aead};
cipher('AEAD_AES_256_GCM') ->
- #cipher_data{key_bytes = 32,
- iv_bytes = 12,
- block_bytes = 16};
+ #cipher{key_bytes = 32,
+ iv_bytes = 12,
+ block_bytes = 16,
+ pkt_type = aead};
cipher('3des-cbc') ->
- #cipher_data{key_bytes = 24,
- iv_bytes = 8,
- block_bytes = 8};
+ #cipher{impl = des3_cbc,
+ key_bytes = 24,
+ iv_bytes = 8,
+ block_bytes = 8};
cipher('aes128-cbc') ->
- #cipher_data{key_bytes = 16,
- iv_bytes = 16,
- block_bytes = 16};
+ #cipher{impl = aes_cbc,
+ key_bytes = 16,
+ iv_bytes = 16,
+ block_bytes = 16};
cipher('aes128-ctr') ->
- #cipher_data{key_bytes = 16,
- iv_bytes = 16,
- block_bytes = 16};
+ #cipher{impl = aes_128_ctr,
+ key_bytes = 16,
+ iv_bytes = 16,
+ block_bytes = 16};
cipher('aes192-ctr') ->
- #cipher_data{key_bytes = 24,
- iv_bytes = 16,
- block_bytes = 16};
+ #cipher{impl = aes_192_ctr,
+ key_bytes = 24,
+ iv_bytes = 16,
+ block_bytes = 16};
cipher('aes256-ctr') ->
- #cipher_data{key_bytes = 32,
- iv_bytes = 16,
- block_bytes = 16};
+ #cipher{impl = aes_256_ctr,
+ key_bytes = 32,
+ iv_bytes = 16,
+ block_bytes = 16};
cipher('[email protected]') -> % FIXME: Verify!!
- #cipher_data{key_bytes = 32,
- iv_bytes = 12,
- block_bytes = 8}.
-
+ #cipher{key_bytes = 32,
+ iv_bytes = 12,
+ block_bytes = 8,
+ pkt_type = aead};
+
+cipher(_) ->
+ #cipher{}.
+
+
+pkt_type(SshCipher) -> (cipher(SshCipher))#cipher.pkt_type.
+
+decrypt_magic(server) -> {"A", "C"};
+decrypt_magic(client) -> {"B", "D"}.
+
+encrypt_magic(client) -> decrypt_magic(server);
+encrypt_magic(server) -> decrypt_magic(client).
+
encrypt_init(#ssh{encrypt = none} = Ssh) ->
{ok, Ssh};
-encrypt_init(#ssh{encrypt = '[email protected]', role = client} = Ssh) ->
+
+encrypt_init(#ssh{encrypt = '[email protected]', role = Role} = Ssh) ->
%% [email protected] uses two independent crypto streams, one (chacha20)
%% for the length used in stream mode, and the other (chacha20-poly1305) as AEAD for
%% the payload and to MAC the length||payload.
%% See draft-josefsson-ssh-chacha20-poly1305-openssh-00
- <<K2:32/binary,K1:32/binary>> = hash(Ssh, "C", 512),
+ {_, KeyMagic} = encrypt_magic(Role),
+ <<K2:32/binary,K1:32/binary>> = hash(Ssh, KeyMagic, 8*64),
{ok, Ssh#ssh{encrypt_keys = {K1,K2}
% encrypt_block_size = 16, %default = 8. What to set it to? 64 (openssl chacha.h)
% ctx and iv is setup for each packet
}};
-encrypt_init(#ssh{encrypt = '[email protected]', role = server} = Ssh) ->
- <<K2:32/binary,K1:32/binary>> = hash(Ssh, "D", 512),
- {ok, Ssh#ssh{encrypt_keys = {K1,K2}
- % encrypt_block_size = 16, %default = 8. What to set it to?
- }};
-encrypt_init(#ssh{encrypt = 'AEAD_AES_128_GCM', role = client} = Ssh) ->
- IV = hash(Ssh, "A", 12*8),
- <<K:16/binary>> = hash(Ssh, "C", 128),
- {ok, Ssh#ssh{encrypt_keys = K,
- encrypt_block_size = 16,
- encrypt_ctx = IV}};
-encrypt_init(#ssh{encrypt = 'AEAD_AES_128_GCM', role = server} = Ssh) ->
- IV = hash(Ssh, "B", 12*8),
- <<K:16/binary>> = hash(Ssh, "D", 128),
- {ok, Ssh#ssh{encrypt_keys = K,
- encrypt_block_size = 16,
- encrypt_ctx = IV}};
-encrypt_init(#ssh{encrypt = 'AEAD_AES_256_GCM', role = client} = Ssh) ->
- IV = hash(Ssh, "A", 12*8),
- <<K:32/binary>> = hash(Ssh, "C", 256),
- {ok, Ssh#ssh{encrypt_keys = K,
- encrypt_block_size = 16,
- encrypt_ctx = IV}};
-encrypt_init(#ssh{encrypt = 'AEAD_AES_256_GCM', role = server} = Ssh) ->
- IV = hash(Ssh, "B", 12*8),
- <<K:32/binary>> = hash(Ssh, "D", 256),
- {ok, Ssh#ssh{encrypt_keys = K,
- encrypt_block_size = 16,
- encrypt_ctx = IV}};
-encrypt_init(#ssh{encrypt = '3des-cbc', role = client} = Ssh) ->
- IV = hash(Ssh, "A", 64),
- <<K1:8/binary, K2:8/binary, K3:8/binary>> = hash(Ssh, "C", 192),
- {ok, Ssh#ssh{encrypt_keys = {K1,K2,K3},
- encrypt_block_size = 8,
- encrypt_ctx = IV}};
-encrypt_init(#ssh{encrypt = '3des-cbc', role = server} = Ssh) ->
- IV = hash(Ssh, "B", 64),
- <<K1:8/binary, K2:8/binary, K3:8/binary>> = hash(Ssh, "D", 192),
- {ok, Ssh#ssh{encrypt_keys = {K1,K2,K3},
- encrypt_block_size = 8,
- encrypt_ctx = IV}};
-encrypt_init(#ssh{encrypt = 'aes128-cbc', role = client} = Ssh) ->
- IV = hash(Ssh, "A", 128),
- <<K:16/binary>> = hash(Ssh, "C", 128),
+
+encrypt_init(#ssh{encrypt = SshCipher, role = Role} = Ssh) when SshCipher == 'AEAD_AES_128_GCM';
+ SshCipher == 'AEAD_AES_256_GCM' ->
+ {IvMagic, KeyMagic} = encrypt_magic(Role),
+ #cipher{key_bytes = KeyBytes,
+ iv_bytes = IvBytes,
+ block_bytes = BlockBytes} = cipher(SshCipher),
+ IV = hash(Ssh, IvMagic, 8*IvBytes),
+ K = hash(Ssh, KeyMagic, 8*KeyBytes),
{ok, Ssh#ssh{encrypt_keys = K,
- encrypt_block_size = 16,
+ encrypt_block_size = BlockBytes,
encrypt_ctx = IV}};
-encrypt_init(#ssh{encrypt = 'aes128-cbc', role = server} = Ssh) ->
- IV = hash(Ssh, "B", 128),
- <<K:16/binary>> = hash(Ssh, "D", 128),
- {ok, Ssh#ssh{encrypt_keys = K,
- encrypt_block_size = 16,
- encrypt_ctx = IV}};
-encrypt_init(#ssh{encrypt = 'aes128-ctr', role = client} = Ssh) ->
- IV = hash(Ssh, "A", 128),
- <<K:16/binary>> = hash(Ssh, "C", 128),
- State = crypto:stream_init(aes_ctr, K, IV),
- {ok, Ssh#ssh{encrypt_keys = K,
- encrypt_block_size = 16,
- encrypt_ctx = State}};
-encrypt_init(#ssh{encrypt = 'aes192-ctr', role = client} = Ssh) ->
- IV = hash(Ssh, "A", 128),
- <<K:24/binary>> = hash(Ssh, "C", 192),
- State = crypto:stream_init(aes_ctr, K, IV),
- {ok, Ssh#ssh{encrypt_keys = K,
- encrypt_block_size = 16,
- encrypt_ctx = State}};
-encrypt_init(#ssh{encrypt = 'aes256-ctr', role = client} = Ssh) ->
- IV = hash(Ssh, "A", 128),
- <<K:32/binary>> = hash(Ssh, "C", 256),
- State = crypto:stream_init(aes_ctr, K, IV),
- {ok, Ssh#ssh{encrypt_keys = K,
- encrypt_block_size = 16,
- encrypt_ctx = State}};
-encrypt_init(#ssh{encrypt = 'aes128-ctr', role = server} = Ssh) ->
- IV = hash(Ssh, "B", 128),
- <<K:16/binary>> = hash(Ssh, "D", 128),
- State = crypto:stream_init(aes_ctr, K, IV),
- {ok, Ssh#ssh{encrypt_keys = K,
- encrypt_block_size = 16,
- encrypt_ctx = State}};
-encrypt_init(#ssh{encrypt = 'aes192-ctr', role = server} = Ssh) ->
- IV = hash(Ssh, "B", 128),
- <<K:24/binary>> = hash(Ssh, "D", 192),
- State = crypto:stream_init(aes_ctr, K, IV),
- {ok, Ssh#ssh{encrypt_keys = K,
- encrypt_block_size = 16,
- encrypt_ctx = State}};
-encrypt_init(#ssh{encrypt = 'aes256-ctr', role = server} = Ssh) ->
- IV = hash(Ssh, "B", 128),
- <<K:32/binary>> = hash(Ssh, "D", 256),
- State = crypto:stream_init(aes_ctr, K, IV),
- {ok, Ssh#ssh{encrypt_keys = K,
- encrypt_block_size = 16,
- encrypt_ctx = State}}.
+
+encrypt_init(#ssh{encrypt = SshCipher, role = Role} = Ssh) ->
+ {IvMagic, KeyMagic} = encrypt_magic(Role),
+ #cipher{impl = CryptoCipher,
+ key_bytes = KeyBytes,
+ iv_bytes = IvBytes,
+ block_bytes = BlockBytes} = cipher(SshCipher),
+ IV = hash(Ssh, IvMagic, 8*IvBytes),
+ K = hash(Ssh, KeyMagic, 8*KeyBytes),
+ Ctx0 = crypto:crypto_init(CryptoCipher, K, IV, true),
+ {ok, Ssh#ssh{encrypt_block_size = BlockBytes,
+ encrypt_ctx = Ctx0}}.
encrypt_final(Ssh) ->
{ok, Ssh#ssh{encrypt = none,
@@ -1487,249 +1435,126 @@ encrypt_final(Ssh) ->
encrypt_ctx = undefined
}}.
+
encrypt(#ssh{encrypt = none} = Ssh, Data) ->
{Ssh, Data};
+
encrypt(#ssh{encrypt = '[email protected]',
encrypt_keys = {K1,K2},
send_sequence = Seq} = Ssh,
<<LenData:4/binary, PayloadData/binary>>) ->
%% Encrypt length
IV1 = <<0:8/unit:8, Seq:8/unit:8>>,
- {_,EncLen} = crypto:stream_encrypt(crypto:stream_init(chacha20, K1, IV1),
- LenData),
+ EncLen = crypto:crypto_one_shot(chacha20, K1, IV1, LenData, true),
%% Encrypt payload
IV2 = <<1:8/little-unit:8, Seq:8/unit:8>>,
- {_,EncPayloadData} = crypto:stream_encrypt(crypto:stream_init(chacha20, K2, IV2),
- PayloadData),
-
+ EncPayloadData = crypto:crypto_one_shot(chacha20, K2, IV2, PayloadData, true),
%% MAC tag
- {_,PolyKey} = crypto:stream_encrypt(crypto:stream_init(chacha20, K2, <<0:8/unit:8,Seq:8/unit:8>>),
- <<0:32/unit:8>>),
+ PolyKey = crypto:crypto_one_shot(chacha20, K2, <<0:8/unit:8,Seq:8/unit:8>>, <<0:32/unit:8>>, true),
EncBytes = <<EncLen/binary,EncPayloadData/binary>>,
Ctag = crypto:poly1305(PolyKey, EncBytes),
%% Result
{Ssh, {EncBytes,Ctag}};
-encrypt(#ssh{encrypt = 'AEAD_AES_128_GCM',
- encrypt_keys = K,
+
+encrypt(#ssh{encrypt = SshCipher,
+ encrypt_keys = K,
encrypt_ctx = IV0} = Ssh,
- <<LenData:4/binary, PayloadData/binary>>) ->
- {Ctext,Ctag} = crypto:block_encrypt(aes_gcm, K, IV0, {LenData,PayloadData}),
- IV = next_gcm_iv(IV0),
- {Ssh#ssh{encrypt_ctx = IV}, {<<LenData/binary,Ctext/binary>>,Ctag}};
-encrypt(#ssh{encrypt = 'AEAD_AES_256_GCM',
- encrypt_keys = K,
- encrypt_ctx = IV0} = Ssh,
- <<LenData:4/binary, PayloadData/binary>>) ->
+ <<LenData:4/binary, PayloadData/binary>>) when SshCipher == 'AEAD_AES_128_GCM' ;
+ SshCipher == 'AEAD_AES_256_GCM' ->
{Ctext,Ctag} = crypto:block_encrypt(aes_gcm, K, IV0, {LenData,PayloadData}),
IV = next_gcm_iv(IV0),
{Ssh#ssh{encrypt_ctx = IV}, {<<LenData/binary,Ctext/binary>>,Ctag}};
-encrypt(#ssh{encrypt = '3des-cbc',
- encrypt_keys = {K1,K2,K3},
- encrypt_ctx = IV0} = Ssh, Data) ->
- Enc = crypto:block_encrypt(des3_cbc, [K1,K2,K3], IV0, Data),
- IV = crypto:next_iv(des3_cbc, Enc),
- {Ssh#ssh{encrypt_ctx = IV}, Enc};
-encrypt(#ssh{encrypt = 'aes128-cbc',
- encrypt_keys = K,
- encrypt_ctx = IV0} = Ssh, Data) ->
- Enc = crypto:block_encrypt(aes_cbc128, K,IV0,Data),
- IV = crypto:next_iv(aes_cbc, Enc),
- {Ssh#ssh{encrypt_ctx = IV}, Enc};
-encrypt(#ssh{encrypt = 'aes128-ctr',
- encrypt_ctx = State0} = Ssh, Data) ->
- {State, Enc} = crypto:stream_encrypt(State0,Data),
- {Ssh#ssh{encrypt_ctx = State}, Enc};
-encrypt(#ssh{encrypt = 'aes192-ctr',
- encrypt_ctx = State0} = Ssh, Data) ->
- {State, Enc} = crypto:stream_encrypt(State0,Data),
- {Ssh#ssh{encrypt_ctx = State}, Enc};
-encrypt(#ssh{encrypt = 'aes256-ctr',
- encrypt_ctx = State0} = Ssh, Data) ->
- {State, Enc} = crypto:stream_encrypt(State0,Data),
- {Ssh#ssh{encrypt_ctx = State}, Enc}.
-
+encrypt(#ssh{encrypt_ctx = Ctx0} = Ssh, Data) ->
+ Enc = crypto:crypto_update(Ctx0, Data),
+ {Ssh, Enc}.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Decryption
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
decrypt_init(#ssh{decrypt = none} = Ssh) ->
{ok, Ssh};
-decrypt_init(#ssh{decrypt = '[email protected]', role = client} = Ssh) ->
- <<K2:32/binary,K1:32/binary>> = hash(Ssh, "D", 512),
- {ok, Ssh#ssh{decrypt_keys = {K1,K2}
- }};
-decrypt_init(#ssh{decrypt = '[email protected]', role = server} = Ssh) ->
- <<K2:32/binary,K1:32/binary>> = hash(Ssh, "C", 512),
+
+decrypt_init(#ssh{decrypt = '[email protected]', role = Role} = Ssh) ->
+ {_, KeyMagic} = decrypt_magic(Role),
+ <<K2:32/binary,K1:32/binary>> = hash(Ssh, KeyMagic, 8*64),
{ok, Ssh#ssh{decrypt_keys = {K1,K2}
}};
-decrypt_init(#ssh{decrypt = 'AEAD_AES_128_GCM', role = client} = Ssh) ->
- IV = hash(Ssh, "B", 12*8),
- <<K:16/binary>> = hash(Ssh, "D", 128),
- {ok, Ssh#ssh{decrypt_keys = K,
- decrypt_block_size = 16,
- decrypt_ctx = IV}};
-decrypt_init(#ssh{decrypt = 'AEAD_AES_128_GCM', role = server} = Ssh) ->
- IV = hash(Ssh, "A", 12*8),
- <<K:16/binary>> = hash(Ssh, "C", 128),
- {ok, Ssh#ssh{decrypt_keys = K,
- decrypt_block_size = 16,
- decrypt_ctx = IV}};
-decrypt_init(#ssh{decrypt = 'AEAD_AES_256_GCM', role = client} = Ssh) ->
- IV = hash(Ssh, "B", 12*8),
- <<K:32/binary>> = hash(Ssh, "D", 256),
- {ok, Ssh#ssh{decrypt_keys = K,
- decrypt_block_size = 16,
- decrypt_ctx = IV}};
-decrypt_init(#ssh{decrypt = 'AEAD_AES_256_GCM', role = server} = Ssh) ->
- IV = hash(Ssh, "A", 12*8),
- <<K:32/binary>> = hash(Ssh, "C", 256),
+
+decrypt_init(#ssh{decrypt = SshCipher, role = Role} = Ssh) when SshCipher == 'AEAD_AES_128_GCM';
+ SshCipher == 'AEAD_AES_256_GCM' ->
+ {IvMagic, KeyMagic} = decrypt_magic(Role),
+ #cipher{key_bytes = KeyBytes,
+ iv_bytes = IvBytes,
+ block_bytes = BlockBytes} = cipher(SshCipher),
+ IV = hash(Ssh, IvMagic, 8*IvBytes),
+ K = hash(Ssh, KeyMagic, 8*KeyBytes),
{ok, Ssh#ssh{decrypt_keys = K,
- decrypt_block_size = 16,
+ decrypt_block_size = BlockBytes,
decrypt_ctx = IV}};
-decrypt_init(#ssh{decrypt = '3des-cbc', role = client} = Ssh) ->
- {IV, KD} = {hash(Ssh, "B", 64),
- hash(Ssh, "D", 192)},
- <<K1:8/binary, K2:8/binary, K3:8/binary>> = KD,
- {ok, Ssh#ssh{decrypt_keys = {K1,K2,K3}, decrypt_ctx = IV,
- decrypt_block_size = 8}};
-decrypt_init(#ssh{decrypt = '3des-cbc', role = server} = Ssh) ->
- {IV, KD} = {hash(Ssh, "A", 64),
- hash(Ssh, "C", 192)},
- <<K1:8/binary, K2:8/binary, K3:8/binary>> = KD,
- {ok, Ssh#ssh{decrypt_keys = {K1, K2, K3}, decrypt_ctx = IV,
- decrypt_block_size = 8}};
-decrypt_init(#ssh{decrypt = 'aes128-cbc', role = client} = Ssh) ->
- {IV, KD} = {hash(Ssh, "B", 128),
- hash(Ssh, "D", 128)},
- <<K:16/binary>> = KD,
- {ok, Ssh#ssh{decrypt_keys = K, decrypt_ctx = IV,
- decrypt_block_size = 16}};
-decrypt_init(#ssh{decrypt = 'aes128-cbc', role = server} = Ssh) ->
- {IV, KD} = {hash(Ssh, "A", 128),
- hash(Ssh, "C", 128)},
- <<K:16/binary>> = KD,
- {ok, Ssh#ssh{decrypt_keys = K, decrypt_ctx = IV,
- decrypt_block_size = 16}};
-decrypt_init(#ssh{decrypt = 'aes128-ctr', role = client} = Ssh) ->
- IV = hash(Ssh, "B", 128),
- <<K:16/binary>> = hash(Ssh, "D", 128),
- State = crypto:stream_init(aes_ctr, K, IV),
- {ok, Ssh#ssh{decrypt_keys = K,
- decrypt_block_size = 16,
- decrypt_ctx = State}};
-decrypt_init(#ssh{decrypt = 'aes192-ctr', role = client} = Ssh) ->
- IV = hash(Ssh, "B", 128),
- <<K:24/binary>> = hash(Ssh, "D", 192),
- State = crypto:stream_init(aes_ctr, K, IV),
- {ok, Ssh#ssh{decrypt_keys = K,
- decrypt_block_size = 16,
- decrypt_ctx = State}};
-decrypt_init(#ssh{decrypt = 'aes256-ctr', role = client} = Ssh) ->
- IV = hash(Ssh, "B", 128),
- <<K:32/binary>> = hash(Ssh, "D", 256),
- State = crypto:stream_init(aes_ctr, K, IV),
- {ok, Ssh#ssh{decrypt_keys = K,
- decrypt_block_size = 16,
- decrypt_ctx = State}};
-decrypt_init(#ssh{decrypt = 'aes128-ctr', role = server} = Ssh) ->
- IV = hash(Ssh, "A", 128),
- <<K:16/binary>> = hash(Ssh, "C", 128),
- State = crypto:stream_init(aes_ctr, K, IV),
- {ok, Ssh#ssh{decrypt_keys = K,
- decrypt_block_size = 16,
- decrypt_ctx = State}};
-decrypt_init(#ssh{decrypt = 'aes192-ctr', role = server} = Ssh) ->
- IV = hash(Ssh, "A", 128),
- <<K:24/binary>> = hash(Ssh, "C", 192),
- State = crypto:stream_init(aes_ctr, K, IV),
- {ok, Ssh#ssh{decrypt_keys = K,
- decrypt_block_size = 16,
- decrypt_ctx = State}};
-decrypt_init(#ssh{decrypt = 'aes256-ctr', role = server} = Ssh) ->
- IV = hash(Ssh, "A", 128),
- <<K:32/binary>> = hash(Ssh, "C", 256),
- State = crypto:stream_init(aes_ctr, K, IV),
- {ok, Ssh#ssh{decrypt_keys = K,
- decrypt_block_size = 16,
- decrypt_ctx = State}}.
-
+decrypt_init(#ssh{decrypt = SshCipher, role = Role} = Ssh) ->
+ {IvMagic, KeyMagic} = decrypt_magic(Role),
+ #cipher{impl = CryptoCipher,
+ key_bytes = KeyBytes,
+ iv_bytes = IvBytes,
+ block_bytes = BlockBytes} = cipher(SshCipher),
+ IV = hash(Ssh, IvMagic, 8*IvBytes),
+ K = hash(Ssh, KeyMagic, 8*KeyBytes),
+ Ctx0 = crypto:crypto_init(CryptoCipher, K, IV, false),
+ {ok, Ssh#ssh{decrypt_block_size = BlockBytes,
+ decrypt_ctx = Ctx0}}.
+
decrypt_final(Ssh) ->
{ok, Ssh#ssh {decrypt = none,
decrypt_keys = undefined,
decrypt_ctx = undefined,
decrypt_block_size = 8}}.
+
decrypt(Ssh, <<>>) ->
{Ssh, <<>>};
+
decrypt(#ssh{decrypt = '[email protected]',
decrypt_keys = {K1,_K2},
recv_sequence = Seq} = Ssh, {length,EncryptedLen}) ->
- {_State,PacketLenBin} =
- crypto:stream_decrypt(crypto:stream_init(chacha20, K1, <<0:8/unit:8, Seq:8/unit:8>>),
- EncryptedLen),
+ PacketLenBin = crypto:crypto_one_shot(chacha20, K1, <<0:8/unit:8, Seq:8/unit:8>>, EncryptedLen, false),
{Ssh, PacketLenBin};
+
decrypt(#ssh{decrypt = '[email protected]',
decrypt_keys = {_K1,K2},
recv_sequence = Seq} = Ssh, {AAD,Ctext,Ctag}) ->
%% The length is already decoded and used to divide the input
%% Check the mac (important that it is timing-safe):
- {_,PolyKey} =
- crypto:stream_encrypt(crypto:stream_init(chacha20, K2, <<0:8/unit:8,Seq:8/unit:8>>),
- <<0:32/unit:8>>),
+ PolyKey = crypto:crypto_one_shot(chacha20, K2, <<0:8/unit:8,Seq:8/unit:8>>, <<0:32/unit:8>>, false),
case equal_const_time(Ctag, crypto:poly1305(PolyKey, <<AAD/binary,Ctext/binary>>)) of
true ->
%% MAC is ok, decode
IV2 = <<1:8/little-unit:8, Seq:8/unit:8>>,
- {_,PlainText} =
- crypto:stream_decrypt(crypto:stream_init(chacha20,K2,IV2), Ctext),
+ PlainText = crypto:crypto_one_shot(chacha20, K2, IV2, Ctext, false),
{Ssh, PlainText};
false ->
{Ssh,error}
end;
+
decrypt(#ssh{decrypt = none} = Ssh, Data) ->
{Ssh, Data};
-decrypt(#ssh{decrypt = 'AEAD_AES_128_GCM',
- decrypt_keys = K,
- decrypt_ctx = IV0} = Ssh, Data = {_AAD,_Ctext,_Ctag}) ->
- Dec = crypto:block_decrypt(aes_gcm, K, IV0, Data), % Dec = PlainText | error
- IV = next_gcm_iv(IV0),
- {Ssh#ssh{decrypt_ctx = IV}, Dec};
-decrypt(#ssh{decrypt = 'AEAD_AES_256_GCM',
+
+decrypt(#ssh{decrypt = SshCipher,
decrypt_keys = K,
- decrypt_ctx = IV0} = Ssh, Data = {_AAD,_Ctext,_Ctag}) ->
+ decrypt_ctx = IV0} = Ssh, Data = {_AAD,_Ctext,_Ctag}) when SshCipher == 'AEAD_AES_128_GCM' ;
+ SshCipher == 'AEAD_AES_256_GCM' ->
Dec = crypto:block_decrypt(aes_gcm, K, IV0, Data), % Dec = PlainText | error
IV = next_gcm_iv(IV0),
{Ssh#ssh{decrypt_ctx = IV}, Dec};
-decrypt(#ssh{decrypt = '3des-cbc', decrypt_keys = Keys,
- decrypt_ctx = IV0} = Ssh, Data) ->
- {K1, K2, K3} = Keys,
- Dec = crypto:block_decrypt(des3_cbc, [K1,K2,K3], IV0, Data),
- IV = crypto:next_iv(des3_cbc, Data),
- {Ssh#ssh{decrypt_ctx = IV}, Dec};
-decrypt(#ssh{decrypt = 'aes128-cbc', decrypt_keys = Key,
- decrypt_ctx = IV0} = Ssh, Data) ->
- Dec = crypto:block_decrypt(aes_cbc128, Key,IV0,Data),
- IV = crypto:next_iv(aes_cbc, Data),
- {Ssh#ssh{decrypt_ctx = IV}, Dec};
-decrypt(#ssh{decrypt = 'aes128-ctr',
- decrypt_ctx = State0} = Ssh, Data) ->
- {State, Enc} = crypto:stream_decrypt(State0,Data),
- {Ssh#ssh{decrypt_ctx = State}, Enc};
-decrypt(#ssh{decrypt = 'aes192-ctr',
- decrypt_ctx = State0} = Ssh, Data) ->
- {State, Enc} = crypto:stream_decrypt(State0,Data),
- {Ssh#ssh{decrypt_ctx = State}, Enc};
-decrypt(#ssh{decrypt = 'aes256-ctr',
- decrypt_ctx = State0} = Ssh, Data) ->
- {State, Enc} = crypto:stream_decrypt(State0,Data),
- {Ssh#ssh{decrypt_ctx = State}, Enc}.
+decrypt(#ssh{decrypt_ctx = Ctx0} = Ssh, Data) ->
+ Dec = crypto:crypto_update(Ctx0, Data),
+ {Ssh, Dec}.
next_gcm_iv(<<Fixed:32, InvCtr:64>>) -> <<Fixed:32, (InvCtr+1):64>>.
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Compression
%%
@@ -2058,9 +1883,9 @@ compute_key(Algorithm, OthersPublic, MyPrivate, Args) ->
dh_bits(#alg{encrypt = Encrypt,
send_mac = SendMac}) ->
C = cipher(Encrypt),
- 8 * lists:max([C#cipher_data.key_bytes,
- C#cipher_data.block_bytes,
- C#cipher_data.iv_bytes,
+ 8 * lists:max([C#cipher.key_bytes,
+ C#cipher.block_bytes,
+ C#cipher.iv_bytes,
mac_key_bytes(SendMac)
]).
@@ -2091,40 +1916,13 @@ select_crypto_supported(L) ->
crypto_supported(Conditions, Supported) ->
lists:all( fun({Tag,CryptoName}) when is_atom(CryptoName) ->
- crypto_name_supported(Tag,CryptoName,Supported);
- ({Tag,{Name,Len}}) when is_integer(Len) ->
- crypto_name_supported(Tag,Name,Supported) andalso
- len_supported(Name,Len)
+ crypto_name_supported(Tag,CryptoName,Supported)
end, Conditions).
crypto_name_supported(Tag, CryptoName, Supported) ->
- Vs = case proplists:get_value(Tag,Supported,[]) of
- [] when Tag == curves -> crypto:ec_curves();
- L -> L
- end,
+ Vs = proplists:get_value(Tag,Supported,[]),
lists:member(CryptoName, Vs).
-len_supported(Name, Len) ->
- try
- case Name of
- aes_ctr ->
- {_, <<_/binary>>} =
- %% Test encryption
- crypto:stream_encrypt(crypto:stream_init(Name, <<0:Len>>, <<0:128>>), <<"">>);
- aes_gcm ->
- {<<_/binary>>, <<_/binary>>} =
- crypto:block_encrypt(Name,
- _Key = <<0:Len>>,
- _IV = <<0:12/unsigned-unit:8>>,
- {<<"AAD">>,"PT"})
- end
- of
- _ -> true
- catch
- _:_ -> false
- end.
-
-
same(Algs) -> [{client2server,Algs}, {server2client,Algs}].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk
index 2890d7fe5b..0f9eee887c 100644
--- a/lib/ssh/vsn.mk
+++ b/lib/ssh/vsn.mk
@@ -1,4 +1,4 @@
#-*-makefile-*- ; force emacs to enter makefile-mode
-SSH_VSN = 4.7.3
+SSH_VSN = 4.7.4
APP_VSN = "ssh-$(SSH_VSN)"
diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml
index 82eb8ff700..a511cb4db3 100644
--- a/lib/ssl/doc/src/notes.xml
+++ b/lib/ssl/doc/src/notes.xml
@@ -27,6 +27,117 @@
</header>
<p>This document describes the changes made to the SSL application.</p>
+<section><title>SSL 9.2.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The timeout for a passive receive was sometimes not
+ cancelled and later caused a server crash. This bug has
+ now been corrected.</p>
+ <p>
+ Own Id: OTP-14701 Aux Id: ERL-883, ERL-884 </p>
+ </item>
+ <item>
+ <p>
+ Add tag for passive message (active N) in cb_info to
+ retain transport transparency.</p>
+ <p>
+ Own Id: OTP-15679 Aux Id: ERL-861 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 9.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix bug that an incorrect return value for gen_statem
+ could be created when alert was a result of handling
+ renegotiation info extension</p>
+ <p>
+ Own Id: OTP-15502</p>
+ </item>
+ <item>
+ <p>
+ Correct check for 3des_ede_cbc, could cause ssl to claim
+ to support 3des_ede_cbc when cryptolib does not.</p>
+ <p>
+ Own Id: OTP-15539</p>
+ </item>
+ <item>
+ <p>
+ Improved DTLS error handling, avoids unexpected
+ connection failure in rare cases.</p>
+ <p>
+ Own Id: OTP-15561</p>
+ </item>
+ <item>
+ <p>
+ Corrected active once emulation bug that could cause the
+ ssl_closed meassage to not be sent. Bug introduced by
+ OTP-15449</p>
+ <p>
+ Own Id: OTP-15666 Aux Id: ERIERL-316, </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add client option {reuse_session, SessionID::binary()}
+ that can be used together with new option value
+ {reuse_sessions, save}. This makes it possible to reuse a
+ session from a specific connection establishment.</p>
+ <p>
+ Own Id: OTP-15369</p>
+ </item>
+ <item>
+ <p>
+ The Reason part of of the error return from the functions
+ connect and handshake has a better and documented format.
+ This will sometimes differ from previous returned
+ reasons, however those where only documented as term()
+ and should for that reason not be relied on.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15423</p>
+ </item>
+ <item>
+ <p>
+ Refactor of state handling to improve TLS application
+ data throughput and reduce CPU overhead</p>
+ <p>
+ Own Id: OTP-15445</p>
+ </item>
+ <item>
+ <p>
+ The SSL code has been optimized in many small ways to
+ reduce CPU load for encryption/decryption, especially for
+ Erlang's distribution protocol over TLS.</p>
+ <p>
+ Own Id: OTP-15529</p>
+ </item>
+ <item>
+ <p>
+ Add support for active N</p>
+ <p>
+ Own Id: OTP-15665 Aux Id: ERL-811, PR-2072 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SSL 9.1.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml
index b145aac6ab..37bf9033a1 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -101,16 +101,21 @@
<datatype>
<name name="transport_option"/>
<desc>
- <p>Defaults to <c>{gen_tcp, tcp, tcp_closed, tcp_error}</c>
- for TLS and <c>{gen_udp, udp, udp_closed, udp_error}</c> for
- DTLS. Can be used to customize the transport layer. The tag
- values should be the values used by the underlying transport
- in its active mode messages. For TLS the callback module must implement a
- reliable transport protocol, behave as <c>gen_tcp</c>, and have functions
- corresponding to <c>inet:setopts/2</c>, <c>inet:getopts/2</c>,
- <c>inet:peername/1</c>, <c>inet:sockname/1</c>, and <c>inet:port/1</c>.
- The callback <c>gen_tcp</c> is treated specially and calls <c>inet</c>
- directly. For DTLS this feature must be considered exprimental.
+ <p>Defaults to <c>{gen_tcp, tcp, tcp_closed, tcp_error,
+ tcp_passive}</c> for TLS (for backward compatibility a four
+ tuple will be converted to a five tuple with the last element
+ "second_element"_passive) and <c>{gen_udp, udp, udp_closed,
+ udp_error}</c> for DTLS (might also be changed to five tuple in
+ the future). Can be used to customize the transport layer. The
+ tag values should be the values used by the underlying
+ transport in its active mode messages. For TLS the callback
+ module must implement a reliable transport protocol, behave as
+ <c>gen_tcp</c>, and have functions corresponding to
+ <c>inet:setopts/2</c>, <c>inet:getopts/2</c>,
+ <c>inet:peername/1</c>, <c>inet:sockname/1</c>, and
+ <c>inet:port/1</c>. The callback <c>gen_tcp</c> is treated
+ specially and calls <c>inet</c> directly. For DTLS this
+ feature must be considered exprimental.
</p>
</desc>
</datatype>
@@ -1658,17 +1663,6 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name since="OTP 22.0">set_log_level(Level) -> ok | {error, Reason}</name>
- <fsummary>Sets log level for the SSL application.</fsummary>
- <type>
- <v>Level = atom()</v>
- </type>
- <desc>
- <p>Sets log level for the SSL application.</p>
- </desc>
- </func>
-
- <func>
<name since="OTP R14B">shutdown(SslSocket, How) -> ok | {error, Reason}</name>
<fsummary>Immediately closes a socket.</fsummary>
<type>
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 5a2d31ffc2..bfa349c8d8 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -55,8 +55,7 @@
format_error/1, renegotiate/1, prf/5, negotiated_protocol/1,
connection_information/1, connection_information/2]).
%% Misc
--export([handle_options/2, tls_version/1, new_ssl_options/3, suite_to_str/1,
- set_log_level/1]).
+-export([handle_options/2, tls_version/1, new_ssl_options/3, suite_to_str/1]).
-deprecated({ssl_accept, 1, eventually}).
-deprecated({ssl_accept, 2, eventually}).
@@ -96,7 +95,9 @@
-type active_msgs() :: {ssl, sslsocket(), Data::binary() | list()} | {ssl_closed, sslsocket()} |
{ssl_error, sslsocket(), Reason::term()} | {ssl_passive, sslsocket()}.
-type transport_option() :: {cb_info, {CallbackModule::atom(), DataTag::atom(),
- ClosedTag::atom(), ErrTag::atom()}}.
+ ClosedTag::atom(), ErrTag::atom()}} |
+ {cb_info, {CallbackModule::atom(), DataTag::atom(),
+ ClosedTag::atom(), ErrTag::atom(), PassiveTag::atom()}}.
-type host() :: hostname() | ip_address().
-type hostname() :: string().
-type ip_address() :: inet:ip_address().
@@ -422,9 +423,9 @@ connect(Socket, SslOptions) when is_port(Socket) ->
timeout() | list()) ->
{ok, #sslsocket{}} | {error, reason()}.
connect(Socket, SslOptions0, Timeout) when is_port(Socket),
- (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
- {Transport,_,_,_} = proplists:get_value(cb_info, SslOptions0,
- {gen_tcp, tcp, tcp_closed, tcp_error}),
+ (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
+ CbInfo = handle_option(cb_info, SslOptions0, default_cb_info(tls)),
+ Transport = element(1, CbInfo),
EmulatedOptions = tls_socket:emulated_options(),
{ok, SocketValues} = tls_socket:getopts(Transport, Socket, EmulatedOptions),
try handle_options(SslOptions0 ++ SocketValues, client) of
@@ -572,8 +573,8 @@ handshake(#sslsocket{pid = [Pid|_], fd = {_, _, _}} = Socket, SslOpts, Timeout)
end;
handshake(Socket, SslOptions, Timeout) when is_port(Socket),
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
- {Transport,_,_,_} =
- proplists:get_value(cb_info, SslOptions, {gen_tcp, tcp, tcp_closed, tcp_error}),
+ CbInfo = handle_option(cb_info, SslOptions, default_cb_info(tls)),
+ Transport = element(1, CbInfo),
EmulatedOptions = tls_socket:emulated_options(),
{ok, SocketValues} = tls_socket:getopts(Transport, Socket, EmulatedOptions),
ConnetionCb = connection_cb(SslOptions),
@@ -625,7 +626,7 @@ close(#sslsocket{pid = [Pid|_]}) when is_pid(Pid) ->
ssl_connection:close(Pid, {close, ?DEFAULT_TIMEOUT});
close(#sslsocket{pid = {dtls, #config{dtls_handler = {Pid, _}}}}) ->
dtls_packet_demux:close(Pid);
-close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_, _, _}}}}) ->
+close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}) ->
Transport:close(ListenSocket).
%%--------------------------------------------------------------------
@@ -641,7 +642,7 @@ close(#sslsocket{pid = [TLSPid|_]},
close(#sslsocket{pid = [TLSPid|_]}, Timeout) when is_pid(TLSPid),
(is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) ->
ssl_connection:close(TLSPid, {close, Timeout});
-close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_, _, _}}}}, _) ->
+close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_,_,_,_}}}}, _) ->
Transport:close(ListenSocket).
%%--------------------------------------------------------------------
@@ -657,7 +658,8 @@ send(#sslsocket{pid = {_, #config{transport_info={_, udp, _, _}}}}, _) ->
{error,enotconn}; %% Emulate connection behaviour
send(#sslsocket{pid = {dtls,_}}, _) ->
{error,enotconn}; %% Emulate connection behaviour
-send(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport, _, _, _}}}}, Data) ->
+send(#sslsocket{pid = {ListenSocket, #config{transport_info = Info}}}, Data) ->
+ Transport = element(1, Info),
Transport:send(ListenSocket, Data). %% {error,enotconn}
%%--------------------------------------------------------------------
@@ -675,7 +677,8 @@ recv(#sslsocket{pid = [Pid|_]}, Length, Timeout) when is_pid(Pid),
recv(#sslsocket{pid = {dtls,_}}, _, _) ->
{error,enotconn};
recv(#sslsocket{pid = {Listen,
- #config{transport_info = {Transport, _, _, _}}}}, _,_) when is_port(Listen)->
+ #config{transport_info = Info}}},_,_) when is_port(Listen)->
+ Transport = element(1, Info),
Transport:recv(Listen, 0). %% {error,enotconn}
%%--------------------------------------------------------------------
@@ -690,7 +693,7 @@ controlling_process(#sslsocket{pid = {dtls, _}},
NewOwner) when is_pid(NewOwner) ->
ok; %% Meaningless but let it be allowed to conform with TLS
controlling_process(#sslsocket{pid = {Listen,
- #config{transport_info = {Transport, _, _, _}}}},
+ #config{transport_info = {Transport,_,_,_,_}}}},
NewOwner) when is_port(Listen),
is_pid(NewOwner) ->
%% Meaningless but let it be allowed to conform with normal sockets
@@ -733,13 +736,13 @@ connection_information(#sslsocket{pid = [Pid|_]}, Items) when is_pid(Pid) ->
%%
%% Description: same as inet:peername/1.
%%--------------------------------------------------------------------
-peername(#sslsocket{pid = [Pid|_], fd = {Transport, Socket, _}}) when is_pid(Pid)->
+peername(#sslsocket{pid = [Pid|_], fd = {Transport, Socket,_}}) when is_pid(Pid)->
dtls_socket:peername(Transport, Socket);
-peername(#sslsocket{pid = [Pid|_], fd = {Transport, Socket, _, _}}) when is_pid(Pid)->
+peername(#sslsocket{pid = [Pid|_], fd = {Transport, Socket,_,_}}) when is_pid(Pid)->
tls_socket:peername(Transport, Socket);
-peername(#sslsocket{pid = {dtls, #config{dtls_handler = {_Pid, _}}}}) ->
+peername(#sslsocket{pid = {dtls, #config{dtls_handler = {_Pid,_}}}}) ->
dtls_socket:peername(dtls, undefined);
-peername(#sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_}}}}) ->
+peername(#sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_,_}}}}) ->
tls_socket:peername(Transport, ListenSocket); %% Will return {error, enotconn}
peername(#sslsocket{pid = {dtls,_}}) ->
{error,enotconn}.
@@ -931,7 +934,7 @@ getopts(#sslsocket{pid = {dtls, #config{transport_info = {Transport,_,_,_}}}} =
_:Error ->
{error, {options, {socket_options, OptionTags, Error}}}
end;
-getopts(#sslsocket{pid = {_, #config{transport_info = {Transport,_,_,_}}}} = ListenSocket,
+getopts(#sslsocket{pid = {_, #config{transport_info = {Transport,_,_,_,_}}}} = ListenSocket,
OptionTags) when is_list(OptionTags) ->
try tls_socket:getopts(Transport, ListenSocket, OptionTags) of
{ok, _} = Result ->
@@ -988,7 +991,7 @@ setopts(#sslsocket{pid = {dtls, #config{transport_info = {Transport,_,_,_}}}} =
_:Error ->
{error, {options, {socket_options, Options, Error}}}
end;
-setopts(#sslsocket{pid = {_, #config{transport_info = {Transport,_,_,_}}}} = ListenSocket, Options) when is_list(Options) ->
+setopts(#sslsocket{pid = {_, #config{transport_info = {Transport,_,_,_,_}}}} = ListenSocket, Options) when is_list(Options) ->
try tls_socket:setopts(Transport, ListenSocket, Options) of
ok ->
ok;
@@ -1032,8 +1035,9 @@ getstat(#sslsocket{pid = [Pid|_], fd = {Transport, Socket, _, _}}, Options) when
%%
%% Description: Same as gen_tcp:shutdown/2
%%--------------------------------------------------------------------
-shutdown(#sslsocket{pid = {Listen, #config{transport_info = {Transport,_, _, _}}}},
+shutdown(#sslsocket{pid = {Listen, #config{transport_info = Info}}},
How) when is_port(Listen) ->
+ Transport = element(1, Info),
Transport:shutdown(Listen, How);
shutdown(#sslsocket{pid = {dtls,_}},_) ->
{error, enotconn};
@@ -1045,13 +1049,13 @@ shutdown(#sslsocket{pid = [Pid|_]}, How) when is_pid(Pid) ->
%%
%% Description: Same as inet:sockname/1
%%--------------------------------------------------------------------
-sockname(#sslsocket{pid = {Listen, #config{transport_info = {Transport, _, _, _}}}}) when is_port(Listen) ->
+sockname(#sslsocket{pid = {Listen, #config{transport_info = {Transport,_,_,_,_}}}}) when is_port(Listen) ->
tls_socket:sockname(Transport, Listen);
sockname(#sslsocket{pid = {dtls, #config{dtls_handler = {Pid, _}}}}) ->
dtls_packet_demux:sockname(Pid);
-sockname(#sslsocket{pid = [Pid|_], fd = {Transport, Socket, _}}) when is_pid(Pid) ->
+sockname(#sslsocket{pid = [Pid|_], fd = {Transport, Socket,_}}) when is_pid(Pid) ->
dtls_socket:sockname(Transport, Socket);
-sockname(#sslsocket{pid = [Pid| _], fd = {Transport, Socket, _, _}}) when is_pid(Pid) ->
+sockname(#sslsocket{pid = [Pid| _], fd = {Transport, Socket,_,_}}) when is_pid(Pid) ->
tls_socket:sockname(Transport, Socket).
%%---------------------------------------------------------------
@@ -1167,32 +1171,6 @@ suite_to_str(Cipher) ->
ssl_cipher_format:suite_to_str(Cipher).
-%%--------------------------------------------------------------------
--spec set_log_level(atom()) -> ok | {error, term()}.
-%%
-%% Description: Set log level for the SSL application
-%%--------------------------------------------------------------------
-set_log_level(Level) ->
- case application:get_all_key(ssl) of
- {ok, PropList} ->
- Modules = proplists:get_value(modules, PropList),
- set_module_level(Modules, Level);
- undefined ->
- {error, ssl_not_started}
- end.
-
-set_module_level(Modules, Level) ->
- Fun = fun (Module) ->
- ok = logger:set_module_level(Module, Level)
- end,
- try lists:map(Fun, Modules) of
- _ ->
- ok
- catch
- error:{badmatch, Error} ->
- Error
- end.
-
%%%--------------------------------------------------------------
%%% Internal functions
%%%--------------------------------------------------------------------
@@ -1211,7 +1189,7 @@ supported_suites(all, Version) ->
supported_suites(anonymous, Version) ->
ssl_cipher:anonymous_suites(Version).
-do_listen(Port, #config{transport_info = {Transport, _, _, _}} = Config, tls_connection) ->
+do_listen(Port, #config{transport_info = {Transport, _, _, _,_}} = Config, tls_connection) ->
tls_socket:listen(Transport, Port, Config);
do_listen(Port, Config, dtls_connection) ->
@@ -1381,7 +1359,7 @@ handle_options(Opts0, Role, Host) ->
log_level = handle_option(log_level, Opts, LogLevel)
},
- CbInfo = proplists:get_value(cb_info, Opts, default_cb_info(Protocol)),
+ CbInfo = handle_option(cb_info, Opts, default_cb_info(Protocol)),
SslOptions = [protocol, versions, verify, verify_fun, partial_chain,
fail_if_no_peer_cert, verify_client_once,
depth, cert, certfile, key, keyfile,
@@ -1425,6 +1403,10 @@ handle_option(sni_fun, Opts, Default) ->
_ ->
throw({error, {conflict_options, [sni_fun, sni_hosts]}})
end;
+handle_option(cb_info, Opts, Default) ->
+ CbInfo = proplists:get_value(cb_info, Opts, Default),
+ true = validate_option(cb_info, CbInfo),
+ handle_cb_info(CbInfo, Default);
handle_option(OptionName, Opts, Default) ->
validate_option(OptionName,
proplists:get_value(OptionName, Opts, Default)).
@@ -1659,9 +1641,29 @@ validate_option(handshake, full = Value) ->
Value;
validate_option(customize_hostname_check, Value) when is_list(Value) ->
Value;
+validate_option(cb_info, {V1, V2, V3, V4}) when is_atom(V1),
+ is_atom(V2),
+ is_atom(V3),
+ is_atom(V4)
+ ->
+ true;
+validate_option(cb_info, {V1, V2, V3, V4, V5}) when is_atom(V1),
+ is_atom(V2),
+ is_atom(V3),
+ is_atom(V4),
+ is_atom(V5)
+ ->
+ true;
+validate_option(cb_info, _) ->
+ false;
validate_option(Opt, Value) ->
throw({error, {options, {Opt, Value}}}).
+handle_cb_info({V1, V2, V3, V4}, {_,_,_,_,_}) ->
+ {V1,V2,V3,V4, list_to_atom(atom_to_list(V2) ++ "passive")};
+handle_cb_info(CbInfo, _) ->
+ CbInfo.
+
handle_hashsigns_option(Value, Version) when is_list(Value)
andalso Version >= {3, 4} ->
case tls_v1:signature_schemes(Version, Value) of
@@ -2132,7 +2134,7 @@ default_option_role(_,_,_) ->
default_cb_info(tls) ->
- {gen_tcp, tcp, tcp_closed, tcp_error};
+ {gen_tcp, tcp, tcp_closed, tcp_error, tcp_passive};
default_cb_info(dtls) ->
{gen_udp, udp, udp_closed, udp_error}.
diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl
index e17476f33b..06b1b005a5 100644
--- a/lib/ssl/src/ssl_alert.erl
+++ b/lib/ssl/src/ssl_alert.erl
@@ -161,6 +161,8 @@ description_txt(?INSUFFICIENT_SECURITY) ->
"Insufficient Security";
description_txt(?INTERNAL_ERROR) ->
"Internal Error";
+description_txt(?INAPPROPRIATE_FALLBACK) ->
+ "Inappropriate Fallback";
description_txt(?USER_CANCELED) ->
"User Canceled";
description_txt(?NO_RENEGOTIATION) ->
@@ -179,8 +181,6 @@ description_txt(?BAD_CERTIFICATE_HASH_VALUE) ->
"Bad Certificate Hash Value";
description_txt(?UNKNOWN_PSK_IDENTITY) ->
"Unknown Psk Identity";
-description_txt(?INAPPROPRIATE_FALLBACK) ->
- "Inappropriate Fallback";
description_txt(?CERTIFICATE_REQUIRED) ->
"Certificate required";
description_txt(?NO_APPLICATION_PROTOCOL) ->
@@ -232,10 +232,14 @@ description_atom(?INSUFFICIENT_SECURITY) ->
insufficient_security;
description_atom(?INTERNAL_ERROR) ->
internal_error;
+description_atom(?INAPPROPRIATE_FALLBACK) ->
+ inappropriate_fallback;
description_atom(?USER_CANCELED) ->
user_canceled;
description_atom(?NO_RENEGOTIATION) ->
no_renegotiation;
+description_atom(?MISSING_EXTENSION) ->
+ missing_extension;
description_atom(?UNSUPPORTED_EXTENSION) ->
unsupported_extension;
description_atom(?CERTIFICATE_UNOBTAINABLE) ->
@@ -248,9 +252,9 @@ description_atom(?BAD_CERTIFICATE_HASH_VALUE) ->
bad_certificate_hash_value;
description_atom(?UNKNOWN_PSK_IDENTITY) ->
unknown_psk_identity;
-description_atom(?INAPPROPRIATE_FALLBACK) ->
- inappropriate_fallback;
+description_atom(?CERTIFICATE_REQUIRED) ->
+ certificate_required;
description_atom(?NO_APPLICATION_PROTOCOL) ->
no_application_protocol;
description_atom(_) ->
- 'unsupported/unkonwn_alert'.
+ 'unsupported/unknown_alert'.
diff --git a/lib/ssl/src/ssl_app.erl b/lib/ssl/src/ssl_app.erl
index 2a5047c75c..9e6d676bef 100644
--- a/lib/ssl/src/ssl_app.erl
+++ b/lib/ssl/src/ssl_app.erl
@@ -44,11 +44,11 @@ start_logger() ->
formatter => {ssl_logger, #{}}},
Filter = {fun logger_filters:domain/2,{log,sub,[otp,ssl]}},
logger:add_handler(ssl_handler, logger_std_h, Config),
- logger:add_handler_filter(ssl_handler, filter_non_ssl, Filter).
+ logger:add_handler_filter(ssl_handler, filter_non_ssl, Filter),
+ logger:set_application_level(ssl, debug).
%%
%% Description: Stop SSL logger
stop_logger() ->
+ logger:unset_application_level(ssl),
logger:remove_handler(ssl_handler).
-
-
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index 6e751f9ceb..fe8736d2df 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -45,7 +45,7 @@
random_bytes/1, calc_mac_hash/4, calc_mac_hash/6,
is_stream_ciphersuite/1, signature_scheme/1,
scheme_to_components/1, hash_size/1, effective_key_bits/1,
- key_material/1]).
+ key_material/1, signature_algorithm_to_scheme/1]).
%% RFC 8446 TLS 1.3
-export([generate_client_shares/1, generate_server_share/1, add_zero_padding/2]).
@@ -900,6 +900,18 @@ scheme_to_components(rsa_pss_pss_sha512) -> {sha512, rsa_pss_pss, undefined};
scheme_to_components(rsa_pkcs1_sha1) -> {sha1, rsa_pkcs1, undefined};
scheme_to_components(ecdsa_sha1) -> {sha1, ecdsa, undefined}.
+
+%% TODO: Add support for EC and RSA-SSA signatures
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha1WithRSAEncryption}) ->
+ rsa_pkcs1_sha1;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha256WithRSAEncryption}) ->
+ rsa_pkcs1_sha256;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha384WithRSAEncryption}) ->
+ rsa_pkcs1_sha384;
+signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha512WithRSAEncryption}) ->
+ rsa_pkcs1_sha512.
+
+
%% RFC 5246: 6.2.3.2. CBC Block Cipher
%%
%% Implementation note: Canvel et al. [CBCTIME] have demonstrated a
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index e5b01cce5f..1e97fe046b 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -385,7 +385,8 @@ handle_alert(#alert{level = ?FATAL} = Alert, StateName,
log_alert(SslOpts#ssl_options.log_level, Role, Connection:protocol_name(),
StateName, Alert#alert{role = opposite_role(Role)}),
Pids = Connection:pids(State),
- alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role, Connection),
+ alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert,
+ opposite_role(Role), Connection),
{stop, {shutdown, normal}, State};
handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl
index 201164949a..ff7207a8ce 100644
--- a/lib/ssl/src/ssl_connection.hrl
+++ b/lib/ssl/src/ssl_connection.hrl
@@ -40,6 +40,7 @@
data_tag :: atom(), % ex tcp.
close_tag :: atom(), % ex tcp_closed
error_tag :: atom(), % ex tcp_error
+ passive_tag :: atom(), % ex tcp_passive
host :: string() | inet:ip_address(),
port :: integer(),
socket :: port() | tuple(), %% TODO: dtls socket
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index 260f603e90..6c95a7edf8 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -79,7 +79,10 @@
select_hashsign_algs/3, empty_extensions/2, add_server_share/3
]).
--export([get_cert_params/1]).
+-export([get_cert_params/1,
+ server_name/3,
+ validation_fun_and_state/9,
+ handle_path_validation_error/7]).
%%====================================================================
%% Create handshake messages
diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl
index d4233bea9b..b248edcaa9 100644
--- a/lib/ssl/src/ssl_handshake.hrl
+++ b/lib/ssl/src/ssl_handshake.hrl
@@ -47,7 +47,9 @@
srp_username,
is_resumable,
time_stamp,
- ecc
+ ecc, %% TLS 1.3 Group
+ sign_alg, %% TLS 1.3 Signature Algorithm
+ dh_public_value %% TLS 1.3 DH Public Value from peer
}).
-define(NUM_OF_SESSION_ID_BYTES, 32). % TSL 1.1 & SSL 3
diff --git a/lib/ssl/src/ssl_logger.erl b/lib/ssl/src/ssl_logger.erl
index b82b3937a1..f497315235 100644
--- a/lib/ssl/src/ssl_logger.erl
+++ b/lib/ssl/src/ssl_logger.erl
@@ -181,6 +181,11 @@ parse_handshake(Direction, #hello_request{} = HelloRequest) ->
[header_prefix(Direction)]),
Message = io_lib:format("~p", [?rec_info(hello_request, HelloRequest)]),
{Header, Message};
+parse_handshake(Direction, #certificate_request_1_3{} = CertificateRequest) ->
+ Header = io_lib:format("~s Handshake, CertificateRequest",
+ [header_prefix(Direction)]),
+ Message = io_lib:format("~p", [?rec_info(certificate_request_1_3, CertificateRequest)]),
+ {Header, Message};
parse_handshake(Direction, #certificate_1_3{} = Certificate) ->
Header = io_lib:format("~s Handshake, Certificate",
[header_prefix(Direction)]),
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 8eb9e56375..fde73cdef1 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -98,7 +98,7 @@
%% Setup
%%====================================================================
start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_, Tracker} = Opts,
- User, {CbModule, _,_, _} = CbInfo,
+ User, {CbModule, _,_, _, _} = CbInfo,
Timeout) ->
try
{ok, Sender} = tls_sender:start(),
@@ -112,7 +112,7 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_, Tracker}
end;
start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_, Tracker} = Opts,
- User, {CbModule, _,_, _} = CbInfo,
+ User, {CbModule, _,_, _, _} = CbInfo,
Timeout) ->
try
{ok, Sender} = tls_sender:start([{spawn_opt, ?DIST_CNTRL_SPAWN_OPTS}]),
@@ -251,13 +251,28 @@ next_event(StateName, Record, State, Actions) ->
%%% TLS record protocol level application data messages
-
-handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName0, State0) ->
+handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName,
+ #state{start_or_recv_from = From,
+ socket_options = #socket_options{active = false}} = State0) when From =/= undefined ->
+ case ssl_connection:read_application_data(Data, State0) of
+ {stop, _, _} = Stop->
+ Stop;
+ {Record, #state{start_or_recv_from = Caller} = State1} ->
+ TimerAction = case Caller of
+ undefined -> %% Passive recv complete cancel timer
+ [{{timeout, recv}, infinity, timeout}];
+ _ ->
+ []
+ end,
+ {next_state, StateName, State, Actions} = next_event(StateName, Record, State1, TimerAction),
+ ssl_connection:hibernate_after(StateName, State, Actions)
+ end;
+handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State0) ->
case ssl_connection:read_application_data(Data, State0) of
{stop, _, _} = Stop->
Stop;
{Record, State1} ->
- case next_event(StateName0, Record, State1) of
+ case next_event(StateName, Record, State1) of
{next_state, StateName, State, Actions} ->
ssl_connection:hibernate_after(StateName, State, Actions);
{stop, _, _} = Stop ->
@@ -308,9 +323,7 @@ handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
handle_alerts(Alerts, {next_state, StateName, State});
[] ->
ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, empty_alert),
- Version, StateName, State);
- #alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State)
+ Version, StateName, State)
catch
_:_ ->
ssl_connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, alert_decode_error),
@@ -941,7 +954,7 @@ code_change(_OldVsn, StateName, State, _) ->
%%% Internal functions
%%--------------------------------------------------------------------
initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, User,
- {CbModule, DataTag, CloseTag, ErrorTag}) ->
+ {CbModule, DataTag, CloseTag, ErrorTag, PassiveTag}) ->
#ssl_options{beast_mitigation = BeastMitigation,
erl_dist = IsErlDist} = SSLOptions,
ConnectionStates = tls_record:init_connection_states(Role, BeastMitigation),
@@ -965,6 +978,7 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
data_tag = DataTag,
close_tag = CloseTag,
error_tag = ErrorTag,
+ passive_tag = PassiveTag,
host = Host,
port = Port,
socket = Socket,
@@ -1061,8 +1075,9 @@ handle_info({Protocol, _, Data}, StateName,
ssl_connection:handle_normal_shutdown(Alert, StateName, State0),
{stop, {shutdown, own_alert}, State0}
end;
-handle_info({tcp_passive, Socket}, StateName,
- #state{static_env = #static_env{socket = Socket},
+handle_info({PassiveTag, Socket}, StateName,
+ #state{static_env = #static_env{socket = Socket,
+ passive_tag = PassiveTag},
protocol_specific = PS
} = State) ->
next_event(StateName, no_record,
@@ -1135,6 +1150,7 @@ encode_handshake(Handshake, Version, ConnectionStates0, Hist0) ->
encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) ->
tls_record:encode_change_cipher_spec(Version, ConnectionStates).
+-spec decode_alerts(binary()) -> list().
decode_alerts(Bin) ->
ssl_alert:decode(Bin).
diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl
index 71ac6a9310..701a5860c2 100644
--- a/lib/ssl/src/tls_connection_1_3.erl
+++ b/lib/ssl/src/tls_connection_1_3.erl
@@ -110,51 +110,75 @@
%% gen_statem helper functions
-export([start/4,
negotiated/4,
+ wait_cert/4,
+ wait_cv/4,
wait_finished/4
]).
-start(internal,
- #change_cipher_spec{} = ChangeCipherSpec, State0, _Module) ->
- case tls_handshake_1_3:do_start(ChangeCipherSpec, State0) of
- #alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, start, State0);
- State1 ->
- {Record, State} = tls_connection:next_record(State1),
- tls_connection:next_event(?FUNCTION_NAME, Record, State)
- end;
+start(internal, #change_cipher_spec{}, State0, _Module) ->
+ {Record, State} = tls_connection:next_record(State0),
+ tls_connection:next_event(?FUNCTION_NAME, Record, State);
start(internal, #client_hello{} = Hello, State0, _Module) ->
case tls_handshake_1_3:do_start(Hello, State0) of
#alert{} = Alert ->
ssl_connection:handle_own_alert(Alert, {3,4}, start, State0);
- {State, _, start} ->
+ {State, start} ->
{next_state, start, State, []};
- {State, Context, negotiated} ->
- {next_state, negotiated, State, [{next_event, internal, Context}]}
+ {State, negotiated} ->
+ {next_state, negotiated, State, [{next_event, internal, start_handshake}]}
end;
start(Type, Msg, State, Connection) ->
ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-negotiated(internal, Map, State0, _Module) ->
- case tls_handshake_1_3:do_negotiated(Map, State0) of
+negotiated(internal, #change_cipher_spec{}, State0, _Module) ->
+ {Record, State} = tls_connection:next_record(State0),
+ tls_connection:next_event(?FUNCTION_NAME, Record, State);
+negotiated(internal, Message, State0, _Module) ->
+ case tls_handshake_1_3:do_negotiated(Message, State0) of
#alert{} = Alert ->
ssl_connection:handle_own_alert(Alert, {3,4}, negotiated, State0);
- State ->
- {next_state, wait_finished, State, []}
-
+ {State, NextState} ->
+ {next_state, NextState, State, []}
end.
-wait_finished(internal,
- #change_cipher_spec{} = ChangeCipherSpec, State0, _Module) ->
- case tls_handshake_1_3:do_wait_finished(ChangeCipherSpec, State0) of
- #alert{} = Alert ->
- ssl_connection:handle_own_alert(Alert, {3,4}, wait_finished, State0);
- State1 ->
+wait_cert(internal, #change_cipher_spec{}, State0, _Module) ->
+ {Record, State} = tls_connection:next_record(State0),
+ tls_connection:next_event(?FUNCTION_NAME, Record, State);
+wait_cert(internal,
+ #certificate_1_3{} = Certificate, State0, _Module) ->
+ case tls_handshake_1_3:do_wait_cert(Certificate, State0) of
+ {#alert{} = Alert, State} ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert, State);
+ {State1, NextState} ->
{Record, State} = tls_connection:next_record(State1),
- tls_connection:next_event(?FUNCTION_NAME, Record, State)
+ tls_connection:next_event(NextState, Record, State)
end;
+wait_cert(Type, Msg, State, Connection) ->
+ ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+
+
+wait_cv(internal, #change_cipher_spec{}, State0, _Module) ->
+ {Record, State} = tls_connection:next_record(State0),
+ tls_connection:next_event(?FUNCTION_NAME, Record, State);
+wait_cv(internal,
+ #certificate_verify_1_3{} = CertificateVerify, State0, _Module) ->
+ case tls_handshake_1_3:do_wait_cv(CertificateVerify, State0) of
+ {#alert{} = Alert, State} ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, wait_cv, State);
+ {State1, NextState} ->
+ {Record, State} = tls_connection:next_record(State1),
+ tls_connection:next_event(NextState, Record, State)
+ end;
+wait_cv(Type, Msg, State, Connection) ->
+ ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
+
+
+wait_finished(internal, #change_cipher_spec{}, State0, _Module) ->
+ {Record, State} = tls_connection:next_record(State0),
+ tls_connection:next_event(?FUNCTION_NAME, Record, State);
wait_finished(internal,
#finished{} = Finished, State0, Module) ->
case tls_handshake_1_3:do_wait_finished(Finished, State0) of
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index 3bc1290361..1e8b046c1e 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -44,6 +44,8 @@
-export([do_start/2,
do_negotiated/2,
+ do_wait_cert/2,
+ do_wait_cv/2,
do_wait_finished/2]).
%%====================================================================
@@ -87,6 +89,36 @@ encrypted_extensions() ->
}.
+certificate_request(SignAlgs0, SignAlgsCert0) ->
+ %% Input arguments contain TLS 1.2 algorithms due to backward compatibility
+ %% reasons. These {Hash, Algo} tuples must be filtered before creating the
+ %% the extensions.
+ SignAlgs = filter_tls13_algs(SignAlgs0),
+ SignAlgsCert = filter_tls13_algs(SignAlgsCert0),
+ Extensions0 = add_signature_algorithms(#{}, SignAlgs),
+ Extensions = add_signature_algorithms_cert(Extensions0, SignAlgsCert),
+ #certificate_request_1_3{
+ certificate_request_context = <<>>,
+ extensions = Extensions}.
+
+
+add_signature_algorithms(Extensions, SignAlgs) ->
+ Extensions#{signature_algorithms =>
+ #signature_algorithms{signature_scheme_list = SignAlgs}}.
+
+
+add_signature_algorithms_cert(Extensions, undefined) ->
+ Extensions;
+add_signature_algorithms_cert(Extensions, SignAlgsCert) ->
+ Extensions#{signature_algorithms_cert =>
+ #signature_algorithms{signature_scheme_list = SignAlgsCert}}.
+
+
+filter_tls13_algs(undefined) -> undefined;
+filter_tls13_algs(Algo) ->
+ lists:filter(fun is_atom/1, Algo).
+
+
%% TODO: use maybe monad for error handling!
%% enum {
%% X509(0),
@@ -144,7 +176,7 @@ certificate_verify(PrivateKey, SignatureScheme,
%% Digital signatures use the hash function defined by the selected signature
%% scheme.
- case digitally_sign(THash, <<"TLS 1.3, server CertificateVerify">>,
+ case sign(THash, <<"TLS 1.3, server CertificateVerify">>,
HashAlgo, PrivateKey) of
{ok, Signature} ->
{ok, #certificate_verify_1_3{
@@ -352,7 +384,7 @@ certificate_entry(DER) ->
%% 79
%% 00
%% 0101010101010101010101010101010101010101010101010101010101010101
-digitally_sign(THash, Context, HashAlgo, PrivateKey) ->
+sign(THash, Context, HashAlgo, PrivateKey) ->
Content = build_content(Context, THash),
%% The length of the Salt MUST be equal to the length of the output
@@ -369,6 +401,23 @@ digitally_sign(THash, Context, HashAlgo, PrivateKey) ->
end.
+verify(THash, Context, HashAlgo, Signature, PublicKey) ->
+ Content = build_content(Context, THash),
+
+ %% The length of the Salt MUST be equal to the length of the output
+ %% of the digest algorithm: rsa_pss_saltlen = -1
+ try public_key:verify(Content, HashAlgo, Signature, PublicKey,
+ [{rsa_padding, rsa_pkcs1_pss_padding},
+ {rsa_pss_saltlen, -1},
+ {rsa_mgf1_md, HashAlgo}]) of
+ Result ->
+ {ok, Result}
+ catch
+ error:badarg ->
+ {error, badarg}
+ end.
+
+
build_content(Context, THash) ->
Prefix = binary:copy(<<32>>, 64),
<<Prefix/binary,Context/binary,?BYTE(0),THash/binary>>.
@@ -379,36 +428,12 @@ build_content(Context, THash) ->
%%====================================================================
-do_start(#change_cipher_spec{},
- #state{connection_states = _ConnectionStates0,
- session = #session{session_id = _SessionId,
- own_certificate = _OwnCert},
- ssl_options = #ssl_options{} = _SslOpts,
- key_share = _KeyShare,
- handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
- static_env = #static_env{
- cert_db = _CertDbHandle,
- cert_db_ref = _CertDbRef,
- socket = _Socket,
- transport_cb = _Transport}
- } = State0) ->
- %% {Ref,Maybe} = maybe(),
-
- try
-
- State0
-
- catch
- {_Ref, {state_not_implemented, State}} ->
- ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {state_not_implemented, State})
- end;
do_start(#client_hello{cipher_suites = ClientCiphers,
session_id = SessionId,
extensions = Extensions} = _Hello,
#state{connection_states = _ConnectionStates0,
ssl_options = #ssl_options{ciphers = ServerCiphers,
signature_algs = ServerSignAlgs,
- signature_algs_cert = _SignatureSchemes, %% TODO: check!
supported_groups = ServerGroups0},
session = #session{own_certificate = Cert}} = State0) ->
@@ -454,7 +479,8 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
%% Generate server_share
KeyShare = ssl_cipher:generate_server_share(Group),
- State1 = update_start_state(State0, Cipher, KeyShare, SessionId),
+ State1 = update_start_state(State0, Cipher, KeyShare, SessionId,
+ Group, SelectedSignAlg, ClientPubKey),
%% 4.1.4. Hello Retry Request
%%
@@ -462,14 +488,7 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
%% message if it is able to find an acceptable set of parameters but the
%% ClientHello does not contain sufficient information to proceed with
%% the handshake.
- {State2, NextState} =
- Maybe(send_hello_retry_request(State1, ClientPubKey, KeyShare, SessionId)),
-
- %% TODO: Add Context to state?
- Context = #{group => Group,
- sign_alg => SelectedSignAlg,
- client_share => ClientPubKey},
- {State2, Context, NextState}
+ Maybe(send_hello_retry_request(State1, ClientPubKey, KeyShare, SessionId))
%% TODO:
%% - session handling
@@ -484,29 +503,29 @@ do_start(#client_hello{cipher_suites = ClientCiphers,
{Ref, no_suitable_cipher} ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher);
{Ref, {insufficient_security, no_suitable_signature_algorithm}} ->
- ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm);
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, "No suitable signature algorithm");
{Ref, {insufficient_security, no_suitable_public_key}} ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key)
end.
-do_negotiated(#{client_share := ClientKey,
- group := SelectedGroup,
- sign_alg := SignatureScheme
- },
- #state{connection_states = ConnectionStates0,
- session = #session{session_id = SessionId,
- own_certificate = OwnCert},
- ssl_options = #ssl_options{} = _SslOpts,
- key_share = KeyShare,
- handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
- connection_env = #connection_env{private_key = CertPrivateKey},
- static_env = #static_env{
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
- socket = _Socket,
- transport_cb = _Transport}
- } = State0) ->
+do_negotiated(start_handshake,
+ #state{connection_states = ConnectionStates0,
+ session = #session{session_id = SessionId,
+ own_certificate = OwnCert,
+ ecc = SelectedGroup,
+ sign_alg = SignatureScheme,
+ dh_public_value = ClientKey},
+ ssl_options = #ssl_options{} = SslOpts,
+ key_share = KeyShare,
+ handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
+ connection_env = #connection_env{private_key = CertPrivateKey},
+ static_env = #static_env{
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ socket = _Socket,
+ transport_cb = _Transport}
+ } = State0) ->
{Ref,Maybe} = maybe(),
try
@@ -527,59 +546,71 @@ do_negotiated(#{client_share := ClientKey,
%% Encode EncryptedExtensions
State4 = tls_connection:queue_handshake(EncryptedExtensions, State3),
+ %% Create and send CertificateRequest ({verify, verify_peer})
+ {State5, NextState} = maybe_send_certificate_request(State4, SslOpts),
+
%% Create Certificate
Certificate = certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server),
%% Encode Certificate
- State5 = tls_connection:queue_handshake(Certificate, State4),
+ State6 = tls_connection:queue_handshake(Certificate, State5),
%% Create CertificateVerify
CertificateVerify = Maybe(certificate_verify(CertPrivateKey, SignatureScheme,
- State5, server)),
+ State6, server)),
%% Encode CertificateVerify
- State6 = tls_connection:queue_handshake(CertificateVerify, State5),
+ State7 = tls_connection:queue_handshake(CertificateVerify, State6),
%% Create Finished
- Finished = finished(State6),
+ Finished = finished(State7),
%% Encode Finished
- State7 = tls_connection:queue_handshake(Finished, State6),
+ State8 = tls_connection:queue_handshake(Finished, State7),
%% Send first flight
- {State8, _} = tls_connection:send_handshake_flight(State7),
+ {State9, _} = tls_connection:send_handshake_flight(State8),
- State8
+ {State9, NextState}
catch
- {Ref, {state_not_implemented, State}} ->
- %% TODO
- ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {state_not_implemented, State})
+ {Ref, badarg} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {digitally_sign, badarg})
end.
-do_wait_finished(#change_cipher_spec{},
- #state{connection_states = _ConnectionStates0,
- session = #session{session_id = _SessionId,
- own_certificate = _OwnCert},
- ssl_options = #ssl_options{} = _SslOpts,
- key_share = _KeyShare,
- handshake_env = #handshake_env{tls_handshake_history = _HHistory0},
- static_env = #static_env{
- cert_db = _CertDbHandle,
- cert_db_ref = _CertDbRef,
- socket = _Socket,
- transport_cb = _Transport}
- } = State0) ->
- %% {Ref,Maybe} = maybe(),
-
+do_wait_cert(#certificate_1_3{} = Certificate, State0) ->
+ {Ref,Maybe} = maybe(),
try
+ Maybe(process_client_certificate(Certificate, State0))
+ catch
+ {Ref, {certificate_required, State}} ->
+ {?ALERT_REC(?FATAL, ?CERTIFICATE_REQUIRED, certificate_required), State};
+ {Ref, {{certificate_unknown, Reason}, State}} ->
+ {?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, Reason), State};
+ {Ref, {{internal_error, Reason}, State}} ->
+ {?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason), State};
+ {Ref, {{handshake_failure, Reason}, State}} ->
+ {?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason), State};
+ {#alert{} = Alert, State} ->
+ {Alert, State}
+ end.
- State0
+do_wait_cv(#certificate_verify_1_3{} = CertificateVerify, State0) ->
+ {Ref,Maybe} = maybe(),
+ try
+ Maybe(verify_signature_algorithm(State0, CertificateVerify)),
+ Maybe(verify_certificate_verify(State0, CertificateVerify))
catch
- {_Ref, {state_not_implemented, State}} ->
- ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {state_not_implemented, State})
- end;
+ {Ref, {{bad_certificate, Reason}, State}} ->
+ {?ALERT_REC(?FATAL, ?BAD_CERTIFICATE, {bad_certificate, Reason}), State};
+ {Ref, {badarg, State}} ->
+ {?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {verify, badarg}), State};
+ {Ref, {{handshake_failure, Reason}, State}} ->
+ {?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {handshake_failure, Reason}), State}
+ end.
+
+
do_wait_finished(#finished{verify_data = VerifyData},
#state{connection_states = _ConnectionStates0,
session = #session{session_id = _SessionId,
@@ -607,16 +638,19 @@ do_wait_finished(#finished{verify_data = VerifyData},
catch
{Ref, decrypt_error} ->
- ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error);
- {_, {state_not_implemented, State}} ->
- %% TODO
- ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {state_not_implemented, State})
+ ?ALERT_REC(?FATAL, ?DECRYPT_ERROR, decrypt_error)
end.
%% TODO: Remove this function!
-%% not_implemented(State) ->
-%% {error, {state_not_implemented, State}}.
+%% not_implemented(State, Reason) ->
+%% {error, {not_implemented, State, Reason}}.
+%%
+%% not_implemented(update_secrets, State0, Reason) ->
+%% State1 = calculate_traffic_secrets(State0),
+%% State = ssl_record:step_encryption_state(State1),
+%% {error, {not_implemented, State, Reason}}.
+
%% Recipients of Finished messages MUST verify that the contents are
@@ -659,6 +693,138 @@ send_hello_retry_request(State0, _, _, _) ->
{ok, {State0, negotiated}}.
+maybe_send_certificate_request(State, #ssl_options{verify = verify_none}) ->
+ {State, wait_finished};
+maybe_send_certificate_request(State, #ssl_options{
+ verify = verify_peer,
+ signature_algs = SignAlgs,
+ signature_algs_cert = SignAlgsCert}) ->
+ CertificateRequest = certificate_request(SignAlgs, SignAlgsCert),
+ {tls_connection:queue_handshake(CertificateRequest, State), wait_cert}.
+
+
+process_client_certificate(#certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = []},
+ #state{ssl_options =
+ #ssl_options{
+ fail_if_no_peer_cert = false}} = State) ->
+ {ok, {State, wait_finished}};
+process_client_certificate(#certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = []},
+ #state{ssl_options =
+ #ssl_options{
+ fail_if_no_peer_cert = true}} = State0) ->
+
+ %% At this point the client believes that the connection is up and starts using
+ %% its traffic secrets. In order to be able send an proper Alert to the client
+ %% the server should also change its connection state and use the traffic
+ %% secrets.
+ State1 = calculate_traffic_secrets(State0),
+ State = ssl_record:step_encryption_state(State1),
+ {error, {certificate_required, State}};
+process_client_certificate(#certificate_1_3{certificate_list = Certs0},
+ #state{ssl_options =
+ #ssl_options{signature_algs = SignAlgs,
+ signature_algs_cert = SignAlgsCert} = SslOptions,
+ static_env =
+ #static_env{
+ role = Role,
+ host = Host,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ crl_db = CRLDbHandle}} = State0) ->
+ %% TODO: handle extensions!
+
+ %% Remove extensions from list of certificates!
+ Certs = convert_certificate_chain(Certs0),
+ case is_supported_signature_algorithm(Certs, SignAlgs, SignAlgsCert) of
+ true ->
+ case validate_certificate_chain(Certs, CertDbHandle, CertDbRef,
+ SslOptions, CRLDbHandle, Role, Host) of
+ {ok, {PeerCert, PublicKeyInfo}} ->
+ State = store_peer_cert(State0, PeerCert, PublicKeyInfo),
+ {ok, {State, wait_cv}};
+ {error, Reason} ->
+ State1 = calculate_traffic_secrets(State0),
+ State = ssl_record:step_encryption_state(State1),
+ {error, {Reason, State}};
+ #alert{} = Alert ->
+ State1 = calculate_traffic_secrets(State0),
+ State = ssl_record:step_encryption_state(State1),
+ {Alert, State}
+ end;
+ false ->
+ State1 = calculate_traffic_secrets(State0),
+ State = ssl_record:step_encryption_state(State1),
+ {error, {{handshake_failure,
+ "Client certificate uses unsupported signature algorithm"}, State}}
+ end.
+
+
+%% TODO: check whole chain!
+is_supported_signature_algorithm(Certs, SignAlgs, undefined) ->
+ is_supported_signature_algorithm(Certs, SignAlgs);
+is_supported_signature_algorithm(Certs, _, SignAlgsCert) ->
+ is_supported_signature_algorithm(Certs, SignAlgsCert).
+%%
+is_supported_signature_algorithm([BinCert|_], SignAlgs0) ->
+ #'OTPCertificate'{signatureAlgorithm = SignAlg} =
+ public_key:pkix_decode_cert(BinCert, otp),
+ SignAlgs = filter_tls13_algs(SignAlgs0),
+ Scheme = ssl_cipher:signature_algorithm_to_scheme(SignAlg),
+ lists:member(Scheme, SignAlgs).
+
+
+validate_certificate_chain(Certs, CertDbHandle, CertDbRef, SslOptions, CRLDbHandle, Role, Host) ->
+ ServerName = ssl_handshake:server_name(SslOptions#ssl_options.server_name_indication, Host, Role),
+ [PeerCert | ChainCerts ] = Certs,
+ try
+ {TrustedCert, CertPath} =
+ ssl_certificate:trusted_cert_and_path(Certs, CertDbHandle, CertDbRef,
+ SslOptions#ssl_options.partial_chain),
+ ValidationFunAndState =
+ ssl_handshake:validation_fun_and_state(SslOptions#ssl_options.verify_fun, Role,
+ CertDbHandle, CertDbRef, ServerName,
+ SslOptions#ssl_options.customize_hostname_check,
+ SslOptions#ssl_options.crl_check, CRLDbHandle, CertPath),
+ Options = [{max_path_length, SslOptions#ssl_options.depth},
+ {verify_fun, ValidationFunAndState}],
+ %% TODO: Validate if Certificate is using a supported signature algorithm
+ %% (signature_algs_cert)!
+ case public_key:pkix_path_validation(TrustedCert, CertPath, Options) of
+ {ok, {PublicKeyInfo,_}} ->
+ {ok, {PeerCert, PublicKeyInfo}};
+ {error, Reason} ->
+ ssl_handshake:handle_path_validation_error(Reason, PeerCert, ChainCerts,
+ SslOptions, Options,
+ CertDbHandle, CertDbRef)
+ end
+ catch
+ error:{badmatch,{asn1, Asn1Reason}} ->
+ %% ASN-1 decode of certificate somehow failed
+ {error, {certificate_unknown, {failed_to_decode_certificate, Asn1Reason}}};
+ error:OtherReason ->
+ {error, {internal_error, {unexpected_error, OtherReason}}}
+ end.
+
+
+store_peer_cert(#state{session = Session,
+ handshake_env = HsEnv} = State, PeerCert, PublicKeyInfo) ->
+ State#state{session = Session#session{peer_certificate = PeerCert},
+ handshake_env = HsEnv#handshake_env{public_key_info = PublicKeyInfo}}.
+
+
+convert_certificate_chain(Certs) ->
+ Fun = fun(#certificate_entry{data = Data}) ->
+ {true, Data};
+ (_) ->
+ false
+ end,
+ lists:filtermap(Fun, Certs).
+
+
%% 4.4.1. The Transcript Hash
%%
%% As an exception to this general rule, when the server responds to a
@@ -746,10 +912,8 @@ calculate_traffic_secrets(#state{connection_states = ConnectionStates,
MasterSecret =
tls_v1:key_schedule(master_secret, HKDFAlgo, HandshakeSecret),
- {Messages0, _} = HHistory,
-
- %% Drop Client Finish
- [_|Messages] = Messages0,
+ %% Get the correct list messages for the handshake context.
+ Messages = get_handshake_context(HHistory),
%% Calculate [sender]_application_traffic_secret_0
ClientAppTrafficSecret0 =
@@ -778,6 +942,11 @@ get_private_key(#key_share_entry{
{_, PrivateKey}}) ->
PrivateKey.
+%% TODO: implement EC keys
+get_public_key({?'rsaEncryption', PublicKey, _}) ->
+ PublicKey.
+
+
%% X25519, X448
calculate_shared_secret(OthersKey, MyKey, Group)
when is_binary(OthersKey) andalso is_binary(MyKey) andalso
@@ -822,7 +991,8 @@ update_connection_state(ConnectionState = #{security_parameters := SecurityParam
update_start_state(#state{connection_states = ConnectionStates0,
connection_env = CEnv,
session = Session} = State,
- Cipher, KeyShare, SessionId) ->
+ Cipher, KeyShare, SessionId,
+ Group, SelectedSignAlg, ClientPubKey) ->
#{security_parameters := SecParamsR0} = PendingRead =
maps:get(pending_read, ConnectionStates0),
#{security_parameters := SecParamsW0} = PendingWrite =
@@ -834,7 +1004,10 @@ update_start_state(#state{connection_states = ConnectionStates0,
pending_write => PendingWrite#{security_parameters => SecParamsW}},
State#state{connection_states = ConnectionStates,
key_share = KeyShare,
- session = Session#session{session_id = SessionId},
+ session = Session#session{session_id = SessionId,
+ ecc = Group,
+ sign_alg = SelectedSignAlg,
+ dh_public_value = ClientPubKey},
connection_env = CEnv#connection_env{negotiated_version = {3,4}}}.
@@ -845,6 +1018,126 @@ cipher_init(Key, IV, FinishedKey) ->
tag_len = 16}.
+%% Get handshake context for verification of CertificateVerify.
+%%
+%% Verify CertificateVerify:
+%% ClientHello (client) (1)
+%% ServerHello (server) (2)
+%% EncryptedExtensions (server) (8)
+%% CertificateRequest (server) (13)
+%% Certificate (server) (11)
+%% CertificateVerify (server) (15)
+%% Finished (server) (20)
+%% Certificate (client) (11)
+%% CertificateVerify (client) (15) - Drop! Not included in calculations!
+get_handshake_context_cv({[<<15,_/binary>>|Messages], _}) ->
+ Messages.
+
+
+%% Get handshake context for traffic key calculation.
+%%
+%% Client is authenticated with certificate:
+%% ClientHello (client) (1)
+%% ServerHello (server) (2)
+%% EncryptedExtensions (server) (8)
+%% CertificateRequest (server) (13)
+%% Certificate (server) (11)
+%% CertificateVerify (server) (15)
+%% Finished (server) (20)
+%% Certificate (client) (11) - Drop! Not included in calculations!
+%% CertificateVerify (client) (15) - Drop! Not included in calculations!
+%% Finished (client) (20) - Drop! Not included in calculations!
+%%
+%% Client is authenticated but sends empty certificate:
+%% ClientHello (client) (1)
+%% ServerHello (server) (2)
+%% EncryptedExtensions (server) (8)
+%% CertificateRequest (server) (13)
+%% Certificate (server) (11)
+%% CertificateVerify (server) (15)
+%% Finished (server) (20)
+%% Certificate (client) (11) - Drop! Not included in calculations!
+%% Finished (client) (20) - Drop! Not included in calculations!
+%%
+%% Client is not authenticated:
+%% ClientHello (client) (1)
+%% ServerHello (server) (2)
+%% EncryptedExtensions (server) (8)
+%% Certificate (server) (11)
+%% CertificateVerify (server) (15)
+%% Finished (server) (20)
+%% Finished (client) (20) - Drop! Not included in calculations!
+%%
+%% Drop all client messages from the front of the iolist using the property that
+%% incoming messages are binaries.
+get_handshake_context({Messages, _}) ->
+ get_handshake_context(Messages);
+get_handshake_context([H|T]) when is_binary(H) ->
+ get_handshake_context(T);
+get_handshake_context(L) ->
+ L.
+
+
+%% If sent by a client, the signature algorithm used in the signature
+%% MUST be one of those present in the supported_signature_algorithms
+%% field of the "signature_algorithms" extension in the
+%% CertificateRequest message.
+verify_signature_algorithm(#state{ssl_options =
+ #ssl_options{
+ signature_algs = ServerSignAlgs}} = State0,
+ #certificate_verify_1_3{algorithm = ClientSignAlg}) ->
+ case lists:member(ClientSignAlg, ServerSignAlgs) of
+ true ->
+ ok;
+ false ->
+ State1 = calculate_traffic_secrets(State0),
+ State = ssl_record:step_encryption_state(State1),
+ {error, {{handshake_failure,
+ "CertificateVerify uses unsupported signature algorithm"}, State}}
+ end.
+
+
+verify_certificate_verify(#state{connection_states = ConnectionStates,
+ handshake_env =
+ #handshake_env{
+ public_key_info = PublicKeyInfo,
+ tls_handshake_history = HHistory}} = State0,
+ #certificate_verify_1_3{
+ algorithm = SignatureScheme,
+ signature = Signature}) ->
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, write),
+ #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,
+
+ {HashAlgo, _, _} =
+ ssl_cipher:scheme_to_components(SignatureScheme),
+
+ Messages = get_handshake_context_cv(HHistory),
+
+ Context = lists:reverse(Messages),
+
+ %% Transcript-Hash uses the HKDF hash function defined by the cipher suite.
+ THash = tls_v1:transcript_hash(Context, HKDFAlgo),
+
+ PublicKey = get_public_key(PublicKeyInfo),
+
+ %% Digital signatures use the hash function defined by the selected signature
+ %% scheme.
+ case verify(THash, <<"TLS 1.3, client CertificateVerify">>,
+ HashAlgo, Signature, PublicKey) of
+ {ok, true} ->
+ {ok, {State0, wait_finished}};
+ {ok, false} ->
+ State1 = calculate_traffic_secrets(State0),
+ State = ssl_record:step_encryption_state(State1),
+ {error, {{handshake_failure, "Failed to verify CertificateVerify"}, State}};
+ {error, badarg} ->
+ State1 = calculate_traffic_secrets(State0),
+ State = ssl_record:step_encryption_state(State1),
+ {error, {badarg, State}}
+ end.
+
+
%% If there is no overlap between the received
%% "supported_groups" and the groups supported by the server, then the
%% server MUST abort the handshake with a "handshake_failure" or an
@@ -861,7 +1154,6 @@ select_common_groups(ServerGroups, ClientGroups) ->
end.
-
%% RFC 8446 - 4.2.8. Key Share
%% This vector MAY be empty if the client is requesting a
%% HelloRetryRequest. Each KeyShareEntry value MUST correspond to a
diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl
index 05acc08392..97331e1510 100644
--- a/lib/ssl/src/tls_record_1_3.erl
+++ b/lib/ssl/src/tls_record_1_3.erl
@@ -124,6 +124,20 @@ decode_cipher_text(#ssl_tls{type = ?OPAQUE_TYPE,
{decode_inner_plaintext(PlainFragment), ConnectionStates}
end;
+
+%% RFC8446 - TLS 1.3 (OpenSSL compatibility)
+%% Handle unencrypted Alerts from openssl s_client when server's
+%% connection states are already stepped into traffic encryption.
+%% (E.g. openssl s_client receives a CertificateRequest with
+%% a signature_algorithms_cert extension that does not contain
+%% the signature algorithm of the client's certificate.)
+decode_cipher_text(#ssl_tls{type = ?ALERT,
+ version = ?LEGACY_VERSION,
+ fragment = <<2,47>>},
+ ConnectionStates0) ->
+ {#ssl_tls{type = ?ALERT,
+ version = {3,4}, %% Internally use real version
+ fragment = <<2,47>>}, ConnectionStates0};
%% RFC8446 - TLS 1.3
%% D.4. Middlebox Compatibility Mode
%% - If not offering early data, the client sends a dummy
@@ -139,7 +153,6 @@ decode_cipher_text(#ssl_tls{type = ?CHANGE_CIPHER_SPEC,
{#ssl_tls{type = ?CHANGE_CIPHER_SPEC,
version = {3,4}, %% Internally use real version
fragment = <<1>>}, ConnectionStates0};
-
decode_cipher_text(#ssl_tls{type = Type,
version = ?LEGACY_VERSION,
fragment = CipherFragment},
diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl
index c3c41d3e12..6c32e6fa04 100644
--- a/lib/ssl/src/tls_socket.erl
+++ b/lib/ssl/src/tls_socket.erl
@@ -46,7 +46,7 @@
send(Transport, Socket, Data) ->
Transport:send(Socket, Data).
-listen(Transport, Port, #config{transport_info = {Transport, _, _, _},
+listen(Transport, Port, #config{transport_info = {Transport, _, _, _, _},
inet_user = Options,
ssl = SslOpts, emulated = EmOpts} = Config) ->
case Transport:listen(Port, Options ++ internal_inet_values()) of
@@ -59,7 +59,7 @@ listen(Transport, Port, #config{transport_info = {Transport, _, _, _},
Err
end.
-accept(ListenSocket, #config{transport_info = {Transport,_,_,_} = CbInfo,
+accept(ListenSocket, #config{transport_info = {Transport,_,_,_,_} = CbInfo,
connection_cb = ConnectionCb,
ssl = SslOpts,
emulated = Tracker}, Timeout) ->
@@ -80,7 +80,7 @@ accept(ListenSocket, #config{transport_info = {Transport,_,_,_} = CbInfo,
{error, Reason}
end.
-upgrade(Socket, #config{transport_info = {Transport,_,_,_}= CbInfo,
+upgrade(Socket, #config{transport_info = {Transport,_,_,_,_}= CbInfo,
ssl = SslOptions,
emulated = EmOpts, connection_cb = ConnectionCb}, Timeout) ->
ok = setopts(Transport, Socket, tls_socket:internal_inet_values()),
@@ -98,7 +98,7 @@ connect(Address, Port,
#config{transport_info = CbInfo, inet_user = UserOpts, ssl = SslOpts,
emulated = EmOpts, inet_ssl = SocketOpts, connection_cb = ConnetionCb},
Timeout) ->
- {Transport, _, _, _} = CbInfo,
+ {Transport, _, _, _, _} = CbInfo,
try Transport:connect(Address, Port, SocketOpts, Timeout) of
{ok, Socket} ->
ssl_connection:connect(ConnetionCb, Address, Port, Socket,
@@ -125,7 +125,7 @@ setopts(gen_tcp, Socket = #sslsocket{pid = {ListenSocket, #config{emulated = Tra
ok = set_emulated_opts(Tracker, EmulatedOpts),
check_active_n(EmulatedOpts, Socket),
inet:setopts(ListenSocket, SockOpts);
-setopts(_, Socket = #sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_},
+setopts(_, Socket = #sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_,_},
emulated = Tracker}}}, Options) ->
{SockOpts, EmulatedOpts} = split_options(Options),
ok = set_emulated_opts(Tracker, EmulatedOpts),
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index 292916692d..ff5638ff34 100644
--- a/lib/ssl/test/ssl_basic_SUITE.erl
+++ b/lib/ssl/test/ssl_basic_SUITE.erl
@@ -281,7 +281,15 @@ tls13_test_group() ->
tls13_1_RTT_handshake,
tls13_basic_ssl_server_openssl_client,
tls13_custom_groups_ssl_server_openssl_client,
- tls13_hello_retry_request_ssl_server_openssl_client].
+ tls13_hello_retry_request_ssl_server_openssl_client,
+ tls13_client_auth_empty_cert_alert_ssl_server_openssl_client,
+ tls13_client_auth_empty_cert_ssl_server_openssl_client,
+ tls13_client_auth_ssl_server_openssl_client,
+ tls13_hrr_client_auth_empty_cert_alert_ssl_server_openssl_client,
+ tls13_hrr_client_auth_empty_cert_ssl_server_openssl_client,
+ tls13_hrr_client_auth_ssl_server_openssl_client,
+ tls13_unsupported_sign_algo_client_auth_ssl_server_openssl_client,
+ tls13_unsupported_sign_algo_cert_client_auth_ssl_server_openssl_client].
%%--------------------------------------------------------------------
init_per_suite(Config0) ->
@@ -4078,7 +4086,7 @@ tls_tcp_error_propagation_in_active_mode(Config) when is_list(Config) ->
[_, _,_, _, Prop] = StatusInfo,
State = ssl_test_lib:state(Prop),
StaticEnv = element(2, State),
- Socket = element(10, StaticEnv),
+ Socket = element(11, StaticEnv),
%% Fake tcp error
Pid ! {tcp_error, Socket, etimedout},
@@ -5588,6 +5596,258 @@ tls13_hello_retry_request_ssl_server_openssl_client(Config) ->
ssl_test_lib:close(Server),
ssl_test_lib:close_port(Client).
+tls13_client_auth_empty_cert_alert_ssl_server_openssl_client() ->
+ [{doc,"TLS 1.3: Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to true."}].
+
+tls13_client_auth_empty_cert_alert_ssl_server_openssl_client(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ %% Delete Client Cert and Key
+ ClientOpts1 = proplists:delete(certfile, ClientOpts0),
+ ClientOpts = proplists:delete(keyfile, ClientOpts1),
+
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ %% Set versions
+ ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']},
+ {verify, verify_peer},
+ {fail_if_no_peer_cert, true}|ServerOpts0],
+ {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts),
+
+ ssl_test_lib:check_result(Server,
+ {error,
+ {tls_alert,
+ {certificate_required,
+ "received SERVER ALERT: Fatal - Certificate required - certificate_required"}}}),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close_port(Client).
+
+tls13_client_auth_empty_cert_ssl_server_openssl_client() ->
+ [{doc,"TLS 1.3: Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to false."}].
+
+tls13_client_auth_empty_cert_ssl_server_openssl_client(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ %% Delete Client Cert and Key
+ ClientOpts1 = proplists:delete(certfile, ClientOpts0),
+ ClientOpts = proplists:delete(keyfile, ClientOpts1),
+
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ %% Set versions
+ ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']},
+ {verify, verify_peer},
+ {fail_if_no_peer_cert, false}|ServerOpts0],
+ {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts),
+
+ ssl_test_lib:check_result(Server, ok),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close_port(Client).
+
+
+tls13_client_auth_ssl_server_openssl_client() ->
+ [{doc,"TLS 1.3: Test client authentication."}].
+
+tls13_client_auth_ssl_server_openssl_client(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ %% Set versions
+ ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']},
+ {verify, verify_peer},
+ {fail_if_no_peer_cert, true}|ServerOpts0],
+ {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts),
+
+ ssl_test_lib:check_result(Server, ok),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close_port(Client).
+
+
+tls13_hrr_client_auth_empty_cert_alert_ssl_server_openssl_client() ->
+ [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to true."}].
+
+tls13_hrr_client_auth_empty_cert_alert_ssl_server_openssl_client(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ %% Delete Client Cert and Key
+ ClientOpts1 = proplists:delete(certfile, ClientOpts0),
+ ClientOpts2 = proplists:delete(keyfile, ClientOpts1),
+ ClientOpts = [{groups,"P-256:X25519"}|ClientOpts2],
+
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ %% Set versions
+ ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']},
+ {verify, verify_peer},
+ {fail_if_no_peer_cert, true},
+ {supported_groups, [x448, x25519]}|ServerOpts0],
+ {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts),
+
+ ssl_test_lib:check_result(Server,
+ {error,
+ {tls_alert,
+ {certificate_required,
+ "received SERVER ALERT: Fatal - Certificate required - certificate_required"}}}),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close_port(Client).
+
+
+tls13_hrr_client_auth_empty_cert_ssl_server_openssl_client() ->
+ [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to false."}].
+
+tls13_hrr_client_auth_empty_cert_ssl_server_openssl_client(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ %% Delete Client Cert and Key
+ ClientOpts1 = proplists:delete(certfile, ClientOpts0),
+ ClientOpts2 = proplists:delete(keyfile, ClientOpts1),
+ ClientOpts = [{groups,"P-256:X25519"}|ClientOpts2],
+
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ %% Set versions
+ ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']},
+ {verify, verify_peer},
+ {fail_if_no_peer_cert, false},
+ {supported_groups, [x448, x25519]}|ServerOpts0],
+ {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts),
+
+ ssl_test_lib:check_result(Server, ok),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close_port(Client).
+
+
+tls13_hrr_client_auth_ssl_server_openssl_client() ->
+ [{doc,"TLS 1.3 (HelloRetryRequest): Test client authentication."}].
+
+tls13_hrr_client_auth_ssl_server_openssl_client(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts = [{groups,"P-256:X25519"}|ClientOpts0],
+
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ %% Set versions
+ ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']},
+ {verify, verify_peer},
+ {fail_if_no_peer_cert, true},
+ {supported_groups, [x448, x25519]}|ServerOpts0],
+ {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts),
+
+ ssl_test_lib:check_result(Server, ok),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close_port(Client).
+
+
+tls13_unsupported_sign_algo_client_auth_ssl_server_openssl_client() ->
+ [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm"}].
+
+tls13_unsupported_sign_algo_client_auth_ssl_server_openssl_client(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ %% Set versions
+ ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']},
+ {verify, verify_peer},
+ %% Skip rsa_pkcs1_sha256!
+ {signature_algs, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]},
+ {fail_if_no_peer_cert, true}|ServerOpts0],
+ {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts),
+
+ ssl_test_lib:check_result(
+ Server,
+ {error,
+ {tls_alert,
+ {insufficient_security,
+ "received SERVER ALERT: Fatal - Insufficient Security - "
+ "\"No suitable signature algorithm\""}}}),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close_port(Client).
+
+
+%% Triggers Client Alert as openssl s_client does not have a certificate with a
+%% signature algorithm supported by the server (signature_algorithms_cert extension
+%% of CertificateRequest does not contain the algorithm of the client certificate).
+tls13_unsupported_sign_algo_cert_client_auth_ssl_server_openssl_client() ->
+ [{doc,"TLS 1.3: Test client authentication with unsupported signature_algorithm_cert"}].
+
+tls13_unsupported_sign_algo_cert_client_auth_ssl_server_openssl_client(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ %% Set versions
+ ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']},
+ {log_level, debug},
+ {verify, verify_peer},
+ {signature_algs, [rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pss_rsae_sha256]},
+ %% Skip rsa_pkcs1_sha256!
+ {signature_algs_cert, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]},
+ {fail_if_no_peer_cert, true}|ServerOpts0],
+ {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts),
+
+ ssl_test_lib:check_result(
+ Server,
+ {error,
+ {tls_alert,
+ {illegal_parameter,
+ "received CLIENT ALERT: Fatal - Illegal Parameter"}}}),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close_port(Client).
+
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl
index f628b4e6d4..7f8e81dbd8 100644
--- a/lib/ssl/test/ssl_test_lib.erl
+++ b/lib/ssl/test/ssl_test_lib.erl
@@ -1114,15 +1114,23 @@ start_basic_client(openssl, Version, Port, ClientOpts) ->
Exe = "openssl",
Args0 = ["s_client", "-verify", "2", "-port", integer_to_list(Port),
ssl_test_lib:version_flag(Version),
- "-cert", Cert, "-CAfile", CA,
- "-key", Key, "-host","localhost", "-msg", "-debug"],
- Args =
+ "-CAfile", CA, "-host", "localhost", "-msg", "-debug"],
+ Args1 =
case Groups0 of
undefined ->
Args0;
G ->
Args0 ++ ["-groups", G]
end,
+ Args =
+ case {Cert, Key} of
+ {C, K} when C =:= undefined orelse
+ K =:= undefined ->
+ Args1;
+ {C, K} ->
+ Args1 ++ ["-cert", C, "-key", K]
+ end,
+
OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args),
true = port_command(OpenSslPort, "Hello world"),
OpenSslPort.
diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk
index 3527062a8a..c4bcc1560c 100644
--- a/lib/ssl/vsn.mk
+++ b/lib/ssl/vsn.mk
@@ -1 +1 @@
-SSL_VSN = 9.1.2
+SSL_VSN = 9.2.1
diff --git a/lib/stdlib/doc/src/beam_lib.xml b/lib/stdlib/doc/src/beam_lib.xml
index 8bb4cf9101..bb44ca3201 100644
--- a/lib/stdlib/doc/src/beam_lib.xml
+++ b/lib/stdlib/doc/src/beam_lib.xml
@@ -470,6 +470,18 @@ CryptoKeyFun(clear) -> term()</code>
</func>
<func>
+ <name name="strip" arity="2" since=""/>
+ <fsummary>Remove chunks not needed by the loader from a BEAM file.
+ </fsummary>
+ <desc>
+ <p>Removes all chunks from a BEAM
+ file except those needed by the loader or passed in. In particular,
+ the debug information (chunk <c>debug_info</c> and <c>abstract_code</c>)
+ is removed.</p>
+ </desc>
+ </func>
+
+ <func>
<name name="strip_files" arity="1" since=""/>
<fsummary>Removes chunks not needed by the loader from BEAM files.
</fsummary>
@@ -483,6 +495,19 @@ CryptoKeyFun(clear) -> term()</code>
</func>
<func>
+ <name name="strip_files" arity="2" since=""/>
+ <fsummary>Removes chunks not needed by the loader from BEAM files.
+ </fsummary>
+ <desc>
+ <p>Removes all chunks except
+ those needed by the loader or passed in from BEAM files. In particular,
+ the debug information (chunk <c>debug_info</c> and <c>abstract_code</c>)
+ is removed. The returned list contains one element for each
+ specified filename, in the same order as in <c>Files</c>.</p>
+ </desc>
+ </func>
+
+ <func>
<name name="strip_release" arity="1" since=""/>
<fsummary>Remove chunks not needed by the loader from all BEAM files of
a release.</fsummary>
@@ -497,6 +522,20 @@ CryptoKeyFun(clear) -> term()</code>
</func>
<func>
+ <name name="strip_release" arity="2" since=""/>
+ <fsummary>Remove chunks not needed by the loader from all BEAM files of
+ a release.</fsummary>
+ <desc>
+ <p>Removes all chunks
+ except those needed by the loader or passed in from the BEAM files of a
+ release. <c><anno>Dir</anno></c> is to be the installation root
+ directory. For example, the current OTP release can be
+ stripped with the call
+ <c>beam_lib:strip_release(code:root_dir())</c>.</p>
+ </desc>
+ </func>
+
+ <func>
<name name="version" arity="1" since=""/>
<fsummary>Read the module version of the BEAM file.</fsummary>
<desc>
diff --git a/lib/stdlib/doc/src/ets.xml b/lib/stdlib/doc/src/ets.xml
index d2ac6a75b1..2cb677785d 100644
--- a/lib/stdlib/doc/src/ets.xml
+++ b/lib/stdlib/doc/src/ets.xml
@@ -642,12 +642,11 @@ Error: fun containing local Erlang function calls
<p><marker id="info_2_safe_fixed_monotonic_time"/></p>
<p><c>Item=safe_fixed|safe_fixed_monotonic_time,
Value={FixationTime,Info}|false</c></p>
- <p>If the table has been fixed using
+ <p>If the table is fixed using
<seealso marker="#safe_fixtable/2">
<c>safe_fixtable/2</c></seealso>,
the call returns a tuple where <c>FixationTime</c> is the
- time when the table was first fixed by a process, which either
- is or is not one of the processes it is fixed by now.</p>
+ last time when the table changed from unfixed to fixed.</p>
<p>The format and value of <c>FixationTime</c> depends on
<c>Item</c>:</p>
<taglist>
@@ -679,8 +678,15 @@ Error: fun containing local Erlang function calls
table is fixed by now. <c>RefCount</c> is the value
of the reference counter and it keeps track of how many times
the table has been fixed by the process.</p>
- <p>If the table never has been fixed, the call returns
- <c>false</c>.</p>
+ <p>Table fixations are not limited to <seealso marker="#safe_fixtable/2">
+ <c>safe_fixtable/2</c></seealso>. Temporary fixations may also
+ be done by for example <seealso marker="#traversal">traversing
+ functions</seealso> like <c>select</c> and <c>match</c>. Such
+ table fixations are automatically released before the
+ corresponding functions returns, but they may be seen by a
+ concurrent call to
+ <c>ets:info(T,safe_fixed|safe_fixed_monotonic_time)</c>.</p>
+ <p>If the table is not fixed at all, the call returns <c>false</c>.</p>
</item>
<item>
<p><c>Item=stats, Value=tuple()</c></p>
diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml
index dee7136eb1..23c3f6e981 100644
--- a/lib/stdlib/doc/src/notes.xml
+++ b/lib/stdlib/doc/src/notes.xml
@@ -31,6 +31,57 @@
</header>
<p>This document describes the changes made to the STDLIB application.</p>
+<section><title>STDLIB 3.8</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p> Fix a bug in the Erlang Pretty Printer: long atom
+ names in combination with <c>&lt;&lt;&gt;&gt;</c> could
+ cause a crash. </p>
+ <p>
+ Own Id: OTP-15592 Aux Id: ERL-818 </p>
+ </item>
+ <item>
+ <p> Fix bugs that could cause wrong results or bad
+ performance when formatting lists of characters using the
+ control sequences <c>p</c> or <c>P</c> and limiting the
+ output with the option <c>chars_limit</c>. </p>
+ <p>
+ Own Id: OTP-15639</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Improved ETS documentation about safe table traversal and
+ the partially bound key optimization for
+ <c>ordered_set</c>.</p>
+ <p>
+ Own Id: OTP-15545 Aux Id: PR-2103, PR-2139 </p>
+ </item>
+ <item>
+ <p> Optimize <c>calendar:gregorian_days_to_date/1</c>.
+ </p>
+ <p>
+ Own Id: OTP-15572 Aux Id: PR-2121 </p>
+ </item>
+ <item>
+ <p> Optimize functions
+ <c>calendar:rfc3339_to_system_time()</c> and
+ <c>calendar:system_time_to_rfc3339()</c>. </p>
+ <p>
+ Own Id: OTP-15630</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>STDLIB 3.7.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/stdlib/src/beam_lib.erl b/lib/stdlib/src/beam_lib.erl
index 3386cfcbe6..aa992f17ab 100644
--- a/lib/stdlib/src/beam_lib.erl
+++ b/lib/stdlib/src/beam_lib.erl
@@ -32,8 +32,12 @@
all_chunks/1,
diff_dirs/2,
strip/1,
+ strip/2,
strip_files/1,
+ strip_files/2,
strip_release/1,
+ strip_release/2,
+ significant_chunks/0,
build_module/1,
version/1,
md5/1,
@@ -188,7 +192,16 @@ diff_dirs(Dir1, Dir2) ->
Beam2 :: beam().
strip(FileName) ->
- try strip_file(FileName)
+ strip(FileName, []).
+
+-spec strip(Beam1, AdditionalChunks) ->
+ {'ok', {module(), Beam2}} | {'error', 'beam_lib', info_rsn()} when
+ Beam1 :: beam(),
+ AdditionalChunks :: [chunkid()],
+ Beam2 :: beam().
+
+strip(FileName, AdditionalChunks) ->
+ try strip_file(FileName, AdditionalChunks)
catch Error -> Error end.
-spec strip_files(Files) ->
@@ -196,8 +209,17 @@ strip(FileName) ->
Files :: [beam()],
Beam :: beam().
-strip_files(Files) when is_list(Files) ->
- try strip_fils(Files)
+strip_files(Files) ->
+ strip_files(Files, []).
+
+-spec strip_files(Files, AdditionalChunks) ->
+ {'ok', [{module(), Beam}]} | {'error', 'beam_lib', info_rsn()} when
+ Files :: [beam()],
+ AdditionalChunks :: [chunkid()],
+ Beam :: beam().
+
+strip_files(Files, AdditionalChunks) when is_list(Files) ->
+ try strip_fils(Files, AdditionalChunks)
catch Error -> Error end.
-spec strip_release(Dir) ->
@@ -207,7 +229,17 @@ strip_files(Files) when is_list(Files) ->
Reason :: {'not_a_directory', term()} | info_rsn().
strip_release(Root) ->
- catch strip_rel(Root).
+ strip_release(Root, []).
+
+-spec strip_release(Dir, AdditionalChunks) ->
+ {'ok', [{module(), file:filename()}]}
+ | {'error', 'beam_lib', Reason} when
+ Dir :: atom() | file:filename(),
+ AdditionalChunks :: [chunkid()],
+ Reason :: {'not_a_directory', term()} | info_rsn().
+
+strip_release(Root, AdditionalChunks) ->
+ catch strip_rel(Root, AdditionalChunks).
-spec version(Beam) ->
{'ok', {module(), [Version :: term()]}} |
@@ -401,17 +433,17 @@ cmp_lists([{Id, C1} | R1], [{Id, C2} | R2]) ->
cmp_lists(_, _) ->
error(different_chunks).
-strip_rel(Root) ->
+strip_rel(Root, AdditionalChunks) ->
ok = assert_directory(Root),
- strip_fils(filelib:wildcard(filename:join(Root, "lib/*/ebin/*.beam"))).
+ strip_fils(filelib:wildcard(filename:join(Root, "lib/*/ebin/*.beam")), AdditionalChunks).
%% -> {ok, [{Mod, BinaryOrFileName}]} | throw(Error)
-strip_fils(Files) ->
- {ok, [begin {ok, Reply} = strip_file(F), Reply end || F <- Files]}.
+strip_fils(Files, AdditionalChunks) ->
+ {ok, [begin {ok, Reply} = strip_file(F, AdditionalChunks), Reply end || F <- Files]}.
%% -> {ok, {Mod, FileName}} | {ok, {Mod, binary()}} | throw(Error)
-strip_file(File) ->
- {ok, {Mod, Chunks}} = read_significant_chunks(File, significant_chunks()),
+strip_file(File, AdditionalChunks) ->
+ {ok, {Mod, Chunks}} = read_significant_chunks(File, AdditionalChunks ++ significant_chunks()),
{ok, Stripped0} = build_module(Chunks),
Stripped = compress(Stripped0),
case File of
diff --git a/lib/stdlib/src/stdlib.appup.src b/lib/stdlib/src/stdlib.appup.src
index 9e5d6a3bd8..37ea97c353 100644
--- a/lib/stdlib/src/stdlib.appup.src
+++ b/lib/stdlib/src/stdlib.appup.src
@@ -40,7 +40,8 @@
{<<"^3\\.6$">>,[restart_new_emulator]},
{<<"^3\\.6\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.7$">>,[restart_new_emulator]},
- {<<"^3\\.7\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}],
+ {<<"^3\\.7\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.7\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
[{<<"^3\\.4$">>,[restart_new_emulator]},
{<<"^3\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
@@ -54,4 +55,5 @@
{<<"^3\\.6$">>,[restart_new_emulator]},
{<<"^3\\.6\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.7$">>,[restart_new_emulator]},
- {<<"^3\\.7\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}]}.
+ {<<"^3\\.7\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.7\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
diff --git a/lib/stdlib/src/string.erl b/lib/stdlib/src/string.erl
index 2939e78d9d..1f8bdc5432 100644
--- a/lib/stdlib/src/string.erl
+++ b/lib/stdlib/src/string.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1247,18 +1247,20 @@ split_1(Bin, [_C|_]=Needle, Start, Where, Curr0, Acc) ->
end
end.
-lexemes_m([CP|_]=Cs0, {GCs,CPs,_}=Seps, Ts) when is_integer(CP) ->
+lexemes_m([CP|_]=Cs0, {GCs,CPs,_}=Seps0, Ts) when is_integer(CP) ->
case lists:member(CP, CPs) of
true ->
[GC|Cs2] = unicode_util:gc(Cs0),
case lists:member(GC, GCs) of
true ->
- lexemes_m(Cs2, Seps, Ts);
+ lexemes_m(Cs2, Seps0, Ts);
false ->
+ Seps = search_compile(Seps0),
{Lexeme,Rest} = lexeme_pick(Cs0, Seps, []),
lexemes_m(Rest, Seps, [Lexeme|Ts])
end;
false ->
+ Seps = search_compile(Seps0),
{Lexeme,Rest} = lexeme_pick(Cs0, Seps, []),
lexemes_m(Rest, Seps, [Lexeme|Ts])
end;
diff --git a/lib/stdlib/test/beam_lib_SUITE.erl b/lib/stdlib/test/beam_lib_SUITE.erl
index 6418dc7eb6..4b2694320e 100644
--- a/lib/stdlib/test/beam_lib_SUITE.erl
+++ b/lib/stdlib/test/beam_lib_SUITE.erl
@@ -35,7 +35,7 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
- normal/1, error/1, cmp/1, cmp_literals/1, strip/1, otp_6711/1,
+ normal/1, error/1, cmp/1, cmp_literals/1, strip/1, strip_add_chunks/1, otp_6711/1,
building/1, md5/1, encrypted_abstr/1, encrypted_abstr_file/1]).
-export([init_per_testcase/2, end_per_testcase/2]).
@@ -45,7 +45,7 @@ suite() ->
{timetrap,{minutes,2}}].
all() ->
- [error, normal, cmp, cmp_literals, strip, otp_6711,
+ [error, normal, cmp, cmp_literals, strip, strip_add_chunks, otp_6711,
building, md5, encrypted_abstr, encrypted_abstr_file].
groups() ->
@@ -401,6 +401,69 @@ strip(Conf) when is_list(Conf) ->
Source5D1, BeamFile5D1]),
ok.
+strip_add_chunks(Conf) when is_list(Conf) ->
+ PrivDir = ?privdir,
+ {SourceD1, BeamFileD1} = make_beam(PrivDir, simple, member),
+ {Source2D1, BeamFile2D1} = make_beam(PrivDir, simple2, concat),
+ {Source3D1, BeamFile3D1} = make_beam(PrivDir, make_fun, make_fun),
+ {Source4D1, BeamFile4D1} = make_beam(PrivDir, constant, constant),
+ {Source5D1, BeamFile5D1} = make_beam(PrivDir, lines, lines),
+
+ NoOfTables = erlang:system_info(ets_count),
+ P0 = pps(),
+
+ %% strip binary
+ verify(not_a_beam_file, beam_lib:strip(<<>>)),
+ {ok, B1} = file:read_file(BeamFileD1),
+ {ok, {simple, NB1}} = beam_lib:strip(B1),
+
+ BId1 = chunk_ids(B1),
+ NBId1 = chunk_ids(NB1),
+ true = length(BId1) > length(NBId1),
+ compare_chunks(B1, NB1, NBId1),
+
+ %% Keep all the extra chunks
+ ExtraChunks = ["Abst" , "Dbgi" , "Attr" , "CInf" , "LocT" , "Atom" ],
+ {ok, {simple, AB1}} = beam_lib:strip(B1, ExtraChunks),
+ ABId1 = chunk_ids(AB1),
+ true = length(BId1) == length(ABId1),
+ compare_chunks(B1, AB1, ABId1),
+
+ %% strip file - Keep extra chunks
+ verify(file_error, beam_lib:strip(foo)),
+ {ok, {simple, _}} = beam_lib:strip(BeamFileD1, ExtraChunks),
+ compare_chunks(B1, BeamFileD1, ABId1),
+
+ %% strip_files
+ {ok, B2} = file:read_file(BeamFile2D1),
+ {ok, [{simple,_},{simple2,_}]} = beam_lib:strip_files([B1, B2], ExtraChunks),
+ {ok, [{simple,_},{simple2,_},{make_fun,_},{constant,_}]} =
+ beam_lib:strip_files([BeamFileD1, BeamFile2D1, BeamFile3D1, BeamFile4D1], ExtraChunks),
+
+ %% check that each module can be loaded.
+ {module, simple} = code:load_abs(filename:rootname(BeamFileD1)),
+ {module, simple2} = code:load_abs(filename:rootname(BeamFile2D1)),
+ {module, make_fun} = code:load_abs(filename:rootname(BeamFile3D1)),
+ {module, constant} = code:load_abs(filename:rootname(BeamFile4D1)),
+
+ %% check that line number information is still present after stripping
+ {module, lines} = code:load_abs(filename:rootname(BeamFile5D1)),
+ {'EXIT',{badarith,[{lines,t,1,Info}|_]}} = (catch lines:t(atom)),
+ false = code:purge(lines),
+ true = code:delete(lines),
+ {ok, {lines,BeamFile5D1}} = beam_lib:strip(BeamFile5D1),
+ {module, lines} = code:load_abs(filename:rootname(BeamFile5D1)),
+ {'EXIT',{badarith,[{lines,t,1,Info}|_]}} = (catch lines:t(atom)),
+
+ true = (P0 == pps()),
+ NoOfTables = erlang:system_info(ets_count),
+
+ delete_files([SourceD1, BeamFileD1,
+ Source2D1, BeamFile2D1,
+ Source3D1, BeamFile3D1,
+ Source4D1, BeamFile4D1,
+ Source5D1, BeamFile5D1]),
+ ok.
otp_6711(Conf) when is_list(Conf) ->
{'EXIT',{function_clause,_}} = (catch {a, beam_lib:info(3)}),
@@ -729,6 +792,7 @@ make_beam(Dir, Module, F) ->
FileBase = filename:join(Dir, atom_to_list(Module)),
Source = FileBase ++ ".erl",
BeamFile = FileBase ++ ".beam",
+ file:delete(BeamFile),
simple_file(Source, Module, F),
{ok, _} = compile:file(Source, [{outdir,Dir}, debug_info, report]),
{Source, BeamFile}.
diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl
index 7703198c4c..87ca9bd32c 100644
--- a/lib/stdlib/test/ets_SUITE.erl
+++ b/lib/stdlib/test/ets_SUITE.erl
@@ -56,6 +56,7 @@
-export([t_match_spec_run/1]).
-export([t_bucket_disappears/1]).
-export([t_named_select/1]).
+-export([select_fixtab_owner_change/1]).
-export([otp_5340/1]).
-export([otp_6338/1]).
-export([otp_6842_select_1000/1]).
@@ -69,7 +70,10 @@
-export([smp_insert/1, smp_fixed_delete/1, smp_unfix_fix/1, smp_select_delete/1,
smp_ordered_iteration/1,
smp_select_replace/1, otp_8166/1, otp_8732/1, delete_unfix_race/1]).
--export([throughput_benchmark/0, test_throughput_benchmark/1]).
+-export([throughput_benchmark/0,
+ throughput_benchmark/1,
+ test_throughput_benchmark/1,
+ long_throughput_benchmark/1]).
-export([exit_large_table_owner/1,
exit_many_large_table_owner/1,
exit_many_tables_owner/1,
@@ -92,6 +96,7 @@
-include_lib("stdlib/include/ms_transform.hrl"). % ets:fun2ms
-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/include/ct_event.hrl").
-define(m(A,B), assert_eq(A,B)).
-define(heap_binary_size, 64).
@@ -130,7 +135,7 @@ all() ->
t_insert_list, t_test_ms, t_select_delete, t_select_replace,
t_select_replace_next_bug,
t_ets_dets, memory, t_select_reverse, t_bucket_disappears,
- t_named_select,
+ t_named_select, select_fixtab_owner_change,
select_fail, t_insert_new, t_repair_continuation,
otp_5340, otp_6338, otp_6842_select_1000, otp_7665,
select_mbuf_trapping,
@@ -150,7 +155,8 @@ all() ->
take,
whereis_table,
delete_unfix_race,
- test_throughput_benchmark].
+ test_throughput_benchmark,
+ {group, benchmark}].
groups() ->
[{new, [],
@@ -178,7 +184,9 @@ groups() ->
{meta_smp, [],
[meta_lookup_unnamed_read, meta_lookup_unnamed_write,
meta_lookup_named_read, meta_lookup_named_write,
- meta_newdel_unnamed, meta_newdel_named]}].
+ meta_newdel_unnamed, meta_newdel_named]},
+ {benchmark, [],
+ [long_throughput_benchmark]}].
init_per_suite(Config) ->
erts_debug:set_internal_state(available_internal_state, true),
@@ -191,9 +199,61 @@ end_per_suite(_Config) ->
catch erts_debug:set_internal_state(available_internal_state, false),
ok.
+init_per_group(benchmark, Config) ->
+ P = self(),
+ %% Spawn owner of ETS table that is alive until end_per_group is run
+ EtsProcess =
+ spawn(
+ fun()->
+ Tab = ets:new(ets_benchmark_result_summary_tab, [public]),
+ P ! {the_table, Tab},
+ receive
+ kill -> ok
+ end
+ end),
+ Tab = receive {the_table, T} -> T end,
+ CounterNames = [nr_of_benchmarks,
+ total_throughput,
+ nr_of_set_benchmarks,
+ total_throughput_set,
+ nr_of_ordered_set_benchmarks,
+ total_throughput_ordered_set],
+ lists:foreach(fun(CtrName) ->
+ ets:insert(Tab, {CtrName, 0.0})
+ end,
+ CounterNames),
+ [{ets_benchmark_result_summary_tab, Tab},
+ {ets_benchmark_result_summary_tab_process, EtsProcess} | Config];
init_per_group(_GroupName, Config) ->
Config.
+end_per_group(benchmark, Config) ->
+ T = proplists:get_value(ets_benchmark_result_summary_tab, Config),
+ EtsProcess = proplists:get_value(ets_benchmark_result_summary_tab_process, Config),
+ Report =
+ fun(NOfBenchmarksCtr, TotThroughoutCtr, Name) ->
+ Average =
+ ets:lookup_element(T, TotThroughoutCtr, 2) /
+ ets:lookup_element(T, NOfBenchmarksCtr, 2),
+ io:format("~p ~p~n", [Name, Average]),
+ ct_event:notify(
+ #event{name = benchmark_data,
+ data = [{suite,"ets_bench"},
+ {name, Name},
+ {value, Average}]})
+ end,
+ Report(nr_of_benchmarks,
+ total_throughput,
+ "Average Throughput"),
+ Report(nr_of_set_benchmarks,
+ total_throughput_set,
+ "Average Throughput Set"),
+ Report(nr_of_ordered_set_benchmarks,
+ total_throughput_ordered_set,
+ "Average Throughput Ordered Set"),
+ ets:delete(T),
+ EtsProcess ! kill,
+ Config;
end_per_group(_GroupName, Config) ->
Config.
@@ -249,7 +309,64 @@ t_named_select_do(Opts) ->
verify_etsmem(EtsMem).
+%% Verify select and friends release fixtab as they should
+%% even when owneship is changed between traps.
+select_fixtab_owner_change(_Config) ->
+ T = ets:new(xxx, [protected]),
+ NKeys = 2000,
+ [ets:insert(T,{K,K band 7}) || K <- lists:seq(1,NKeys)],
+
+ %% Buddy and Papa will ping-pong table ownership between them
+ %% and the aim is to give Buddy the table when he is
+ %% in the middle of a yielding select* call.
+ {Buddy,_} = spawn_opt(fun() -> sfoc_buddy_loop(T, 1, undefined) end,
+ [link,monitor]),
+ sfoc_papa_loop(T, Buddy),
+
+ receive {'DOWN', _, process, Buddy, _} -> ok end,
+ ets:delete(T),
+ ok.
+
+sfoc_buddy_loop(T, I, State0) ->
+ receive
+ {'ETS-TRANSFER', T, Papa, _} ->
+ ets:give_away(T, Papa, State0),
+ case State0 of
+ done ->
+ ok;
+ _ ->
+ State1 = sfoc_traverse(T, I, State0),
+ %% Verify no fixation left
+ {I, false} = {I, ets:info(T, safe_fixed_monotonic_time)},
+ sfoc_buddy_loop(T, I+1, State1)
+ end
+ end.
+
+sfoc_papa_loop(T, Buddy) ->
+ ets:give_away(T, Buddy, "Catch!"),
+ receive
+ {'ETS-TRANSFER', T, Buddy, State} ->
+ case State of
+ done ->
+ ok;
+ _ ->
+ sfoc_papa_loop(T, Buddy)
+ end
+ end.
+
+sfoc_traverse(T, 1, S) ->
+ ets:select(T, [{{'$1',7}, [], ['$1']}]), S;
+sfoc_traverse(T, 2, S) ->
+ 0 = ets:select_count(T, [{{'$1',7}, [], [false]}]), S;
+sfoc_traverse(T, 3, _) ->
+ Limit = ets:info(T, size) div 2,
+ {_, Continuation} = ets:select(T, [{{'$1',7}, [], ['$1']}],
+ Limit),
+ Continuation;
+sfoc_traverse(_T, 4, Continuation) ->
+ _ = ets:select(Continuation),
+ done.
%% Check ets:match_spec_run/2.
t_match_spec_run(Config) when is_list(Config) ->
@@ -6472,8 +6589,8 @@ whereis_table(Config) when is_list(Config) ->
ok.
-%% The following work functions are used by
-%% throughput_benchmark/4. They are declared on the top level beacuse
+%% The following help functions are used by
+%% throughput_benchmark. They are declared on the top level beacuse
%% declaring them as function local funs cause a scalability issue.
get_op([{_,O}], _RandNum) ->
O;
@@ -6508,10 +6625,131 @@ prefill_table_loop(T, RS0, N, ObjFun) ->
ets:insert(T, ObjFun(Key)),
prefill_table_loop(T, RS1, N-1, ObjFun).
-throughput_benchmark() ->
- throughput_benchmark(false, not_set, not_set).
+-record(ets_throughput_bench_config,
+ {benchmark_duration_ms = 3000,
+ recover_time_ms = 1000,
+ thread_counts = not_set,
+ key_ranges = [1000000],
+ scenarios =
+ [
+ [
+ {0.5, insert},
+ {0.5, delete}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.8, lookup}
+ ],
+ [
+ {0.01, insert},
+ {0.01, delete},
+ {0.98, lookup}
+ ],
+ [
+ {1.0, lookup}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.4, lookup},
+ {0.4, nextseq10}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.4, lookup},
+ {0.4, nextseq100}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.4, lookup},
+ {0.4, nextseq1000}
+ ],
+ [
+ {1.0, nextseq1000}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.79, lookup},
+ {0.01, selectAll}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.7999, lookup},
+ {0.0001, selectAll}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.799999, lookup},
+ {0.000001, selectAll}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.79, lookup},
+ {0.01, partial_select1000}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.7999, lookup},
+ {0.0001, partial_select1000}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.799999, lookup},
+ {0.000001, partial_select1000}
+ ]
+ ],
+ table_types =
+ [
+ [ordered_set, public],
+ [ordered_set, public, {write_concurrency, true}],
+ [ordered_set, public, {read_concurrency, true}],
+ [ordered_set, public, {write_concurrency, true}, {read_concurrency, true}],
+ [set, public],
+ [set, public, {write_concurrency, true}],
+ [set, public, {read_concurrency, true}],
+ [set, public, {write_concurrency, true}, {read_concurrency, true}]
+ ],
+ etsmem_fun = fun() -> ok end,
+ verify_etsmem_fun = fun(_) -> true end,
+ notify_res_fun = fun(_Name, _Throughput) -> ok end,
+ print_result_paths_fun =
+ fun(ResultPath, _LatestResultPath) ->
+ Comment =
+ io_lib:format("<a href=\"file:///~s\">Result visualization</a>",[ResultPath]),
+ {comment, Comment}
+ end
+ }).
+
+stdout_notify_res(ResultPath, LatestResultPath) ->
+ io:format("Result Location: /~s~n", [ResultPath]),
+ io:format("Latest Result Location: ~s~n", [LatestResultPath]).
-throughput_benchmark(TestMode, BenchmarkRunMs, RecoverTimeMs) ->
+throughput_benchmark() ->
+ throughput_benchmark(
+ #ets_throughput_bench_config{
+ print_result_paths_fun = fun stdout_notify_res/2}).
+
+throughput_benchmark(
+ #ets_throughput_bench_config{
+ benchmark_duration_ms = BenchmarkDurationMs,
+ recover_time_ms = RecoverTimeMs,
+ thread_counts = ThreadCountsOpt,
+ key_ranges = KeyRanges,
+ scenarios = Scenarios,
+ table_types = TableTypes,
+ etsmem_fun = ETSMemFun,
+ verify_etsmem_fun = VerifyETSMemFun,
+ notify_res_fun = NotifyResFun,
+ print_result_paths_fun = PrintResultPathsFun}) ->
NrOfSchedulers = erlang:system_info(schedulers),
%% Definitions of operations that are supported by the benchmark
NextSeqOp =
@@ -6576,7 +6814,7 @@ throughput_benchmark(TestMode, BenchmarkRunMs, RecoverTimeMs) ->
fun(T,KeyRange) -> NextSeqOp(T,KeyRange,1000) end,
selectAll =>
fun(T,_KeyRange) ->
- case -1 =:= ets:select_count(T, ets:fun2ms(fun(X) -> true end)) of
+ case -1 =:= ets:select_count(T, ets:fun2ms(fun(_X) -> true end)) of
true -> io:format("Will never be printed");
false -> ok
end
@@ -6625,11 +6863,28 @@ throughput_benchmark(TestMode, BenchmarkRunMs, RecoverTimeMs) ->
false -> ok
end
end,
+ DataHolder =
+ fun DataHolderFun(Data)->
+ receive
+ {get_data, Pid} -> Pid ! {ets_bench_data, Data};
+ D -> DataHolderFun([Data,D])
+ end
+ end,
+ DataHolderPid = spawn_link(fun()-> DataHolder([]) end),
+ PrintData =
+ fun (Str, List) ->
+ io:format(Str, List),
+ DataHolderPid ! io_lib:format(Str, List)
+ end,
+ GetData =
+ fun () ->
+ DataHolderPid ! {get_data, self()},
+ receive {ets_bench_data, Data} -> Data end
+ end,
%% Function that runs a benchmark instance and returns the number
%% of operations that were performed
RunBenchmark =
- fun(NrOfProcs, TableConfig, Scenario,
- Range, Duration, RecoverTime) ->
+ fun({NrOfProcs, TableConfig, Scenario, Range, Duration}) ->
ProbHelpTab = CalculateOpsProbHelpTab(Scenario, 0),
Table = ets:new(t, TableConfig),
Nobj = Range div 2,
@@ -6637,16 +6892,15 @@ throughput_benchmark(TestMode, BenchmarkRunMs, RecoverTimeMs) ->
Nobj = ets:info(Table, size),
SafeFixTableIfRequired(Table, Scenario, true),
ParentPid = self(),
+ Worker =
+ fun() ->
+ receive start -> ok end,
+ WorksDone =
+ do_work(0, Table, ProbHelpTab, Range, Operations),
+ ParentPid ! WorksDone
+ end,
ChildPids =
- lists:map(
- fun(_N) ->
- spawn(fun() ->
- receive start -> ok end,
- WorksDone =
- do_work(0, Table, ProbHelpTab, Range, Operations),
- ParentPid ! WorksDone
- end)
- end, lists:seq(1, NrOfProcs)),
+ lists:map(fun(_N) ->spawn_link(Worker)end, lists:seq(1, NrOfProcs)),
lists:foreach(fun(Pid) -> Pid ! start end, ChildPids),
timer:sleep(Duration),
lists:foreach(fun(Pid) -> Pid ! stop end, ChildPids),
@@ -6658,185 +6912,194 @@ throughput_benchmark(TestMode, BenchmarkRunMs, RecoverTimeMs) ->
end, 0, ChildPids),
SafeFixTableIfRequired(Table, Scenario, false),
ets:delete(Table),
- timer:sleep(RecoverTime),
TotalWorksDone
end,
- %%
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %%%% Benchmark Configuration %%%%%%%%%%%%%%%%%%%%%%%%
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %%
- %% Change the following variables to configure the benchmark runs
- ThreadCounts =
- case TestMode of
- true -> [1, NrOfSchedulers];
- false -> CalculateThreadCounts([1])
+ RunBenchmarkInSepProcess =
+ fun(ParameterTuple) ->
+ P = self(),
+ spawn_link(fun()-> P ! {bench_result, RunBenchmark(ParameterTuple)} end),
+ Result = receive {bench_result, Res} -> Res end,
+ timer:sleep(RecoverTimeMs),
+ Result
end,
- KeyRanges = % Sizes of the key ranges
- case TestMode of
- true -> [50000];
- false -> [1000000]
+ RunBenchmarkAndReport =
+ fun(ThreadCount,
+ TableType,
+ Scenario,
+ KeyRange,
+ Duration) ->
+ Result = RunBenchmarkInSepProcess({ThreadCount,
+ TableType,
+ Scenario,
+ KeyRange,
+ Duration}),
+ Throughput = Result/(Duration/1000.0),
+ PrintData("; ~f",[Throughput]),
+ Name = io_lib:format("Scenario: ~w, Key Range Size: ~w, "
+ "# of Processes: ~w, Table Type: ~w",
+ [Scenario, KeyRange, ThreadCount, TableType]),
+ NotifyResFun(Name, Throughput)
end,
- Duration =
- case BenchmarkRunMs of % Duration of a benchmark run in milliseconds
- not_set -> 30000;
- _ -> BenchmarkRunMs
- end,
- TimeMsToSleepAfterEachBenchmarkRun =
- case RecoverTimeMs of
- not_set -> 1000;
- _ -> RecoverTimeMs
+ ThreadCounts =
+ case ThreadCountsOpt of
+ not_set ->
+ CalculateThreadCounts([1]);
+ _ -> ThreadCountsOpt
end,
- TableTypes = % The table types that will be benchmarked
- [
- [ordered_set, public],
- [ordered_set, public, {write_concurrency, true}],
- [ordered_set, public, {read_concurrency, true}],
- [ordered_set, public, {write_concurrency, true}, {read_concurrency, true}],
- [set, public],
- [set, public, {write_concurrency, true}],
- [set, public, {read_concurrency, true}],
- [set, public, {write_concurrency, true}, {read_concurrency, true}]
- ],
- Scenarios = % Benchmark scenarios (the fractions should add up to approximately 1.0)
- [
- [
- {0.5, insert},
- {0.5, delete}
- ],
- [
- {0.1, insert},
- {0.1, delete},
- {0.8, lookup}
- ],
- [
- {0.01, insert},
- {0.01, delete},
- {0.98, lookup}
- ],
- [
- {1.0, lookup}
- ],
- [
- {0.1, insert},
- {0.1, delete},
- {0.4, lookup},
- {0.4, nextseq10}
- ],
- [
- {0.1, insert},
- {0.1, delete},
- {0.4, lookup},
- {0.4, nextseq100}
- ],
- [
- {0.1, insert},
- {0.1, delete},
- {0.4, lookup},
- {0.4, nextseq1000}
- ],
- [
- {1.0, nextseq1000}
- ],
- [
- {0.1, insert},
- {0.1, delete},
- {0.79, lookup},
- {0.01, selectAll}
- ],
- [
- {0.1, insert},
- {0.1, delete},
- {0.7999, lookup},
- {0.0001, selectAll}
- ],
- [
- {0.1, insert},
- {0.1, delete},
- {0.799999, lookup},
- {0.000001, selectAll}
- ],
- [
- {0.1, insert},
- {0.1, delete},
- {0.79, lookup},
- {0.01, partial_select1000}
- ],
- [
- {0.1, insert},
- {0.1, delete},
- {0.7999, lookup},
- {0.0001, partial_select1000}
- ],
- [
- {0.1, insert},
- {0.1, delete},
- {0.799999, lookup},
- {0.000001, partial_select1000}
- ]
- ],
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %%%% End of Benchmark Configuration %%%%%%%%%%%%%%%%
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %% Prepare for memory check
- EtsMem = case TestMode of
- true -> etsmem();
- false -> ok
- end,
%% Run the benchmark
- io:format("# Each instance of the benchmark runs for ~w seconds:~n", [Duration/1000]),
- io:format("# The result of a benchmark instance is presented as a number representing~n"),
- io:format("# the number of operations performed per second:~n~n~n"),
- io:format("# To plot graphs for the results below:~n"),
- io:format("# 1. Open \"$ERL_TOP/lib/stdlib/test/ets_SUITE_data/visualize_throughput.html\" in a web browser~n"),
- io:format("# 2. Copy the lines between \"#BENCHMARK STARTED$\" and \"#BENCHMARK ENDED$\" below~n"),
- io:format("# 3. Paste the lines copied in step 2 to the text box in the browser window opened in~n"),
- io:format("# step 1 and press the Render button~n~n"),
- io:format("#BENCHMARK STARTED$~n"),
+ PrintData("# Each instance of the benchmark runs for ~w seconds:~n", [BenchmarkDurationMs/1000]),
+ PrintData("# The result of a benchmark instance is presented as a number representing~n",[]),
+ PrintData("# the number of operations performed per second:~n~n~n",[]),
+ PrintData("# To plot graphs for the results below:~n",[]),
+ PrintData("# 1. Open \"$ERL_TOP/lib/stdlib/test/ets_SUITE_data/visualize_throughput.html\" in a web browser~n",[]),
+ PrintData("# 2. Copy the lines between \"#BENCHMARK STARTED$\" and \"#BENCHMARK ENDED$\" below~n",[]),
+ PrintData("# 3. Paste the lines copied in step 2 to the text box in the browser window opened in~n",[]),
+ PrintData("# step 1 and press the Render button~n~n",[]),
+ PrintData("#BENCHMARK STARTED$~n",[]),
+ EtsMem = ETSMemFun(),
%% The following loop runs all benchmark scenarios and prints the results (i.e, operations/second)
lists:foreach(
fun(KeyRange) ->
lists:foreach(
fun(Scenario) ->
- io:format("Scenario: ~s | Key Range Size: ~w$~n",
- [RenderScenario(Scenario, ""),
- KeyRange]),
+ PrintData("Scenario: ~s | Key Range Size: ~w$~n",
+ [RenderScenario(Scenario, ""), KeyRange]),
lists:foreach(
fun(ThreadCount) ->
- io:format("; ~w",[ThreadCount])
+ PrintData("; ~w",[ThreadCount])
end,
ThreadCounts),
- io:format("$~n",[]),
+ PrintData("$~n",[]),
lists:foreach(
fun(TableType) ->
- io:format("~w ",[TableType]),
+ PrintData("~w ",[TableType]),
lists:foreach(
fun(ThreadCount) ->
- Result = RunBenchmark(ThreadCount,
+ RunBenchmarkAndReport(ThreadCount,
TableType,
Scenario,
KeyRange,
- Duration,
- TimeMsToSleepAfterEachBenchmarkRun),
- io:format("; ~f",[Result/(Duration/1000.0)])
+ BenchmarkDurationMs)
end,
ThreadCounts),
- io:format("$~n",[])
+ PrintData("$~n",[])
end,
TableTypes)
end,
Scenarios)
end,
KeyRanges),
- io:format("~n#BENCHMARK ENDED$~n~n"),
- case TestMode of
- true -> verify_etsmem(EtsMem);
- false -> ok
- end.
+ PrintData("~n#BENCHMARK ENDED$~n~n",[]),
+ VerifyETSMemFun(EtsMem),
+ DataDir = filename:join(filename:dirname(code:which(?MODULE)), "ets_SUITE_data"),
+ TemplatePath = filename:join(DataDir, "visualize_throughput.html"),
+ {ok, Template} = file:read_file(TemplatePath),
+ OutputData = string:replace(Template, "#bench_data_placeholder", GetData()),
+ OutputPath1 = filename:join(DataDir, "ets_bench_result.html"),
+ {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_datetime(erlang:timestamp()),
+ StrTime = lists:flatten(io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w",[Year,Month,Day,Hour,Minute,Second])),
+ OutputPath2 = filename:join(DataDir, io_lib:format("ets_bench_result_~s.html", [StrTime])),
+ file:write_file(OutputPath1, OutputData),
+ file:write_file(OutputPath2, OutputData),
+ PrintResultPathsFun(OutputPath2, OutputPath1).
test_throughput_benchmark(Config) when is_list(Config) ->
- throughput_benchmark(true, 100, 0).
-
+ throughput_benchmark(
+ #ets_throughput_bench_config{
+ benchmark_duration_ms = 100,
+ recover_time_ms = 0,
+ thread_counts = [1, erlang:system_info(schedulers)],
+ key_ranges = [50000],
+ etsmem_fun = fun etsmem/0,
+ verify_etsmem_fun = fun verify_etsmem/1}).
+
+long_throughput_benchmark(Config) when is_list(Config) ->
+ N = erlang:system_info(schedulers),
+ throughput_benchmark(
+ #ets_throughput_bench_config{
+ benchmark_duration_ms = 3000,
+ recover_time_ms = 1000,
+ thread_counts = [1, N div 2, N],
+ key_ranges = [1000000],
+ scenarios =
+ [
+ [
+ {0.5, insert},
+ {0.5, delete}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.8, lookup}
+ ],
+ [
+ {0.01, insert},
+ {0.01, delete},
+ {0.98, lookup}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.4, lookup},
+ {0.4, nextseq100}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.79, lookup},
+ {0.01, selectAll}
+ ],
+ [
+ {0.1, insert},
+ {0.1, delete},
+ {0.79, lookup},
+ {0.01, partial_select1000}
+ ]
+ ],
+ table_types =
+ [
+ [ordered_set, public, {write_concurrency, true}, {read_concurrency, true}],
+ [set, public, {write_concurrency, true}, {read_concurrency, true}]
+ ],
+ etsmem_fun = fun etsmem/0,
+ verify_etsmem_fun = fun verify_etsmem/1,
+ notify_res_fun =
+ fun(Name, Throughput) ->
+ SummaryTable =
+ proplists:get_value(ets_benchmark_result_summary_tab, Config),
+ AddToSummaryCounter =
+ case SummaryTable of
+ undefined ->
+ fun(_, _) ->
+ ok
+ end;
+ Tab ->
+ fun(CounterName, ToAdd) ->
+ OldVal = ets:lookup_element(Tab, CounterName, 2),
+ NewVal = OldVal + ToAdd,
+ ets:insert(Tab, {CounterName, NewVal})
+ end
+ end,
+ Record =
+ fun(NoOfBenchsCtr, TotThrputCtr) ->
+ AddToSummaryCounter(NoOfBenchsCtr, 1),
+ AddToSummaryCounter(TotThrputCtr, Throughput)
+ end,
+ Record(nr_of_benchmarks, total_throughput),
+ case string:find(Name, "ordered_set") of
+ nomatch ->
+ Record(nr_of_set_benchmarks, total_throughput_set);
+ _ ->
+ Record(nr_of_ordered_set_benchmarks,
+ total_throughput_ordered_set)
+ end,
+ ct_event:notify(
+ #event{name = benchmark_data,
+ data = [{suite,"ets_bench"},
+ {name, Name},
+ {value,Throughput}]})
+ end
+ }).
add_lists(L1,L2) ->
add_lists(L1,L2,[]).
diff --git a/lib/stdlib/test/ets_SUITE_data/visualize_throughput.html b/lib/stdlib/test/ets_SUITE_data/visualize_throughput.html
index a2c61aa938..27d6849c60 100644
--- a/lib/stdlib/test/ets_SUITE_data/visualize_throughput.html
+++ b/lib/stdlib/test/ets_SUITE_data/visualize_throughput.html
@@ -42,7 +42,7 @@
</p>
Paste the generated data in the field below and press the Render button:
<br>
- <textarea id="dataField" rows="4" cols="50"></textarea>
+ <textarea id="dataField" rows="4" cols="50">#bench_data_placeholder</textarea>
<br>
<input type="checkbox" id="barPlot"> Bar Plot
<br>
@@ -56,13 +56,13 @@
<br>
<input type="checkbox" class="showCheck" value="[ordered_set,public,{write_concurrency,true},{read_concurrency,true}]" checked> Show <code>[ordered_set,public,{write_concurrency,true},{read_concurrency,true}]</code>
<br>
- <input type="checkbox" class="showCheck" value="[set,public]"> Show <code>[set,public]</code>
+ <input type="checkbox" class="showCheck" value="[set,public]" checked> Show <code>[set,public]</code>
<br>
- <input type="checkbox" class="showCheck" value="[set,public,{write_concurrency,true}]"> Show <code>[set,public,{write_concurrency,true}]</code>
+ <input type="checkbox" class="showCheck" value="[set,public,{write_concurrency,true}]" checked> Show <code>[set,public,{write_concurrency,true}]</code>
<br>
- <input type="checkbox" class="showCheck" value="[set,public,{read_concurrency,true}]"> Show <code>[set,public,{read_concurrency,true}]</code>
+ <input type="checkbox" class="showCheck" value="[set,public,{read_concurrency,true}]" checked> Show <code>[set,public,{read_concurrency,true}]</code>
<br>
- <input type="checkbox" class="showCheck" value="[set,public,{write_concurrency,true},{read_concurrency,true}]"> Show <code>[set,public,{write_concurrency,true},{read_concurrency,true}]</code>
+ <input type="checkbox" class="showCheck" value="[set,public,{write_concurrency,true},{read_concurrency,true}]" checked> Show <code>[set,public,{write_concurrency,true},{read_concurrency,true}]</code>
<br>
<button id="renderButton" type="button">Render</button>
diff --git a/lib/stdlib/test/stdlib.spec b/lib/stdlib/test/stdlib.spec
index 4de7c1a0eb..bf64eae2c7 100644
--- a/lib/stdlib/test/stdlib.spec
+++ b/lib/stdlib/test/stdlib.spec
@@ -2,3 +2,6 @@
{skip_groups,"../stdlib_test",stdlib_bench_SUITE,
[binary,base64,gen_server,gen_statem,unicode],
"Benchmark only"}.
+{skip_groups,"../stdlib_test",ets_SUITE,
+ [benchmark],
+ "Benchmark only"}.
diff --git a/lib/stdlib/test/stdlib_bench.spec b/lib/stdlib/test/stdlib_bench.spec
index 7a0da811a0..6d665f22b6 100644
--- a/lib/stdlib/test/stdlib_bench.spec
+++ b/lib/stdlib/test/stdlib_bench.spec
@@ -8,3 +8,4 @@
{skip_groups,"../stdlib_test",stdlib_bench_SUITE,
[gen_server_comparison,gen_statem_comparison],
"Not a benchmark"}.
+{groups,"../stdlib_test",ets_SUITE,[benchmark]}.
diff --git a/lib/stdlib/test/string_SUITE.erl b/lib/stdlib/test/string_SUITE.erl
index 251e09121c..6afe9e7a76 100644
--- a/lib/stdlib/test/string_SUITE.erl
+++ b/lib/stdlib/test/string_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -52,7 +52,7 @@
suite() ->
[{ct_hooks,[ts_install_cth]},
- {timetrap,{minutes,1}}].
+ {timetrap,{minutes,2}}].
all() ->
[{group, chardata}, {group, list_string}].
@@ -737,10 +737,10 @@ meas(Config) ->
case ct:get_timetrap_info() of
{_,{_,Scale}} when Scale > 1 ->
{skip,{will_not_run_in_debug,Scale}};
- _ -> % No scaling, run at most 1.5 min
+ _ -> % No scaling, run at most 2 mins
Tester = spawn(Exec),
receive {test_done, Tester} -> ok
- after 90000 ->
+ after 120000 ->
io:format("Timelimit reached stopping~n",[]),
exit(Tester, die)
end,
@@ -754,19 +754,22 @@ do_measure(DataDir) ->
io:format("~p~n",[byte_size(Bin)]),
Do = fun(Name, Func, Mode) ->
{N, Mean, Stddev, _} = time_func(Func, Mode, Bin, 20),
- io:format("~15w ~6w ~6.2fms ±~5.2fms #~.2w gc included~n",
+ io:format("~15w ~15w ~8.2fms ±~6.2fms #~.2w gc included~n",
[Name, Mode, Mean/1000, Stddev/1000, N])
end,
Do2 = fun(Name, Func, Mode) ->
{N, Mean, Stddev, _} = time_func(Func, binary, <<>>, 20),
- io:format("~15w ~6w ~6.2fms ±~5.2fms #~.2w gc included~n",
+ io:format("~15w ~15w ~8.2fms ±~6.2fms #~.2w gc included~n",
[Name, Mode, Mean/1000, Stddev/1000, N])
end,
+ %% lefty_list means a list balanced to the left, like
+ %% [[[30],31],32]. Only some functions check such lists.
+ Modes = [list, lefty_list, binary, {many_lists,1}, {many_lists, 4}],
io:format("----------------------~n"),
Do(old_tokens, fun(Str) -> string:tokens(Str, [$\n,$\r]) end, list),
Tokens = {lexemes, fun(Str) -> string:lexemes(Str, [$\n,$\r]) end},
- [Do(Name,Fun,Mode) || {Name,Fun} <- [Tokens], Mode <- [list, binary]],
+ [Do(Name,Fun,Mode) || {Name,Fun} <- [Tokens], Mode <- Modes],
S0 = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxy.....",
S0B = <<"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxy.....">>,
@@ -824,17 +827,17 @@ do_measure(DataDir) ->
io:format("--~n",[]),
NthTokens = {nth_lexemes, fun(Str) -> string:nth_lexeme(Str, 18000, [$\n,$\r]) end},
- [Do(Name,Fun,Mode) || {Name,Fun} <- [NthTokens], Mode <- [list, binary]],
+ [Do(Name,Fun,Mode) || {Name,Fun} <- [NthTokens], Mode <- Modes],
Do2(take_t, repeat(fun() -> string:take(S0, [$.,$y], false, trailing) end), list),
Do2(take_t, repeat(fun() -> string:take(S0B, [$.,$y], false, trailing) end), binary),
Do2(take_tc, repeat(fun() -> string:take(S0, [$x], true, trailing) end), list),
Do2(take_tc, repeat(fun() -> string:take(S0B, [$x], true, trailing) end), binary),
Length = {length, fun(Str) -> string:length(Str) end},
- [Do(Name,Fun,Mode) || {Name,Fun} <- [Length], Mode <- [list, binary]],
+ [Do(Name,Fun,Mode) || {Name,Fun} <- [Length], Mode <- Modes],
Reverse = {reverse, fun(Str) -> string:reverse(Str) end},
- [Do(Name,Fun,Mode) || {Name,Fun} <- [Reverse], Mode <- [list, binary]],
+ [Do(Name,Fun,Mode) || {Name,Fun} <- [Reverse], Mode <- Modes],
ok.
@@ -1064,7 +1067,33 @@ time_func(N,Sum,SumSq, _, _, Res, _) ->
{N, Mean, Stdev, Res}.
mode(binary, Bin) -> Bin;
-mode(list, Bin) -> unicode:characters_to_list(Bin).
+mode(list, Bin) -> unicode:characters_to_list(Bin);
+mode(lefty_list, Bin) ->
+ L = unicode:characters_to_list(Bin),
+ to_left(L);
+mode({many_lists, N}, Bin) ->
+ group(unicode:characters_to_list(Bin), N).
+
+group([], _N) ->
+ [];
+group(L, N) ->
+ try lists:split(N, L) of
+ {L1, L2} ->
+ [L1 | group(L2, N)]
+ catch
+ _:_ ->
+ [L]
+ end.
+
+to_left([]) ->
+ [];
+to_left([H|L]) ->
+ to_left([H], L).
+
+to_left(V, []) ->
+ V;
+to_left(V, [H|L]) ->
+ to_left([V,H], L).
%%
%% Old string lists Test cases starts here.
diff --git a/lib/stdlib/uc_spec/gen_unicode_mod.escript b/lib/stdlib/uc_spec/gen_unicode_mod.escript
index 70eec1a6f2..8636c69a0d 100755..100644
--- a/lib/stdlib/uc_spec/gen_unicode_mod.escript
+++ b/lib/stdlib/uc_spec/gen_unicode_mod.escript
@@ -4,7 +4,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2017. All Rights Reserved.
+%% Copyright Ericsson AB 2017-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -460,17 +460,73 @@ gen_cp(Fd) ->
" maybe_improper_list() | {error, unicode:chardata()}.\n"),
io:put_chars(Fd, "cp([C|_]=L) when is_integer(C) -> L;\n"),
io:put_chars(Fd, "cp([List]) -> cp(List);\n"),
- io:put_chars(Fd, "cp([List|R]) ->\n"),
- io:put_chars(Fd, " case cp(List) of\n"),
- io:put_chars(Fd, " [] -> cp(R);\n"),
- io:put_chars(Fd, " [CP] -> [CP|R];\n"),
- io:put_chars(Fd, " [C|R0] -> [C|[R0|R]];\n"),
- io:put_chars(Fd, " {error,Error} -> {error,[Error|R]}\n"),
- io:put_chars(Fd, " end;\n"),
+ io:put_chars(Fd, "cp([List|R]) -> cpl(List, R);\n"),
io:put_chars(Fd, "cp([]) -> [];\n"),
io:put_chars(Fd, "cp(<<C/utf8, R/binary>>) -> [C|R];\n"),
io:put_chars(Fd, "cp(<<>>) -> [];\n"),
- io:put_chars(Fd, "cp(<<R/binary>>) -> {error,R}.\n\n"),
+ io:put_chars(Fd, "cp(<<R/binary>>) -> {error,R}.\n"),
+ io:put_chars(Fd, "\n"),
+ io:put_chars(Fd, "cpl([C], R) when is_integer(C) -> [C|cpl_1_cont(R)];\n"),
+ io:put_chars(Fd, "cpl([C|T], R) when is_integer(C) -> [C|cpl_cont(T, R)];\n"),
+ io:put_chars(Fd, "cpl([List], R) -> cpl(List, R);\n"),
+ io:put_chars(Fd, "cpl([List|T], R) -> cpl(List, [T|R]);\n"),
+ io:put_chars(Fd, "cpl([], R) -> cp(R);\n"),
+ io:put_chars(Fd, "cpl(<<C/utf8, T/binary>>, R) -> [C,T|R];\n"),
+ io:put_chars(Fd, "cpl(<<>>, R) -> cp(R);\n"),
+ io:put_chars(Fd, "cpl(<<B/binary>>, R) -> {error,[B|R]}.\n"),
+ io:put_chars(Fd, "\n"),
+ io:put_chars(Fd, "%%%\n"),
+ io:put_chars(Fd, "\n"),
+ io:put_chars(Fd, "cpl_cont([C|T], R) when is_integer(C) -> [C|cpl_cont2(T, R)];\n"),
+ io:put_chars(Fd, "cpl_cont([L], R) -> cpl_cont(L, R);\n"),
+ io:put_chars(Fd, "cpl_cont([L|T], R) -> cpl_cont(L, [T|R]);\n"),
+ io:put_chars(Fd, "cpl_cont([], R) -> cpl_1_cont(R);\n"),
+ io:put_chars(Fd, "cpl_cont(T, R) -> [T|R].\n"),
+ io:put_chars(Fd, "\n"),
+ io:put_chars(Fd, "cpl_cont2([C|T], R) when is_integer(C) -> [C|cpl_cont3(T, R)];\n"),
+ io:put_chars(Fd, "cpl_cont2([L], R) -> cpl_cont2(L, R);\n"),
+ io:put_chars(Fd, "cpl_cont2([L|T], R) -> cpl_cont2(L, [T|R]);\n"),
+ io:put_chars(Fd, "cpl_cont2([], R) -> cpl_1_cont2(R);\n"),
+ io:put_chars(Fd, "cpl_cont2(T, R) -> [T|R].\n"),
+ io:put_chars(Fd, "\n"),
+ io:put_chars(Fd, "cpl_cont3([C], R) when is_integer(C) -> [C|R];\n"),
+ io:put_chars(Fd, "cpl_cont3([C|T], R) when is_integer(C) -> [C,T|R];\n"),
+ io:put_chars(Fd, "cpl_cont3([L], R) -> cpl_cont3(L, R);\n"),
+ io:put_chars(Fd, "cpl_cont3([L|T], R) -> cpl_cont3(L, [T|R]);\n"),
+ io:put_chars(Fd, "cpl_cont3([], R) -> cpl_1_cont3(R);\n"),
+ io:put_chars(Fd, "cpl_cont3(T, R) -> [T|R].\n"),
+ io:put_chars(Fd, "\n"),
+ io:put_chars(Fd, "%%%\n"),
+ io:put_chars(Fd, "\n"),
+ io:put_chars(Fd, "cpl_1_cont([C|T]) when is_integer(C) -> [C|cpl_1_cont2(T)];\n"),
+ io:put_chars(Fd, "cpl_1_cont([L]) -> cpl_1_cont(L);\n"),
+ io:put_chars(Fd, "cpl_1_cont([L|T]) -> cpl_cont(L, T);\n"),
+ io:put_chars(Fd, "cpl_1_cont(T) -> T.\n"),
+ io:put_chars(Fd, "\n"),
+ io:put_chars(Fd, "cpl_1_cont2([C|T]) when is_integer(C) -> [C|cpl_1_cont3(T)];\n"),
+ io:put_chars(Fd, "cpl_1_cont2([L]) -> cpl_1_cont2(L);\n"),
+ io:put_chars(Fd, "cpl_1_cont2([L|T]) -> cpl_cont2(L, T);\n"),
+ io:put_chars(Fd, "cpl_1_cont2(T) -> T.\n"),
+ io:put_chars(Fd, "\n"),
+ io:put_chars(Fd, "cpl_1_cont3([C|_]=T) when is_integer(C) -> T;\n"),
+ io:put_chars(Fd, "cpl_1_cont3([L]) -> cpl_1_cont3(L);\n"),
+ io:put_chars(Fd, "cpl_1_cont3([L|T]) -> cpl_cont3(L, T);\n"),
+ io:put_chars(Fd, "cpl_1_cont3(T) -> T.\n"),
+ io:put_chars(Fd, "\n"),
+ io:put_chars(Fd, "%%%\n"),
+ io:put_chars(Fd, "\n"),
+ io:put_chars(Fd, "cp_no_bin([C|_]=L) when is_integer(C) -> L;\n"),
+ io:put_chars(Fd, "cp_no_bin([List]) -> cp_no_bin(List);\n"),
+ io:put_chars(Fd, "cp_no_bin([List|R]) -> cp_no_binl(List, R);\n"),
+ io:put_chars(Fd, "cp_no_bin([]) -> [];\n"),
+ io:put_chars(Fd, "cp_no_bin(_) -> binary_found.\n"),
+ io:put_chars(Fd, "\n"),
+ io:put_chars(Fd, "cp_no_binl([C], R) when is_integer(C) -> [C|cpl_1_cont(R)];\n"),
+ io:put_chars(Fd, "cp_no_binl([C|T], R) when is_integer(C) -> [C|cpl_cont(T, R)];\n"),
+ io:put_chars(Fd, "cp_no_binl([List], R) -> cp_no_binl(List, R);\n"),
+ io:put_chars(Fd, "cp_no_binl([List|T], R) -> cp_no_binl(List, [T|R]);\n"),
+ io:put_chars(Fd, "cp_no_binl([], R) -> cp_no_bin(R);\n"),
+ io:put_chars(Fd, "cp_no_binl(_, _) -> binary_found.\n\n"),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -481,11 +537,26 @@ gen_gc(Fd, GBP) ->
"-spec gc(String::unicode:chardata()) ->"
" maybe_improper_list() | {error, unicode:chardata()}.\n"),
io:put_chars(Fd,
- "gc([CP1, CP2|_]=T)\n"
- " when CP1 < 256, CP2 < 256, CP1 =/= $\r -> %% Ascii Fast path\n"
- " T;\n"
+ "gc([]=R) -> R;\n"
+ "gc([CP]=R) when is_integer(CP) -> R;\n"
+ "gc([$\\r=CP|R0]) ->\n"
+ " case cp(R0) of % Don't break CRLF\n"
+ " [$\\n|R1] -> [[$\\r,$\\n]|R1];\n"
+ " T -> [CP|T]\n"
+ " end;\n"
+ "gc([CP1|T1]=T) when CP1 < 256 ->\n"
+ " case T1 of\n"
+ " [CP2|_] when CP2 < 256 -> T; %% Ascii Fast path\n"
+ " _ -> %% Keep the tail binary.\n"
+ " case cp_no_bin(T1) of\n"
+ " [CP2|_]=T3 when CP2 < 256 -> [CP1|T3]; %% Asciii Fast path\n"
+ " binary_found -> gc_1(T);\n"
+ " T4 -> gc_1([CP1|T4])\n"
+ " end\n"
+ " end;\n"
+ "gc(<<>>) -> [];\n"
"gc(<<CP1/utf8, Rest/binary>>) ->\n"
- " if CP1 < 256, CP1 =/= $\r ->\n"
+ " if CP1 < 256, CP1 =/= $\\r ->\n"
" case Rest of\n"
" <<CP2/utf8, _/binary>> when CP2 < 256 -> %% Ascii Fast path\n"
" [CP1|Rest];\n"
@@ -493,13 +564,12 @@ gen_gc(Fd, GBP) ->
" end;\n"
" true -> gc_1([CP1|Rest])\n"
" end;\n"
+ "gc([CP|_]=T) when is_integer(CP) -> gc_1(T);\n"
"gc(Str) ->\n"
- " gc_1(cp(Str)).\n\n"
- "gc_1([$\\r|R0] = R) ->\n"
- " case cp(R0) of % Don't break CRLF\n"
- " [$\\n|R1] -> [[$\\r,$\\n]|R1];\n"
- " _ -> R\n"
- " end;\n"
+ " case cp(Str) of\n"
+ " {error,_}=Error -> Error;\n"
+ " CPs -> gc(CPs)\n"
+ " end.\n"
),
GenExtP = fun(Range) -> io:format(Fd, "gc_1~s gc_ext_pict(R1,[CP]);\n", [gen_clause(Range)]) end,
@@ -507,7 +577,12 @@ gen_gc(Fd, GBP) ->
%% Pick codepoints below 256 (some data knowledge here)
{ExtendedPictographicLow,ExtendedPictographicHigh} =
lists:splitwith(fun({Start,undefined}) -> Start < 256 end,ExtendedPictographic0),
-
+ io:put_chars(Fd,
+ "\ngc_1([$\\r|R0] = R) ->\n"
+ " case cp(R0) of % Don't break CRLF\n"
+ " [$\\n|R1] -> [[$\\r,$\\n]|R1];\n"
+ " _ -> R\n"
+ " end;\n"),
io:put_chars(Fd, "\n%% Handle control\n"),
GenControl = fun(Range) -> io:format(Fd, "gc_1~s R0;\n", [gen_clause(Range)]) end,
CRs0 = merge_ranges(maps:get(cr, GBP) ++ maps:get(lf, GBP) ++ maps:get(control, GBP), false),
@@ -516,7 +591,14 @@ gen_gc(Fd, GBP) ->
%%GenControl(R1),GenControl(R2),GenControl(R3),
io:put_chars(Fd, "\n%% Optimize Latin-1\n"),
[GenExtP(CP) || CP <- merge_ranges(ExtendedPictographicLow)],
- io:format(Fd, "gc_1([CP|R]) when CP < 256 -> gc_extend(R,CP);\n\n", []),
+
+ io:format(Fd,
+ "gc_1([CP|R]=R0) when CP < 256 ->\n"
+ " case R of\n"
+ " [CP2|_] when CP2 < 256 -> R0;\n"
+ " _ -> gc_extend(cp(R), R, CP)\n"
+ " end;\n",
+ []),
io:put_chars(Fd, "\n%% Continue control\n"),
[GenControl(CP) || CP <- Crs],
%% One clause per CP
@@ -540,7 +622,7 @@ gen_gc(Fd, GBP) ->
io:put_chars(Fd, "gc_1([CP|_]=R0) when 44000 < CP, CP < 56000 -> gc_h_lv_lvt(R0, []);\n"),
io:put_chars(Fd, "\n%% Handle Regional\n"),
- GenRegional = fun(Range) -> io:format(Fd, "gc_1~s gc_regional(R1,[CP]);\n", [gen_clause(Range)]) end,
+ GenRegional = fun(Range) -> io:format(Fd, "gc_1~s gc_regional(R1,CP);\n", [gen_clause(Range)]) end,
[GenRegional(CP) || CP <- merge_ranges(maps:get(regional_indicator,GBP))],
%% io:put_chars(Fd, "%% Handle E_Base\n"),
%% GenEBase = fun(Range) -> io:format(Fd, "gc_1~s gc_e_cont(R1,[CP]);\n", [gen_clause(Range)]) end,
@@ -552,9 +634,7 @@ gen_gc(Fd, GBP) ->
io:put_chars(Fd, "%% Handle extended_pictographic\n"),
[GenExtP(CP) || CP <- merge_ranges(ExtendedPictographicHigh)],
io:put_chars(Fd, "\n%% default clauses\n"),
- io:put_chars(Fd, "gc_1([CP|R]) -> gc_extend(R, CP);\n"),
- io:put_chars(Fd, "gc_1([]) -> [];\n"),
- io:put_chars(Fd, "gc_1({error,_}=Error) -> Error.\n\n"),
+ io:put_chars(Fd, "gc_1([CP|R]) -> gc_extend(cp(R), R, CP).\n\n"),
io:put_chars(Fd, "%% Handle Prepend\n"),
io:put_chars(Fd,
@@ -581,31 +661,24 @@ gen_gc(Fd, GBP) ->
"%% To simplify binary handling in libraries the tail should be kept binary\n"
"%% and not a lookahead CP\n"
),
- io:put_chars(Fd, "gc_extend(T, Acc) ->\n"
- " gc_extend(cp(T), T, Acc).\n\n"),
io:put_chars(Fd,
- "gc_extend([CP|T], T0, Acc0) ->\n"
+ "gc_extend([CP|T], T0, CP0) ->\n"
" case is_extend(CP) of\n"
- " false ->\n"
- " case Acc0 of\n"
- " [Acc] -> [Acc|T0];\n"
- " [_|_]=Acc -> [lists:reverse(Acc)|T0];\n"
- " Acc -> [Acc|T0]\n"
- " end;\n"
- " _TrueOrZWJ ->\n"
- " case Acc0 of\n"
- " [_|_] -> gc_extend(T, [CP|Acc0]);\n"
- " Acc -> gc_extend(T, [CP,Acc])\n"
- " end\n"
+ " false -> [CP0|T0]; % losing work done on T\n"
+ " _TrueOrZWJ -> gc_extend2(cp(T), T, [CP,CP0])\n"
" end;\n"
- "gc_extend([], _, Acc0) ->\n"
- " case Acc0 of\n"
- " [_]=Acc -> Acc;\n"
- " [_|_]=Acc -> [lists:reverse(Acc)];\n"
- " Acc -> [Acc]\n"
+ "gc_extend([], _, CP) -> [CP];\n"
+ "gc_extend({error,R}, _, CP) -> [CP|R].\n\n"),
+ io:put_chars(Fd,
+ "gc_extend2([CP|T], T0, Acc) ->\n"
+ " case is_extend(CP) of\n"
+ " false -> [lists:reverse(Acc)|T0]; % losing work done on T\n"
+ " _TrueOrZWJ -> gc_extend2(cp(T), T, [CP|Acc])\n"
" end;\n"
- "gc_extend({error,R}, T, Acc0) ->\n"
- " gc_extend([], T, Acc0) ++ [R].\n\n"
+ "gc_extend2([], _, Acc) ->\n"
+ " [lists:reverse(Acc)];\n"
+ "gc_extend2({error,R}, _, Acc) ->\n"
+ " [lists:reverse(Acc)] ++ [R].\n\n"
),
[ZWJ] = maps:get(zwj, GBP),
GenExtend = fun(R) when R =:= ZWJ -> io:format(Fd, "is_extend~s zwj;\n", [gen_single_clause(ZWJ)]);
@@ -660,10 +733,10 @@ gen_gc(Fd, GBP) ->
%% --------------------
io:put_chars(Fd, "%% Handle Regional\n"),
[{RLess,RLarge}] = merge_ranges(maps:get(regional_indicator,GBP)),
- io:put_chars(Fd,"gc_regional(R0, Acc) ->\n"
+ io:put_chars(Fd,"gc_regional(R0, CP0) ->\n"
" case cp(R0) of\n"),
- io:format(Fd, " [CP|R1] when ~w =< CP,CP =< ~w-> gc_extend(R1,[CP|Acc]);~n",[RLess, RLarge]),
- io:put_chars(Fd," R1 -> gc_extend(R1, R0, Acc)\n"
+ io:format(Fd, " [CP|R1] when ~w =< CP,CP =< ~w-> gc_extend2(cp(R1),R1,[CP,CP0]);~n",[RLess, RLarge]),
+ io:put_chars(Fd," R1 -> gc_extend(R1, R0, CP0)\n"
" end.\n\n"),
%% Special hangul
@@ -685,16 +758,23 @@ gen_gc(Fd, GBP) ->
GenHangulV_2 = fun(Range) -> io:format(Fd, "~8c~s gc_h_T(R1,[CP|Acc]);\n",
[$\s,gen_case_clause(Range)]) end,
[GenHangulV_2(CP) || CP <- merge_ranges(maps:get(t,GBP))],
- io:put_chars(Fd, " R1 -> gc_extend(R1, R0, Acc)\n end.\n\n"),
-
+ io:put_chars(Fd,
+ " R1 ->\n"
+ " case Acc of\n"
+ " [CP] -> gc_extend(R1, R0, CP);\n"
+ " _ -> gc_extend2(R1, R0, Acc)\n"
+ " end\n end.\n\n"),
io:put_chars(Fd, "%% Handle Hangul T\n"),
io:put_chars(Fd, "gc_h_T(R0, Acc) ->\n case cp(R0) of\n"),
GenHangulT_1 = fun(Range) -> io:format(Fd, "~8c~s gc_h_T(R1,[CP|Acc]);\n",
[$\s,gen_case_clause(Range)]) end,
[GenHangulT_1(CP) || CP <- merge_ranges(maps:get(t,GBP))],
- io:put_chars(Fd, " R1 -> gc_extend(R1, R0, Acc)\n end.\n\n"),
-
- io:put_chars(Fd, "gc_h_lv_lvt({error,_}=Error, Acc) -> gc_extend(Error, [], Acc);\n"),
+ io:put_chars(Fd,
+ " R1 ->\n"
+ " case Acc of\n"
+ " [CP] -> gc_extend(R1, R0, CP);\n"
+ " _ -> gc_extend2(R1, R0, Acc)\n"
+ " end\n end.\n\n"),
io:put_chars(Fd, "%% Handle Hangul LV\n"),
GenHangulLV = fun(Range) -> io:format(Fd, "gc_h_lv_lvt~s gc_h_V(R1,[CP|Acc]);\n",
[gen_clause2(Range)]) end,
@@ -703,8 +783,10 @@ gen_gc(Fd, GBP) ->
GenHangulLVT = fun(Range) -> io:format(Fd, "gc_h_lv_lvt~s gc_h_T(R1,[CP|Acc]);\n",
[gen_clause2(Range)]) end,
[GenHangulLVT(CP) || CP <- merge_ranges(maps:get(lvt,GBP))],
- io:put_chars(Fd, "gc_h_lv_lvt([CP|R], []) -> gc_extend(R, CP);\n"), %% From gc_1/1
- io:put_chars(Fd, "gc_h_lv_lvt(R, Acc) -> gc_extend(R, Acc).\n\n"),
+ io:put_chars(Fd, "gc_h_lv_lvt([CP|R], []) -> gc_extend(cp(R), R, CP);\n"), %% From gc_1/1
+ io:put_chars(Fd, "%% Also handles error tuples\n"),
+ io:put_chars(Fd, "gc_h_lv_lvt(R, [CP]) -> gc_extend(R, R, CP);\n"),
+ io:put_chars(Fd, "gc_h_lv_lvt(R, Acc) -> gc_extend2(R, R, Acc).\n\n"),
ok.
gen_compose_pairs(Fd, ExclData, Data) ->
diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk
index d46173497b..cbefd6590a 100644
--- a/lib/stdlib/vsn.mk
+++ b/lib/stdlib/vsn.mk
@@ -1 +1 @@
-STDLIB_VSN = 3.7.1
+STDLIB_VSN = 3.8
diff --git a/lib/syntax_tools/doc/src/notes.xml b/lib/syntax_tools/doc/src/notes.xml
index dc13fe474b..772f5e6e04 100644
--- a/lib/syntax_tools/doc/src/notes.xml
+++ b/lib/syntax_tools/doc/src/notes.xml
@@ -32,6 +32,20 @@
<p>This document describes the changes made to the Syntax_Tools
application.</p>
+<section><title>Syntax_Tools 2.1.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p> Fix pretty-printing of type funs. </p>
+ <p>
+ Own Id: OTP-15519 Aux Id: ERL-815 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Syntax_Tools 2.1.6</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/syntax_tools/vsn.mk b/lib/syntax_tools/vsn.mk
index 8959ebbd04..538c71dc24 100644
--- a/lib/syntax_tools/vsn.mk
+++ b/lib/syntax_tools/vsn.mk
@@ -1 +1 @@
-SYNTAX_TOOLS_VSN = 2.1.6
+SYNTAX_TOOLS_VSN = 2.1.7
diff --git a/lib/tools/doc/src/notes.xml b/lib/tools/doc/src/notes.xml
index a6781dfdb3..28f8346a19 100644
--- a/lib/tools/doc/src/notes.xml
+++ b/lib/tools/doc/src/notes.xml
@@ -31,6 +31,45 @@
</header>
<p>This document describes the changes made to the Tools application.</p>
+<section><title>Tools 3.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Minor fixes for <c>make clean</c>.</p>
+ <p>
+ Own Id: OTP-15657</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ In the HTML file generated by
+ <c>cover:analyse_to_file/1,2</c>, a link is now added to
+ the line number. This makes it easier to share pointers
+ to specific lines.</p>
+ <p>
+ Own Id: OTP-15541</p>
+ </item>
+ <item>
+ <p>
+ Uncovered lines are now marked with a sad face,
+ <c>:-(</c>, in the HTML output from
+ <c>cover:analyse_to_file/1,2</c>. This is to make these
+ lines easier to find by search.</p>
+ <p>
+ Own Id: OTP-15542</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Tools 3.0.2</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/tools/vsn.mk b/lib/tools/vsn.mk
index bb8305e9f1..5700885549 100644
--- a/lib/tools/vsn.mk
+++ b/lib/tools/vsn.mk
@@ -1 +1 @@
-TOOLS_VSN = 3.0.2
+TOOLS_VSN = 3.1
diff --git a/lib/wx/doc/src/notes.xml b/lib/wx/doc/src/notes.xml
index 1061e73138..33d02e22ba 100644
--- a/lib/wx/doc/src/notes.xml
+++ b/lib/wx/doc/src/notes.xml
@@ -32,6 +32,25 @@
<p>This document describes the changes made to the wxErlang
application.</p>
+<section><title>Wx 1.8.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Improved support for wxWidgets 3.1.3 which have changed
+ <c>wxFONTWEIGTH</c>, also added <c>wxGCDC</c> and
+ <c>wxDisplay</c> modules.</p>
+ <p>
+ Fixed a crash on Mojave and check for events more often.</p>
+ <p>
+ Own Id: OTP-15587</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Wx 1.8.6</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/wx/vsn.mk b/lib/wx/vsn.mk
index d241a7a1b4..dac219fa98 100644
--- a/lib/wx/vsn.mk
+++ b/lib/wx/vsn.mk
@@ -1 +1 @@
-WX_VSN = 1.8.6
+WX_VSN = 1.8.7