aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--erts/emulator/beam/erl_driver.h2
-rwxr-xr-xerts/emulator/utils/gen_git_version6
-rw-r--r--erts/etc/unix/etp-commands.in8
-rw-r--r--erts/vsn.mk2
-rw-r--r--lib/compiler/src/cerl.erl23
-rw-r--r--lib/dialyzer/doc/src/dialyzer.xml109
-rw-r--r--lib/dialyzer/src/dialyzer_cl_parse.erl13
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/maps_difftype3
-rw-r--r--lib/dialyzer/test/small_SUITE_data/src/maps_difftype.erl11
-rw-r--r--lib/dialyzer/test/small_SUITE_data/src/remote_field2.erl17
-rw-r--r--lib/diameter/doc/src/diameter.xml2
-rw-r--r--lib/diameter/doc/src/notes.xml2
-rw-r--r--lib/diameter/examples/code/client.erl2
-rw-r--r--lib/diameter/examples/code/client_cb.erl14
-rw-r--r--lib/diameter/examples/code/redirect_cb.erl8
-rw-r--r--lib/diameter/examples/code/relay_cb.erl8
-rw-r--r--lib/diameter/examples/code/server_cb.erl53
-rw-r--r--lib/diameter/include/diameter.hrl5
-rw-r--r--lib/diameter/include/diameter_gen.hrl117
-rw-r--r--lib/diameter/src/base/diameter_codec.erl196
-rw-r--r--lib/diameter/src/base/diameter_config.erl4
-rw-r--r--lib/diameter/src/base/diameter_lib.erl56
-rw-r--r--lib/diameter/src/base/diameter_peer_fsm.erl158
-rw-r--r--lib/diameter/src/base/diameter_service.erl84
-rw-r--r--lib/diameter/src/base/diameter_traffic.erl215
-rw-r--r--lib/diameter/src/base/diameter_watchdog.erl89
-rw-r--r--lib/diameter/src/compiler/diameter_dict_util.erl9
-rw-r--r--lib/diameter/src/diameter.appup.src28
-rw-r--r--lib/diameter/src/transport/diameter_sctp.erl2
-rw-r--r--lib/diameter/test/diameter_compiler_SUITE.erl15
-rw-r--r--lib/diameter/test/diameter_dpr_SUITE.erl8
-rw-r--r--lib/diameter/test/diameter_examples_SUITE.erl85
-rw-r--r--lib/diameter/test/diameter_failover_SUITE.erl8
-rw-r--r--lib/diameter/test/diameter_traffic_SUITE.erl85
-rw-r--r--lib/diameter/vsn.mk2
-rw-r--r--lib/erl_interface/include/ei.h2
-rw-r--r--lib/hipe/cerl/cerl_prettypr.erl24
-rw-r--r--lib/hipe/cerl/erl_types.erl4
-rw-r--r--lib/inets/doc/src/notes.xml34
-rw-r--r--lib/inets/src/http_client/httpc_handler.erl12
-rw-r--r--lib/inets/src/http_lib/http_internal.hrl4
-rw-r--r--lib/inets/src/http_server/httpd_request.erl204
-rw-r--r--lib/inets/src/http_server/httpd_request_handler.erl31
-rw-r--r--lib/inets/src/inets_app/inets.appup.src13
-rw-r--r--lib/inets/test/http_format_SUITE.erl15
-rw-r--r--lib/inets/test/httpc_SUITE.erl44
-rw-r--r--lib/inets/test/httpd_basic_SUITE.erl317
-rw-r--r--lib/inets/test/httpd_test_lib.erl2
-rw-r--r--lib/inets/vsn.mk2
-rw-r--r--lib/mnesia/src/mnesia_controller.erl9
-rw-r--r--lib/mnesia/src/mnesia_locker.erl9
-rw-r--r--lib/mnesia/test/mnesia_qlc_test.erl2
-rw-r--r--lib/mnesia/test/mnesia_test_lib.hrl35
-rw-r--r--lib/mnesia/test/mnesia_trans_access_test.erl7
-rw-r--r--lib/observer/src/cdv_timer_cb.erl7
-rw-r--r--lib/observer/src/cdv_virtual_list_wx.erl11
-rw-r--r--lib/observer/src/crashdump_viewer.erl40
-rw-r--r--lib/observer/src/crashdump_viewer.hrl1
-rw-r--r--lib/observer/src/observer_tv_table.erl4
-rw-r--r--lib/observer/src/observer_wx.erl4
-rw-r--r--lib/observer/test/crashdump_helper.erl4
-rw-r--r--lib/observer/test/crashdump_viewer_SUITE.erl10
-rw-r--r--lib/observer/test/observer_SUITE.erl40
-rw-r--r--lib/ssh/doc/src/notes.xml32
-rw-r--r--lib/ssh/src/ssh.appup.src4
-rw-r--r--lib/ssh/src/ssh_io.erl2
-rw-r--r--lib/ssh/test/ssh_basic_SUITE.erl3
-rw-r--r--lib/ssh/vsn.mk2
-rw-r--r--lib/ssl/src/ssl.appup.src24
-rw-r--r--lib/ssl/src/ssl.erl238
-rw-r--r--lib/ssl/src/ssl_cipher.erl5
-rw-r--r--lib/ssl/src/ssl_connection.erl133
-rw-r--r--lib/ssl/src/ssl_connection.hrl3
-rw-r--r--lib/ssl/src/ssl_record.hrl2
-rw-r--r--lib/ssl/src/ssl_socket.erl54
-rw-r--r--lib/ssl/src/tls_connection.erl111
-rw-r--r--lib/ssl/src/tls_v1.erl52
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl22
-rw-r--r--lib/ssl/test/ssl_test_lib.erl52
-rw-r--r--lib/ssl/test/ssl_to_openssl_SUITE.erl2
-rw-r--r--lib/ssl/vsn.mk2
-rw-r--r--lib/stdlib/doc/src/maps.xml20
-rw-r--r--lib/stdlib/src/maps.erl18
-rw-r--r--lib/stdlib/test/Makefile3
-rw-r--r--lib/stdlib/test/maps_SUITE.erl69
-rw-r--r--lib/syntax_tools/src/erl_syntax.erl4
-rw-r--r--lib/syntax_tools/test/syntax_tools_SUITE.erl14
-rw-r--r--otp_versions.table1
-rw-r--r--system/doc/reference_manual/expressions.xml238
-rw-r--r--system/doc/reference_manual/maps.xml274
-rw-r--r--system/doc/reference_manual/part.xml1
-rw-r--r--system/doc/reference_manual/xmlfiles.mk1
92 files changed, 2275 insertions, 1451 deletions
diff --git a/erts/emulator/beam/erl_driver.h b/erts/emulator/beam/erl_driver.h
index 3ecb379326..5ced8c5ca0 100644
--- a/erts/emulator/beam/erl_driver.h
+++ b/erts/emulator/beam/erl_driver.h
@@ -198,7 +198,7 @@ typedef long long ErlDrvSInt64;
#error No 64-bit integer type
#endif
-#if defined(__WIN32__)
+#if defined(__WIN32__) || defined(_WIN32)
typedef ErlDrvUInt ErlDrvSizeT;
typedef ErlDrvSInt ErlDrvSSizeT;
#else
diff --git a/erts/emulator/utils/gen_git_version b/erts/emulator/utils/gen_git_version
index ef06a4b8e2..9faf015b62 100755
--- a/erts/emulator/utils/gen_git_version
+++ b/erts/emulator/utils/gen_git_version
@@ -5,9 +5,9 @@ OUTPUT_FILE=$1
if command -v git 2>&1 >/dev/null &&
test -d $ERL_TOP/.git -o -f $ERL_TOP/.git
then
- VSN=`git describe --match "OTP_R[0-9][0-9][A-B]*" HEAD`
+ VSN=`git describe --match "OTP-[0-9]*" HEAD`
case "$VSN" in
- OTP_R*-g*)
+ OTP-*-g*)
VSN=`echo $VSN | sed -e 's/.*-g\\(.*\\)/\\1/g'` ;;
*) VSN="na" ;;
esac
@@ -36,4 +36,4 @@ then
fi
exit 0
fi
-exit 1 \ No newline at end of file
+exit 1
diff --git a/erts/etc/unix/etp-commands.in b/erts/etc/unix/etp-commands.in
index ed90e26024..19c92681d0 100644
--- a/erts/etc/unix/etp-commands.in
+++ b/erts/etc/unix/etp-commands.in
@@ -1065,7 +1065,9 @@ define etp-cp-1
set $etp_cp_mid = $etp_cp_low + ($etp_cp_high-$etp_cp_low)/2
end
if $etp_cp_p
- set $etp_cp_low = (Eterm**)($etp_cp_p->start + 8)
+ # 12 = MI_FUNCTIONS
+ set $etp_cp_low = (Eterm**)($etp_cp_p->start + 12)
+ # 0 = MI_NUM_FUNCTIONS
set $etp_cp_high = $etp_cp_low +$etp_cp_p->start[0]
set $etp_cp_p = 0
while $etp_cp_low < $etp_cp_high
@@ -3277,7 +3279,7 @@ define etp-block-size-1
else
set $etp_MBC_ABLK_OFFSET_SHIFT = (32 - 9)
end
- set $etp_MBC_ABLK_SZ_MASK = ~(~0 << $etp_MBC_ABLK_OFFSET_SHIFT) & ~7
+ set $etp_MBC_ABLK_SZ_MASK = ((UWord)1 << $etp_MBC_ABLK_OFFSET_SHIFT) - 1 - 7
end
set $etp_blk_sz = ($arg0)->bhdr & $etp_MBC_ABLK_SZ_MASK
end
@@ -3300,7 +3302,7 @@ define etp-block2mbc-1
set $etp_MBC_ABLK_OFFSET_SHIFT = (32 - 9)
end
end
- set $etp_mbc = (Carrier_t*) ((((UWord)($arg0)) & (~0 << 18)) - ((($arg0)->bhdr >> $etp_MBC_ABLK_OFFSET_SHIFT) << 18))
+ set $etp_mbc = (Carrier_t*) ((((UWord)($arg0) >> 18) - (($arg0)->bhdr >> $etp_MBC_ABLK_OFFSET_SHIFT)) << 18)
end
end
diff --git a/erts/vsn.mk b/erts/vsn.mk
index 2e773079f3..fff334c89f 100644
--- a/erts/vsn.mk
+++ b/erts/vsn.mk
@@ -17,7 +17,7 @@
# %CopyrightEnd%
#
-VSN = 6.1
+VSN = 6.0.2
# Port number 4365 in 4.2
# Port number 4366 in 4.3
diff --git a/lib/compiler/src/cerl.erl b/lib/compiler/src/cerl.erl
index 54eac20ac4..9d6768b157 100644
--- a/lib/compiler/src/cerl.erl
+++ b/lib/compiler/src/cerl.erl
@@ -126,9 +126,11 @@
map_es/1,
map_arg/1,
update_c_map/3,
+ c_map/1, is_c_map_empty/1,
ann_c_map/2, ann_c_map/3,
map_pair_op/1,map_pair_key/1,map_pair_val/1,
update_c_map_pair/4,
+ c_map_pair/2,
ann_c_map_pair/4
]).
@@ -1582,9 +1584,20 @@ map_es(#c_map{es = Es}) ->
-spec map_arg(c_map()) -> c_map() | c_literal().
-map_arg(#c_map{arg = M}) ->
+map_arg(#c_map{arg=M}) ->
M.
+-spec c_map([c_map_pair()]) -> c_map().
+
+c_map(Pairs) ->
+ #c_map{es=Pairs}.
+
+-spec is_c_map_empty(c_map() | c_literal()) -> boolean().
+
+is_c_map_empty(#c_map{ es=[] }) -> true;
+is_c_map_empty(#c_literal{val=M}) when is_map(M),map_size(M) =:= 0 -> true;
+is_c_map_empty(_) -> false.
+
-spec ann_c_map([term()], [cerl()]) -> c_map() | c_literal().
ann_c_map(As,Es) ->
@@ -1644,6 +1657,11 @@ map_pair_key(#c_map_pair{key=K}) -> K.
map_pair_val(#c_map_pair{val=V}) -> V.
map_pair_op(#c_map_pair{op=Op}) -> Op.
+-spec c_map_pair(cerl(), cerl()) -> c_map_pair().
+
+c_map_pair(Key,Val) ->
+ #c_map_pair{op=#c_literal{val=assoc},key=Key,val=Val}.
+
-spec ann_c_map_pair([term()], cerl(), cerl(), cerl()) ->
c_map_pair().
@@ -4245,6 +4263,9 @@ ann_make_tree(As, bitstr, [[V],[S],[U],[T],[Fs]]) ->
ann_c_bitstr(As, V, S, U, T, Fs);
ann_make_tree(As, cons, [[H], [T]]) -> ann_c_cons(As, H, T);
ann_make_tree(As, tuple, [Es]) -> ann_c_tuple(As, Es);
+ann_make_tree(As, map, [Es]) -> ann_c_map(As, Es);
+ann_make_tree(As, map, [[A], Es]) -> ann_c_map(As, A, Es);
+ann_make_tree(As, map_pair, [[Op], [K], [V]]) -> ann_c_map_pair(As, Op, K, V);
ann_make_tree(As, 'let', [Vs, [A], [B]]) -> ann_c_let(As, Vs, A, B);
ann_make_tree(As, seq, [[A], [B]]) -> ann_c_seq(As, A, B);
ann_make_tree(As, apply, [[Op], Es]) -> ann_c_apply(As, Op, Es);
diff --git a/lib/dialyzer/doc/src/dialyzer.xml b/lib/dialyzer/doc/src/dialyzer.xml
index a92b890a80..3de60b2f7a 100644
--- a/lib/dialyzer/doc/src/dialyzer.xml
+++ b/lib/dialyzer/doc/src/dialyzer.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2006</year><year>2013</year>
+ <year>2006</year><year>2014</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -50,33 +50,31 @@
<p>Dialyzer also has a command line version for automated use. Below is a
brief description of the list of its options. The same information can
be obtained by writing</p>
- <code type="none"><![CDATA[
- dialyzer --help
- ]]></code>
+ <code type="none">
+ dialyzer --help</code>
<p>in a shell. Please refer to the GUI description for more details on
the operation of Dialyzer.</p>
<p>The exit status of the command line version is:</p>
- <code type="none"><![CDATA[
+ <code type="none">
0 - No problems were encountered during the analysis and no
warnings were emitted.
1 - Problems were encountered during the analysis.
- 2 - No problems were encountered, but warnings were emitted.
- ]]></code>
+ 2 - No problems were encountered, but warnings were emitted.</code>
<p>Usage:</p>
- <code type="none"><![CDATA[
+ <code type="none">
dialyzer [--help] [--version] [--shell] [--quiet] [--verbose]
[-pa dir]* [--plt plt] [--plts plt*] [-Ddefine]*
- [-I include_dir]* [--output_plt file] [-Wwarn]*
+ [-I include_dir]* [--output_plt file] [-Wwarn]* [--raw]
[--src] [--gui] [files_or_dirs] [-r dirs]
[--apps applications] [-o outfile]
[--build_plt] [--add_to_plt] [--remove_from_plt]
[--check_plt] [--no_check_plt] [--plt_info] [--get_warnings]
- [--no_native] [--fullpath]
- ]]></code>
+ [--dump_callgraph file] [--no_native] [--fullpath]
+ [--statistics]</code>
<p>Options:</p>
<taglist>
<tag><c><![CDATA[files_or_dirs]]></c> (for backwards compatibility also
- as: <c><![CDATA[-c files_or_dirs]]></c></tag>
+ as: <c><![CDATA[-c files_or_dirs]]></c>)</tag>
<item>Use Dialyzer from the command line to detect defects in the
specified files or directories containing <c><![CDATA[.erl]]></c> or
<c><![CDATA[.beam]]></c> files, depending on the type of the
@@ -88,16 +86,14 @@
analysis.</item>
<tag><c><![CDATA[--apps applications]]></c></tag>
<item>Option typically used when building or modifying a plt as in:
- <code type="none"><![CDATA[
- dialyzer --build_plt --apps erts kernel stdlib mnesia ...
- ]]></code>
+ <code type="none">
+ dialyzer --build_plt --apps erts kernel stdlib mnesia ...</code>
to conveniently refer to library applications corresponding to the
Erlang/OTP installation. However, the option is general and can also
be used during analysis in order to refer to Erlang/OTP applications.
In addition, file or directory names can also be included, as in:
- <code type="none"><![CDATA[
- dialyzer --apps inets ssl ./ebin ../other_lib/ebin/my_module.beam
- ]]></code></item>
+ <code type="none">
+ dialyzer --apps inets ssl ./ebin ../other_lib/ebin/my_module.beam</code></item>
<tag><c><![CDATA[-o outfile]]></c> (or
<c><![CDATA[--output outfile]]></c>)</tag>
<item>When using Dialyzer from the command line, send the analysis
@@ -129,24 +125,26 @@
that the plts are disjoint (i.e., do not have any module
appearing in more than one plt).
The plts are created in the usual way:
- <code type="none"><![CDATA[
+ <code type="none">
dialyzer --build_plt --output_plt plt_1 files_to_include
...
- dialyzer --build_plt --output_plt plt_n files_to_include
- ]]></code>
+ dialyzer --build_plt --output_plt plt_n files_to_include</code>
and then can be used in either of the following ways:
- <code type="none"><![CDATA[
- dialyzer files_to_analyze --plts plt_1 ... plt_n
- ]]></code>
+ <code type="none">
+ dialyzer files_to_analyze --plts plt_1 ... plt_n</code>
or:
- <code type="none"><![CDATA[
- dialyzer --plts plt_1 ... plt_n -- files_to_analyze
- ]]></code>
+ <code type="none">
+ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
(Note the -- delimiter in the second case)</item>
<tag><c><![CDATA[-Wwarn]]></c></tag>
<item>A family of options which selectively turn on/off warnings
(for help on the names of warnings use
- <c><![CDATA[dialyzer -Whelp]]></c>).</item>
+ <c><![CDATA[dialyzer -Whelp]]></c>).
+ Note that the options can also be given in the file with a
+ <c>-dialyzer({nowarn_tag, WarningTags})</c> attribute.
+ See <seealso
+ marker="doc/reference_manual:typespec#suppression">Erlang Reference
+ Manual</seealso> for details.</item>
<tag><c><![CDATA[--shell]]></c></tag>
<item>Do not disable the Erlang shell while running the GUI.</item>
<tag><c><![CDATA[--version]]></c> (or <c><![CDATA[-v]]></c>)</tag>
@@ -220,8 +218,6 @@
<item>Suppress warnings for unused functions.</item>
<tag><c><![CDATA[-Wno_improper_lists]]></c></tag>
<item>Suppress warnings for construction of improper lists.</item>
- <tag><c><![CDATA[-Wno_tuple_as_fun]]></c></tag>
- <item>Suppress warnings for using tuples instead of funs.</item>
<tag><c><![CDATA[-Wno_fun_app]]></c></tag>
<item>Suppress warnings for fun applications that will fail.</item>
<tag><c><![CDATA[-Wno_match]]></c></tag>
@@ -229,9 +225,16 @@
match.</item>
<tag><c><![CDATA[-Wno_opaque]]></c></tag>
<item>Suppress warnings for violations of opaqueness of data types.</item>
+ <tag><c><![CDATA[-Wno_fail_call]]></c></tag>
+ <item>Suppress warnings for failing calls.</item>
+ <tag><c><![CDATA[-Wno_contracts]]></c></tag>
+ <item>Suppress warnings about invalid contracts.</item>
<tag><c><![CDATA[-Wno_behaviours]]></c></tag>
<item>Suppress warnings about behaviour callbacks which drift from the
published recommended interfaces.</item>
+ <tag><c><![CDATA[-Wno_undefined_callbacks]]></c></tag>
+ <item>Suppress warnings about behaviours that have no
+ <c>-callback</c> attributes for their callbacks.</item>
<tag><c><![CDATA[-Wunmatched_returns]]></c>***</tag>
<item>Include warnings for function calls which ignore a structured return
value or do not match against one of many possible return
@@ -278,13 +281,13 @@
</type>
<desc>
<p>Dialyzer GUI version.</p>
- <code type="none"><![CDATA[
+ <code type="none">
OptList :: [Option]
Option :: {files, [Filename :: string()]}
| {files_rec, [DirName :: string()]}
| {defines, [{Macro: atom(), Value : term()}]}
- | {from, src_code | byte_code} %% Defaults to byte_code
- | {init_plt, FileName :: string()} %% If changed from default
+ | {from, src_code | byte_code} %% Defaults to byte_code
+ | {init_plt, FileName :: string()} %% If changed from default
| {plts, [FileName :: string()]} %% If changed from default
| {include_dirs, [DirName :: string()]}
| {output_file, FileName :: string()}
@@ -304,14 +307,15 @@ WarnOpts :: no_return
| no_match
| no_opaque
| no_fail_call
+ | no_contracts
+ | no_behaviours
+ | no_undefined_callbacks
+ | unmatched_returns
| error_handling
| race_conditions
- | behaviours
- | unmatched_returns
| overspecs
| underspecs
- | specdiffs
- ]]></code>
+ | specdiffs</code>
</desc>
</func>
<func>
@@ -323,17 +327,30 @@ WarnOpts :: no_return
</type>
<desc>
<p>Dialyzer command line version.</p>
- <code type="none"><![CDATA[
+ <code type="none">
Warnings :: [{Tag, Id, Msg}]
-Tag :: 'warn_return_no_exit' | 'warn_return_only_exit'
- | 'warn_not_called' | 'warn_non_proper_list'
- | 'warn_fun_app' | 'warn_matching'
- | 'warn_failing_call' | 'warn_contract_types'
- | 'warn_contract_syntax' | 'warn_contract_not_equal'
- | 'warn_contract_subtype' | 'warn_contract_supertype'
+Tag :: 'warn_behaviour'
+ | 'warn_bin_construction'
+ | 'warn_callgraph'
+ | 'warn_contract_not_equal'
+ | 'warn_contract_range'
+ | 'warn_contract_subtype'
+ | 'warn_contract_supertype'
+ | 'warn_contract_syntax'
+ | 'warn_contract_types'
+ | 'warn_failing_call'
+ | 'warn_fun_app'
+ | 'warn_matching'
+ | 'warn_non_proper_list'
+ | 'warn_not_called'
+ | 'warn_opaque'
+ | 'warn_race_condition'
+ | 'warn_return_no_exit'
+ | 'warn_return_only_exit'
+ | 'warn_umatched_return'
+ | 'warn_undefined_callbacks'
Id = {File :: string(), Line :: integer()}
-Msg = msg() -- Undefined
-]]></code>
+Msg = msg() -- Undefined</code>
</desc>
</func>
<func>
diff --git a/lib/dialyzer/src/dialyzer_cl_parse.erl b/lib/dialyzer/src/dialyzer_cl_parse.erl
index db27b2037d..04ce0e8bc3 100644
--- a/lib/dialyzer/src/dialyzer_cl_parse.erl
+++ b/lib/dialyzer/src/dialyzer_cl_parse.erl
@@ -2,7 +2,7 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -357,12 +357,13 @@ help_warnings() ->
help_message() ->
S = "Usage: dialyzer [--help] [--version] [--shell] [--quiet] [--verbose]
[-pa dir]* [--plt plt] [--plts plt*] [-Ddefine]*
- [-I include_dir]* [--output_plt file] [-Wwarn]*
+ [-I include_dir]* [--output_plt file] [-Wwarn]* [--raw]
[--src] [--gui] [files_or_dirs] [-r dirs]
[--apps applications] [-o outfile]
[--build_plt] [--add_to_plt] [--remove_from_plt]
[--check_plt] [--no_check_plt] [--plt_info] [--get_warnings]
- [--no_native] [--fullpath] [--statistics]
+ [--dump_callgraph file] [--no_native] [--fullpath]
+ [--statistics]
Options:
files_or_dirs (for backwards compatibility also as: -c files_or_dirs)
Use Dialyzer from the command line to detect defects in the
@@ -495,14 +496,16 @@ warning_options_msg() ->
Suppress warnings for unused functions.
-Wno_improper_lists
Suppress warnings for construction of improper lists.
- -Wno_tuple_as_fun
- Suppress warnings for using tuples instead of funs.
-Wno_fun_app
Suppress warnings for fun applications that will fail.
-Wno_match
Suppress warnings for patterns that are unused or cannot match.
-Wno_opaque
Suppress warnings for violations of opaqueness of data types.
+ -Wno_fail_call
+ Suppress warnings for failing calls.
+ -Wno_contracts
+ Suppress warnings about invalid contracts.
-Wno_behaviours
Suppress warnings about behaviour callbacks which drift from the published
recommended interfaces.
diff --git a/lib/dialyzer/test/small_SUITE_data/results/maps_difftype b/lib/dialyzer/test/small_SUITE_data/results/maps_difftype
new file mode 100644
index 0000000000..8980321135
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/results/maps_difftype
@@ -0,0 +1,3 @@
+
+maps_difftype.erl:10: Function empty_mismatch/1 has no local return
+maps_difftype.erl:11: The pattern ~{}~ can never match the type tuple()
diff --git a/lib/dialyzer/test/small_SUITE_data/src/maps_difftype.erl b/lib/dialyzer/test/small_SUITE_data/src/maps_difftype.erl
new file mode 100644
index 0000000000..19e61a7944
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/src/maps_difftype.erl
@@ -0,0 +1,11 @@
+%%
+%% File: maps_difftype.erl
+%% Author: Björn-Egil Dahlberg
+%% Created: 2014-04-29
+%%
+-module(maps_difftype).
+
+-export([empty_mismatch/1]).
+
+empty_mismatch(Tuple) when is_tuple(Tuple) ->
+ case Tuple of #{} -> ok end.
diff --git a/lib/dialyzer/test/small_SUITE_data/src/remote_field2.erl b/lib/dialyzer/test/small_SUITE_data/src/remote_field2.erl
new file mode 100644
index 0000000000..35687e22ec
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/src/remote_field2.erl
@@ -0,0 +1,17 @@
+-module(remote_field2).
+
+-export([handle_cast/2]).
+
+-record(state, {tcp_socket :: inet:socket()}).
+
+-spec handle_cast(_,_) ->
+ {noreply,_} |
+ {stop,{shutdown,connection_closed},
+ #state{tcp_socket :: port()}}.
+handle_cast({send, Message}, #state{tcp_socket = TCPSocket} = State) ->
+ case gen_tcp:send(TCPSocket, Message) of
+ ok ->
+ {noreply, State};
+ {error, closed} ->
+ {stop, {shutdown, connection_closed}, State}
+ end.
diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml
index 7d6a28e51c..ab9ad25a3a 100644
--- a/lib/diameter/doc/src/diameter.xml
+++ b/lib/diameter/doc/src/diameter.xml
@@ -227,7 +227,7 @@ question is as if a callback had taken place and returned
<c>{error, failure}</c>.</p>
<p>
-Defaults to <c>report</c> if unspecified.</p>
+Defaults to <c>discard</c> if unspecified.</p>
</item>
<tag><c>{request_errors, answer_3xxx|answer|callback}</c></tag>
diff --git a/lib/diameter/doc/src/notes.xml b/lib/diameter/doc/src/notes.xml
index 675ffcfd18..68e69dbfeb 100644
--- a/lib/diameter/doc/src/notes.xml
+++ b/lib/diameter/doc/src/notes.xml
@@ -238,7 +238,7 @@ first.</p>
<section><title>diameter 1.4.4</title>
- <section><title>Known Bugs and Problems</title>
+ <section><title>Fixed Bugs and Malfunctions</title>
<list>
<item>
<p>
diff --git a/lib/diameter/examples/code/client.erl b/lib/diameter/examples/code/client.erl
index bfe71b0e56..46eb4a55db 100644
--- a/lib/diameter/examples/code/client.erl
+++ b/lib/diameter/examples/code/client.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
diff --git a/lib/diameter/examples/code/client_cb.erl b/lib/diameter/examples/code/client_cb.erl
index ee3dcb2fec..843cdd9262 100644
--- a/lib/diameter/examples/code/client_cb.erl
+++ b/lib/diameter/examples/code/client_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -77,23 +77,11 @@ prepare_retransmit(Packet, SvcName, Peer) ->
%% handle_answer/4
-%% Since client.erl has detached the call when using the list
-%% encoding and not otherwise, output to the terminal in the
-%% the former case, return in the latter.
-
-handle_answer(#diameter_packet{msg = Msg}, Request, _SvcName, _Peer)
- when is_list(Request) ->
- io:format("answer: ~p~n", [Msg]);
-
handle_answer(#diameter_packet{msg = Msg}, _Request, _SvcName, _Peer) ->
{ok, Msg}.
%% handle_error/4
-handle_error(Reason, Request, _SvcName, _Peer)
- when is_list(Request) ->
- io:format("error: ~p~n", [Reason]);
-
handle_error(Reason, _Request, _SvcName, _Peer) ->
{error, Reason}.
diff --git a/lib/diameter/examples/code/redirect_cb.erl b/lib/diameter/examples/code/redirect_cb.erl
index 69836774a1..8d98b0d2df 100644
--- a/lib/diameter/examples/code/redirect_cb.erl
+++ b/lib/diameter/examples/code/redirect_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -34,12 +34,10 @@
-define(UNEXPECTED, erlang:error({unexpected, ?MODULE, ?LINE})).
-peer_up(_SvcName, {PeerRef, _}, State) ->
- io:format("up: ~p~n", [PeerRef]),
+peer_up(_SvcName, _Peer, State) ->
State.
-peer_down(_SvcName, {PeerRef, _}, State) ->
- io:format("down: ~p~n", [PeerRef]),
+peer_down(_SvcName, _Peer, State) ->
State.
pick_peer(_, _, _SvcName, _State) ->
diff --git a/lib/diameter/examples/code/relay_cb.erl b/lib/diameter/examples/code/relay_cb.erl
index 9f9cd8d5ae..68798014e6 100644
--- a/lib/diameter/examples/code/relay_cb.erl
+++ b/lib/diameter/examples/code/relay_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -32,12 +32,10 @@
handle_error/5,
handle_request/3]).
-peer_up(_SvcName, {PeerRef, _}, State) ->
- io:format("up: ~p~n", [PeerRef]),
+peer_up(_SvcName, _Peer, State) ->
State.
-peer_down(_SvcName, {PeerRef, _}, State) ->
- io:format("down: ~p~n", [PeerRef]),
+peer_down(_SvcName, _Peer, State) ->
State.
%% Returning 'relay' from handle_request causes diameter to resend the
diff --git a/lib/diameter/examples/code/server_cb.erl b/lib/diameter/examples/code/server_cb.erl
index 0f6eb32ed6..9d8d395d06 100644
--- a/lib/diameter/examples/code/server_cb.erl
+++ b/lib/diameter/examples/code/server_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -38,12 +38,10 @@
-define(UNEXPECTED, erlang:error({unexpected, ?MODULE, ?LINE})).
-peer_up(_SvcName, {PeerRef, _}, State) ->
- io:format("up: ~p~n", [PeerRef]),
+peer_up(_SvcName, _Peer, State) ->
State.
-peer_down(_SvcName, {PeerRef, _}, State) ->
- io:format("down: ~p~n", [PeerRef]),
+peer_down(_SvcName, _Peer, State) ->
State.
pick_peer(_, _, _SvcName, _State) ->
@@ -68,10 +66,13 @@ handle_request(#diameter_packet{msg = Req, errors = []}, _SvcName, {_, Caps})
origin_realm = {OR,_}}
= Caps,
#diameter_base_RAR{'Session-Id' = Id,
- 'Re-Auth-Request-Type' = RT}
+ 'Re-Auth-Request-Type' = Type}
= Req,
- {reply, answer(RT, Id, OH, OR)};
+ {reply, #diameter_base_RAA{'Result-Code' = rc(Type),
+ 'Origin-Host' = OH,
+ 'Origin-Realm' = OR,
+ 'Session-Id' = Id}};
%% ... or one that wasn't. 3xxx errors are answered by diameter itself
%% but these are 5xxx errors for which we must contruct a reply.
@@ -84,32 +85,18 @@ handle_request(#diameter_packet{msg = Req}, _SvcName, {_, Caps})
#diameter_base_RAR{'Session-Id' = Id}
= Req,
- Ans = #diameter_base_RAA{'Origin-Host' = OH,
- 'Origin-Realm' = OR,
- 'Session-Id' = Id},
+ {reply, #diameter_base_RAA{'Origin-Host' = OH,
+ 'Origin-Realm' = OR,
+ 'Session-Id' = Id}};
- {reply, Ans};
+%% Answer that any other message is unsupported.
+handle_request(#diameter_packet{}, _SvcName, _) ->
+ {answer_message, 3001}. %% DIAMETER_COMMAND_UNSUPPORTED
-%% Should really reply to other base messages that we don't support
-%% but simply discard them instead.
-handle_request(#diameter_packet{}, _SvcName, {_,_}) ->
- discard.
+%% Map Re-Auth-Request-Type to Result-Code just for the purpose of
+%% generating different answers.
-%% ---------------------------------------------------------------------------
-
-%% Answer using the record or list encoding depending on
-%% Re-Auth-Request-Type. This is just as an example. You would
-%% typically just choose one, and this has nothing to do with the how
-%% client.erl sends.
-
-answer(0, Id, OH, OR) ->
- #diameter_base_RAA{'Result-Code' = 2001, %% DIAMETER_SUCCESS
- 'Origin-Host' = OH,
- 'Origin-Realm' = OR,
- 'Session-Id' = Id};
-
-answer(_, Id, OH, OR) ->
- ['RAA', {'Result-Code', 5012}, %% DIAMETER_UNABLE_TO_COMPLY
- {'Origin-Host', OH},
- {'Origin-Realm', OR},
- {'Session-Id', Id}].
+rc(0) ->
+ 2001; %% DIAMETER_SUCCESS
+rc(_) ->
+ 5012. %% DIAMETER_UNABLE_TO_COMPLY
diff --git a/lib/diameter/include/diameter.hrl b/lib/diameter/include/diameter.hrl
index 5a40e42300..c2c271a9a3 100644
--- a/lib/diameter/include/diameter.hrl
+++ b/lib/diameter/include/diameter.hrl
@@ -126,7 +126,7 @@
default,
extra = []}).
-%% The diameter service and diameter_apps records are only passed
+%% The diameter service and diameter_app records are only passed
%% through the transport interface when starting a transport process,
%% although typically a transport implementation will (and probably
%% should) only be interested host_ip_address.
@@ -143,6 +143,7 @@
init_state, %% option 'state', initial callback state
id, %% 32-bit unsigned application identifier = Dict:id()
mutable = false, %% boolean(), do traffic callbacks modify state?
- options = [{answer_errors, report}, %% | callback | discard
+ options = [{answer_errors, discard}, %% | callback | report
{request_errors, answer_3xxx}]}). %% | callback | answer
+
-endif. %% -ifdef(diameter_hrl).
diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl
index c8f706dc3e..7e91ce375f 100644
--- a/lib/diameter/include/diameter_gen.hrl
+++ b/lib/diameter/include/diameter_gen.hrl
@@ -30,6 +30,10 @@
%% error or not. See is_strict/0.
-define(STRICT_KEY, strict).
+%% Key that says whether or not we should do a best-effort decode
+%% within Failed-AVP.
+-define(FAILED_KEY, failed).
+
-type parent_name() :: atom(). %% parent = Message or AVP
-type parent_record() :: tuple(). %%
-type avp_name() :: atom().
@@ -286,15 +290,7 @@ decode(Name, 'AVP', Avp, Acc) ->
%% d/3
-%% Don't try to decode the value of a Failed-AVP component since it
-%% probably won't. Note that matching on 'Failed-AVP' assumes that
-%% this is the RFC AVP, with code 279. Strictly, this doesn't need to
-%% be the case, so we're assuming no one defines another Failed-AVP.
-d('Failed-AVP' = Name, Avp, Acc) ->
- decode_AVP(Name, Avp, Acc);
-
-%% Or try to decode.
-d(Name, Avp, {Avps, Acc}) ->
+d(Name, Avp, Acc) ->
#diameter_avp{name = AvpName,
data = Data,
type = Type,
@@ -307,51 +303,81 @@ d(Name, Avp, {Avps, Acc}) ->
%% value around through the entire decode. The solution here is
%% simple in comparison, both to implement and to understand.
- Reset = relax(Type, M),
+ Strict = relax(Type, M),
+ %% Use the process dictionary again to keep track of whether we're
+ %% decoding within Failed-AVP and should ignore decode errors
+ %% altogether.
+
+ Failed = relax(Name), %% Not AvpName or else a failed Failed-AVP
+ %% decode is packed into 'AVP'.
try avp(decode, Data, AvpName) of
V ->
+ {Avps, T} = Acc,
{H, A} = ungroup(V, Avp),
- {[H | Avps], pack_avp(Name, A, Acc)}
+ {[H | Avps], pack_avp(Name, A, T)}
catch
error: Reason ->
- %% Failures here won't be visible since they're a "normal"
- %% occurrence if the peer sends a faulty AVP that we need to
- %% respond sensibly to. Log the occurence for traceability,
- %% but the peer will also receive info in the resulting
- %% answer-message.
- diameter_lib:log({decode, failure},
- ?MODULE,
- ?LINE,
- {Reason, Avp, erlang:get_stacktrace()}),
- {Rec, Failed} = Acc,
- {[Avp|Avps], {Rec, [rc(Reason, Avp) | Failed]}}
+ d(undefined == Failed orelse is_failed(), Reason, Name, Avp, Acc)
after
- relax(Reset)
+ reset(?STRICT_KEY, Strict),
+ reset(?FAILED_KEY, Failed)
end.
+%% Ignore a decode error within Failed-AVP ...
+d(true, _, Name, Avp, Acc) ->
+ decode_AVP(Name, Avp, Acc);
+
+%% ... or not. Failures here won't be visible since they're a "normal"
+%% occurrence if the peer sends a faulty AVP that we need to respond
+%% sensibly to. Log the occurence for traceability, but the peer will
+%% also receive info in the resulting answer message.
+d(false, Reason, Name, Avp, {Avps, Acc}) ->
+ Stack = diameter_lib:get_stacktrace(),
+ diameter_lib:log(decode_error,
+ ?MODULE,
+ ?LINE,
+ {Reason, Name, Avp#diameter_avp.name, Stack}),
+ {Rec, Failed} = Acc,
+ {[Avp|Avps], {Rec, [rc(Reason, Avp) | Failed]}}.
+
%% Set false in the process dictionary as soon as we see a Grouped AVP
%% that doesn't set the M-bit, so that is_strict() can say whether or
%% not to ignore the M-bit on an encapsulated AVP.
relax('Grouped', M) ->
- V = getr(?STRICT_KEY),
- if V == undefined andalso not M ->
+ case getr(?STRICT_KEY) of
+ undefined when not M ->
putr(?STRICT_KEY, M);
- true ->
+ _ ->
false
end;
relax(_, _) ->
false.
-%% Reset strictness.
-relax(undefined) ->
- eraser(?STRICT_KEY);
-relax(false) ->
- ok.
-
is_strict() ->
false /= getr(?STRICT_KEY).
+%% Set true in the process dictionary as soon as we see Failed-AVP.
+%% Matching on 'Failed-AVP' assumes that this is the RFC AVP.
+%% Strictly, this doesn't need to be the case.
+relax('Failed-AVP') ->
+ case getr(?FAILED_KEY) of
+ undefined ->
+ putr(?FAILED_KEY, true);
+ true = Yes ->
+ Yes
+ end;
+relax(_) ->
+ is_failed().
+
+is_failed() ->
+ true == getr(?FAILED_KEY).
+
+reset(Key, undefined) ->
+ eraser(Key);
+reset(_, _) ->
+ ok.
+
%% decode_AVP/3
%%
%% Don't know this AVP: see if it can be packed in an 'AVP' field
@@ -410,6 +436,23 @@ pack_avp(_, Arity, Avp, Acc) ->
%% pack_AVP/3
+%% Length failure was induced because of a header/payload length
+%% mismatch. The AVP Length is reset to match the received data if
+%% this AVP is encoded in an answer message, since the length is
+%% computed.
+%%
+%% Data is a truncated header if command_code = undefined, otherwise
+%% payload bytes. The former is padded to the length of a header if
+%% the AVP reaches an outgoing encode in diameter_codec.
+%%
+%% RFC 6733 says that an AVP returned with 5014 can contain a minimal
+%% payload for the AVP's type, but in this case we don't know the
+%% type.
+
+pack_AVP(_, #diameter_avp{data = <<0:1, Data/binary>>} = Avp, Acc) ->
+ {Rec, Failed} = Acc,
+ {Rec, [{5014, Avp#diameter_avp{data = Data}} | Failed]};
+
pack_AVP(Name, #diameter_avp{is_mandatory = M} = Avp, Acc) ->
case pack_arity(Name, M) of
0 ->
@@ -422,7 +465,15 @@ pack_AVP(Name, #diameter_avp{is_mandatory = M} = Avp, Acc) ->
%% Give Failed-AVP special treatment since it'll contain any
%% unrecognized mandatory AVP's.
pack_arity(Name, M) ->
- case Name /= 'Failed-AVP' andalso M andalso is_strict() of
+ NF = Name /= 'Failed-AVP' andalso not is_failed(),
+ %% Not testing just Name /= 'Failed-AVP' means we're changing the
+ %% packing of AVPs nested within Failed-AVP, but the point of
+ %% ignoring errors within Failed-AVP is to decode as much as
+ %% possible, and failing because a mandatory AVP couldn't be
+ %% packed into a dedicated field defeats that point. Note that we
+ %% can't just test not is_failed() since this will be 'true' when
+ %% packing an unknown AVP directly within Failed-AVP.
+ case NF andalso M andalso is_strict() of
true ->
0;
false ->
diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl
index 0de4d53973..06a4f5de64 100644
--- a/lib/diameter/src/base/diameter_codec.erl
+++ b/lib/diameter/src/base/diameter_codec.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -70,12 +70,15 @@ encode(Mod, #diameter_packet{} = Pkt) ->
try
e(Mod, Pkt)
catch
+ exit: {Reason, Stack, #diameter_header{} = H} = T ->
+ %% Exit with a header in the reason to let the caller
+ %% count encode errors.
+ ?LOG(encode_error, {Reason, Stack, H}),
+ exit({?MODULE, encode, T});
error: Reason ->
- %% Be verbose since a crash report may be truncated and
- %% encode errors are self-inflicted.
- X = {?MODULE, encode, {Reason, ?STACK}},
- diameter_lib:error_report(X, {?MODULE, encode, [Mod, Pkt]}),
- exit(X)
+ T = {Reason, diameter_lib:get_stacktrace()},
+ ?LOG(encode_error, T),
+ exit({?MODULE, encode, T})
end;
encode(Mod, Msg) ->
@@ -87,53 +90,62 @@ encode(Mod, Msg) ->
msg = Msg}).
e(_, #diameter_packet{msg = [#diameter_header{} = Hdr | As]} = Pkt) ->
- Avps = encode_avps(As),
- Length = size(Avps) + 20,
-
- #diameter_header{version = Vsn,
- cmd_code = Code,
- application_id = Aid,
- hop_by_hop_id = Hid,
- end_to_end_id = Eid}
- = Hdr,
-
- Flags = make_flags(0, Hdr),
-
- Pkt#diameter_packet{header = Hdr,
- bin = <<Vsn:8, Length:24,
- Flags:8, Code:24,
- Aid:32,
- Hid:32,
- Eid:32,
- Avps/binary>>};
+ try encode_avps(As) of
+ Avps ->
+ Length = size(Avps) + 20,
+
+ #diameter_header{version = Vsn,
+ cmd_code = Code,
+ application_id = Aid,
+ hop_by_hop_id = Hid,
+ end_to_end_id = Eid}
+ = Hdr,
+
+ Flags = make_flags(0, Hdr),
+
+ Pkt#diameter_packet{header = Hdr,
+ bin = <<Vsn:8, Length:24,
+ Flags:8, Code:24,
+ Aid:32,
+ Hid:32,
+ Eid:32,
+ Avps/binary>>}
+ catch
+ error: Reason ->
+ exit({Reason, diameter_lib:get_stacktrace(), Hdr})
+ end;
-e(Mod, #diameter_packet{header = Hdr, msg = Msg} = Pkt) ->
+e(Mod, #diameter_packet{header = Hdr0, msg = Msg} = Pkt) ->
#diameter_header{version = Vsn,
hop_by_hop_id = Hid,
end_to_end_id = Eid}
- = Hdr,
+ = Hdr0,
MsgName = rec2msg(Mod, Msg),
- {Code, Flags0, Aid} = msg_header(Mod, MsgName, Hdr),
- Flags = make_flags(Flags0, Hdr),
-
- Avps = encode_avps(Mod, MsgName, values(Msg)),
- Length = size(Avps) + 20,
-
- Pkt#diameter_packet{header = Hdr#diameter_header
- {length = Length,
- cmd_code = Code,
- application_id = Aid,
- is_request = 0 /= ?MASK(7, Flags),
- is_proxiable = 0 /= ?MASK(6, Flags),
- is_error = 0 /= ?MASK(5, Flags),
- is_retransmitted = 0 /= ?MASK(4, Flags)},
- bin = <<Vsn:8, Length:24,
- Flags:8, Code:24,
- Aid:32,
- Hid:32,
- Eid:32,
- Avps/binary>>}.
+ {Code, Flags0, Aid} = msg_header(Mod, MsgName, Hdr0),
+ Flags = make_flags(Flags0, Hdr0),
+ Hdr = Hdr0#diameter_header{cmd_code = Code,
+ application_id = Aid,
+ is_request = 0 /= ?MASK(7, Flags),
+ is_proxiable = 0 /= ?MASK(6, Flags),
+ is_error = 0 /= ?MASK(5, Flags),
+ is_retransmitted = 0 /= ?MASK(4, Flags)},
+ Values = values(Msg),
+
+ try encode_avps(Mod, MsgName, Values) of
+ Avps ->
+ Length = size(Avps) + 20,
+ Pkt#diameter_packet{header = Hdr#diameter_header{length = Length},
+ bin = <<Vsn:8, Length:24,
+ Flags:8, Code:24,
+ Aid:32,
+ Hid:32,
+ Eid:32,
+ Avps/binary>>}
+ catch
+ error: Reason ->
+ exit({Reason, diameter_lib:get_stacktrace(), Hdr})
+ end.
%% make_flags/2
@@ -225,7 +237,7 @@ rec2msg(Mod, Rec) ->
%% Unsuccessfully decoded AVPs will be placed in #diameter_packet.errors.
--spec decode(module(), #diameter_packet{} | bitstring())
+-spec decode(module(), #diameter_packet{} | binary())
-> #diameter_packet{}.
decode(Mod, Pkt) ->
@@ -259,34 +271,34 @@ decode(_, Mod, #diameter_packet{header = Hdr} = Pkt) ->
decode_avps(MsgName, Mod, Pkt, collect_avps(Pkt));
decode(Id, Mod, Bin)
- when is_bitstring(Bin) ->
+ when is_binary(Bin) ->
decode(Id, Mod, #diameter_packet{header = decode_header(Bin), bin = Bin}).
decode_avps(MsgName, Mod, Pkt, {E, Avps}) ->
- ?LOG(invalid, Pkt#diameter_packet.bin),
+ ?LOG(invalid_avp_length, Pkt#diameter_packet.header),
#diameter_packet{errors = Failed}
= P
= decode_avps(MsgName, Mod, Pkt, Avps),
P#diameter_packet{errors = [E | Failed]};
-decode_avps('', Mod, Pkt, Avps) -> %% unknown message ...
- ?LOG(unknown, {Mod, Pkt#diameter_packet.header}),
+decode_avps('', _, Pkt, Avps) -> %% unknown message ...
+ ?LOG(unknown_message, Pkt#diameter_packet.header),
Pkt#diameter_packet{avps = lists:reverse(Avps),
errors = [3001]}; %% DIAMETER_COMMAND_UNSUPPORTED
%% msg = undefined identifies this case.
decode_avps(MsgName, Mod, Pkt, Avps) -> %% ... or not
- {Rec, As, Failed} = Mod:decode_avps(MsgName, Avps),
- ?LOGC([] /= Failed, failed, {Mod, Failed}),
+ {Rec, As, Errors} = Mod:decode_avps(MsgName, Avps),
+ ?LOGC([] /= Errors, decode_errors, Pkt#diameter_packet.header),
Pkt#diameter_packet{msg = Rec,
- errors = Failed,
+ errors = Errors,
avps = As}.
%%% ---------------------------------------------------------------------------
%%% # decode_header/1
%%% ---------------------------------------------------------------------------
--spec decode_header(bitstring())
+-spec decode_header(binary())
-> #diameter_header{}
| false.
@@ -297,7 +309,7 @@ decode_header(<<Version:8,
ApplicationId:32,
HopByHopId:32,
EndToEndId:32,
- _/bitstring>>) ->
+ _/binary>>) ->
<<R:1, P:1, E:1, T:1, _:4>>
= CmdFlags,
%% 3588 (ch 3) says that reserved bits MUST be set to 0 and ignored
@@ -410,7 +422,7 @@ msg_id(#diameter_header{application_id = A,
is_request = R}) ->
{A, C, if R -> 1; true -> 0 end};
-msg_id(<<_:32, Rbit:1, _:7, CmdCode:24, ApplId:32, _/bitstring>>) ->
+msg_id(<<_:32, Rbit:1, _:7, CmdCode:24, ApplId:32, _/binary>>) ->
{ApplId, CmdCode, Rbit}.
%%% ---------------------------------------------------------------------------
@@ -421,17 +433,18 @@ msg_id(<<_:32, Rbit:1, _:7, CmdCode:24, ApplId:32, _/bitstring>>) ->
%% order in the binary. Note also that grouped avp's aren't unraveled,
%% only those at the top level.
--spec collect_avps(#diameter_packet{} | bitstring())
+-spec collect_avps(#diameter_packet{} | binary())
-> [Avp]
| {Error, [Avp]}
when Avp :: #diameter_avp{},
Error :: {5014, #diameter_avp{}}.
collect_avps(#diameter_packet{bin = Bin}) ->
- <<_:20/binary, Avps/bitstring>> = Bin,
+ <<_:20/binary, Avps/binary>> = Bin,
collect_avps(Avps);
-collect_avps(Bin) ->
+collect_avps(Bin)
+ when is_binary(Bin) ->
collect_avps(Bin, 0, []).
collect_avps(<<>>, _, Acc) ->
@@ -461,7 +474,9 @@ collect_avps(Bin, N, Acc) ->
split_avp(Bin) ->
{Code, V, M, P, Len, HdrLen} = split_head(Bin),
- {Data, B} = split_data(Bin, HdrLen, Len - HdrLen),
+
+ <<_:HdrLen/binary, Rest/binary>> = Bin,
+ {Data, B} = split_data(Rest, Len - HdrLen),
{B, #diameter_avp{code = Code,
vendor_id = V,
@@ -471,17 +486,15 @@ split_avp(Bin) ->
%% split_head/1
-split_head(<<Code:32, 1:1, M:1, P:1, _:5, Len:24, V:32, _/bitstring>>) ->
+split_head(<<Code:32, 1:1, M:1, P:1, _:5, Len:24, V:32, _/binary>>) ->
{Code, V, M, P, Len, 12};
-split_head(<<Code:32, 0:1, M:1, P:1, _:5, Len:24, _/bitstring>>) ->
+split_head(<<Code:32, 0:1, M:1, P:1, _:5, Len:24, _/binary>>) ->
{Code, undefined, M, P, Len, 8};
-%% Header is truncated: pack_avp/1 will pad to the minimum header
-%% length.
-split_head(B)
- when is_bitstring(B) ->
- ?THROW({5014, #diameter_avp{data = B}}).
+%% Header is truncated.
+split_head(Bin) ->
+ ?THROW({5014, #diameter_avp{data = Bin}}).
%% 3588:
%%
@@ -516,34 +529,27 @@ split_head(B)
%% split_data/3
-split_data(Bin, HdrLen, Len)
- when 0 =< Len ->
- split_data(Bin, HdrLen, Len, (4 - (Len rem 4)) rem 4);
+split_data(Bin, Len) ->
+ Pad = (4 - (Len rem 4)) rem 4,
-split_data(_, _, _) ->
- invalid_avp_length().
+ %% Len might be negative here, but that ensures the failure of the
+ %% binary match.
-%% split_data/4
-
-split_data(Bin, HdrLen, Len, Pad) ->
case Bin of
- <<_:HdrLen/binary, Data:Len/binary, _:Pad/binary, Rest/bitstring>> ->
+ <<Data:Len/binary, _:Pad/binary, Rest/binary>> ->
{Data, Rest};
_ ->
- invalid_avp_length()
+ %% Header length points past the end of the message. As
+ %% stated in the 6733 text above, it's sufficient to
+ %% return a zero-filled minimal payload if this is a
+ %% request. Do this (in cases that we know the type) by
+ %% inducing a decode failure and letting the dictionary's
+ %% decode (in diameter_gen) deal with it. Here we don't
+ %% know type. If the type isn't known, then the decode
+ %% just strips the extra bit.
+ {<<0:1, Bin/binary>>, <<>>}
end.
-%% invalid_avp_length/0
-%%
-%% AVP Length doesn't mesh with payload. Induce a decode error by
-%% returning a payload that no valid Diameter type can have. This is
-%% so that a known AVP will result in 5014 error with a zero'd
-%% payload. Here we simply don't know how to construct this payload.
-%% (Yes, this solution is an afterthought.)
-
-invalid_avp_length() ->
- {<<0:1>>, <<>>}.
-
%%% ---------------------------------------------------------------------------
%%% # pack_avp/1
%%% ---------------------------------------------------------------------------
@@ -575,17 +581,23 @@ pack_avp(#diameter_avp{data = {Dict, Name, Value}} = A) ->
{Name, Type} = Dict:avp_name(Code, Vid),
pack_avp(A#diameter_avp{data = {Hdr, {Type, Value}}});
+%% ... with a truncated header ...
pack_avp(#diameter_avp{code = undefined, data = B})
- when is_bitstring(B) ->
+ when is_binary(B) ->
%% Reset the AVP Length of an AVP Header resulting from a 5014
%% error. The RFC doesn't explicitly say to do this but the
%% receiver can't correctly extract this and following AVP's
%% without a correct length. On the downside, the header doesn't
%% reveal if the received header has been padded.
Pad = 8*header_length(B) - bit_size(B),
- Len = size(<<H:5/binary, _:24, T/binary>> = <<B/bitstring, 0:Pad>>),
+ Len = size(<<H:5/binary, _:24, T/binary>> = <<B/binary, 0:Pad>>),
<<H/binary, Len:24, T/binary>>;
+%% ... from a dictionary compiled against old code in diameter_gen ...
+%% ... when ignoring errors in Failed-AVP ...
+pack_avp(#diameter_avp{data = <<0:1, B/binary>>} = A) ->
+ pack_avp(A#diameter_avp{data = B});
+
%% ... or as an iolist.
pack_avp(#diameter_avp{code = Code,
vendor_id = V,
diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl
index f5ea459fd0..dd1c9b73bb 100644
--- a/lib/diameter/src/base/diameter_config.erl
+++ b/lib/diameter/src/base/diameter_config.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -753,7 +753,7 @@ app_acc({application, Opts} = T, Acc) ->
Alias = get_opt(alias, Opts, Dict),
ModS = get_opt(state, Opts, Alias),
M = get_opt(call_mutates_state, Opts, false, [true]),
- A = get_opt(answer_errors, Opts, report, [callback, discard]),
+ A = get_opt(answer_errors, Opts, discard, [callback, report]),
P = get_opt(request_errors, Opts, answer_3xxx, [answer, callback]),
[#diameter_app{alias = Alias,
dictionary = Dict,
diff --git a/lib/diameter/src/base/diameter_lib.erl b/lib/diameter/src/base/diameter_lib.erl
index 44d81e2778..5b3a2063f8 100644
--- a/lib/diameter/src/base/diameter_lib.erl
+++ b/lib/diameter/src/base/diameter_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -25,6 +25,8 @@
now_diff/1,
time/1,
eval/1,
+ eval_name/1,
+ get_stacktrace/0,
ipaddr/1,
spawn_opts/2,
wait/1,
@@ -32,6 +34,22 @@
log/4]).
%% ---------------------------------------------------------------------------
+%% # get_stacktrace/0
+%% ---------------------------------------------------------------------------
+
+%% Return a stacktrace with a leading, potentially large, argument
+%% list replaced by an arity. Trace on stacktrace/0 to see the
+%% original.
+
+get_stacktrace() ->
+ stacktrace(erlang:get_stacktrace()).
+
+stacktrace([{M,F,A,L} | T]) when is_list(A) ->
+ [{M, F, length(A), L} | T];
+stacktrace(L) ->
+ L.
+
+%% ---------------------------------------------------------------------------
%% # info_report/2
%% ---------------------------------------------------------------------------
@@ -60,9 +78,17 @@ warning_report(Reason, T) ->
report(fun error_logger:warning_report/1, Reason, T).
report(Fun, Reason, T) ->
- Fun([{why, Reason}, {who, self()}, {what, T}]),
+ Fun(io_lib:format("diameter: ~" ++ fmt(Reason) ++ "~n ~p~n",
+ [Reason, T])),
false.
+fmt(T) ->
+ if is_list(T) ->
+ "s";
+ true ->
+ "p"
+ end.
+
%% ---------------------------------------------------------------------------
%% # now_diff/1
%% ---------------------------------------------------------------------------
@@ -129,8 +155,8 @@ eval({M,F,A}) ->
eval([{M,F,A} | X]) ->
apply(M, F, X ++ A);
-eval([[F|A] | X]) ->
- eval([F | X ++ A]);
+eval([[F|X] | A]) ->
+ eval([F | A ++ X]);
eval([F|A]) ->
apply(F,A);
@@ -142,6 +168,28 @@ eval(F) ->
F().
%% ---------------------------------------------------------------------------
+%% eval_name/1
+%% ---------------------------------------------------------------------------
+
+eval_name({M,F,A}) ->
+ {M, F, length(A)};
+
+eval_name([{M,F,A} | X]) ->
+ {M, F, length(A) + length(X)};
+
+eval_name([[F|A] | X]) ->
+ eval_name([F | X ++ A]);
+
+eval_name([F|_]) ->
+ F;
+
+eval_name({F}) ->
+ eval_name(F);
+
+eval_name(F) ->
+ F.
+
+%% ---------------------------------------------------------------------------
%% # ipaddr/1
%%
%% Parse an IP address.
diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl
index f76bd96c3c..31e570ae20 100644
--- a/lib/diameter/src/base/diameter_peer_fsm.erl
+++ b/lib/diameter/src/base/diameter_peer_fsm.erl
@@ -283,7 +283,7 @@ handle_info(T, #state{} = State) ->
ok ->
{noreply, State};
#state{state = X} = S ->
- ?LOGC(X =/= State#state.state, transition, X),
+ ?LOGC(X /= State#state.state, transition, X),
{noreply, S};
{stop, Reason} ->
?LOG(stop, Reason),
@@ -292,15 +292,12 @@ handle_info(T, #state{} = State) ->
?LOG(stop, T),
{stop, {shutdown, T}, State}
catch
- exit: {diameter_codec, encode, _} = Reason ->
+ exit: {diameter_codec, encode, T} = Reason ->
+ incr_error(send, T, State#state.dictionary),
?LOG(stop, Reason),
- %% diameter_codec:encode/2 emits an error report. Only
- %% indicate the probable reason here.
- diameter_lib:info_report(probable_configuration_error,
- insufficient_capabilities),
{stop, {shutdown, Reason}, State};
{?MODULE, Tag, Reason} ->
- ?LOG(Tag, {Reason, T}),
+ ?LOG(stop, Tag),
{stop, {shutdown, Reason}, State}
end.
%% The form of the throw caught here is historical. It's
@@ -476,12 +473,12 @@ send_CER(#state{state = {'Wait-Conn-Ack', Tmo},
orelse
close({already_connected, Remote, LCaps}),
CER = build_CER(S),
- ?LOG(send, 'CER'),
#diameter_packet{header = #diameter_header{end_to_end_id = Eid,
hop_by_hop_id = Hid}}
= Pkt
= encode(CER, Dict),
send(TPid, Pkt),
+ ?LOG(send, 'CER'),
start_timer(Tmo, S#state{state = {'Wait-CEA', Hid, Eid}}).
%% Register ourselves as connecting to the remote endpoint in
@@ -526,7 +523,6 @@ recv(#diameter_packet{header = #diameter_header{} = Hdr}
= S) ->
Name = diameter_codec:msg_name(Dict0, Hdr),
Pid ! {recv, self(), Name, Pkt},
- diameter_stats:incr({msg_id(Name, Hdr), recv}), %% count received
rcv(Name, Pkt, S);
recv(#diameter_packet{header = undefined,
@@ -553,42 +549,30 @@ recv(#diameter_header{length = Len}
recv(#diameter_header{}
= H,
#diameter_packet{bin = Bin},
- #state{length_errors = E}
- = S) ->
- invalid(E,
- invalid_message_length,
- recv,
- [size(Bin), bit_size(Bin) rem 8, H, S]);
+ #state{length_errors = E}) ->
+ T = {size(Bin), bit_size(Bin) rem 8, H},
+ invalid(E, message_length_mismatch, T);
-recv(false, Pkt, #state{length_errors = E} = S) ->
- invalid(E, truncated_header, recv, [Pkt, S]).
+recv(false, #diameter_packet{bin = Bin}, #state{length_errors = E}) ->
+ invalid(E, truncated_header, Bin).
%% Note that counters here only count discarded messages.
-invalid(E, Reason, F, A) ->
+invalid(E, Reason, T) ->
diameter_stats:incr(Reason),
- abort(E, Reason, F, A).
-
-abort(exit, Reason, F, A) ->
- diameter_lib:warning_report(Reason, {?MODULE, F, A}),
- throw({?MODULE, abort, Reason});
-
-abort(_, _, _, _) ->
+ E == exit andalso close({Reason, T}),
+ ?LOG(Reason, T),
ok.
-msg_id({_,_,_} = T, _) ->
- T;
-msg_id(_, Hdr) ->
- {_,_,_} = diameter_codec:msg_id(Hdr).
-
%% rcv/3
%% Incoming CEA.
-rcv('CEA',
+rcv('CEA' = N,
#diameter_packet{header = #diameter_header{end_to_end_id = Eid,
hop_by_hop_id = Hid}}
= Pkt,
#state{state = {'Wait-CEA', Hid, Eid}}
= S) ->
+ ?LOG(recv, N),
handle_CEA(Pkt, S);
%% Incoming CER
@@ -609,34 +593,71 @@ rcv('DPR' = N, Pkt, S) ->
%% DPA in response to DPR and with the expected identifiers.
rcv('DPA' = N,
#diameter_packet{header = #diameter_header{end_to_end_id = Eid,
- hop_by_hop_id = Hid}},
- #state{transport = TPid,
+ hop_by_hop_id = Hid}
+ = H}
+ = Pkt,
+ #state{dictionary = Dict0,
+ transport = TPid,
dpr = {Hid, Eid}}) ->
+ ?LOG(recv, N),
+ incr(recv, H, Dict0),
+ incr_rc(recv, diameter_codec:decode(Dict0, Pkt), Dict0),
diameter_peer:close(TPid),
{stop, N};
%% Ignore anything else, an unsolicited DPA in particular.
+rcv(N, #diameter_packet{header = H}, _)
+ when N == 'CER';
+ N == 'CEA';
+ N == 'DPR';
+ N == 'DPA' ->
+ ?LOG(ignored, N),
+ %% Note that these aren't counted in the normal recv counter.
+ diameter_stats:incr({diameter_codec:msg_id(H), recv, ignored}),
+ ok;
+
rcv(_, _, _) ->
ok.
+%% incr/3
+
+incr(Dir, Hdr, Dict0) ->
+ diameter_traffic:incr(Dir, Hdr, self(), Dict0).
+
+%% incr_rc/3
+
+incr_rc(Dir, Pkt, Dict0) ->
+ diameter_traffic:incr_rc(Dir, Pkt, self(), Dict0).
+
+%% incr_error/3
+
+incr_error(Dir, Pkt, Dict0) ->
+ diameter_traffic:incr_error(Dir, Pkt, self(), Dict0).
+
%% send/2
%% Msg here could be a #diameter_packet or a binary depending on who's
%% sending. In particular, the watchdog will send DWR as a binary
%% while messages coming from clients will be in a #diameter_packet.
send(Pid, Msg) ->
- diameter_stats:incr({diameter_codec:msg_id(Msg), send}),
diameter_peer:send(Pid, Msg).
%% handle_request/3
+%%
+%% Incoming CER or DPR.
-handle_request(Type, #diameter_packet{} = Pkt, #state{dictionary = D} = S) ->
- ?LOG(recv, Type),
- send_answer(Type, diameter_codec:decode(D, Pkt), S).
+handle_request(Name,
+ #diameter_packet{header = H} = Pkt,
+ #state{dictionary = Dict0} = S) ->
+ ?LOG(recv, Name),
+ incr(recv, H, Dict0),
+ send_answer(Name, diameter_codec:decode(Dict0, Pkt), S).
%% send_answer/3
send_answer(Type, ReqPkt, #state{transport = TPid, dictionary = Dict} = S) ->
+ incr_error(recv, ReqPkt, Dict),
+
#diameter_packet{header = H,
transport_data = TD}
= ReqPkt,
@@ -653,13 +674,19 @@ send_answer(Type, ReqPkt, #state{transport = TPid, dictionary = Dict} = S) ->
msg = Msg,
transport_data = TD},
- send(TPid, diameter_codec:encode(Dict, Pkt)),
+ AnsPkt = diameter_codec:encode(Dict, Pkt),
+
+ incr(send, AnsPkt, Dict),
+ incr_rc(send, AnsPkt, Dict),
+ send(TPid, AnsPkt),
+ ?LOG(send, ans(Type)),
eval(PostF, S).
+ans('CER') -> 'CEA';
+ans('DPR') -> 'DPA'.
+
eval([F|A], S) ->
apply(F, A ++ [S]);
-eval(ok, S) ->
- S;
eval(T, _) ->
close(T).
@@ -723,8 +750,8 @@ cea(CEA, RC, Dict0) ->
post('CER' = T, RC, Pkt, S) ->
{T, caps(S), {RC, Pkt}};
-post('DPR', _, _, _) ->
- ok.
+post('DPR' = T, _, _, #state{parent = Pid}) ->
+ [fun(S) -> Pid ! {T, self()}, S end].
rejected({capabilities_cb, _F, Reason}, T, S) ->
rejected(Reason, T, S);
@@ -734,7 +761,7 @@ rejected(discard, T, _) ->
rejected({N, Es}, T, S) ->
{answer('CER', N, failed_avp(N, Es), S), T};
rejected(N, T, S) ->
- rejected({N, []}, T, S).
+ {answer('CER', N, [], S), T}.
failed_avp(RC, [{RC, Avp} | _]) ->
[{'Failed-AVP', [[{'AVP', [Avp]}]]}];
@@ -848,28 +875,27 @@ recv_CER(CER, #state{service = Svc, dictionary = Dict}) ->
close({'CER', CER, Svc, Dict, Reason})
end.
-%% handle_CEA/1
+%% handle_CEA/2
-handle_CEA(#diameter_packet{bin = Bin}
+handle_CEA(#diameter_packet{header = H}
= Pkt,
#state{dictionary = Dict0,
service = #diameter_service{capabilities = LCaps}}
- = S)
- when is_binary(Bin) ->
- ?LOG(recv, 'CEA'),
+ = S) ->
+ incr(recv, H, Dict0),
- #diameter_packet{msg = CEA}
+ #diameter_packet{}
= DPkt
= diameter_codec:decode(Dict0, Pkt),
+ RC = result_code(incr_rc(recv, DPkt, Dict0)),
+
{SApps, IS, RCaps} = recv_CEA(DPkt, S),
#diameter_caps{origin_host = {OH, DH}}
= Caps
= capz(LCaps, RCaps),
- RC = Dict0:'#get-'('Result-Code', CEA),
-
%% Ensure that we don't already have a connection to the peer in
%% question. This isn't the peer election of 3588 except in the
%% sense that, since we don't know who we're talking to until we
@@ -877,7 +903,7 @@ handle_CEA(#diameter_packet{bin = Bin}
%% connection with the peer.
try
- ?IS_SUCCESS(RC)
+ is_integer(RC) andalso ?IS_SUCCESS(RC)
orelse ?THROW(RC),
[] == SApps
andalso ?THROW(no_common_application),
@@ -897,6 +923,11 @@ handle_CEA(#diameter_packet{bin = Bin}
%% capabilities exchange could send DIAMETER_LIMITED_SUCCESS = 2002,
%% even if this isn't required by RFC 3588.
+result_code({'Result-Code', N}) ->
+ N;
+result_code(_) ->
+ undefined.
+
%% recv_CEA/2
recv_CEA(#diameter_packet{header = #diameter_header{version
@@ -988,19 +1019,13 @@ capz(#diameter_caps{} = L, #diameter_caps{} = R) ->
tl(tuple_to_list(R)))]).
%% close/1
+%%
+%% A good function to trace on in case of problems with capabilities
+%% exchange.
close(Reason) ->
- report(Reason),
throw({?MODULE, close, Reason}).
-%% Could possibly log more here.
-report({M, _, _, _, _} = T)
- when M == 'CER';
- M == 'CEA' ->
- diameter_lib:error_report(failure, T);
-report(_) ->
- ok.
-
%% dpr/2
%%
%% The RFC isn't clear on whether DPR should be send in a non-Open
@@ -1034,7 +1059,7 @@ dpr(_Reason, _S) ->
%% process and contact it. (eg. diameter:service_info/2)
dpr([CB|Rest], [Reason | _] = Args, S) ->
- try diameter_lib:eval([CB | Args]) of
+ case diameter_lib:eval([CB | Args]) of
{dpr, Opts} when is_list(Opts) ->
send_dpr(Reason, Opts, S);
dpr ->
@@ -1044,14 +1069,7 @@ dpr([CB|Rest], [Reason | _] = Args, S) ->
ignore ->
dpr(Rest, Args, S);
T ->
- No = {disconnect_cb, T},
- diameter_lib:error_report(invalid, No),
- {stop, No}
- catch
- E:R ->
- No = {disconnect_cb, E, R, ?STACK},
- diameter_lib:error_report(failure, No),
- {stop, No}
+ ?ERROR({disconnect_cb, CB, Args, T})
end;
dpr([], [Reason | _], S) ->
diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl
index 8914992f17..b7cd311e02 100644
--- a/lib/diameter/src/base/diameter_service.erl
+++ b/lib/diameter/src/base/diameter_service.erl
@@ -499,9 +499,21 @@ transition(Req, S) ->
%% # terminate/2
%% ---------------------------------------------------------------------------
-terminate(Reason, #state{service_name = Name} = S) ->
+terminate(Reason, #state{service_name = Name, peerT = PeerT} = S) ->
send_event(Name, stop),
ets:delete(?STATE_TABLE, Name),
+
+ %% Communicate pending loss of any peers that connection_down/3
+ %% won't. This is needed when stopping a service since we don't
+ %% wait for watchdog state changes to take care of if. That this
+ %% takes place after deleting the state entry ensures that the
+ %% resulting failover by request processes accomplishes nothing.
+ ets:foldl(fun(#peer{pid = TPid}, _) ->
+ diameter_traffic:peer_down(TPid)
+ end,
+ ok,
+ PeerT),
+
shutdown == Reason %% application shutdown
andalso shutdown(application, S).
@@ -719,14 +731,27 @@ remotes(F) ->
L when is_list(L) ->
L;
T ->
- diameter_lib:error_report({invalid_return, T}, F),
+ ?LOG(invalid_return, {F,T}),
+ error_report(invalid_return, share_peers, F),
[]
catch
E:R ->
- diameter_lib:error_report({failure, {E, R, ?STACK}}, F),
+ ?LOG(failure, {E, R, F, diameter_lib:get_stacktrace()}),
+ error_report(failure, share_peers, F),
[]
end.
+%% error_report/3
+
+error_report(T, What, F) ->
+ Reason = io_lib:format("~s from ~p callback", [reason(T), What]),
+ diameter_lib:error_report(Reason, diameter_lib:eval_name(F)).
+
+reason(invalid_return) ->
+ "invalid return";
+reason(failure) ->
+ "failure".
+
%% ---------------------------------------------------------------------------
%% # start/3
%% ---------------------------------------------------------------------------
@@ -869,7 +894,7 @@ watchdog(TPid, [], ?WD_OKAY, ?WD_SUSPECT = To, Wd, State) ->
%% Watchdog has lost its connection.
watchdog(TPid, [], _, ?WD_DOWN = To, Wd, #state{peerT = PeerT} = S) ->
- close(Wd, S),
+ close(Wd),
watchdog_down(Wd, To, S),
ets:delete(PeerT, TPid);
@@ -1026,8 +1051,11 @@ peer_cb(App, F, A) ->
true
catch
E:R ->
- diameter_lib:error_report({failure, {E, R, ?STACK}},
- {App, F, A}),
+ %% Don't include arguments since a #diameter_caps{} strings
+ %% from the peer, which could be anything (especially, large).
+ [Mod|X] = App#diameter_app.module,
+ ?LOG(failure, {E, R, Mod, F, diameter_lib:get_stacktrace()}),
+ error_report(failure, F, {Mod, F, A ++ X}),
false
end.
@@ -1187,26 +1215,16 @@ tc(false = No, _, _) -> %% removed
%% another watchdog to be able to detect that it should transition
%% from initial into reopen rather than okay. That someone is either
%% the accepting watchdog upon reception of a CER from the previously
-%% connected peer, or us after connect_timer timeout.
+%% connected peer, or us after connect_timer timeout or immediately.
-close(#watchdog{type = connect}, _) ->
+close(#watchdog{type = connect}) ->
ok;
+
close(#watchdog{type = accept,
pid = Pid,
- ref = Ref,
- options = Opts},
- #state{service_name = SvcName}) ->
- c(Pid, diameter_config:have_transport(SvcName, Ref), Opts).
-
-%% Tell watchdog to (maybe) die later ...
-c(Pid, true, Opts) ->
+ options = Opts}) ->
Tc = connect_timer(Opts, 2*?DEFAULT_TC),
- erlang:send_after(Tc, Pid, close);
-
-%% ... or now.
-c(Pid, false, _Opts) ->
- Pid ! close.
-
+ erlang:send_after(Tc, Pid, close).
%% The RFC's only document the behaviour of Tc, our connect_timer,
%% for the establishment of connections but we also give
%% connect_timer semantics for a listener, being the time within
@@ -1260,13 +1278,14 @@ cm([#diameter_app{alias = Alias} = App], Req, From, Svc) ->
mod_state(Alias, ModS),
{T, RC};
T ->
- diameter_lib:error_report({invalid, T},
- {App, handle_call, Args}),
+ ModX = App#diameter_app.module,
+ ?LOG(invalid_return, {ModX, handle_call, Args, T}),
invalid
catch
E: Reason ->
- diameter_lib:error_report({failure, {E, Reason, ?STACK}},
- {App, handle_call, Args}),
+ ModX = App#diameter_app.module,
+ Stack = diameter_lib:get_stacktrace(),
+ ?LOG(failure, {E, Reason, ModX, handle_call, Stack}),
failure
end;
@@ -1424,13 +1443,16 @@ pick_peer(Local,
T; %% Accept returned state in the immutable
{false = No, S} -> %% case as long it isn't changed.
No;
- T ->
- diameter_lib:error_report({invalid, T, App},
- {App, pick_peer, Args})
+ T when M ->
+ ModX = App#diameter_app.module,
+ ?LOG(invalid_return, {ModX, pick_peer, T}),
+ false
catch
- E: Reason ->
- diameter_lib:error_report({failure, {E, Reason, ?STACK}},
- {App, pick_peer, Args})
+ E: Reason when M ->
+ ModX = App#diameter_app.module,
+ Stack = diameter_lib:get_stacktrace(),
+ ?LOG(failure, {E, Reason, ModX, pick_peer, Stack}),
+ false
end.
%% peers/4
diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl
index 7fbb306b02..5fac61f416 100644
--- a/lib/diameter/src/base/diameter_traffic.erl
+++ b/lib/diameter/src/base/diameter_traffic.erl
@@ -31,6 +31,11 @@
%% towards diameter_watchdog
-export([receive_message/4]).
+%% towards diameter_peer_fsm and diameter_watchdog
+-export([incr/4,
+ incr_error/4,
+ incr_rc/4]).
+
%% towards diameter_service
-export([make_recvdata/1,
peer_up/1,
@@ -44,6 +49,8 @@
-include_lib("diameter/include/diameter.hrl").
-include("diameter_internal.hrl").
+-define(LOGX(Reason, T), begin ?LOG(Reason, T), x({Reason, T}) end).
+
-define(RELAY, ?DIAMETER_DICT_RELAY).
-define(BASE, ?DIAMETER_DICT_COMMON). %% Note: the RFC 3588 dictionary
@@ -109,6 +116,67 @@ peer_down(TPid) ->
failover(TPid).
%% ---------------------------------------------------------------------------
+%% incr/4
+%% ---------------------------------------------------------------------------
+
+incr(Dir, #diameter_packet{header = H}, TPid, Dict) ->
+ incr(Dir, H, TPid, Dict);
+
+incr(Dir, #diameter_header{} = H, TPid, Dict) ->
+ incr(TPid, {msg_id(H, Dict), Dir}).
+
+%% ---------------------------------------------------------------------------
+%% incr_error/4
+%% ---------------------------------------------------------------------------
+
+%% Decoded message without errors.
+incr_error(recv, #diameter_packet{errors = []}, _, _) ->
+ ok;
+
+incr_error(recv = D, #diameter_packet{header = H}, TPid, Dict) ->
+ incr_error(D, H, TPid, Dict);
+
+%% Encoded message with errors and an identifiable header ...
+incr_error(send = D, {_, _, #diameter_header{} = H}, TPid, Dict) ->
+ incr_error(D, H, TPid, Dict);
+
+%% ... or not.
+incr_error(send = D, {_,_}, TPid, _) ->
+ incr_error(D, unknown, TPid);
+
+incr_error(Dir, #diameter_header{} = H, TPid, Dict) ->
+ incr_error(Dir, msg_id(H, Dict), TPid);
+
+incr_error(Dir, Id, TPid, _) ->
+ incr_error(Dir, Id, TPid).
+
+incr_error(Dir, Id, TPid) ->
+ incr(TPid, {Id, Dir, error}).
+
+%% ---------------------------------------------------------------------------
+%% incr_rc/4
+%% ---------------------------------------------------------------------------
+
+-spec incr_rc(send|recv, Pkt, TPid, Dict0)
+ -> {Counter, non_neg_integer()}
+ | Reason
+ when Pkt :: #diameter_packet{},
+ TPid :: pid(),
+ Dict0 :: module(),
+ Counter :: {'Result-Code', integer()}
+ | {'Experimental-Result', integer(), integer()},
+ Reason :: atom().
+
+incr_rc(Dir, Pkt, TPid, Dict0) ->
+ try
+ incr_rc(Dir, Pkt, Dict0, TPid, Dict0)
+ catch
+ exit: {E,_} when E == no_result_code;
+ E == invalid_error_bit ->
+ E
+ end.
+
+%% ---------------------------------------------------------------------------
%% pending/1
%% ---------------------------------------------------------------------------
@@ -182,7 +250,7 @@ spawn_request(TPid, Pkt, Dict0, Opts, RecvData) ->
spawn_opt(fun() -> recv_request(TPid, Pkt, Dict0, RecvData) end, Opts)
catch
error: system_limit = E -> %% discard
- ?LOG({error, E}, now())
+ ?LOG(error, E)
end.
%% ---------------------------------------------------------------------------
@@ -211,7 +279,9 @@ recv_R({#diameter_app{id = Id, dictionary = Dict} = App, Caps},
Pkt0,
Dict0,
RecvData) ->
+ incr(recv, Pkt0, TPid, Dict),
Pkt = errors(Id, diameter_codec:decode(Id, Dict, Pkt0)),
+ incr_error(recv, Pkt, TPid, Dict),
{Caps, Pkt, App, recv_R(App, TPid, Dict0, Caps, RecvData, Pkt)};
%% Note that the decode is different depending on whether or not Id is
%% ?APP_ID_RELAY.
@@ -283,23 +353,25 @@ rc(N) ->
%% This error is returned when a request is received with an invalid
%% message length.
-errors(_, #diameter_packet{header = #diameter_header{length = Len},
+errors(_, #diameter_packet{header = #diameter_header{length = Len} = H,
bin = Bin,
errors = Es}
= Pkt)
when Len < 20;
0 /= Len rem 4;
8*Len /= bit_size(Bin) ->
+ ?LOG(invalid_message_length, {H, bit_size(Bin)}),
Pkt#diameter_packet{errors = [5015 | Es]};
%% DIAMETER_UNSUPPORTED_VERSION 5011
%% This error is returned when a request was received, whose version
%% number is unsupported.
-errors(_, #diameter_packet{header = #diameter_header{version = V},
+errors(_, #diameter_packet{header = #diameter_header{version = V} = H,
errors = Es}
= Pkt)
when V /= ?DIAMETER_VERSION ->
+ ?LOG(unsupported_version, H),
Pkt#diameter_packet{errors = [5011 | Es]};
%% DIAMETER_COMMAND_UNSUPPORTED 3001
@@ -307,12 +379,13 @@ errors(_, #diameter_packet{header = #diameter_header{version = V},
%% recognize or support. This MUST be used when a Diameter node
%% receives an experimental command that it does not understand.
-errors(Id, #diameter_packet{header = #diameter_header{is_proxiable = P},
+errors(Id, #diameter_packet{header = #diameter_header{is_proxiable = P} = H,
msg = M,
errors = Es}
= Pkt)
when ?APP_ID_RELAY /= Id, undefined == M; %% don't know the command
?APP_ID_RELAY == Id, not P -> %% command isn't proxiable
+ ?LOG(command_unsupported, H),
Pkt#diameter_packet{errors = [3001 | Es]};
%% DIAMETER_INVALID_HDR_BITS 3008
@@ -321,9 +394,11 @@ errors(Id, #diameter_packet{header = #diameter_header{is_proxiable = P},
%% inconsistent with the command code's definition.
errors(_, #diameter_packet{header = #diameter_header{is_request = true,
- is_error = true},
+ is_error = true}
+ = H,
errors = Es}
= Pkt) ->
+ ?LOG(invalid_hdr_bits, H),
Pkt#diameter_packet{errors = [3008 | Es]};
%% Green.
@@ -479,7 +554,6 @@ answer_message(RC,
origin_realm = {OR,_}},
Dict0,
Pkt) ->
- ?LOG({error, RC}, Pkt),
{Dict0, answer_message(OH, OR, RC, Dict0, Pkt)}.
%% resend/7
@@ -595,9 +669,11 @@ reply([Msg], Dict, TPid, Dict0, Fs, ReqPkt)
reply(Msg, Dict, TPid, Dict0, Fs, ReqPkt) ->
Pkt = encode(Dict,
+ TPid,
reset(make_answer_packet(Msg, ReqPkt), Dict, Dict0),
Fs),
- incr(send, Pkt, Dict, TPid, Dict0), %% count outgoing result codes
+ incr(send, Pkt, TPid, Dict),
+ incr_rc(send, Pkt, Dict, TPid, Dict0), %% count outgoing
send(TPid, Pkt).
%% reset/3
@@ -962,35 +1038,48 @@ find(Pred, [H|T]) ->
%% code, the missing vendor id, and a zero filled payload of the minimum
%% required length for the omitted AVP will be added.
-%% incr/4
+%% incr_rc/5
%%
%% Increment a stats counter for result codes in incoming and outgoing
%% answers.
%% Outgoing message as binary: don't count. (Sending binaries is only
%% partially supported.)
-incr(_, #diameter_packet{msg = undefined}, _, _, _) ->
- ok;
-
-%% Incoming with decode errors.
-incr(recv = D, #diameter_packet{header = H, errors = [_|_]}, _, TPid, _) ->
- incr(TPid, {diameter_codec:msg_id(H), D, error});
+incr_rc(_, #diameter_packet{msg = undefined = No}, _, _, _) ->
+ No;
-%% Incoming without errors or outgoing. Outgoing with encode errors
-%% never gets here since encode fails.
-incr(Dir, Pkt, Dict, TPid, Dict0) ->
+%% Incoming or outgoing. Outgoing with encode errors never gets here
+%% since encode fails.
+incr_rc(Dir, Pkt, Dict, TPid, Dict0) ->
#diameter_packet{header = #diameter_header{is_error = E}
= Hdr,
- msg = Rec}
+ msg = Msg,
+ errors = Es}
= Pkt,
- RC = int(get_avp_value(Dict, 'Result-Code', Rec)),
+ Id = msg_id(Hdr, Dict),
+
+ %% Count incoming decode errors.
+ recv /= Dir orelse [] == Es orelse incr_error(Dir, Id, TPid, Dict),
- %% Exit on an improper Result-Code.
+ %% Exit on a missing result code.
+ T = rc_counter(Dict, Msg),
+ T == false andalso ?LOGX(no_result_code, {Dict, Dir, Hdr}),
+ {Ctr, RC} = T,
+
+ %% Or on an inappropriate value.
is_result(RC, E, Dict0)
- orelse x({invalid_error_bit, RC}, answer, [Dir, Pkt]),
+ orelse ?LOGX(invalid_error_bit, {Dict, Dir, Hdr, RC}),
+
+ incr(TPid, {Id, Dir, Ctr}),
+ Ctr.
- irc(TPid, Hdr, Dir, rc_counter(Dict, Rec, RC)).
+%% Only count on known keeps so as not to be vulnerable to attack:
+%% there are 2^32 (application ids) * 2^24 (command codes) * 2 (R-bits)
+%% = 2^57 Ids for an attacker to choose from.
+msg_id(Hdr, Dict) ->
+ {_ApplId, Code, R} = Id = diameter_codec:msg_id(Hdr),
+ choose('' == Dict:msg_name(Code, 0 == R), unknown, Id).
%% No E-bit: can't be 3xxx.
is_result(RC, false, _Dict0) ->
@@ -1006,12 +1095,6 @@ is_result(RC, true, _) ->
orelse
5000 =< RC andalso RC < 6000.
-irc(_, _, _, undefined) ->
- false;
-
-irc(TPid, Hdr, Dir, Ctr) ->
- incr(TPid, {diameter_codec:msg_id(Hdr), Dir, Ctr}).
-
%% incr/2
incr(TPid, Counter) ->
@@ -1024,14 +1107,16 @@ incr(TPid, Counter) ->
%% All Diameter answer messages defined in vendor-specific
%% applications MUST include either one Result-Code AVP or one
%% Experimental-Result AVP.
-%%
-%% Maintain statistics assuming one or the other, not both, which is
-%% surely the intent of the RFC.
-rc_counter(Dict, Rec, undefined) ->
- rcc(get_avp_value(Dict, 'Experimental-Result', Rec));
-rc_counter(_, _, RC) ->
- {'Result-Code', RC}.
+rc_counter(Dict, Msg) ->
+ rcc(Dict, Msg, int(get_avp_value(Dict, 'Result-Code', Msg))).
+
+rcc(Dict, Msg, undefined) ->
+ rcc(get_avp_value(Dict, 'Experimental-Result', Msg));
+
+rcc(_, _, N)
+ when is_integer(N) ->
+ {{'Result-Code', N}, N}.
%% Outgoing answers may be in any of the forms messages can be sent
%% in. Incoming messages will be records. We're assuming here that the
@@ -1039,12 +1124,12 @@ rc_counter(_, _, RC) ->
rcc([{_,_,N} = T | _])
when is_integer(N) ->
- T;
+ {T,N};
rcc({_,_,N} = T)
when is_integer(N) ->
- T;
+ {T,N};
rcc(_) ->
- undefined.
+ false.
%% Extract the first good looking integer. There's no guarantee
%% that what we're looking for has arity 1.
@@ -1057,13 +1142,6 @@ int(N)
int(_) ->
undefined.
--spec x(any(), atom(), list()) -> no_return().
-
-%% Warn and exit request process on errors in an incoming answer.
-x(Reason, F, A) ->
- diameter_lib:warning_report(Reason, {?MODULE, F, A}),
- x(Reason).
-
x(T) ->
exit(T).
@@ -1305,7 +1383,7 @@ send_R(Pkt0,
{Pid, Ref},
SvcName,
Fs) ->
- Pkt = encode(Dict, Pkt0, Fs),
+ Pkt = encode(Dict, TPid, Pkt0, Fs),
#options{timeout = Timeout}
= Opts,
@@ -1370,11 +1448,19 @@ handle_answer(SvcName,
%% want to examine the answer?
handle_A(Pkt, SvcName, Dict, Dict0, App, #request{transport = TPid} = Req) ->
+ incr(recv, Pkt, TPid, Dict),
+
try
- incr(recv, Pkt, Dict, TPid, Dict0) %% count incoming result codes
+ incr_rc(recv, Pkt, Dict, TPid, Dict0) %% count incoming
of
_ -> answer(Pkt, SvcName, App, Req)
catch
+ exit: {no_result_code, _} ->
+ %% RFC 6733 requires one of Result-Code or
+ %% Experimental-Result, but the decode will have detected
+ %% a missing AVP. If both are optional in the dictionary
+ %% then this isn't a decode error: just continue on.
+ answer(Pkt, SvcName, App, Req);
exit: {invalid_error_bit, RC} ->
#diameter_packet{errors = Es}
= Pkt,
@@ -1401,11 +1487,16 @@ a(#diameter_packet{errors = Es}
callback == AE ->
cb(ModX, handle_answer, [Pkt, msg(P), SvcName, {TPid, Caps}]);
-a(Pkt, SvcName, _, report, Req) ->
- x(errors, handle_answer, [SvcName, Req, Pkt]);
+a(Pkt, SvcName, _, AE, _) ->
+ a(Pkt#diameter_packet.header, SvcName, AE).
+
+a(Hdr, SvcName, report) ->
+ MFA = {?MODULE, handle_answer, [SvcName, Hdr]},
+ diameter_lib:warning_report(errors, MFA),
+ a(Hdr, SvcName, discard);
-a(Pkt, SvcName, _, discard, Req) ->
- x({errors, handle_answer, [SvcName, Req, Pkt]}).
+a(Hdr, SvcName, discard) ->
+ x({answer_errors, {SvcName, Hdr}}).
%% Note that we don't check that the application id in the answer's
%% header is what we expect. (TODO: Does the rfc says anything about
@@ -1463,10 +1554,10 @@ msg(#diameter_packet{msg = undefined, bin = Bin}) ->
msg(#diameter_packet{msg = Msg}) ->
Msg.
-%% encode/3
+%% encode/4
-encode(Dict, Pkt, Fs) ->
- P = encode(Dict, Pkt),
+encode(Dict, TPid, Pkt, Fs) ->
+ P = encode(Dict, TPid, Pkt),
eval_packet(P, Fs),
P.
@@ -1478,11 +1569,17 @@ encode(Dict, Pkt, Fs) ->
%% support retransmission but is useful for test.
%% A message to be encoded.
-encode(Dict, #diameter_packet{bin = undefined} = Pkt) ->
- diameter_codec:encode(Dict, Pkt);
+encode(Dict, TPid, #diameter_packet{bin = undefined} = Pkt) ->
+ try
+ diameter_codec:encode(Dict, Pkt)
+ catch
+ exit: {diameter_codec, encode, T} = Reason ->
+ incr_error(send, T, TPid, Dict),
+ exit(Reason)
+ end;
%% An encoded binary: just send.
-encode(_, #diameter_packet{} = Pkt) ->
+encode(_, _, #diameter_packet{} = Pkt) ->
Pkt.
%% send_request/5
@@ -1579,13 +1676,13 @@ resend_request(Pkt0,
SvcName,
Tmo,
Fs) ->
- Pkt = encode(Dict, Pkt0, Fs),
+ Pkt = encode(Dict, TPid, Pkt0, Fs),
Req = Req0#request{transport = TPid,
packet = Pkt0,
caps = Caps},
- ?LOG(retransmission, Req),
+ ?LOG(retransmission, Pkt#diameter_packet.header),
TRef = send_request(TPid, Pkt, Req, SvcName, Tmo),
{TRef, Req}.
diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl
index 53e659e3f6..eff5096745 100644
--- a/lib/diameter/src/base/diameter_watchdog.erl
+++ b/lib/diameter/src/base/diameter_watchdog.erl
@@ -49,8 +49,6 @@
-define(IS_NATURAL(N), (is_integer(N) andalso 0 =< N)).
--define(CHOOSE(B,T,F), if (B) -> T; true -> F end).
-
-record(config,
{suspect = 1 :: non_neg_integer(), %% OKAY -> SUSPECT
okay = 3 :: non_neg_integer()}). %% REOPEN -> OKAY
@@ -221,7 +219,6 @@ dict0(_, _, Acc) ->
Acc.
config_error(T) ->
- diameter_lib:error_report(configuration_error, T),
exit({shutdown, {configuration_error, T}}).
%% handle_call/3
@@ -270,7 +267,7 @@ event(Msg,
TPid = tpid(F,T),
E = {[TPid | data(Msg, TPid, From, To)], From, To},
send(Pid, {watchdog, self(), E}),
- ?LOG(transition, {self(), E}).
+ ?LOG(transition, {From, To}).
data(Msg, TPid, reopen, okay) ->
{recv, TPid, 'DWA', _Pkt} = Msg, %% assert
@@ -313,14 +310,13 @@ code_change(_, State, _) ->
%% The state transitions documented here are extracted from RFC 3539,
%% the commentary is ours.
-%% Service or watchdog is telling the watchdog of an accepting
-%% transport to die after connect_timer expiry or reestablished
-%% connection (in another transport process) respectively.
-transition(close, #watchdog{status = down}) ->
+%% Service is telling the watchdog of an accepting transport to die
+%% following transport death in state INITIAL, or after connect_timer
+%% expiry; or another watchdog is saying the same after reestablishing
+%% a connection previously had by this one.
+transition(close, #watchdog{}) ->
{{accept, _}, _, _} = getr(restart), %% assert
stop;
-transition(close, #watchdog{}) ->
- ok;
%% Service is asking for the peer to be taken down gracefully.
transition({shutdown, Pid, _}, #watchdog{parent = Pid,
@@ -332,6 +328,11 @@ transition({shutdown = T, Pid, Reason}, #watchdog{parent = Pid,
send(TPid, {T, self(), Reason}),
S#watchdog{shutdown = true};
+%% Transport is telling us that DPA has been sent in response to DPR:
+%% its death should lead to ours.
+transition({'DPR', TPid}, #watchdog{transport = TPid} = S) ->
+ S#watchdog{shutdown = true};
+
%% Parent process has died,
transition({'DOWN', _, process, Pid, _Reason},
#watchdog{parent = Pid}) ->
@@ -403,18 +404,39 @@ transition({open = Key, TPid, _Hosts, T},
%% REOPEN Connection down CloseConnection()
%% SetWatchdog() DOWN
+%% Transport has died after DPA or service requested termination ...
transition({'DOWN', _, process, TPid, _Reason},
#watchdog{transport = TPid,
shutdown = true}) ->
stop;
+%% ... or not.
transition({'DOWN', _, process, TPid, _Reason},
#watchdog{transport = TPid,
- status = T}
- = S) ->
- set_watchdog(S#watchdog{status = ?CHOOSE(initial == T, T, down),
- pending = false,
- transport = undefined});
+ status = T,
+ restrict = {_,R}}
+ = S0) ->
+ S = S0#watchdog{pending = false,
+ transport = undefined},
+ {{M,_}, _, _} = getr(restart),
+
+ %% Close an accepting watchdog immediately if there's no
+ %% restriction on the number of connections to the same peer: the
+ %% state machine never enters state REOPEN in this case. The
+ %% 'close' message (instead of stop) is so as not to bypass the
+ %% sending of messages to the service process in handle_info/2.
+
+ if T /= initial, M == accept, not R ->
+ send(self(), close),
+ S#watchdog{status = down};
+ T /= initial ->
+ set_watchdog(S#watchdog{status = down});
+ M == connect ->
+ set_watchdog(S);
+ M == accept ->
+ send(self(), close),
+ S
+ end;
%% Incoming message.
transition({recv, TPid, Name, Pkt}, #watchdog{transport = TPid} = S) ->
@@ -454,9 +476,7 @@ encode(dwr = M, Dict0, Mask) ->
hop_by_hop_id = Seq},
Pkt = #diameter_packet{header = Hdr,
msg = Msg},
- #diameter_packet{bin = Bin} = diameter_codec:encode(Dict0, Pkt),
- Bin;
-
+ diameter_codec:encode(Dict0, Pkt);
encode(dwa, Dict0, #diameter_packet{header = H, transport_data = TD}
= ReqPkt) ->
@@ -525,10 +545,14 @@ send_watchdog(#watchdog{pending = false,
dictionary = Dict0,
sequence = Mask}
= S) ->
- send(TPid, {send, encode(dwr, Dict0, Mask)}),
+ #diameter_packet{bin = Bin} = EPkt = encode(dwr, Dict0, Mask),
+ diameter_traffic:incr(send, EPkt, TPid, Dict0),
+ send(TPid, {send, Bin}),
?LOG(send, 'DWR'),
S#watchdog{pending = true}.
+%% Dont' count encode errors since we don't expect any on DWR/DWA.
+
%% recv/3
recv(Name, Pkt, S) ->
@@ -545,13 +569,29 @@ recv(Name, Pkt, S) ->
rcv('DWR', Pkt, #watchdog{transport = TPid,
dictionary = Dict0}) ->
- send(TPid, {send, encode(dwa, Dict0, Pkt)}),
+ ?LOG(recv, 'DWR'),
+ DPkt = diameter_codec:decode(Dict0, Pkt),
+ diameter_traffic:incr(recv, DPkt, TPid, Dict0),
+ diameter_traffic:incr_error(recv, DPkt, TPid, Dict0),
+ EPkt = encode(dwa, Dict0, Pkt),
+ diameter_traffic:incr(send, EPkt, TPid, Dict0),
+ diameter_traffic:incr_rc(send, EPkt, TPid, Dict0),
+
+ send(TPid, {send, EPkt}),
?LOG(send, 'DWA');
+rcv('DWA', Pkt, #watchdog{transport = TPid,
+ dictionary = Dict0}) ->
+ ?LOG(recv, 'DWA'),
+ diameter_traffic:incr(recv, Pkt, TPid, Dict0),
+ diameter_traffic:incr_rc(recv,
+ diameter_codec:decode(Dict0, Pkt),
+ TPid,
+ Dict0);
+
rcv(N, _, _)
when N == 'CER';
N == 'CEA';
- N == 'DWA';
N == 'DPR';
N == 'DPA' ->
false;
@@ -740,7 +780,7 @@ timeout(#watchdog{status = T} = S)
restart(#watchdog{transport = undefined} = S) ->
restart(getr(restart), S);
-restart(S) ->
+restart(S) -> %% reconnect has won race with timeout
S.
%% restart/2
@@ -770,9 +810,10 @@ restart({{connect, _} = T, Opts, Svc},
%% die. Note that a state machine never enters state REOPEN in this
%% case.
restart({{accept, _}, _, _}, #watchdog{restrict = {_, false}}) ->
- stop;
+ stop; %% 'DOWN' was in old code: 'close' was not sent
-%% Otherwise hang around until told to die.
+%% Otherwise hang around until told to die, either by the service or
+%% by another watchdog.
restart({{accept, _}, _, _}, S) ->
S.
diff --git a/lib/diameter/src/compiler/diameter_dict_util.erl b/lib/diameter/src/compiler/diameter_dict_util.erl
index 136bba16cb..cf4741e563 100644
--- a/lib/diameter/src/compiler/diameter_dict_util.erl
+++ b/lib/diameter/src/compiler/diameter_dict_util.erl
@@ -731,8 +731,8 @@ no_messages_without_id(Dict) ->
%% explode/4
%%
-%% {avp_vendor_id, AvpName} -> [Lineno, Id::integer()]
-%% {custom_types|codecs|inherits, AvpName} -> [Lineno, Mod::string()]
+%% {avp_vendor_id, AvpName} -> [Lineno, Id::integer()]
+%% {custom|inherits, AvpName} -> [Lineno, Mod::string()]
explode({_, Line, AvpName}, Dict, {_, _, X} = T, K) ->
true = K /= avp_vendor_id orelse is_uint32(T, [K]),
@@ -1094,7 +1094,7 @@ explode_avps([{_, Line, Name} | Toks], Dict) ->
Vid = avp_vendor_id(Flags, Name, Line, Dict),
%% An AVP is uniquely defined by its AVP code and vendor id (if any).
- %% Ensure there are no duplicate.
+ %% Ensure there are no duplicates.
store_new({avp_types, {Code, Vid}},
[Line, Name],
Dict,
@@ -1302,8 +1302,7 @@ x({K, {Name, AvpName}}, [Line | _], Dict)
%% Ditto.
x({K, AvpName}, [Line | _], Dict)
when K == avp_vendor_id;
- K == custom_types;
- K == codecs ->
+ K == custom ->
true = avp_is_defined(AvpName, Dict, Line);
%% Ensure that all local AVP's of type Grouped are also present in @grouped.
diff --git a/lib/diameter/src/diameter.appup.src b/lib/diameter/src/diameter.appup.src
index 0d421c229e..b7b9662383 100644
--- a/lib/diameter/src/diameter.appup.src
+++ b/lib/diameter/src/diameter.appup.src
@@ -34,7 +34,19 @@
{"1.4.2", [{restart_application, diameter}]}, %% R16B01
{"1.4.3", [{restart_application, diameter}]}, %% R16B02
{"1.4.4", [{restart_application, diameter}]},
- {"1.5", [{restart_application, diameter}]} %% R16B03
+ {"1.5", [{restart_application, diameter}]}, %% R16B03
+ {"1.6", [{load_module, diameter_lib}, %% 17.0
+ {load_module, diameter_traffic},
+ {load_module, diameter_watchdog},
+ {load_module, diameter_peer_fsm},
+ {load_module, diameter_service},
+ {load_module, diameter_gen_base_rfc6733},
+ {load_module, diameter_gen_acct_rfc6733},
+ {load_module, diameter_gen_base_rfc3588},
+ {load_module, diameter_gen_accounting},
+ {load_module, diameter_gen_relay},
+ {load_module, diameter_codec},
+ {load_module, diameter_sctp}]}
],
[
{"0.9", [{restart_application, diameter}]},
@@ -51,6 +63,18 @@
{"1.4.2", [{restart_application, diameter}]},
{"1.4.3", [{restart_application, diameter}]},
{"1.4.4", [{restart_application, diameter}]},
- {"1.5", [{restart_application, diameter}]}
+ {"1.5", [{restart_application, diameter}]},
+ {"1.6", [{load_module, diameter_sctp},
+ {load_module, diameter_codec},
+ {load_module, diameter_gen_relay},
+ {load_module, diameter_gen_accounting},
+ {load_module, diameter_gen_base_rfc3588},
+ {load_module, diameter_gen_acct_rfc6733},
+ {load_module, diameter_gen_base_rfc6733},
+ {load_module, diameter_service},
+ {load_module, diameter_peer_fsm},
+ {load_module, diameter_watchdog},
+ {load_module, diameter_traffic},
+ {load_module, diameter_lib}]}
]
}.
diff --git a/lib/diameter/src/transport/diameter_sctp.erl b/lib/diameter/src/transport/diameter_sctp.erl
index d0a01351f3..32e7aaca39 100644
--- a/lib/diameter/src/transport/diameter_sctp.erl
+++ b/lib/diameter/src/transport/diameter_sctp.erl
@@ -616,6 +616,8 @@ send(#diameter_packet{bin = Bin, transport_data = {outstream, SId}},
S;
%% ... or not: rotate through all streams.
+send(#diameter_packet{bin = Bin}, S) ->
+ send(Bin, S);
send(Bin, #transport{streams = {_, OS},
os = N}
= S)
diff --git a/lib/diameter/test/diameter_compiler_SUITE.erl b/lib/diameter/test/diameter_compiler_SUITE.erl
index 08ffe5981d..20c9275808 100644
--- a/lib/diameter/test/diameter_compiler_SUITE.erl
+++ b/lib/diameter/test/diameter_compiler_SUITE.erl
@@ -317,6 +317,21 @@
{avp_not_defined,
"CEA ::=",
"<XXX> &"},
+ {ok,
+ "@avp_types",
+ "@codecs tmod Session-Id &"},
+ {ok,
+ "@avp_types",
+ "@custom_types tmod Session-Id &"},
+ {avp_not_defined,
+ "@avp_types",
+ "@codecs tmod OctetString &"},
+ {avp_not_defined,
+ "@avp_types",
+ "@custom_types tmod OctetString &"},
+ {avp_already_defined,
+ "@avp_types",
+ "@codecs tmod Session-Id @custom_types tmod Session-Id &"},
{not_loaded,
[{"@avp_types", "@inherits nomod XXX &"},
{"CEA ::=", "<XXX> &"}]},
diff --git a/lib/diameter/test/diameter_dpr_SUITE.erl b/lib/diameter/test/diameter_dpr_SUITE.erl
index 9252650bf7..f3f16b06e0 100644
--- a/lib/diameter/test/diameter_dpr_SUITE.erl
+++ b/lib/diameter/test/diameter_dpr_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012. All Rights Reserved.
+%% Copyright Ericsson AB 2012-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -73,7 +73,7 @@
%% Valid values for Disconnect-Cause.
-define(CAUSES, [0, rebooting, 1, busy, 2, goaway]).
-%% Establish one client connection for element of this list,
+%% Establish one client connection for each element of this list,
%% configured with disconnect/5 as disconnect_cb and returning the
%% specified value.
-define(RETURNS,
@@ -129,8 +129,8 @@ stop_service(Config) ->
service == group(Config)
andalso (ok = diameter:stop_service(?CLIENT)).
-%% Check for callbacks and stop the service. (Not the other way around
-%% for the timing reason explained below.)
+%% Check for callbacks before diameter:stop/0, not the other way around
+%% for the timing reason explained below.
check(Config) ->
Grp = group(Config),
[Pid | Refs] = ?util:read_priv(Config, config),
diff --git a/lib/diameter/test/diameter_examples_SUITE.erl b/lib/diameter/test/diameter_examples_SUITE.erl
index 02c8d34361..aef4bc35ef 100644
--- a/lib/diameter/test/diameter_examples_SUITE.erl
+++ b/lib/diameter/test/diameter_examples_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -24,7 +24,10 @@
-module(diameter_examples_SUITE).
-export([suite/0,
- all/0]).
+ all/0,
+ groups/0,
+ init_per_group/2,
+ end_per_group/2]).
%% testcases
-export([dict/1, dict/0,
@@ -46,7 +49,7 @@
%% The order here is significant and causes the server to listen
%% before the clients connect.
--define(NODES, [compile, server, client]).
+-define(NODES, [server, client]).
%% Options to ct_slave:start/2.
-define(TIMEOUTS, [{T, 15000} || T <- [boot_timeout,
@@ -63,6 +66,9 @@
%% Common dictionaries to inherit from examples.
-define(DICT0, [rfc3588_base, rfc6733_base]).
+%% Transport protocols over which the example Diameter nodes are run.
+-define(PROTS, [tcp, sctp]).
+
%% ===========================================================================
suite() ->
@@ -71,7 +77,34 @@ suite() ->
all() ->
[dict,
code,
- slave,
+ {group, all}].
+
+groups() ->
+ Tc = tc(),
+ [{all, [parallel], [{group, P} || P <- ?PROTS]}
+ | [{P, [], Tc} || P <- ?PROTS]].
+
+init_per_group(all, Config) ->
+ Config;
+
+init_per_group(tcp = N, Config) ->
+ [{group, N} | Config];
+
+init_per_group(sctp = N, Config) ->
+ case gen_sctp:open() of
+ {ok, Sock} ->
+ gen_sctp:close(Sock),
+ [{group, N} | Config];
+ {error, E} when E == eprotonosupport;
+ E == esocktnosupport -> %% fail on any other reason
+ {skip, no_sctp}
+ end.
+
+end_per_group(_, _) ->
+ ok.
+
+tc() ->
+ [slave,
enslave,
start,
traffic,
@@ -88,7 +121,7 @@ dict() ->
dict(_Config) ->
Dirs = [filename:join(H ++ ["examples", "dict"])
|| H <- [[code:lib_dir(diameter)], [here(), ".."]]],
- [] = [{F,D,RC} || {_,F} <- sort(find_files(Dirs, ".*\\.dia")),
+ [] = [{F,D,RC} || {_,F} <- sort(find_files(Dirs, ".*\\.dia$")),
D <- ?DICT0,
RC <- [make(F,D)],
RC /= ok].
@@ -184,17 +217,18 @@ make_name(Dict) ->
%% Compile example code under examples/code.
code(Config) ->
- Node = slave(hd(?NODES), here()),
+ Node = slave(compile, here()),
[] = rpc:call(Node,
?MODULE,
install,
- [proplists:get_value(priv_dir, Config)]).
+ [proplists:get_value(priv_dir, Config)]),
+ {ok, Node} = ct_slave:stop(compile).
%% Compile on another node since the code path may be modified.
install(PrivDir) ->
Top = install(here(), PrivDir),
Src = filename:join([Top, "examples", "code"]),
- Files = find_files([Src], ".*\\.erl"),
+ Files = find_files([Src], ".*\\.erl$"),
[] = [{F,E} || {_,F} <- Files,
{error, _, _} = E <- [compile:file(F, [warnings_as_errors,
return_errors])]].
@@ -226,7 +260,7 @@ install(Dir, PrivDir) ->
Inc = filename:join([Top, "include"]),
Gen = filename:join([Top, "src", "gen"]),
- Files = find_files([Inc, Gen], ".*\\.hrl"),
+ Files = find_files([Inc, Gen], ".*\\.hrl$"),
[] = [{F,E} || {_,F} <- Files,
B <- [filename:basename(F)],
D <- [filename:join([TmpInc, B])],
@@ -280,9 +314,10 @@ now_diff(_) ->
%% Start two nodes: one for the server, one for the client.
enslave(Config) ->
+ Prot = proplists:get_value(group, Config),
Dir = here(),
- Nodes = [{N, slave(N, Dir)} || N <- tl(?NODES)],
- ?util:write_priv(Config, nodes, Nodes).
+ Nodes = [{S, slave(N, Dir)} || S <- ?NODES, N <- [concat(Prot, S)]],
+ ?util:write_priv(Config, Prot, Nodes).
slave(Name, Dir) ->
{ok, Node} = ct_slave:start(Name, ?TIMEOUTS),
@@ -292,6 +327,9 @@ slave(Name, Dir) ->
[[Dir, filename:join([Dir, "..", "ebin"])]]),
Node.
+concat(Prot, Svc) ->
+ list_to_atom(atom_to_list(Prot) ++ atom_to_list(Svc)).
+
here() ->
filename:dirname(code:which(?MODULE)).
@@ -304,24 +342,25 @@ top(Dir, LibDir) ->
%% start/1
-start(server) ->
+start({server, Prot}) ->
ok = diameter:start(),
ok = server:start(),
- {ok, Ref} = server:listen(tcp),
- [_] = ?util:lport(tcp, Ref),
+ {ok, Ref} = server:listen(Prot),
+ [_] = ?util:lport(Prot, Ref),
ok;
-start(client) ->
+start({client = Svc, Prot}) ->
ok = diameter:start(),
- true = diameter:subscribe(client),
+ true = diameter:subscribe(Svc),
ok = client:start(),
- {ok, Ref} = client:connect(tcp),
+ {ok, Ref} = client:connect(Prot),
receive #diameter_event{info = {up, Ref, _, _, _}} -> ok end;
start(Config) ->
- Nodes = ?util:read_priv(Config, nodes),
+ Prot = proplists:get_value(group, Config),
+ Nodes = ?util:read_priv(Config, Prot),
[] = [RC || {T,N} <- Nodes,
- RC <- [rpc:call(N, ?MODULE, start, [T])],
+ RC <- [rpc:call(N, ?MODULE, start, [{T, Prot}])],
RC /= ok].
%% traffic/1
@@ -336,7 +375,8 @@ traffic(client) ->
receive {'DOWN', MRef, process, _, Reason} -> Reason end;
traffic(Config) ->
- Nodes = ?util:read_priv(Config, nodes),
+ Prot = proplists:get_value(group, Config),
+ Nodes = ?util:read_priv(Config, Prot),
[] = [RC || {T,N} <- Nodes,
RC <- [rpc:call(N, ?MODULE, traffic, [T])],
RC /= ok].
@@ -355,5 +395,6 @@ stop(Name)
{ok, _Node} = ct_slave:stop(Name),
ok;
-stop(_Config) ->
- [] = [RC || N <- ?NODES, RC <- [stop(N)], RC /= ok].
+stop(Config) ->
+ Prot = proplists:get_value(group, Config),
+ [] = [RC || N <- ?NODES, RC <- [stop(concat(Prot, N))], RC /= ok].
diff --git a/lib/diameter/test/diameter_failover_SUITE.erl b/lib/diameter/test/diameter_failover_SUITE.erl
index dfd3253827..c1494dcdb1 100644
--- a/lib/diameter/test/diameter_failover_SUITE.erl
+++ b/lib/diameter/test/diameter_failover_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -47,6 +47,7 @@
send_discard_1/1,
send_discard_2/1,
stop_services/1,
+ empty/1,
stop/1]).
%% diameter callbacks
@@ -121,6 +122,7 @@ all() ->
send_discard_1,
send_discard_2,
stop_services,
+ empty,
stop].
%% ===========================================================================
@@ -147,6 +149,10 @@ stop_services(_Config) ->
T <- [diameter:stop_service(H)],
T /= ok].
+%% Ensure transports have been removed from request table.
+empty(_Config) ->
+ [] = ets:tab2list(diameter_request).
+
stop(_Config) ->
ok = diameter:stop().
diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl
index a97c54fc04..4b67372016 100644
--- a/lib/diameter/test/diameter_traffic_SUITE.erl
+++ b/lib/diameter/test/diameter_traffic_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -43,7 +43,9 @@
send_protocol_error/1,
send_arbitrary/1,
send_unknown/1,
+ send_unknown_short/1,
send_unknown_mandatory/1,
+ send_unknown_short_mandatory/1,
send_noreply/1,
send_unsupported/1,
send_unsupported_app/1,
@@ -54,7 +56,8 @@
send_zero_avp_length/1,
send_invalid_avp_length/1,
send_invalid_reject/1,
- send_unrecognized_mandatory/1,
+ send_unexpected_mandatory_decode/1,
+ send_unexpected_mandatory/1,
send_long/1,
send_nopeer/1,
send_noapp/1,
@@ -266,7 +269,9 @@ tc() ->
send_protocol_error,
send_arbitrary,
send_unknown,
+ send_unknown_short,
send_unknown_mandatory,
+ send_unknown_short_mandatory,
send_noreply,
send_unsupported,
send_unsupported_app,
@@ -277,7 +282,8 @@ tc() ->
send_zero_avp_length,
send_invalid_avp_length,
send_invalid_reject,
- send_unrecognized_mandatory,
+ send_unexpected_mandatory_decode,
+ send_unexpected_mandatory,
send_long,
send_nopeer,
send_noapp,
@@ -447,6 +453,24 @@ send_unknown(Config) ->
data = <<17>>}]}
= lists:last(Avps).
+%% Ditto, and point the AVP length past the end of the message. Expect
+%% 5014.
+send_unknown_short(Config) ->
+ send_unknown_short(Config, false, ?INVALID_AVP_LENGTH).
+
+send_unknown_short(Config, M, RC) ->
+ Req = ['ASR', {'AVP', [#diameter_avp{code = 999,
+ is_mandatory = M,
+ data = <<17>>}]}],
+ ['ASA', _SessionId, {'Result-Code', RC} | Avps]
+ = call(Config, Req),
+ [#'diameter_base_Failed-AVP'{'AVP' = As}]
+ = proplists:get_value('Failed-AVP', Avps),
+ [#diameter_avp{code = 999,
+ is_mandatory = M,
+ data = <<17, _/binary>>}] %% extra bits from padding
+ = As.
+
%% Ditto but set the M flag.
send_unknown_mandatory(Config) ->
Req = ['ASR', {'AVP', [#diameter_avp{code = 999,
@@ -461,6 +485,27 @@ send_unknown_mandatory(Config) ->
data = <<17>>}]
= As.
+%% Ditto, and point the AVP length past the end of the message. Expect
+%% 5014 instead of 5001.
+send_unknown_short_mandatory(Config) ->
+ send_unknown_short(Config, true, ?INVALID_AVP_LENGTH).
+
+%% Send an ACR containing an unexpected mandatory Session-Timeout.
+%% Expect 5001, and check that the value in Failed-AVP was decoded.
+send_unexpected_mandatory_decode(Config) ->
+ Req = ['ASR', {'AVP', [#diameter_avp{code = 27, %% Session-Timeout
+ is_mandatory = true,
+ data = <<12:32>>}]}],
+ ['ASA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | Avps]
+ = call(Config, Req),
+ [#'diameter_base_Failed-AVP'{'AVP' = As}]
+ = proplists:get_value('Failed-AVP', Avps),
+ [#diameter_avp{code = 27,
+ is_mandatory = true,
+ value = 12,
+ data = <<12:32>>}]
+ = As.
+
%% Send an STR that the server ignores.
send_noreply(Config) ->
Req = ['STR', {'Termination-Cause', ?BAD_ANSWER}],
@@ -527,9 +572,9 @@ send_invalid_reject(Config) ->
?answer_message(?TOO_BUSY)
= call(Config, Req).
-%% Send an STR containing a known AVP, but one that's not allowed and
-%% sets the M-bit.
-send_unrecognized_mandatory(Config) ->
+%% Send an STR containing a known AVP, but one that's not expected and
+%% that sets the M-bit.
+send_unexpected_mandatory(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT}],
['STA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | _]
@@ -836,6 +881,26 @@ log(#diameter_packet{bin = Bin} = P, T)
%% prepare/4
prepare(Pkt, Caps, N, #group{client_dict0 = Dict0} = Group)
+ when N == send_unknown_short_mandatory;
+ N == send_unknown_short ->
+ Req = prepare(Pkt, Caps, Group),
+
+ #diameter_packet{header = #diameter_header{length = L},
+ bin = Bin}
+ = E
+ = diameter_codec:encode(Dict0, Pkt#diameter_packet{msg = Req}),
+
+ %% Find the unknown AVP data at the end of the message and alter
+ %% its length header.
+
+ {Padding, [17|_]} = lists:splitwith(fun(C) -> C == 0 end,
+ lists:reverse(binary_to_list(Bin))),
+
+ Offset = L - length(Padding) - 4,
+ <<H:Offset/binary, Len:24, T/binary>> = Bin,
+ E#diameter_packet{bin = <<H/binary, (Len+9):24, T/binary>>};
+
+prepare(Pkt, Caps, N, #group{client_dict0 = Dict0} = Group)
when N == send_long_avp_length;
N == send_short_avp_length;
N == send_zero_avp_length ->
@@ -876,8 +941,8 @@ prepare(Pkt, Caps, N, #group{client_dict0 = Dict0} = Group)
<<V, L:24, H/binary>> = H0, %% assert
E#diameter_packet{bin = <<V, (L+4):24, H/binary, 16:24, 0:32, T/binary>>};
-prepare(Pkt, Caps, send_unrecognized_mandatory, #group{client_dict0 = Dict0}
- = Group) ->
+prepare(Pkt, Caps, send_unexpected_mandatory, #group{client_dict0 = Dict0}
+ = Group) ->
Req = prepare(Pkt, Caps, Group),
#diameter_packet{bin = <<V, Len:24, T/binary>>}
= E
@@ -997,7 +1062,9 @@ answer(Rec, [_|_], N)
N == send_short_avp_length;
N == send_zero_avp_length;
N == send_invalid_avp_length;
- N == send_invalid_reject ->
+ N == send_invalid_reject;
+ N == send_unknown_short_mandatory;
+ N == send_unexpected_mandatory_decode ->
Rec;
answer(Rec, [], _) ->
Rec.
diff --git a/lib/diameter/vsn.mk b/lib/diameter/vsn.mk
index 54019fa46c..560c2aed50 100644
--- a/lib/diameter/vsn.mk
+++ b/lib/diameter/vsn.mk
@@ -18,5 +18,5 @@
# %CopyrightEnd%
APPLICATION = diameter
-DIAMETER_VSN = 1.6
+DIAMETER_VSN = 1.7
APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN)
diff --git a/lib/erl_interface/include/ei.h b/lib/erl_interface/include/ei.h
index a3eb437f88..3f3435977d 100644
--- a/lib/erl_interface/include/ei.h
+++ b/lib/erl_interface/include/ei.h
@@ -39,7 +39,7 @@
#include <stdio.h> /* Need type FILE */
#include <errno.h> /* Need EHOSTUNREACH, ENOMEM, ... */
-#if !defined(__WIN32__) && !defined(VXWORKS) || (defined(VXWORKS) && defined(HAVE_SENS))
+#if !(defined(__WIN32__) || defined(_WIN32)) && !defined(VXWORKS) || (defined(VXWORKS) && defined(HAVE_SENS))
# include <netdb.h>
#endif
diff --git a/lib/hipe/cerl/cerl_prettypr.erl b/lib/hipe/cerl/cerl_prettypr.erl
index 22f5b8945a..9a3873f46d 100644
--- a/lib/hipe/cerl/cerl_prettypr.erl
+++ b/lib/hipe/cerl/cerl_prettypr.erl
@@ -63,7 +63,8 @@
seq_arg/1, seq_body/1, string_lit/1, try_arg/1,
try_body/1, try_vars/1, try_evars/1, try_handler/1,
tuple_es/1, type/1, values_es/1, var_name/1,
- map_es/1, map_pair_key/1, map_pair_val/1, map_pair_op/1
+ c_map/1, map_arg/1, map_es/1, is_c_map_empty/1,
+ c_map_pair/2, map_pair_key/1, map_pair_val/1, map_pair_op/1
]).
-define(PAPER, 76).
@@ -489,7 +490,13 @@ lay_literal(Node, Ctxt) ->
%% `lay_cons' will check for strings.
lay_cons(Node, Ctxt);
V when is_tuple(V) ->
- lay_tuple(Node, Ctxt)
+ lay_tuple(Node, Ctxt);
+ M when is_map(M), map_size(M) =:= 0 ->
+ text("~{}~");
+ M when is_map(M) ->
+ lay_map(c_map([c_map_pair(abstract(K),abstract(V))
+ || {K,V} <- maps:to_list(M)]),
+ Ctxt)
end.
lay_var(Node, Ctxt) ->
@@ -596,10 +603,17 @@ lay_tuple(Node, Ctxt) ->
floating(text("}")))).
lay_map(Node, Ctxt) ->
+ Arg = map_arg(Node),
+ After = case is_c_map_empty(Arg) of
+ true -> floating(text("}~"));
+ false ->
+ beside(floating(text(" | ")),
+ beside(lay(Arg,Ctxt),
+ floating(text("}~"))))
+ end,
beside(floating(text("~{")),
- beside(par(seq(map_es(Node), floating(text(",")),
- Ctxt, fun lay/2)),
- floating(text("}~")))).
+ beside(par(seq(map_es(Node), floating(text(",")), Ctxt, fun lay/2)),
+ After)).
lay_map_pair(Node, Ctxt) ->
K = map_pair_key(Node),
diff --git a/lib/hipe/cerl/erl_types.erl b/lib/hipe/cerl/erl_types.erl
index 06c0d10296..67661130a5 100644
--- a/lib/hipe/cerl/erl_types.erl
+++ b/lib/hipe/cerl/erl_types.erl
@@ -4487,7 +4487,9 @@ get_mod_record([{FieldName, DeclType}|Left1],
[{FieldName, ModType}|Left2], Acc) ->
ModTypeNoVars = subst_all_vars_to_any(ModType),
case
- contains_remote(ModTypeNoVars) orelse t_is_subtype(ModTypeNoVars, DeclType)
+ contains_remote(ModTypeNoVars)
+ orelse contains_remote(DeclType)
+ orelse t_is_subtype(ModTypeNoVars, DeclType)
of
false -> {error, FieldName};
true -> get_mod_record(Left1, Left2, [{FieldName, ModType}|Acc])
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index e29144f014..596c0d77f4 100644
--- a/lib/inets/doc/src/notes.xml
+++ b/lib/inets/doc/src/notes.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2002</year><year>2013</year>
+ <year>2002</year><year>2014</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -32,7 +32,37 @@
<file>notes.xml</file>
</header>
- <section><title>Inets 5.10</title>
+ <section><title>Inets 5.10.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Correct distirbing mode for httpd:reload_config/2</p>
+ <p>
+ Own Id: OTP-11914</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Improved handling of invalid strings in the HTTP request
+ line.</p>
+ <p>
+ Impact: May improve memory consumption</p>
+ <p>
+ Own Id: OTP-11925 Aux Id: Sequence 12601 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Inets 5.10</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl
index 88e08be789..612eb05358 100644
--- a/lib/inets/src/http_client/httpc_handler.erl
+++ b/lib/inets/src/http_client/httpc_handler.erl
@@ -1116,8 +1116,16 @@ handle_http_body(Body, #state{headers = Headers,
{new_body, NewBody}]),
NewHeaders = http_chunk:handle_headers(Headers,
ChunkedHeaders),
- handle_response(State#state{headers = NewHeaders,
- body = NewBody})
+ case Body of
+ <<>> ->
+ handle_response(State#state{headers = NewHeaders,
+ body = NewBody});
+ _ ->
+ {NewBody2, NewRequest} =
+ stream(NewBody, Request, Code),
+ handle_response(State#state{headers = NewHeaders,
+ body = NewBody2})
+ end
end;
Enc when Enc =:= "identity"; Enc =:= undefined ->
?hcrt("handle_http_body - identity", []),
diff --git a/lib/inets/src/http_lib/http_internal.hrl b/lib/inets/src/http_lib/http_internal.hrl
index 97cf474ab9..53b776c4e7 100644
--- a/lib/inets/src/http_lib/http_internal.hrl
+++ b/lib/inets/src/http_lib/http_internal.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -26,6 +26,8 @@
-define(HTTP_MAX_BODY_SIZE, nolimit).
-define(HTTP_MAX_HEADER_SIZE, 10240).
-define(HTTP_MAX_URI_SIZE, nolimit).
+-define(HTTP_MAX_VERSION_STRING, 8).
+-define(HTTP_MAX_METHOD_STRING, 20).
-ifndef(HTTP_DEFAULT_SSL_KIND).
-define(HTTP_DEFAULT_SSL_KIND, essl).
diff --git a/lib/inets/src/http_server/httpd_request.erl b/lib/inets/src/http_server/httpd_request.erl
index 5ba79b2706..712c73599f 100644
--- a/lib/inets/src/http_server/httpd_request.erl
+++ b/lib/inets/src/http_server/httpd_request.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -44,26 +44,26 @@
%%%=========================================================================
parse([Bin, MaxSizes]) ->
?hdrt("parse", [{bin, Bin}, {max_sizes, MaxSizes}]),
- parse_method(Bin, [], MaxSizes, []);
+ parse_method(Bin, [], 0, proplists:get_value(max_method, MaxSizes), MaxSizes, []);
parse(Unknown) ->
?hdrt("parse", [{unknown, Unknown}]),
exit({bad_args, Unknown}).
%% Functions that may be returned during the decoding process
%% if the input data is incompleate.
-parse_method([Bin, Method, MaxSizes, Result]) ->
- parse_method(Bin, Method, MaxSizes, Result).
+parse_method([Bin, Method, Current, Max, MaxSizes, Result]) ->
+ parse_method(Bin, Method, Current, Max, MaxSizes, Result).
-parse_uri([Bin, URI, CurrSize, MaxSizes, Result]) ->
- parse_uri(Bin, URI, CurrSize, MaxSizes, Result).
+parse_uri([Bin, URI, Current, Max, MaxSizes, Result]) ->
+ parse_uri(Bin, URI, Current, Max, MaxSizes, Result).
-parse_version([Bin, Rest, Version, MaxSizes, Result]) ->
- parse_version(<<Rest/binary, Bin/binary>>, Version, MaxSizes,
+parse_version([Bin, Rest, Version, Current, Max, MaxSizes, Result]) ->
+ parse_version(<<Rest/binary, Bin/binary>>, Version, Current, Max, MaxSizes,
Result).
-parse_headers([Bin, Rest, Header, Headers, CurrSize, MaxSizes, Result]) ->
+parse_headers([Bin, Rest, Header, Headers, Current, Max, MaxSizes, Result]) ->
parse_headers(<<Rest/binary, Bin/binary>>,
- Header, Headers, CurrSize, MaxSizes, Result).
+ Header, Headers, Current, Max, MaxSizes, Result).
whole_body([Bin, Body, Length]) ->
whole_body(<<Body/binary, Bin/binary>>, Length).
@@ -107,8 +107,12 @@ validate("POST", Uri, "HTTP/1." ++ _N) ->
validate("TRACE", Uri, "HTTP/1." ++ N) when hd(N) >= $1 ->
validate_uri(Uri);
validate(Method, Uri, Version) ->
- {error, {not_supported, {Method, Uri, Version}}}.
-
+ case validate_version(Version) of
+ true ->
+ {error, {not_supported, {Method, Uri, Version}}};
+ false ->
+ {error, {bad_version, Version}}
+ end.
%%----------------------------------------------------------------------
%% The request is passed through the server as a record of type mod
%% create it.
@@ -131,104 +135,75 @@ update_mod_data(ModData, Method, RequestURI, HTTPVersion, Headers)->
%%%========================================================================
%%% Internal functions
%%%========================================================================
-parse_method(<<>>, Method, MaxSizes, Result) ->
- ?hdrt("parse_method - empty bin",
- [{method, Method}, {max_sizes, MaxSizes}, {result, Result}]),
- {?MODULE, parse_method, [Method, MaxSizes, Result]};
-parse_method(<<?SP, Rest/binary>>, Method, MaxSizes, Result) ->
- ?hdrt("parse_method - SP begin",
- [{rest, Rest},
- {method, Method},
- {max_sizes, MaxSizes},
- {result, Result}]),
- parse_uri(Rest, [], 0, MaxSizes,
+parse_method(<<>>, Method, Current, Max, MaxSizes, Result) ->
+ {?MODULE, parse_method, [Method, Current, Max, MaxSizes, Result]};
+parse_method(<<?SP, Rest/binary>>, Method, _Current, _Max, MaxSizes, Result) ->
+ parse_uri(Rest, [], 0, proplists:get_value(max_uri, MaxSizes), MaxSizes,
[string:strip(lists:reverse(Method)) | Result]);
-parse_method(<<Octet, Rest/binary>>, Method, MaxSizes, Result) ->
- ?hdrt("parse_method",
- [{octet, Octet},
- {rest, Rest},
- {method, Method},
- {max_sizes, MaxSizes},
- {result, Result}]),
- parse_method(Rest, [Octet | Method], MaxSizes, Result).
-
-parse_uri(_, _, CurrSize, {MaxURI, _}, _)
- when (CurrSize > MaxURI) andalso (MaxURI =/= nolimit) ->
- ?hdrt("parse_uri",
- [{current_size, CurrSize},
- {max_uri, MaxURI}]),
+parse_method(<<Octet, Rest/binary>>, Method, Current, Max, MaxSizes, Result) when Current =< Max ->
+ parse_method(Rest, [Octet | Method], Current + 1, Max, MaxSizes, Result);
+parse_method(_, _, _, Max, _, _) ->
+ %% We do not know the version of the client as it comes after the
+ %% method send the lowest version in the response so that the client
+ %% will be able to handle it.
+ {error, {too_long, Max, 413, "Method unreasonably long"}, lowest_version()}.
+
+parse_uri(_, _, Current, MaxURI, _, _)
+ when (Current > MaxURI) andalso (MaxURI =/= nolimit) ->
%% We do not know the version of the client as it comes after the
%% uri send the lowest version in the response so that the client
%% will be able to handle it.
- HttpVersion = "HTTP/0.9",
- {error, {uri_too_long, MaxURI}, HttpVersion};
-parse_uri(<<>>, URI, CurrSize, MaxSizes, Result) ->
- ?hdrt("parse_uri - empty bin",
- [{uri, URI},
- {current_size, CurrSize},
- {max_sz, MaxSizes},
- {result, Result}]),
- {?MODULE, parse_uri, [URI, CurrSize, MaxSizes, Result]};
-parse_uri(<<?SP, Rest/binary>>, URI, _, MaxSizes, Result) ->
- ?hdrt("parse_uri - SP begin",
- [{uri, URI},
- {max_sz, MaxSizes},
- {result, Result}]),
- parse_version(Rest, [], MaxSizes,
+ {error, {too_long, MaxURI, 414, "URI unreasonably long"},lowest_version()};
+parse_uri(<<>>, URI, Current, Max, MaxSizes, Result) ->
+ {?MODULE, parse_uri, [URI, Current, Max, MaxSizes, Result]};
+parse_uri(<<?SP, Rest/binary>>, URI, _, _, MaxSizes, Result) ->
+ parse_version(Rest, [], 0, proplists:get_value(max_version, MaxSizes), MaxSizes,
[string:strip(lists:reverse(URI)) | Result]);
%% Can happen if it is a simple HTTP/0.9 request e.i "GET /\r\n\r\n"
-parse_uri(<<?CR, _Rest/binary>> = Data, URI, _, MaxSizes, Result) ->
- ?hdrt("parse_uri - CR begin",
- [{uri, URI},
- {max_sz, MaxSizes},
- {result, Result}]),
- parse_version(Data, [], MaxSizes,
+parse_uri(<<?CR, _Rest/binary>> = Data, URI, _, _, MaxSizes, Result) ->
+ parse_version(Data, [], 0, proplists:get_value(max_version, MaxSizes), MaxSizes,
[string:strip(lists:reverse(URI)) | Result]);
-parse_uri(<<Octet, Rest/binary>>, URI, CurrSize, MaxSizes, Result) ->
- ?hdrt("parse_uri",
- [{octet, Octet},
- {uri, URI},
- {curr_sz, CurrSize},
- {max_sz, MaxSizes},
- {result, Result}]),
- parse_uri(Rest, [Octet | URI], CurrSize + 1, MaxSizes, Result).
-
-parse_version(<<>>, Version, MaxSizes, Result) ->
- {?MODULE, parse_version, [<<>>, Version, MaxSizes, Result]};
-parse_version(<<?LF, Rest/binary>>, Version, MaxSizes, Result) ->
+parse_uri(<<Octet, Rest/binary>>, URI, Current, Max, MaxSizes, Result) ->
+ parse_uri(Rest, [Octet | URI], Current + 1, Max, MaxSizes, Result).
+
+parse_version(<<>>, Version, Current, Max, MaxSizes, Result) ->
+ {?MODULE, parse_version, [<<>>, Version, Current, Max, MaxSizes, Result]};
+parse_version(<<?LF, Rest/binary>>, Version, Current, Max, MaxSizes, Result) ->
%% If ?CR is is missing RFC2616 section-19.3
- parse_version(<<?CR, ?LF, Rest/binary>>, Version, MaxSizes, Result);
-parse_version(<<?CR, ?LF, Rest/binary>>, Version, MaxSizes, Result) ->
- parse_headers(Rest, [], [], 0, MaxSizes,
+ parse_version(<<?CR, ?LF, Rest/binary>>, Version, Current, Max, MaxSizes, Result);
+parse_version(<<?CR, ?LF, Rest/binary>>, Version, _, _, MaxSizes, Result) ->
+ parse_headers(Rest, [], [], 0, proplists:get_value(max_header, MaxSizes), MaxSizes,
[string:strip(lists:reverse(Version)) | Result]);
-parse_version(<<?CR>> = Data, Version, MaxSizes, Result) ->
- {?MODULE, parse_version, [Data, Version, MaxSizes, Result]};
-parse_version(<<Octet, Rest/binary>>, Version, MaxSizes, Result) ->
- parse_version(Rest, [Octet | Version], MaxSizes, Result).
-
-parse_headers(_, _, _, CurrSize, {_, MaxHeaderSize}, Result)
- when CurrSize > MaxHeaderSize, MaxHeaderSize =/= nolimit ->
+parse_version(<<?CR>> = Data, Version, Current, Max, MaxSizes, Result) ->
+ {?MODULE, parse_version, [Data, Version, Current, Max, MaxSizes, Result]};
+parse_version(<<Octet, Rest/binary>>, Version, Current, Max, MaxSizes, Result) when Current =< Max ->
+ parse_version(Rest, [Octet | Version], Current + 1, Max, MaxSizes, Result);
+parse_version(_, _, _, Max,_,_) ->
+ {error, {too_long, Max, 413, "Version string unreasonably long"}, lowest_version()}.
+
+parse_headers(_, _, _, Current, Max, _, Result)
+ when Max =/= nolimit andalso Current > Max ->
HttpVersion = lists:nth(3, lists:reverse(Result)),
- {error, {header_too_long, MaxHeaderSize}, HttpVersion};
+ {error, {too_long, Max, 413, "Headers unreasonably long"}, HttpVersion};
-parse_headers(<<>>, Header, Headers, CurrSize, MaxSizes, Result) ->
- {?MODULE, parse_headers, [<<>>, Header, Headers, CurrSize,
+parse_headers(<<>>, Header, Headers, Current, Max, MaxSizes, Result) ->
+ {?MODULE, parse_headers, [<<>>, Header, Headers, Current, Max,
MaxSizes, Result]};
-parse_headers(<<?CR,?LF,?LF,Body/binary>>, [], [], CurrSize, MaxSizes, Result) ->
+parse_headers(<<?CR,?LF,?LF,Body/binary>>, [], [], Current, Max, MaxSizes, Result) ->
%% If ?CR is is missing RFC2616 section-19.3
- parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], CurrSize,
+ parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], Current, Max,
MaxSizes, Result);
-parse_headers(<<?LF,?LF,Body/binary>>, [], [], CurrSize, MaxSizes, Result) ->
+parse_headers(<<?LF,?LF,Body/binary>>, [], [], Current, Max, MaxSizes, Result) ->
%% If ?CR is is missing RFC2616 section-19.3
- parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], CurrSize,
+ parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], Current, Max,
MaxSizes, Result);
-parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], _, _, Result) ->
+parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], _, _, _, Result) ->
NewResult = list_to_tuple(lists:reverse([Body, {#http_request_h{}, []} |
Result])),
{ok, NewResult};
-parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, Header, Headers, _,
+parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, Header, Headers, _, _,
_, Result) ->
HTTPHeaders = [lists:reverse(Header) | Headers],
RequestHeaderRcord =
@@ -238,52 +213,51 @@ parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, Header, Headers, _,
HTTPHeaders} | Result])),
{ok, NewResult};
-parse_headers(<<?CR,?LF,?CR>> = Data, Header, Headers, CurrSize,
+parse_headers(<<?CR,?LF,?CR>> = Data, Header, Headers, Current, Max,
MaxSizes, Result) ->
- {?MODULE, parse_headers, [Data, Header, Headers, CurrSize,
+ {?MODULE, parse_headers, [Data, Header, Headers, Current, Max,
MaxSizes, Result]};
-parse_headers(<<?LF>>, [], [], CurrSize, MaxSizes, Result) ->
+parse_headers(<<?LF>>, [], [], Current, Max, MaxSizes, Result) ->
%% If ?CR is is missing RFC2616 section-19.3
- parse_headers(<<?CR,?LF>>, [], [], CurrSize, MaxSizes, Result);
+ parse_headers(<<?CR,?LF>>, [], [], Current, Max, MaxSizes, Result);
%% There where no headers, which is unlikely to happen.
-parse_headers(<<?CR,?LF>>, [], [], _, _, Result) ->
+parse_headers(<<?CR,?LF>>, [], [], _, _, _, Result) ->
NewResult = list_to_tuple(lists:reverse([<<>>, {#http_request_h{}, []} |
Result])),
{ok, NewResult};
-parse_headers(<<?LF>>, Header, Headers, CurrSize,
+parse_headers(<<?LF>>, Header, Headers, Current, Max,
MaxSizes, Result) ->
%% If ?CR is is missing RFC2616 section-19.3
- parse_headers(<<?CR,?LF>>, Header, Headers, CurrSize, MaxSizes, Result);
+ parse_headers(<<?CR,?LF>>, Header, Headers, Current, Max, MaxSizes, Result);
-parse_headers(<<?CR,?LF>> = Data, Header, Headers, CurrSize,
+parse_headers(<<?CR,?LF>> = Data, Header, Headers, Current, Max,
MaxSizes, Result) ->
- {?MODULE, parse_headers, [Data, Header, Headers, CurrSize,
+ {?MODULE, parse_headers, [Data, Header, Headers, Current, Max,
MaxSizes, Result]};
-parse_headers(<<?LF, Octet, Rest/binary>>, Header, Headers, CurrSize,
+parse_headers(<<?LF, Octet, Rest/binary>>, Header, Headers, Current, Max,
MaxSizes, Result) ->
%% If ?CR is is missing RFC2616 section-19.3
- parse_headers(<<?CR,?LF, Octet, Rest/binary>>, Header, Headers, CurrSize,
+ parse_headers(<<?CR,?LF, Octet, Rest/binary>>, Header, Headers, Current, Max,
MaxSizes, Result);
-parse_headers(<<?CR,?LF, Octet, Rest/binary>>, Header, Headers, CurrSize,
+parse_headers(<<?CR,?LF, Octet, Rest/binary>>, Header, Headers, _, Max,
MaxSizes, Result) ->
parse_headers(Rest, [Octet], [lists:reverse(Header) | Headers],
- CurrSize + 1, MaxSizes, Result);
-
-parse_headers(<<?CR>> = Data, Header, Headers, CurrSize,
+ 0, Max, MaxSizes, Result);
+parse_headers(<<?CR>> = Data, Header, Headers, Current, Max,
MaxSizes, Result) ->
- {?MODULE, parse_headers, [Data, Header, Headers, CurrSize,
+ {?MODULE, parse_headers, [Data, Header, Headers, Current, Max,
MaxSizes, Result]};
-parse_headers(<<?LF>>, Header, Headers, CurrSize,
+parse_headers(<<?LF>>, Header, Headers, Current, Max,
MaxSizes, Result) ->
%% If ?CR is is missing RFC2616 section-19.3
- parse_headers(<<?CR, ?LF>>, Header, Headers, CurrSize,
+ parse_headers(<<?CR, ?LF>>, Header, Headers, Current, Max,
MaxSizes, Result);
-parse_headers(<<Octet, Rest/binary>>, Header, Headers,
- CurrSize, MaxSizes, Result) ->
- parse_headers(Rest, [Octet | Header], Headers, CurrSize + 1,
+parse_headers(<<Octet, Rest/binary>>, Header, Headers, Current,
+ Max, MaxSizes, Result) ->
+ parse_headers(Rest, [Octet | Header], Headers, Current + 1, Max,
MaxSizes, Result).
whole_body(Body, Length) ->
@@ -326,6 +300,14 @@ validate_path([".." | Rest], N, RequestURI) ->
validate_path([_ | Rest], N, RequestURI) ->
validate_path(Rest, N + 1, RequestURI).
+validate_version("HTTP/1.1") ->
+ true;
+validate_version("HTTP/1.0") ->
+ true;
+validate_version("HTTP/0.9") ->
+ true;
+validate_version(_) ->
+ false.
%%----------------------------------------------------------------------
%% There are 3 possible forms of the reuqest URI
%%
@@ -430,3 +412,5 @@ tag([$:|Rest], Tag) ->
tag([Chr|Rest], Tag) ->
tag(Rest, [Chr|Tag]).
+lowest_version()->
+ "HTTP/0.9".
diff --git a/lib/inets/src/http_server/httpd_request_handler.erl b/lib/inets/src/http_server/httpd_request_handler.erl
index bd37066ff6..b3c9cbc46a 100644
--- a/lib/inets/src/http_server/httpd_request_handler.erl
+++ b/lib/inets/src/http_server/httpd_request_handler.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2013. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -123,7 +123,8 @@ continue_init(Manager, ConfigDB, SocketType, Socket, TimeOut) ->
{_, Status} = httpd_manager:new_connection(Manager),
- MFA = {httpd_request, parse, [{MaxURISize, MaxHeaderSize}]},
+ MFA = {httpd_request, parse, [[{max_uri, MaxURISize}, {max_header, MaxHeaderSize},
+ {max_version, ?HTTP_MAX_VERSION_STRING}, {max_method, ?HTTP_MAX_METHOD_STRING}]]},
State = #state{mod = Mod,
manager = Manager,
@@ -207,23 +208,15 @@ handle_info({Proto, Socket, Data},
set_new_data_size(cancel_request_timeout(State), NewDataSize)
end,
handle_http_msg(Result, NewState);
-
- {error, {uri_too_long, MaxSize}, Version} ->
- NewModData = ModData#mod{http_version = Version},
- httpd_response:send_status(NewModData, 414, "URI too long"),
- Reason = io_lib:format("Uri too long, max size is ~p~n",
- [MaxSize]),
- error_log(Reason, NewModData),
- {stop, normal, State#state{response_sent = true,
- mod = NewModData}};
- {error, {header_too_long, MaxSize}, Version} ->
+ {error, {too_long, MaxSize, ErrCode, ErrStr}, Version} ->
NewModData = ModData#mod{http_version = Version},
- httpd_response:send_status(NewModData, 413, "Header too long"),
- Reason = io_lib:format("Header too long, max size is ~p~n",
- [MaxSize]),
+ httpd_response:send_status(NewModData, ErrCode, ErrStr),
+ Reason = io_lib:format("~p: ~p max size is ~p~n",
+ [ErrCode, ErrStr, MaxSize]),
error_log(Reason, NewModData),
{stop, normal, State#state{response_sent = true,
mod = NewModData}};
+
NewMFA ->
http_transport:setopts(SockType, Socket, [{active, once}]),
case NewDataSize of
@@ -382,6 +375,11 @@ handle_http_msg({Method, Uri, Version, {RecordHeaders, Headers}, Body},
400, URI),
Reason = io_lib:format("Malformed syntax in URI: ~p~n", [URI]),
error_log(Reason, ModData),
+ {stop, normal, State#state{response_sent = true}};
+ {error, {bad_version, Ver}} ->
+ httpd_response:send_status(ModData#mod{http_version = "HTTP/0.9"}, 400, Ver),
+ Reason = io_lib:format("Malformed syntax version: ~p~n", [Ver]),
+ error_log(Reason, ModData),
{stop, normal, State#state{response_sent = true}}
end;
handle_http_msg({ChunkedHeaders, Body},
@@ -549,7 +547,8 @@ handle_next_request(#state{mod = #mod{connection = true} = ModData,
MaxHeaderSize = max_header_size(ModData#mod.config_db),
MaxURISize = max_uri_size(ModData#mod.config_db),
- MFA = {httpd_request, parse, [{MaxURISize, MaxHeaderSize}]},
+ MFA = {httpd_request, parse, [[{max_uri, MaxURISize}, {max_header, MaxHeaderSize},
+ {max_version, ?HTTP_MAX_VERSION_STRING}, {max_method, ?HTTP_MAX_METHOD_STRING}]]},
TmpState = State#state{mod = NewModData,
mfa = MFA,
max_keep_alive_request = decrease(Max),
diff --git a/lib/inets/src/inets_app/inets.appup.src b/lib/inets/src/inets_app/inets.appup.src
index dd081962cc..5499596bbd 100644
--- a/lib/inets/src/inets_app/inets.appup.src
+++ b/lib/inets/src/inets_app/inets.appup.src
@@ -17,11 +17,20 @@
%% %CopyrightEnd%
{"%VSN%",
[
- {"5.9.8", [{load_module, ftp, soft_purge, soft_purge, []}]},
+ {"5.10",
+ [{load_module, httpd, soft_purge, soft_purge, []},
+ {load_module, httpd_manager, soft_purge, soft_purge, []},
+ {load_module, httpd_request, soft_purge, soft_purge, []},
+ {load_module, httpd_request_handler, soft_purge, soft_purge,
+ []}]},
{<<"5\\..*">>,[{restart_application, inets}]}
],
[
- {"5.9.8", [{load_module, ftp, soft_purge, soft_purge, []}]},
+ {"5.10",
+ [{load_module, httpd, soft_purge, soft_purge, []},
+ {load_module, httpd_manager, soft_purge, soft_purge, []},
+ {load_module, httpd_request, soft_purge, soft_purge, []},
+ {load_module, httpd_request_handler, soft_purge, soft_purge, []}]},
{<<"5\\..*">>,[{restart_application, inets}]}
]
}.
diff --git a/lib/inets/test/http_format_SUITE.erl b/lib/inets/test/http_format_SUITE.erl
index c5920a3968..d4a3f28f38 100644
--- a/lib/inets/test/http_format_SUITE.erl
+++ b/lib/inets/test/http_format_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -356,7 +356,10 @@ http_request(Config) when is_list(Config) ->
"HTTP/1.1",
{#http_request_h{host = "www.erlang.org", te = []},
["te: ","host:www.erlang.org"]}, <<>>} =
- parse(httpd_request, parse, [?HTTP_MAX_HEADER_SIZE], HttpHead),
+ parse(httpd_request, parse, [[{max_header, ?HTTP_MAX_HEADER_SIZE},
+ {max_version, ?HTTP_MAX_VERSION_STRING},
+ {max_method, ?HTTP_MAX_METHOD_STRING}]],
+ HttpHead),
HttpHead1 = ["GET http://www.erlang.org HTTP/1.1" ++
[?CR], [?LF, ?CR, ?LF]],
@@ -364,7 +367,9 @@ http_request(Config) when is_list(Config) ->
"http://www.erlang.org",
"HTTP/1.1",
{#http_request_h{}, []}, <<>>} =
- parse(httpd_request, parse, [?HTTP_MAX_HEADER_SIZE], HttpHead1),
+ parse(httpd_request, parse, [[{max_header, ?HTTP_MAX_HEADER_SIZE},
+ {max_version, ?HTTP_MAX_VERSION_STRING},
+ {max_method, ?HTTP_MAX_METHOD_STRING}]], HttpHead1),
HttpHead2 = ["GET http://www.erlang.org HTTP/1.1" ++
@@ -373,7 +378,9 @@ http_request(Config) when is_list(Config) ->
"http://www.erlang.org",
"HTTP/1.1",
{#http_request_h{}, []}, <<>>} =
- parse(httpd_request, parse, [?HTTP_MAX_HEADER_SIZE], HttpHead2),
+ parse(httpd_request, parse, [[{max_header, ?HTTP_MAX_HEADER_SIZE},
+ {max_version, ?HTTP_MAX_VERSION_STRING},
+ {max_method, ?HTTP_MAX_METHOD_STRING}]], HttpHead2),
%% Note the following body is not related to the headers above
HttpBody = ["<HTML>\n<HEAD>\n<TITLE> dummy </TITLE>\n</HEAD>\n<BODY>\n",
diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl
index b1b799c953..f150cab877 100644
--- a/lib/inets/test/httpc_SUITE.erl
+++ b/lib/inets/test/httpc_SUITE.erl
@@ -27,15 +27,14 @@
-include_lib("kernel/include/file.hrl").
-include_lib("common_test/include/ct.hrl").
-include("inets_test_lib.hrl").
-
+-include("http_internal.hrl").
%% Note: This directive should only be used in test suites.
-compile(export_all).
-define(URL_START, "http://").
-define(TLS_URL_START, "https://").
-define(NOT_IN_USE_PORT, 8997).
--define(LF, $\n).
--define(HTTP_MAX_HEADER_SIZE, 10240).
+
-record(sslsocket, {fd = nil, pid = nil}).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -94,6 +93,7 @@ only_simulated() ->
empty_set_cookie,
trace,
stream_once,
+ stream_single_chunk,
no_content_204,
tolerate_missing_CR,
userinfo,
@@ -387,6 +387,13 @@ stream_once(Config) when is_list(Config) ->
Request2 = {url(group_name(Config), "/once_chunked.html", Config), []},
stream_test(Request2, {stream, {self, once}}).
+%%-------------------------------------------------------------------------
+stream_single_chunk() ->
+ [{doc, "Test the option stream for asynchrony requests"}].
+stream_single_chunk(Config) when is_list(Config) ->
+ Request = {url(group_name(Config), "/single_chunk.html", Config), []},
+ stream_test(Request, {stream, self}).
+
%%-------------------------------------------------------------------------
redirect_multiple_choises() ->
@@ -1047,7 +1054,7 @@ stream_test(Request, To) ->
ct:fail(Msg)
end,
- Body == binary_to_list(StreamedBody).
+ Body = binary_to_list(StreamedBody).
url(http, End, Config) ->
Port = ?config(port, Config),
@@ -1226,7 +1233,10 @@ dummy_server_init(Caller, ip_comm, Inet, _) ->
{ok, ListenSocket} = gen_tcp:listen(0, [Inet | BaseOpts]),
{ok, Port} = inet:port(ListenSocket),
Caller ! {port, Port},
- dummy_ipcomm_server_loop({httpd_request, parse, [?HTTP_MAX_HEADER_SIZE]},
+ dummy_ipcomm_server_loop({httpd_request, parse, [[{max_uri, ?HTTP_MAX_URI_SIZE},
+ {max_header, ?HTTP_MAX_HEADER_SIZE},
+ {max_version,?HTTP_MAX_VERSION_STRING},
+ {max_method, ?HTTP_MAX_METHOD_STRING}]]},
[], ListenSocket);
dummy_server_init(Caller, ssl, Inet, SSLOptions) ->
@@ -1238,7 +1248,10 @@ dummy_ssl_server_init(Caller, BaseOpts, Inet) ->
{ok, ListenSocket} = ssl:listen(0, [Inet | BaseOpts]),
{ok, {_, Port}} = ssl:sockname(ListenSocket),
Caller ! {port, Port},
- dummy_ssl_server_loop({httpd_request, parse, [?HTTP_MAX_HEADER_SIZE]},
+ dummy_ssl_server_loop({httpd_request, parse, [[{max_uri, ?HTTP_MAX_URI_SIZE},
+ {max_method, ?HTTP_MAX_METHOD_STRING},
+ {max_version,?HTTP_MAX_VERSION_STRING},
+ {max_method, ?HTTP_MAX_METHOD_STRING}]]},
[], ListenSocket).
dummy_ipcomm_server_loop(MFA, Handlers, ListenSocket) ->
@@ -1268,6 +1281,7 @@ dummy_ssl_server_loop(MFA, Handlers, ListenSocket) ->
From ! {stopped, self()}
after 0 ->
{ok, Socket} = ssl:transport_accept(ListenSocket),
+ ok = ssl:ssl_accept(Socket, infinity),
HandlerPid = dummy_request_handler(MFA, Socket),
ssl:controlling_process(Socket, HandlerPid),
HandlerPid ! ssl_controller,
@@ -1314,10 +1328,16 @@ handle_request(Module, Function, Args, Socket) ->
stop ->
stop;
<<>> ->
- {httpd_request, parse, [[<<>>, ?HTTP_MAX_HEADER_SIZE]]};
+ {httpd_request, parse, [[<<>>, [{max_uri, ?HTTP_MAX_URI_SIZE},
+ {max_header, ?HTTP_MAX_HEADER_SIZE},
+ {max_version,?HTTP_MAX_VERSION_STRING},
+ {max_method, ?HTTP_MAX_METHOD_STRING}]]]};
Data ->
handle_request(httpd_request, parse,
- [Data |[?HTTP_MAX_HEADER_SIZE]], Socket)
+ [Data, [{max_uri, ?HTTP_MAX_URI_SIZE},
+ {max_header, ?HTTP_MAX_HEADER_SIZE},
+ {max_version,?HTTP_MAX_VERSION_STRING},
+ {max_method, ?HTTP_MAX_METHOD_STRING}]], Socket)
end;
NewMFA ->
NewMFA
@@ -1675,6 +1695,14 @@ handle_uri(_,"/once_chunked.html",_,_,Socket,_) ->
http_chunk:encode("obar</BODY></HTML>")),
http_chunk:encode_last();
+handle_uri(_,"/single_chunk.html",_,_,Socket,_) ->
+ Chunk = "HTTP/1.1 200 ok\r\n" ++
+ "Transfer-Encoding:Chunked\r\n\r\n" ++
+ http_chunk:encode("<HTML><BODY>fo") ++
+ http_chunk:encode("obar</BODY></HTML>") ++
+ http_chunk:encode_last(),
+ send(Socket, Chunk);
+
handle_uri(_,"/once.html",_,_,Socket,_) ->
Head = "HTTP/1.1 200 ok\r\n" ++
"Content-Length:32\r\n\r\n",
diff --git a/lib/inets/test/httpd_basic_SUITE.erl b/lib/inets/test/httpd_basic_SUITE.erl
index fbe65145dc..1fcc5f257e 100644
--- a/lib/inets/test/httpd_basic_SUITE.erl
+++ b/lib/inets/test/httpd_basic_SUITE.erl
@@ -32,9 +32,9 @@
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
- [
- uri_too_long_414,
+ [uri_too_long_414,
header_too_long_413,
+ entity_too_long,
erl_script_nocache_opt,
script_nocache,
escaped_url_in_error_body,
@@ -63,15 +63,13 @@ end_per_group(_GroupName, Config) ->
%% variable, but should NOT alter/remove any existing entries.
%%--------------------------------------------------------------------
init_per_suite(Config) ->
- tsp("init_per_suite -> entry with"
- "~n Config: ~p", [Config]),
inets_test_lib:stop_apps([inets]),
inets_test_lib:start_apps([inets]),
PrivDir = ?config(priv_dir, Config),
DataDir = ?config(data_dir, Config),
-
+
Dummy =
-"<HTML>
+ "<HTML>
<HEAD>
<TITLE>/index.html</TITLE>
</HEAD>
@@ -79,7 +77,7 @@ init_per_suite(Config) ->
DUMMY
</BODY>
</HTML>",
-
+
DummyFile = filename:join([PrivDir,"dummy.html"]),
CgiDir = filename:join(PrivDir, "cgi-bin"),
ok = file:make_dir(CgiDir),
@@ -116,8 +114,6 @@ DUMMY
%% Description: Cleanup after the whole suite
%%--------------------------------------------------------------------
end_per_suite(_Config) ->
- tsp("end_per_suite -> entry with"
- "~n Config: ~p", [_Config]),
inets:stop(),
ok.
@@ -134,8 +130,6 @@ end_per_suite(_Config) ->
%% variable, but should NOT alter/remove any existing entries.
%%--------------------------------------------------------------------
init_per_testcase(Case, Config) ->
- tsp("init_per_testcase(~w) -> entry with"
- "~n Config: ~p", [Case, Config]),
Config.
@@ -147,22 +141,18 @@ init_per_testcase(Case, Config) ->
%% A list of key/value pairs, holding the test case configuration.
%% Description: Cleanup after each test case
%%--------------------------------------------------------------------
-end_per_testcase(Case, Config) ->
- tsp("end_per_testcase(~w) -> entry with"
- "~n Config: ~p", [Case, Config]),
+end_per_testcase(_Case, Config) ->
Config.
%%-------------------------------------------------------------------------
%% Test cases starts here.
%%-------------------------------------------------------------------------
-uri_too_long_414(doc) ->
- ["Test that too long uri's get 414 HTTP code"];
-uri_too_long_414(suite) ->
- [];
+uri_too_long_414() ->
+ [{doc, "Test that too long uri's get 414 HTTP code"}].
uri_too_long_414(Config) when is_list(Config) ->
HttpdConf = ?config(httpd_conf, Config),
- {ok, Pid} = inets:start(httpd, [{port, 0}, {max_uri_size, 10}
+ {ok, Pid} = inets:start(httpd, [{max_uri_size, 10}
| HttpdConf]),
Info = httpd:info(Pid),
Port = proplists:get_value(port, Info),
@@ -178,17 +168,12 @@ uri_too_long_414(Config) when is_list(Config) ->
{version, "HTTP/0.9"}]),
inets:stop(httpd, Pid).
-
-%%-------------------------------------------------------------------------
%%-------------------------------------------------------------------------
-
-header_too_long_413(doc) ->
- ["Test that too long headers's get 413 HTTP code"];
-header_too_long_413(suite) ->
- [];
+header_too_long_413() ->
+ [{doc,"Test that too long headers's get 413 HTTP code"}].
header_too_long_413(Config) when is_list(Config) ->
HttpdConf = ?config(httpd_conf, Config),
- {ok, Pid} = inets:start(httpd, [{port, 0}, {max_header_size, 10}
+ {ok, Pid} = inets:start(httpd, [{max_header_size, 10}
| HttpdConf]),
Info = httpd:info(Pid),
Port = proplists:get_value(port, Info),
@@ -202,8 +187,72 @@ header_too_long_413(Config) when is_list(Config) ->
inets:stop(httpd, Pid).
%%-------------------------------------------------------------------------
+
+entity_too_long() ->
+ [{doc, "Test that too long versions and method strings are rejected"}].
+entity_too_long(Config) when is_list(Config) ->
+ HttpdConf = ?config(httpd_conf, Config),
+ {ok, Pid} = inets:start(httpd, HttpdConf),
+ Info = httpd:info(Pid),
+ Port = proplists:get_value(port, Info),
+ Address = proplists:get_value(bind_address, Info),
+
+ %% Not so long but wrong
+ ok = httpd_test_lib:verify_request(ip_comm, Address, Port, node(),
+ "GET / " ++
+ lists:duplicate(5, $A) ++ "\r\n\r\n",
+ [{statuscode, 400},
+ %% Server will send lowest version
+ %% as it will not get to the
+ %% client version
+ %% before aborting
+ {version, "HTTP/0.9"}]),
+
+ %% Too long
+ ok = httpd_test_lib:verify_request(ip_comm, Address, Port, node(),
+ "GET / " ++
+ lists:duplicate(100, $A) ++ "\r\n\r\n",
+ [{statuscode, 413},
+ %% Server will send lowest version
+ %% as it will not get to the
+ %% client version
+ %% before aborting
+ {version, "HTTP/0.9"}]),
+ %% Not so long but wrong
+ ok = httpd_test_lib:verify_request(ip_comm, Address, Port, node(),
+ lists:duplicate(5, $A) ++ " / "
+ "HTTP/1.1\r\n\r\n",
+ [{statuscode, 501},
+ %% Server will send lowest version
+ %% as it will not get to the
+ %% client version
+ %% before aborting
+ {version, "HTTP/1.1"}]),
+ %% Too long
+ ok = httpd_test_lib:verify_request(ip_comm, Address, Port, node(),
+ lists:duplicate(100, $A) ++ " / "
+ "HTTP/1.1\r\n\r\n",
+ [{statuscode, 413},
+ %% Server will send lowest version
+ %% as it will not get to the
+ %% client version
+ %% before aborting
+ {version, "HTTP/0.9"}]),
+ inets:stop(httpd, Pid).
+
%%-------------------------------------------------------------------------
+script_nocache() ->
+ [{doc,"Test nocache option for mod_cgi and mod_esi"}].
+script_nocache(Config) when is_list(Config) ->
+ Normal = {no_header, "cache-control"},
+ NoCache = {header, "cache-control", "no-cache"},
+ verify_script_nocache(Config, false, false, Normal, Normal),
+ verify_script_nocache(Config, true, false, NoCache, Normal),
+ verify_script_nocache(Config, false, true, Normal, NoCache),
+ verify_script_nocache(Config, true, true, NoCache, NoCache).
+
+%%-------------------------------------------------------------------------
erl_script_nocache_opt(doc) ->
["Test that too long headers's get 413 HTTP code"];
erl_script_nocache_opt(suite) ->
@@ -225,155 +274,49 @@ erl_script_nocache_opt(Config) when is_list(Config) ->
inets:stop(httpd, Pid).
%%-------------------------------------------------------------------------
-%%-------------------------------------------------------------------------
-script_nocache(doc) ->
- ["Test nocache option for mod_cgi and mod_esi"];
-script_nocache(suite) ->
- [];
-script_nocache(Config) when is_list(Config) ->
- Normal = {no_header, "cache-control"},
- NoCache = {header, "cache-control", "no-cache"},
- verify_script_nocache(Config, false, false, Normal, Normal),
- verify_script_nocache(Config, true, false, NoCache, Normal),
- verify_script_nocache(Config, false, true, Normal, NoCache),
- verify_script_nocache(Config, true, true, NoCache, NoCache),
- ok.
-verify_script_nocache(Config, CgiNoCache, EsiNoCache, CgiOption, EsiOption) ->
- HttpdConf = ?config(httpd_conf, Config),
- CgiScript = ?config(cgi_printenv, Config),
- CgiDir = ?config(cgi_dir, Config),
- {ok, Pid} = inets:start(httpd, [{port, 0},
- {script_alias,
- {"/cgi-bin/", CgiDir ++ "/"}},
- {script_nocache, CgiNoCache},
- {erl_script_alias,
- {"/cgi-bin/erl", [httpd_example,io]}},
- {erl_script_nocache, EsiNoCache}
- | HttpdConf]),
- Info = httpd:info(Pid),
- Port = proplists:get_value(port, Info),
- Address = proplists:get_value(bind_address, Info),
- ok = httpd_test_lib:verify_request(ip_comm, Address, Port, node(),
- "GET /cgi-bin/" ++ CgiScript ++
- " HTTP/1.0\r\n\r\n",
- [{statuscode, 200},
- CgiOption,
- {version, "HTTP/1.0"}]),
- ok = httpd_test_lib:verify_request(ip_comm, Address, Port, node(),
- "GET /cgi-bin/erl/httpd_example:get "
- "HTTP/1.0\r\n\r\n",
- [{statuscode, 200},
- EsiOption,
- {version, "HTTP/1.0"}]),
- inets:stop(httpd, Pid).
-
-
-%%-------------------------------------------------------------------------
%%-------------------------------------------------------------------------
-escaped_url_in_error_body(doc) ->
- ["Test Url-encoding see OTP-8940"];
-escaped_url_in_error_body(suite) ->
- [];
-escaped_url_in_error_body(Config) when is_list(Config) ->
- %% <CONDITIONAL-SKIP>
- %% This skip is due to a problem on windows with long path's
- %% If a path is too long file:open fails with, for example, eio.
- %% Until that problem is fixed, we skip this case...
- Skippable = [win32],
- Condition = fun() -> ?OS_BASED_SKIP(Skippable) end,
- ?NON_PC_TC_MAYBE_SKIP(Config, Condition),
- %% </CONDITIONAL-SKIP>
-
- tsp("escaped_url_in_error_body -> entry"),
+escaped_url_in_error_body() ->
+ [{doc, "Test Url-encoding see OTP-8940"}].
+escaped_url_in_error_body(Config) when is_list(Config) ->
HttpdConf = ?config(httpd_conf, Config),
{ok, Pid} = inets:start(httpd, [{port, 0} | HttpdConf]),
Info = httpd:info(Pid),
Port = proplists:get_value(port, Info),
- _Address = proplists:get_value(bind_address, Info),
-
- %% Request 1
- tss(1000),
- tsp("escaped_url_in_error_body -> request 1"),
URL1 = ?URL_START ++ integer_to_list(Port),
- %% Make sure the server is ok, by making a request for a valid page
- case httpc:request(get, {URL1 ++ "/dummy.html", []},
- [{url_encode, false},
- {version, "HTTP/1.0"}],
- [{full_result, false}]) of
- {ok, {200, _}} ->
- %% Don't care about the the body, just that we get a ok response
- ok;
- {ok, {StatusCode1, Body1}} ->
- tsf({unexpected_ok_1, StatusCode1, Body1})
- end,
-
- %% Request 2
- tss(1000),
- tsp("escaped_url_in_error_body -> request 2"),
- %% Make sure the server is ok, by making a request for a valid page
- case httpc:request(get, {URL1 ++ "/dummy.html", []},
- [{url_encode, true},
- {version, "HTTP/1.0"}],
- [{full_result, false}]) of
- {ok, {200, _}} ->
- %% Don't care about the the body, just that we get a ok response
- ok;
- {ok, {StatusCode2, Body2}} ->
- tsf({unexpected_ok_2, StatusCode2, Body2})
- end,
-
- %% Request 3
- tss(1000),
- tsp("escaped_url_in_error_body -> request 3"),
+
+ %% Sanity check
+ {ok, {200, _}} = httpc:request(get, {URL1 ++ "/dummy.html", []},
+ [{url_encode, false},
+ {version, "HTTP/1.0"}],
+ [{full_result, false}]),
+ {ok, {200, _}} = httpc:request(get, {URL1 ++ "/dummy.html", []},
+ [{url_encode, true},
+ {version, "HTTP/1.0"}],
+ [{full_result, false}]),
+
%% Ask for a non-existing page(1)
Path = "/<b>this_is_bold<b>",
HTMLEncodedPath = http_util:html_encode(Path),
URL2 = URL1 ++ Path,
- case httpc:request(get, {URL2, []},
- [{url_encode, true},
- {version, "HTTP/1.0"}],
- [{full_result, false}]) of
- {ok, {404, Body3}} ->
- case find_URL_path(string:tokens(Body3, " ")) of
- HTMLEncodedPath ->
- ok;
- BadPath3 ->
- tsf({unexpected_path_3, HTMLEncodedPath, BadPath3})
- end;
- {ok, UnexpectedOK3} ->
- tsf({unexpected_ok_3, UnexpectedOK3})
- end,
+ {ok, {404, Body3}} = httpc:request(get, {URL2, []},
+ [{url_encode, true},
+ {version, "HTTP/1.0"}],
+ [{full_result, false}]),
- %% Request 4
- tss(1000),
- tsp("escaped_url_in_error_body -> request 4"),
- %% Ask for a non-existing page(2)
- case httpc:request(get, {URL2, []},
- [{url_encode, false},
- {version, "HTTP/1.0"}],
- [{full_result, false}]) of
- {ok, {404, Body4}} ->
- case find_URL_path(string:tokens(Body4, " ")) of
- HTMLEncodedPath ->
- ok;
- BadPath4 ->
- tsf({unexpected_path_4, HTMLEncodedPath, BadPath4})
- end;
- {ok, UnexpectedOK4} ->
- tsf({unexpected_ok_4, UnexpectedOK4})
- end,
- tss(1000),
- tsp("escaped_url_in_error_body -> stop inets"),
- inets:stop(httpd, Pid),
- tsp("escaped_url_in_error_body -> done"),
- ok.
+ HTMLEncodedPath = find_URL_path(string:tokens(Body3, " ")),
+ {ok, {404, Body4}} = httpc:request(get, {URL2, []},
+ [{url_encode, false},
+ {version, "HTTP/1.0"}],
+ [{full_result, false}]),
+
+ HTMLEncodedPath = find_URL_path(string:tokens(Body4, " ")),
+ inets:stop(httpd, Pid).
%%-------------------------------------------------------------------------
-%%-------------------------------------------------------------------------
keep_alive_timeout(doc) ->
["Test the keep_alive_timeout option"];
@@ -393,7 +336,6 @@ keep_alive_timeout(Config) when is_list(Config) ->
inets:stop(httpd, Pid).
%%-------------------------------------------------------------------------
-%%-------------------------------------------------------------------------
script_timeout(doc) ->
["Test the httpd script_timeout option"];
@@ -423,12 +365,10 @@ verify_script_timeout(Config, ScriptTimeout, StatusCode) ->
{version, "HTTP/1.0"}]),
inets:stop(httpd, Pid).
-
-%%-------------------------------------------------------------------------
%%-------------------------------------------------------------------------
-slowdose(doc) ->
- ["Testing minimum bytes per second option"];
+slowdose() ->
+ [{doc, "Testing minimum bytes per second option"}].
slowdose(Config) when is_list(Config) ->
HttpdConf = ?config(httpd_conf, Config),
{ok, Pid} = inets:start(httpd, [{port, 0}, {minimum_bytes_per_second, 200}|HttpdConf]),
@@ -439,6 +379,40 @@ slowdose(Config) when is_list(Config) ->
after 6000 ->
{error, closed} = gen_tcp:send(Socket, "Hey")
end.
+
+%%-------------------------------------------------------------------------
+%% Internal functions
+%%-------------------------------------------------------------------------
+
+verify_script_nocache(Config, CgiNoCache, EsiNoCache, CgiOption, EsiOption) ->
+ HttpdConf = ?config(httpd_conf, Config),
+ CgiScript = ?config(cgi_printenv, Config),
+ CgiDir = ?config(cgi_dir, Config),
+ {ok, Pid} = inets:start(httpd, [{port, 0},
+ {script_alias,
+ {"/cgi-bin/", CgiDir ++ "/"}},
+ {script_nocache, CgiNoCache},
+ {erl_script_alias,
+ {"/cgi-bin/erl", [httpd_example,io]}},
+ {erl_script_nocache, EsiNoCache}
+ | HttpdConf]),
+ Info = httpd:info(Pid),
+ Port = proplists:get_value(port, Info),
+ Address = proplists:get_value(bind_address, Info),
+ ok = httpd_test_lib:verify_request(ip_comm, Address, Port, node(),
+ "GET /cgi-bin/" ++ CgiScript ++
+ " HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ CgiOption,
+ {version, "HTTP/1.0"}]),
+ ok = httpd_test_lib:verify_request(ip_comm, Address, Port, node(),
+ "GET /cgi-bin/erl/httpd_example:get "
+ "HTTP/1.0\r\n\r\n",
+ [{statuscode, 200},
+ EsiOption,
+ {version, "HTTP/1.0"}]),
+ inets:stop(httpd, Pid).
+
find_URL_path([]) ->
"";
find_URL_path(["URL", URL | _]) ->
@@ -446,21 +420,6 @@ find_URL_path(["URL", URL | _]) ->
find_URL_path([_ | Rest]) ->
find_URL_path(Rest).
-
-tsp(F) ->
- inets_test_lib:tsp(F).
-tsp(F, A) ->
- inets_test_lib:tsp(F, A).
-
-tsf(Reason) ->
- inets_test_lib:tsf(Reason).
-
-tss(Time) ->
- inets_test_lib:tss(Time).
-
-
-
-
skip(Reason) ->
{skip, Reason}.
diff --git a/lib/inets/test/httpd_test_lib.erl b/lib/inets/test/httpd_test_lib.erl
index ed466fd727..36a5bb9e71 100644
--- a/lib/inets/test/httpd_test_lib.erl
+++ b/lib/inets/test/httpd_test_lib.erl
@@ -103,7 +103,7 @@ verify_request(SocketType, Host, Port, TranspOpts0, Node, RequestStr, Options, T
try inets_test_lib:connect_bin(SocketType, Host, Port, TranspOpts) of
{ok, Socket} ->
- SendRes = inets_test_lib:send(SocketType, Socket, RequestStr),
+ ok = inets_test_lib:send(SocketType, Socket, RequestStr),
State = case inets_regexp:match(RequestStr, "printenv") of
nomatch ->
#state{};
diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk
index cbcf0362c9..bbd86c3eb3 100644
--- a/lib/inets/vsn.mk
+++ b/lib/inets/vsn.mk
@@ -18,6 +18,6 @@
# %CopyrightEnd%
APPLICATION = inets
-INETS_VSN = 5.10
+INETS_VSN = 5.10.1
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)"
diff --git a/lib/mnesia/src/mnesia_controller.erl b/lib/mnesia/src/mnesia_controller.erl
index a83e55ac62..fe2fd67d71 100644
--- a/lib/mnesia/src/mnesia_controller.erl
+++ b/lib/mnesia/src/mnesia_controller.erl
@@ -1208,7 +1208,14 @@ handle_info(Done = #loader_done{worker_pid=WPid, table_name=Tab}, State0) ->
{value,{_,Worker}} = lists:keysearch(WPid,1,get_loaders(State0)),
add_loader(Tab,Worker,State1);
_ ->
- State1
+ DelState = State1#state{late_loader_queue=gb_trees:delete_any(Tab, LateQueue0)},
+ case ?catch_val({Tab, storage_type}) of
+ ram_copies ->
+ cast({disc_load, Tab, ram_only}),
+ DelState;
+ _ ->
+ DelState
+ end
end
end,
State3 = opt_start_worker(State2),
diff --git a/lib/mnesia/src/mnesia_locker.erl b/lib/mnesia/src/mnesia_locker.erl
index 81b435c6dc..c3846b00c0 100644
--- a/lib/mnesia/src/mnesia_locker.erl
+++ b/lib/mnesia/src/mnesia_locker.erl
@@ -131,9 +131,14 @@ send_release_tid(Nodes, Tid) ->
receive_release_tid_acc([Node | Nodes], Tid) ->
receive
{?MODULE, Node, {tid_released, Tid}} ->
- receive_release_tid_acc(Nodes, Tid);
- {mnesia_down, Node} ->
receive_release_tid_acc(Nodes, Tid)
+ after 0 ->
+ receive
+ {?MODULE, Node, {tid_released, Tid}} ->
+ receive_release_tid_acc(Nodes, Tid);
+ {mnesia_down, Node} ->
+ receive_release_tid_acc(Nodes, Tid)
+ end
end;
receive_release_tid_acc([], _Tid) ->
ok.
diff --git a/lib/mnesia/test/mnesia_qlc_test.erl b/lib/mnesia/test/mnesia_qlc_test.erl
index 5f46840ae9..9886754710 100644
--- a/lib/mnesia/test/mnesia_qlc_test.erl
+++ b/lib/mnesia/test/mnesia_qlc_test.erl
@@ -264,7 +264,7 @@ atomic_eval(Config) ->
?match({1,[{a,{a,9},91}]}, ok(Restart,[Pid3, Cursor])),
QC1 = ok(fun() -> qlc:cursor(Q1) end, []),
- ?match({'EXIT', _}, qlc:next_answers(QC1)),
+ ?match({'EXIT', _}, (catch qlc:next_answers(QC1))),
?match({aborted,_}, ok(fun()->qlc:next_answers(QC1)end,[])),
?verify_mnesia(Ns, []).
diff --git a/lib/mnesia/test/mnesia_test_lib.hrl b/lib/mnesia/test/mnesia_test_lib.hrl
index 281634c239..94a195f01f 100644
--- a/lib/mnesia/test/mnesia_test_lib.hrl
+++ b/lib/mnesia/test/mnesia_test_lib.hrl
@@ -46,15 +46,32 @@
-define(match(ExpectedRes,Expr),
fun() ->
- AcTuAlReS = (catch (Expr)),
- case AcTuAlReS of
- ExpectedRes ->
- ?verbose("ok, ~n Result as expected:~p~n",[AcTuAlReS]),
- {success,AcTuAlReS};
- _ ->
- ?error("Not Matching Actual result was:~n ~p~n",
- [AcTuAlReS]),
- {fail,AcTuAlReS}
+ try Expr of
+ _AR_0 = ExpectedRes ->
+ ?verbose("ok, ~n Result as expected:~p~n",[_AR_0]),
+ {success,_AR_0};
+ _AR_0 ->
+ ?error("Not Matching Actual result was:~n ~p~n",[_AR_0]),
+ {fail,_AR_0}
+ catch
+ exit:{aborted, _ER_1} when
+ element(1, _ER_1) =:= node_not_running;
+ element(1, _ER_1) =:= bad_commit;
+ element(1, _ER_1) =:= cyclic ->
+ %% Need to re-raise these to restart transaction
+ erlang:raise(exit, {aborted, _ER_1}, erlang:get_stacktrace());
+ exit:_AR_1 ->
+ case fun(_AR_EXIT_) -> {'EXIT', _AR_EXIT_} end(_AR_1) of
+ _AR_2 = ExpectedRes ->
+ ?verbose("ok, ~n Result as expected:~p~n",[_AR_2]),
+ {success,_AR_2};
+ _AR_2 ->
+ ?error("Not Matching Actual result was:~n ~p~n", [_AR_2]),
+ {fail,_AR_2}
+ end;
+ _:_AR_1 ->
+ ?error("Not Matching Actual result was:~n ~p~n", [_AR_1]),
+ {fail,_AR_1}
end
end()).
diff --git a/lib/mnesia/test/mnesia_trans_access_test.erl b/lib/mnesia/test/mnesia_trans_access_test.erl
index 157e441b27..237984978e 100644
--- a/lib/mnesia/test/mnesia_trans_access_test.erl
+++ b/lib/mnesia/test/mnesia_trans_access_test.erl
@@ -677,7 +677,7 @@ check_res(sync_dirty, Res) when is_list(Res) ->
check_res(ets, Res) when is_list(Res) ->
Res;
check_res(Type,Res) ->
- ?match(bug,{Type,Res}).
+ ?match({bug, bug},{Type,Res}).
read_op(Oid) ->
case lists:reverse(mnesia:read(Oid)) of
@@ -1118,10 +1118,7 @@ create_live_table_index(Config, Storage) ->
ValPos = 3,
mnesia:dirty_write({Tab, 1, 2}),
- Fun = fun() ->
- ?match(ok, mnesia:write({Tab, 2, 2})),
- ok
- end,
+ Fun = fun() -> mnesia:write({Tab, 2, 2}) end,
?match({atomic, ok}, mnesia:transaction(Fun)),
?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
IRead = fun() -> lists:sort(mnesia:index_read(Tab, 2, ValPos)) end,
diff --git a/lib/observer/src/cdv_timer_cb.erl b/lib/observer/src/cdv_timer_cb.erl
index 9cdbfa05a9..d44592cf18 100644
--- a/lib/observer/src/cdv_timer_cb.erl
+++ b/lib/observer/src/cdv_timer_cb.erl
@@ -27,18 +27,21 @@
%% Defines
-define(COL_OWNER, 0).
--define(COL_MSG, ?COL_OWNER+1).
+-define(COL_NAME, ?COL_OWNER+1).
+-define(COL_MSG, ?COL_NAME+1).
-define(COL_TIME, ?COL_MSG+1).
%% Callbacks for cdv_virtual_list_wx
col_to_elem(id) -> col_to_elem(?COL_OWNER);
col_to_elem(?COL_OWNER) -> #timer.pid;
+col_to_elem(?COL_NAME) -> #timer.name;
col_to_elem(?COL_MSG) -> #timer.msg;
col_to_elem(?COL_TIME) -> #timer.time.
col_spec() ->
[{"Owner", ?wxLIST_FORMAT_LEFT, 110},
- {"Message", ?wxLIST_FORMAT_LEFT, 400},
+ {"Owner name", ?wxLIST_FORMAT_LEFT, 150},
+ {"Message", ?wxLIST_FORMAT_LEFT, 300},
{"Time left (ms)", ?wxLIST_FORMAT_RIGHT, 80}].
get_info(Owner) ->
diff --git a/lib/observer/src/cdv_virtual_list_wx.erl b/lib/observer/src/cdv_virtual_list_wx.erl
index c5a7d9a2e5..bfe115a42e 100644
--- a/lib/observer/src/cdv_virtual_list_wx.erl
+++ b/lib/observer/src/cdv_virtual_list_wx.erl
@@ -269,7 +269,7 @@ handle_event(#wx{event=#wxList{type=command_list_item_right_click,
MenuId = ?ID_DETAILS + Col,
ColText = call(Holder, {get_row, self(), Row, Col}),
case ColText of
- "[]" -> [];
+ Empty when Empty=="[]"; Empty=="" -> [];
_ ->
What =
case catch list_to_integer(ColText) of
@@ -284,8 +284,13 @@ handle_event(#wx{event=#wxList{type=command_list_item_right_click,
end
end,
MenuCols),
- wxWindow:popupMenu(Panel, Menu),
- wxMenu:destroy(Menu),
+ case MenuItems of
+ [] ->
+ wxMenu:destroy(Menu);
+ _ ->
+ wxWindow:popupMenu(Panel, Menu),
+ wxMenu:destroy(Menu)
+ end,
{noreply,State#state{menu_items=MenuItems}};
handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}},
diff --git a/lib/observer/src/crashdump_viewer.erl b/lib/observer/src/crashdump_viewer.erl
index a08d27d070..99329b94e2 100644
--- a/lib/observer/src/crashdump_viewer.erl
+++ b/lib/observer/src/crashdump_viewer.erl
@@ -298,6 +298,7 @@ expand_binary(Pos) ->
%%--------------------------------------------------------------------
init([]) ->
ets:new(cdv_dump_index_table,[ordered_set,named_table,public]),
+ ets:new(cdv_reg_proc_table,[ordered_set,named_table,public]),
{ok, #state{}}.
%%--------------------------------------------------------------------
@@ -978,9 +979,20 @@ count() ->
%%-----------------------------------------------------------------
%% Page with all processes
procs_summary(File,WS) ->
- ParseFun = fun(Fd,Pid) ->
+ ParseFun = fun(Fd,Pid0) ->
+ Pid = list_to_pid(Pid0),
Proc = get_procinfo(Fd,fun main_procinfo/5,
- #proc{pid=list_to_pid(Pid)},WS),
+ #proc{pid=Pid},WS),
+ case Proc#proc.name of
+ undefined ->
+ true;
+ Name ->
+ %% Registered process - store to allow
+ %% lookup for timers connected to
+ %% registered name instead of pid.
+ ets:insert(cdv_reg_proc_table,{Name,Pid}),
+ ets:insert(cdv_reg_proc_table,{Pid0,Name})
+ end,
case Proc#proc.memory of
undefined -> Proc#proc{memory=Proc#proc.stack_heap};
_ -> Proc
@@ -1495,8 +1507,28 @@ get_internal_ets_tables(File,WS) ->
%%-----------------------------------------------------------------
%% Page with list of all timers
get_timers(File,Pid) ->
- ParseFun = fun(Fd,Id) -> get_timerinfo_1(Fd,#timer{pid=list_to_pid(Id)}) end,
- lookup_and_parse_index(File,{?timer,Pid},ParseFun,"timers").
+ ParseFun = fun(Fd,Id) -> get_timerinfo(Fd,Id) end,
+ T1 = lookup_and_parse_index(File,{?timer,Pid},ParseFun,"timers"),
+ T2 = case ets:lookup(cdv_reg_proc_table,Pid) of
+ [{_,Name}] ->
+ lookup_and_parse_index(File,{?timer,Name},ParseFun,"timers");
+ _ ->
+ []
+ end,
+ T1 ++ T2.
+
+get_timerinfo(Fd,Id) ->
+ case catch list_to_pid(Id) of
+ Pid when is_pid(Pid) ->
+ get_timerinfo_1(Fd,#timer{pid=Pid});
+ _ ->
+ case ets:lookup(cdv_reg_proc_table,Id) of
+ [{_,Pid}] when is_pid(Pid) ->
+ get_timerinfo_1(Fd,#timer{pid=Pid,name=Id});
+ [] ->
+ get_timerinfo_1(Fd,#timer{name=Id})
+ end
+ end.
get_timerinfo_1(Fd,Timer) ->
case line_head(Fd) of
diff --git a/lib/observer/src/crashdump_viewer.hrl b/lib/observer/src/crashdump_viewer.hrl
index ae288ed573..0e2eba6dee 100644
--- a/lib/observer/src/crashdump_viewer.hrl
+++ b/lib/observer/src/crashdump_viewer.hrl
@@ -108,6 +108,7 @@
-record(timer,
{pid,
+ name,
msg,
time}).
diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl
index 59fe5b5670..7757dfea53 100644
--- a/lib/observer/src/observer_tv_table.erl
+++ b/lib/observer/src/observer_tv_table.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -409,7 +409,7 @@ handle_info({refresh, Min, Min}, State = #state{grid=Grid}) ->
wxListCtrl:refreshItem(Grid, Min), %% Avoid assert in wx below if Max is 0
{noreply, State};
handle_info({refresh, Min, Max}, State = #state{grid=Grid}) ->
- wxListCtrl:refreshItems(Grid, Min, Max),
+ Max > 0 andalso wxListCtrl:refreshItems(Grid, Min, Max),
{noreply, State};
handle_info(refresh_interval, State = #state{pid=Pid}) ->
diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl
index ced26f7119..03ca1bf9c1 100644
--- a/lib/observer/src/observer_wx.erl
+++ b/lib/observer/src/observer_wx.erl
@@ -112,7 +112,8 @@ setup(#state{frame = Frame} = State) ->
observer_lib:create_menus(DefMenus, MenuBar, default),
wxFrame:setMenuBar(Frame, MenuBar),
- StatusBar = wxFrame:createStatusBar(Frame, []),
+ StatusBar = wxStatusBar:new(Frame),
+ wxFrame:setStatusBar(Frame, StatusBar),
wxFrame:setTitle(Frame, atom_to_list(node())),
wxStatusBar:setStatusText(StatusBar, atom_to_list(node())),
@@ -388,6 +389,7 @@ handle_info(_Info, State) ->
terminate(_Reason, #state{frame = Frame}) ->
wxFrame:destroy(Frame),
+ wx:destroy(),
ok.
code_change(_, _, State) ->
diff --git a/lib/observer/test/crashdump_helper.erl b/lib/observer/test/crashdump_helper.erl
index 40dbe28d46..0eb4a92c53 100644
--- a/lib/observer/test/crashdump_helper.erl
+++ b/lib/observer/test/crashdump_helper.erl
@@ -35,7 +35,9 @@ n1_proc(Creator,_N2,Pid2,Port2,_L) ->
register(aaaaaaaa,self()),
process_flag(save_calls,3),
ets:new(cdv_test_ordset_table,[ordered_set]),
- erlang:send_after(1000000,self(),cdv_test_timer_message),
+ erlang:send_after(1000000,self(),cdv_test_timer_message1),
+ erlang:send_after(1000000,aaaaaaaa,cdv_test_timer_message2),
+ erlang:send_after(1000000,noexistproc,cdv_test_timer_message3),
Port = hd(erlang:ports()),
Fun = fun() -> ok end,
Ref = make_ref(),
diff --git a/lib/observer/test/crashdump_viewer_SUITE.erl b/lib/observer/test/crashdump_viewer_SUITE.erl
index e9567c82cb..03ab0c20e1 100644
--- a/lib/observer/test/crashdump_viewer_SUITE.erl
+++ b/lib/observer/test/crashdump_viewer_SUITE.erl
@@ -385,8 +385,14 @@ special(File,Procs) ->
{ok,[_Ets=#ets_table{}],[]} = crashdump_viewer:ets_tables(Pid),
io:format(" ets tables ok",[]),
- {ok,[_Timer=#timer{}],[]} = crashdump_viewer:timers(Pid),
- io:format(" timers ok",[]),
+
+ {ok,[#timer{pid=Pid0,name=undefined},
+ #timer{pid=Pid0,name="aaaaaaaa"}],[]} =
+ crashdump_viewer:timers(Pid),
+ {ok,AllTimers,_TimersTW} = crashdump_viewer:timers(all),
+ #timer{name="noexistproc"} =
+ lists:keyfind(undefined,#timer.pid,AllTimers),
+ io:format(" timers ok:",[]),
{ok,Mod1=#loaded_mod{},[]} =
crashdump_viewer:loaded_mod_details(atom_to_list(?helper_mod)),
diff --git a/lib/observer/test/observer_SUITE.erl b/lib/observer/test/observer_SUITE.erl
index af07165456..5cf719acb1 100644
--- a/lib/observer/test/observer_SUITE.erl
+++ b/lib/observer/test/observer_SUITE.erl
@@ -45,7 +45,7 @@ all() ->
groups() ->
[{gui, [],
[basic
- %% , process_win, table_win
+ , process_win, table_win
]
}].
@@ -107,6 +107,10 @@ appup_file(Config) when is_list(Config) ->
basic(suite) -> [];
basic(doc) -> [""];
basic(Config) when is_list(Config) ->
+ timer:send_after(100, "foobar"), %% Otherwise the timer sever gets added to procs
+ ProcsBefore = processes(),
+ NumProcsBefore = length(ProcsBefore),
+
ok = observer:start(),
Notebook = setup_whitebox_testing(),
@@ -116,11 +120,11 @@ basic(Config) when is_list(Config) ->
0 = wxNotebook:getSelection(Notebook),
timer:sleep(500),
Check = fun(N, TestMore) ->
- ok = wxNotebook:advanceSelection(Notebook),
TestMore andalso
test_page(wxNotebook:getPageText(Notebook, N),
wxNotebook:getCurrentPage(Notebook)),
- timer:sleep(200)
+ timer:sleep(200),
+ ok = wxNotebook:advanceSelection(Notebook)
end,
%% Just verify that we can toogle trough all pages
[_|_] = [Check(N, false) || N <- lists:seq(1, Count)],
@@ -128,9 +132,22 @@ basic(Config) when is_list(Config) ->
Frame = get_top_level_parent(Notebook),
{W,H} = wxWindow:getSize(Frame),
wxWindow:setSize(Frame, W+10, H+10),
- [_|_] = [Check(N, true) || N <- lists:seq(1, Count)],
-
- ok = observer:stop().
+ [_|_] = [Check(N, true) || N <- lists:seq(0, Count-1)],
+
+ ok = observer:stop(),
+ timer:sleep(2000), %% stop is async
+ ProcsAfter = processes(),
+ NumProcsAfter = length(ProcsAfter),
+ if NumProcsAfter=/=NumProcsBefore ->
+ ct:log("Before but not after:~n~p~n",
+ [[{P,process_info(P)} || P <- ProcsBefore -- ProcsAfter]]),
+ ct:log("After but not before:~n~p~n",
+ [[{P,process_info(P)} || P <- ProcsAfter -- ProcsBefore]]),
+ ct:fail("leaking processes");
+ true ->
+ ok
+ end,
+ ok.
test_page("Load Charts" ++ _, _Window) ->
%% Just let it display some info and hopefully it doesn't crash
@@ -163,8 +180,11 @@ test_page("Processes" ++ _, _Window) ->
timer:sleep(1000), %% Give it time to refresh
ok;
-test_page("Table" ++ _, _Window) ->
+test_page(_Title = "Table" ++ _, _Window) ->
Tables = [ets:new(list_to_atom("Test-" ++ [C]), [public]) || C <- lists:seq($A, $Z)],
+ Table = lists:nth(3, Tables),
+ ets:insert(Table, [{N,100-N} || N <- lists:seq(1,100)]),
+
Active = get_active(),
Active ! refresh_interval,
ChangeSort = fun(N) ->
@@ -174,8 +194,6 @@ test_page("Table" ++ _, _Window) ->
end,
[ChangeSort(N) || N <- lists:seq(1,5) ++ [0]],
timer:sleep(1000),
- Table = lists:nth(3, Tables),
- ets:insert(Table, [{N,100-N} || N <- lists:seq(1,100)]),
Focus = #wx{event=#wxList{type=command_list_item_selected, itemIndex=2}},
Active ! Focus,
Activate = #wx{event=#wxList{type=command_list_item_activated, itemIndex=2}},
@@ -226,14 +244,12 @@ table_win(Config) when is_list(Config) ->
%% Modal can not test edit..
%% TPid = wx_object:get_pid(TObj),
%% TPid ! #wx{event=#wxList{type=command_list_item_activated, itemIndex=12}},
- timer:sleep(2000),
+ timer:sleep(3000),
wx_object:get_pid(TObj) ! #wx{event=#wxClose{type=close_window}},
observer:stop(),
ok.
-
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
get_top_level_parent(Window) ->
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index bce02966ae..84d5e5c86e 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2004</year><year>2013</year>
+ <year>2004</year><year>2014</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -29,6 +29,36 @@
<file>notes.xml</file>
</header>
+<section><title>Ssh 3.0.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed timeout bug in ssh:connect.</p>
+ <p>
+ Own Id: OTP-11908</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Option <c>max_sessions</c> added to
+ <c>ssh:daemon/{2,3}</c>. This option, if set, limits the
+ number of simultaneous connections accepted by the
+ daemon.</p>
+ <p>
+ Own Id: OTP-11885</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 3.0.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src
index 1917c95f5a..42eb2167e0 100644
--- a/lib/ssh/src/ssh.appup.src
+++ b/lib/ssh/src/ssh.appup.src
@@ -19,9 +19,13 @@
{"%VSN%",
[
+ {"3.0.1", [{load_module, ssh, soft_purge, soft_purge, []},
+ {load_module, ssh_acceptor, soft_purge, soft_purge, []}]},
{<<".*">>, [{restart_application, ssh}]}
],
[
+ {"3.0.1", [{load_module, ssh, soft_purge, soft_purge, []},
+ {load_module, ssh_acceptor, soft_purge, soft_purge, []}]},
{<<".*">>, [{restart_application, ssh}]}
]
}.
diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl
index 832b144db9..35336bce8b 100644
--- a/lib/ssh/src/ssh_io.erl
+++ b/lib/ssh/src/ssh_io.erl
@@ -81,6 +81,8 @@ format(Fmt, Args) ->
trim(Line) when is_list(Line) ->
lists:reverse(trim1(lists:reverse(trim1(Line))));
+trim(Line) when is_binary(Line) ->
+ trim(unicode:characters_to_list(Line));
trim(Other) -> Other.
trim1([$\s|Cs]) -> trim(Cs);
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl
index 37a307d783..ba38c1da40 100644
--- a/lib/ssh/test/ssh_basic_SUITE.erl
+++ b/lib/ssh/test/ssh_basic_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -49,6 +49,7 @@ all() ->
server_userpassword_option,
double_close,
ssh_connect_timeout,
+ ssh_connect_arg4_timeout,
{group, hardening_tests}
].
diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk
index c8cac3e852..40ed27d8f5 100644
--- a/lib/ssh/vsn.mk
+++ b/lib/ssh/vsn.mk
@@ -1,5 +1,5 @@
#-*-makefile-*- ; force emacs to enter makefile-mode
-SSH_VSN = 3.1
+SSH_VSN = 3.0.2
APP_VSN = "ssh-$(SSH_VSN)"
diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src
index b0ef292c4e..b713f86c1e 100644
--- a/lib/ssl/src/ssl.appup.src
+++ b/lib/ssl/src/ssl.appup.src
@@ -1,33 +1,13 @@
%% -*- erlang -*-
{"%VSN%",
[
- {"5.3.3", [{load_module, ssl, soft_purge, soft_purge, []},
- {load_module, ssl_connection, soft_purge, soft_purge, []},
- {load_module, ssl_handshake, soft_purge, soft_purge, []},
- {load_module, tls_handshake, soft_purge, soft_purge, []},
- {load_module, tls_connection, soft_purge, soft_purge, []}]},
- {"5.3.2", [{load_module, ssl, soft_purge, soft_purge, []},
- {load_module, ssl_connection, soft_purge, soft_purge, []},
- {load_module, ssl_handshake, soft_purge, soft_purge, []},
- {load_module, tls_handshake, soft_purge, soft_purge, []},
- {load_module, tls_connection, soft_purge, soft_purge, []}]},
- {<<"5\\.3\\.1($|\\..*)">>, [{restart_application, ssl}]},
+ {<<"5\\.3\\.[1-4]($|\\..*)">>, [{restart_application, ssl}]},
{<<"5\\.[0-2]($|\\..*)">>, [{restart_application, ssl}]},
{<<"4\\..*">>, [{restart_application, ssl}]},
{<<"3\\..*">>, [{restart_application, ssl}]}
],
[
- {"5.3.3", [{load_module, ssl, soft_purge, soft_purge, []},
- {load_module, ssl_connection, soft_purge, soft_purge, []},
- {load_module, ssl_handshake, soft_purge, soft_purge, []},
- {load_module, tls_handshake, soft_purge, soft_purge, []},
- {load_module, tls_connection, soft_purge, soft_purge, []}]},
- {"5.3.2", [{load_module, ssl, soft_purge, soft_purge, []},
- {load_module, ssl_connection, soft_purge, soft_purge, []},
- {load_module, ssl_handshake, soft_purge, soft_purge, []},
- {load_module, tls_handshake, soft_purge, soft_purge, []},
- {load_module, tls_connection, soft_purge, soft_purge, []}]},
- {<<"5\\.3\\.1($|\\..*)">>, [{restart_application, ssl}]},
+ {<<"5\\.3\\.[1-4]($|\\..*)">>, [{restart_application, ssl}]},
{<<"5\\.[0-2]($|\\..*)">>, [{restart_application, ssl}]},
{<<"4\\..*">>, [{restart_application, ssl}]},
{<<"3\\..*">>, [{restart_application, ssl}]}
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 234db21443..be1041ca13 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -99,7 +99,7 @@ connect(Socket, SslOptions0, Timeout) when is_port(Socket) ->
{gen_tcp, tcp, tcp_closed, tcp_error}),
EmulatedOptions = ssl_socket:emulated_options(),
{ok, SocketValues} = ssl_socket:getopts(Transport, Socket, EmulatedOptions),
- try handle_options(SslOptions0 ++ SocketValues, client) of
+ try handle_options(SslOptions0 ++ SocketValues) of
{ok, #config{transport_info = CbInfo, ssl = SslOptions, emulated = EmOpts,
connection_cb = ConnectionCb}} ->
@@ -107,7 +107,7 @@ connect(Socket, SslOptions0, Timeout) when is_port(Socket) ->
case ssl_socket:peername(Transport, Socket) of
{ok, {Address, Port}} ->
ssl_connection:connect(ConnectionCb, Address, Port, Socket,
- {SslOptions, emulated_socket_options(EmOpts, #socket_options{})},
+ {SslOptions, emulated_socket_options(EmOpts, #socket_options{}), undefined},
self(), CbInfo, Timeout);
{error, Error} ->
{error, Error}
@@ -121,7 +121,7 @@ connect(Host, Port, Options) ->
connect(Host, Port, Options, infinity).
connect(Host, Port, Options, Timeout) ->
- try handle_options(Options, client) of
+ try handle_options(Options) of
{ok, Config} ->
do_connect(Host,Port,Config,Timeout)
catch
@@ -139,7 +139,7 @@ listen(_Port, []) ->
{error, nooptions};
listen(Port, Options0) ->
try
- {ok, Config} = handle_options(Options0, server),
+ {ok, Config} = handle_options(Options0),
ConnectionCb = connection_cb(Options0),
#config{transport_info = {Transport, _, _, _}, inet_user = Options, connection_cb = ConnectionCb,
ssl = SslOpts, emulated = EmOpts} = Config,
@@ -176,11 +176,11 @@ transport_accept(#sslsocket{pid = {ListenSocket,
{ok, EmOpts} = ssl_socket:get_emulated_opts(Tracker),
{ok, Port} = ssl_socket:port(Transport, Socket),
ConnArgs = [server, "localhost", Port, Socket,
- {SslOpts, emulated_socket_options(EmOpts, #socket_options{})}, self(), CbInfo],
+ {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), Tracker}, self(), CbInfo],
ConnectionSup = connection_sup(ConnectionCb),
case ConnectionSup:start_child(ConnArgs) of
{ok, Pid} ->
- ssl_connection:socket_control(ConnectionCb, Socket, Pid, Transport);
+ ssl_connection:socket_control(ConnectionCb, Socket, Pid, Transport, Tracker);
{error, Reason} ->
{error, Reason}
end;
@@ -211,10 +211,11 @@ ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) ->
ssl_accept(#sslsocket{} = Socket, [], Timeout) ->
ssl_accept(#sslsocket{} = Socket, Timeout);
-ssl_accept(#sslsocket{} = Socket, SslOptions, Timeout) ->
+ssl_accept(#sslsocket{fd = {_, _, _, Tracker}} = Socket, SslOpts0, Timeout) ->
try
- {ok, #config{ssl = SSL}} = handle_options(SslOptions, server),
- ssl_connection:handshake(Socket, SSL, Timeout)
+ {ok, EmOpts, InheritedSslOpts} = ssl_socket:get_all_opts(Tracker),
+ SslOpts = handle_options(SslOpts0, InheritedSslOpts),
+ ssl_connection:handshake(Socket, {SslOpts, emulated_socket_options(EmOpts, #socket_options{})}, Timeout)
catch
Error = {error, _Reason} -> Error
end;
@@ -224,12 +225,12 @@ ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) ->
EmulatedOptions = ssl_socket:emulated_options(),
{ok, SocketValues} = ssl_socket:getopts(Transport, Socket, EmulatedOptions),
ConnetionCb = connection_cb(SslOptions),
- try handle_options(SslOptions ++ SocketValues, server) of
+ try handle_options(SslOptions ++ SocketValues) of
{ok, #config{transport_info = CbInfo, ssl = SslOpts, emulated = EmOpts}} ->
ok = ssl_socket:setopts(Transport, Socket, ssl_socket:internal_inet_values()),
{ok, Port} = ssl_socket:port(Transport, Socket),
ssl_connection:ssl_accept(ConnetionCb, Port, Socket,
- {SslOpts, emulated_socket_options(EmOpts, #socket_options{})},
+ {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), undefined},
self(), CbInfo, Timeout)
catch
Error = {error, _Reason} -> Error
@@ -299,7 +300,7 @@ connection_info(#sslsocket{pid = {Listen, _}}) when is_port(Listen) ->
%%
%% Description: same as inet:peername/1.
%%--------------------------------------------------------------------
-peername(#sslsocket{pid = Pid, fd = {Transport, Socket, _}}) when is_pid(Pid)->
+peername(#sslsocket{pid = Pid, fd = {Transport, Socket, _, _}}) when is_pid(Pid)->
ssl_socket:peername(Transport, Socket);
peername(#sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_}}}}) ->
ssl_socket:peername(Transport, ListenSocket). %% Will return {error, enotconn}
@@ -345,17 +346,22 @@ negotiated_next_protocol(#sslsocket{pid = Pid}) ->
%%--------------------------------------------------------------------
cipher_suites() ->
cipher_suites(erlang).
-
+
cipher_suites(erlang) ->
Version = tls_record:highest_protocol_version([]),
- [suite_definition(S) || S <- ssl_cipher:suites(Version)];
-
+ ssl_cipher:filter_suites([suite_definition(S)
+ || S <- ssl_cipher:suites(Version)]);
cipher_suites(openssl) ->
Version = tls_record:highest_protocol_version([]),
- [ssl_cipher:openssl_suite_name(S) || S <- ssl_cipher:suites(Version)];
+ [ssl_cipher:openssl_suite_name(S)
+ || S <- ssl_cipher:filter_suites(ssl_cipher:suites(Version))];
cipher_suites(all) ->
Version = tls_record:highest_protocol_version([]),
- [suite_definition(S) || S <- ssl_cipher:all_suites(Version)].
+ Supported = ssl_cipher:all_suites(Version)
+ ++ ssl_cipher:anonymous_suites(Version)
+ ++ ssl_cipher:psk_suites(Version)
+ ++ ssl_cipher:srp_suites(),
+ ssl_cipher:filter_suites([suite_definition(S) || S <- Supported]).
%%--------------------------------------------------------------------
-spec getopts(#sslsocket{}, [gen_tcp:option_name()]) ->
@@ -423,10 +429,10 @@ shutdown(#sslsocket{pid = Pid}, How) ->
%%
%% Description: Same as inet:sockname/1
%%--------------------------------------------------------------------
-sockname(#sslsocket{pid = {Listen, #config{transport_info = {Transport,_, _, _}}}}) when is_port(Listen) ->
+sockname(#sslsocket{pid = {Listen, #config{transport_info = {Transport, _, _, _}}}}) when is_port(Listen) ->
ssl_socket:sockname(Transport, Listen);
-sockname(#sslsocket{pid = Pid, fd = {Transport, Socket, _}}) when is_pid(Pid) ->
+sockname(#sslsocket{pid = Pid, fd = {Transport, Socket, _, _}}) when is_pid(Pid) ->
ssl_socket:sockname(Transport, Socket).
%%---------------------------------------------------------------
@@ -546,7 +552,7 @@ do_connect(Address, Port,
try Transport:connect(Address, Port, SocketOpts, Timeout) of
{ok, Socket} ->
ssl_connection:connect(ConnetionCb, Address, Port, Socket,
- {SslOpts, emulated_socket_options(EmOpts, #socket_options{})},
+ {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), undefined},
self(), CbInfo, Timeout);
{error, Reason} ->
{error, Reason}
@@ -559,53 +565,47 @@ do_connect(Address, Port,
{error, {options, {socket_options, UserOpts}}}
end.
-handle_options(Opts0, _Role) ->
+%% Handle extra ssl options given to ssl_accept
+handle_options(Opts0, #ssl_options{protocol = Protocol, cacerts = CaCerts0,
+ cacertfile = CaCertFile0} = InheritedSslOpts) ->
+ RecordCB = record_cb(Protocol),
+ CaCerts = handle_option(cacerts, Opts0, CaCerts0),
+ {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun} = handle_verify_options(Opts0, CaCerts),
+ CaCertFile = case proplists:get_value(cacertfile, Opts0, CaCertFile0) of
+ undefined ->
+ CaCertDefault;
+ CAFile ->
+ CAFile
+ end,
+ NewVerifyOpts = InheritedSslOpts#ssl_options{cacerts = CaCerts,
+ cacertfile = CaCertFile,
+ verify = Verify,
+ verify_fun = VerifyFun,
+ fail_if_no_peer_cert = FailIfNoPeerCert},
+ SslOpts1 = lists:foldl(fun(Key, PropList) ->
+ proplists:delete(Key, PropList)
+ end, Opts0, [cacerts, cacertfile, verify, verify_fun, fail_if_no_peer_cert]),
+ case handle_option(versions, SslOpts1, []) of
+ [] ->
+ new_ssl_options(SslOpts1, NewVerifyOpts, RecordCB);
+ Value ->
+ Versions = [RecordCB:protocol_version(Vsn) || Vsn <- Value],
+ new_ssl_options(proplists:delete(versions, SslOpts1),
+ NewVerifyOpts#ssl_options{versions = Versions}, record_cb(Protocol))
+ end.
+
+%% Handle all options in listen and connect
+handle_options(Opts0) ->
Opts = proplists:expand([{binary, [{mode, binary}]},
{list, [{mode, list}]}], Opts0),
assert_proplist(Opts),
RecordCb = record_cb(Opts),
ReuseSessionFun = fun(_, _, _, _) -> true end,
-
- DefaultVerifyNoneFun =
- {fun(_,{bad_cert, _}, UserState) ->
- {valid, UserState};
- (_,{extension, _}, UserState) ->
- {unknown, UserState};
- (_, valid, UserState) ->
- {valid, UserState};
- (_, valid_peer, UserState) ->
- {valid, UserState}
- end, []},
-
- VerifyNoneFun = handle_option(verify_fun, Opts, DefaultVerifyNoneFun),
-
- UserFailIfNoPeerCert = handle_option(fail_if_no_peer_cert, Opts, false),
- UserVerifyFun = handle_option(verify_fun, Opts, undefined),
CaCerts = handle_option(cacerts, Opts, undefined),
- {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun} =
- %% Handle 0, 1, 2 for backwards compatibility
- case proplists:get_value(verify, Opts, verify_none) of
- 0 ->
- {verify_none, false,
- ca_cert_default(verify_none, VerifyNoneFun, CaCerts), VerifyNoneFun};
- 1 ->
- {verify_peer, false,
- ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun};
- 2 ->
- {verify_peer, true,
- ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun};
- verify_none ->
- {verify_none, false,
- ca_cert_default(verify_none, VerifyNoneFun, CaCerts), VerifyNoneFun};
- verify_peer ->
- {verify_peer, UserFailIfNoPeerCert,
- ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun};
- Value ->
- throw({error, {options, {verify, Value}}})
- end,
-
+ {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun} = handle_verify_options(Opts, CaCerts),
+
CertFile = handle_option(certfile, Opts, <<>>),
RecordCb = record_cb(Opts),
@@ -652,7 +652,8 @@ handle_options(Opts0, _Role) ->
handle_option(client_preferred_next_protocols, Opts, undefined)),
log_alert = handle_option(log_alert, Opts, true),
server_name_indication = handle_option(server_name_indication, Opts, undefined),
- honor_cipher_order = handle_option(honor_cipher_order, Opts, false)
+ honor_cipher_order = handle_option(honor_cipher_order, Opts, false),
+ protocol = proplists:get_value(protocol, Opts, tls)
},
CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}),
@@ -671,10 +672,10 @@ handle_options(Opts0, _Role) ->
proplists:delete(Key, PropList)
end, Opts, SslOptions),
- {SSLsock, Emulated} = emulated_options(SockOpts),
+ {Sock, Emulated} = emulated_options(SockOpts),
ConnetionCb = connection_cb(Opts),
- {ok, #config{ssl = SSLOptions, emulated = Emulated, inet_ssl = SSLsock,
+ {ok, #config{ssl = SSLOptions, emulated = Emulated, inet_ssl = Sock,
inet_user = SockOpts, transport_info = CbInfo, connection_cb = ConnetionCb
}}.
@@ -933,8 +934,11 @@ handle_cipher_option(Value, Version) when is_list(Value) ->
error:_->
throw({error, {options, {ciphers, Value}}})
end.
-binary_cipher_suites(Version, []) -> % Defaults to all supported suites
- ssl_cipher:suites(Version);
+
+binary_cipher_suites(Version, []) ->
+ %% Defaults to all supported suites that does
+ %% not require explicit configuration
+ ssl_cipher:filter_suites(ssl_cipher:suites(Version));
binary_cipher_suites(Version, [{_,_,_,_}| _] = Ciphers0) -> %% Backwards compatibility
Ciphers = [{KeyExchange, Cipher, Hash} || {KeyExchange, Cipher, Hash, _} <- Ciphers0],
binary_cipher_suites(Version, Ciphers);
@@ -943,14 +947,15 @@ binary_cipher_suites(Version, [{_,_,_}| _] = Ciphers0) ->
binary_cipher_suites(Version, Ciphers);
binary_cipher_suites(Version, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) ->
- Supported0 = ssl_cipher:suites(Version)
+ All = ssl_cipher:suites(Version)
++ ssl_cipher:anonymous_suites()
++ ssl_cipher:psk_suites(Version)
++ ssl_cipher:srp_suites(),
- Supported = ssl_cipher:filter_suites(Supported0),
- case [Cipher || Cipher <- Ciphers0, lists:member(Cipher, Supported)] of
+ case [Cipher || Cipher <- Ciphers0, lists:member(Cipher, All)] of
[] ->
- Supported; %% Defaults to all supported suits
+ %% Defaults to all supported suites that does
+ %% not require explicit configuration
+ ssl_cipher:filter_suites(ssl_cipher:suites(Version));
Ciphers ->
Ciphers
end;
@@ -1034,7 +1039,7 @@ record_cb(tls) ->
record_cb(dtls) ->
dtls_record;
record_cb(Opts) ->
- record_cb(proplists:get_value(protocol, Opts, tls)).
+ record_cb(proplists:get_value(protocol, Opts, tls)).
connection_sup(tls_connection) ->
tls_connection_sup;
@@ -1070,3 +1075,98 @@ emulated_socket_options(InetValues, #socket_options{
packet = proplists:get_value(packet, InetValues, Packet),
packet_size = proplists:get_value(packet_size, InetValues, Size)
}.
+
+new_ssl_options([], #ssl_options{} = Opts, _) ->
+ Opts;
+new_ssl_options([{verify_client_once, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{verify_client_once = validate_option(verify_client_once, Value)}, RecordCB);
+new_ssl_options([{depth, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{depth = validate_option(depth, Value)}, RecordCB);
+new_ssl_options([{cert, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{cert = validate_option(cert, Value)}, RecordCB);
+new_ssl_options([{certfile, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{certfile = validate_option(certfile, Value)}, RecordCB);
+new_ssl_options([{key, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{key = validate_option(key, Value)}, RecordCB);
+new_ssl_options([{keyfile, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{keyfile = validate_option(keyfile, Value)}, RecordCB);
+new_ssl_options([{password, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{password = validate_option(password, Value)}, RecordCB);
+new_ssl_options([{dh, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{dh = validate_option(dh, Value)}, RecordCB);
+new_ssl_options([{dhfile, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{dhfile = validate_option(dhfile, Value)}, RecordCB);
+new_ssl_options([{user_lookup_fun, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{user_lookup_fun = validate_option(user_lookup_fun, Value)}, RecordCB);
+new_ssl_options([{psk_identity, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{psk_identity = validate_option(psk_identity, Value)}, RecordCB);
+new_ssl_options([{srp_identity, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{srp_identity = validate_option(srp_identity, Value)}, RecordCB);
+new_ssl_options([{ciphers, Value} | Rest], #ssl_options{versions = Versions} = Opts, RecordCB) ->
+ Ciphers = handle_cipher_option(Value, RecordCB:highest_protocol_version(Versions)),
+ new_ssl_options(Rest,
+ Opts#ssl_options{ciphers = Ciphers}, RecordCB);
+new_ssl_options([{reuse_session, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{reuse_session = validate_option(reuse_session, Value)}, RecordCB);
+new_ssl_options([{reuse_sessions, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{reuse_sessions = validate_option(reuse_sessions, Value)}, RecordCB);
+new_ssl_options([{ssl_imp, _Value} | Rest], #ssl_options{} = Opts, RecordCB) -> %% Not used backwards compatibility
+ new_ssl_options(Rest, Opts, RecordCB);
+new_ssl_options([{renegotiate_at, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{ renegotiate_at = validate_option(renegotiate_at, Value)}, RecordCB);
+new_ssl_options([{secure_renegotiate, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{secure_renegotiate = validate_option(secure_renegotiate, Value)}, RecordCB);
+new_ssl_options([{hibernate_after, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{hibernate_after = validate_option(hibernate_after, Value)}, RecordCB);
+new_ssl_options([{next_protocols_advertised, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{next_protocols_advertised = validate_option(next_protocols_advertised, Value)}, RecordCB);
+new_ssl_options([{client_preferred_next_protocols, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{next_protocol_selector =
+ make_next_protocol_selector(validate_option(client_preferred_next_protocols, Value))}, RecordCB);
+new_ssl_options([{log_alert, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{log_alert = validate_option(log_alert, Value)}, RecordCB);
+new_ssl_options([{server_name_indication, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{server_name_indication = validate_option(server_name_indication, Value)}, RecordCB);
+new_ssl_options([{honor_cipher_order, Value} | Rest], #ssl_options{} = Opts, RecordCB) ->
+ new_ssl_options(Rest, Opts#ssl_options{honor_cipher_order = validate_option(honor_cipher_order, Value)}, RecordCB);
+new_ssl_options([{Key, Value} | _Rest], #ssl_options{}, _) ->
+ throw({error, {options, {Key, Value}}}).
+
+
+handle_verify_options(Opts, CaCerts) ->
+ DefaultVerifyNoneFun =
+ {fun(_,{bad_cert, _}, UserState) ->
+ {valid, UserState};
+ (_,{extension, _}, UserState) ->
+ {unknown, UserState};
+ (_, valid, UserState) ->
+ {valid, UserState};
+ (_, valid_peer, UserState) ->
+ {valid, UserState}
+ end, []},
+ VerifyNoneFun = handle_option(verify_fun, Opts, DefaultVerifyNoneFun),
+
+ UserFailIfNoPeerCert = handle_option(fail_if_no_peer_cert, Opts, false),
+ UserVerifyFun = handle_option(verify_fun, Opts, undefined),
+
+
+ %% Handle 0, 1, 2 for backwards compatibility
+ case proplists:get_value(verify, Opts, verify_none) of
+ 0 ->
+ {verify_none, false,
+ ca_cert_default(verify_none, VerifyNoneFun, CaCerts), VerifyNoneFun};
+ 1 ->
+ {verify_peer, false,
+ ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun};
+ 2 ->
+ {verify_peer, true,
+ ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun};
+ verify_none ->
+ {verify_none, false,
+ ca_cert_default(verify_none, VerifyNoneFun, CaCerts), VerifyNoneFun};
+ verify_peer ->
+ {verify_peer, UserFailIfNoPeerCert,
+ ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun};
+ Value ->
+ throw({error, {options, {verify, Value}}})
+ end.
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index a3ec419c2a..72467ea2a0 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -1019,7 +1019,8 @@ openssl_suite_name(Cipher) ->
%%--------------------------------------------------------------------
-spec filter(undefined | binary(), [cipher_suite()]) -> [cipher_suite()].
%%
-%% Description: .
+%% Description: Select the cipher suites that can be used together with the
+%% supplied certificate. (Server side functionality)
%%-------------------------------------------------------------------
filter(undefined, Ciphers) ->
Ciphers;
@@ -1053,7 +1054,7 @@ filter(DerCert, Ciphers) ->
%%--------------------------------------------------------------------
-spec filter_suites([cipher_suite()]) -> [cipher_suite()].
%%
-%% Description: filter suites for algorithms
+%% Description: Filter suites for algorithms supported by crypto.
%%-------------------------------------------------------------------
filter_suites(Suites = [{_,_,_}|_]) ->
Algos = crypto:supports(),
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index 1eda926bcb..34006612a2 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -37,7 +37,7 @@
%% Setup
-export([connect/8, ssl_accept/7, handshake/2, handshake/3,
- socket_control/4]).
+ socket_control/4, socket_control/5]).
%% User Events
-export([send/2, recv/3, close/1, shutdown/2,
@@ -50,7 +50,7 @@
%% SSL FSM state functions
-export([hello/3, abbreviated/3, certify/3, cipher/3, connection/3]).
%% SSL all state functions
--export([handle_sync_event/4, handle_info/3, terminate/3]).
+-export([handle_sync_event/4, handle_info/3, terminate/3, format_status/2]).
%%====================================================================
@@ -121,9 +121,16 @@ handshake(#sslsocket{pid = Pid}, SslOptions, Timeout) ->
%% Description: Set the ssl process to own the accept socket
%%--------------------------------------------------------------------
socket_control(Connection, Socket, Pid, Transport) ->
+ socket_control(Connection, Socket, Pid, Transport, undefined).
+
+%--------------------------------------------------------------------
+-spec socket_control(tls_connection | dtls_connection, port(), pid(), atom(), pid()| undefined) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%--------------------------------------------------------------------
+socket_control(Connection, Socket, Pid, Transport, ListenTracker) ->
case Transport:controlling_process(Socket, Pid) of
ok ->
- {ok, ssl_socket:socket(Pid, Transport, Socket, Connection)};
+ {ok, ssl_socket:socket(Pid, Transport, Socket, Connection, ListenTracker)};
{error, Reason} ->
{error, Reason}
end.
@@ -602,7 +609,7 @@ cipher(#finished{verify_data = Data} = Finished,
cipher(#next_protocol{selected_protocol = SelectedProtocol},
#state{role = server, expecting_next_protocol_negotiation = true} = State0, Connection) ->
{Record, State} = Connection:next_record(State0#state{next_protocol = SelectedProtocol}),
- Connection:next_state(cipher, cipher, Record, State);
+ Connection:next_state(cipher, cipher, Record, State#state{expecting_next_protocol_negotiation = false});
cipher(timeout, State, _) ->
{next_state, cipher, State, hibernate};
@@ -642,12 +649,27 @@ handle_sync_event({application_data, Data}, From, StateName,
State#state{send_queue = queue:in({From, Data}, Queue)},
get_timeout(State)};
-handle_sync_event({start, Timeout}, StartFrom, hello, #state{protocol_cb = Connection} = State) ->
- Timer = start_or_recv_cancel_timer(Timeout, StartFrom),
- Connection:hello(start, State#state{start_or_recv_from = StartFrom,
- timer = Timer});
+handle_sync_event({start, Timeout}, StartFrom, hello, #state{role = Role,
+ protocol_cb = Connection,
+ ssl_options = SSLOpts} = State0) ->
+ try
+ State = ssl_config(SSLOpts, Role, State0),
+ Timer = start_or_recv_cancel_timer(Timeout, StartFrom),
+ Connection:hello(start, State#state{start_or_recv_from = StartFrom,
+ timer = Timer})
+ catch throw:Error ->
+ {stop, normal, {error, Error}, State0}
+ end;
+
+handle_sync_event({start, {Opts, EmOpts}, Timeout}, From, StateName, State) ->
+ try
+ handle_sync_event({start, Timeout}, From, StateName, State#state{socket_options = EmOpts,
+ ssl_options = Opts})
+ catch throw:Error ->
+ {stop, normal, {error, Error}, State}
+ end;
-%% The two clauses below could happen if a server upgrades a socket in
+%% These two clauses below could happen if a server upgrades a socket in
%% active mode. Note that in this case we are lucky that
%% controlling_process has been evalueated before receiving handshake
%% messages from client. The server should put the socket in passive
@@ -657,17 +679,16 @@ handle_sync_event({start, Timeout}, StartFrom, hello, #state{protocol_cb = Conne
%% they upgrade an active socket.
handle_sync_event({start,_}, _, connection, State) ->
{reply, connected, connection, State, get_timeout(State)};
-handle_sync_event({start,_}, _From, error, {Error, State = #state{}}) ->
- {stop, {shutdown, Error}, {error, Error}, State};
-handle_sync_event({start, Timeout}, StartFrom, StateName, State) ->
- Timer = start_or_recv_cancel_timer(Timeout, StartFrom),
- {next_state, StateName, State#state{start_or_recv_from = StartFrom,
- timer = Timer}, get_timeout(State)};
-
-handle_sync_event({start, Opts, Timeout}, From, StateName, #state{ssl_options = SslOpts} = State) ->
- NewOpts = new_ssl_options(Opts, SslOpts),
- handle_sync_event({start, Timeout}, From, StateName, State#state{ssl_options = NewOpts});
+handle_sync_event({start, Timeout}, StartFrom, StateName, #state{role = Role, ssl_options = SslOpts} = State0) ->
+ try
+ State = ssl_config(SslOpts, Role, State0),
+ Timer = start_or_recv_cancel_timer(Timeout, StartFrom),
+ {next_state, StateName, State#state{start_or_recv_from = StartFrom,
+ timer = Timer}, get_timeout(State)}
+ catch throw:Error ->
+ {stop, normal, {error, Error}, State0}
+ end;
handle_sync_event(close, _, StateName, #state{protocol_cb = Connection} = State) ->
%% Run terminate before returning
@@ -675,7 +696,6 @@ handle_sync_event(close, _, StateName, #state{protocol_cb = Connection} = State)
%% as intended.
(catch Connection:terminate(user_close, StateName, State)),
{stop, normal, ok, State#state{terminated = true}};
-
handle_sync_event({shutdown, How0}, _, StateName,
#state{transport_cb = Transport,
negotiated_version = Version,
@@ -697,17 +717,14 @@ handle_sync_event({shutdown, How0}, _, StateName,
Error ->
{stop, normal, Error, State}
end;
-
handle_sync_event({recv, _N, _Timeout}, _RecvFrom, StateName,
#state{socket_options = #socket_options{active = Active}} = State) when Active =/= false ->
{reply, {error, einval}, StateName, State, get_timeout(State)};
-
handle_sync_event({recv, N, Timeout}, RecvFrom, connection = StateName,
#state{protocol_cb = Connection} = State0) ->
Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
Connection:passive_receive(State0#state{bytes_to_read = N,
start_or_recv_from = RecvFrom, timer = Timer}, StateName);
-
%% Doing renegotiate wait with handling request until renegotiate is
%% finished. Will be handled by next_state_is_connection/2.
handle_sync_event({recv, N, Timeout}, RecvFrom, StateName, State) ->
@@ -715,26 +732,22 @@ handle_sync_event({recv, N, Timeout}, RecvFrom, StateName, State) ->
{next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom,
timer = Timer},
get_timeout(State)};
-
handle_sync_event({new_user, User}, _From, StateName,
State =#state{user_application = {OldMon, _}}) ->
NewMon = erlang:monitor(process, User),
erlang:demonitor(OldMon, [flush]),
{reply, ok, StateName, State#state{user_application = {NewMon,User}},
get_timeout(State)};
-
handle_sync_event({get_opts, OptTags}, _From, StateName,
#state{socket = Socket,
transport_cb = Transport,
socket_options = SockOpts} = State) ->
OptsReply = get_socket_opts(Transport, Socket, OptTags, SockOpts, []),
{reply, OptsReply, StateName, State, get_timeout(State)};
-
handle_sync_event(negotiated_next_protocol, _From, StateName, #state{next_protocol = undefined} = State) ->
{reply, {error, next_protocol_not_negotiated}, StateName, State, get_timeout(State)};
handle_sync_event(negotiated_next_protocol, _From, StateName, #state{next_protocol = NextProtocol} = State) ->
{reply, {ok, NextProtocol}, StateName, State, get_timeout(State)};
-
handle_sync_event({set_opts, Opts0}, _From, StateName0,
#state{socket_options = Opts1,
protocol_cb = Connection,
@@ -773,13 +786,10 @@ handle_sync_event({set_opts, Opts0}, _From, StateName0,
end
end
end;
-
handle_sync_event(renegotiate, From, connection, #state{protocol_cb = Connection} = State) ->
Connection:renegotiate(State#state{renegotiation = {true, From}});
-
handle_sync_event(renegotiate, _, StateName, State) ->
{reply, {error, already_renegotiating}, StateName, State, get_timeout(State)};
-
handle_sync_event({prf, Secret, Label, Seed, WantedLength}, _, StateName,
#state{connection_states = ConnectionStates,
negotiated_version = Version} = State) ->
@@ -805,7 +815,6 @@ handle_sync_event({prf, Secret, Label, Seed, WantedLength}, _, StateName,
error:Reason -> {error, Reason}
end,
{reply, Reply, StateName, State, get_timeout(State)};
-
handle_sync_event(info, _, StateName,
#state{negotiated_version = Version,
session = #session{cipher_suite = Suite}} = State) ->
@@ -813,14 +822,12 @@ handle_sync_event(info, _, StateName,
AtomVersion = tls_record:protocol_version(Version),
{reply, {ok, {AtomVersion, ssl:suite_definition(Suite)}},
StateName, State, get_timeout(State)};
-
handle_sync_event(session_info, _, StateName,
#state{session = #session{session_id = Id,
cipher_suite = Suite}} = State) ->
{reply, [{session_id, Id},
{cipher_suite, ssl:suite_definition(Suite)}],
StateName, State, get_timeout(State)};
-
handle_sync_event(peer_certificate, _, StateName,
#state{session = #session{peer_certificate = Cert}}
= State) ->
@@ -830,8 +837,9 @@ handle_info({ErrorTag, Socket, econnaborted}, StateName,
#state{socket = Socket, transport_cb = Transport,
start_or_recv_from = StartFrom, role = Role,
protocol_cb = Connection,
- error_tag = ErrorTag} = State) when StateName =/= connection ->
- Connection:alert_user(Transport, Socket, StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role),
+ error_tag = ErrorTag,
+ tracker = Tracker} = State) when StateName =/= connection ->
+ Connection:alert_user(Transport, Tracker,Socket, StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role),
{stop, normal, State};
handle_info({ErrorTag, Socket, Reason}, StateName, #state{socket = Socket,
@@ -881,7 +889,6 @@ terminate(_, _, #state{terminated = true}) ->
%% we want to guarantee that Transport:close has been called
%% when ssl:close/1 returns.
ok;
-
terminate({shutdown, transport_closed}, StateName, #state{send_queue = SendQueue,
renegotiation = Renegotiate} = State) ->
handle_unrecv_data(StateName, State),
@@ -894,7 +901,6 @@ terminate({shutdown, own_alert}, _StateName, #state{send_queue = SendQueue,
handle_trusted_certs_db(State),
notify_senders(SendQueue),
notify_renegotiater(Renegotiate);
-
terminate(Reason, connection, #state{negotiated_version = Version,
protocol_cb = Connection,
connection_states = ConnectionStates,
@@ -911,7 +917,6 @@ terminate(Reason, connection, #state{negotiated_version = Version,
_ ->
ok
end;
-
terminate(_Reason, _StateName, #state{transport_cb = Transport,
socket = Socket, send_queue = SendQueue,
renegotiation = Renegotiate} = State) ->
@@ -920,9 +925,50 @@ terminate(_Reason, _StateName, #state{transport_cb = Transport,
notify_renegotiater(Renegotiate),
Transport:close(Socket).
+format_status(normal, [_, State]) ->
+ [{data, [{"StateData", State}]}];
+format_status(terminate, [_, State]) ->
+ SslOptions = (State#state.ssl_options),
+ NewOptions = SslOptions#ssl_options{password = "***",
+ cert = "***",
+ cacerts = "***",
+ key = "***",
+ dh = "***",
+ psk_identity = "***",
+ srp_identity = "***"},
+ [{data, [{"StateData", State#state{connection_states = "***",
+ protocol_buffers = "***",
+ user_data_buffer = "***",
+ tls_handshake_history = "***",
+ session = "***",
+ private_key = "***",
+ diffie_hellman_params = "***",
+ diffie_hellman_keys = "***",
+ srp_params = "***",
+ srp_keys = "***",
+ premaster_secret = "***",
+ ssl_options = NewOptions
+ }}]}].
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+ssl_config(Opts, Role, State) ->
+ {ok, Ref, CertDbHandle, FileRefHandle, CacheHandle, OwnCert, Key, DHParams} =
+ ssl_config:init(Opts, Role),
+ Handshake = ssl_handshake:init_handshake_history(),
+ TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}),
+ Session = State#state.session,
+ State#state{tls_handshake_history = Handshake,
+ session = Session#session{own_certificate = OwnCert,
+ time_stamp = TimeStamp},
+ file_ref_db = FileRefHandle,
+ cert_db_ref = Ref,
+ cert_db = CertDbHandle,
+ session_cache = CacheHandle,
+ private_key = Key,
+ diffie_hellman_params = DHParams,
+ ssl_options = Opts}.
+
do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocols} =
ServerHelloExt,
#state{negotiated_version = Version,
@@ -1825,17 +1871,6 @@ make_premaster_secret({MajVer, MinVer}, rsa) ->
make_premaster_secret(_, _) ->
undefined.
-%% One day this can be maps instead, but we have to be backwards compatible for now
-new_ssl_options(New, Old) ->
- new_ssl_options(tuple_to_list(New), tuple_to_list(Old), []).
-
-new_ssl_options([], [], Acc) ->
- list_to_tuple(lists:reverse(Acc));
-new_ssl_options([undefined | Rest0], [Head1| Rest1], Acc) ->
- new_ssl_options(Rest0, Rest1, [Head1 | Acc]);
-new_ssl_options([Head0 | Rest0], [_| Rest1], Acc) ->
- new_ssl_options(Rest0, Rest1, [Head0 | Acc]).
-
negotiated_hashsign(undefined, Alg, Version) ->
%% Not negotiated choose default
case is_anonymous(Alg) of
diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl
index b01c6cb1b3..592889b177 100644
--- a/lib/ssl/src/ssl_connection.hrl
+++ b/lib/ssl/src/ssl_connection.hrl
@@ -78,7 +78,8 @@
allow_renegotiate = true ::boolean(),
expecting_next_protocol_negotiation = false ::boolean(),
next_protocol = undefined :: undefined | binary(),
- client_ecc % {Curves, PointFmt}
+ client_ecc, % {Curves, PointFmt}
+ tracker :: pid() %% Tracker process for listen socket
}).
-define(DEFAULT_DIFFIE_HELLMAN_PARAMS,
diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl
index 87ed233c0a..6aab35d6da 100644
--- a/lib/ssl/src/ssl_record.hrl
+++ b/lib/ssl/src/ssl_record.hrl
@@ -70,7 +70,7 @@
-define(INITIAL_BYTES, 5).
--define(MAX_SEQENCE_NUMBER, 18446744073709552000). %% math:pow(2, 64) - 1 = 1.8446744073709552e19
+-define(MAX_SEQENCE_NUMBER, 18446744073709551615). %% (1 bsl 64) - 1 = 18446744073709551615
%% Sequence numbers can not wrap so when max is about to be reached we should renegotiate.
%% We will renegotiate a little before so that there will be sequence numbers left
%% for the rehandshake and a little data. Currently we decided to renegotiate a little more
diff --git a/lib/ssl/src/ssl_socket.erl b/lib/ssl/src/ssl_socket.erl
index e42d637301..55eb569b20 100644
--- a/lib/ssl/src/ssl_socket.erl
+++ b/lib/ssl/src/ssl_socket.erl
@@ -23,24 +23,25 @@
-include("ssl_internal.hrl").
-include("ssl_api.hrl").
--export([socket/4, setopts/3, getopts/3, peername/2, sockname/2, port/2]).
+-export([socket/5, setopts/3, getopts/3, peername/2, sockname/2, port/2]).
-export([emulated_options/0, internal_inet_values/0, default_inet_values/0,
- init/1, start_link/2, terminate/2, inherit_tracker/3, get_emulated_opts/1,
- set_emulated_opts/2, handle_call/3, handle_cast/2,
+ init/1, start_link/3, terminate/2, inherit_tracker/3, get_emulated_opts/1,
+ set_emulated_opts/2, get_all_opts/1, handle_call/3, handle_cast/2,
handle_info/2, code_change/3]).
-record(state, {
emulated_opts,
- port
+ port,
+ ssl_opts
}).
%%--------------------------------------------------------------------
%%% Internal API
%%--------------------------------------------------------------------
-socket(Pid, Transport, Socket, ConnectionCb) ->
+socket(Pid, Transport, Socket, ConnectionCb, Tracker) ->
#sslsocket{pid = Pid,
%% "The name "fd" is keept for backwards compatibility
- fd = {Transport, Socket, ConnectionCb}}.
+ fd = {Transport, Socket, ConnectionCb, Tracker}}.
setopts(gen_tcp, #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) ->
{SockOpts, EmulatedOpts} = split_options(Options),
ok = set_emulated_opts(Tracker, EmulatedOpts),
@@ -96,28 +97,24 @@ internal_inet_values() ->
default_inet_values() ->
[{packet_size, 0}, {packet,0}, {header, 0}, {active, true}, {mode, list}].
-inherit_tracker(ListenSocket, EmOpts, #ssl_options{erl_dist = false}) ->
- ssl_listen_tracker_sup:start_child([ListenSocket, EmOpts]);
-inherit_tracker(ListenSocket, EmOpts, #ssl_options{erl_dist = true}) ->
- ssl_listen_tracker_sup:start_child_dist([ListenSocket, EmOpts]).
-
-get_emulated_opts(TrackerPid, EmOptNames) ->
- {ok, EmOpts} = get_emulated_opts(TrackerPid),
- lists:map(fun(Name) -> {value, Value} = lists:keysearch(Name, 1, EmOpts),
- Value end,
- EmOptNames).
+inherit_tracker(ListenSocket, EmOpts, #ssl_options{erl_dist = false} = SslOpts) ->
+ ssl_listen_tracker_sup:start_child([ListenSocket, EmOpts, SslOpts]);
+inherit_tracker(ListenSocket, EmOpts, #ssl_options{erl_dist = true} = SslOpts) ->
+ ssl_listen_tracker_sup:start_child_dist([ListenSocket, EmOpts, SslOpts]).
get_emulated_opts(TrackerPid) ->
call(TrackerPid, get_emulated_opts).
set_emulated_opts(TrackerPid, InetValues) ->
call(TrackerPid, {set_emulated_opts, InetValues}).
+get_all_opts(TrackerPid) ->
+ call(TrackerPid, get_all_opts).
%%====================================================================
%% ssl_listen_tracker_sup API
%%====================================================================
-start_link(Port, SockOpts) ->
- gen_server:start_link(?MODULE, [Port, SockOpts], []).
+start_link(Port, SockOpts, SslOpts) ->
+ gen_server:start_link(?MODULE, [Port, SockOpts, SslOpts], []).
%%--------------------------------------------------------------------
-spec init(list()) -> {ok, #state{}}.
@@ -126,10 +123,10 @@ start_link(Port, SockOpts) ->
%%
%% Description: Initiates the server
%%--------------------------------------------------------------------
-init([Port, Opts]) ->
+init([Port, Opts, SslOpts]) ->
process_flag(trap_exit, true),
true = link(Port),
- {ok, #state{emulated_opts = Opts, port = Port}}.
+ {ok, #state{emulated_opts = Opts, port = Port, ssl_opts = SslOpts}}.
%%--------------------------------------------------------------------
-spec handle_call(msg(), from(), #state{}) -> {reply, reply(), #state{}}.
@@ -148,7 +145,11 @@ handle_call({set_emulated_opts, Opts0}, _From,
{reply, ok, State#state{emulated_opts = Opts}};
handle_call(get_emulated_opts, _From,
#state{emulated_opts = Opts} = State) ->
- {reply, {ok, Opts}, State}.
+ {reply, {ok, Opts}, State};
+handle_call(get_all_opts, _From,
+ #state{emulated_opts = EmOpts,
+ ssl_opts = SslOpts} = State) ->
+ {reply, {ok, EmOpts, SslOpts}, State}.
%%--------------------------------------------------------------------
-spec handle_cast(msg(), #state{}) -> {noreply, #state{}}.
@@ -162,10 +163,11 @@ handle_cast(_, State)->
{noreply, State}.
%%--------------------------------------------------------------------
--spec handle_info(msg(), #state{}) -> {noreply, #state{}}.
+-spec handle_info(msg(), #state{}) -> {stop, reason(), #state{}}.
%% Possible return values not used now.
+%% {noreply, #state{}}.
%% |{noreply, #state{}, timeout()} |
-%% {stop, reason(), #state{}}.
+%%
%%
%% Description: Handling all non call/cast messages
%%-------------------------------------------------------------------
@@ -227,3 +229,9 @@ get_socket_opts(_, [], _) ->
get_socket_opts(ListenSocket, SockOptNames, Cb) ->
{ok, Opts} = Cb:getopts(ListenSocket, SockOptNames),
Opts.
+
+get_emulated_opts(TrackerPid, EmOptNames) ->
+ {ok, EmOpts} = get_emulated_opts(TrackerPid),
+ lists:map(fun(Name) -> {value, Value} = lists:keysearch(Name, 1, EmOpts),
+ Value end,
+ EmOptNames).
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 32086ff6ce..2ab085321a 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -53,7 +53,7 @@
%% Alert and close handling
-export([send_alert/2, handle_own_alert/4, handle_close_alert/3,
handle_normal_shutdown/3, handle_unexpected_message/3,
- workaround_transport_delivery_problems/2, alert_user/5, alert_user/8
+ workaround_transport_delivery_problems/2, alert_user/6, alert_user/9
]).
%% Data handling
@@ -66,18 +66,18 @@
%% gen_fsm callbacks
-export([init/1, hello/2, certify/2, cipher/2,
abbreviated/2, connection/2, handle_event/3,
- handle_sync_event/4, handle_info/3, terminate/3, code_change/4]).
+ handle_sync_event/4, handle_info/3, terminate/3, code_change/4, format_status/2]).
%%====================================================================
%% Internal application API
%%====================================================================
-start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_} = Opts,
+start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_, Tracker} = Opts,
User, {CbModule, _,_, _} = CbInfo,
Timeout) ->
try
{ok, Pid} = tls_connection_sup:start_child([Role, Host, Port, Socket,
Opts, User, CbInfo]),
- {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule),
+ {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule, Tracker),
ok = ssl_connection:handshake(SslSocket, Timeout),
{ok, SslSocket}
catch
@@ -85,13 +85,13 @@ start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_} = Opts,
Error
end;
-start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_} = Opts,
+start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_, Tracker} = Opts,
User, {CbModule, _,_, _} = CbInfo,
Timeout) ->
try
{ok, Pid} = tls_connection_sup:start_child_dist([Role, Host, Port, Socket,
Opts, User, CbInfo]),
- {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule),
+ {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule, Tracker),
ok = ssl_connection:handshake(SslSocket, Timeout),
{ok, SslSocket}
catch
@@ -144,29 +144,10 @@ send_change_cipher(Msg, #state{connection_states = ConnectionStates0,
start_link(Role, Host, Port, Socket, Options, User, CbInfo) ->
{ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}.
-init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, User, CbInfo]) ->
+init([Role, Host, Port, Socket, Options, User, CbInfo]) ->
process_flag(trap_exit, true),
- State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo),
- Handshake = ssl_handshake:init_handshake_history(),
- TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}),
- try ssl_config:init(SSLOpts0, Role) of
- {ok, Ref, CertDbHandle, FileRefHandle, CacheHandle, OwnCert, Key, DHParams} ->
- Session = State0#state.session,
- State = State0#state{
- tls_handshake_history = Handshake,
- session = Session#session{own_certificate = OwnCert,
- time_stamp = TimeStamp},
- file_ref_db = FileRefHandle,
- cert_db_ref = Ref,
- cert_db = CertDbHandle,
- session_cache = CacheHandle,
- private_key = Key,
- diffie_hellman_params = DHParams},
- gen_fsm:enter_loop(?MODULE, [], hello, State, get_timeout(State))
- catch
- throw:Error ->
- gen_fsm:enter_loop(?MODULE, [], error, {Error,State0}, get_timeout(State0))
- end.
+ State = initial_state(Role, Host, Port, Socket, Options, User, CbInfo),
+ gen_fsm:enter_loop(?MODULE, [], hello, State, get_timeout(State)).
%%--------------------------------------------------------------------
%% Description:There should be one instance of this function for each
@@ -342,8 +323,7 @@ handle_info(Msg, StateName, State) ->
%% Reason. The return value is ignored.
%%--------------------------------------------------------------------
terminate(Reason, StateName, State) ->
- ssl_connection:terminate(Reason, StateName, State).
-
+ catch ssl_connection:terminate(Reason, StateName, State).
%%--------------------------------------------------------------------
%% code_change(OldVsn, StateName, State, Extra) -> {ok, StateName, NewState}
@@ -352,6 +332,9 @@ terminate(Reason, StateName, State) ->
code_change(_OldVsn, StateName, State, _Extra) ->
{ok, StateName, State}.
+format_status(Type, Data) ->
+ ssl_connection:format_status(Type, Data).
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
@@ -368,7 +351,7 @@ encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) ->
decode_alerts(Bin) ->
ssl_alert:decode(Bin).
-initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
+initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, User,
{CbModule, DataTag, CloseTag, ErrorTag}) ->
ConnectionStates = ssl_record:init_connection_states(Role),
@@ -382,9 +365,7 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
Monitor = erlang:monitor(process, User),
#state{socket_options = SocketOptions,
- %% We do not want to save the password in the state so that
- %% could be written in the clear into error logs.
- ssl_options = SSLOptions#ssl_options{password = undefined},
+ ssl_options = SSLOptions,
session = #session{is_resumable = new},
transport_cb = CbModule,
data_tag = DataTag,
@@ -402,7 +383,8 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
renegotiation = {false, first},
start_or_recv_from = undefined,
send_queue = queue:new(),
- protocol_cb = ?MODULE
+ protocol_cb = ?MODULE,
+ tracker = Tracker
}.
next_state(Current,_, #alert{} = Alert, #state{negotiated_version = Version} = State) ->
@@ -507,7 +489,7 @@ next_record(State) ->
next_record_if_active(State =
#state{socket_options =
- #socket_options{active = false}}) ->
+ #socket_options{active = false}}) ->
{no_record ,State};
next_record_if_active(State) ->
@@ -571,7 +553,8 @@ read_application_data(Data, #state{user_application = {_Mon, Pid},
bytes_to_read = BytesToRead,
start_or_recv_from = RecvFrom,
timer = Timer,
- user_data_buffer = Buffer0} = State0) ->
+ user_data_buffer = Buffer0,
+ tracker = Tracker} = State0) ->
Buffer1 = if
Buffer0 =:= <<>> -> Data;
Data =:= <<>> -> Buffer0;
@@ -579,7 +562,7 @@ read_application_data(Data, #state{user_application = {_Mon, Pid},
end,
case get_data(SOpts, BytesToRead, Buffer1) of
{ok, ClientData, Buffer} -> % Send data
- SocketOpt = deliver_app_data(Transport, Socket, SOpts, ClientData, Pid, RecvFrom),
+ SocketOpt = deliver_app_data(Transport, Socket, SOpts, ClientData, Pid, RecvFrom, Tracker),
cancel_timer(Timer),
State = State0#state{user_data_buffer = Buffer,
start_or_recv_from = undefined,
@@ -600,7 +583,7 @@ read_application_data(Data, #state{user_application = {_Mon, Pid},
{passive, Buffer} ->
next_record_if_active(State0#state{user_data_buffer = Buffer});
{error,_Reason} -> %% Invalid packet in packet mode
- deliver_packet_error(Transport, Socket, SOpts, Buffer1, Pid, RecvFrom),
+ deliver_packet_error(Transport, Socket, SOpts, Buffer1, Pid, RecvFrom, Tracker),
{stop, normal, State0}
end.
@@ -655,8 +638,8 @@ decode_packet(Type, Buffer, PacketOpts) ->
%% HTTP headers using the {packet, httph} option, we don't do any automatic
%% switching of states.
deliver_app_data(Transport, Socket, SOpts = #socket_options{active=Active, packet=Type},
- Data, Pid, From) ->
- send_or_reply(Active, Pid, From, format_reply(Transport, Socket, SOpts, Data)),
+ Data, Pid, From, Tracker) ->
+ send_or_reply(Active, Pid, From, format_reply(Transport, Socket, SOpts, Data, Tracker)),
SO = case Data of
{P, _, _, _} when ((P =:= http_request) or (P =:= http_response)),
((Type =:= http) or (Type =:= http_bin)) ->
@@ -676,20 +659,20 @@ deliver_app_data(Transport, Socket, SOpts = #socket_options{active=Active, packe
end.
format_reply(_, _,#socket_options{active = false, mode = Mode, packet = Packet,
- header = Header}, Data) ->
+ header = Header}, Data, _) ->
{ok, do_format_reply(Mode, Packet, Header, Data)};
format_reply(Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet,
- header = Header}, Data) ->
- {ssl, ssl_socket:socket(self(), Transport, Socket, ?MODULE),
+ header = Header}, Data, Tracker) ->
+ {ssl, ssl_socket:socket(self(), Transport, Socket, ?MODULE, Tracker),
do_format_reply(Mode, Packet, Header, Data)}.
-deliver_packet_error(Transport, Socket, SO= #socket_options{active = Active}, Data, Pid, From) ->
- send_or_reply(Active, Pid, From, format_packet_error(Transport, Socket, SO, Data)).
+deliver_packet_error(Transport, Socket, SO= #socket_options{active = Active}, Data, Pid, From, Tracker) ->
+ send_or_reply(Active, Pid, From, format_packet_error(Transport, Socket, SO, Data, Tracker)).
-format_packet_error(_, _,#socket_options{active = false, mode = Mode}, Data) ->
+format_packet_error(_, _,#socket_options{active = false, mode = Mode}, Data, _) ->
{error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}};
-format_packet_error(Transport, Socket, #socket_options{active = _, mode = Mode}, Data) ->
- {ssl_error, ssl_socket:socket(self(), Transport, Socket, ?MODULE),
+format_packet_error(Transport, Socket, #socket_options{active = _, mode = Mode}, Data, Tracker) ->
+ {ssl_error, ssl_socket:socket(self(), Transport, Socket, ?MODULE, Tracker),
{invalid_packet, do_format_reply(Mode, raw, 0, Data)}}.
do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode
@@ -833,10 +816,10 @@ handle_alert(#alert{level = ?FATAL} = Alert, StateName,
#state{socket = Socket, transport_cb = Transport,
ssl_options = SslOpts, start_or_recv_from = From, host = Host,
port = Port, session = Session, user_application = {_Mon, Pid},
- role = Role, socket_options = Opts} = State) ->
+ role = Role, socket_options = Opts, tracker = Tracker} = State) ->
invalidate_session(Role, Host, Port, Session),
log_alert(SslOpts#ssl_options.log_alert, StateName, Alert),
- alert_user(Transport, Socket, StateName, Opts, Pid, From, Alert, Role),
+ alert_user(Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role),
{stop, normal, State};
handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
@@ -864,30 +847,30 @@ handle_alert(#alert{level = ?WARNING} = Alert, StateName,
{Record, State} = next_record(State0),
next_state(StateName, StateName, Record, State).
-alert_user(Transport, Socket, connection, Opts, Pid, From, Alert, Role) ->
- alert_user(Transport,Socket, Opts#socket_options.active, Pid, From, Alert, Role);
-alert_user(Transport, Socket,_, _, _, From, Alert, Role) ->
- alert_user(Transport, Socket, From, Alert, Role).
+alert_user(Transport, Tracker, Socket, connection, Opts, Pid, From, Alert, Role) ->
+ alert_user(Transport, Tracker, Socket, Opts#socket_options.active, Pid, From, Alert, Role);
+alert_user(Transport, Tracker, Socket,_, _, _, From, Alert, Role) ->
+ alert_user(Transport, Tracker, Socket, From, Alert, Role).
-alert_user(Transport, Socket, From, Alert, Role) ->
- alert_user(Transport, Socket, false, no_pid, From, Alert, Role).
+alert_user(Transport, Tracker, Socket, From, Alert, Role) ->
+ alert_user(Transport, Tracker, Socket, false, no_pid, From, Alert, Role).
-alert_user(_,_, false = Active, Pid, From, Alert, Role) ->
+alert_user(_, _, _, false = Active, Pid, From, Alert, Role) ->
%% If there is an outstanding ssl_accept | recv
%% From will be defined and send_or_reply will
%% send the appropriate error message.
ReasonCode = ssl_alert:reason_code(Alert, Role),
send_or_reply(Active, Pid, From, {error, ReasonCode});
-alert_user(Transport, Socket, Active, Pid, From, Alert, Role) ->
+alert_user(Transport, Tracker, Socket, Active, Pid, From, Alert, Role) ->
case ssl_alert:reason_code(Alert, Role) of
closed ->
send_or_reply(Active, Pid, From,
{ssl_closed, ssl_socket:socket(self(),
- Transport, Socket, ?MODULE)});
+ Transport, Socket, ?MODULE, Tracker)});
ReasonCode ->
send_or_reply(Active, Pid, From,
{ssl_error, ssl_socket:socket(self(),
- Transport, Socket, ?MODULE), ReasonCode})
+ Transport, Socket, ?MODULE, Tracker), ReasonCode})
end.
log_alert(true, Info, Alert) ->
@@ -920,15 +903,17 @@ handle_own_alert(Alert, Version, StateName,
handle_normal_shutdown(Alert, _, #state{socket = Socket,
transport_cb = Transport,
start_or_recv_from = StartFrom,
+ tracker = Tracker,
role = Role, renegotiation = {false, first}}) ->
- alert_user(Transport, Socket, StartFrom, Alert, Role);
+ alert_user(Transport, Tracker,Socket, StartFrom, Alert, Role);
handle_normal_shutdown(Alert, StateName, #state{socket = Socket,
socket_options = Opts,
transport_cb = Transport,
user_application = {_Mon, Pid},
+ tracker = Tracker,
start_or_recv_from = RecvFrom, role = Role}) ->
- alert_user(Transport, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role).
+ alert_user(Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role).
handle_unexpected_message(Msg, Info, #state{negotiated_version = Version} = State) ->
Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE),
diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl
index 067417d163..7a5f9c1b38 100644
--- a/lib/ssl/src/tls_v1.erl
+++ b/lib/ssl/src/tls_v1.erl
@@ -183,23 +183,7 @@ mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor},
-spec suites(1|2|3) -> [ssl_cipher:cipher_suite()].
-suites(Minor) when Minor == 1; Minor == 2->
- case sufficent_ec_support() of
- true ->
- all_suites(Minor);
- false ->
- no_ec_suites(Minor)
- end;
-
-suites(Minor) when Minor == 3 ->
- case sufficent_ec_support() of
- true ->
- all_suites(3) ++ all_suites(2);
- false ->
- no_ec_suites(3) ++ no_ec_suites(2)
- end.
-
-all_suites(Minor) when Minor == 1; Minor == 2->
+suites(Minor) when Minor == 1; Minor == 2 ->
[
?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
@@ -235,7 +219,7 @@ all_suites(Minor) when Minor == 1; Minor == 2->
?TLS_RSA_WITH_DES_CBC_SHA
];
-all_suites(3) ->
+suites(3) ->
[
?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
@@ -254,33 +238,7 @@ all_suites(3) ->
?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,
?TLS_RSA_WITH_AES_128_CBC_SHA256
- ].
-
-no_ec_suites(Minor) when Minor == 1; Minor == 2->
- [
- ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
- ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
- ?TLS_RSA_WITH_AES_256_CBC_SHA,
- ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
- ?TLS_RSA_WITH_3DES_EDE_CBC_SHA,
- ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
- ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
- ?TLS_RSA_WITH_AES_128_CBC_SHA,
- ?TLS_RSA_WITH_RC4_128_SHA,
- ?TLS_RSA_WITH_RC4_128_MD5,
- ?TLS_DHE_RSA_WITH_DES_CBC_SHA,
- ?TLS_RSA_WITH_DES_CBC_SHA
- ];
-no_ec_suites(3) ->
- [
- ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
- ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
- ?TLS_RSA_WITH_AES_256_CBC_SHA256,
- ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
- ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,
- ?TLS_RSA_WITH_AES_128_CBC_SHA256
- ].
+ ] ++ suites(2).
%%--------------------------------------------------------------------
%%% Internal functions
@@ -442,7 +400,3 @@ enum_to_oid(27) -> ?brainpoolP384r1;
enum_to_oid(28) -> ?brainpoolP512r1;
enum_to_oid(_) ->
undefined.
-
-sufficent_ec_support() ->
- CryptoSupport = crypto:supports(),
- proplists:get_bool(ecdh, proplists:get_value(public_keys, CryptoSupport)).
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index a1b766e05f..2f440f1f3c 100644
--- a/lib/ssl/test/ssl_basic_SUITE.erl
+++ b/lib/ssl/test/ssl_basic_SUITE.erl
@@ -122,8 +122,7 @@ options_tests() ->
].
api_tests() ->
- [new_options_in_accept,
- connection_info,
+ [connection_info,
peername,
peercert,
peercert_with_client_cert,
@@ -142,7 +141,8 @@ api_tests() ->
ssl_recv_timeout,
versions_option,
server_name_indication_option,
- accept_pool
+ accept_pool,
+ new_options_in_accept
].
session_tests() ->
@@ -194,6 +194,7 @@ error_handling_tests()->
close_transport_accept,
recv_active,
recv_active_once,
+ recv_error_handling,
dont_crash_on_handshake_garbage
].
@@ -345,14 +346,15 @@ new_options_in_accept() ->
[{doc,"Test that you can set ssl options in ssl_accept/3 and not tcp upgrade"}].
new_options_in_accept(Config) when is_list(Config) ->
ClientOpts = ?config(client_opts, Config),
- ServerOpts = ?config(server_opts, Config),
+ ServerOpts0 = ?config(server_dsa_opts, Config),
+ [_ , _ | ServerSslOpts] = ?config(server_opts, Config), %% Remove non ssl opts
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
- {ssl_opts, [{versions, [sslv3]},
- {ciphers,[{rsa,rc4_128,sha}]}]}, %% To be set in ssl_accept/3
+ {ssl_extra_opts, [{versions, [sslv3]},
+ {ciphers,[{rsa,rc4_128,sha}]} | ServerSslOpts]}, %% To be set in ssl_accept/3
{mfa, {?MODULE, connection_info_result, []}},
- {options, ServerOpts}]),
+ {options, proplists:delete(cacertfile, ServerOpts0)}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
@@ -1244,7 +1246,7 @@ dh_params(Config) when is_list(Config) ->
{from, self()},
{mfa, {ssl_test_lib, send_recv_result_active, []}},
{options,
- [{ciphers,[{dhe_rsa,aes_256_cbc,sha,ignore}]} |
+ [{ciphers,[{dhe_rsa,aes_256_cbc,sha}]} |
ClientOpts]}]),
ssl_test_lib:check_result(Server, ok, Client, ok),
@@ -1343,7 +1345,7 @@ tcp_connect() ->
tcp_connect(Config) when is_list(Config) ->
ServerOpts = ?config(server_opts, Config),
{_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- TcpOpts = [binary, {reuseaddr, true}],
+ TcpOpts = [binary, {reuseaddr, true}, {active, false}],
Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0},
{from, self()},
@@ -3694,7 +3696,7 @@ run_suites(Ciphers, Version, Config, Type) ->
Result = lists:map(fun(Cipher) ->
cipher(Cipher, Version, Config, ClientOpts, ServerOpts) end,
- Ciphers),
+ ssl_test_lib:filter_suites(Ciphers)),
case lists:flatten(Result) of
[] ->
ok;
diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl
index 69b222fc43..150b5037d7 100644
--- a/lib/ssl/test/ssl_test_lib.erl
+++ b/lib/ssl/test/ssl_test_lib.erl
@@ -115,7 +115,7 @@ connect(#sslsocket{} = ListenSocket, Opts) ->
Node = proplists:get_value(node, Opts),
ReconnectTimes = proplists:get_value(reconnect_times, Opts, 0),
Timeout = proplists:get_value(timeout, Opts, infinity),
- SslOpts = proplists:get_value(ssl_opts, Opts, []),
+ SslOpts = proplists:get_value(ssl_extra_opts, Opts, []),
AcceptSocket = connect(ListenSocket, Node, 1 + ReconnectTimes, dummy, Timeout, SslOpts),
case ReconnectTimes of
0 ->
@@ -186,10 +186,7 @@ run_client(Opts) ->
Pid = proplists:get_value(from, Opts),
Transport = proplists:get_value(transport, Opts, ssl),
Options = proplists:get_value(options, Opts),
- ct:log("~p:~p~nssl:connect(~p, ~p, ~p)~n", [?MODULE,?LINE, Host, Port, Options]),
-ct:log("~p:~p~nnet_adm:ping(~p)=~p",[?MODULE,?LINE, Node,net_adm:ping(Node)]),
-%%ct:log("~p:~p~n~p:connect(~p, ~p, ~p)@~p~n", [?MODULE,?LINE, Transport, Host, Port, Options, Node]),
-ct:log("~p:~p~n~p:connect(~p, ~p, ...)@~p~n", [?MODULE,?LINE, Transport, Host, Port, Node]),
+ ct:log("~p:~p~n~p:connect(~p, ~p)@~p~n", [?MODULE,?LINE, Transport, Host, Port, Node]),
case rpc:call(Node, Transport, connect, [Host, Port, Options]) of
{ok, Socket} ->
Pid ! {connected, Socket},
@@ -875,25 +872,34 @@ psk_suites() ->
{psk, '3des_ede_cbc', sha},
{psk, aes_128_cbc, sha},
{psk, aes_256_cbc, sha},
+ {psk, aes_128_cbc, sha256},
+ {psk, aes_256_cbc, sha384},
{dhe_psk, rc4_128, sha},
{dhe_psk, '3des_ede_cbc', sha},
{dhe_psk, aes_128_cbc, sha},
{dhe_psk, aes_256_cbc, sha},
+ {dhe_psk, aes_128_cbc, sha256},
+ {dhe_psk, aes_256_cbc, sha384},
{rsa_psk, rc4_128, sha},
{rsa_psk, '3des_ede_cbc', sha},
{rsa_psk, aes_128_cbc, sha},
- {rsa_psk, aes_256_cbc, sha}],
+ {rsa_psk, aes_256_cbc, sha},
+ {rsa_psk, aes_128_cbc, sha256},
+ {rsa_psk, aes_256_cbc, sha384}
+],
ssl_cipher:filter_suites(Suites).
psk_anon_suites() ->
- [{psk, rc4_128, sha},
- {psk, '3des_ede_cbc', sha},
- {psk, aes_128_cbc, sha},
- {psk, aes_256_cbc, sha},
- {dhe_psk, rc4_128, sha},
- {dhe_psk, '3des_ede_cbc', sha},
- {dhe_psk, aes_128_cbc, sha},
- {dhe_psk, aes_256_cbc, sha}].
+ Suites =
+ [{psk, rc4_128, sha},
+ {psk, '3des_ede_cbc', sha},
+ {psk, aes_128_cbc, sha},
+ {psk, aes_256_cbc, sha},
+ {dhe_psk, rc4_128, sha},
+ {dhe_psk, '3des_ede_cbc', sha},
+ {dhe_psk, aes_128_cbc, sha},
+ {dhe_psk, aes_256_cbc, sha}],
+ ssl_cipher:filter_suites(Suites).
srp_suites() ->
Suites =
@@ -906,9 +912,11 @@ srp_suites() ->
ssl_cipher:filter_suites(Suites).
srp_anon_suites() ->
- [{srp_anon, '3des_ede_cbc', sha},
- {srp_anon, aes_128_cbc, sha},
- {srp_anon, aes_256_cbc, sha}].
+ Suites =
+ [{srp_anon, '3des_ede_cbc', sha},
+ {srp_anon, aes_128_cbc, sha},
+ {srp_anon, aes_256_cbc, sha}],
+ ssl_cipher:filter_suites(Suites).
srp_dss_suites() ->
Suites =
@@ -1118,3 +1126,13 @@ version_flag('tlsv1.2') ->
" -tls1_2 ";
version_flag(sslv3) ->
" -ssl3 ".
+
+filter_suites(Ciphers0) ->
+ Version = tls_record:highest_protocol_version([]),
+ Supported0 = ssl_cipher:suites(Version)
+ ++ ssl_cipher:anonymous_suites()
+ ++ ssl_cipher:psk_suites(Version)
+ ++ ssl_cipher:srp_suites(),
+ Supported1 = ssl_cipher:filter_suites(Supported0),
+ Supported2 = [ssl:suite_definition(S) || S <- Supported1],
+ [Cipher || Cipher <- Ciphers0, lists:member(Cipher, Supported2)].
diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl
index a7361755e5..d36e441c7a 100644
--- a/lib/ssl/test/ssl_to_openssl_SUITE.erl
+++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl
@@ -1341,7 +1341,7 @@ check_sane_openssl_renegotaite(Config, Version) when Version == 'tlsv1.1';
{skip, "Known renegotiation bug in OpenSSL"};
"OpenSSL 1.0.1a" ++ _ ->
{skip, "Known renegotiation bug in OpenSSL"};
- "OpenSSL 1.0.1" ++ _ ->
+ "OpenSSL 1.0.1 " ++ _ ->
{skip, "Known renegotiation bug in OpenSSL"};
_ ->
check_sane_openssl_renegotaite(Config)
diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk
index e08f5dff78..004cacf7fc 100644
--- a/lib/ssl/vsn.mk
+++ b/lib/ssl/vsn.mk
@@ -1 +1 @@
-SSL_VSN = 5.3.4
+SSL_VSN = 5.3.5
diff --git a/lib/stdlib/doc/src/maps.xml b/lib/stdlib/doc/src/maps.xml
index 76137e3dee..b37f7fd7fd 100644
--- a/lib/stdlib/doc/src/maps.xml
+++ b/lib/stdlib/doc/src/maps.xml
@@ -108,6 +108,26 @@
</func>
<func>
+ <name name="get" arity="3"/>
+ <fsummary></fsummary>
+ <desc>
+ <p>
+ Returns the value <c><anno>Value</anno></c> associated with <c><anno>Key</anno></c> if
+ <c><anno>Map</anno></c> contains <c><anno>Key</anno></c>.
+ If no value is associated with <c><anno>Key</anno></c> then returns <c><anno>Default</anno></c>.
+ </p>
+ <p>Example:</p>
+ <code type="none">
+> Map = #{ key1 => val1, key2 => val2 }.
+#{key1 => val1,key2 => val2}
+> maps:get(key1, Map, "Default value").
+val1
+> maps:get(key3, Map, "Default value").
+"Default value"</code>
+ </desc>
+ </func>
+
+ <func>
<name name="is_key" arity="2"/>
<fsummary></fsummary>
<desc>
diff --git a/lib/stdlib/src/maps.erl b/lib/stdlib/src/maps.erl
index fd6d56fa47..4ef1638e6d 100644
--- a/lib/stdlib/src/maps.erl
+++ b/lib/stdlib/src/maps.erl
@@ -23,7 +23,8 @@
fold/3,
map/2,
size/1,
- without/2
+ without/2,
+ get/3
]).
@@ -142,6 +143,21 @@ values(_) -> erlang:nif_error(undef).
%%% End of BIFs
+-spec get(Key, Map, Default) -> Value | Default when
+ Key :: term(),
+ Map :: map(),
+ Value :: term(),
+ Default :: term().
+
+get(Key, Map, Default) ->
+ case maps:find(Key, Map) of
+ {ok, Value} ->
+ Value;
+ error ->
+ Default
+ end.
+
+
-spec fold(Fun,Init,Map) -> Acc when
Fun :: fun((K, V, AccIn) -> AccOut),
Init :: term(),
diff --git a/lib/stdlib/test/Makefile b/lib/stdlib/test/Makefile
index 39f6ce423a..a271229c59 100644
--- a/lib/stdlib/test/Makefile
+++ b/lib/stdlib/test/Makefile
@@ -85,7 +85,8 @@ MODULES= \
zip_SUITE \
random_unicode_list \
random_iolist \
- error_logger_forwarder
+ error_logger_forwarder \
+ maps_SUITE
ERL_FILES= $(MODULES:%=%.erl)
diff --git a/lib/stdlib/test/maps_SUITE.erl b/lib/stdlib/test/maps_SUITE.erl
new file mode 100644
index 0000000000..c826ee731a
--- /dev/null
+++ b/lib/stdlib/test/maps_SUITE.erl
@@ -0,0 +1,69 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2014. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%%----------------------------------------------------------------
+%%% Purpose: Test suite for the 'maps' module.
+%%%-----------------------------------------------------------------
+
+-module(maps_SUITE).
+
+-include_lib("test_server/include/test_server.hrl").
+
+% Default timetrap timeout (set in init_per_testcase).
+% This should be set relatively high (10-15 times the expected
+% max testcasetime).
+-define(default_timeout, ?t:minutes(4)).
+
+% Test server specific exports
+-export([all/0]).
+-export([suite/0]).
+-export([init_per_suite/1]).
+-export([end_per_suite/1]).
+-export([init_per_testcase/2]).
+-export([end_per_testcase/2]).
+
+-export([get3/1]).
+
+suite() ->
+ [{ct_hooks, [ts_install_cth]}].
+
+all() ->
+ [get3].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog=test_server:timetrap(?default_timeout),
+ [{watchdog, Dog}|Config].
+
+end_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+get3(Config) when is_list(Config) ->
+ Map = #{ key1 => value1, key2 => value2 },
+ DefaultValue = "Default value",
+ ?line value1 = maps:get(key1, Map, DefaultValue),
+ ?line value2 = maps:get(key2, Map, DefaultValue),
+ ?line DefaultValue = maps:get(key3, Map, DefaultValue),
+ ok.
diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl
index c9996c954e..46a5ca48df 100644
--- a/lib/syntax_tools/src/erl_syntax.erl
+++ b/lib/syntax_tools/src/erl_syntax.erl
@@ -2071,7 +2071,7 @@ map_field_assoc_value(Node) ->
{map_field_assoc, _, _, Value} ->
Value;
_ ->
- (data(Node))#map_field_assoc.name
+ (data(Node))#map_field_assoc.value
end.
@@ -2129,7 +2129,7 @@ map_field_exact_value(Node) ->
{map_field_exact, _, _, Value} ->
Value;
_ ->
- (data(Node))#map_field_exact.name
+ (data(Node))#map_field_exact.value
end.
diff --git a/lib/syntax_tools/test/syntax_tools_SUITE.erl b/lib/syntax_tools/test/syntax_tools_SUITE.erl
index d4c54a72aa..6fb3e5ccfb 100644
--- a/lib/syntax_tools/test/syntax_tools_SUITE.erl
+++ b/lib/syntax_tools/test/syntax_tools_SUITE.erl
@@ -24,12 +24,12 @@
init_per_group/2,end_per_group/2]).
%% Test cases
--export([app_test/1,appup_test/1,smoke_test/1,revert/1]).
+-export([app_test/1,appup_test/1,smoke_test/1,revert/1,revert_map/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
- [app_test,appup_test,smoke_test,revert].
+ [app_test,appup_test,smoke_test,revert,revert_map].
groups() ->
[].
@@ -109,6 +109,16 @@ revert_file(File, Path) ->
ok
end.
+%% Testing bug fix for reverting map_field_assoc
+revert_map(Config) ->
+ Dog = ?t:timetrap(?t:minutes(1)),
+ ?line [{map_field_assoc,16,{atom,17,name},{var,18,'Value'}}] =
+ erl_syntax:revert_forms([{tree,map_field_assoc,
+ {attr,16,[],none},
+ {map_field_assoc,
+ {atom,17,name},{var,18,'Value'}}}]),
+ ?line ?t:timetrap_cancel(Dog).
+
p_run(Test, List) ->
N = erlang:system_info(schedulers),
p_run_loop(Test, List, N, [], 0).
diff --git a/otp_versions.table b/otp_versions.table
index a8a40df85a..1120add531 100644
--- a/otp_versions.table
+++ b/otp_versions.table
@@ -1,2 +1,3 @@
+OTP-17.0.2 : inets-5.10.1 ssh-3.0.2 # asn1-3.0 common_test-1.8 compiler-5.0 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.3 debugger-4.0 dialyzer-2.7 diameter-1.6 edoc-0.7.13 eldap-1.0.3 erl_docgen-0.3.5 erl_interface-3.7.16 erts-6.0.1 et-1.5 eunit-2.2.7 gs-1.5.16 hipe-3.10.3 ic-4.3.5 jinterface-1.5.9 kernel-3.0 megaco-3.17.1 mnesia-4.12 observer-2.0 odbc-2.10.20 orber-3.6.27 os_mon-2.2.15 ose-1.0 otp_mibs-1.0.9 parsetools-2.0.11 percept-0.8.9 public_key-0.22 reltool-0.6.5 runtime_tools-1.8.14 sasl-2.4 snmp-4.25.1 ssl-5.3.4 stdlib-2.0 syntax_tools-1.6.14 test_server-3.7 tools-2.6.14 typer-0.9.7 webtool-0.8.10 wx-1.2 xmerl-1.3.7 :
OTP-17.0.1 : erts-6.0.1 typer-0.9.7 # asn1-3.0 common_test-1.8 compiler-5.0 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.3 debugger-4.0 dialyzer-2.7 diameter-1.6 edoc-0.7.13 eldap-1.0.3 erl_docgen-0.3.5 erl_interface-3.7.16 et-1.5 eunit-2.2.7 gs-1.5.16 hipe-3.10.3 ic-4.3.5 inets-5.10 jinterface-1.5.9 kernel-3.0 megaco-3.17.1 mnesia-4.12 observer-2.0 odbc-2.10.20 orber-3.6.27 os_mon-2.2.15 ose-1.0 otp_mibs-1.0.9 parsetools-2.0.11 percept-0.8.9 public_key-0.22 reltool-0.6.5 runtime_tools-1.8.14 sasl-2.4 snmp-4.25.1 ssh-3.0.1 ssl-5.3.4 stdlib-2.0 syntax_tools-1.6.14 test_server-3.7 tools-2.6.14 webtool-0.8.10 wx-1.2 xmerl-1.3.7 :
OTP-17.0 : asn1-3.0 common_test-1.8 compiler-5.0 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.3 debugger-4.0 dialyzer-2.7 diameter-1.6 edoc-0.7.13 eldap-1.0.3 erl_docgen-0.3.5 erl_interface-3.7.16 erts-6.0 et-1.5 eunit-2.2.7 gs-1.5.16 hipe-3.10.3 ic-4.3.5 inets-5.10 jinterface-1.5.9 kernel-3.0 megaco-3.17.1 mnesia-4.12 observer-2.0 odbc-2.10.20 orber-3.6.27 os_mon-2.2.15 ose-1.0 otp_mibs-1.0.9 parsetools-2.0.11 percept-0.8.9 public_key-0.22 reltool-0.6.5 runtime_tools-1.8.14 sasl-2.4 snmp-4.25.1 ssh-3.0.1 ssl-5.3.4 stdlib-2.0 syntax_tools-1.6.14 test_server-3.7 tools-2.6.14 typer-0.9.6 webtool-0.8.10 wx-1.2 xmerl-1.3.7 # :
diff --git a/system/doc/reference_manual/expressions.xml b/system/doc/reference_manual/expressions.xml
index 37208710fe..0ca425da86 100644
--- a/system/doc/reference_manual/expressions.xml
+++ b/system/doc/reference_manual/expressions.xml
@@ -792,6 +792,244 @@ Expr1 -- Expr2</pre>
</section>
<section>
+ <title>Map Expressions</title>
+ <section>
+ <title>Creating Maps</title>
+ <p>
+ Constructing a new map is done by letting an expression <c>K</c> be associated with
+ another expression <c>V</c>:
+ </p>
+ <code>#{ K => V }</code>
+ <p>
+ New maps may include multiple associations at construction by listing every
+ association:
+ </p>
+ <code>#{ K1 => V1, .., Kn => Vn }</code>
+ <p>
+ An empty map is constructed by not associating any terms with each other:
+ </p>
+ <code>#{}</code>
+ <p>
+ All keys and values in the map are terms. Any expression is first evaluated and
+ then the resulting terms are used as <em>key</em> and <em>value</em> respectively.
+ </p>
+ <p>
+ Keys and values are separated by the <c>=></c> arrow and associations are
+ separated by <c>,</c>.
+ </p>
+
+ <p>
+ Examples:
+ </p>
+ <code>
+M0 = #{}, % empty map
+M1 = #{a => &lt;&lt;"hello"&gt;&gt;}, % single association with literals
+M2 = #{1 => 2, b => b}, % multiple associations with literals
+M3 = #{k => {A,B}}, % single association with variables
+M4 = #{{"w", 1} => f()}. % compound key associated with an evaluated expression</code>
+ <p>
+ where, <c>A</c> and <c>B</c> are any expressions and <c>M0</c> through <c>M4</c>
+ are the resulting map terms.
+ </p>
+ <p>
+ If two matching keys are declared, the latter key will take precedence.
+ </p>
+ <p>
+ Example:
+ </p>
+
+<pre>
+1> <input>#{1 => a, 1 => b}.</input>
+#{1 => b }
+2> <input>#{1.0 => a, 1 => b}.</input>
+#{1 => b, 1.0 => a}
+</pre>
+ <p>
+ The order in which the expressions constructing the keys and their
+ associated values are evaluated is not defined. The syntactic order of
+ the key-value pairs in the construction is of no relevance, except in
+ the above mentioned case of two matching keys.
+ </p>
+ </section>
+
+ <section>
+ <title>Updating Maps</title>
+ <p>
+ Updating a map has similar syntax as constructing it.
+ </p>
+ <p>
+ An expression defining the map to be updated is put in front of the expression
+ defining the keys to be updated and their respective values.
+ </p>
+ <code>M#{ K => V }</code>
+ <p>
+ where <c>M</c> is a term of type map and <c>K</c> and <c>V</c> are any expression.
+ </p>
+ <p>
+ If key <c>K</c> does not match any existing key in the map, a new association
+ will be created from key <c>K</c> to value <c>V</c>. If key <c>K</c> matches
+ an existing key in map <c>M</c> its associated value will be replaced by the
+ new value <c>V</c>. In both cases the evaluated map expression will return a new map.
+ </p>
+ <p>
+ If <c>M</c> is not of type map an exception of type <c>badmap</c> is thrown.
+ </p>
+ <p>
+ To only update an existing value, the following syntax is used,
+ </p>
+ <code>M#{ K := V } </code>
+ <p>
+ where <c>M</c> is an term of type map, <c>V</c> is an expression and <c>K</c>
+ is an expression which evaluates to an existing key in <c>M</c>.
+ </p>
+ <p>
+ If key <c>K</c> does not match any existing keys in map <c>M</c> an exception
+ of type <c>badarg</c> will be triggered at runtime. If a matching key <c>K</c>
+ is present in map <c>M</c> its associated value will be replaced by the new
+ value <c>V</c> and the evaluated map expression returns a new map.
+ </p>
+ <p>
+ If <c>M</c> is not of type map an exception of type <c>badmap</c> is thrown.
+ </p>
+ <p>
+ Examples:
+ </p>
+ <code>
+M0 = #{},
+M1 = M0#{a => 0},
+M2 = M1#{a => 1, b => 2},
+M3 = M2#{"function" => fun() -> f() end},
+M4 = M3#{a := 2, b := 3}. % 'a' and 'b' was added in `M1` and `M2`.</code>
+ <p>
+ where <c>M0</c> is any map. It follows that <c>M1 .. M4</c> are maps as well.
+ </p>
+ <p>
+ More Examples:
+ </p>
+<pre>
+1> <input>M = #{1 => a}.</input>
+#{1 => a }
+2> <input>M#{1.0 => b}.</input>
+#{1 => a, 1.0 => b}.
+3> <input>M#{1 := b}.</input>
+#{1 => b}
+4> <input>M#{1.0 := b}.</input>
+** exception error: bad argument
+</pre>
+ <p>
+ As in construction, the order in which the key and value expressions
+ are evaluated is not defined. The
+ syntactic order of the key-value pairs in the update is of no
+ relevance, except in the case where two keys match, in which
+ case the latter value is used.
+ </p>
+ </section>
+
+ <section>
+ <title>Maps in Patterns</title>
+ <p>
+ Matching of key-value associations from maps is done in the following way:
+ </p>
+
+ <code>#{ K := V } = M</code>
+ <p>
+ where <c>M</c> is any map. The key <c>K</c> has to be an expression with bound
+ variables or a literals, and <c>V</c> can be any pattern with either bound or
+ unbound variables.
+ </p>
+ <p>
+ If the variable <c>V</c> is unbound, it will be bound to the value associated
+ with the key <c>K</c>, which has to exist in the map <c>M</c>. If the variable
+ <c>V</c> is bound, it has to match the value associated with <c>K</c> in <c>M</c>.
+ </p>
+ <p> Example: </p>
+<code>
+1> <input>M = #{"tuple" => {1,2}}.</input>
+#{"tuple" => {1,2}}
+2> <input>#{"tuple" := {1,B}} = M.</input>
+#{"tuple" => {1,2}}
+3> <input>B.</input>
+2.</code>
+ <p>
+ This will bind variable <c>B</c> to integer <c>2</c>.
+ </p>
+ <p>
+ Similarly, multiple values from the map may be matched:
+ </p>
+ <code>#{ K1 := V1, .., Kn := Vn } = M</code>
+ <p>
+ where keys <c>K1 .. Kn</c> are any expressions with literals or bound variables. If all
+ keys exist in map <c>M</c> all variables in <c>V1 .. Vn</c> will be matched to the
+ associated values of their respective keys.
+ </p>
+ <p>
+ If the matching conditions are not met, the match will fail, either with
+ </p>
+ <list>
+ <item>
+ a <c>badmatch</c> exception, if used in the context of the matching operator
+ as in the example,
+ </item>
+ <item>
+ or resulting in the next clause being tested in function heads and
+ case expressions.
+ </item>
+ </list>
+ <p>
+ Matching in maps only allows for <c>:=</c> as delimiters of associations.
+ The order in which keys are declared in matching has no relevance.
+ </p>
+ <p>
+ Duplicate keys are allowed in matching and will match each pattern associated
+ to the keys.
+ </p>
+ <code>#{ K := V1, K := V2 } = M</code>
+ <p>
+ Matching an expression against an empty map literal will match its type but
+ no variables will be bound:
+ </p>
+ <code>#{} = Expr</code>
+ <p>
+ This expression will match if the expression <c>Expr</c> is of type map, otherwise
+ it will fail with an exception <c>badmatch</c>.
+ </p>
+ <section>
+ <title>Matching syntax: Example with literals in function heads</title>
+ <p>
+ Matching of literals as keys are allowed in function heads.
+ </p>
+ <code>
+%% only start if not_started
+handle_call(start, From, #{ state := not_started } = S) ->
+...
+ {reply, ok, S#{ state := start }};
+
+%% only change if started
+handle_call(change, From, #{ state := start } = S) ->
+...
+ {reply, ok, S#{ state := changed }};</code>
+ </section>
+ </section>
+ <section>
+ <title>Maps in Guards</title>
+ <p>
+ Maps are allowed in guards as long as all sub-expressions are valid guard expressions.
+ </p>
+ <p>
+ Two guard BIFs handles maps:
+ </p>
+ <list>
+ <item>
+ <seealso marker="erts:erlang#is_map/1">is_map/1</seealso>
+ </item>
+ <item>
+ <seealso marker="erts:erlang#map_size/1">map_size/1</seealso>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
<marker id="bit_syntax"></marker>
<title>Bit Syntax Expressions</title>
<code type="none"><![CDATA[<<>>
diff --git a/system/doc/reference_manual/maps.xml b/system/doc/reference_manual/maps.xml
deleted file mode 100644
index 78808ce4a2..0000000000
--- a/system/doc/reference_manual/maps.xml
+++ /dev/null
@@ -1,274 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE chapter SYSTEM "chapter.dtd">
-
-<chapter>
- <header>
- <copyright>
- <year>2014</year>
- <holder>Ericsson AB. All Rights Reserved.</holder>
- </copyright>
- <legalnotice>
- The contents of this file are subject to the Erlang Public License,
- Version 1.1, (the "License"); you may not use this file except in
- compliance with the License. You should have received a copy of the
- Erlang Public License along with this software. If not, it can be
- retrieved online at http://www.erlang.org/.
-
- Software distributed under the License is distributed on an "AS IS"
- basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
- the License for the specific language governing rights and limitations
- under the License.
- </legalnotice>
-
- <title>Maps</title>
- <prepared></prepared>
- <docno></docno>
- <date></date>
- <rev></rev>
- <file>maps.xml</file>
- </header>
-
- <note>
- <p>Maps are considered experimental during OTP 17 and may be subject to change.</p>
- <p>The documentation below describes it being possible to use arbitrary
- expressions or variables as keys, this is <em>NOT</em> implemented in the current
- version of Erlang/OTP.</p>
- <p>Exceptions returns <c>badarg</c> instead of <c>badmap</c>, this will change in
- the future releases.</p>
- </note>
-
- <section>
- <title>Creating Maps</title>
- <p>
- Constructing a new map is done by letting an expression <c>K</c> be associated with
- another expression <c>V</c>:
- </p>
- <code>#{ K => V }</code>
- <p>
- New maps may include multiple associations at construction by listing every
- association:
- </p>
- <code>#{ K1 => V1, .., Kn => Vn }</code>
- <p>
- An empty map is constructed by not associating any terms with each other:
- </p>
- <code>#{}</code>
- <p>
- All keys and values in the map are terms. Any expression is first evaluated and
- then the resulting terms are used as <em>key</em> and <em>value</em> respectively.
- </p>
- <p>
- Keys and values are separated by the <c>=></c> arrow and associations are
- separated by <c>,</c>.
- </p>
-
- <p>
- Examples:
- </p>
- <code>
-M0 = #{}, % empty map
-M1 = #{a => &lt;&lt;"hello"&gt;&gt;}, % single association with literals
-M2 = #{1 => 2, b => b}, % multiple associations with literals
-M3 = #{k => {A,B}}, % single association with variables
-M4 = #{{"w", 1} => f()}. % compound key associated with an evaluated expression</code>
- <p>
- where, <c>A</c> and <c>B</c> are any expressions and <c>M0</c> through <c>M4</c>
- are the resulting map terms.
- </p>
- <p>
- If two matching keys are declared, the latter key will take precedence.
- </p>
- <p>
- Example:
- </p>
-
-<pre>
-1> <input>#{1 => a, 1 => b}.</input>
-#{1 => b }
-2> <input>#{1.0 => a, 1 => b}.</input>
-#{1 => b, 1.0 => a}
-</pre>
- <p>
- The order in which the expressions constructing the keys and their
- associated values are evaluated is not defined. The syntactic order of
- the key-value pairs in the construction is of no relevance, except in
- the above mentioned case of two matching keys.
- </p>
- </section>
-
- <section>
- <title>Updating Maps</title>
- <p>
- Updating a map has similar syntax as constructing it.
- </p>
- <p>
- An expression defining the map to be updated is put in front of the expression
- defining the keys to be updated and their respective values.
- </p>
- <code>M#{ K => V }</code>
- <p>
- where <c>M</c> is a term of type map and <c>K</c> and <c>V</c> are any expression.
- </p>
- <p>
- If key <c>K</c> does not match any existing key in the map, a new association
- will be created from key <c>K</c> to value <c>V</c>. If key <c>K</c> matches
- an existing key in map <c>M</c> its associated value will be replaced by the
- new value <c>V</c>. In both cases the evaluated map expression will return a new map.
- </p>
- <p>
- If <c>M</c> is not of type map an exception of type <c>badmap</c> is thrown.
- </p>
- <p>
- To only update an existing value, the following syntax is used,
- </p>
- <code>M#{ K := V } </code>
- <p>
- where <c>M</c> is an term of type map, <c>V</c> is an expression and <c>K</c>
- is an expression which evaluates to an existing key in <c>M</c>.
- </p>
- <p>
- If key <c>K</c> does not match any existing keys in map <c>M</c> an exception
- of type <c>badarg</c> will be triggered at runtime. If a matching key <c>K</c>
- is present in map <c>M</c> its associated value will be replaced by the new
- value <c>V</c> and the evaluated map expression returns a new map.
- </p>
- <p>
- If <c>M</c> is not of type map an exception of type <c>badmap</c> is thrown.
- </p>
- <p>
- Examples:
- </p>
- <code>
-M0 = #{},
-M1 = M0#{a => 0},
-M2 = M1#{a => 1, b => 2},
-M3 = M2#{"function" => fun() -> f() end},
-M4 = M3#{a := 2, b := 3}. % 'a' and 'b' was added in `M1` and `M2`.</code>
- <p>
- where <c>M0</c> is any map. It follows that <c>M1 .. M4</c> are maps as well.
- </p>
- <p>
- More Examples:
- </p>
-<pre>
-1> <input>M = #{1 => a}.</input>
-#{1 => a }
-2> <input>M#{1.0 => b}.</input>
-#{1 => a, 1.0 => b}.
-3> <input>M#{1 := b}.</input>
-#{1 => b}
-4> <input>M#{1.0 := b}.</input>
-** exception error: bad argument
-</pre>
- <p>
- As in construction, the order in which the key and value expressions
- are evaluated is not defined. The
- syntactic order of the key-value pairs in the update is of no
- relevance, except in the case where two keys match, in which
- case the latter value is used.
- </p>
- </section>
-
- <section>
- <title>Maps in Patterns</title>
- <p>
- Matching of key-value associations from maps is done in the following way:
- </p>
-
- <code>#{ K := V } = M</code>
- <p>
- where <c>M</c> is any map. The key <c>K</c> has to be an expression with bound
- variables or a literals, and <c>V</c> can be any pattern with either bound or
- unbound variables.
- </p>
- <p>
- If the variable <c>V</c> is unbound, it will be bound to the value associated
- with the key <c>K</c>, which has to exist in the map <c>M</c>. If the variable
- <c>V</c> is bound, it has to match the value associated with <c>K</c> in <c>M</c>.
- </p>
- <p> Example: </p>
-<code>
-1> <input>M = #{"tuple" => {1,2}}.</input>
-#{"tuple" => {1,2}}
-2> <input>#{"tuple" := {1,B}} = M.</input>
-#{"tuple" => {1,2}}
-3> <input>B.</input>
-2.</code>
- <p>
- This will bind variable <c>B</c> to integer <c>2</c>.
- </p>
- <p>
- Similarly, multiple values from the map may be matched:
- </p>
- <code>#{ K1 := V1, .., Kn := Vn } = M</code>
- <p>
- where keys <c>K1 .. Kn</c> are any expressions with literals or bound variables. If all
- keys exist in map <c>M</c> all variables in <c>V1 .. Vn</c> will be matched to the
- associated values of their respective keys.
- </p>
- <p>
- If the matching conditions are not met, the match will fail, either with
- </p>
- <list>
- <item>
- a <c>badmatch</c> exception, if used in the context of the matching operator
- as in the example,
- </item>
- <item>
- or resulting in the next clause being tested in function heads and
- case expressions.
- </item>
- </list>
- <p>
- Matching in maps only allows for <c>:=</c> as delimiters of associations.
- The order in which keys are declared in matching has no relevance.
- </p>
- <p>
- Duplicate keys are allowed in matching and will match each pattern associated
- to the keys.
- </p>
- <code>#{ K := V1, K := V2 } = M</code>
- <p>
- Matching an expression against an empty map literal will match its type but
- no variables will be bound:
- </p>
- <code>#{} = Expr</code>
- <p>
- This expression will match if the expression <c>Expr</c> is of type map, otherwise
- it will fail with an exception <c>badmatch</c>.
- </p>
- <section>
- <title>Matching syntax: Example with literals in function heads</title>
- <p>
- Matching of literals as keys are allowed in function heads.
- </p>
- <code>
-%% only start if not_started
-handle_call(start, From, #{ state := not_started } = S) ->
-...
- {reply, ok, S#{ state := start }};
-
-%% only change if started
-handle_call(change, From, #{ state := start } = S) ->
-...
- {reply, ok, S#{ state := changed }};</code>
- </section>
- </section>
- <section>
- <title>Maps in Guards</title>
- <p>
- Maps are allowed in guards as long as all sub-expressions are valid guard expressions.
- </p>
- <p>
- Two guard BIFs handles maps:
- </p>
- <list>
- <item>
- <seealso marker="erts:erlang#is_map/1">is_map/1</seealso>
- </item>
- <item>
- <seealso marker="erts:erlang#map_size/1">map_size/1</seealso>
- </item>
- </list>
- </section>
-</chapter>
diff --git a/system/doc/reference_manual/part.xml b/system/doc/reference_manual/part.xml
index 36fb888748..ee8f3dd7eb 100644
--- a/system/doc/reference_manual/part.xml
+++ b/system/doc/reference_manual/part.xml
@@ -36,7 +36,6 @@
<xi:include href="typespec.xml"/>
<xi:include href="expressions.xml"/>
<xi:include href="macros.xml"/>
- <xi:include href="maps.xml"/>
<xi:include href="records.xml"/>
<xi:include href="errors.xml"/>
<xi:include href="processes.xml"/>
diff --git a/system/doc/reference_manual/xmlfiles.mk b/system/doc/reference_manual/xmlfiles.mk
index 181e6f8042..6886c8c7cf 100644
--- a/system/doc/reference_manual/xmlfiles.mk
+++ b/system/doc/reference_manual/xmlfiles.mk
@@ -24,7 +24,6 @@ REF_MAN_CHAPTER_FILES = \
functions.xml \
expressions.xml \
macros.xml \
- maps.xml \
records.xml \
errors.xml \
processes.xml \