aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--erts/etc/common/ct_run.c40
-rw-r--r--erts/etc/common/dialyzer.c39
-rw-r--r--erts/etc/common/erlc.c37
-rw-r--r--erts/etc/common/erlexec.c8
-rw-r--r--erts/etc/common/escript.c30
-rw-r--r--erts/etc/common/typer.c38
-rw-r--r--erts/lib_src/win/ethread.c4
-rw-r--r--lib/common_test/doc/src/run_test_chapter.xml44
-rw-r--r--lib/debugger/doc/src/debugger_chapter.xml8
-rw-r--r--lib/debugger/src/dbg_icmd.erl44
-rw-r--r--lib/debugger/src/dbg_wx_win.erl5
-rw-r--r--lib/kernel/src/net_kernel.erl38
-rw-r--r--lib/kernel/test/erl_distribution_SUITE.erl99
-rw-r--r--lib/public_key/src/pubkey_ssh.erl39
-rw-r--r--lib/public_key/src/public_key.erl1
-rw-r--r--lib/ssh/src/ssh.hrl11
-rw-r--r--lib/ssh/src/ssh_auth.erl126
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl236
-rw-r--r--lib/ssh/src/ssh_dbg.erl5
-rw-r--r--lib/ssh/src/ssh_file.erl24
-rw-r--r--lib/ssh/src/ssh_message.erl52
-rw-r--r--lib/ssh/src/ssh_options.erl42
-rw-r--r--lib/ssh/src/ssh_transport.erl461
-rw-r--r--lib/ssh/src/ssh_transport.hrl15
-rw-r--r--lib/ssh/test/ssh_basic_SUITE.erl21
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE.erl1
-rw-r--r--lib/ssh/test/ssh_relay.erl4
-rw-r--r--lib/ssh/test/ssh_test_lib.erl18
-rw-r--r--lib/ssh/test/ssh_to_openssh_SUITE.erl25
-rw-r--r--lib/ssh/test/ssh_trpt_test_lib.erl11
-rw-r--r--lib/ssl/src/dtls_record.erl13
-rw-r--r--lib/ssl/src/dtls_v1.erl7
-rw-r--r--lib/ssl/src/tls_v1.erl11
-rw-r--r--lib/stdlib/doc/src/gen_fsm.xml2
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml190
-rw-r--r--lib/stdlib/doc/src/shell.xml21
-rw-r--r--lib/stdlib/src/gen_statem.erl207
-rw-r--r--lib/stdlib/src/lib.erl223
-rw-r--r--lib/stdlib/src/qlc.erl45
-rw-r--r--lib/stdlib/src/shell.erl9
-rw-r--r--lib/stdlib/test/erl_internal_SUITE.erl11
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl139
-rw-r--r--lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl37
-rw-r--r--lib/stdlib/test/qlc_SUITE.erl85
-rw-r--r--lib/stdlib/test/shell_SUITE.erl93
-rw-r--r--lib/stdlib/test/sofs_SUITE.erl4
-rw-r--r--lib/tools/emacs/erlang.el5
-rw-r--r--lib/wx/c_src/wxe_impl.cpp4
-rw-r--r--system/doc/design_principles/statem.xml115
-rw-r--r--system/doc/efficiency_guide/processes.xml4
50 files changed, 2086 insertions, 665 deletions
diff --git a/erts/etc/common/ct_run.c b/erts/etc/common/ct_run.c
index acdfa8c8b8..898c8ccde0 100644
--- a/erts/etc/common/ct_run.c
+++ b/erts/etc/common/ct_run.c
@@ -82,6 +82,9 @@ static int eargc; /* Number of arguments in eargv. */
static void error(char* format, ...);
static void* emalloc(size_t size);
+#ifdef HAVE_COPYING_PUTENV
+static void efree(void *p);
+#endif
static char* strsave(char* string);
static void push_words(char* src);
static int run_erlang(char* name, char** argv);
@@ -119,6 +122,30 @@ char *strerror(int errnum)
}
#endif /* !HAVE_STRERROR */
+
+static void
+set_env(char *key, char *value)
+{
+#ifdef __WIN32__
+ WCHAR wkey[MAXPATHLEN];
+ WCHAR wvalue[MAXPATHLEN];
+ MultiByteToWideChar(CP_UTF8, 0, key, -1, wkey, MAXPATHLEN);
+ MultiByteToWideChar(CP_UTF8, 0, value, -1, wvalue, MAXPATHLEN);
+ if (!SetEnvironmentVariableW(wkey, wvalue))
+ error("SetEnvironmentVariable(\"%s\", \"%s\") failed!", key, value);
+#else
+ size_t size = strlen(key) + 1 + strlen(value) + 1;
+ char *str = emalloc(size);
+ sprintf(str, "%s=%s", key, value);
+ if (putenv(str) != 0)
+ error("putenv(\"%s\") failed!", str);
+#ifdef HAVE_COPYING_PUTENV
+ efree(str);
+#endif
+#endif
+}
+
+
#ifdef __WIN32__
int wmain(int argc, wchar_t **wcargv)
{
@@ -155,6 +182,11 @@ int main(int argc, char** argv)
emulator = get_default_emulator(argv[0]);
/*
+ * Add scriptname to env
+ */
+ set_env("ESCRIPT_NAME", argv[0]);
+
+ /*
* Allocate the argv vector to be used for arguments to Erlang.
* Arrange for starting to pushing information in the middle of
* the array, to allow easy addition of commands in the beginning.
@@ -458,6 +490,14 @@ erealloc(void *p, size_t size)
}
#endif
+#ifdef HAVE_COPYING_PUTENV
+static void
+efree(void *p)
+{
+ free(p);
+}
+#endif
+
static char*
strsave(char* string)
{
diff --git a/erts/etc/common/dialyzer.c b/erts/etc/common/dialyzer.c
index 6ba3605422..829984ef3f 100644
--- a/erts/etc/common/dialyzer.c
+++ b/erts/etc/common/dialyzer.c
@@ -64,6 +64,9 @@ static int eargc; /* Number of arguments in eargv. */
static void error(char* format, ...);
static void* emalloc(size_t size);
+#ifdef HAVE_COPYING_PUTENV
+static void efree(void *p);
+#endif
static char* strsave(char* string);
static void push_words(char* src);
static int run_erlang(char* name, char** argv);
@@ -147,6 +150,29 @@ free_env_val(char *value)
#endif
}
+static void
+set_env(char *key, char *value)
+{
+#ifdef __WIN32__
+ WCHAR wkey[MAXPATHLEN];
+ WCHAR wvalue[MAXPATHLEN];
+ MultiByteToWideChar(CP_UTF8, 0, key, -1, wkey, MAXPATHLEN);
+ MultiByteToWideChar(CP_UTF8, 0, value, -1, wvalue, MAXPATHLEN);
+ if (!SetEnvironmentVariableW(wkey, wvalue))
+ error("SetEnvironmentVariable(\"%s\", \"%s\") failed!", key, value);
+#else
+ size_t size = strlen(key) + 1 + strlen(value) + 1;
+ char *str = emalloc(size);
+ sprintf(str, "%s=%s", key, value);
+ if (putenv(str) != 0)
+ error("putenv(\"%s\") failed!", str);
+#ifdef HAVE_COPYING_PUTENV
+ efree(str);
+#endif
+#endif
+}
+
+
#ifdef __WIN32__
int wmain(int argc, wchar_t **wcargv)
{
@@ -181,6 +207,11 @@ int main(int argc, char** argv)
error("Value of environment variable DIALYZER_EMULATOR is too large");
/*
+ * Add scriptname to env
+ */
+ set_env("ESCRIPT_NAME", argv[0]);
+
+ /*
* Allocate the argv vector to be used for arguments to Erlang.
* Arrange for starting to pushing information in the middle of
* the array, to allow easy addition of commands in the beginning.
@@ -434,6 +465,14 @@ erealloc(void *p, size_t size)
}
#endif
+#ifdef HAVE_COPYING_PUTENV
+static void
+efree(void *p)
+{
+ free(p);
+}
+#endif
+
static char*
strsave(char* string)
{
diff --git a/erts/etc/common/erlc.c b/erts/etc/common/erlc.c
index b54cb31bef..2abeff33a3 100644
--- a/erts/etc/common/erlc.c
+++ b/erts/etc/common/erlc.c
@@ -72,6 +72,9 @@ static int pause_after_execution = 0;
static char* process_opt(int* pArgc, char*** pArgv, int offset);
static void error(char* format, ...);
static void* emalloc(size_t size);
+#ifdef HAVE_COPYING_PUTENV
+static void efree(void *p);
+#endif
static char* strsave(char* string);
static void push_words(char* src);
static int run_erlang(char* name, char** argv);
@@ -147,6 +150,28 @@ get_env(char *key)
}
static void
+set_env(char *key, char *value)
+{
+#ifdef __WIN32__
+ WCHAR wkey[MAXPATHLEN];
+ WCHAR wvalue[MAXPATHLEN];
+ MultiByteToWideChar(CP_UTF8, 0, key, -1, wkey, MAXPATHLEN);
+ MultiByteToWideChar(CP_UTF8, 0, value, -1, wvalue, MAXPATHLEN);
+ if (!SetEnvironmentVariableW(wkey, wvalue))
+ error("SetEnvironmentVariable(\"%s\", \"%s\") failed!", key, value);
+#else
+ size_t size = strlen(key) + 1 + strlen(value) + 1;
+ char *str = emalloc(size);
+ sprintf(str, "%s=%s", key, value);
+ if (putenv(str) != 0)
+ error("putenv(\"%s\") failed!", str);
+#ifdef HAVE_COPYING_PUTENV
+ efree(str);
+#endif
+#endif
+}
+
+static void
free_env_val(char *value)
{
#ifdef __WIN32__
@@ -188,6 +213,11 @@ int main(int argc, char** argv)
error("Value of environment variable ERLC_EMULATOR is too large");
/*
+ * Add scriptname to env
+ */
+ set_env("ESCRIPT_NAME", argv[0]);
+
+ /*
* Allocate the argv vector to be used for arguments to Erlang.
* Arrange for starting to pushing information in the middle of
* the array, to allow easy adding of emulator options (like -pa)
@@ -499,6 +529,13 @@ erealloc(void *p, size_t size)
}
#endif
+#ifdef HAVE_COPYING_PUTENV
+static void
+efree(void *p)
+{
+ free(p);
+}
+#endif
static char*
strsave(char* string)
{
diff --git a/erts/etc/common/erlexec.c b/erts/etc/common/erlexec.c
index 8b75199a1e..f11ec2320a 100644
--- a/erts/etc/common/erlexec.c
+++ b/erts/etc/common/erlexec.c
@@ -587,8 +587,12 @@ int main(int argc, char **argv)
erts_snprintf(tmpStr, sizeof(tmpStr), "%s" DIRSEP "%s" BINARY_EXT, bindir, emu);
emu = strsave(tmpStr);
- add_Eargs(emu); /* Will be argv[0] -- necessary! */
-
+ s = get_env("ESCRIPT_NAME");
+ if(s) {
+ add_Eargs(s); /* argv[0] = scriptname*/
+ } else {
+ add_Eargs(progname); /* argv[0] = erl or cerl */
+ }
/*
* Add the bindir to the path (unless it is there already).
*/
diff --git a/erts/etc/common/escript.c b/erts/etc/common/escript.c
index 4134a3ff36..c28e45a044 100644
--- a/erts/etc/common/escript.c
+++ b/erts/etc/common/escript.c
@@ -155,6 +155,29 @@ free_env_val(char *value)
efree(value);
#endif
}
+
+static void
+set_env(char *key, char *value)
+{
+#ifdef __WIN32__
+ WCHAR wkey[MAXPATHLEN];
+ WCHAR wvalue[MAXPATHLEN];
+ MultiByteToWideChar(CP_UTF8, 0, key, -1, wkey, MAXPATHLEN);
+ MultiByteToWideChar(CP_UTF8, 0, value, -1, wvalue, MAXPATHLEN);
+ if (!SetEnvironmentVariableW(wkey, wvalue))
+ error("SetEnvironmentVariable(\"%s\", \"%s\") failed!", key, value);
+#else
+ size_t size = strlen(key) + 1 + strlen(value) + 1;
+ char *str = emalloc(size);
+ sprintf(str, "%s=%s", key, value);
+ if (putenv(str) != 0)
+ error("putenv(\"%s\") failed!", str);
+#ifdef HAVE_COPYING_PUTENV
+ efree(str);
+#endif
+#endif
+}
+
/*
* Find absolute path to this program
*/
@@ -548,7 +571,12 @@ main(int argc, char** argv)
while (--eargc_base >= 0) {
UNSHIFT(eargv_base[eargc_base]);
}
-
+
+ /*
+ * Add scriptname to env
+ */
+ set_env("ESCRIPT_NAME", scriptname);
+
/*
* Invoke Erlang with the collected options.
*/
diff --git a/erts/etc/common/typer.c b/erts/etc/common/typer.c
index 77a95ccded..644c90a795 100644
--- a/erts/etc/common/typer.c
+++ b/erts/etc/common/typer.c
@@ -64,6 +64,9 @@ static int eargc; /* Number of arguments in eargv. */
static void error(char* format, ...);
static void* emalloc(size_t size);
+#ifdef HAVE_COPYING_PUTENV
+static void efree(void *p);
+#endif
static char* strsave(char* string);
static void push_words(char* src);
static int run_erlang(char* name, char** argv);
@@ -101,6 +104,29 @@ char *strerror(int errnum)
}
#endif /* !HAVE_STRERROR */
+static void
+set_env(char *key, char *value)
+{
+#ifdef __WIN32__
+ WCHAR wkey[MAXPATHLEN];
+ WCHAR wvalue[MAXPATHLEN];
+ MultiByteToWideChar(CP_UTF8, 0, key, -1, wkey, MAXPATHLEN);
+ MultiByteToWideChar(CP_UTF8, 0, value, -1, wvalue, MAXPATHLEN);
+ if (!SetEnvironmentVariableW(wkey, wvalue))
+ error("SetEnvironmentVariable(\"%s\", \"%s\") failed!", key, value);
+#else
+ size_t size = strlen(key) + 1 + strlen(value) + 1;
+ char *str = emalloc(size);
+ sprintf(str, "%s=%s", key, value);
+ if (putenv(str) != 0)
+ error("putenv(\"%s\") failed!", str);
+#ifdef HAVE_COPYING_PUTENV
+ efree(str);
+#endif
+#endif
+}
+
+
#ifdef __WIN32__
int wmain(int argc, wchar_t **wcargv)
{
@@ -129,6 +155,10 @@ main(int argc, char** argv)
#endif
emulator = get_default_emulator(argv[0]);
+ /*
+ * Add scriptname to env
+ */
+ set_env("ESCRIPT_NAME", argv[0]);
/*
* Allocate the argv vector to be used for arguments to Erlang.
@@ -353,6 +383,14 @@ erealloc(void *p, size_t size)
}
#endif
+#ifdef HAVE_COPYING_PUTENV
+static void
+efree(void *p)
+{
+ free(p);
+}
+#endif
+
static char*
strsave(char* string)
{
diff --git a/erts/lib_src/win/ethread.c b/erts/lib_src/win/ethread.c
index ba77b3dc88..98fcd2d451 100644
--- a/erts/lib_src/win/ethread.c
+++ b/erts/lib_src/win/ethread.c
@@ -203,6 +203,8 @@ ethr_init(ethr_init_data *id)
if (!ethr_not_inited__)
return EINVAL;
+ ethr_not_inited__ = 0;
+
#ifdef _WIN32_WINNT
os_version.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&os_version);
@@ -238,8 +240,6 @@ ethr_init(ethr_init_data *id)
if (erts_get_cpu_configured(ethr_cpu_info__) == 1)
child_wait_spin_count = 0;
- ethr_not_inited__ = 0;
-
return 0;
error:
diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml
index aea4d05867..fa336b7e16 100644
--- a/lib/common_test/doc/src/run_test_chapter.xml
+++ b/lib/common_test/doc/src/run_test_chapter.xml
@@ -1322,8 +1322,8 @@
<title>The Unexpected I/O Log</title>
<p>The test suites overview page includes a link to the Unexpected I/O Log.
In this log, <c>Common Test</c> saves printouts made with
- <seealso marker="ct#log-2"><c>ct:log/2</c></seealso> and
- <seealso marker="ct#pal-2"><c>ct:pal/2</c></seealso>, as well as captured system
+ <seealso marker="ct#log-2"><c>ct:log/1,2,3,4,5</c></seealso> and
+ <seealso marker="ct#pal-2"><c>ct:pal/1,2,3,4,5</c></seealso>, as well as captured system
error- and progress reports, which cannot be associated with particular test cases and
therefore cannot be written to individual test case log files. This occurs,
for example, if a log printout is made from an external process (not a test
@@ -1338,7 +1338,7 @@
<title>The Pre- and Post Test I/O Log</title>
<p>The <c>Common Test</c> Framework Log page includes links to the
Pre- and Post Test I/O Log. In this log, <c>Common Test</c> saves printouts made
- with <c>ct:log/2</c> and <c>ct:pal/2</c>, as well as captured system error-
+ with <c>ct:log/1,2,3,4,5</c> and <c>ct:pal/1,2,3,4,5</c>, as well as captured system error-
and progress reports, which take place before, and after, the test run.
Examples of this are printouts from a CT hook init- or terminate function, or
progress reports generated when an OTP application is started from a CT hook
@@ -1349,8 +1349,8 @@
applications, see section
<seealso marker="ct_hooks_chapter#synchronizing">Synchronizing</seealso>
in section Common Test Hooks.</p>
- <note><p>Logging to file with <c>ct:log/2</c> or <c>ct:pal/2</c>
- only works when <c>Common Test</c> is running. Printouts with <c>ct:pal/2</c>
+ <note><p>Logging to file with <c>ct:log/1,2,3,4,5</c> or <c>ct:pal/1,2,3,4,5</c>
+ only works when <c>Common Test</c> is running. Printouts with <c>ct:pal/1,2,3,4,5</c>
are however always displayed on screen.</p></note>
</section>
@@ -1383,24 +1383,38 @@
<p><c>Common Test</c> includes an <em>optional</em> feature to allow
user HTML style sheets for customizing printouts. The
functions in <c>ct</c> that print to a test case HTML log
- file (<c>log/3</c> and <c>pal/3</c>) accept <c>Category</c>
+ file (<c>log/3,4,5</c> and <c>pal/3,4,5</c>) accept <c>Category</c>
as first argument. With this argument a category can be specified
- that can be mapped to a selector in a CSS
- definition. This is useful, especially for coloring text
+ that can be mapped to a named <c>div</c> selector in a CSS rule-set.
+ This is useful, especially for coloring text
differently depending on the type of (or reason for) the
- printout. Say you want one color for test system
+ printout. Say you want one particular background color for test system
configuration information, a different one for test system
state information, and finally one for errors detected by the
test case functions. The corresponding style sheet can
look as follows:</p>
<pre>
- div.sys_config { background:blue; color:white }
- div.sys_state { background:yellow; color:black }
- div.error { background:red; color:white }</pre>
+ div.sys_config { background:blue }
+ div.sys_state { background:yellow }
+ div.error { background:red }</pre>
+
+ <p>Common Test prints the text from <c>ct:log/3,4,5</c> or
+ <c>ct:pal/3,4,5</c> inside a <c>pre</c> element
+ nested under the named <c>div</c> element. Since the <c>pre</c> selector
+ has a predefined CSS rule (in file <c>ct_default.css</c>) for the attributes
+ <c>color</c>, <c>font-family</c> and <c>font-size</c>, if a user wants to
+ change any of the predefined attribute settings, a new rule for <c>pre</c>
+ must be added to the user stylesheet. Example:</p>
+
+ <pre>
+div.error pre { color:white }</pre>
+
+ <p>Here, white text is used instead of the default black for <c>div.error</c>
+ printouts (and no other attribute settings for <c>pre</c> are affected).</p>
<p>To install the CSS file (<c>Common Test</c> inlines the definition in the
- HTML code), the name can be provided when executing <c>ct_run</c>.</p>
+ HTML code), the file name can be provided when executing <c>ct_run</c>.</p>
<p><em>Example:</em></p>
<pre>
@@ -1448,8 +1462,8 @@
suite.</p>
<p>Argument <c>Category</c> in the previous example can have the
- value (atom) <c>sys_config</c> (white on blue), <c>sys_state</c>
- (black on yellow), or <c>error</c> (white on red).</p>
+ value (atom) <c>sys_config</c> (blue background), <c>sys_state</c>
+ (yellow background), or <c>error</c> (white text on red background).</p>
</section>
<section>
diff --git a/lib/debugger/doc/src/debugger_chapter.xml b/lib/debugger/doc/src/debugger_chapter.xml
index 45dfdb3776..3c37d4b924 100644
--- a/lib/debugger/doc/src/debugger_chapter.xml
+++ b/lib/debugger/doc/src/debugger_chapter.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>1997</year><year>2016</year>
+ <year>1997</year><year>2017</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -652,8 +652,10 @@ c_break(Bindings) ->
<item><p>The Bindings area, displaying all variables bindings. If you
click a variable name, the value is displayed in the Evaluator area.
Double-click a variable name to open a window where
- the variable value can be edited. Notice however that pid,
- reference, binary, or port values cannot be edited.</p>
+ the variable value can be edited. Notice however that pid, port,
+ reference, or fun
+ values cannot be edited unless they can be represented in the
+ running system.</p>
</item>
<item><p>The Trace area, which displays a trace output for the
diff --git a/lib/debugger/src/dbg_icmd.erl b/lib/debugger/src/dbg_icmd.erl
index 57a3719a50..4cd3dce670 100644
--- a/lib/debugger/src/dbg_icmd.erl
+++ b/lib/debugger/src/dbg_icmd.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1998-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -382,14 +382,19 @@ eval_restricted({From,_Mod,Cmd,SP}, Bs) ->
case catch parse_cmd(Cmd, 1) of
{'EXIT', _Reason} ->
From ! {self(), {eval_rsp, 'Parse error'}};
- [{var,_,Var}] ->
+ {[{var,_,Var}], XBs} ->
Bs2 = bindings(Bs, SP),
Res = case get_binding(Var, Bs2) of
{value, Value} -> Value;
- unbound -> unbound
+ unbound ->
+ case get_binding(Var, XBs) of
+ {value, _} ->
+ 'Only possible to inspect variables';
+ unbound -> unbound
+ end
end,
From ! {self(), {eval_rsp, Res}};
- _Forms ->
+ {_Forms, _XBs} ->
Rsp = 'Only possible to inspect variables',
From ! {self(), {eval_rsp, Rsp}}
end.
@@ -404,22 +409,22 @@ eval_nonrestricted({From, _Mod, Cmd, _SP}, Bs,
{'EXIT', _Reason} ->
From ! {self(), {eval_rsp, 'Parse error'}},
Bs;
- Forms ->
+ {Forms, XBs} ->
mark_running(Line, Le),
+ Bs1 = merge_bindings(Bs, XBs),
{Res, Bs2} =
lists:foldl(fun(Expr, {_Res, Bs0}) ->
eval_nonrestricted_1(Expr,Bs0,Ieval)
end,
- {null, Bs},
+ {null, Bs1},
Forms),
mark_break(M, Line, Le),
From ! {self(), {eval_rsp, Res}},
- Bs2
+ remove_binding_structs(Bs2, XBs)
end.
eval_nonrestricted_1({match,_,{var,_,Var},Expr}, Bs, Ieval) ->
- {value,Res,Bs2} =
- dbg_ieval:eval_expr(Expr, Bs, Ieval#ieval{top=false}),
+ {Res,Bs2} = eval_expr(Expr, Bs, Ieval),
Bs3 = case lists:keyfind(Var, 1, Bs) of
{Var,_Value} ->
lists:keyreplace(Var, 1, Bs2, {Var,Res});
@@ -433,10 +438,21 @@ eval_nonrestricted_1({var,_,Var}, Bs, _Ieval) ->
end,
{Res,Bs};
eval_nonrestricted_1(Expr, Bs, Ieval) ->
- {value,Res,Bs2} =
- dbg_ieval:eval_expr(Expr, Bs, Ieval#ieval{top=false}),
+ eval_expr(Expr, Bs, Ieval).
+
+eval_expr(Expr, Bs, Ieval) ->
+ {value,Res,Bs2} =
+ dbg_ieval:eval_expr(Expr, Bs, Ieval#ieval{top=false}),
{Res,Bs2}.
+%% XBs have unique keys.
+merge_bindings(Bs1, XBs) ->
+ Bs1 ++ erl_eval:bindings(XBs).
+
+remove_binding_structs(Bs1, XBs) ->
+ lists:foldl(fun({N, _V}, Bs) -> lists:keydelete(N, 1, Bs)
+ end, Bs1, erl_eval:bindings(XBs)).
+
mark_running(LineNo, Le) ->
put(next_break, running),
put(user_eval, [{LineNo, Le} | get(user_eval)]),
@@ -450,9 +466,9 @@ mark_break(Cm, LineNo, Le) ->
dbg_iserver:cast(get(int), {set_status,self(),break,{Cm,LineNo}}).
parse_cmd(Cmd, LineNo) ->
- {ok,Tokens,_} = erl_scan:string(Cmd, LineNo),
- {ok,Forms} = erl_parse:parse_exprs(Tokens),
- Forms.
+ {ok,Tokens,_} = erl_scan:string(Cmd, LineNo, [text]),
+ {ok,Forms,Bs} = lib:extended_parse_exprs(Tokens),
+ {Forms, Bs}.
%%====================================================================
%% Library functions for attached process handling
diff --git a/lib/debugger/src/dbg_wx_win.erl b/lib/debugger/src/dbg_wx_win.erl
index 2c9d83ea74..f1298154ab 100644
--- a/lib/debugger/src/dbg_wx_win.erl
+++ b/lib/debugger/src/dbg_wx_win.erl
@@ -273,10 +273,9 @@ entry(Parent, Title, Prompt, {Type, Value}) ->
verify(Type, Str) ->
- case erl_scan:string(Str) of
+ case erl_scan:string(Str, 1, [text]) of
{ok, Tokens, _EndLine} when Type==term ->
-
- case erl_parse:parse_term(Tokens++[{dot, erl_anno:new(1)}]) of
+ case lib:extended_parse_term(Tokens++[{dot, erl_anno:new(1)}]) of
{ok, Value} -> {edit, Value};
_Error ->
ignore
diff --git a/lib/kernel/src/net_kernel.erl b/lib/kernel/src/net_kernel.erl
index 0a9f9316b0..9921a0adfd 100644
--- a/lib/kernel/src/net_kernel.erl
+++ b/lib/kernel/src/net_kernel.erl
@@ -1262,11 +1262,22 @@ create_name(Name, LongOrShortNames, Try) ->
{Head,Host1} = create_hostpart(Name, LongOrShortNames),
case Host1 of
{ok,HostPart} ->
- {ok,list_to_atom(Head ++ HostPart)};
+ case valid_name_head(Head) of
+ true ->
+ {ok,list_to_atom(Head ++ HostPart)};
+ false ->
+ error_logger:info_msg("Invalid node name!\n"
+ "Please check your configuration\n"),
+ {error, badarg}
+ end;
{error,long} when Try =:= 1 ->
%% It could be we haven't read domain name from resolv file yet
inet_config:do_load_resolv(os:type(), longnames),
create_name(Name, LongOrShortNames, 0);
+ {error, hostname_not_allowed} ->
+ error_logger:info_msg("Invalid node name!\n"
+ "Please check your configuration\n"),
+ {error, badarg};
{error,Type} ->
error_logger:info_msg(
lists:concat(["Can\'t set ",
@@ -1279,12 +1290,13 @@ create_name(Name, LongOrShortNames, Try) ->
create_hostpart(Name, LongOrShortNames) ->
{Head,Host} = split_node(Name),
Host1 = case {Host,LongOrShortNames} of
- {[$@,_|_],longnames} ->
- {ok,Host};
+ {[$@,_|_] = Host,longnames} ->
+ validate_hostname(Host);
{[$@,_|_],shortnames} ->
case lists:member($.,Host) of
true -> {error,short};
- _ -> {ok,Host}
+ _ ->
+ validate_hostname(Host)
end;
{_,shortnames} ->
case inet_db:gethostname() of
@@ -1304,6 +1316,24 @@ create_hostpart(Name, LongOrShortNames) ->
end,
{Head,Host1}.
+validate_hostname([$@|HostPart] = Host) ->
+ {ok, MP} = re:compile("^[!-ÿ]*$", [unicode]),
+ case re:run(HostPart, MP) of
+ {match, _} ->
+ {ok, Host};
+ nomatch ->
+ {error, hostname_not_allowed}
+ end.
+
+valid_name_head(Head) ->
+ {ok, MP} = re:compile("^[0-9A-Za-z_\\-]*$", [unicode]),
+ case re:run(Head, MP) of
+ {match, _} ->
+ true;
+ nomatch ->
+ false
+ end.
+
split_node(Name) ->
lists:splitwith(fun(C) -> C =/= $@ end, atom_to_list(Name)).
diff --git a/lib/kernel/test/erl_distribution_SUITE.erl b/lib/kernel/test/erl_distribution_SUITE.erl
index 09c80a0956..ecfa3d6cdb 100644
--- a/lib/kernel/test/erl_distribution_SUITE.erl
+++ b/lib/kernel/test/erl_distribution_SUITE.erl
@@ -24,7 +24,9 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2]).
--export([tick/1, tick_change/1, illegal_nodenames/1, hidden_node/1,
+-export([tick/1, tick_change/1,
+ nodenames/1, hostnames/1,
+ illegal_nodenames/1, hidden_node/1,
setopts/1,
table_waste/1, net_setuptime/1,
inet_dist_options_options/1,
@@ -53,7 +55,6 @@
-export([pinger/1]).
-
-define(DUMMY_NODE,dummy@test01).
%%-----------------------------------------------------------------
@@ -68,8 +69,8 @@ suite() ->
{timetrap,{minutes,4}}].
all() ->
- [tick, tick_change, illegal_nodenames, hidden_node,
- setopts,
+ [tick, tick_change, nodenames, hostnames, illegal_nodenames,
+ hidden_node, setopts,
table_waste, net_setuptime, inet_dist_options_options,
{group, monitor_nodes}].
@@ -179,7 +180,97 @@ table_waste(Config) when is_list(Config) ->
stop_node(N),
ok.
+%% Test that starting nodes with different legal name part works, and that illegal
+%% ones are filtered
+nodenames(Config) when is_list(Config) ->
+ legal("a1@b"),
+ legal("a-1@b"),
+ legal("a_1@b"),
+
+ illegal("cdé@a"),
+ illegal("te欢st@a").
+
+%% Test that starting nodes with different legal host part works, and that illegal
+%% ones are filtered
+hostnames(Config) when is_list(Config) ->
+ Host = gethostname(),
+ legal([$a,$@|atom_to_list(Host)]),
+ legal("1@b1"),
+ legal("b@b1-c"),
+ legal("c@b1_c"),
+ legal("d@b1#c"),
+ legal("f@::1"),
+ legal("g@1:bc3:4e3f:f20:0:1"),
+
+ case file:native_name_encoding() of
+ latin1 -> ignore;
+ _ -> legal("e@b1é")
+ end,
+ long_hostnames(net_kernel:longnames()),
+
+ illegal("h@testالع"),
+ illegal("i@языtest"),
+ illegal("j@te欢st").
+
+long_hostnames(true) ->
+ legal("[email protected]"),
+ legal("[email protected]"),
+ legal("[email protected]_c.d"),
+ legal("[email protected]"),
+ legal("[email protected]");
+long_hostnames(false) ->
+ illegal("[email protected]").
+
+legal(Name) ->
+ case test_node(Name) of
+ started ->
+ ok;
+ not_started ->
+ ct:fail("no ~p node started", [Name])
+ end.
+
+illegal(Name) ->
+ case test_node(Name) of
+ not_started ->
+ ok;
+ started ->
+ ct:fail("~p node started with illegal name", [Name])
+ end.
+test_node(Name) ->
+ ProgName = atom_to_list(lib:progname()),
+ Command = ProgName ++ " -noinput " ++ long_or_short() ++ Name ++
+ " -eval \"net_adm:ping('" ++ atom_to_list(node()) ++ "')\"",
+ net_kernel:monitor_nodes(true),
+ BinCommand = unicode:characters_to_binary(Command, utf8),
+ open_port({spawn, BinCommand}, [stream]),
+ Node = list_to_atom(Name),
+ receive
+ {nodeup, Node} ->
+ net_kernel:monitor_nodes(false),
+ slave:stop(Node),
+ started
+ after 5000 ->
+ net_kernel:monitor_nodes(false),
+ not_started
+ end.
+
+long_or_short() ->
+ case net_kernel:longnames() of
+ true -> " -name ";
+ false -> " -sname "
+ end.
+
+% get the localhost's name, depending on the using name policy
+gethostname() ->
+ Hostname = case net_kernel:longnames() of
+ true->
+ net_adm:localhost();
+ _->
+ {ok, Name}=inet:gethostname(),
+ Name
+ end,
+ list_to_atom(Hostname).
%% Test that pinging an illegal nodename does not kill the node.
illegal_nodenames(Config) when is_list(Config) ->
diff --git a/lib/public_key/src/pubkey_ssh.erl b/lib/public_key/src/pubkey_ssh.erl
index 90726b1eb3..816d7b3336 100644
--- a/lib/public_key/src/pubkey_ssh.erl
+++ b/lib/public_key/src/pubkey_ssh.erl
@@ -44,11 +44,6 @@
%%====================================================================
%%--------------------------------------------------------------------
--spec decode(binary(), public_key | public_key:ssh_file()) ->
- [{public_key:public_key(), Attributes::list()}]
- ; (binary(), ssh2_pubkey) -> public_key:public_key()
- .
-%%
%% Description: Decodes a ssh file-binary.
%%--------------------------------------------------------------------
decode(Bin, public_key)->
@@ -66,11 +61,6 @@ decode(Bin, Type) ->
openssh_decode(Bin, Type).
%%--------------------------------------------------------------------
--spec encode([{public_key:public_key(), Attributes::list()}], public_key:ssh_file()) ->
- binary()
- ; (public_key:public_key(), ssh2_pubkey) -> binary()
- .
-%%
%% Description: Encodes a list of ssh file entries.
%%--------------------------------------------------------------------
encode(Bin, ssh2_pubkey) ->
@@ -81,10 +71,6 @@ encode(Entries, Type) ->
end, Entries)).
%%--------------------------------------------------------------------
--spec dh_gex_group(integer(), integer(), integer(),
- undefined | [{integer(),[{integer(),integer()}]}]) ->
- {ok,{integer(),{integer(),integer()}}} | {error,any()} .
-%%
%% Description: Returns Generator and Modulus given MinSize, WantedSize
%% and MaxSize
%%--------------------------------------------------------------------
@@ -421,14 +407,21 @@ comma_list_encode([Option | Rest], []) ->
comma_list_encode([Option | Rest], Acc) ->
comma_list_encode(Rest, Acc ++ "," ++ Option).
+
+%% An experimental fix adding the signature algorithm name as the last element in a tuple...
+
ssh2_pubkey_encode(#'RSAPublicKey'{modulus = N, publicExponent = E}) ->
- TypeStr = <<"ssh-rsa">>,
- StrLen = size(TypeStr),
+ ssh2_pubkey_encode({#'RSAPublicKey'{modulus = N, publicExponent = E}, 'ssh-rsa'});
+ssh2_pubkey_encode({#'RSAPublicKey'{modulus = N, publicExponent = E}, SignAlg}) ->
+ SignAlgName = list_to_binary(atom_to_list(SignAlg)),
+ StrLen = size(SignAlgName),
EBin = mpint(E),
NBin = mpint(N),
- <<?UINT32(StrLen), TypeStr:StrLen/binary,
+ <<?UINT32(StrLen), SignAlgName:StrLen/binary,
EBin/binary,
NBin/binary>>;
+ssh2_pubkey_encode({{_,#'Dss-Parms'{}}=Key, _}) ->
+ ssh2_pubkey_encode(Key);
ssh2_pubkey_encode({Y, #'Dss-Parms'{p = P, q = Q, g = G}}) ->
TypeStr = <<"ssh-dss">>,
StrLen = size(TypeStr),
@@ -441,6 +434,8 @@ ssh2_pubkey_encode({Y, #'Dss-Parms'{p = P, q = Q, g = G}}) ->
QBin/binary,
GBin/binary,
YBin/binary>>;
+ssh2_pubkey_encode({{#'ECPoint'{},_}=Key, _}) ->
+ ssh2_pubkey_encode(Key);
ssh2_pubkey_encode(Key={#'ECPoint'{point = Q}, {namedCurve,OID}}) ->
TypeStr = key_type(Key),
StrLen = size(TypeStr),
@@ -453,10 +448,16 @@ ssh2_pubkey_encode(Key={#'ECPoint'{point = Q}, {namedCurve,OID}}) ->
ssh2_pubkey_decode(Bin = <<?UINT32(Len), Type:Len/binary, _/binary>>) ->
ssh2_pubkey_decode(Type, Bin).
-ssh2_pubkey_decode(<<"ssh-rsa">>,
+%% An experimental fix with the Signature Algorithm Name
+ssh2_pubkey_decode(SignAlgName,
<<?UINT32(Len), _:Len/binary,
?UINT32(SizeE), E:SizeE/binary,
- ?UINT32(SizeN), N:SizeN/binary>>) ->
+ ?UINT32(SizeN), N:SizeN/binary>>)
+ when SignAlgName == <<"ssh-rsa">> ;
+ SignAlgName == <<"rsa-sha2-256">> ;
+ SignAlgName == <<"rsa-sha2-384">> ;
+ SignAlgName == <<"rsa-sha2-512">>
+ ->
#'RSAPublicKey'{modulus = erlint(SizeN, N),
publicExponent = erlint(SizeE, E)};
diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl
index 7b5819fa84..0894e1860b 100644
--- a/lib/public_key/src/public_key.erl
+++ b/lib/public_key/src/public_key.erl
@@ -900,6 +900,7 @@ ssh_decode(SshBin, Type) when is_binary(SshBin),
%%--------------------------------------------------------------------
-spec ssh_encode([{public_key(), Attributes::list()}], ssh_file()) -> binary()
; (public_key(), ssh2_pubkey) -> binary()
+ ; ({public_key(),atom()}, ssh2_pubkey) -> binary()
.
%%
%% Description: Encodes a list of ssh file entries (public keys and
diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl
index 315310f700..cf2a359e6c 100644
--- a/lib/ssh/src/ssh.hrl
+++ b/lib/ssh/src/ssh.hrl
@@ -38,7 +38,6 @@
-define(MAX_RND_PADDING_LEN, 15).
-define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password").
--define(SUPPORTED_USER_KEYS, ['ssh-rsa','ssh-dss','ecdsa-sha2-nistp256','ecdsa-sha2-nistp384','ecdsa-sha2-nistp521']).
-define(FALSE, 0).
-define(TRUE, 1).
@@ -134,7 +133,7 @@
role :: client | role(),
peer :: undefined |
{inet:hostname(),
- {inet:ip_adress(),inet:port_number()}}, %% string version of peer address
+ {inet:ip_address(),inet:port_number()}}, %% string version of peer address
c_vsn, %% client version {Major,Minor}
s_vsn, %% server version {Major,Minor}
@@ -145,6 +144,9 @@
c_keyinit, %% binary payload of kexinit packet
s_keyinit, %% binary payload of kexinit packet
+ send_ext_info, %% May send ext-info to peer
+ recv_ext_info, %% Expect ext-info from peer
+
algorithms, %% #alg{}
kex, %% key exchange algorithm
@@ -198,6 +200,7 @@
userauth_quiet_mode, % boolean()
userauth_methods, % list( string() ) eg ["keyboard-interactive", "password"]
userauth_supported_methods, % string() eg "keyboard-interactive,password"
+ userauth_pubkeys,
kb_tries_left = 0, % integer(), num tries left for "keyboard-interactive"
userauth_preference,
available_host_keys,
@@ -216,7 +219,9 @@
compress,
decompress,
c_lng,
- s_lng
+ s_lng,
+ send_ext_info,
+ recv_ext_info
}).
-record(ssh_key,
diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl
index 88c8144063..9eb11a53dc 100644
--- a/lib/ssh/src/ssh_auth.erl
+++ b/lib/ssh/src/ssh_auth.erl
@@ -136,34 +136,40 @@ keyboard_interactive_msg([#ssh{user = User,
Ssh)
end.
-publickey_msg([Alg, #ssh{user = User,
+publickey_msg([SigAlg, #ssh{user = User,
session_id = SessionId,
service = Service,
opts = Opts} = Ssh]) ->
- Hash = ssh_transport:sha(Alg),
+ Hash = ssh_transport:sha(SigAlg),
+ KeyAlg = key_alg(SigAlg),
{KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts),
UserOpts = ?GET_OPT(user_options, Opts),
- case KeyCb:user_key(Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of
+ case KeyCb:user_key(KeyAlg, [{key_cb_private,KeyCbOpts}|UserOpts]) of
{ok, PrivKey} ->
- StrAlgo = atom_to_list(Alg),
- case encode_public_key(StrAlgo, ssh_transport:extract_public_key(PrivKey)) of
- not_ok ->
- {not_ok, Ssh};
+ SigAlgStr = atom_to_list(SigAlg),
+ try
+ Key = ssh_transport:extract_public_key(PrivKey),
+ public_key:ssh_encode(Key, ssh2_pubkey)
+ of
PubKeyBlob ->
- SigData = build_sig_data(SessionId,
- User, Service, PubKeyBlob, StrAlgo),
+ SigData = build_sig_data(SessionId, User, Service,
+ PubKeyBlob, SigAlgStr),
Sig = ssh_transport:sign(SigData, Hash, PrivKey),
- SigBlob = list_to_binary([?string(StrAlgo), ?binary(Sig)]),
+ SigBlob = list_to_binary([?string(SigAlgStr),
+ ?binary(Sig)]),
ssh_transport:ssh_packet(
#ssh_msg_userauth_request{user = User,
service = Service,
method = "publickey",
data = [?TRUE,
- ?string(StrAlgo),
+ ?string(SigAlgStr),
?binary(PubKeyBlob),
?binary(SigBlob)]},
Ssh)
- end;
+ catch
+ _:_ ->
+ {not_ok, Ssh}
+ end;
_Error ->
{not_ok, Ssh}
end.
@@ -175,6 +181,7 @@ service_request_msg(Ssh) ->
%%%----------------------------------------------------------------
init_userauth_request_msg(#ssh{opts = Opts} = Ssh) ->
+ %% Client side
case ?GET_OPT(user, Opts) of
undefined ->
ErrStr = "Could not determine the users name",
@@ -183,25 +190,16 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) ->
description = ErrStr});
User ->
- Msg = #ssh_msg_userauth_request{user = User,
- service = "ssh-connection",
- method = "none",
- data = <<>>},
- Algs0 = ?GET_OPT(pref_public_key_algs, Opts),
- %% The following line is not strictly correct. The call returns the
- %% supported HOST key types while we are interested in USER keys. However,
- %% they "happens" to be the same (for now). This could change....
- %% There is no danger as long as the set of user keys is a subset of the set
- %% of host keys.
- CryptoSupported = ssh_transport:supported_algorithms(public_key),
- Algs = [A || A <- Algs0,
- lists:member(A, CryptoSupported)],
-
- Prefs = method_preference(Algs),
- ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User,
- userauth_preference = Prefs,
- userauth_methods = none,
- service = "ssh-connection"})
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
+ method = "none",
+ data = <<>>},
+ Ssh#ssh{user = User,
+ userauth_preference = method_preference(Ssh#ssh.userauth_pubkeys),
+ userauth_methods = none,
+ service = "ssh-connection"}
+ )
end.
%%%----------------------------------------------------------------
@@ -272,8 +270,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
#ssh{opts = Opts,
userauth_supported_methods = Methods} = Ssh) ->
- case pre_verify_sig(User, binary_to_list(BAlg),
- KeyBlob, Opts) of
+ case pre_verify_sig(User, KeyBlob, Opts) of
true ->
{not_authorized, {User, undefined},
ssh_transport:ssh_packet(
@@ -299,8 +296,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
userauth_supported_methods = Methods} = Ssh) ->
case verify_sig(SessionId, User, "ssh-connection",
- binary_to_list(BAlg),
- KeyBlob, SigWLen, Opts) of
+ BAlg, KeyBlob, SigWLen, Opts) of
true ->
{authorized, User,
ssh_transport:ssh_packet(
@@ -453,14 +449,14 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{},
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-method_preference(Algs) ->
- lists:foldr(fun(A, Acc) ->
- [{"publickey", ?MODULE, publickey_msg, [A]} | Acc]
- end,
- [{"password", ?MODULE, password_msg, []},
- {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}
- ],
- Algs).
+method_preference(SigKeyAlgs) ->
+ %% PubKeyAlgs: List of user (client) public key algorithms to try to use.
+ %% All of the acceptable algorithms is the default values.
+ PubKeyDefs = [{"publickey", ?MODULE, publickey_msg, [A]} || A <- SigKeyAlgs],
+ NonPKmethods = [{"password", ?MODULE, password_msg, []},
+ {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}
+ ],
+ PubKeyDefs ++ NonPKmethods.
check_password(User, Password, Opts, Ssh) ->
case ?GET_OPT(pwdfun, Opts) of
@@ -499,9 +495,9 @@ get_password_option(Opts, User) ->
false -> ?GET_OPT(password, Opts)
end.
-pre_verify_sig(User, Alg, KeyBlob, Opts) ->
+pre_verify_sig(User, KeyBlob, Opts) ->
try
- {ok, Key} = decode_public_key_v2(KeyBlob, Alg),
+ Key = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception
{KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts),
UserOpts = ?GET_OPT(user_options, Opts),
KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts])
@@ -510,23 +506,18 @@ pre_verify_sig(User, Alg, KeyBlob, Opts) ->
false
end.
-verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) ->
+verify_sig(SessionId, User, Service, AlgBin, KeyBlob, SigWLen, Opts) ->
try
- {ok, Key} = decode_public_key_v2(KeyBlob, Alg),
-
+ Alg = binary_to_list(AlgBin),
{KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts),
UserOpts = ?GET_OPT(user_options, Opts),
- case KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts]) of
- true ->
- PlainText = build_sig_data(SessionId, User,
- Service, KeyBlob, Alg),
- <<?UINT32(AlgSigLen), AlgSig:AlgSigLen/binary>> = SigWLen,
- <<?UINT32(AlgLen), _Alg:AlgLen/binary,
- ?UINT32(SigLen), Sig:SigLen/binary>> = AlgSig,
- ssh_transport:verify(PlainText, ssh_transport:sha(list_to_atom(Alg)), Sig, Key);
- false ->
- false
- end
+ Key = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception
+ true = KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts]),
+ PlainText = build_sig_data(SessionId, User, Service, KeyBlob, Alg),
+ <<?UINT32(AlgSigLen), AlgSig:AlgSigLen/binary>> = SigWLen,
+ <<?UINT32(AlgLen), _Alg:AlgLen/binary,
+ ?UINT32(SigLen), Sig:SigLen/binary>> = AlgSig,
+ ssh_transport:verify(PlainText, ssh_transport:sha(Alg), Sig, Key)
catch
_:_ ->
false
@@ -598,18 +589,7 @@ keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos, NumPrompts) ->
language = "en"}})
end.
-decode_public_key_v2(Bin, _Type) ->
- try
- public_key:ssh_decode(Bin, ssh2_pubkey)
- of
- Key -> {ok, Key}
- catch
- _:_ -> {error, bad_format}
- end.
-encode_public_key(_Alg, Key) ->
- try
- public_key:ssh_encode(Key, ssh2_pubkey)
- catch
- _:_ -> not_ok
- end.
+key_alg('rsa-sha2-256') -> 'ssh-rsa';
+key_alg('rsa-sha2-512') -> 'ssh-rsa';
+key_alg(Alg) -> Alg.
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
index 84bb7dc23f..74e14a233f 100644
--- a/lib/ssh/src/ssh_connection_handler.erl
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -365,7 +365,7 @@ init_connection_handler(Role, Socket, Opts) ->
{ok, StartState, D} ->
process_flag(trap_exit, true),
gen_statem:enter_loop(?MODULE,
- [], %%[{debug,[trace,log,statistics,debug]} || Role==server],
+ [], %%[{debug,[trace,log,statistics,debug]} ], %% []
StartState,
D);
@@ -453,7 +453,9 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) ->
PeerName = case ?GET_INTERNAL_OPT(host, Opts) of
PeerIP when is_tuple(PeerIP) ->
inet_parse:ntoa(PeerIP);
- PeerName0 ->
+ PeerName0 when is_atom(PeerName0) ->
+ atom_to_list(PeerName0);
+ PeerName0 when is_list(PeerName0) ->
PeerName0
end,
S0#ssh{c_vsn = Vsn,
@@ -462,6 +464,7 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) ->
true -> ssh_io;
false -> ssh_no_io
end,
+ userauth_pubkeys = ?GET_OPT(pref_public_key_algs, Opts),
userauth_quiet_mode = ?GET_OPT(quiet_mode, Opts),
peer = {PeerName, PeerAddr}
};
@@ -487,25 +490,42 @@ init_ssh_record(Role, _Socket, PeerAddr, Opts) ->
-type renegotiate_flag() :: init | renegotiate.
-type state_name() ::
- {hello, role()}
- | {kexinit, role(), renegotiate_flag()}
- | {key_exchange, role(), renegotiate_flag()}
- | {key_exchange_dh_gex_init, server, renegotiate_flag()}
+ {hello, role() }
+ | {kexinit, role(), renegotiate_flag()}
+ | {key_exchange, role(), renegotiate_flag()}
+ | {key_exchange_dh_gex_init, server, renegotiate_flag()}
| {key_exchange_dh_gex_reply, client, renegotiate_flag()}
- | {new_keys, role()}
- | {service_request, role()}
- | {userauth, role()}
- | {userauth_keyboard_interactive, role()}
- | {connected, role()}
+ | {new_keys, role(), renegotiate_flag()}
+ | {ext_info, role(), renegotiate_flag()}
+ | {service_request, role() }
+ | {userauth, role() }
+ | {userauth_keyboard_interactive, role() }
+ | {userauth_keyboard_interactive_extra, server }
+ | {userauth_keyboard_interactive_info_response, client }
+ | {connected, role() }
.
--type handle_event_result() :: gen_statem:handle_event_result().
+%% The state names must fulfill some rules regarding
+%% where the role() and the renegotiate_flag() is placed:
+
+-spec role(state_name()) -> role().
+role({_,Role}) -> Role;
+role({_,Role,_}) -> Role.
+
+-spec renegotiation(state_name()) -> boolean().
+renegotiation({_,_,ReNeg}) -> ReNeg == renegotiation;
+renegotiation(_) -> false.
+
+
+-define(CONNECTED(StateName),
+ (element(1,StateName) == connected orelse
+ element(1,StateName) == ext_info ) ).
-spec handle_event(gen_statem:event_type(),
event_content(),
state_name(),
#data{}
- ) -> handle_event_result().
+ ) -> gen_statem:event_handler_result(state_name()) .
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
@@ -589,13 +609,17 @@ handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg},
handle_event(_, #ssh_msg_kexdh_init{} = Msg, {key_exchange,server,ReNeg}, D) ->
{ok, KexdhReply, Ssh1} = ssh_transport:handle_kexdh_init(Msg, D#data.ssh_params),
send_bytes(KexdhReply, D),
- {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1),
+ {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1),
send_bytes(NewKeys, D),
+ {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh2),
+ send_bytes(ExtInfo, D),
{next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}};
handle_event(_, #ssh_msg_kexdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) ->
- {ok, NewKeys, Ssh} = ssh_transport:handle_kexdh_reply(Msg, D#data.ssh_params),
+ {ok, NewKeys, Ssh1} = ssh_transport:handle_kexdh_reply(Msg, D#data.ssh_params),
send_bytes(NewKeys, D),
+ {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1),
+ send_bytes(ExtInfo, D),
{next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}};
%%%---- diffie-hellman group exchange
@@ -620,13 +644,17 @@ handle_event(_, #ssh_msg_kex_dh_gex_group{} = Msg, {key_exchange,client,ReNeg},
handle_event(_, #ssh_msg_kex_ecdh_init{} = Msg, {key_exchange,server,ReNeg}, D) ->
{ok, KexEcdhReply, Ssh1} = ssh_transport:handle_kex_ecdh_init(Msg, D#data.ssh_params),
send_bytes(KexEcdhReply, D),
- {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1),
+ {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1),
send_bytes(NewKeys, D),
+ {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh2),
+ send_bytes(ExtInfo, D),
{next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}};
handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) ->
- {ok, NewKeys, Ssh} = ssh_transport:handle_kex_ecdh_reply(Msg, D#data.ssh_params),
+ {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_ecdh_reply(Msg, D#data.ssh_params),
send_bytes(NewKeys, D),
+ {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1),
+ send_bytes(ExtInfo, D),
{next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}};
@@ -635,8 +663,10 @@ handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D)
handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,server,ReNeg}, D) ->
{ok, KexGexReply, Ssh1} = ssh_transport:handle_kex_dh_gex_init(Msg, D#data.ssh_params),
send_bytes(KexGexReply, D),
- {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1),
+ {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1),
send_bytes(NewKeys, D),
+ {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh2),
+ send_bytes(ExtInfo, D),
{next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}};
@@ -645,30 +675,60 @@ handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,serv
handle_event(_, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,client,ReNeg}, D) ->
{ok, NewKeys, Ssh1} = ssh_transport:handle_kex_dh_gex_reply(Msg, D#data.ssh_params),
send_bytes(NewKeys, D),
- {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh1}};
+ {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1),
+ send_bytes(ExtInfo, D),
+ {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}};
%%% ######## {new_keys, client|server} ####
%% First key exchange round:
-handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,init}, D) ->
+handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,client,init}, D) ->
{ok, Ssh1} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params),
- Ssh = case Role of
- client ->
- {MsgReq, Ssh2} = ssh_auth:service_request_msg(Ssh1),
- send_bytes(MsgReq, D),
- Ssh2;
- server ->
- Ssh1
- end,
- {next_state, {service_request,Role}, D#data{ssh_params=Ssh}};
+ %% {ok, ExtInfo, Ssh2} = ssh_transport:ext_info_message(Ssh1),
+ %% send_bytes(ExtInfo, D),
+ {MsgReq, Ssh} = ssh_auth:service_request_msg(Ssh1),
+ send_bytes(MsgReq, D),
+ {next_state, {ext_info,client,init}, D#data{ssh_params=Ssh}};
+
+handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,server,init}, D) ->
+ {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params),
+ %% {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1),
+ %% send_bytes(ExtInfo, D),
+ {next_state, {ext_info,server,init}, D#data{ssh_params=Ssh}};
%% Subsequent key exchange rounds (renegotiation):
handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,renegotiate}, D) ->
{ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params),
- {next_state, {connected,Role}, D#data{ssh_params=Ssh}};
+ %% {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1),
+ %% send_bytes(ExtInfo, D),
+ {next_state, {ext_info,Role,renegotiate}, D#data{ssh_params=Ssh}};
+
+
+%%% ######## {ext_info, client|server, init|renegotiate} ####
+
+handle_event(_, #ssh_msg_ext_info{}=Msg, {ext_info,Role,init}, D0) ->
+ D = handle_ssh_msg_ext_info(Msg, D0),
+ {next_state, {service_request,Role}, D};
+
+handle_event(_, #ssh_msg_ext_info{}=Msg, {ext_info,Role,renegotiate}, D0) ->
+ D = handle_ssh_msg_ext_info(Msg, D0),
+ {next_state, {connected,Role}, D};
+
+handle_event(_, #ssh_msg_newkeys{}=Msg, {ext_info,_Role,renegotiate}, D) ->
+ {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params),
+ {keep_state, D#data{ssh_params = Ssh}};
+
+
+handle_event(internal, Msg, {ext_info,Role,init}, D) when is_tuple(Msg) ->
+ %% If something else arrives, goto next state and handle the event in that one
+ {next_state, {service_request,Role}, D, [postpone]};
-%%% ######## {service_request, client|server}
+handle_event(internal, Msg, {ext_info,Role,_ReNegFlag}, D) when is_tuple(Msg) ->
+ %% If something else arrives, goto next state and handle the event in that one
+ {next_state, {connected,Role}, D, [postpone]};
+
+%%% ######## {service_request, client|server} ####
handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {service_request,server}, D) ->
case ServiceName of
@@ -747,6 +807,11 @@ handle_event(_,
end;
%%---- userauth success to client
+handle_event(_, #ssh_msg_ext_info{}=Msg, {userauth,client}, D0) ->
+ %% FIXME: need new state to receive this msg!
+ D = handle_ssh_msg_ext_info(Msg, D0),
+ {keep_state, D};
+
handle_event(_, #ssh_msg_userauth_success{}, {userauth,client}, D=#data{ssh_params = Ssh}) ->
D#data.starter ! ssh_connected,
{next_state, {connected,client}, D#data{ssh_params=Ssh#ssh{authenticated = true}}};
@@ -849,6 +914,11 @@ handle_event(_, #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info
end,
{next_state, {userauth,client}, D, [postpone]};
+handle_event(_, #ssh_msg_ext_info{}=Msg, {userauth_keyboard_interactive_info_response, client}, D0) ->
+ %% FIXME: need new state to receive this msg!
+ D = handle_ssh_msg_ext_info(Msg, D0),
+ {keep_state, D};
+
handle_event(_, #ssh_msg_userauth_success{}, {userauth_keyboard_interactive_info_response, client}, D) ->
{next_state, {userauth,client}, D, [postpone]};
@@ -967,12 +1037,10 @@ handle_event(cast, data_size, _, _) ->
-handle_event(cast, _, StateName, _) when StateName /= {connected,server},
- StateName /= {connected,client} ->
+handle_event(cast, _, StateName, _) when not ?CONNECTED(StateName) ->
{keep_state_and_data, [postpone]};
-
-handle_event(cast, {adjust_window,ChannelId,Bytes}, {connected,_}, D) ->
+handle_event(cast, {adjust_window,ChannelId,Bytes}, StateName, D) when ?CONNECTED(StateName) ->
case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{recv_window_size = WinSize,
recv_window_pending = Pending,
@@ -998,7 +1066,7 @@ handle_event(cast, {adjust_window,ChannelId,Bytes}, {connected,_}, D) ->
keep_state_and_data
end;
-handle_event(cast, {reply_request,success,ChannelId}, {connected,_}, D) ->
+handle_event(cast, {reply_request,success,ChannelId}, StateName, D) when ?CONNECTED(StateName) ->
case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{remote_id = RemoteId} ->
Msg = ssh_connection:channel_success_msg(RemoteId),
@@ -1009,13 +1077,13 @@ handle_event(cast, {reply_request,success,ChannelId}, {connected,_}, D) ->
keep_state_and_data
end;
-handle_event(cast, {request,ChannelPid, ChannelId, Type, Data}, {connected,_}, D) ->
+handle_event(cast, {request,ChannelPid, ChannelId, Type, Data}, StateName, D) when ?CONNECTED(StateName) ->
{keep_state, handle_request(ChannelPid, ChannelId, Type, Data, false, none, D)};
-handle_event(cast, {request,ChannelId,Type,Data}, {connected,_}, D) ->
+handle_event(cast, {request,ChannelId,Type,Data}, StateName, D) when ?CONNECTED(StateName) ->
{keep_state, handle_request(ChannelId, Type, Data, false, none, D)};
-handle_event(cast, {unknown,Data}, {connected,_}, D) ->
+handle_event(cast, {unknown,Data}, StateName, D) when ?CONNECTED(StateName) ->
Msg = #ssh_msg_unimplemented{sequence = Data},
{keep_state, send_msg(Msg,D)};
@@ -1076,30 +1144,34 @@ handle_event({call,From}, stop, StateName, D0) ->
{Repls,D} = send_replies(Replies, D0),
{stop_and_reply, normal, [{reply,From,ok}|Repls], D#data{connection_state=Connection}};
-handle_event({call,_}, _, StateName, _) when StateName /= {connected,server},
- StateName /= {connected,client} ->
+
+handle_event({call,_}, _, StateName, _) when not ?CONNECTED(StateName) ->
{keep_state_and_data, [postpone]};
-handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, {connected,_}, D0) ->
+handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, StateName, D0)
+ when ?CONNECTED(StateName) ->
D = handle_request(ChannelPid, ChannelId, Type, Data, true, From, D0),
%% Note reply to channel will happen later when reply is recived from peer on the socket
start_channel_request_timer(ChannelId, From, Timeout),
{keep_state, cache_request_idle_timer_check(D)};
-handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, {connected,_}, D0) ->
+handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, StateName, D0)
+ when ?CONNECTED(StateName) ->
D = handle_request(ChannelId, Type, Data, true, From, D0),
%% Note reply to channel will happen later when reply is recived from peer on the socket
start_channel_request_timer(ChannelId, From, Timeout),
{keep_state, cache_request_idle_timer_check(D)};
-handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, {connected,_}, D0) ->
+handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName, D0)
+ when ?CONNECTED(StateName) ->
{{replies, Replies}, Connection} =
ssh_connection:channel_data(ChannelId, Type, Data, D0#data.connection_state, From),
{Repls,D} = send_replies(Replies, D0#data{connection_state = Connection}),
start_channel_request_timer(ChannelId, From, Timeout), % FIXME: No message exchange so why?
{keep_state, D, Repls};
-handle_event({call,From}, {eof, ChannelId}, {connected,_}, D0) ->
+handle_event({call,From}, {eof, ChannelId}, StateName, D0)
+ when ?CONNECTED(StateName) ->
case ssh_channel:cache_lookup(cache(D0), ChannelId) of
#channel{remote_id = Id, sent_close = false} ->
D = send_msg(ssh_connection:channel_eof_msg(Id), D0),
@@ -1110,8 +1182,8 @@ handle_event({call,From}, {eof, ChannelId}, {connected,_}, D0) ->
handle_event({call,From},
{open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout},
- {connected,_},
- D0) ->
+ StateName,
+ D0) when ?CONNECTED(StateName) ->
erlang:monitor(process, ChannelPid),
{ChannelId, D1} = new_channel_id(D0),
D2 = send_msg(ssh_connection:channel_open_msg(Type, ChannelId,
@@ -1131,7 +1203,8 @@ handle_event({call,From},
start_channel_request_timer(ChannelId, From, Timeout),
{keep_state, cache_cancel_idle_timer(D)};
-handle_event({call,From}, {send_window, ChannelId}, {connected,_}, D) ->
+handle_event({call,From}, {send_window, ChannelId}, StateName, D)
+ when ?CONNECTED(StateName) ->
Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{send_window_size = WinSize,
send_packet_size = Packsize} ->
@@ -1141,7 +1214,8 @@ handle_event({call,From}, {send_window, ChannelId}, {connected,_}, D) ->
end,
{keep_state_and_data, [{reply,From,Reply}]};
-handle_event({call,From}, {recv_window, ChannelId}, {connected,_}, D) ->
+handle_event({call,From}, {recv_window, ChannelId}, StateName, D)
+ when ?CONNECTED(StateName) ->
Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{recv_window_size = WinSize,
recv_packet_size = Packsize} ->
@@ -1151,7 +1225,8 @@ handle_event({call,From}, {recv_window, ChannelId}, {connected,_}, D) ->
end,
{keep_state_and_data, [{reply,From,Reply}]};
-handle_event({call,From}, {close, ChannelId}, {connected,_}, D0) ->
+handle_event({call,From}, {close, ChannelId}, StateName, D0)
+ when ?CONNECTED(StateName) ->
case ssh_channel:cache_lookup(cache(D0), ChannelId) of
#channel{remote_id = Id} = Channel ->
D1 = send_msg(ssh_connection:channel_close_msg(Id), D0),
@@ -1319,11 +1394,16 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) ->
handle_event(internal, {disconnect,Msg,_Reason}, StateName, D) ->
disconnect(Msg, StateName, D);
+handle_event(_Type, _Msg, {ext_info,Role,_ReNegFlag}, D) ->
+ %% If something else arrives, goto next state and handle the event in that one
+ {next_state, {connected,Role}, D, [postpone]};
+
handle_event(Type, Ev, StateName, D) ->
Descr =
case catch atom_to_list(element(1,Ev)) of
"ssh_msg_" ++_ when Type==internal ->
- "Message in wrong state";
+%% "Message in wrong state";
+ lists:flatten(io_lib:format("Message ~p in wrong state (~p)", [element(1,Ev), StateName]));
_ ->
"Internal error"
end,
@@ -1463,16 +1543,6 @@ peer_role(client) -> server;
peer_role(server) -> client.
%%--------------------------------------------------------------------
-%% StateName to Role
-role({_,Role}) -> Role;
-role({_,Role,_}) -> Role.
-
-%%--------------------------------------------------------------------
-%% Check the StateName to see if we are in the renegotiation phase
-renegotiation({_,_,ReNeg}) -> ReNeg == renegotiation;
-renegotiation(_) -> false.
-
-%%--------------------------------------------------------------------
supported_host_keys(client, _, Options) ->
try
find_sup_hkeys(Options)
@@ -1508,14 +1578,19 @@ find_sup_hkeys(Options) ->
%% Alg :: atom()
available_host_key({KeyCb,KeyCbOpts}, Alg, Opts) ->
UserOpts = ?GET_OPT(user_options, Opts),
- element(1,
- catch KeyCb:host_key(Alg, [{key_cb_private,KeyCbOpts}|UserOpts])) == ok.
+ case KeyCb:host_key(Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of
+ {ok,_} -> true;
+ _ -> false
+ end.
+
send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) ->
{Bytes, Ssh} = ssh_transport:ssh_packet(Msg, Ssh0),
send_bytes(Bytes, State),
State#data{ssh_params=Ssh}.
+send_bytes("", _D) ->
+ ok;
send_bytes(Bytes, #data{socket = Socket, transport_cb = Transport}) ->
_ = Transport:send(Socket, Bytes),
ok.
@@ -1622,6 +1697,29 @@ cache(#data{connection_state=C}) -> C#connection.channel_cache.
%%%----------------------------------------------------------------
+handle_ssh_msg_ext_info(#ssh_msg_ext_info{}, D=#data{ssh_params = #ssh{recv_ext_info=false}} ) ->
+ % The peer sent this although we didn't allow it!
+ D;
+
+handle_ssh_msg_ext_info(#ssh_msg_ext_info{data=Data}, D0) ->
+ lists:foldl(fun ext_info/2, D0, Data).
+
+
+ext_info({"server-sig-algs",SigAlgs}, D0 = #data{ssh_params=#ssh{role=client}=Ssh0}) ->
+ %% Make strings to eliminate risk of beeing bombed with odd strings that fills the atom table:
+ SupportedAlgs = lists:map(fun erlang:atom_to_list/1, ssh_transport:supported_algorithms(public_key)),
+ Ssh = Ssh0#ssh{userauth_pubkeys =
+ [list_to_atom(SigAlg) || SigAlg <- string:tokens(SigAlgs,","),
+ %% length of SigAlg is implicitly checked by member:
+ lists:member(SigAlg, SupportedAlgs)
+ ]},
+ D0#data{ssh_params = Ssh};
+
+ext_info(_, D0) ->
+ %% Not implemented
+ D0.
+
+%%%----------------------------------------------------------------
handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, D) ->
case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{remote_id = Id} = Channel ->
@@ -1911,12 +2009,14 @@ handshake(Pid, Ref, Timeout) ->
end.
update_inet_buffers(Socket) ->
- {ok, BufSzs0} = inet:getopts(Socket, [sndbuf,recbuf]),
- MinVal = 655360,
- case
- [{Tag,MinVal} || {Tag,Val} <- BufSzs0,
- Val < MinVal]
+ try
+ {ok, BufSzs0} = inet:getopts(Socket, [sndbuf,recbuf]),
+ MinVal = 655360,
+ [{Tag,MinVal} || {Tag,Val} <- BufSzs0,
+ Val < MinVal]
of
[] -> ok;
NewOpts -> inet:setopts(Socket, NewOpts)
+ catch
+ _:_ -> ok
end.
diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl
index 0345bbdea7..9431bf1817 100644
--- a/lib/ssh/src/ssh_dbg.erl
+++ b/lib/ssh/src/ssh_dbg.erl
@@ -56,7 +56,7 @@ messages(Write, MangleArg) when is_function(Write,2),
dbg_ssh_messages() ->
dbg:tp(ssh_message,encode,1, x),
dbg:tp(ssh_message,decode,1, x),
- dbg:tpl(ssh_transport,select_algorithm,3, x),
+ dbg:tpl(ssh_transport,select_algorithm,4, x),
dbg:tp(ssh_transport,hello_version_msg,1, x),
dbg:tp(ssh_transport,handle_hello_version,1, x).
@@ -77,7 +77,7 @@ msg_formater({trace_ts,Pid,return_from,{ssh_message,decode,1},Msg,TS}, D) ->
msg_formater({trace_ts,_Pid,call,{ssh_transport,select_algorithm,_},_TS}, D) ->
D;
-msg_formater({trace_ts,Pid,return_from,{ssh_transport,select_algorithm,3},{ok,Alg},TS}, D) ->
+msg_formater({trace_ts,Pid,return_from,{ssh_transport,select_algorithm,_},{ok,Alg},TS}, D) ->
fmt("~n~s ~p ALGORITHMS~n~s~n", [ts(TS),Pid, wr_record(Alg)], D);
msg_formater({trace_ts,_Pid,call,{ssh_transport,hello_version_msg,_},_TS}, D) ->
@@ -160,6 +160,7 @@ shrink_bin(X) -> X.
?wr_record(ssh_msg_kexdh_init);
?wr_record(ssh_msg_kexdh_reply);
?wr_record(ssh_msg_newkeys);
+?wr_record(ssh_msg_ext_info);
?wr_record(ssh_msg_kex_dh_gex_request);
?wr_record(ssh_msg_kex_dh_gex_request_old);
?wr_record(ssh_msg_kex_dh_gex_group);
diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl
index 88f4d10792..4498c70d34 100644
--- a/lib/ssh/src/ssh_file.erl
+++ b/lib/ssh/src/ssh_file.erl
@@ -75,17 +75,12 @@ host_key(Algorithm, Opts) ->
Password = proplists:get_value(identity_pass_phrase(Algorithm), Opts, ignore),
case decode(File, Password) of
{ok,Key} ->
- case {Key,Algorithm} of
- {#'RSAPrivateKey'{}, 'ssh-rsa'} -> {ok,Key};
- {#'DSAPrivateKey'{}, 'ssh-dss'} -> {ok,Key};
- {#'ECPrivateKey'{parameters = {namedCurve, ?'secp256r1'}}, 'ecdsa-sha2-nistp256'} -> {ok,Key};
- {#'ECPrivateKey'{parameters = {namedCurve, ?'secp384r1'}}, 'ecdsa-sha2-nistp384'} -> {ok,Key};
- {#'ECPrivateKey'{parameters = {namedCurve, ?'secp521r1'}}, 'ecdsa-sha2-nistp521'} -> {ok,Key};
- _ ->
- {error,bad_keytype_in_file}
+ case ssh_transport:valid_key_sha_alg(Key,Algorithm) of
+ true -> {ok,Key};
+ false -> {error,bad_keytype_in_file}
end;
- Other ->
- Other
+ {error,DecodeError} ->
+ {error,DecodeError}
end.
is_auth_key(Key, User,Opts) ->
@@ -115,6 +110,9 @@ user_key(Algorithm, Opts) ->
%% Internal functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
file_base_name('ssh-rsa' ) -> "ssh_host_rsa_key";
+file_base_name('rsa-sha2-256' ) -> "ssh_host_rsa_key";
+file_base_name('rsa-sha2-384' ) -> "ssh_host_rsa_key";
+file_base_name('rsa-sha2-512' ) -> "ssh_host_rsa_key";
file_base_name('ssh-dss' ) -> "ssh_host_dsa_key";
file_base_name('ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key";
file_base_name('ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key";
@@ -253,12 +251,18 @@ do_lookup_host_key(KeyToMatch, Host, Alg, Opts) ->
identity_key_filename('ssh-dss' ) -> "id_dsa";
identity_key_filename('ssh-rsa' ) -> "id_rsa";
+identity_key_filename('rsa-sha2-256' ) -> "id_rsa";
+identity_key_filename('rsa-sha2-384' ) -> "id_rsa";
+identity_key_filename('rsa-sha2-512' ) -> "id_rsa";
identity_key_filename('ecdsa-sha2-nistp256') -> "id_ecdsa";
identity_key_filename('ecdsa-sha2-nistp384') -> "id_ecdsa";
identity_key_filename('ecdsa-sha2-nistp521') -> "id_ecdsa".
identity_pass_phrase("ssh-dss" ) -> dsa_pass_phrase;
identity_pass_phrase("ssh-rsa" ) -> rsa_pass_phrase;
+identity_pass_phrase("rsa-sha2-256" ) -> rsa_pass_phrase;
+identity_pass_phrase("rsa-sha2-384" ) -> rsa_pass_phrase;
+identity_pass_phrase("rsa-sha2-512" ) -> rsa_pass_phrase;
identity_pass_phrase("ecdsa-sha2-"++_) -> ecdsa_pass_phrase;
identity_pass_phrase(P) when is_atom(P) ->
identity_pass_phrase(atom_to_list(P)).
diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl
index 562f040477..21c0eabcd3 100644
--- a/lib/ssh/src/ssh_message.erl
+++ b/lib/ssh/src/ssh_message.erl
@@ -215,6 +215,16 @@ encode(#ssh_msg_service_accept{
}) ->
<<?Ebyte(?SSH_MSG_SERVICE_ACCEPT), ?Estring_utf8(Service)>>;
+encode(#ssh_msg_ext_info{
+ nr_extensions = N,
+ data = Data
+ }) ->
+ lists:foldl(fun({ExtName,ExtVal}, Acc) ->
+ <<Acc/binary, ?Estring(ExtName), ?Estring(ExtVal)>>
+ end,
+ <<?Ebyte(?SSH_MSG_EXT_INFO), ?Euint32(N)>>,
+ Data);
+
encode(#ssh_msg_newkeys{}) ->
<<?Ebyte(?SSH_MSG_NEWKEYS)>>;
@@ -435,6 +445,18 @@ decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_RESPONSE), ?UINT32(Num), Data/binary>>) ->
num_responses = Num,
data = Data};
+decode(<<?BYTE(?SSH_MSG_EXT_INFO), ?UINT32(N), BinData/binary>>) ->
+ Data = bin_foldr(
+ fun(Bin,Acc) when length(Acc) == N ->
+ {Bin,Acc};
+ (<<?DEC_BIN(V0,__0), ?DEC_BIN(V1,__1), Rest/binary>>, Acc) ->
+ {Rest,[{binary_to_list(V0),binary_to_list(V1)}|Acc]}
+ end, [], BinData),
+ #ssh_msg_ext_info{
+ nr_extensions = N,
+ data = Data
+ };
+
%%% Keyexchange messages
decode(<<?BYTE(?SSH_MSG_KEXINIT), Cookie:128, Data/binary>>) ->
decode_kex_init(Data, [Cookie, ssh_msg_kexinit], 10);
@@ -537,17 +559,28 @@ decode(<<?BYTE(?SSH_MSG_DEBUG), ?BYTE(Bool), ?DEC_BIN(Msg,__0), ?DEC_BIN(Lang,__
%%% Helper functions
%%%
+bin_foldr(Fun, Acc, Bin) ->
+ lists:reverse(bin_foldl(Fun, Acc, Bin)).
+
+bin_foldl(_, Acc, <<>>) -> Acc;
+bin_foldl(Fun, Acc0, Bin0) ->
+ {Bin,Acc} = Fun(Bin0,Acc0),
+ bin_foldl(Fun, Acc, Bin).
+
+%%%----------------------------------------------------------------
decode_keyboard_interactive_prompts(<<>>, Acc) ->
lists:reverse(Acc);
decode_keyboard_interactive_prompts(<<?DEC_BIN(Prompt,__0), ?BYTE(Bool), Bin/binary>>,
Acc) ->
decode_keyboard_interactive_prompts(Bin, [{Prompt, erl_boolean(Bool)} | Acc]).
+%%%----------------------------------------------------------------
erl_boolean(0) ->
false;
erl_boolean(1) ->
true.
+%%%----------------------------------------------------------------
decode_kex_init(<<?BYTE(Bool), ?UINT32(X)>>, Acc, 0) ->
list_to_tuple(lists:reverse([X, erl_boolean(Bool) | Acc]));
decode_kex_init(<<?BYTE(Bool)>>, Acc, 0) ->
@@ -569,11 +602,22 @@ decode_signature(<<?DEC_BIN(_Alg,__0), ?UINT32(_), Signature/binary>>) ->
Signature.
-encode_signature(#'RSAPublicKey'{}, Signature) ->
- <<?Ebinary(<<"ssh-rsa">>), ?Ebinary(Signature)>>;
-encode_signature({_, #'Dss-Parms'{}}, Signature) ->
+encode_signature({#'RSAPublicKey'{},Sign}, Signature) ->
+ SignName = list_to_binary(atom_to_list(Sign)),
+ <<?Ebinary(SignName), ?Ebinary(Signature)>>;
+encode_signature({{_, #'Dss-Parms'{}},_}, Signature) ->
<<?Ebinary(<<"ssh-dss">>), ?Ebinary(Signature)>>;
-encode_signature({#'ECPoint'{}, {namedCurve,OID}}, Signature) ->
+encode_signature({{#'ECPoint'{}, {namedCurve,OID}},_}, Signature) ->
CurveName = public_key:oid2ssh_curvename(OID),
<<?Ebinary(<<"ecdsa-sha2-",CurveName/binary>>), ?Ebinary(Signature)>>.
+%% encode_signature(#'RSAPublicKey'{}, Signature) ->
+%% SignName = <<"ssh-rsa">>,
+%% <<?Ebinary(SignName), ?Ebinary(Signature)>>;
+%% encode_signature({_, #'Dss-Parms'{}}, Signature) ->
+%% <<?Ebinary(<<"ssh-dss">>), ?Ebinary(Signature)>>;
+%% encode_signature({#'ECPoint'{}, {namedCurve,OID}}, Signature) ->
+%% CurveName = public_key:oid2ssh_curvename(OID),
+%% <<?Ebinary(<<"ecdsa-sha2-",CurveName/binary>>), ?Ebinary(Signature)>>.
+
+
diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl
index ee3cdbb8a0..0886d5b34d 100644
--- a/lib/ssh/src/ssh_options.erl
+++ b/lib/ssh/src/ssh_options.erl
@@ -437,9 +437,7 @@ default(client) ->
{pref_public_key_algs, def} =>
#{default =>
- %% Get dynamically supported keys in the order of the ?SUPPORTED_USER_KEYS
- [A || A <- ?SUPPORTED_USER_KEYS,
- lists:member(A, ssh_transport:supported_algorithms(public_key))],
+ ssh_transport:supported_algorithms(public_key),
chk =>
fun check_pref_public_key_algs/1,
class =>
@@ -614,11 +612,23 @@ default(common) ->
},
{max_random_length_padding, def} =>
- #{default => ?MAX_RND_PADDING_LEN,
- chk => fun check_non_neg_integer/1,
- class => user_options
- }
- }.
+ #{default => ?MAX_RND_PADDING_LEN,
+ chk => fun check_non_neg_integer/1,
+ class => user_options
+ },
+
+ {send_ext_info, def} =>
+ #{default => true,
+ chk => fun erlang:is_boolean/1,
+ class => user_options
+ },
+
+ {recv_ext_info, def} =>
+ #{default => true,
+ chk => fun erlang:is_boolean/1,
+ class => user_options
+ }
+ }.
%%%================================================================
@@ -658,20 +668,8 @@ check_pref_public_key_algs(V) ->
PKs = ssh_transport:supported_algorithms(public_key),
CHK = fun(A, Ack) ->
case lists:member(A, PKs) of
- true ->
- [A|Ack];
- false ->
- %% Check with the documented options, that is,
- %% the one we can handle
- case lists:member(A,?SUPPORTED_USER_KEYS) of
- false ->
- %% An algorithm ssh never can handle
- error_in_check(A, "Not supported public key");
- true ->
- %% An algorithm ssh can handle, but not in
- %% this very call
- Ack
- end
+ true -> [A|Ack];
+ false -> error_in_check(A, "Not supported public key")
end
end,
case lists:foldr(
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl
index 6b47868d5c..7c7dda7a1e 100644
--- a/lib/ssh/src/ssh_transport.erl
+++ b/lib/ssh/src/ssh_transport.erl
@@ -38,6 +38,7 @@
handle_hello_version/1,
key_exchange_init_msg/1,
key_init/3, new_keys_message/1,
+ ext_info_message/1,
handle_kexinit_msg/3, handle_kexdh_init/2,
handle_kex_dh_gex_group/2, handle_kex_dh_gex_init/2, handle_kex_dh_gex_reply/2,
handle_new_keys/2, handle_kex_dh_gex_request/2,
@@ -47,6 +48,7 @@
parallell_gen_key/1,
extract_public_key/1,
ssh_packet/2, pack/2,
+ valid_key_sha_alg/2,
sha/1, sign/3, verify/4]).
%%% For test suites
@@ -90,6 +92,10 @@ default_algorithms(cipher) ->
default_algorithms(mac) ->
supported_algorithms(mac, same(['AEAD_AES_128_GCM',
'AEAD_AES_256_GCM']));
+default_algorithms(public_key) ->
+ supported_algorithms(public_key, ['rsa-sha2-256',
+ 'rsa-sha2-384',
+ 'rsa-sha2-512']);
default_algorithms(Alg) ->
supported_algorithms(Alg, []).
@@ -116,6 +122,9 @@ supported_algorithms(public_key) ->
{'ecdsa-sha2-nistp384', [{public_keys,ecdsa}, {hashs,sha384}, {ec_curve,secp384r1}]},
{'ecdsa-sha2-nistp521', [{public_keys,ecdsa}, {hashs,sha512}, {ec_curve,secp521r1}]},
{'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {ec_curve,secp256r1}]},
+ {'rsa-sha2-256', [{public_keys,rsa}, {hashs,sha256} ]},
+ {'rsa-sha2-384', [{public_keys,rsa}, {hashs,sha384} ]},
+ {'rsa-sha2-512', [{public_keys,rsa}, {hashs,sha512} ]},
{'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]},
{'ssh-dss', [{public_keys,dss}, {hashs,sha} ]} % Gone in OpenSSH 7.3.p1
]);
@@ -230,7 +239,7 @@ key_exchange_init_msg(Ssh0) ->
kex_init(#ssh{role = Role, opts = Opts, available_host_keys = HostKeyAlgs}) ->
Random = ssh_bits:random(16),
PrefAlgs = ?GET_OPT(preferred_algorithms, Opts),
- kexinit_message(Role, Random, PrefAlgs, HostKeyAlgs).
+ kexinit_message(Role, Random, PrefAlgs, HostKeyAlgs, Opts).
key_init(client, Ssh, Value) ->
Ssh#ssh{c_keyinit = Value};
@@ -238,10 +247,11 @@ key_init(server, Ssh, Value) ->
Ssh#ssh{s_keyinit = Value}.
-kexinit_message(_Role, Random, Algs, HostKeyAlgs) ->
+kexinit_message(Role, Random, Algs, HostKeyAlgs, Opts) ->
#ssh_msg_kexinit{
cookie = Random,
- kex_algorithms = to_strings( get_algs(kex,Algs) ),
+ kex_algorithms = to_strings( get_algs(kex,Algs) )
+ ++ kex_ext_info(Role,Opts),
server_host_key_algorithms = HostKeyAlgs,
encryption_algorithms_client_to_server = c2s(cipher,Algs),
encryption_algorithms_server_to_client = s2c(cipher,Algs),
@@ -263,39 +273,42 @@ get_algs(Key, Algs) -> proplists:get_value(Key, Algs, default_algorithms(Key)).
to_strings(L) -> lists:map(fun erlang:atom_to_list/1, L).
new_keys_message(Ssh0) ->
- {SshPacket, Ssh} =
- ssh_packet(#ssh_msg_newkeys{}, Ssh0),
+ {SshPacket, Ssh1} = ssh_packet(#ssh_msg_newkeys{}, Ssh0),
+ Ssh = install_alg(snd, Ssh1),
{ok, SshPacket, Ssh}.
handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own,
- #ssh{role = client} = Ssh0) ->
- {ok, Algoritms} = select_algorithm(client, Own, CounterPart),
- case verify_algorithm(Algoritms) of
- true ->
- key_exchange_first_msg(Algoritms#alg.kex,
- Ssh0#ssh{algorithms = Algoritms});
- {false,Alg} ->
- %% TODO: Correct code?
- ssh_connection_handler:disconnect(
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
- description = "Selection of key exchange algorithm failed: "
- ++ Alg
- })
+ #ssh{role = client} = Ssh) ->
+ try
+ {ok, Algorithms} = select_algorithm(client, Own, CounterPart, Ssh#ssh.opts),
+ true = verify_algorithm(Algorithms),
+ Algorithms
+ of
+ Algos ->
+ key_exchange_first_msg(Algos#alg.kex,
+ Ssh#ssh{algorithms = Algos})
+ catch
+ _:_ ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Selection of key exchange algorithm failed"})
end;
handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own,
- #ssh{role = server} = Ssh) ->
- {ok, Algoritms} = select_algorithm(server, CounterPart, Own),
- case verify_algorithm(Algoritms) of
- true ->
- {ok, Ssh#ssh{algorithms = Algoritms}};
- {false,Alg} ->
- ssh_connection_handler:disconnect(
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
- description = "Selection of key exchange algorithm failed: "
- ++ Alg
- })
+ #ssh{role = server} = Ssh) ->
+ try
+ {ok, Algorithms} = select_algorithm(server, CounterPart, Own, Ssh#ssh.opts),
+ true = verify_algorithm(Algorithms),
+ Algorithms
+ of
+ Algos ->
+ {ok, Ssh#ssh{algorithms = Algos}}
+ catch
+ _:_ ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Selection of key exchange algorithm failed"})
end.
@@ -308,6 +321,8 @@ verify_algorithm(#alg{decrypt = undefined}) -> {false, "decrypt"};
verify_algorithm(#alg{compress = undefined}) -> {false, "compress"};
verify_algorithm(#alg{decompress = undefined}) -> {false, "decompress"};
verify_algorithm(#alg{kex = Kex}) ->
+ %% This also catches the error if 'ext-info-s' or 'ext-info-c' is selected.
+ %% (draft-ietf-curdle-ssh-ext-info-04 2.2)
case lists:member(Kex, supported_algorithms(kex)) of
true -> true;
false -> {false, "kex"}
@@ -370,7 +385,8 @@ key_exchange_first_msg(Kex, Ssh0) when Kex == 'ecdh-sha2-nistp256' ;
%%% diffie-hellman-group18-sha512
%%%
handle_kexdh_init(#ssh_msg_kexdh_init{e = E},
- Ssh0 = #ssh{algorithms = #alg{kex=Kex} = Algs}) ->
+ Ssh0 = #ssh{algorithms = #alg{kex=Kex,
+ hkey=SignAlg} = Algs}) ->
%% server
{G, P} = dh_group(Kex),
if
@@ -378,12 +394,12 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e = E},
Sz = dh_bits(Algs),
{Public, Private} = generate_key(dh, [P,G,2*Sz]),
K = compute_key(dh, E, Private, [P,G]),
- MyPrivHostKey = get_host_key(Ssh0),
+ MyPrivHostKey = get_host_key(Ssh0, SignAlg),
MyPubHostKey = extract_public_key(MyPrivHostKey),
- H = kex_h(Ssh0, MyPubHostKey, E, Public, K),
- H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H),
+ H = kex_hash(Ssh0, MyPubHostKey, SignAlg, sha(Kex), {E,Public,K}),
+ H_SIG = sign(H, sha(SignAlg), MyPrivHostKey),
{SshPacket, Ssh1} =
- ssh_packet(#ssh_msg_kexdh_reply{public_host_key = MyPubHostKey,
+ ssh_packet(#ssh_msg_kexdh_reply{public_host_key = {MyPubHostKey,SignAlg},
f = Public,
h_sig = H_SIG
}, Ssh0),
@@ -404,19 +420,20 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e = E},
handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = PeerPubHostKey,
f = F,
h_sig = H_SIG},
- #ssh{keyex_key = {{Private, Public}, {G, P}}} = Ssh0) ->
+ #ssh{keyex_key = {{Private, Public}, {G, P}},
+ algorithms = #alg{kex=Kex,
+ hkey=SignAlg}} = Ssh0) ->
%% client
if
1=<F, F=<(P-1)->
K = compute_key(dh, F, Private, [P,G]),
- H = kex_h(Ssh0, PeerPubHostKey, Public, F, K),
-
+ H = kex_hash(Ssh0, PeerPubHostKey, SignAlg, sha(Kex), {Public,F,K}),
case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of
ok ->
{SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0),
- {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K),
- exchanged_hash = H,
- session_id = sid(Ssh, H)}};
+ {ok, SshPacket, install_alg(snd, Ssh#ssh{shared_secret = ssh_bits:mpint(K),
+ exchanged_hash = H,
+ session_id = sid(Ssh, H)})};
Error ->
ssh_connection_handler:disconnect(
#ssh_msg_disconnect{
@@ -486,7 +503,7 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request_old{n = NBits},
ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0),
{ok, SshPacket,
Ssh#ssh{keyex_key = {x, {G, P}},
- keyex_info = {-1, -1, NBits} % flag for kex_h hash calc
+ keyex_info = {-1, -1, NBits} % flag for kex_hash calc
}};
{error,_} ->
ssh_connection_handler:disconnect(
@@ -532,20 +549,21 @@ handle_kex_dh_gex_group(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0) ->
handle_kex_dh_gex_init(#ssh_msg_kex_dh_gex_init{e = E},
#ssh{keyex_key = {{Private, Public}, {G, P}},
- keyex_info = {Min, Max, NBits}} =
- Ssh0) ->
+ keyex_info = {Min, Max, NBits},
+ algorithms = #alg{kex=Kex,
+ hkey=SignAlg}} = Ssh0) ->
%% server
if
1=<E, E=<(P-1) ->
K = compute_key(dh, E, Private, [P,G]),
if
1<K, K<(P-1) ->
- MyPrivHostKey = get_host_key(Ssh0),
+ MyPrivHostKey = get_host_key(Ssh0, SignAlg),
MyPubHostKey = extract_public_key(MyPrivHostKey),
- H = kex_h(Ssh0, MyPubHostKey, Min, NBits, Max, P, G, E, Public, K),
- H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H),
+ H = kex_hash(Ssh0, MyPubHostKey, SignAlg, sha(Kex), {Min,NBits,Max,P,G,E,Public,K}),
+ H_SIG = sign(H, sha(SignAlg), MyPrivHostKey),
{SshPacket, Ssh} =
- ssh_packet(#ssh_msg_kex_dh_gex_reply{public_host_key = MyPubHostKey,
+ ssh_packet(#ssh_msg_kex_dh_gex_reply{public_host_key = {MyPubHostKey,SignAlg},
f = Public,
h_sig = H_SIG}, Ssh0),
{ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K),
@@ -571,7 +589,9 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK
f = F,
h_sig = H_SIG},
#ssh{keyex_key = {{Private, Public}, {G, P}},
- keyex_info = {Min, Max, NBits}} =
+ keyex_info = {Min, Max, NBits},
+ algorithms = #alg{kex=Kex,
+ hkey=SignAlg}} =
Ssh0) ->
%% client
if
@@ -579,14 +599,13 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK
K = compute_key(dh, F, Private, [P,G]),
if
1<K, K<(P-1) ->
- H = kex_h(Ssh0, PeerPubHostKey, Min, NBits, Max, P, G, Public, F, K),
-
+ H = kex_hash(Ssh0, PeerPubHostKey, SignAlg, sha(Kex), {Min,NBits,Max,P,G,Public,F,K}),
case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of
ok ->
{SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0),
- {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K),
- exchanged_hash = H,
- session_id = sid(Ssh, H)}};
+ {ok, SshPacket, install_alg(snd, Ssh#ssh{shared_secret = ssh_bits:mpint(K),
+ exchanged_hash = H,
+ session_id = sid(Ssh, H)})};
_Error ->
ssh_connection_handler:disconnect(
#ssh_msg_disconnect{
@@ -616,7 +635,8 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK
%%% diffie-hellman-ecdh-sha2-*
%%%
handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic},
- Ssh0 = #ssh{algorithms = #alg{kex=Kex}}) ->
+ Ssh0 = #ssh{algorithms = #alg{kex=Kex,
+ hkey=SignAlg}}) ->
%% at server
Curve = ecdh_curve(Kex),
{MyPublic, MyPrivate} = generate_key(ecdh, Curve),
@@ -624,12 +644,12 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic},
compute_key(ecdh, PeerPublic, MyPrivate, Curve)
of
K ->
- MyPrivHostKey = get_host_key(Ssh0),
+ MyPrivHostKey = get_host_key(Ssh0, SignAlg),
MyPubHostKey = extract_public_key(MyPrivHostKey),
- H = kex_h(Ssh0, Curve, MyPubHostKey, PeerPublic, MyPublic, K),
- H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H),
+ H = kex_hash(Ssh0, MyPubHostKey, SignAlg, sha(Curve), {PeerPublic, MyPublic, K}),
+ H_SIG = sign(H, sha(SignAlg), MyPrivHostKey),
{SshPacket, Ssh1} =
- ssh_packet(#ssh_msg_kex_ecdh_reply{public_host_key = MyPubHostKey,
+ ssh_packet(#ssh_msg_kex_ecdh_reply{public_host_key = {MyPubHostKey,SignAlg},
q_s = MyPublic,
h_sig = H_SIG},
Ssh0),
@@ -649,20 +669,21 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic},
handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey,
q_s = PeerPublic,
h_sig = H_SIG},
- #ssh{keyex_key = {{MyPublic,MyPrivate}, Curve}} = Ssh0
+ #ssh{keyex_key = {{MyPublic,MyPrivate}, Curve},
+ algorithms = #alg{hkey=SignAlg}} = Ssh0
) ->
%% at client
try
compute_key(ecdh, PeerPublic, MyPrivate, Curve)
of
K ->
- H = kex_h(Ssh0, Curve, PeerPubHostKey, MyPublic, PeerPublic, K),
+ H = kex_hash(Ssh0, PeerPubHostKey, SignAlg, sha(Curve), {MyPublic,PeerPublic,K}),
case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of
ok ->
{SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0),
- {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K),
- exchanged_hash = H,
- session_id = sid(Ssh, H)}};
+ {ok, SshPacket, install_alg(snd, Ssh#ssh{shared_secret = ssh_bits:mpint(K),
+ exchanged_hash = H,
+ session_id = sid(Ssh, H)})};
Error ->
ssh_connection_handler:disconnect(
#ssh_msg_disconnect{
@@ -682,7 +703,7 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey,
%%%----------------------------------------------------------------
handle_new_keys(#ssh_msg_newkeys{}, Ssh0) ->
- try install_alg(Ssh0) of
+ try install_alg(rcv, Ssh0) of
#ssh{} = Ssh ->
{ok, Ssh}
catch
@@ -693,34 +714,49 @@ handle_new_keys(#ssh_msg_newkeys{}, Ssh0) ->
})
end.
+
+%%%----------------------------------------------------------------
+kex_ext_info(Role, Opts) ->
+ case ?GET_OPT(recv_ext_info,Opts) of
+ true when Role==client -> ["ext-info-c"];
+ true when Role==server -> ["ext-info-s"];
+ false -> []
+ end.
+
+ext_info_message(#ssh{role=client,
+ send_ext_info=true} = Ssh0) ->
+ %% FIXME: no extensions implemented
+ {ok, "", Ssh0};
+
+ext_info_message(#ssh{role=server,
+ send_ext_info=true} = Ssh0) ->
+ AlgsList = lists:map(fun erlang:atom_to_list/1,
+ ssh_transport:default_algorithms(public_key)),
+ Msg = #ssh_msg_ext_info{nr_extensions = 1,
+ data = [{"server-sig-algs", string:join(AlgsList,",")}]
+ },
+ {SshPacket, Ssh} = ssh_packet(Msg, Ssh0),
+ {ok, SshPacket, Ssh};
+
+ext_info_message(Ssh0) ->
+ {ok, "", Ssh0}. % "" means: 'do not send'
+
+%%%----------------------------------------------------------------
%% select session id
-sid(#ssh{session_id = undefined}, H) ->
- H;
-sid(#ssh{session_id = Id}, _) ->
- Id.
+sid(#ssh{session_id = undefined}, H) -> H;
+sid(#ssh{session_id = Id}, _) -> Id.
%%
%% The host key should be read from storage
%%
-get_host_key(SSH) ->
- #ssh{key_cb = {KeyCb,KeyCbOpts}, opts = Opts, algorithms = ALG} = SSH,
+get_host_key(SSH, SignAlg) ->
+ #ssh{key_cb = {KeyCb,KeyCbOpts}, opts = Opts} = SSH,
UserOpts = ?GET_OPT(user_options, Opts),
- case KeyCb:host_key(ALG#alg.hkey, [{key_cb_private,KeyCbOpts}|UserOpts]) of
- {ok, #'RSAPrivateKey'{} = Key} -> Key;
- {ok, #'DSAPrivateKey'{} = Key} -> Key;
- {ok, #'ECPrivateKey'{} = Key} -> Key;
- Result ->
- exit({error, {Result, unsupported_key_type}})
+ case KeyCb:host_key(SignAlg, [{key_cb_private,KeyCbOpts}|UserOpts]) of
+ {ok, PrivHostKey} -> PrivHostKey;
+ Result -> exit({error, {Result, unsupported_key_type}})
end.
-sign_host_key(_Ssh, PrivateKey, H) ->
- sign(H, sign_host_key_sha(PrivateKey), PrivateKey).
-
-sign_host_key_sha(#'ECPrivateKey'{parameters = {namedCurve,OID}}) -> sha(OID);
-sign_host_key_sha(#'RSAPrivateKey'{}) -> sha;
-sign_host_key_sha(#'DSAPrivateKey'{}) -> sha.
-
-
extract_public_key(#'RSAPrivateKey'{modulus = N, publicExponent = E}) ->
#'RSAPublicKey'{modulus = N, publicExponent = E};
extract_public_key(#'DSAPrivateKey'{y = Y, p = P, q = Q, g = G}) ->
@@ -730,8 +766,8 @@ extract_public_key(#'ECPrivateKey'{parameters = {namedCurve,OID},
{#'ECPoint'{point=Q}, {namedCurve,OID}}.
-verify_host_key(SSH, PublicKey, Digest, Signature) ->
- case verify(Digest, host_key_sha(PublicKey), Signature, PublicKey) of
+verify_host_key(#ssh{algorithms=Alg}=SSH, PublicKey, Digest, Signature) ->
+ case verify(Digest, sha(Alg#alg.hkey), Signature, PublicKey) of
false ->
{error, bad_signature};
true ->
@@ -739,16 +775,6 @@ verify_host_key(SSH, PublicKey, Digest, Signature) ->
end.
-host_key_sha(#'RSAPublicKey'{}) -> sha;
-host_key_sha({_, #'Dss-Parms'{}}) -> sha;
-host_key_sha({#'ECPoint'{},{namedCurve,OID}}) -> sha(OID).
-
-public_algo(#'RSAPublicKey'{}) -> 'ssh-rsa';
-public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss';
-public_algo({#'ECPoint'{},{namedCurve,OID}}) ->
- Curve = public_key:oid2ssh_curvename(OID),
- list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)).
-
accepted_host(Ssh, PeerName, Public, Opts) ->
case ?GET_OPT(silently_accept_hosts, Opts) of
@@ -812,7 +838,7 @@ known_host_key(#ssh{opts = Opts, key_cb = {KeyCb,KeyCbOpts}, peer = {PeerName,_}
%%
%% The first algorithm in each list MUST be the preferred (guessed)
%% algorithm. Each string MUST contain at least one algorithm name.
-select_algorithm(Role, Client, Server) ->
+select_algorithm(Role, Client, Server, Opts) ->
{Encrypt0, Decrypt0} = select_encrypt_decrypt(Role, Client, Server),
{SendMac0, RecvMac0} = select_send_recv_mac(Role, Client, Server),
@@ -837,17 +863,34 @@ select_algorithm(Role, Client, Server) ->
Kex = select(Client#ssh_msg_kexinit.kex_algorithms,
Server#ssh_msg_kexinit.kex_algorithms),
- Alg = #alg{kex = Kex,
- hkey = HK,
- encrypt = Encrypt,
- decrypt = Decrypt,
- send_mac = SendMac,
- recv_mac = RecvMac,
- compress = Compression,
- decompress = Decompression,
- c_lng = C_Lng,
- s_lng = S_Lng},
- {ok, Alg}.
+ SendExtInfo =
+ %% To send we must have that option enabled and ...
+ ?GET_OPT(send_ext_info,Opts) andalso
+ %% ... the peer must have told us to send:
+ case Role of
+ server -> lists:member("ext-info-c", Client#ssh_msg_kexinit.kex_algorithms);
+ client -> lists:member("ext-info-s", Server#ssh_msg_kexinit.kex_algorithms)
+ end,
+
+ RecvExtInfo =
+ %% The peer should not send unless told so by us (which is
+ %% guided by an option).
+ %% (However a malicious peer could send anyway, so we must be prepared)
+ ?GET_OPT(recv_ext_info,Opts),
+
+ {ok, #alg{kex = Kex,
+ hkey = HK,
+ encrypt = Encrypt,
+ decrypt = Decrypt,
+ send_mac = SendMac,
+ recv_mac = RecvMac,
+ compress = Compression,
+ decompress = Decompression,
+ c_lng = C_Lng,
+ s_lng = S_Lng,
+ send_ext_info = SendExtInfo,
+ recv_ext_info = RecvExtInfo
+ }}.
%%% It is an agreed problem with RFC 5674 that if the selection is
@@ -928,45 +971,66 @@ select_compression_decompression(server, Client, Server) ->
Server#ssh_msg_kexinit.compression_algorithms_server_to_client),
{Compression, Decompression}.
-install_alg(SSH) ->
- SSH1 = alg_final(SSH),
- SSH2 = alg_setup(SSH1),
- alg_init(SSH2).
+%% DIr = rcv | snd
+install_alg(Dir, SSH) ->
+ SSH1 = alg_final(Dir, SSH),
+ SSH2 = alg_setup(Dir, SSH1),
+ alg_init(Dir, SSH2).
-alg_setup(SSH) ->
+alg_setup(snd, SSH) ->
ALG = SSH#ssh.algorithms,
SSH#ssh{kex = ALG#alg.kex,
hkey = ALG#alg.hkey,
encrypt = ALG#alg.encrypt,
- decrypt = ALG#alg.decrypt,
send_mac = ALG#alg.send_mac,
send_mac_size = mac_digest_size(ALG#alg.send_mac),
+ compress = ALG#alg.compress,
+ c_lng = ALG#alg.c_lng,
+ s_lng = ALG#alg.s_lng,
+ send_ext_info = ALG#alg.send_ext_info,
+ recv_ext_info = ALG#alg.recv_ext_info
+ };
+
+alg_setup(rcv, SSH) ->
+ ALG = SSH#ssh.algorithms,
+ SSH#ssh{kex = ALG#alg.kex,
+ hkey = ALG#alg.hkey,
+ decrypt = ALG#alg.decrypt,
recv_mac = ALG#alg.recv_mac,
recv_mac_size = mac_digest_size(ALG#alg.recv_mac),
- compress = ALG#alg.compress,
decompress = ALG#alg.decompress,
c_lng = ALG#alg.c_lng,
s_lng = ALG#alg.s_lng,
- algorithms = undefined
+ send_ext_info = ALG#alg.send_ext_info,
+ recv_ext_info = ALG#alg.recv_ext_info
}.
-alg_init(SSH0) ->
+
+alg_init(snd, SSH0) ->
{ok,SSH1} = send_mac_init(SSH0),
- {ok,SSH2} = recv_mac_init(SSH1),
- {ok,SSH3} = encrypt_init(SSH2),
- {ok,SSH4} = decrypt_init(SSH3),
- {ok,SSH5} = compress_init(SSH4),
- {ok,SSH6} = decompress_init(SSH5),
- SSH6.
-
-alg_final(SSH0) ->
+ {ok,SSH2} = encrypt_init(SSH1),
+ {ok,SSH3} = compress_init(SSH2),
+ SSH3;
+
+alg_init(rcv, SSH0) ->
+ {ok,SSH1} = recv_mac_init(SSH0),
+ {ok,SSH2} = decrypt_init(SSH1),
+ {ok,SSH3} = decompress_init(SSH2),
+ SSH3.
+
+
+alg_final(snd, SSH0) ->
{ok,SSH1} = send_mac_final(SSH0),
- {ok,SSH2} = recv_mac_final(SSH1),
- {ok,SSH3} = encrypt_final(SSH2),
- {ok,SSH4} = decrypt_final(SSH3),
- {ok,SSH5} = compress_final(SSH4),
- {ok,SSH6} = decompress_final(SSH5),
- SSH6.
+ {ok,SSH2} = encrypt_final(SSH1),
+ {ok,SSH3} = compress_final(SSH2),
+ SSH3;
+
+alg_final(rcv, SSH0) ->
+ {ok,SSH1} = recv_mac_final(SSH0),
+ {ok,SSH2} = decrypt_final(SSH1),
+ {ok,SSH3} = decompress_final(SSH2),
+ SSH3.
+
select_all(CL, SL) when length(CL) + length(SL) < ?MAX_NUM_ALGORITHMS ->
A = CL -- SL, %% algortihms only used by client
@@ -1130,29 +1194,37 @@ payload(<<PacketLen:32, PaddingLen:8, PayloadAndPadding/binary>>) ->
<<Payload:PayloadLen/binary, _/binary>> = PayloadAndPadding,
Payload.
-sign(SigData, Hash, #'DSAPrivateKey'{} = Key) ->
- DerSignature = public_key:sign(SigData, Hash, Key),
+sign(SigData, HashAlg, #'DSAPrivateKey'{} = Key) ->
+ DerSignature = public_key:sign(SigData, HashAlg, Key),
#'Dss-Sig-Value'{r = R, s = S} = public_key:der_decode('Dss-Sig-Value', DerSignature),
<<R:160/big-unsigned-integer, S:160/big-unsigned-integer>>;
-sign(SigData, Hash, Key = #'ECPrivateKey'{}) ->
- DerEncodedSign = public_key:sign(SigData, Hash, Key),
+sign(SigData, HashAlg, Key = #'ECPrivateKey'{}) ->
+ DerEncodedSign = public_key:sign(SigData, HashAlg, Key),
#'ECDSA-Sig-Value'{r=R, s=S} = public_key:der_decode('ECDSA-Sig-Value', DerEncodedSign),
<<?Empint(R),?Empint(S)>>;
-sign(SigData, Hash, Key) ->
- public_key:sign(SigData, Hash, Key).
-
-verify(PlainText, Hash, Sig, {_, #'Dss-Parms'{}} = Key) ->
- <<R:160/big-unsigned-integer, S:160/big-unsigned-integer>> = Sig,
- Signature = public_key:der_encode('Dss-Sig-Value', #'Dss-Sig-Value'{r = R, s = S}),
- public_key:verify(PlainText, Hash, Signature, Key);
-verify(PlainText, Hash, Sig, {#'ECPoint'{},_} = Key) ->
- <<?UINT32(Rlen),R:Rlen/big-signed-integer-unit:8,
- ?UINT32(Slen),S:Slen/big-signed-integer-unit:8>> = Sig,
- Sval = #'ECDSA-Sig-Value'{r=R, s=S},
- DerEncodedSig = public_key:der_encode('ECDSA-Sig-Value',Sval),
- public_key:verify(PlainText, Hash, DerEncodedSig, Key);
-verify(PlainText, Hash, Sig, Key) ->
- public_key:verify(PlainText, Hash, Sig, Key).
+sign(SigData, HashAlg, Key) ->
+ public_key:sign(SigData, HashAlg, Key).
+
+verify(PlainText, HashAlg, Sig, {_, #'Dss-Parms'{}} = Key) ->
+ case Sig of
+ <<R:160/big-unsigned-integer, S:160/big-unsigned-integer>> ->
+ Signature = public_key:der_encode('Dss-Sig-Value', #'Dss-Sig-Value'{r = R, s = S}),
+ public_key:verify(PlainText, HashAlg, Signature, Key);
+ _ ->
+ false
+ end;
+verify(PlainText, HashAlg, Sig, {#'ECPoint'{},_} = Key) ->
+ case Sig of
+ <<?UINT32(Rlen),R:Rlen/big-signed-integer-unit:8,
+ ?UINT32(Slen),S:Slen/big-signed-integer-unit:8>> ->
+ Sval = #'ECDSA-Sig-Value'{r=R, s=S},
+ DerEncodedSig = public_key:der_encode('ECDSA-Sig-Value',Sval),
+ public_key:verify(PlainText, HashAlg, DerEncodedSig, Key);
+ _ ->
+ false
+ end;
+verify(PlainText, HashAlg, Sig, Key) ->
+ public_key:verify(PlainText, HashAlg, Sig, Key).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1659,39 +1731,63 @@ hash(K, H, Ki, N, HashAlg) ->
hash(K, H, <<Ki/binary, Kj/binary>>, N-128, HashAlg).
%%%----------------------------------------------------------------
-kex_h(SSH, Key, E, F, K) ->
- KeyBin = public_key:ssh_encode(Key, ssh2_pubkey),
- L = <<?Estring(SSH#ssh.c_version), ?Estring(SSH#ssh.s_version),
- ?Ebinary(SSH#ssh.c_keyinit), ?Ebinary(SSH#ssh.s_keyinit), ?Ebinary(KeyBin),
- ?Empint(E), ?Empint(F), ?Empint(K)>>,
- crypto:hash(sha((SSH#ssh.algorithms)#alg.kex), L).
-
-kex_h(SSH, Curve, Key, Q_c, Q_s, K) ->
- KeyBin = public_key:ssh_encode(Key, ssh2_pubkey),
- L = <<?Estring(SSH#ssh.c_version), ?Estring(SSH#ssh.s_version),
- ?Ebinary(SSH#ssh.c_keyinit), ?Ebinary(SSH#ssh.s_keyinit), ?Ebinary(KeyBin),
- ?Empint(Q_c), ?Empint(Q_s), ?Empint(K)>>,
- crypto:hash(sha(Curve), L).
-
-kex_h(SSH, Key, Min, NBits, Max, Prime, Gen, E, F, K) ->
- KeyBin = public_key:ssh_encode(Key, ssh2_pubkey),
- L = if Min==-1; Max==-1 ->
- %% flag from 'ssh_msg_kex_dh_gex_request_old'
- %% It was like this before that message was supported,
- %% why?
- <<?Estring(SSH#ssh.c_version), ?Estring(SSH#ssh.s_version),
- ?Ebinary(SSH#ssh.c_keyinit), ?Ebinary(SSH#ssh.s_keyinit), ?Ebinary(KeyBin),
- ?Empint(E), ?Empint(F), ?Empint(K)>>;
- true ->
- <<?Estring(SSH#ssh.c_version), ?Estring(SSH#ssh.s_version),
- ?Ebinary(SSH#ssh.c_keyinit), ?Ebinary(SSH#ssh.s_keyinit), ?Ebinary(KeyBin),
- ?Euint32(Min), ?Euint32(NBits), ?Euint32(Max),
- ?Empint(Prime), ?Empint(Gen), ?Empint(E), ?Empint(F), ?Empint(K)>>
- end,
- crypto:hash(sha((SSH#ssh.algorithms)#alg.kex), L).
-
+kex_hash(SSH, Key, SignAlg, HashAlg, Args) ->
+ crypto:hash(HashAlg, kex_plaintext(SSH,Key,SignAlg,Args)).
+
+kex_plaintext(SSH, Key, SignAlg, Args) ->
+ EncodedKey = public_key:ssh_encode({Key,SignAlg}, ssh2_pubkey),
+ <<?Estring(SSH#ssh.c_version), ?Estring(SSH#ssh.s_version),
+ ?Ebinary(SSH#ssh.c_keyinit), ?Ebinary(SSH#ssh.s_keyinit),
+ ?Ebinary(EncodedKey),
+ (kex_alg_dependent(Args))/binary>>.
+
+kex_alg_dependent({E, F, K}) ->
+ %% diffie-hellman and ec diffie-hellman (with E = Q_c, F = Q_s)
+ <<?Empint(E), ?Empint(F), ?Empint(K)>>;
+
+kex_alg_dependent({-1, _, -1, _, _, E, F, K}) ->
+ %% ssh_msg_kex_dh_gex_request_old
+ <<?Empint(E), ?Empint(F), ?Empint(K)>>;
+
+kex_alg_dependent({Min, NBits, Max, Prime, Gen, E, F, K}) ->
+ %% diffie-hellman group exchange
+ <<?Euint32(Min), ?Euint32(NBits), ?Euint32(Max),
+ ?Empint(Prime), ?Empint(Gen), ?Empint(E), ?Empint(F), ?Empint(K)>>.
+
+%%%----------------------------------------------------------------
+
+valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-512') -> true;
+valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-384') -> true;
+valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-256') -> true;
+valid_key_sha_alg(#'RSAPublicKey'{}, 'ssh-rsa' ) -> true;
+
+valid_key_sha_alg(#'RSAPrivateKey'{}, 'rsa-sha2-512') -> true;
+valid_key_sha_alg(#'RSAPrivateKey'{}, 'rsa-sha2-384') -> true;
+valid_key_sha_alg(#'RSAPrivateKey'{}, 'rsa-sha2-256') -> true;
+valid_key_sha_alg(#'RSAPrivateKey'{}, 'ssh-rsa' ) -> true;
+
+valid_key_sha_alg({_, #'Dss-Parms'{}}, 'ssh-dss') -> true;
+valid_key_sha_alg(#'DSAPrivateKey'{}, 'ssh-dss') -> true;
+
+valid_key_sha_alg({#'ECPoint'{},{namedCurve,OID}}, Alg) -> sha(OID) == sha(Alg);
+valid_key_sha_alg(#'ECPrivateKey'{parameters = {namedCurve,OID}}, Alg) -> sha(OID) == sha(Alg);
+valid_key_sha_alg(_, _) -> false.
+
+
+
+public_algo(#'RSAPublicKey'{}) -> 'ssh-rsa'; % FIXME: Not right with draft-curdle-rsa-sha2
+public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss';
+public_algo({#'ECPoint'{},{namedCurve,OID}}) ->
+ Curve = public_key:oid2ssh_curvename(OID),
+ list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)).
+
+
+
sha('ssh-rsa') -> sha;
+sha('rsa-sha2-256') -> sha256;
+sha('rsa-sha2-384') -> sha384;
+sha('rsa-sha2-512') -> sha512;
sha('ssh-dss') -> sha;
sha('ecdsa-sha2-nistp256') -> sha(secp256r1);
sha('ecdsa-sha2-nistp384') -> sha(secp384r1);
@@ -1711,7 +1807,8 @@ sha(?'secp384r1') -> sha(secp384r1);
sha(?'secp521r1') -> sha(secp521r1);
sha('ecdh-sha2-nistp256') -> sha(secp256r1);
sha('ecdh-sha2-nistp384') -> sha(secp384r1);
-sha('ecdh-sha2-nistp521') -> sha(secp521r1).
+sha('ecdh-sha2-nistp521') -> sha(secp521r1);
+sha(Str) when is_list(Str), length(Str)<50 -> sha(list_to_atom(Str)).
mac_key_bytes('hmac-sha1') -> 20;
diff --git a/lib/ssh/src/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl
index 19b3f5c437..faae6008f2 100644
--- a/lib/ssh/src/ssh_transport.hrl
+++ b/lib/ssh/src/ssh_transport.hrl
@@ -48,6 +48,7 @@
-define(SSH_MSG_DEBUG, 4).
-define(SSH_MSG_SERVICE_REQUEST, 5).
-define(SSH_MSG_SERVICE_ACCEPT, 6).
+-define(SSH_MSG_EXT_INFO, 7).
-define(SSH_MSG_KEXINIT, 20).
-define(SSH_MSG_NEWKEYS, 21).
@@ -88,6 +89,20 @@
name %% string
}).
+-record(ssh_msg_ext_info,
+ {
+ nr_extensions, %% uint32
+
+ %% repeat the following 2 fields "nr-extensions" times:
+ %% string extension-name
+ %% string extension-value
+
+ data %% [{extension-name, %% string
+ %% extension-value}, %% string
+ %% ...
+ %% ]
+ }).
+
-record(ssh_msg_kexinit,
{
cookie, %% random(16)
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl
index 089d191fea..c271ff49ef 100644
--- a/lib/ssh/test/ssh_basic_SUITE.erl
+++ b/lib/ssh/test/ssh_basic_SUITE.erl
@@ -651,6 +651,7 @@ exec_key_differs_fail(Config) when is_list(Config) ->
IO = ssh_test_lib:start_io_server(),
ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir},
+ {recv_ext_info, false},
{preferred_algorithms,[{public_key,['ssh-rsa']}]},
{pref_public_key_algs,['ssh-dss']}]),
receive
@@ -1366,13 +1367,25 @@ new_do_shell(IO, N, Ops=[{Order,Arg}|More]) ->
ct:log("Skip newline ~p",[_X]),
new_do_shell(IO, N, Ops);
- <<Pfx:PfxSize/binary,P1,"> ">> when (P1-$0)==N ->
+ <<P1,"> ">> when (P1-$0)==N ->
+ new_do_shell_prompt(IO, N, Order, Arg, More);
+ <<"(",Pfx:PfxSize/binary,")",P1,"> ">> when (P1-$0)==N ->
+ new_do_shell_prompt(IO, N, Order, Arg, More);
+ <<"('",Pfx:PfxSize/binary,"')",P1,"> ">> when (P1-$0)==N ->
new_do_shell_prompt(IO, N, Order, Arg, More);
- <<Pfx:PfxSize/binary,P1,P2,"> ">> when (P1-$0)*10 + (P2-$0) == N ->
+ <<P1,P2,"> ">> when (P1-$0)*10 + (P2-$0) == N ->
+ new_do_shell_prompt(IO, N, Order, Arg, More);
+ <<"(",Pfx:PfxSize/binary,")",P1,P2,"> ">> when (P1-$0)*10 + (P2-$0) == N ->
+ new_do_shell_prompt(IO, N, Order, Arg, More);
+ <<"('",Pfx:PfxSize/binary,"')",P1,P2,"> ">> when (P1-$0)*10 + (P2-$0) == N ->
new_do_shell_prompt(IO, N, Order, Arg, More);
- <<Pfx:PfxSize/binary,P1,P2,P3,"> ">> when (P1-$0)*100 + (P2-$0)*10 + (P3-$0) == N ->
+ <<P1,P2,P3,"> ">> when (P1-$0)*100 + (P2-$0)*10 + (P3-$0) == N ->
+ new_do_shell_prompt(IO, N, Order, Arg, More);
+ <<"(",Pfx:PfxSize/binary,")",P1,P2,P3,"> ">> when (P1-$0)*100 + (P2-$0)*10 + (P3-$0) == N ->
+ new_do_shell_prompt(IO, N, Order, Arg, More);
+ <<"('",Pfx:PfxSize/binary,"')",P1,P2,P3,"> ">> when (P1-$0)*100 + (P2-$0)*10 + (P3-$0) == N ->
new_do_shell_prompt(IO, N, Order, Arg, More);
Err when element(1,Err)==error ->
@@ -1408,7 +1421,7 @@ prompt_prefix() ->
case node() of
nonode@nohost -> <<>>;
Node -> list_to_binary(
- lists:concat(["(",Node,")"]))
+ atom_to_list(Node))
end.
diff --git a/lib/ssh/test/ssh_protocol_SUITE.erl b/lib/ssh/test/ssh_protocol_SUITE.erl
index 2c4fa8be88..9e7d1a5fa3 100644
--- a/lib/ssh/test/ssh_protocol_SUITE.erl
+++ b/lib/ssh/test/ssh_protocol_SUITE.erl
@@ -752,6 +752,7 @@ connect_and_kex(Config, InitialState) ->
{cipher,?DEFAULT_CIPHERS}
]},
{silently_accept_hosts, true},
+ {recv_ext_info, false},
{user_dir, user_dir(Config)},
{user_interaction, false}]},
receive_hello,
diff --git a/lib/ssh/test/ssh_relay.erl b/lib/ssh/test/ssh_relay.erl
index 1e3810e9d4..763130358b 100644
--- a/lib/ssh/test/ssh_relay.erl
+++ b/lib/ssh/test/ssh_relay.erl
@@ -242,11 +242,11 @@ handle_info(stop, State) ->
{stop, normal, State};
handle_info({'DOWN', _Ref, _process, LPid, Reason}, S) when S#state.lpid == LPid ->
- io:format("Acceptor has finished: ~p~n", [Reason]),
+ io:format("Acceptor in ~p has finished: ~p~n", [?MODULE,Reason]),
{noreply, S};
handle_info(_Info, State) ->
- io:format("Unhandled info: ~p~n", [_Info]),
+ io:format("~p:~p Unhandled info: ~p~n", [?MODULE,?LINE,_Info]),
{noreply, State}.
%%--------------------------------------------------------------------
diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl
index 6186d44890..ded47ca4f6 100644
--- a/lib/ssh/test/ssh_test_lib.erl
+++ b/lib/ssh/test/ssh_test_lib.erl
@@ -39,8 +39,9 @@ connect(Port, Options) when is_integer(Port) ->
connect(any, Port, Options) ->
connect(hostname(), Port, Options);
connect(Host, Port, Options) ->
- ct:log("~p:~p Calling ssh:connect(~p, ~p, ~p)",[?MODULE,?LINE,Host, Port, Options]),
- {ok, ConnectionRef} = ssh:connect(Host, Port, Options),
+ R = ssh:connect(Host, Port, Options),
+ ct:log("~p:~p ssh:connect(~p, ~p, ~p)~n -> ~p",[?MODULE,?LINE,Host, Port, Options, R]),
+ {ok, ConnectionRef} = R,
ConnectionRef.
%%%----------------------------------------------------------------
@@ -858,8 +859,9 @@ get_kex_init(Conn) ->
get_kex_init(Conn, Ref, TRef) ->
%% First, validate the key exchange is complete (StateName == connected)
- case sys:get_state(Conn) of
- {{connected,_}, S} ->
+ {State, S} = sys:get_state(Conn),
+ case expected_state(State) of
+ true ->
timer:cancel(TRef),
%% Next, walk through the elements of the #state record looking
%% for the #ssh_msg_kexinit record. This method is robust against
@@ -873,8 +875,8 @@ get_kex_init(Conn, Ref, TRef) ->
KexInit
end;
- {OtherState, S} ->
- ct:log("Not in 'connected' state: ~p",[OtherState]),
+ false ->
+ ct:log("Not in 'connected' state: ~p",[State]),
receive
{reneg_timeout,Ref} ->
ct:log("S = ~p", [S]),
@@ -886,6 +888,10 @@ get_kex_init(Conn, Ref, TRef) ->
end
end.
+expected_state({ext_info,_,_}) -> true;
+expected_state({connected,_}) -> true;
+expected_state(_) -> false.
+
%%%----------------------------------------------------------------
%%% Return a string with N random characters
%%%
diff --git a/lib/ssh/test/ssh_to_openssh_SUITE.erl b/lib/ssh/test/ssh_to_openssh_SUITE.erl
index 35e3ee3edf..6b3055ebab 100644
--- a/lib/ssh/test/ssh_to_openssh_SUITE.erl
+++ b/lib/ssh/test/ssh_to_openssh_SUITE.erl
@@ -153,7 +153,7 @@ erlang_shell_client_openssh_server(Config) when is_list(Config) ->
IO = ssh_test_lib:start_io_server(),
Shell = ssh_test_lib:start_shell(?SSH_DEFAULT_PORT, IO),
IO ! {input, self(), "echo Hej\n"},
- receive_data("Hej"),
+ receive_data("Hej", undefined),
IO ! {input, self(), "exit\n"},
receive_logout(),
receive_normal_exit(Shell).
@@ -451,7 +451,6 @@ erlang_server_openssh_client_renegotiate(Config) ->
%%--------------------------------------------------------------------
erlang_client_openssh_server_renegotiate(_Config) ->
process_flag(trap_exit, true),
-
IO = ssh_test_lib:start_io_server(),
Ref = make_ref(),
Parent = self(),
@@ -487,11 +486,11 @@ erlang_client_openssh_server_renegotiate(_Config) ->
ct:fail("Error=~p",[Error]);
{ok, Ref, ConnectionRef} ->
IO ! {input, self(), "echo Hej1\n"},
- receive_data("Hej1"),
+ receive_data("Hej1", ConnectionRef),
Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
ssh_connection_handler:renegotiate(ConnectionRef),
IO ! {input, self(), "echo Hej2\n"},
- receive_data("Hej2"),
+ receive_data("Hej2", ConnectionRef),
Kex2 = ssh_test_lib:get_kex_init(ConnectionRef),
IO ! {input, self(), "exit\n"},
receive_logout(),
@@ -554,23 +553,29 @@ erlang_client_openssh_server_nonexistent_subsystem(Config) when is_list(Config)
%%--------------------------------------------------------------------
%%% Internal functions -----------------------------------------------
%%--------------------------------------------------------------------
-receive_data(Data) ->
+receive_data(Data, Conn) ->
receive
Info when is_binary(Info) ->
Lines = string:tokens(binary_to_list(Info), "\r\n "),
case lists:member(Data, Lines) of
true ->
- ct:log("Expected result found in lines: ~p~n", [Lines]),
+ ct:log("Expected result ~p found in lines: ~p~n", [Data,Lines]),
ok;
false ->
ct:log("Extra info: ~p~n", [Info]),
- receive_data(Data)
+ receive_data(Data, Conn)
end;
Other ->
ct:log("Unexpected: ~p",[Other]),
- receive_data(Data)
- after
- 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ receive_data(Data, Conn)
+ after
+ 30000 ->
+ {State, _} = case Conn of
+ undefined -> {'??','??'};
+ _ -> sys:get_state(Conn)
+ end,
+ ct:log("timeout ~p:~p~nExpect ~p~nState = ~p",[?MODULE,?LINE,Data,State]),
+ ct:fail("timeout ~p:~p",[?MODULE,?LINE])
end.
receive_logout() ->
diff --git a/lib/ssh/test/ssh_trpt_test_lib.erl b/lib/ssh/test/ssh_trpt_test_lib.erl
index e1f4c65300..781889ddd1 100644
--- a/lib/ssh/test/ssh_trpt_test_lib.erl
+++ b/lib/ssh/test/ssh_trpt_test_lib.erl
@@ -397,6 +397,12 @@ send(S0, {special,Msg,PacketFun}) when is_tuple(Msg),
send_bytes(Packet, S#s{ssh = C, %%inc_send_seq_num(C),
return_value = Msg});
+send(S0, #ssh_msg_newkeys{} = Msg) ->
+ S = opt(print_messages, S0,
+ fun(X) when X==true;X==detail -> {"Send~n~s~n",[format_msg(Msg)]} end),
+ {ok, Packet, C} = ssh_transport:new_keys_message(S#s.ssh),
+ send_bytes(Packet, S#s{ssh = C});
+
send(S0, Msg) when is_tuple(Msg) ->
S = opt(print_messages, S0,
fun(X) when X==true;X==detail -> {"Send~n~s~n",[format_msg(Msg)]} end),
@@ -455,7 +461,10 @@ recv(S0 = #s{}) ->
};
#ssh_msg_kexdh_reply{} ->
{ok, _NewKeys, C} = ssh_transport:handle_kexdh_reply(PeerMsg, S#s.ssh),
- S#s{ssh=C#ssh{send_sequence=S#s.ssh#ssh.send_sequence}}; % Back the number
+ S#s{ssh = (S#s.ssh)#ssh{shared_secret = C#ssh.shared_secret,
+ exchanged_hash = C#ssh.exchanged_hash,
+ session_id = C#ssh.session_id}};
+ %%%S#s{ssh=C#ssh{send_sequence=S#s.ssh#ssh.send_sequence}}; % Back the number
#ssh_msg_newkeys{} ->
{ok, C} = ssh_transport:handle_new_keys(PeerMsg, S#s.ssh),
S#s{ssh=C};
diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl
index de6b6e400f..bc2097c021 100644
--- a/lib/ssl/src/dtls_record.erl
+++ b/lib/ssl/src/dtls_record.erl
@@ -534,8 +534,7 @@ calc_mac_hash(Type, Version, #{mac_secret := MacSecret,
security_parameters := #security_parameters{mac_algorithm = MacAlg}},
Epoch, SeqNo, Fragment) ->
Length = erlang:iolist_size(Fragment),
- NewSeq = (Epoch bsl 48) + SeqNo,
- mac_hash(Version, MacAlg, MacSecret, NewSeq, Type,
+ mac_hash(Version, MacAlg, MacSecret, Epoch, SeqNo, Type,
Length, Fragment).
highest_protocol_version() ->
@@ -548,9 +547,11 @@ sufficient_dtlsv1_2_crypto_support() ->
CryptoSupport = crypto:supports(),
proplists:get_bool(sha256, proplists:get_value(hashs, CryptoSupport)).
-mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) ->
- dtls_v1:mac_hash(Version, MacAlg, MacSecret, SeqNo, Type,
- Length, Fragment).
-
+mac_hash({Major, Minor}, MacAlg, MacSecret, Epoch, SeqNo, Type, Length, Fragment) ->
+ Value = [<<?UINT16(Epoch), ?UINT48(SeqNo), ?BYTE(Type),
+ ?BYTE(Major), ?BYTE(Minor), ?UINT16(Length)>>,
+ Fragment],
+ dtls_v1:hmac_hash(MacAlg, MacSecret, Value).
+
calc_aad(Type, {MajVer, MinVer}, Epoch, SeqNo) ->
<<?UINT16(Epoch), ?UINT48(SeqNo), ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>.
diff --git a/lib/ssl/src/dtls_v1.erl b/lib/ssl/src/dtls_v1.erl
index 4aaf8baa6c..7f7223cde7 100644
--- a/lib/ssl/src/dtls_v1.erl
+++ b/lib/ssl/src/dtls_v1.erl
@@ -21,7 +21,7 @@
-include("ssl_cipher.hrl").
--export([suites/1, all_suites/1, mac_hash/7, ecc_curves/1,
+-export([suites/1, all_suites/1, hmac_hash/3, ecc_curves/1,
corresponding_tls_version/1, corresponding_dtls_version/1,
cookie_secret/0, cookie_timeout/0]).
@@ -40,9 +40,8 @@ all_suites(Version) ->
end,
ssl_cipher:all_suites(corresponding_tls_version(Version))).
-mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) ->
- tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version,
- Length, Fragment).
+hmac_hash(MacAlg, MacSecret, Value) ->
+ tls_v1:hmac_hash(MacAlg, MacSecret, Value).
ecc_curves({_Major, Minor}) ->
tls_v1:ecc_curves(corresponding_minor_tls_version(Minor)).
diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl
index f52ee06e71..bc487fdca4 100644
--- a/lib/ssl/src/tls_v1.erl
+++ b/lib/ssl/src/tls_v1.erl
@@ -29,7 +29,7 @@
-include("ssl_internal.hrl").
-include("ssl_record.hrl").
--export([master_secret/4, finished/5, certificate_verify/3, mac_hash/7,
+-export([master_secret/4, finished/5, certificate_verify/3, mac_hash/7, hmac_hash/3,
setup_keys/8, suites/1, prf/5,
ecc_curves/1, ecc_curves/2, oid_to_enum/1, enum_to_oid/1,
default_signature_algs/1, signature_algs/2]).
@@ -221,11 +221,7 @@ suites(Minor) when Minor == 1; Minor == 2 ->
?TLS_RSA_WITH_3DES_EDE_CBC_SHA
];
suites(3) ->
- [
- ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
- ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
-
- ?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+ [?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
@@ -234,7 +230,10 @@ suites(3) ->
?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,
?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,
+ ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
+ ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+
?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384,
?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
diff --git a/lib/stdlib/doc/src/gen_fsm.xml b/lib/stdlib/doc/src/gen_fsm.xml
index e70f347479..7187630b43 100644
--- a/lib/stdlib/doc/src/gen_fsm.xml
+++ b/lib/stdlib/doc/src/gen_fsm.xml
@@ -29,7 +29,7 @@
<rev></rev>
</header>
<module>gen_fsm</module>
- <modulesummary>Deprecated and replaced by <seealso marker="gen_statem"><c>gen_statem</c></seealso> </modulesummary>
+ <modulesummary>Deprecated and replaced by gen_statem </modulesummary>
<description>
<p> Deprecated and replaced by <seealso marker="gen_statem"><c>gen_statem</c></seealso> </p>
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index 18089a8191..17a3a3c83c 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2016-2017</year>
+ <year>2016</year><year>2017</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -67,13 +67,16 @@
It has the same features and adds some really useful:
</p>
<list type="bulleted">
- <item>State code is gathered.</item>
- <item>The state can be any term.</item>
- <item>Events can be postponed.</item>
- <item>Events can be self-generated.</item>
- <item>Automatic state enter code can be called.</item>
- <item>A reply can be sent from a later state.</item>
- <item>There can be multiple <c>sys</c> traceable replies.</item>
+ <item>Gathered state code.</item>
+ <item>Arbitrary term state.</item>
+ <item>Event postponing.</item>
+ <item>Self-generated events.</item>
+ <item>State time-out.</item>
+ <item>Multiple generic named time-outs.</item>
+ <item>Absolute time-out time.</item>
+ <item>Automatic state enter calls.</item>
+ <item>Reply from other state than the request.</item>
+ <item>Multiple <c>sys</c> traceable replies.</item>
</list>
<p>
The callback model(s) for <c>gen_statem</c> differs from
@@ -148,7 +151,7 @@ erlang:'!' -----> Module:StateName/3
This gathers all code for a specific state
in one function as the <c>gen_statem</c> engine
branches depending on state name.
- Notice the fact that there is a mandatory callback function
+ Note the fact that the callback function
<seealso marker="#Module:terminate/3"><c>Module:terminate/3</c></seealso>
makes the state name <c>terminate</c> unusable in this mode.
</p>
@@ -533,10 +536,12 @@ handle_event(_, _, State, Data) ->
originate from the corresponding API functions.
For calls, the event contains whom to reply to.
Type <c>info</c> originates from regular process messages sent
- to the <c>gen_statem</c>. Also, the state machine
- implementation can generate events of types
- <c>timeout</c>, <c>state_timeout</c>,
- and <c>internal</c> to itself.
+ to the <c>gen_statem</c>. The state machine
+ implementation can, in addition to the above,
+ generate
+ <seealso marker="#type-event_type"><c>events of types</c></seealso>
+ <c>timeout</c>, <c>{timeout,<anno>Name</anno>}</c>,
+ <c>state_timeout</c>, and <c>internal</c> to itself.
</p>
</desc>
</datatype>
@@ -703,13 +708,14 @@ handle_event(_, _, State, Data) ->
</item>
<item>
<p>
- Timeout timers
- <seealso marker="#type-state_timeout"><c>state_timeout()</c></seealso>
+ Time-out timers
+ <seealso marker="#type-event_timeout"><c>event_timeout()</c></seealso>,
+ <seealso marker="#type-generic_timeout"><c>generic_timeout()</c></seealso>
and
- <seealso marker="#type-event_timeout"><c>event_timeout()</c></seealso>
+ <seealso marker="#type-state_timeout"><c>state_timeout()</c></seealso>
are handled. Time-outs with zero time are guaranteed to be
delivered to the state machine before any external
- not yet received event so if there is such a timeout requested,
+ not yet received event so if there is such a time-out requested,
the corresponding time-out zero event is enqueued as
the newest event.
</p>
@@ -787,49 +793,102 @@ handle_event(_, _, State, Data) ->
<name name="event_timeout"/>
<desc>
<p>
- Generates an event of
+ Starts a timer set by
+ <seealso marker="#type-enter_action"><c>enter_action()</c></seealso>
+ <c>timeout</c>.
+ When the timer expires an event of
<seealso marker="#type-event_type"><c>event_type()</c></seealso>
- <c>timeout</c>
- after this time (in milliseconds) unless another
- event arrives or has arrived
- in which case this time-out is cancelled.
+ <c>timeout</c> will be generated.
+ See
+ <seealso marker="erts:erlang#start_timer/4"><c>erlang:start_timer/4</c></seealso>
+ for how <c>Time</c> and
+ <seealso marker="#type-timeout_option"><c>Options</c></seealso>
+ are interpreted. Future <c>erlang:start_timer/4</c> <c>Options</c>
+ will not necessarily be supported.
+ </p>
+ <p>
+ Any event that arrives cancels this time-out.
Note that a retried or inserted event counts as arrived.
So does a state time-out zero event, if it was generated
- before this timer is requested.
+ before this time-out is requested.
</p>
<p>
- If the value is <c>infinity</c>, no timer is started, as
- it never would trigger anyway.
+ If <c>Time</c> is <c>infinity</c>,
+ no timer is started, as it never would expire anyway.
</p>
<p>
- If the value is <c>0</c> no timer is actually started,
+ If <c>Time</c> is relative and <c>0</c>
+ no timer is actually started,
instead the the time-out event is enqueued to ensure
that it gets processed before any not yet
received external event.
</p>
<p>
- Note that it is not possible or needed to cancel this time-out,
+ Note that it is not possible nor needed to cancel this time-out,
as it is cancelled automatically by any other event.
</p>
</desc>
</datatype>
<datatype>
+ <name name="generic_timeout"/>
+ <desc>
+ <p>
+ Starts a timer set by
+ <seealso marker="#type-enter_action"><c>enter_action()</c></seealso>
+ <c>{timeout,Name}</c>.
+ When the timer expires an event of
+ <seealso marker="#type-event_type"><c>event_type()</c></seealso>
+ <c>{timeout,Name}</c> will be generated.
+ See
+ <seealso marker="erts:erlang#start_timer/4"><c>erlang:start_timer/4</c></seealso>
+ for how <c>Time</c> and
+ <seealso marker="#type-timeout_option"><c>Options</c></seealso>
+ are interpreted. Future <c>erlang:start_timer/4</c> <c>Options</c>
+ will not necessarily be supported.
+ </p>
+ <p>
+ If <c>Time</c> is <c>infinity</c>,
+ no timer is started, as it never would expire anyway.
+ </p>
+ <p>
+ If <c>Time</c> is relative and <c>0</c>
+ no timer is actually started,
+ instead the the time-out event is enqueued to ensure
+ that it gets processed before any not yet
+ received external event.
+ </p>
+ <p>
+ Setting a timer with the same <c>Name</c> while it is running
+ will restart it with the new time-out value.
+ Therefore it is possible to cancel
+ a specific time-out by setting it to <c>infinity</c>.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
<name name="state_timeout"/>
<desc>
<p>
- Generates an event of
+ Starts a timer set by
+ <seealso marker="#type-enter_action"><c>enter_action()</c></seealso>
+ <c>state_timeout</c>.
+ When the timer expires an event of
<seealso marker="#type-event_type"><c>event_type()</c></seealso>
- <c>state_timeout</c>
- after this time (in milliseconds) unless the <c>gen_statem</c>
- changes states (<c>NewState =/= OldState</c>)
- which case this time-out is cancelled.
+ <c>state_timeout</c> will be generated.
+ See
+ <seealso marker="erts:erlang#start_timer/4"><c>erlang:start_timer/4</c></seealso>
+ for how <c>Time</c> and
+ <seealso marker="#type-timeout_option"><c>Options</c></seealso>
+ are interpreted. Future <c>erlang:start_timer/4</c> <c>Options</c>
+ will not necessarily be supported.
</p>
<p>
- If the value is <c>infinity</c>, no timer is started, as
- it never would trigger anyway.
+ If <c>Time</c> is <c>infinity</c>,
+ no timer is started, as it never would expire anyway.
</p>
<p>
- If the value is <c>0</c> no timer is actually started,
+ If <c>Time</c> is relative and <c>0</c>
+ no timer is actually started,
instead the the time-out event is enqueued to ensure
that it gets processed before any not yet
received external event.
@@ -842,6 +901,20 @@ handle_event(_, _, State, Data) ->
</desc>
</datatype>
<datatype>
+ <name name="timeout_option"/>
+ <desc>
+ <p>
+ If <c>Abs</c> is <c>true</c> an absolute timer is started,
+ and if it is <c>false</c> a relative, which is the default.
+ See
+ <seealso marker="erts:erlang#start_timer/4"><c>erlang:start_timer/4</c></seealso>
+ for details.
+ </p>
+ <p>
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
<name name="action"/>
<desc>
<p>
@@ -955,7 +1028,21 @@ handle_event(_, _, State, Data) ->
Sets the
<seealso marker="#type-transition_option"><c>transition_option()</c></seealso>
<seealso marker="#type-event_timeout"><c>event_timeout()</c></seealso>
- to <c><anno>Time</anno></c> with <c><anno>EventContent</anno></c>.
+ to <c><anno>Time</anno></c> with <c><anno>EventContent</anno></c>
+ and time-out options
+ <seealso marker="#type-timeout_option"><c><anno>Options</anno></c></seealso>.
+ </p>
+ </item>
+ <tag><c>{timeout,<anno>Name</anno>}</c></tag>
+ <item>
+ <p>
+ Sets the
+ <seealso marker="#type-transition_option"><c>transition_option()</c></seealso>
+ <seealso marker="#type-generic_timeout"><c>generic_timeout()</c></seealso>
+ to <c><anno>Time</anno></c> for <c><anno>Name</anno></c>
+ with <c><anno>EventContent</anno></c>
+ and time-out options
+ <seealso marker="#type-timeout_option"><c><anno>Options</anno></c></seealso>.
</p>
</item>
<tag><c>state_timeout</c></tag>
@@ -964,7 +1051,9 @@ handle_event(_, _, State, Data) ->
Sets the
<seealso marker="#type-transition_option"><c>transition_option()</c></seealso>
<seealso marker="#type-state_timeout"><c>state_timeout()</c></seealso>
- to <c><anno>Time</anno></c> with <c><anno>EventContent</anno></c>.
+ to <c><anno>Time</anno></c> with <c><anno>EventContent</anno></c>
+ and time-out options
+ <seealso marker="#type-timeout_option"><c><anno>Options</anno></c></seealso>.
</p>
</item>
</taglist>
@@ -1236,7 +1325,7 @@ handle_event(_, _, State, Data) ->
to avoid that the calling process dies when the call
times out, you will have to be prepared to handle
a late reply.
- So why not just allow the calling process to die?
+ So why not just let the calling process die?
</p>
</note>
<p>
@@ -1645,6 +1734,16 @@ handle_event(_, _, State, Data) ->
<v>Reason = term()</v>
</type>
<desc>
+ <note>
+ <p>
+ This callback is optional, so callback modules need not export it.
+ If a release upgrade/downgrade with
+ <c>Change={advanced,Extra}</c>
+ specified in the <c>.appup</c> file is made
+ when <c>code_change/4</c> is not implemented
+ the process will crash with exit reason <c>undef</c>.
+ </p>
+ </note>
<p>
This function is called by a <c>gen_statem</c> when it is to
update its internal state during a release upgrade/downgrade,
@@ -1705,7 +1804,7 @@ handle_event(_, _, State, Data) ->
<func>
<name>Module:init(Args) -> Result(StateType)</name>
<fsummary>
- Optional function for initializing process and internal state.
+ Initializing process and internal state.
</fsummary>
<type>
<v>Args = term()</v>
@@ -1721,7 +1820,7 @@ handle_event(_, _, State, Data) ->
<seealso marker="#start_link/3"><c>start_link/3,4</c></seealso>
or
<seealso marker="#start/3"><c>start/3,4</c></seealso>,
- this optional function is called by the new process to initialize
+ this function is called by the new process to initialize
the implementation state and server data.
</p>
<p>
@@ -1730,13 +1829,16 @@ handle_event(_, _, State, Data) ->
</p>
<note>
<p>
- This callback is optional, so a callback module does not need
- to export it, but most do. If this function is not exported,
- the <c>gen_statem</c> should be started through
+ Note that if the <c>gen_statem</c> is started trough
<seealso marker="proc_lib"><c>proc_lib</c></seealso>
and
- <seealso marker="#enter_loop/4"><c>enter_loop/4-6</c></seealso>.
+ <seealso marker="#enter_loop/4"><c>enter_loop/4-6</c></seealso>,
+ this callback will never be called.
+ Since this callback is not optional it can
+ in that case be implemented as:
</p>
+ <pre>
+init(Args) -> erlang:error(not_implemented, [Args]).</pre>
</note>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/shell.xml b/lib/stdlib/doc/src/shell.xml
index ab62c2fcdd..2593d3690b 100644
--- a/lib/stdlib/doc/src/shell.xml
+++ b/lib/stdlib/doc/src/shell.xml
@@ -569,7 +569,7 @@ Hello Number: 3378
<pre>
42> <input>E = ets:new(t, []).</input>
-17</pre>
+#Ref&lt;0.1662103692.2407923716.214192></pre>
<p>Command 42 creates an ETS table.</p>
@@ -602,7 +602,7 @@ false</pre>
<pre>
47> <input>E = ets:new(t, []).</input>
-18
+#Ref&lt;0.1662103692.2407923716.214197>
48> <input>ets:insert({d,1,2}).</input>
* exception error: undefined function ets:insert/1</pre>
@@ -617,10 +617,23 @@ true</pre>
<p>Command 49 successfully inserts the tuple into the ETS table.</p>
<pre>
-50> <input>halt().</input>
+50> <input>ets:insert(#Ref&lt;0.1662103692.2407923716.214197>, {e,3,4}).</input>
+true</pre>
+
+ <p>Command 50 inserts another tuple into the ETS table. This time
+ the first argument is the table identifier itself. The shell can
+ parse commands with pids (<c>&lt;0.60.0></c>), ports
+ (<c>#Port&lt;0.536></c>), references
+ (<c>#Ref&lt;0.1662103692.2407792644.214210></c>), and external
+ functions (<c>#Fun&lt;a.b.1></c>), but the command fails unless
+ the corresponding pid, port, reference, or function can be created
+ in the running system.</p>
+
+ <pre>
+51> <input>halt().</input>
strider 2></pre>
- <p>Command 50 exits the Erlang runtime system.</p>
+ <p>Command 51 exits the Erlang runtime system.</p>
</section>
<section>
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index cacc932ec4..6f566b8beb 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -78,8 +78,9 @@
-type data() :: term().
-type event_type() ::
- {'call',From :: from()} | 'cast' |
- 'info' | 'timeout' | 'state_timeout' | 'internal'.
+ {'call',From :: from()} | 'cast' | 'info' |
+ 'timeout' | {'timeout', Name :: term()} | 'state_timeout' |
+ 'internal'.
-type callback_mode_result() ::
callback_mode() | [callback_mode() | state_enter()].
@@ -88,7 +89,7 @@
-type transition_option() ::
postpone() | hibernate() |
- event_timeout() | state_timeout().
+ event_timeout() | generic_timeout() | state_timeout().
-type postpone() ::
%% If 'true' postpone the current event
%% and retry it when the state changes (=/=)
@@ -97,13 +98,17 @@
%% If 'true' hibernate the server instead of going into receive
boolean().
-type event_timeout() ::
- %% Generate a ('timeout', EventContent, ...) event after Time
+ %% Generate a ('timeout', EventContent, ...) event
%% unless some other event is delivered
- Time :: timeout().
+ Time :: timeout() | integer().
+-type generic_timeout() ::
+ %% Generate a ({'timeout',Name}, EventContent, ...) event
+ Time :: timeout() | integer().
-type state_timeout() ::
- %% Generate a ('state_timeout', EventContent, ...) event after Time
+ %% Generate a ('state_timeout', EventContent, ...) event
%% unless the state is changed
- Time :: timeout().
+ Time :: timeout() | integer().
+-type timeout_option() :: {abs,Abs :: boolean()}.
-type action() ::
%% During a state change:
@@ -137,8 +142,24 @@
(Timeout :: event_timeout()) | % {timeout,Timeout}
{'timeout', % Set the event_timeout option
Time :: event_timeout(), EventContent :: term()} |
+ {'timeout', % Set the event_timeout option
+ Time :: event_timeout(),
+ EventContent :: term(),
+ Options :: (timeout_option() | [timeout_option()])} |
+ %%
+ {{'timeout', Name :: term()}, % Set the generic_timeout option
+ Time :: generic_timeout(), EventContent :: term()} |
+ {{'timeout', Name :: term()}, % Set the generic_timeout option
+ Time :: generic_timeout(),
+ EventContent :: term(),
+ Options :: (timeout_option() | [timeout_option()])} |
+ %%
{'state_timeout', % Set the state_timeout option
Time :: state_timeout(), EventContent :: term()} |
+ {'state_timeout', % Set the state_timeout option
+ Time :: state_timeout(),
+ EventContent :: term(),
+ Options :: (timeout_option() | [timeout_option()])} |
%%
reply_action().
-type reply_action() ::
@@ -287,8 +308,7 @@
StatusOption :: 'normal' | 'terminate'.
-optional_callbacks(
- [init/1, % One may use enter_loop/5,6,7 instead
- format_status/2, % Has got a default implementation
+ [format_status/2, % Has got a default implementation
terminate/3, % Has got a default implementation
code_change/4, % Only needed by advanced soft upgrade
%%
@@ -303,37 +323,26 @@
%% Type validation functions
callback_mode(CallbackMode) ->
case CallbackMode of
- state_functions ->
- true;
- handle_event_function ->
- true;
- _ ->
- false
+ state_functions -> true;
+ handle_event_function -> true;
+ _ -> false
end.
%%
-from({Pid,_}) when is_pid(Pid) ->
- true;
-from(_) ->
- false.
+from({Pid,_}) when is_pid(Pid) -> true;
+from(_) -> false.
%%
event_type({call,From}) ->
from(From);
event_type(Type) ->
case Type of
- {call,From} ->
- from(From);
- cast ->
- true;
- info ->
- true;
- timeout ->
- true;
- state_timeout ->
- true;
- internal ->
- true;
- _ ->
- false
+ {call,From} -> from(From);
+ cast -> true;
+ info -> true;
+ timeout -> true;
+ state_timeout -> true;
+ internal -> true;
+ {timeout,_} -> true;
+ _ -> false
end.
@@ -1313,7 +1322,7 @@ parse_enter_actions(Debug, S, State, Actions, Hibernate, TimeoutsR) ->
parse_actions(Debug, S, State, Actions) ->
Hibernate = false,
- TimeoutsR = [{timeout,infinity,infinity}], %% Will cancel event timer
+ TimeoutsR = [infinity], %% Will cancel event timer
Postpone = false,
NextEventsR = [],
parse_actions(
@@ -1379,7 +1388,11 @@ parse_actions(
?STACKTRACE()}
end;
%%
- {state_timeout,_,_} = Timeout ->
+ {{timeout,_},_,_} = Timeout ->
+ parse_actions_timeout(
+ Debug, S, State, Actions,
+ Hibernate, TimeoutsR, Postpone, NextEventsR, Timeout);
+ {{timeout,_},_,_,_} = Timeout ->
parse_actions_timeout(
Debug, S, State, Actions,
Hibernate, TimeoutsR, Postpone, NextEventsR, Timeout);
@@ -1387,6 +1400,18 @@ parse_actions(
parse_actions_timeout(
Debug, S, State, Actions,
Hibernate, TimeoutsR, Postpone, NextEventsR, Timeout);
+ {timeout,_,_,_} = Timeout ->
+ parse_actions_timeout(
+ Debug, S, State, Actions,
+ Hibernate, TimeoutsR, Postpone, NextEventsR, Timeout);
+ {state_timeout,_,_} = Timeout ->
+ parse_actions_timeout(
+ Debug, S, State, Actions,
+ Hibernate, TimeoutsR, Postpone, NextEventsR, Timeout);
+ {state_timeout,_,_,_} = Timeout ->
+ parse_actions_timeout(
+ Debug, S, State, Actions,
+ Hibernate, TimeoutsR, Postpone, NextEventsR, Timeout);
Time ->
parse_actions_timeout(
Debug, S, State, Actions,
@@ -1396,26 +1421,64 @@ parse_actions(
parse_actions_timeout(
Debug, S, State, Actions,
Hibernate, TimeoutsR, Postpone, NextEventsR, Timeout) ->
- Time =
- case Timeout of
- {_,T,_} -> T;
- T -> T
- end,
- case validate_time(Time) of
- true ->
- parse_actions(
- Debug, S, State, Actions,
- Hibernate, [Timeout|TimeoutsR],
- Postpone, NextEventsR);
- false ->
- {error,
- {bad_action_from_state_function,Timeout},
- ?STACKTRACE()}
+ case Timeout of
+ {TimerType,Time,TimerMsg,TimerOpts} ->
+ case validate_timer_args(Time, listify(TimerOpts)) of
+ true ->
+ parse_actions(
+ Debug, S, State, Actions,
+ Hibernate, [Timeout|TimeoutsR],
+ Postpone, NextEventsR);
+ false ->
+ NewTimeout = {TimerType,Time,TimerMsg},
+ parse_actions(
+ Debug, S, State, Actions,
+ Hibernate, [NewTimeout|TimeoutsR],
+ Postpone, NextEventsR);
+ error ->
+ {error,
+ {bad_action_from_state_function,Timeout},
+ ?STACKTRACE()}
+ end;
+ {_,Time,_} ->
+ case validate_timer_args(Time, []) of
+ false ->
+ parse_actions(
+ Debug, S, State, Actions,
+ Hibernate, [Timeout|TimeoutsR],
+ Postpone, NextEventsR);
+ error ->
+ {error,
+ {bad_action_from_state_function,Timeout},
+ ?STACKTRACE()}
+ end;
+ Time ->
+ case validate_timer_args(Time, []) of
+ false ->
+ parse_actions(
+ Debug, S, State, Actions,
+ Hibernate, [Timeout|TimeoutsR],
+ Postpone, NextEventsR);
+ error ->
+ {error,
+ {bad_action_from_state_function,Timeout},
+ ?STACKTRACE()}
+ end
end.
-validate_time(Time) when is_integer(Time), Time >= 0 -> true;
-validate_time(infinity) -> true;
-validate_time(_) -> false.
+validate_timer_args(Time, Opts) ->
+ validate_timer_args(Time, Opts, false).
+%%
+validate_timer_args(Time, [], true) when is_integer(Time) ->
+ true;
+validate_timer_args(Time, [], false) when is_integer(Time), Time >= 0 ->
+ false;
+validate_timer_args(infinity, [], Abs) ->
+ Abs;
+validate_timer_args(Time, [{abs,Abs}|Opts], _) when is_boolean(Abs) ->
+ validate_timer_args(Time, Opts, Abs);
+validate_timer_args(_, [_|_], _) ->
+ error.
%% Stop and start timers as well as create timeout zero events
%% and pending event timer
@@ -1431,22 +1494,39 @@ parse_timers(
TimerRefs, TimerTypes, CancelTimers, [Timeout|TimeoutsR],
Seen, TimeoutEvents) ->
case Timeout of
+ {TimerType,Time,TimerMsg,TimerOpts} ->
+ %% Absolute timer
+ parse_timers(
+ TimerRefs, TimerTypes, CancelTimers, TimeoutsR,
+ Seen, TimeoutEvents,
+ TimerType, Time, TimerMsg, listify(TimerOpts));
+ %% Relative timers below
+ {TimerType,0,TimerMsg} ->
+ parse_timers(
+ TimerRefs, TimerTypes, CancelTimers, TimeoutsR,
+ Seen, TimeoutEvents,
+ TimerType, zero, TimerMsg, []);
{TimerType,Time,TimerMsg} ->
parse_timers(
- TimerRefs, TimerTypes, CancelTimers, TimeoutsR,
- Seen, TimeoutEvents,
- TimerType, Time, TimerMsg);
+ TimerRefs, TimerTypes, CancelTimers, TimeoutsR,
+ Seen, TimeoutEvents,
+ TimerType, Time, TimerMsg, []);
+ 0 ->
+ parse_timers(
+ TimerRefs, TimerTypes, CancelTimers, TimeoutsR,
+ Seen, TimeoutEvents,
+ timeout, zero, 0, []);
Time ->
parse_timers(
- TimerRefs, TimerTypes, CancelTimers, TimeoutsR,
- Seen, TimeoutEvents,
- timeout, Time, Time)
+ TimerRefs, TimerTypes, CancelTimers, TimeoutsR,
+ Seen, TimeoutEvents,
+ timeout, Time, Time, [])
end.
parse_timers(
TimerRefs, TimerTypes, CancelTimers, TimeoutsR,
Seen, TimeoutEvents,
- TimerType, Time, TimerMsg) ->
+ TimerType, Time, TimerMsg, TimerOpts) ->
case Seen of
#{TimerType := _} ->
%% Type seen before - ignore
@@ -1465,7 +1545,7 @@ parse_timers(
parse_timers(
TimerRefs, NewTimerTypes, NewCancelTimers, TimeoutsR,
NewSeen, TimeoutEvents);
- 0 ->
+ zero ->
%% Cancel any running timer
{NewTimerTypes,NewCancelTimers} =
cancel_timer_by_type(
@@ -1478,7 +1558,8 @@ parse_timers(
_ ->
%% (Re)start the timer
TimerRef =
- erlang:start_timer(Time, self(), TimerMsg),
+ erlang:start_timer(
+ Time, self(), TimerMsg, TimerOpts),
case TimerTypes of
#{TimerType := OldTimerRef} ->
%% Cancel the running timer
@@ -1492,6 +1573,8 @@ parse_timers(
NewCancelTimers, TimeoutsR,
NewSeen, TimeoutEvents);
#{} ->
+ %% Insert the new timer into
+ %% both TimerRefs and TimerTypes
parse_timers(
TimerRefs#{TimerRef => TimerType},
TimerTypes#{TimerType => TimerRef},
diff --git a/lib/stdlib/src/lib.erl b/lib/stdlib/src/lib.erl
index 56654097d9..aa6797bce6 100644
--- a/lib/stdlib/src/lib.erl
+++ b/lib/stdlib/src/lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -22,6 +22,9 @@
-export([flush_receive/0, error_message/2, progname/0, nonl/1, send/2,
sendw/2, eval_str/1]).
+-export([extended_parse_exprs/1, extended_parse_term/1,
+ subst_values_for_vars/2]).
+
-export([format_exception/6, format_exception/7,
format_stacktrace/4, format_stacktrace/5,
format_call/4, format_call/5, format_fun/1]).
@@ -127,6 +130,224 @@ all_white([$\t|T]) -> all_white(T);
all_white([]) -> true;
all_white(_) -> false.
+%% `Tokens' is assumed to have been scanned with the 'text' option.
+%% The annotations of the returned expressions are locations.
+%%
+%% Can handle pids, ports, references, and external funs ("items").
+%% Known items are represented by variables in the erl_parse tree, and
+%% the items themselves are stored in the returned bindings.
+
+-spec extended_parse_exprs(Tokens) ->
+ {'ok', ExprList, Bindings} | {'error', ErrorInfo} when
+ Tokens :: [erl_scan:token()],
+ ExprList :: [erl_parse:abstract_expr()],
+ Bindings :: erl_eval:binding_struct(),
+ ErrorInfo :: erl_parse:error_info().
+
+extended_parse_exprs(Tokens) ->
+ Ts = tokens_fixup(Tokens),
+ case erl_parse:parse_exprs(Ts) of
+ {ok, Exprs0} ->
+ {Exprs, Bs} = expr_fixup(Exprs0),
+ {ok, reset_expr_anno(Exprs), Bs};
+ _ErrorInfo ->
+ erl_parse:parse_exprs(reset_token_anno(Ts))
+ end.
+
+tokens_fixup([]) -> [];
+tokens_fixup([T|Ts]=Ts0) ->
+ try token_fixup(Ts0) of
+ {NewT, NewTs} ->
+ [NewT|tokens_fixup(NewTs)]
+ catch
+ _:_ ->
+ [T|tokens_fixup(Ts)]
+ end.
+
+token_fixup(Ts) ->
+ {AnnoL, NewTs, FixupTag} = unscannable(Ts),
+ String = lists:append([erl_anno:text(A) || A <- AnnoL]),
+ _ = (fixup_fun(FixupTag))(String),
+ NewAnno = erl_anno:set_text(fixup_text(FixupTag), hd(AnnoL)),
+ {{string, NewAnno, String}, NewTs}.
+
+unscannable([{'#', A1}, {var, A2, 'Fun'}, {'<', A3}, {atom, A4, _},
+ {'.', A5}, {float, A6, _}, {'>', A7}|Ts]) ->
+ {[A1, A2, A3, A4, A5, A6, A7], Ts, function};
+unscannable([{'#', A1}, {var, A2, 'Fun'}, {'<', A3}, {atom, A4, _},
+ {'.', A5}, {atom, A6, _}, {'.', A7}, {integer, A8, _},
+ {'>', A9}|Ts]) ->
+ {[A1, A2, A3, A4, A5, A6, A7, A8, A9], Ts, function};
+unscannable([{'<', A1}, {float, A2, _}, {'.', A3}, {integer, A4, _},
+ {'>', A5}|Ts]) ->
+ {[A1, A2, A3, A4, A5], Ts, pid};
+unscannable([{'#', A1}, {var, A2, 'Port'}, {'<', A3}, {float, A4, _},
+ {'>', A5}|Ts]) ->
+ {[A1, A2, A3, A4, A5], Ts, port};
+unscannable([{'#', A1}, {var, A2, 'Ref'}, {'<', A3}, {float, A4, _},
+ {'.', A5}, {float, A6, _}, {'>', A7}|Ts]) ->
+ {[A1, A2, A3, A4, A5, A6, A7], Ts, reference}.
+
+expr_fixup(Expr0) ->
+ {Expr, Bs, _} = expr_fixup(Expr0, erl_eval:new_bindings(), 1),
+ {Expr, Bs}.
+
+expr_fixup({string,A,S}=T, Bs0, I) ->
+ try string_fixup(A, S) of
+ Value ->
+ Var = new_var(I),
+ Bs = erl_eval:add_binding(Var, Value, Bs0),
+ {{var, A, Var}, Bs, I+1}
+ catch
+ _:_ ->
+ {T, Bs0, I}
+ end;
+expr_fixup(Tuple, Bs0, I0) when is_tuple(Tuple) ->
+ {L, Bs, I} = expr_fixup(tuple_to_list(Tuple), Bs0, I0),
+ {list_to_tuple(L), Bs, I};
+expr_fixup([E0|Es0], Bs0, I0) ->
+ {E, Bs1, I1} = expr_fixup(E0, Bs0, I0),
+ {Es, Bs, I} = expr_fixup(Es0, Bs1, I1),
+ {[E|Es], Bs, I};
+expr_fixup(T, Bs, I) ->
+ {T, Bs, I}.
+
+string_fixup(A, S) ->
+ Text = erl_anno:text(A),
+ FixupTag = fixup_tag(Text, S),
+ (fixup_fun(FixupTag))(S).
+
+new_var(I) ->
+ list_to_atom(lists:concat(['__ExtendedParseExprs_', I, '__'])).
+
+reset_token_anno(Tokens) ->
+ [setelement(2, T, (reset_anno())(element(2, T))) || T <- Tokens].
+
+reset_expr_anno(Exprs) ->
+ [erl_parse:map_anno(reset_anno(), E) || E <- Exprs].
+
+reset_anno() ->
+ fun(A) -> erl_anno:new(erl_anno:location(A)) end.
+
+fixup_fun(function) -> fun function/1;
+fixup_fun(pid) -> fun erlang:list_to_pid/1;
+fixup_fun(port) -> fun erlang:list_to_port/1;
+fixup_fun(reference) -> fun erlang:list_to_ref/1.
+
+function(S) ->
+ %% External function.
+ {ok, [_, _, _,
+ {atom, _, Module}, _,
+ {atom, _, Function}, _,
+ {integer, _, Arity}|_], _} = erl_scan:string(S),
+ erlang:make_fun(Module, Function, Arity).
+
+fixup_text(function) -> "function";
+fixup_text(pid) -> "pid";
+fixup_text(port) -> "port";
+fixup_text(reference) -> "reference".
+
+fixup_tag("function", "#"++_) -> function;
+fixup_tag("pid", "<"++_) -> pid;
+fixup_tag("port", "#"++_) -> port;
+fixup_tag("reference", "#"++_) -> reference.
+
+%%% End of extended_parse_exprs.
+
+%% `Tokens' is assumed to have been scanned with the 'text' option.
+%%
+%% Can handle pids, ports, references, and external funs.
+
+-spec extended_parse_term(Tokens) ->
+ {'ok', Term} | {'error', ErrorInfo} when
+ Tokens :: [erl_scan:token()],
+ Term :: term(),
+ ErrorInfo :: erl_parse:error_info().
+
+extended_parse_term(Tokens) ->
+ case extended_parse_exprs(Tokens) of
+ {ok, [Expr], Bindings} ->
+ try normalise(Expr, Bindings) of
+ Term ->
+ {ok, Term}
+ catch
+ _:_ ->
+ Loc = erl_anno:location(element(2, Expr)),
+ {error,{Loc,?MODULE,"bad term"}}
+ end;
+ {ok, [_,Expr|_], _Bindings} ->
+ Loc = erl_anno:location(element(2, Expr)),
+ {error,{Loc,?MODULE,"bad term"}};
+ {error, _} = Error ->
+ Error
+ end.
+
+%% From erl_parse.
+normalise({var, _, V}, Bs) ->
+ {value, Value} = erl_eval:binding(V, Bs),
+ Value;
+normalise({char,_,C}, _Bs) -> C;
+normalise({integer,_,I}, _Bs) -> I;
+normalise({float,_,F}, _Bs) -> F;
+normalise({atom,_,A}, _Bs) -> A;
+normalise({string,_,S}, _Bs) -> S;
+normalise({nil,_}, _Bs) -> [];
+normalise({bin,_,Fs}, Bs) ->
+ {value, B, _} =
+ eval_bits:expr_grp(Fs, [],
+ fun(E, _) ->
+ {value, normalise(E, Bs), []}
+ end, [], true),
+ B;
+normalise({cons,_,Head,Tail}, Bs) ->
+ [normalise(Head, Bs)|normalise(Tail, Bs)];
+normalise({tuple,_,Args}, Bs) ->
+ list_to_tuple(normalise_list(Args, Bs));
+normalise({map,_,Pairs}, Bs) ->
+ maps:from_list(lists:map(fun
+ %% only allow '=>'
+ ({map_field_assoc,_,K,V}) ->
+ {normalise(K, Bs),normalise(V, Bs)}
+ end, Pairs));
+%% Special case for unary +/-.
+normalise({op,_,'+',{char,_,I}}, _Bs) -> I;
+normalise({op,_,'+',{integer,_,I}}, _Bs) -> I;
+normalise({op,_,'+',{float,_,F}}, _Bs) -> F;
+normalise({op,_,'-',{char,_,I}}, _Bs) -> -I; %Weird, but compatible!
+normalise({op,_,'-',{integer,_,I}}, _Bs) -> -I;
+normalise({op,_,'-',{float,_,F}}, _Bs) -> -F;
+normalise({'fun',_,{function,{atom,_,M},{atom,_,F},{integer,_,A}}}, _Bs) ->
+ %% Since "#Fun<M.F.A>" is recognized, "fun M:F/A" should be too.
+ fun M:F/A.
+
+normalise_list([H|T], Bs) ->
+ [normalise(H, Bs)|normalise_list(T, Bs)];
+normalise_list([], _Bs) ->
+ [].
+
+%% To be used on ExprList and Bindings returned from extended_parse_exprs().
+%% Substitute {value, A, Item} for {var, A, ExtendedParseVar}.
+%% {value, A, Item} is a shell/erl_eval convention, and for example
+%% the linter cannot handle it.
+
+-spec subst_values_for_vars(ExprList, Bindings) -> [term()] when
+ ExprList :: [erl_parse:abstract_expr()],
+ Bindings :: erl_eval:binding_struct().
+
+subst_values_for_vars({var, A, V}=Var, Bs) ->
+ case erl_eval:binding(V, Bs) of
+ {value, Value} ->
+ {value, A, Value};
+ unbound ->
+ Var
+ end;
+subst_values_for_vars(L, Bs) when is_list(L) ->
+ [subst_values_for_vars(E, Bs) || E <- L];
+subst_values_for_vars(T, Bs) when is_tuple(T) ->
+ list_to_tuple(subst_values_for_vars(tuple_to_list(T), Bs));
+subst_values_for_vars(T, _Bs) ->
+ T.
+
%%% Formatting of exceptions, mfa:s and funs.
%% -> iolist() (no \n at end)
diff --git a/lib/stdlib/src/qlc.erl b/lib/stdlib/src/qlc.erl
index 8c4d835432..20aaa2638c 100644
--- a/lib/stdlib/src/qlc.erl
+++ b/lib/stdlib/src/qlc.erl
@@ -635,14 +635,25 @@ string_to_handle(Str, Options, Bindings) when is_list(Str) ->
badarg ->
erlang:error(badarg, [Str, Options, Bindings]);
[Unique, Cache, MaxLookup, Join, Lookup] ->
- case erl_scan:string(Str) of
+ case erl_scan:string(Str, 1, [text]) of
{ok, Tokens, _} ->
- case erl_parse:parse_exprs(Tokens) of
- {ok, [Expr]} ->
- case qlc_pt:transform_expression(Expr, Bindings) of
+ ScanRes =
+ case lib:extended_parse_exprs(Tokens) of
+ {ok, [Expr0], SBs} ->
+ {ok, Expr0, SBs};
+ {ok, _ExprList, _SBs} ->
+ erlang:error(badarg,
+ [Str, Options, Bindings]);
+ E ->
+ E
+ end,
+ case ScanRes of
+ {ok, Expr, XBs} ->
+ Bs1 = merge_binding_structs(Bindings, XBs),
+ case qlc_pt:transform_expression(Expr, Bs1) of
{ok, {call, _, _QlcQ, Handle}} ->
{value, QLC_lc, _} =
- erl_eval:exprs(Handle, Bindings),
+ erl_eval:exprs(Handle, Bs1),
O = #qlc_opt{unique = Unique,
cache = Cache,
max_lookup = MaxLookup,
@@ -652,8 +663,6 @@ string_to_handle(Str, Options, Bindings) when is_list(Str) ->
{not_ok, [{error, Error} | _]} ->
error(Error)
end;
- {ok, _ExprList} ->
- erlang:error(badarg, [Str, Options, Bindings]);
{error, ErrorInfo} ->
error(ErrorInfo)
end;
@@ -770,6 +779,10 @@ all_selections([{I,Cs} | ICs]) ->
%%% Local functions
%%%
+merge_binding_structs(Bs1, Bs2) ->
+ lists:foldl(fun({N, V}, Bs) -> erl_eval:add_binding(N, V, Bs)
+ end, Bs1, erl_eval:bindings(Bs2)).
+
aux_name1(Name, N, AllNames) ->
SN = name_suffix(Name, N),
case sets:is_element(SN, AllNames) of
@@ -1180,9 +1193,12 @@ abstract1({table, {M, F, As0}}, _NElements, _Depth, Anno)
abstract1({table, TableDesc}, _NElements, _Depth, _A) ->
case io_lib:deep_char_list(TableDesc) of
true ->
- {ok, Tokens, _} = erl_scan:string(lists:flatten(TableDesc++".")),
- {ok, [Expr]} = erl_parse:parse_exprs(Tokens),
- Expr;
+ {ok, Tokens, _} =
+ erl_scan:string(lists:flatten(TableDesc++"."), 1, [text]),
+ {ok, Es, Bs} =
+ lib:extended_parse_exprs(Tokens),
+ [Expr] = lib:subst_values_for_vars(Es, Bs),
+ special(Expr);
false -> % abstract expression
TableDesc
end;
@@ -1210,6 +1226,15 @@ abstract1({list, L}, NElements, Depth, _A) when NElements =:= infinity;
abstract1({list, L}, NElements, Depth, _A) ->
abstract_term(depth(lists:sublist(L, NElements), Depth) ++ '...', 1).
+special({value, _, Thing}) ->
+ abstract_term(Thing);
+special(Tuple) when is_tuple(Tuple) ->
+ list_to_tuple(special(tuple_to_list(Tuple)));
+special([E|Es]) ->
+ [special(E)|special(Es)];
+special(Expr) ->
+ Expr.
+
depth(List, infinity) ->
List;
depth(List, Depth) ->
diff --git a/lib/stdlib/src/shell.erl b/lib/stdlib/src/shell.erl
index 961f5f8a30..76a2789406 100644
--- a/lib/stdlib/src/shell.erl
+++ b/lib/stdlib/src/shell.erl
@@ -229,8 +229,9 @@ server_loop(N0, Eval_0, Bs00, RT, Ds00, History0, Results0) ->
{Eval_1,Bs0,Ds0,Prompt} = prompt(N, Eval_0, Bs00, RT, Ds00),
{Res,Eval0} = get_command(Prompt, Eval_1, Bs0, RT, Ds0),
case Res of
- {ok,Es0} ->
- case expand_hist(Es0, N) of
+ {ok,Es0,XBs} ->
+ Es1 = lib:subst_values_for_vars(Es0, XBs),
+ case expand_hist(Es1, N) of
{ok,Es} ->
{V,Eval,Bs,Ds} = shell_cmd(Es, Eval0, Bs0, RT, Ds0, cmd),
{History,Results} = check_and_get_history_and_results(),
@@ -276,10 +277,10 @@ get_command(Prompt, Eval, Bs, RT, Ds) ->
fun() ->
exit(
case
- io:scan_erl_exprs(group_leader(), Prompt, 1)
+ io:scan_erl_exprs(group_leader(), Prompt, 1, [text])
of
{ok,Toks,_EndPos} ->
- erl_parse:parse_exprs(Toks);
+ lib:extended_parse_exprs(Toks);
{eof,_EndPos} ->
eof;
{error,ErrorInfo,_EndPos} ->
diff --git a/lib/stdlib/test/erl_internal_SUITE.erl b/lib/stdlib/test/erl_internal_SUITE.erl
index 099f21f905..789a9d4363 100644
--- a/lib/stdlib/test/erl_internal_SUITE.erl
+++ b/lib/stdlib/test/erl_internal_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -60,7 +60,7 @@ end_per_testcase(_Case, _Config) ->
%% Check that the behaviour callbacks are correctly defined.
behav(_) ->
Modules = [application, gen_server, gen_fsm, gen_event,
- supervisor_bridge, supervisor],
+ gen_statem, supervisor_bridge, supervisor],
lists:foreach(fun check_behav/1, Modules).
check_behav(Module) ->
@@ -89,6 +89,10 @@ callbacks(gen_event) ->
[{init,1}, {handle_event,2}, {handle_call,2},
{handle_info,2}, {terminate,2}, {code_change,3},
{format_status,2}];
+callbacks(gen_statem) ->
+ [{init, 1}, {callback_mode, 0}, {state_name, 3},
+ {handle_event, 4}, {terminate, 3}, {code_change, 4},
+ {format_status, 2}];
callbacks(supervisor_bridge) ->
[{init,1}, {terminate,2}];
callbacks(supervisor) ->
@@ -102,6 +106,9 @@ optional_callbacks(gen_fsm) ->
[{handle_info, 3}, {terminate, 3}, {code_change, 4}, {format_status, 2}];
optional_callbacks(gen_event) ->
[{handle_info, 2}, {terminate, 2}, {code_change, 3}, {format_status, 2}];
+optional_callbacks(gen_statem) ->
+ [{state_name, 3}, {handle_event, 4},
+ {terminate, 3}, {code_change, 4}, {format_status, 2}];
optional_callbacks(supervisor_bridge) ->
[];
optional_callbacks(supervisor) ->
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index ac27c9fc79..05934b3953 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2016. All Rights Reserved.
+%% Copyright Ericsson AB 2016-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -38,9 +38,10 @@ all() ->
{group, abnormal},
{group, abnormal_handle_event},
shutdown, stop_and_reply, state_enter, event_order,
- state_timeout, event_types, code_change,
+ state_timeout, event_types, generic_timers, code_change,
{group, sys},
- hibernate, enter_loop].
+ hibernate, enter_loop, {group, undef_callbacks},
+ undef_in_terminate].
groups() ->
[{start, [], tcs(start)},
@@ -50,7 +51,8 @@ groups() ->
{abnormal, [], tcs(abnormal)},
{abnormal_handle_event, [], tcs(abnormal)},
{sys, [], tcs(sys)},
- {sys_handle_event, [], tcs(sys)}].
+ {sys_handle_event, [], tcs(sys)},
+ {undef_callbacks, [], tcs(undef_callbacks)}].
tcs(start) ->
[start1, start2, start3, start4, start5, start6, start7,
@@ -62,8 +64,9 @@ tcs(abnormal) ->
tcs(sys) ->
[sys1, call_format_status,
error_format_status, terminate_crash_format,
- get_state, replace_state].
-
+ get_state, replace_state];
+tcs(undef_callbacks) ->
+ [undef_code_change, undef_terminate1, undef_terminate2].
init_per_suite(Config) ->
Config.
@@ -77,6 +80,11 @@ init_per_group(GroupName, Config)
GroupName =:= abnormal_handle_event;
GroupName =:= sys_handle_event ->
[{callback_mode,handle_event_function}|Config];
+init_per_group(undef_callbacks, Config) ->
+ DataDir = ?config(data_dir, Config),
+ StatemPath = filename:join(DataDir, "oc_statem.erl"),
+ {ok, oc_statem} = compile:file(StatemPath),
+ Config;
init_per_group(_GroupName, Config) ->
Config.
@@ -834,6 +842,7 @@ event_types(_Config) ->
{next_event,timeout,3},
{next_event,info,4},
{next_event,cast,5},
+ {next_event,{timeout,6}, 6},
{next_event,Call,Req}]}
end,
state1 =>
@@ -857,6 +866,10 @@ event_types(_Config) ->
{next_state, state6, undefined}
end,
state6 =>
+ fun ({timeout,6}, 6, undefined) ->
+ {next_state, state7, undefined}
+ end,
+ state7 =>
fun ({call,From}, stop, undefined) ->
{stop_and_reply, shutdown,
[{reply,From,stopped}]}
@@ -884,6 +897,69 @@ event_types(_Config) ->
+generic_timers(_Config) ->
+ process_flag(trap_exit, true),
+
+ Machine =
+ %% Abusing the internal format of From...
+ #{init =>
+ fun () ->
+ {ok, start, undefined}
+ end,
+ start =>
+ fun ({call,_} = Call, Req, undefined) ->
+ {next_state, state1, undefined,
+ [{{timeout,a},1500,1},
+ {state_timeout,1500,1},
+ {{timeout,b},1000,1},
+ {next_event,Call,Req}]}
+ end,
+ state1 =>
+ fun ({call,_} = Call, Req, undefined) ->
+ T = erlang:monotonic_time(millisecond) + 500,
+ {next_state, state2, undefined,
+ [{{timeout,c},T,2,{abs,true}},
+ {{timeout,d},0,2,[{abs,false}]},
+ {timeout,0,2},
+ {{timeout,b},infinity,2},
+ {{timeout,a},1000,{Call,Req}}]}
+ end,
+ state2 =>
+ fun ({timeout,d}, 2, undefined) ->
+ {next_state, state3, undefined}
+ end,
+ state3 =>
+ fun ({timeout,c}, 2, undefined) ->
+ {next_state, state4, undefined}
+ end,
+ state4 =>
+ fun ({timeout,a}, {{call,From},stop}, undefined) ->
+ {stop_and_reply, shutdown,
+ [{reply,From,stopped}]}
+ end},
+ {ok,STM} =
+ gen_statem:start_link(
+ ?MODULE, {map_statem,Machine,[]}, [{debug,[trace]}]),
+
+ stopped = gen_statem:call(STM, stop),
+ receive
+ {'EXIT',STM,shutdown} ->
+ ok
+ after 500 ->
+ ct:fail(did_not_stop)
+ end,
+
+ {noproc,_} =
+ ?EXPECT_FAILURE(gen_statem:call(STM, hej), Reason),
+ case flush() of
+ [] ->
+ ok;
+ Other2 ->
+ ct:fail({unexpected,Other2})
+ end.
+
+
+
sys1(Config) ->
{ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []),
{status, Pid, {module,gen_statem}, _} = sys:get_status(Pid),
@@ -1393,6 +1469,51 @@ enter_loop(Reg1, Reg2) ->
gen_statem:enter_loop(?MODULE, [], state0, [])
end.
+undef_code_change(_Config) ->
+ {ok, Statem} = gen_statem:start(oc_statem, [], []),
+ {error, {'EXIT',
+ {undef, [{oc_statem, code_change, [_, _, _, _], _}|_]}}}
+ = fake_upgrade(Statem, oc_statem).
+
+fake_upgrade(Pid, Mod) ->
+ sys:suspend(Pid),
+ sys:replace_state(Pid, fun(State) -> {new, State} end),
+ Ret = sys:change_code(Pid, Mod, old_vsn, []),
+ ok = sys:resume(Pid),
+ Ret.
+
+undef_terminate1(_Config) ->
+ {ok, Statem} = gen_statem:start(oc_statem, [], []),
+ MRef = monitor(process, Statem),
+ ok = gen_statem:stop(Statem),
+ verify_down(Statem, MRef, normal),
+ ok.
+
+undef_terminate2(_Config) ->
+ Reason = {error, test},
+ {ok, Statem} = oc_statem:start(),
+ MRef = monitor(process, Statem),
+ ok = gen_statem:stop(Statem, Reason, infinity),
+ verify_down(Statem, MRef, Reason).
+
+undef_in_terminate(_Config) ->
+ Data = {undef_in_terminate, {?MODULE, terminate}},
+ {ok, Statem} = gen_statem:start(?MODULE, {data, Data}, []),
+ try
+ gen_statem:stop(Statem),
+ ct:fail(should_crash)
+ catch
+ exit:{undef, [{?MODULE, terminate, _, _}|_]} ->
+ ok
+ end.
+
+verify_down(Statem, MRef, Reason) ->
+ receive
+ {'DOWN', MRef, process, Statem, Reason} ->
+ ok
+ after 5000 ->
+ ct:fail(default_terminate_failed)
+ end.
%% Test the order for multiple {next_event,T,C}
next_events(Config) ->
@@ -1571,6 +1692,9 @@ callback_mode() ->
terminate(_, _State, crash_terminate) ->
exit({crash,terminate});
+terminate(_, _State, {undef_in_terminate, {Mod, Fun}}) ->
+ Mod:Fun(),
+ ok;
terminate({From,stopped}, State, _Data) ->
From ! {self(),{stopped,State}},
ok;
@@ -1597,8 +1721,9 @@ idle({call,From}, {delayed_answer,T}, Data) ->
throw({keep_state,Data})
end;
idle({call,From}, {timeout,Time}, _Data) ->
+ AbsTime = erlang:monotonic_time(millisecond) + Time,
{next_state,timeout,{From,Time},
- {timeout,Time,idle}};
+ {timeout,AbsTime,idle,[{abs,true}]}};
idle(cast, next_event, _Data) ->
{next_state,next_events,[a,b,c],
[{next_event,internal,a},
diff --git a/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl b/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl
new file mode 100644
index 0000000000..27c9e0718d
--- /dev/null
+++ b/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl
@@ -0,0 +1,37 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2017. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(oc_statem).
+
+-behaviour(gen_statem).
+
+%% API
+-export([start/0]).
+
+%% gen_statem callbacks
+-export([init/1, callback_mode/0]).
+
+start() ->
+ gen_statem:start({local, ?MODULE}, ?MODULE, [], []).
+
+init([]) ->
+ {ok, state_name, #{}}.
+
+callback_mode() ->
+ handle_event_function.
diff --git a/lib/stdlib/test/qlc_SUITE.erl b/lib/stdlib/test/qlc_SUITE.erl
index 2b5d52287e..5e9e03e410 100644
--- a/lib/stdlib/test/qlc_SUITE.erl
+++ b/lib/stdlib/test/qlc_SUITE.erl
@@ -1240,6 +1240,17 @@ string_to_handle(Config) when is_list(Config) ->
{'EXIT', {no_lookup_to_carry_out, _}} =
(catch qlc:e(qlc:string_to_handle(Q, {lookup,true}, Bs2))),
ets:delete(Ets),
+
+ %% References can be scanned and parsed.
+ E2 = ets:new(test, [bag]),
+ Ref = make_ref(),
+ true = ets:insert(E2, [{Ref,Ref}]),
+ S2 = "[{Val1} || {Ref1, Val1} <- ets:table("++io_lib:write(E2)++"),"
+ "Ref1 =:= Ref].",
+ Bs = erl_eval:add_binding('Ref', Ref, erl_eval:new_bindings()),
+ [{Ref}] = qlc:e(qlc:string_to_handle(S2, [], Bs)),
+ ets:delete(E2),
+
ok.
%% table
@@ -4321,7 +4332,18 @@ ets(Config) when is_list(Config) ->
R = qlc:e(Q),
ets:delete(E),
[] = R">>]
- end
+ end,
+
+ <<"E2 = ets:new(test, [bag]),
+ Ref = make_ref(),
+ true = ets:insert(E2, [{Ref,Ref}]),
+ Q2 = qlc:q([{Val1} ||
+ {Ref1, Val1} <- ets:table(E2),
+ Ref1 =:= Ref]),
+ S = qlc:info(Q2),
+ true = is_list(S),
+ [{Ref}] = qlc:e(Q2),
+ ets:delete(E2)">>
],
@@ -7071,7 +7093,7 @@ otp_12946(Config) when is_list(Config) ->
%% Examples from qlc(3).
manpage(Config) when is_list(Config) ->
-
+ dets:start(),
ok = compile_gb_table(Config),
Ts = [
@@ -7138,11 +7160,14 @@ manpage(Config) when is_list(Config) ->
\" [{X,Z}|{W,Y}] <- V2\n\"
\" ])\n\"
\"end\",
- Info =
+ Info1 =
re:replace(qlc:info(Q),
- \"table\\\\(-*[0-9]*\",
+ \"table\\\\(#Ref<[\\.0-9]*>\",
\"table(_\", [{return,list},global]),
- L = Info,
+ F = fun(C) -> C =/= $\n andalso C =/= $\s end,
+ Info = lists:filter(F, Info1),
+ L1 = lists:filter(F, L),
+ L1 = Info,
ets:delete(E1),
ets:delete(E2)">>,
@@ -7445,10 +7470,10 @@ etsc(F, Opts, Objs) ->
V.
join_info(H) ->
- {qlc, S, Options} = strip_qlc_call(H),
+ {{qlc, S, Options}, Bs} = strip_qlc_call2(H),
%% "Hide" the call to qlc_pt from the test in run_test().
LoadedPT = code:is_loaded(qlc_pt),
- QH = qlc:string_to_handle(S, Options),
+ QH = qlc:string_to_handle(S, Options, Bs),
_ = [unload_pt() || false <- [LoadedPT]], % doesn't take long...
case {join_info_count(H), join_info_count(QH)} of
{N, N} ->
@@ -7458,30 +7483,34 @@ join_info(H) ->
end.
strip_qlc_call(H) ->
+ {Expr, _Bs} = strip_qlc_call2(H),
+ Expr.
+
+strip_qlc_call2(H) ->
S = qlc:info(H, {flat, false}),
- {ok, Tokens, _EndLine} = erl_scan:string(S++"."),
- {ok, [Expr]} = erl_parse:parse_exprs(Tokens),
- case Expr of
- {call,_,{remote,_,{atom,_,qlc},{atom,_,q}},[LC]} ->
- {qlc, lists:flatten([erl_pp:expr(LC), "."]), []};
- {call,_,{remote,_,{atom,_,qlc},{atom,_,q}},[LC, Opts]} ->
- {qlc, lists:flatten([erl_pp:expr(LC), "."]),
- erl_parse:normalise(Opts)};
- {call,_,{remote,_,{atom,_,ets},{atom,_,match_spec_run}},_} ->
- {match_spec, Expr};
- {call,_,{remote,_,{atom,_,M},{atom,_,table}},_} ->
- {table, M, Expr};
- _ ->
- []
- end.
+ {ok, Tokens, _EndLine} = erl_scan:string(S++".", 1, [text]),
+ {ok, [Expr], Bs} = lib:extended_parse_exprs(Tokens),
+ {case Expr of
+ {call,_,{remote,_,{atom,_,qlc},{atom,_,q}},[LC]} ->
+ {qlc, lists:flatten([erl_pp:expr(LC), "."]), []};
+ {call,_,{remote,_,{atom,_,qlc},{atom,_,q}},[LC, Opts]} ->
+ {qlc, lists:flatten([erl_pp:expr(LC), "."]),
+ erl_parse:normalise(Opts)};
+ {call,_,{remote,_,{atom,_,ets},{atom,_,match_spec_run}},_} ->
+ {match_spec, Expr};
+ {call,_,{remote,_,{atom,_,M},{atom,_,table}},_} ->
+ {table, M, Expr};
+ _ ->
+ []
+ end, Bs}.
-record(ji, {nmerge = 0, nlookup = 0, nnested_loop = 0, nkeysort = 0}).
%% Counts join options and (all) calls to qlc:keysort().
join_info_count(H) ->
S = qlc:info(H, {flat, false}),
- {ok, Tokens, _EndLine} = erl_scan:string(S++"."),
- {ok, [Expr]} = erl_parse:parse_exprs(Tokens),
+ {ok, Tokens, _EndLine} = erl_scan:string(S++".", 1, [text]),
+ {ok, [Expr], _Bs} = lib:extended_parse_exprs(Tokens),
#ji{nmerge = Nmerge, nlookup = Nlookup,
nkeysort = NKeysort, nnested_loop = Nnested_loop} =
ji(Expr, #ji{}),
@@ -7524,8 +7553,8 @@ lookup_keys({list,Q,_}, L) ->
lookup_keys({generate,_,Q}, L) ->
lookup_keys(Q, L);
lookup_keys({table,Chars}, L) when is_list(Chars) ->
- {ok, Tokens, _} = erl_scan:string(lists:flatten(Chars++".")),
- {ok, [Expr]} = erl_parse:parse_exprs(Tokens),
+ {ok, Tokens, _} = erl_scan:string(lists:flatten(Chars++"."), 1, [text]),
+ {ok, [Expr], _Bs} = lib:extended_parse_exprs(Tokens),
case Expr of
{call,_,_,[_fun,AKs]} ->
case erl_parse:normalise(AKs) of
@@ -7842,7 +7871,7 @@ run_test(Config, Extra, {cres, Body, Opts, ExpectedCompileReturn}) ->
{module, _} = code:load_abs(AbsFile, Mod),
Ms0 = erlang:process_info(self(),messages),
- Before = {{get(), ets:all(), Ms0}, pps()},
+ Before = {{get(), lists:sort(ets:all()), Ms0}, pps()},
%% Prepare the check that the qlc module does not call qlc_pt.
_ = [unload_pt() || {file, Name} <- [code:is_loaded(qlc_pt)],
@@ -7874,7 +7903,7 @@ run_test(Config, Extra, Body) ->
wait_for_expected(R, {Strict0,PPS0}=Before, SourceFile, Wait) ->
Ms = erlang:process_info(self(),messages),
- After = {_,PPS1} = {{get(), ets:all(), Ms}, pps()},
+ After = {_,PPS1} = {{get(), lists:sort(ets:all()), Ms}, pps()},
case {R, After} of
{ok, Before} ->
ok;
diff --git a/lib/stdlib/test/shell_SUITE.erl b/lib/stdlib/test/shell_SUITE.erl
index 56002dda25..99411bc8fd 100644
--- a/lib/stdlib/test/shell_SUITE.erl
+++ b/lib/stdlib/test/shell_SUITE.erl
@@ -31,7 +31,7 @@
progex_lc/1, progex_funs/1,
otp_5990/1, otp_6166/1, otp_6554/1,
otp_7184/1, otp_7232/1, otp_8393/1, otp_10302/1, otp_13719/1,
- otp_14285/1]).
+ otp_14285/1, otp_14296/1]).
-export([ start_restricted_from_shell/1,
start_restricted_on_command_line/1,restricted_local/1]).
@@ -92,7 +92,7 @@ groups() ->
progex_funs]},
{tickets, [],
[otp_5990, otp_6166, otp_6554, otp_7184,
- otp_7232, otp_8393, otp_10302, otp_13719, otp_14285]}].
+ otp_7232, otp_8393, otp_10302, otp_13719, otp_14285, otp_14296]}].
init_per_suite(Config) ->
Config.
@@ -2841,6 +2841,95 @@ otp_14285(Config) ->
test_server:stop_node(Node),
ok.
+otp_14296(Config) when is_list(Config) ->
+ fun() ->
+ F = fun() -> a end,
+ LocalFun = term_to_string(F),
+ S = LocalFun ++ ".",
+ "1: syntax error before: Fun" = comm_err(S)
+ end(),
+
+ fun() ->
+ F = fun mod:func/1,
+ ExternalFun = term_to_string(F),
+ S = ExternalFun ++ ".",
+ R = ExternalFun ++ ".\n",
+ R = t(S)
+ end(),
+
+ fun() ->
+ UnknownPid = "<100000.0.0>",
+ S = UnknownPid ++ ".",
+ "1: syntax error before: '<'" = comm_err(S)
+ end(),
+
+ fun() ->
+ KnownPid = term_to_string(self()),
+ S = KnownPid ++ ".",
+ R = KnownPid ++ ".\n",
+ R = t(S)
+ end(),
+
+ fun() ->
+ Port = open_port({spawn, "ls"}, [line]),
+ KnownPort = erlang:port_to_list(Port),
+ S = KnownPort ++ ".",
+ R = KnownPort ++ ".\n",
+ R = t(S)
+ end(),
+
+ fun() ->
+ UnknownPort = "#Port<100000.0>",
+ S = UnknownPort ++ ".",
+ "1: syntax error before: Port" = comm_err(S)
+ end(),
+
+ fun() ->
+ UnknownRef = "#Ref<100000.0.0.0>",
+ S = UnknownRef ++ ".",
+ "1: syntax error before: Ref" = comm_err(S)
+ end(),
+
+ fun() ->
+ KnownRef = term_to_string(make_ref()),
+ S = KnownRef ++ ".",
+ R = KnownRef ++ ".\n",
+ R = t(S)
+ end(),
+
+ %% Test lib:extended_parse_term/1
+ TF = fun(S) ->
+ {ok, Ts, _} = erl_scan:string(S++".", 1, [text]),
+ case lib:extended_parse_term(Ts) of
+ {ok, Term} -> Term;
+ {error, _}=Error -> Error
+ end
+ end,
+ Fun = fun m:f/1,
+ Fun = TF(term_to_string(Fun)),
+ Fun = TF("fun m:f/1"),
+ Pid = self(),
+ Pid = TF(term_to_string(Pid)),
+ Ref = make_ref(),
+ Ref = TF(term_to_string(Ref)),
+ Term = {[10, a], {"foo", []}, #{x => <<"bar">>}},
+ Term = TF(lists:flatten(io_lib:format("~p", [Term]))),
+ {$a, F1, "foo"} = TF("{$a, 1.0, \"foo\"}"),
+ true = is_float(F1),
+ 3 = TF("+3"),
+ $a = TF("+$a"),
+ true = is_float(TF("+1.0")),
+ true = -3 =:= TF("-3"),
+ true = -$a =:= TF("-$a"),
+ true = is_float(TF("-1.0")),
+ {error, {_, _, ["syntax error"++_|_]}} = TF("{1"),
+ {error, {_,_,"bad term"}} = TF("fun() -> foo end"),
+ {error, {_,_,"bad term"}} = TF("1, 2"),
+ ok.
+
+term_to_string(T) ->
+ lists:flatten(io_lib:format("~w", [T])).
+
scan(B) ->
F = fun(Ts) ->
case erl_parse:parse_term(Ts) of
diff --git a/lib/stdlib/test/sofs_SUITE.erl b/lib/stdlib/test/sofs_SUITE.erl
index f67bf16f0f..39e56c6df6 100644
--- a/lib/stdlib/test/sofs_SUITE.erl
+++ b/lib/stdlib/test/sofs_SUITE.erl
@@ -1783,7 +1783,7 @@ multiple_relative_product(Conf) when is_list(Conf) ->
ok.
digraph(Conf) when is_list(Conf) ->
- T0 = ets:all(),
+ T0 = lists:sort(ets:all()),
E = empty_set(),
R = relation([{a,b},{b,c},{c,d},{d,a}]),
F = relation_to_family(R),
@@ -1833,7 +1833,7 @@ digraph(Conf) when is_list(Conf) ->
true -> ok
end,
- true = T0 == ets:all(),
+ true = T0 == lists:sort(ets:all()),
ok.
digraph_fail(ExitReason, Fail) ->
diff --git a/lib/tools/emacs/erlang.el b/lib/tools/emacs/erlang.el
index 2d86eb44d4..4ffc2ca172 100644
--- a/lib/tools/emacs/erlang.el
+++ b/lib/tools/emacs/erlang.el
@@ -928,7 +928,6 @@ resulting regexp is surrounded by \\_< and \\_>."
"get_cookie"
"get_module_info"
"get_stacktrace"
- "hash"
"has_prepared_code_on_load"
"hibernate"
"insert_element"
@@ -4707,6 +4706,7 @@ for a tag on the form `module:tag'."
(if (fboundp 'advice-add)
;; Emacs 24.4+
+ (require 'etags)
(advice-add 'etags-tags-completion-table :around
#'erlang-etags-tags-completion-table-advice)
;; Emacs 23.1-24.3
@@ -4873,6 +4873,7 @@ considered first when it is time to jump to the definition.")
(and (erlang-soft-require 'xref)
(erlang-soft-require 'cl-generic)
(erlang-soft-require 'eieio)
+ (erlang-soft-require 'etags)
;; The purpose of using eval here is to avoid compilation
;; warnings in emacsen without cl-defmethod etc.
(eval
@@ -5351,7 +5352,7 @@ editing control characters:
\\{erlang-shell-mode-map}"
(interactive
(when current-prefix-arg
- (list ((read-shell-command "Erlang command: ")))))
+ (list (read-shell-command "Erlang command: "))))
(require 'comint)
(let (cmd opts)
(if command
diff --git a/lib/wx/c_src/wxe_impl.cpp b/lib/wx/c_src/wxe_impl.cpp
index 05d56667ab..90cd35455a 100644
--- a/lib/wx/c_src/wxe_impl.cpp
+++ b/lib/wx/c_src/wxe_impl.cpp
@@ -672,7 +672,7 @@ void * WxeApp::getPtr(char * bp, wxeMemEnv *memenv) {
throw wxe_badarg(index);
}
void * temp = memenv->ref2ptr[index];
- if((index < memenv->next) && ((index == 0) || (temp > NULL)))
+ if((index < memenv->next) && ((index == 0) || (temp != (void *)NULL)))
return temp;
else {
throw wxe_badarg(index);
@@ -684,7 +684,7 @@ void WxeApp::registerPid(char * bp, ErlDrvTermData pid, wxeMemEnv * memenv) {
if(!memenv)
throw wxe_badarg(index);
void * temp = memenv->ref2ptr[index];
- if((index < memenv->next) && ((index == 0) || (temp > NULL))) {
+ if((index < memenv->next) && ((index == 0) || (temp != (void *) NULL))) {
ptrMap::iterator it;
it = ptr2ref.find(temp);
if(it != ptr2ref.end()) {
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index f4d84ab163..0667af7868 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2016</year>
+ <year>2016</year><year>2017</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -293,6 +293,13 @@ StateName(EventType, EventContent, Data) ->
<seealso marker="#State Time-Outs">State Time-Outs</seealso>
</item>
<item>
+ Start a
+ <seealso marker="stdlib:gen_statem#type-generic_timeout">
+ generic time-out</seealso>,
+ read more in section
+ <seealso marker="#Generic Time-Outs">Generic Time-Outs</seealso>
+ </item>
+ <item>
Start an
<seealso marker="stdlib:gen_statem#type-event_timeout">event time-out</seealso>,
see more in section
@@ -320,8 +327,9 @@ StateName(EventType, EventContent, Data) ->
<c>gen_statem(3)</c>
</seealso>
manual page.
- You can, for example, reply to many callers
- and generate multiple next events to handle.
+ You can, for example, reply to many callers,
+ generate multiple next events,
+ and set time-outs to relative or absolute times.
</p>
</section>
@@ -369,6 +377,14 @@ StateName(EventType, EventContent, Data) ->
</seealso>
state timer timing out.
</item>
+ <tag><c>{timeout,Name}</c></tag>
+ <item>
+ Generated by state transition action
+ <seealso marker="stdlib:gen_statem#type-generic_timeout">
+ <c>{{timeout,Name},Time,EventContent}</c>
+ </seealso>
+ generic timer timing out.
+ </item>
<tag><c>timeout</c></tag>
<item>
Generated by state transition action
@@ -450,7 +466,7 @@ locked(
[Digit] ->
do_unlock(),
{next_state, open, Data#{remaining := Code},
- [{state_timeout,10000,lock}];
+ [{state_timeout,10000,lock}]};
[Digit|Rest] -> % Incomplete
{next_state, locked, Data#{remaining := Rest}};
_Wrong ->
@@ -779,7 +795,7 @@ handle_event(cast, {button,Digit}, State, #{code := Code} = Data) ->
[Digit] -> % Complete
do_unlock(),
{next_state, open, Data#{remaining := Code},
- [{state_timeout,10000,lock}};
+ [{state_timeout,10000,lock}]};
[Digit|Rest] -> % Incomplete
{keep_state, Data#{remaining := Rest}};
[_|_] -> % Wrong
@@ -873,7 +889,7 @@ stop() ->
<marker id="Event Time-Outs" />
<title>Event Time-Outs</title>
<p>
- A timeout feature inherited from <c>gen_statem</c>'s predecessor
+ A time-out feature inherited from <c>gen_statem</c>'s predecessor
<seealso marker="stdlib:gen_fsm"><c>gen_fsm</c></seealso>,
is an event time-out, that is,
if an event arrives the timer is cancelled.
@@ -906,24 +922,24 @@ locked(
...
]]></code>
<p>
- Whenever we receive a button event we start an event timeout
+ Whenever we receive a button event we start an event time-out
of 30 seconds, and if we get an event type <c>timeout</c>
we reset the remaining code sequence.
</p>
<p>
- An event timeout is cancelled by any other event so you either
- get some other event or the timeout event. It is therefore
- not possible nor needed to cancel or restart an event timeout.
+ An event time-out is cancelled by any other event so you either
+ get some other event or the time-out event. It is therefore
+ not possible nor needed to cancel or restart an event time-out.
Whatever event you act on has already cancelled
- the event timeout...
+ the event time-out...
</p>
</section>
<!-- =================================================================== -->
<section>
- <marker id="Erlang Timers" />
- <title>Erlang Timers</title>
+ <marker id="Generic Time-Outs" />
+ <title>Generic Time-Outs</title>
<p>
The previous example of state time-outs only work if
the state machine stays in the same state during the
@@ -934,13 +950,68 @@ locked(
You may want to start a timer in one state and respond
to the time-out in another, maybe cancel the time-out
without changing states, or perhaps run multiple
- time-outs in parallel. All this can be accomplished
- with Erlang Timers:
+ time-outs in parallel. All this can be accomplished with
+ <seealso marker="stdlib:gen_statem#type-generic_timeout">generic time-outs</seealso>.
+ They may look a little bit like
+ <seealso marker="stdlib:gen_statem#type-event_timeout">event time-outs</seealso>
+ but contain a name to allow for any number of them simultaneously
+ and they are not automatically cancelled.
+ </p>
+ <p>
+ Here is how to accomplish the state time-out
+ in the previous example by instead using a generic time-out
+ named <c>open_tm</c>:
+ </p>
+ <code type="erl"><![CDATA[
+...
+locked(
+ cast, {button,Digit},
+ #{code := Code, remaining := Remaining} = Data) ->
+ case Remaining of
+ [Digit] ->
+ do_unlock(),
+ {next_state, open, Data#{remaining := Code},
+ [{{timeout,open_tm},10000,lock}]};
+...
+
+open({timeout,open_tm}, lock, Data) ->
+ do_lock(),
+ {next_state,locked,Data};
+open(cast, {button,_}, Data) ->
+ {keep_state,Data};
+...
+ ]]></code>
+ <p>
+ Just as
+ <seealso marker="#State Time-Outs">state time-outs</seealso>
+ you can restart or cancel a specific generic time-out
+ by setting it to a new time or <c>infinity</c>.
+ </p>
+ <p>
+ Another way to handle a late time-out can be to not cancel it,
+ but to ignore it if it arrives in a state
+ where it is known to be late.
+ </p>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <marker id="Erlang Timers" />
+ <title>Erlang Timers</title>
+ <p>
+ The most versatile way to handle time-outs is to use
+ Erlang Timers; see
<seealso marker="erts:erlang#start_timer/4"><c>erlang:start_timer3,4</c></seealso>.
+ Most time-out tasks can be performed with the
+ time-out features in <c>gen_statem</c>,
+ but an example of one that can not is if you should need
+ the return value from
+ <seealso marker="erts:erlang#cancel_timer/2"><c>erlang:cancel_timer(Tref)</c></seealso>, that is; the remaining time of the timer.
</p>
<p>
Here is how to accomplish the state time-out
- in the previous example by insted using an Erlang Timer:
+ in the previous example by instead using an Erlang Timer:
</p>
<code type="erl"><![CDATA[
...
@@ -1596,7 +1667,7 @@ handle_event(
{call,From}, code_length,
{_StateName,_LockButton}, #{code := Code}) ->
{keep_state_and_data,
- [{reply,From,length(Code)}]};
+ [{reply,From,length(Code)}]};
%%
%% State: locked
handle_event(
@@ -1636,7 +1707,7 @@ handle_event(
if
Digit =:= LockButton ->
{next_state, {locked,LockButton}, Data,
- [{reply,From,locked}]);
+ [{reply,From,locked}]};
true ->
{keep_state_and_data,
[postpone]}
@@ -1710,10 +1781,10 @@ handle_event(
EventType, EventContent,
{open,LockButton}, Data) ->
case {EventType, EventContent} of
- {enter, _OldState} ->
- do_unlock(),
- {keep_state_and_data,
- [{state_timeout,10000,lock},hibernate]};
+ {enter, _OldState} ->
+ do_unlock(),
+ {keep_state_and_data,
+ [{state_timeout,10000,lock},hibernate]};
...
]]></code>
<p>
diff --git a/system/doc/efficiency_guide/processes.xml b/system/doc/efficiency_guide/processes.xml
index afa4325d8e..3b64c863ff 100644
--- a/system/doc/efficiency_guide/processes.xml
+++ b/system/doc/efficiency_guide/processes.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2001</year><year>2016</year>
+ <year>2001</year><year>2017</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -222,7 +222,7 @@ kilo_byte(N, Acc) ->
<pre>
4> <input>T = ets:new(tab, []).</input>
-17
+#Ref&lt;0.1662103692.2407923716.214181>
5> <input>ets:insert(T, {key,efficiency_guide:kilo_byte()}).</input>
true
6> <input>erts_debug:size(element(2, hd(ets:lookup(T, key)))).</input>