aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/asn1/src/asn1ct_constructed_ber_bin_v2.erl13
-rw-r--r--lib/asn1/src/asn1ct_gen.erl16
-rw-r--r--lib/asn1/test/asn1_SUITE_data/DoubleEllipses.asn31
-rw-r--r--lib/asn1/test/testDoubleEllipses.erl14
-rw-r--r--lib/common_test/doc/src/event_handler_chapter.xml9
-rw-r--r--lib/common_test/priv/Makefile.in2
-rw-r--r--lib/common_test/src/ct.erl14
-rw-r--r--lib/common_test/src/ct_cover.erl127
-rw-r--r--lib/common_test/src/ct_framework.erl74
-rw-r--r--lib/common_test/src/ct_logs.erl45
-rw-r--r--lib/common_test/src/ct_master.erl13
-rw-r--r--lib/common_test/src/ct_netconfc.erl8
-rw-r--r--lib/common_test/src/ct_run.erl24
-rw-r--r--lib/common_test/src/ct_telnet.erl3
-rw-r--r--lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl2
-rw-r--r--lib/common_test/test/ct_cover_SUITE.erl79
-rw-r--r--lib/common_test/test/ct_cover_SUITE_data/cover_SUITE.erl4
-rw-r--r--lib/common_test/test/ct_error_SUITE.erl3
-rw-r--r--lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_12_SUITE.erl10
-rw-r--r--lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_13_SUITE.erl2
-rw-r--r--lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_14_SUITE.erl2
-rw-r--r--lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_3_SUITE.erl2
-rw-r--r--lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_7_SUITE.erl2
-rw-r--r--lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_8_SUITE.erl4
-rw-r--r--lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl18
-rw-r--r--lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_helper.erl2
-rw-r--r--lib/common_test/test/ct_event_handler_SUITE.erl38
-rw-r--r--lib/common_test/test/ct_event_handler_SUITE_data/event_handling_1/test/eh_11_SUITE.erl105
-rw-r--r--lib/common_test/test/ct_gen_conn_SUITE_data/conn_SUITE.erl34
-rw-r--r--lib/common_test/test/ct_group_leader_SUITE_data/group_leader_SUITE.erl6
-rw-r--r--lib/common_test/test/ct_groups_test_1_SUITE.erl20
-rw-r--r--lib/common_test/test/ct_groups_test_1_SUITE_data/groups_1/test/groups_12_SUITE.erl2
-rw-r--r--lib/common_test/test/ct_groups_test_2_SUITE.erl2
-rw-r--r--lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_22_SUITE.erl2
-rw-r--r--lib/common_test/test/ct_hooks_SUITE.erl32
-rw-r--r--lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_cth_fail_one_skip_one_SUITE.erl9
-rw-r--r--lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_SUITE.erl2
-rw-r--r--lib/common_test/test/ct_hooks_SUITE_data/cth/tests/empty_cth.erl15
-rw-r--r--lib/common_test/test/ct_hooks_SUITE_data/cth/tests/minimal_terminate_cth.erl4
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl33
-rw-r--r--lib/common_test/test/ct_netconfc_SUITE_data/ns.erl7
-rw-r--r--lib/common_test/test/ct_pre_post_test_io_SUITE.erl12
-rw-r--r--lib/common_test/test/ct_repeat_1_SUITE.erl39
-rw-r--r--lib/common_test/test/ct_repeat_testrun_SUITE_data/a_test/r1_SUITE.erl2
-rw-r--r--lib/common_test/test/ct_repeat_testrun_SUITE_data/b_test/r2_SUITE.erl2
-rw-r--r--lib/common_test/test/ct_sequence_1_SUITE.erl21
-rw-r--r--lib/common_test/test/ct_shell_SUITE.erl2
-rw-r--r--lib/common_test/test/ct_skip_SUITE_data/skip/test/auto_skip_4_SUITE.erl2
-rw-r--r--lib/common_test/test/ct_smoke_test_SUITE.erl10
-rw-r--r--lib/common_test/test/ct_snmp_SUITE_data/snmp_SUITE.erl4
-rw-r--r--lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl38
-rw-r--r--lib/common_test/test/ct_test_server_if_1_SUITE_data/test_server_if/test/ts_if_1_SUITE.erl8
-rw-r--r--lib/common_test/test/ct_test_support.erl14
-rw-r--r--lib/common_test/test/ct_testspec_1_SUITE.erl8
-rw-r--r--lib/common_test/test/ct_testspec_1_SUITE_data/groups_1/groups_12_SUITE.erl2
-rw-r--r--lib/common_test/test/ct_testspec_1_SUITE_data/groups_2/groups_22_SUITE.erl2
-rw-r--r--lib/common_test/test/telnet_server.erl8
-rw-r--r--lib/compiler/src/beam_bool.erl75
-rw-r--r--lib/compiler/src/core_lib.erl9
-rw-r--r--lib/compiler/src/sys_core_fold.erl199
-rw-r--r--lib/compiler/test/bs_match_SUITE.erl24
-rw-r--r--lib/compiler/test/core_fold_SUITE.erl16
-rw-r--r--lib/compiler/test/guard_SUITE.erl18
-rw-r--r--lib/compiler/test/match_SUITE.erl23
-rw-r--r--lib/crypto/c_src/crypto.c10
-rw-r--r--lib/crypto/test/crypto_SUITE.erl64
-rw-r--r--lib/debugger/src/dbg_wx_settings.erl10
-rw-r--r--lib/dialyzer/src/dialyzer_dataflow.erl1
-rw-r--r--lib/dialyzer/test/small_SUITE_data/src/maps_redef2.erl23
-rw-r--r--lib/diameter/include/diameter_gen.hrl57
-rw-r--r--lib/diameter/src/base/diameter_codec.erl21
-rw-r--r--lib/diameter/src/base/diameter_lib.erl16
-rw-r--r--lib/diameter/src/base/diameter_service.erl3
-rw-r--r--lib/diameter/src/base/diameter_service_sup.erl4
-rw-r--r--lib/diameter/src/base/diameter_sup.erl4
-rw-r--r--lib/diameter/src/base/diameter_traffic.erl35
-rw-r--r--lib/diameter/src/base/diameter_types.erl2
-rw-r--r--lib/diameter/src/transport/diameter_transport_sup.erl4
-rw-r--r--lib/diameter/test/diameter_app_SUITE.erl43
-rw-r--r--lib/diameter/test/diameter_codec_SUITE.erl188
-rw-r--r--lib/diameter/test/diameter_config_SUITE.erl5
-rw-r--r--lib/diameter/test/diameter_gen_sctp_SUITE.erl28
-rw-r--r--lib/diameter/test/diameter_traffic_SUITE.erl20
-rw-r--r--lib/diameter/test/diameter_transport_SUITE.erl6
-rw-r--r--lib/eldap/doc/src/eldap.xml22
-rw-r--r--lib/eldap/doc/src/notes.xml29
-rw-r--r--lib/eldap/src/eldap.erl5
-rw-r--r--lib/eldap/test/Makefile5
-rw-r--r--lib/eldap/test/eldap_basic_SUITE.erl1130
-rw-r--r--lib/eldap/test/eldap_basic_SUITE_data/RANDbin0 -> 512 bytes
-rw-r--r--lib/eldap/test/eldap_connections_SUITE.erl147
-rw-r--r--lib/eldap/test/eldap_misc_SUITE.erl51
-rw-r--r--lib/eldap/test/make_certs.erl357
-rw-r--r--lib/eldap/vsn.mk2
-rw-r--r--lib/hipe/cerl/erl_types.erl34
-rw-r--r--lib/hipe/ppc/hipe_rtl_to_ppc.erl14
-rw-r--r--lib/hipe/rtl/hipe_rtl.erl18
-rw-r--r--lib/hipe/rtl/hipe_rtl_binary_match.erl18
-rw-r--r--lib/hipe/sparc/hipe_rtl_to_sparc.erl10
-rw-r--r--lib/hipe/x86/hipe_rtl_to_x86.erl15
-rw-r--r--lib/inets/doc/src/http_uri.xml11
-rw-r--r--lib/inets/doc/src/httpd.xml13
-rw-r--r--lib/inets/doc/src/httpd_conf.xml8
-rw-r--r--lib/inets/doc/src/notes.xml35
-rw-r--r--lib/inets/examples/httpd_load_test/hdlt_slave.erl2
-rw-r--r--lib/inets/src/http_client/httpc_cookie.erl24
-rw-r--r--lib/inets/src/http_lib/http_internal.hrl3
-rw-r--r--lib/inets/src/http_lib/http_request.erl26
-rw-r--r--lib/inets/src/http_lib/http_uri.erl35
-rw-r--r--lib/inets/src/http_server/httpd_conf.erl28
-rw-r--r--lib/inets/src/http_server/httpd_request.erl102
-rw-r--r--lib/inets/src/http_server/httpd_request_handler.erl32
-rw-r--r--lib/inets/src/http_server/mod_alias.erl14
-rw-r--r--lib/inets/test/http_format_SUITE.erl16
-rw-r--r--lib/inets/test/httpc_SUITE.erl52
-rw-r--r--lib/inets/test/httpd_SUITE.erl45
-rw-r--r--lib/inets/test/uri_SUITE.erl37
-rw-r--r--lib/inets/vsn.mk2
-rw-r--r--lib/kernel/doc/src/gen_sctp.xml2
-rw-r--r--lib/kernel/doc/src/inet.xml10
-rw-r--r--lib/kernel/doc/src/kernel_app.xml14
-rw-r--r--lib/kernel/src/application_controller.erl14
-rw-r--r--lib/kernel/src/erl_distribution.erl1
-rw-r--r--lib/kernel/src/gen_udp.erl2
-rw-r--r--lib/kernel/src/inet.erl4
-rw-r--r--lib/kernel/src/inet_tcp_dist.erl48
-rw-r--r--lib/kernel/src/standard_error.erl155
-rw-r--r--lib/kernel/test/Makefile3
-rw-r--r--lib/kernel/test/erl_distribution_SUITE.erl76
-rw-r--r--lib/kernel/test/file_SUITE.erl2
-rw-r--r--lib/kernel/test/inet_SUITE.erl37
-rw-r--r--lib/kernel/test/prim_file_SUITE.erl2
-rw-r--r--lib/kernel/test/standard_error_SUITE.erl38
-rw-r--r--lib/mnesia/doc/src/Mnesia_chap5.xmlsrc1
-rw-r--r--lib/mnesia/doc/src/mnesia.xml16
-rw-r--r--lib/mnesia/src/mnesia_locker.erl10
-rw-r--r--lib/mnesia/src/mnesia_recover.erl31
-rw-r--r--lib/mnesia/test/mnesia_recovery_test.erl13
-rw-r--r--lib/mnesia/test/mnesia_test_lib.hrl10
-rw-r--r--lib/orber/src/cdr_decode.erl4
-rw-r--r--lib/os_mon/c_src/memsup.c2
-rw-r--r--lib/os_mon/src/cpu_sup.erl2
-rw-r--r--lib/public_key/doc/src/public_key.xml89
-rw-r--r--lib/public_key/src/pubkey_cert.erl21
-rw-r--r--lib/public_key/src/pubkey_crl.erl8
-rw-r--r--lib/public_key/src/public_key.erl99
-rw-r--r--lib/public_key/test/public_key_SUITE.erl40
-rw-r--r--lib/public_key/test/public_key_SUITE_data/crl_signer.pem25
-rw-r--r--lib/public_key/test/public_key_SUITE_data/idp_cert.pem30
-rw-r--r--lib/public_key/test/public_key_SUITE_data/idp_crl.pem18
-rw-r--r--lib/runtime_tools/src/dbg.erl60
-rw-r--r--lib/runtime_tools/test/dbg_SUITE.erl36
-rw-r--r--lib/sasl/test/release_handler_SUITE.erl18
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/regexp_appup/app1/ebin/app1.app29
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/regexp_appup/app1/ebin/app1.appup23
-rw-r--r--lib/ssh/doc/src/ssh.xml4
-rw-r--r--lib/ssh/doc/src/ssh_connection.xml14
-rw-r--r--lib/ssh/doc/src/using_ssh.xml2
-rw-r--r--lib/ssh/examples/Makefile5
-rw-r--r--lib/ssh/examples/ssh_device.erl62
-rw-r--r--lib/ssh/src/ssh_connection.erl22
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl17
-rw-r--r--lib/ssh/src/ssh_info.erl11
-rw-r--r--lib/ssh/src/ssh_sftpd.erl122
-rw-r--r--lib/ssh/test/ssh_basic_SUITE.erl39
-rw-r--r--lib/ssh/test/ssh_connection_SUITE.erl29
-rw-r--r--lib/ssh/test/ssh_sftpd_SUITE.erl48
-rw-r--r--lib/ssh/vsn.mk2
-rw-r--r--lib/ssl/doc/src/ssl.xml53
-rw-r--r--lib/ssl/doc/src/ssl_app.xml13
-rw-r--r--lib/ssl/src/dtls_record.erl4
-rw-r--r--lib/ssl/src/ssl.erl13
-rw-r--r--lib/ssl/src/ssl_alert.erl4
-rw-r--r--lib/ssl/src/ssl_alert.hrl4
-rw-r--r--lib/ssl/src/ssl_certificate.erl2
-rw-r--r--lib/ssl/src/ssl_cipher.erl53
-rw-r--r--lib/ssl/src/ssl_cipher.hrl6
-rw-r--r--lib/ssl/src/ssl_internal.hrl6
-rw-r--r--lib/ssl/src/ssl_manager.erl94
-rw-r--r--lib/ssl/src/ssl_pkix_db.erl33
-rw-r--r--lib/ssl/src/ssl_record.erl11
-rw-r--r--lib/ssl/src/tls_connection.erl7
-rw-r--r--lib/ssl/src/tls_handshake.erl82
-rw-r--r--lib/ssl/src/tls_record.erl31
-rw-r--r--lib/ssl/test/Makefile3
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl46
-rw-r--r--lib/ssl/test/ssl_certificate_verify_SUITE.erl4
-rw-r--r--lib/ssl/test/ssl_cipher_SUITE.erl188
-rw-r--r--lib/ssl/test/ssl_pem_cache_SUITE.erl127
-rw-r--r--lib/stdlib/doc/src/proc_lib.xml2
-rw-r--r--lib/stdlib/doc/src/re.xml21
-rw-r--r--lib/stdlib/src/binary.erl8
-rw-r--r--lib/stdlib/src/c.erl17
-rw-r--r--lib/stdlib/src/ets.erl7
-rw-r--r--lib/syntax_tools/src/epp_dodger.erl6
-rw-r--r--lib/test_server/src/erl2html2.erl20
-rw-r--r--lib/test_server/src/test_server.erl153
-rw-r--r--lib/test_server/src/test_server_ctrl.erl55
-rw-r--r--lib/test_server/src/ts_install.erl53
-rw-r--r--lib/test_server/src/ts_make.erl12
-rw-r--r--lib/test_server/test/erl2html2_SUITE.erl2
-rw-r--r--lib/tools/emacs/erlang.el91
-rw-r--r--lib/tools/src/lcnt.erl70
-rw-r--r--lib/tools/src/tags.erl9
-rw-r--r--lib/wx/src/wxe_server.erl14
-rw-r--r--lib/wx/test/wx_event_SUITE.erl8
206 files changed, 4917 insertions, 2043 deletions
diff --git a/lib/asn1/src/asn1ct_constructed_ber_bin_v2.erl b/lib/asn1/src/asn1ct_constructed_ber_bin_v2.erl
index 5fadd0495a..820d19b85c 100644
--- a/lib/asn1/src/asn1ct_constructed_ber_bin_v2.erl
+++ b/lib/asn1/src/asn1ct_constructed_ber_bin_v2.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2013. 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
@@ -234,7 +234,7 @@ gen_decode_sequence(Erules,Typename,D) when is_record(D,type) ->
asn1ct_name:new(rb),
emit([" {'",RecordName,"'}.",nl,nl]);
{LeadingAttrTerm,PostponedDecArgs} ->
- emit([com,nl,nl]),
+ emit([nl]),
case {LeadingAttrTerm,PostponedDecArgs} of
{[],[]} ->
ok;
@@ -413,7 +413,7 @@ gen_decode_set(Erules,Typename,D) when is_record(D,type) ->
%% return value as record
emit([" {'",RecordName,"'}.",nl]);
{LeadingAttrTerm,PostponedDecArgs} ->
- emit([com,nl,nl]),
+ emit([nl]),
case {LeadingAttrTerm,PostponedDecArgs} of
{[],[]} ->
ok;
@@ -617,18 +617,20 @@ gen_dec_sequence_call1(Erules,TopType,[#'ComponentType'{name=Cname,typespec=Type
{LA,PostponedDec} =
gen_dec_component(Erules,TopType,Cname,Tags,Type,Num,Prop,
Ext,DecObjInf),
+ emit([com,nl]),
case Rest of
[] ->
{LA ++ LeadingAttrAcc,PostponedDec ++ ArgsAcc};
_ ->
- emit([com,nl]),
asn1ct_name:new(bytes),
gen_dec_sequence_call1(Erules,TopType,Rest,Num+1,Ext,DecObjInf,
LA++LeadingAttrAcc,PostponedDec++ArgsAcc)
end;
gen_dec_sequence_call1(_Erules,_TopType,[],1,_,_,_,_) ->
- no_terms.
+ no_terms;
+gen_dec_sequence_call1(_, _, [], _Num, _, _, LA, PostponedDec) ->
+ {LA, PostponedDec}.
gen_dec_sequence_call2(_Erules,_TopType, {[], [], []}, _Ext,_DecObjInf) ->
no_terms;
@@ -643,7 +645,6 @@ gen_dec_sequence_call2(Erules,TopType,{Root1,EList,Root2},_Ext,DecObjInf) ->
%% TagList is the tags of Root2 elements from the first up to and
%% including the first mandatory element.
TagList = get_root2_taglist(Root2,[]),
- emit({com,nl}),
emit([{curr,tlv}," = ",
{call,ber,skip_ExtensionAdditions,
[{prev,tlv},{asis,TagList}]},com,nl]),
diff --git a/lib/asn1/src/asn1ct_gen.erl b/lib/asn1/src/asn1ct_gen.erl
index 450d309688..2ef8466309 100644
--- a/lib/asn1/src/asn1ct_gen.erl
+++ b/lib/asn1/src/asn1ct_gen.erl
@@ -1228,15 +1228,23 @@ gen_record(TorPtype,Name,Type,Num) when is_record(Type,type) ->
emit({"}).",nl,nl}),
Tr ++ ExtensionList2;
{Rootl1,Extl,Rootl2} ->
+ case Rootl1 =/= [] andalso Extl++Rootl2 =/= [] of
+ true -> emit([com]);
+ false -> ok
+ end,
case Rootl1 of
- [] -> true;
- _ -> emit([",",nl])
+ [_|_] -> emit([nl]);
+ [] -> ok
end,
emit(["%% with extensions",nl]),
gen_record2(Name,'SEQUENCE',Extl,"",ext),
+ case Extl =/= [] andalso Rootl2 =/= [] of
+ true -> emit([com]);
+ false -> ok
+ end,
case Extl of
- [_H|_] when Rootl2 /= [] -> emit([",",nl]);
- _ -> ok
+ [_|_] -> emit([nl]);
+ [] -> ok
end,
emit(["%% end of extensions",nl]),
gen_record2(Name,'SEQUENCE',Rootl2,"",noext),
diff --git a/lib/asn1/test/asn1_SUITE_data/DoubleEllipses.asn b/lib/asn1/test/asn1_SUITE_data/DoubleEllipses.asn
index e90cf55d61..846c3e7569 100644
--- a/lib/asn1/test/asn1_SUITE_data/DoubleEllipses.asn
+++ b/lib/asn1/test/asn1_SUITE_data/DoubleEllipses.asn
@@ -12,6 +12,15 @@ Seq ::= SEQUENCE
c BOOLEAN
}
+SeqV1 ::= SEQUENCE
+ {
+ a INTEGER,
+ ...,
+ b BOOLEAN,
+ ...
+ }
+
+
SeqV2 ::= SEQUENCE
{
a INTEGER,
@@ -50,6 +59,18 @@ SeqAltV2 ::= SEQUENCE
g INTEGER
}
+SeqDoubleEmpty1 ::= SEQUENCE {
+ ...,
+ ...
+}
+
+SeqDoubleEmpty2 ::= SEQUENCE {
+ a BOOLEAN,
+ b INTEGER OPTIONAL,
+ ...,
+ ...
+}
+
Set ::= SET {
a INTEGER,
...,
@@ -57,6 +78,14 @@ Set ::= SET {
c BOOLEAN
}
+
+SetV1 ::= SET {
+ a INTEGER,
+ ...,
+ b BOOLEAN,
+ ...
+ }
+
SetV2 ::= SET
{
a INTEGER,
@@ -96,4 +125,4 @@ SetAltV2 ::= SET
}
-END \ No newline at end of file
+END
diff --git a/lib/asn1/test/testDoubleEllipses.erl b/lib/asn1/test/testDoubleEllipses.erl
index 4e8972cdfc..bd6831bf1e 100644
--- a/lib/asn1/test/testDoubleEllipses.erl
+++ b/lib/asn1/test/testDoubleEllipses.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2007-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,17 +24,20 @@
-include_lib("test_server/include/test_server.hrl").
-record('Seq',{a, c}).
+-record('SeqV1',{a, b}).
-record('SeqV2',{a, b ,c}).
-record('SeqAlt',{a,d,b,e,c,f,g}).
-record('SeqAltV2',{a,d,b,e,h,i,c,f,g}).
-record('Set',{a, c}).
+-record('SetV1',{a, b}).
-record('SetV2',{a, b ,c}).
-record('SetAlt',{a,d,b,e,c,f,g}).
-record('SetAltV2',{a,d,b,e,h,i,c,f,g}).
main(_Rules) ->
roundtrip('Seq', #'Seq'{a=10,c=true}),
+ roundtrip('SeqV1', #'SeqV1'{a=10,b=false}),
roundtrip('SeqV2', #'SeqV2'{a=10,b=false,c=true}),
roundtrip('SeqAlt',
#'SeqAlt'{a=10,d=12,b = <<2#1010:4>>,
@@ -45,6 +48,7 @@ main(_Rules) ->
e=true,h="PS",i=13,c=false,f=14,g=16}),
roundtrip('Set', #'Set'{a=10,c=true}),
+ roundtrip('SetV1', #'SetV1'{a=10,b=false}),
roundtrip('SetV2', #'SetV2'{a=10,b=false,c=true}),
roundtrip('SetAlt',
#'SetAlt'{a=10,d=12,
@@ -54,6 +58,14 @@ main(_Rules) ->
#'SetAltV2'{a=10,d=12,
b = <<2#1010:4>>,
e=true,h="PS",i=13,c=false,f=14,g=16}),
+
+ roundtrip('SeqDoubleEmpty1',
+ {'SeqDoubleEmpty1'}),
+ roundtrip('SeqDoubleEmpty2',
+ {'SeqDoubleEmpty2',true,42}),
+ roundtrip('SeqDoubleEmpty2',
+ {'SeqDoubleEmpty2',true,asn1_NOVALUE}),
+
ok.
roundtrip(T, V) ->
diff --git a/lib/common_test/doc/src/event_handler_chapter.xml b/lib/common_test/doc/src/event_handler_chapter.xml
index 45f01c12ec..f39f391818 100644
--- a/lib/common_test/doc/src/event_handler_chapter.xml
+++ b/lib/common_test/doc/src/event_handler_chapter.xml
@@ -59,6 +59,15 @@
Event handlers plugged into this manager will receive the events from
all the test nodes as well as information from the CT Master server
itself.</p>
+
+ <p>User specific event handlers may be plugged into a Common Test event
+ manager, either by telling Common Test to install them before the test
+ run (see below), or by adding the handlers dynamically during the test
+ run by means of
+ <c>gen_event:add_handler/3</c> or <c>gen_event:add_sup_handler/3</c>.
+ In the latter scenario, the reference of the Common Test event manager is
+ required. To get it, call <c>ct:get_event_mgr_ref/0</c> or (on the CT
+ Master node) <c>ct_master:get_event_mgr_ref/0</c>.</p>
</section>
<section>
<marker id="usage"></marker>
diff --git a/lib/common_test/priv/Makefile.in b/lib/common_test/priv/Makefile.in
index 5a9fabbe45..1bc6b82ebb 100644
--- a/lib/common_test/priv/Makefile.in
+++ b/lib/common_test/priv/Makefile.in
@@ -71,7 +71,7 @@ debug opt:
$(V_at)sed -e 's;@CT_VSN@;$(VSN);' \
-e 's;@TS_VSN@;$(TEST_SERVER_VSN);' \
../install.sh.in > install.sh
- $(V_at)chmod 775 install.sh
+ - $(V_at)chmod -f 775 install.sh
docs:
diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl
index 85afdc7834..9d8fce2789 100644
--- a/lib/common_test/src/ct.erl
+++ b/lib/common_test/src/ct.erl
@@ -52,6 +52,7 @@
-module(ct).
-include("ct.hrl").
+-include("ct_util.hrl").
%% Command line user interface for running tests
-export([install/1, run/1, run/2, run/3,
@@ -77,6 +78,7 @@
%% Other interface functions
-export([get_status/0, abort_current_testcase/1,
+ get_event_mgr_ref/0,
encrypt_config_file/2, encrypt_config_file/3,
decrypt_config_file/2, decrypt_config_file/3]).
@@ -1005,6 +1007,18 @@ abort_current_testcase(Reason) ->
test_server_ctrl:abort_current_testcase(Reason).
%%%-----------------------------------------------------------------
+%%% @spec get_event_mgr_ref() -> EvMgrRef
+%%% EvMgrRef = atom()
+%%%
+%%% @doc <p>Call this function in order to get a reference to the
+%%% CT event manager. The reference can be used to e.g. add
+%%% a user specific event handler while tests are running.
+%%% Example:
+%%% <c>gen_event:add_handler(ct:get_event_mgr_ref(), my_ev_h, [])</c></p>
+get_event_mgr_ref() ->
+ ?CT_EVMGR_REF.
+
+%%%-----------------------------------------------------------------
%%% @spec encrypt_config_file(SrcFileName, EncryptFileName) ->
%%% ok | {error,Reason}
%%% SrcFileName = string()
diff --git a/lib/common_test/src/ct_cover.erl b/lib/common_test/src/ct_cover.erl
index c7f446dee9..b630a51835 100644
--- a/lib/common_test/src/ct_cover.erl
+++ b/lib/common_test/src/ct_cover.erl
@@ -174,7 +174,7 @@ get_spec_test(File) ->
[] -> [#cover{app=none, level=details}];
_ -> Res
end,
- case get_cover_opts(Apps, Terms, []) of
+ case get_cover_opts(Apps, Terms, Dir, []) of
E = {error,_} ->
E;
[CoverSpec] ->
@@ -205,124 +205,125 @@ collect_apps([], Apps) ->
%% get_cover_opts(Terms) -> AppCoverInfo
%% AppCoverInfo: [#cover{app=App,...}]
-get_cover_opts([App | Apps], Terms, CoverInfo) ->
- case get_app_info(App, Terms) of
+get_cover_opts([App | Apps], Terms, Dir, CoverInfo) ->
+ case get_app_info(App, Terms, Dir) of
E = {error,_} -> E;
AppInfo ->
AppInfo1 = files2mods(AppInfo),
- get_cover_opts(Apps, Terms, [AppInfo1|CoverInfo])
+ get_cover_opts(Apps, Terms, Dir, [AppInfo1|CoverInfo])
end;
-get_cover_opts([], _, CoverInfo) ->
+get_cover_opts([], _, _, CoverInfo) ->
lists:reverse(CoverInfo).
-%% get_app_info(App, Terms) -> App1
+%% get_app_info(App, Terms, Dir) -> App1
-get_app_info(App=#cover{app=none}, [{incl_dirs,Dirs}|Terms]) ->
- get_app_info(App, [{incl_dirs,none,Dirs}|Terms]);
-get_app_info(App=#cover{app=Name}, [{incl_dirs,Name,Dirs}|Terms]) ->
- case get_files(Dirs, ".beam", false, []) of
+get_app_info(App=#cover{app=none}, [{incl_dirs,Dirs}|Terms], Dir) ->
+ get_app_info(App, [{incl_dirs,none,Dirs}|Terms], Dir);
+get_app_info(App=#cover{app=Name}, [{incl_dirs,Name,Dirs}|Terms], Dir) ->
+ case get_files(Dirs, Dir, ".beam", false, []) of
E = {error,_} -> E;
Mods1 ->
Mods = App#cover.incl_mods,
- get_app_info(App#cover{incl_mods=Mods++Mods1},Terms)
+ get_app_info(App#cover{incl_mods=Mods++Mods1},Terms,Dir)
end;
-get_app_info(App=#cover{app=none}, [{incl_dirs_r,Dirs}|Terms]) ->
- get_app_info(App, [{incl_dirs_r,none,Dirs}|Terms]);
-get_app_info(App=#cover{app=Name}, [{incl_dirs_r,Name,Dirs}|Terms]) ->
- case get_files(Dirs, ".beam", true, []) of
+get_app_info(App=#cover{app=none}, [{incl_dirs_r,Dirs}|Terms], Dir) ->
+ get_app_info(App, [{incl_dirs_r,none,Dirs}|Terms], Dir);
+get_app_info(App=#cover{app=Name}, [{incl_dirs_r,Name,Dirs}|Terms], Dir) ->
+ case get_files(Dirs, Dir, ".beam", true, []) of
E = {error,_} -> E;
Mods1 ->
Mods = App#cover.incl_mods,
- get_app_info(App#cover{incl_mods=Mods++Mods1},Terms)
+ get_app_info(App#cover{incl_mods=Mods++Mods1},Terms,Dir)
end;
-get_app_info(App=#cover{app=none}, [{incl_mods,Mods1}|Terms]) ->
- get_app_info(App, [{incl_mods,none,Mods1}|Terms]);
-get_app_info(App=#cover{app=Name}, [{incl_mods,Name,Mods1}|Terms]) ->
+get_app_info(App=#cover{app=none}, [{incl_mods,Mods1}|Terms], Dir) ->
+ get_app_info(App, [{incl_mods,none,Mods1}|Terms], Dir);
+get_app_info(App=#cover{app=Name}, [{incl_mods,Name,Mods1}|Terms], Dir) ->
Mods = App#cover.incl_mods,
- get_app_info(App#cover{incl_mods=Mods++Mods1},Terms);
+ get_app_info(App#cover{incl_mods=Mods++Mods1},Terms,Dir);
-get_app_info(App=#cover{app=none}, [{excl_dirs,Dirs}|Terms]) ->
- get_app_info(App, [{excl_dirs,none,Dirs}|Terms]);
-get_app_info(App=#cover{app=Name}, [{excl_dirs,Name,Dirs}|Terms]) ->
- case get_files(Dirs, ".beam", false, []) of
+get_app_info(App=#cover{app=none}, [{excl_dirs,Dirs}|Terms], Dir) ->
+ get_app_info(App, [{excl_dirs,none,Dirs}|Terms], Dir);
+get_app_info(App=#cover{app=Name}, [{excl_dirs,Name,Dirs}|Terms], Dir) ->
+ case get_files(Dirs, Dir, ".beam", false, []) of
E = {error,_} -> E;
Mods1 ->
Mods = App#cover.excl_mods,
- get_app_info(App#cover{excl_mods=Mods++Mods1},Terms)
+ get_app_info(App#cover{excl_mods=Mods++Mods1},Terms,Dir)
end;
-get_app_info(App=#cover{app=none}, [{excl_dirs_r,Dirs}|Terms]) ->
- get_app_info(App, [{excl_dirs_r,none,Dirs}|Terms]);
-get_app_info(App=#cover{app=Name}, [{excl_dirs_r,Name,Dirs}|Terms]) ->
- case get_files(Dirs, ".beam", true, []) of
+get_app_info(App=#cover{app=none}, [{excl_dirs_r,Dirs}|Terms],Dir) ->
+ get_app_info(App, [{excl_dirs_r,none,Dirs}|Terms],Dir);
+get_app_info(App=#cover{app=Name}, [{excl_dirs_r,Name,Dirs}|Terms],Dir) ->
+ case get_files(Dirs, Dir, ".beam", true, []) of
E = {error,_} -> E;
Mods1 ->
Mods = App#cover.excl_mods,
- get_app_info(App#cover{excl_mods=Mods++Mods1},Terms)
+ get_app_info(App#cover{excl_mods=Mods++Mods1},Terms,Dir)
end;
-get_app_info(App=#cover{app=none}, [{excl_mods,Mods1}|Terms]) ->
- get_app_info(App, [{excl_mods,none,Mods1}|Terms]);
-get_app_info(App=#cover{app=Name}, [{excl_mods,Name,Mods1}|Terms]) ->
+get_app_info(App=#cover{app=none}, [{excl_mods,Mods1}|Terms], Dir) ->
+ get_app_info(App, [{excl_mods,none,Mods1}|Terms], Dir);
+get_app_info(App=#cover{app=Name}, [{excl_mods,Name,Mods1}|Terms], Dir) ->
Mods = App#cover.excl_mods,
- get_app_info(App#cover{excl_mods=Mods++Mods1},Terms);
+ get_app_info(App#cover{excl_mods=Mods++Mods1},Terms,Dir);
-get_app_info(App=#cover{app=none}, [{cross,Cross}|Terms]) ->
- get_app_info(App, [{cross,none,Cross}|Terms]);
-get_app_info(App=#cover{app=Name}, [{cross,Name,Cross1}|Terms]) ->
+get_app_info(App=#cover{app=none}, [{cross,Cross}|Terms], Dir) ->
+ get_app_info(App, [{cross,none,Cross}|Terms], Dir);
+get_app_info(App=#cover{app=Name}, [{cross,Name,Cross1}|Terms], Dir) ->
Cross = App#cover.cross,
- get_app_info(App#cover{cross=Cross++Cross1},Terms);
+ get_app_info(App#cover{cross=Cross++Cross1},Terms,Dir);
-get_app_info(App=#cover{app=none}, [{src_dirs,Dirs}|Terms]) ->
- get_app_info(App, [{src_dirs,none,Dirs}|Terms]);
-get_app_info(App=#cover{app=Name}, [{src_dirs,Name,Dirs}|Terms]) ->
- case get_files(Dirs, ".erl", false, []) of
+get_app_info(App=#cover{app=none}, [{src_dirs,Dirs}|Terms], Dir) ->
+ get_app_info(App, [{src_dirs,none,Dirs}|Terms], Dir);
+get_app_info(App=#cover{app=Name}, [{src_dirs,Name,Dirs}|Terms], Dir) ->
+ case get_files(Dirs, Dir, ".erl", false, []) of
E = {error,_} -> E;
Src1 ->
Src = App#cover.src,
- get_app_info(App#cover{src=Src++Src1},Terms)
+ get_app_info(App#cover{src=Src++Src1},Terms,Dir)
end;
-get_app_info(App=#cover{app=none}, [{src_dirs_r,Dirs}|Terms]) ->
- get_app_info(App, [{src_dirs_r,none,Dirs}|Terms]);
-get_app_info(App=#cover{app=Name}, [{src_dirs_r,Name,Dirs}|Terms]) ->
- case get_files(Dirs, ".erl", true, []) of
+get_app_info(App=#cover{app=none}, [{src_dirs_r,Dirs}|Terms], Dir) ->
+ get_app_info(App, [{src_dirs_r,none,Dirs}|Terms], Dir);
+get_app_info(App=#cover{app=Name}, [{src_dirs_r,Name,Dirs}|Terms], Dir) ->
+ case get_files(Dirs, Dir, ".erl", true, []) of
E = {error,_} -> E;
Src1 ->
Src = App#cover.src,
- get_app_info(App#cover{src=Src++Src1},Terms)
+ get_app_info(App#cover{src=Src++Src1},Terms,Dir)
end;
-get_app_info(App=#cover{app=none}, [{src_files,Src1}|Terms]) ->
- get_app_info(App, [{src_files,none,Src1}|Terms]);
-get_app_info(App=#cover{app=Name}, [{src_files,Name,Src1}|Terms]) ->
+get_app_info(App=#cover{app=none}, [{src_files,Src1}|Terms], Dir) ->
+ get_app_info(App, [{src_files,none,Src1}|Terms], Dir);
+get_app_info(App=#cover{app=Name}, [{src_files,Name,Src1}|Terms], Dir) ->
Src = App#cover.src,
- get_app_info(App#cover{src=Src++Src1},Terms);
+ get_app_info(App#cover{src=Src++Src1},Terms,Dir);
-get_app_info(App, [_|Terms]) ->
- get_app_info(App, Terms);
+get_app_info(App, [_|Terms], Dir) ->
+ get_app_info(App, Terms, Dir);
-get_app_info(App, []) ->
+get_app_info(App, [], _) ->
App.
%% get_files(...)
-get_files([Dir|Dirs], Ext, Recurse, Files) ->
- case file:list_dir(Dir) of
+get_files([Dir|Dirs], RootDir, Ext, Recurse, Files) ->
+ DirAbs = filename:absname(Dir, RootDir),
+ case file:list_dir(DirAbs) of
{ok,Entries} ->
- {SubDirs,Matches} = analyse_files(Entries, Dir, Ext, [], []),
+ {SubDirs,Matches} = analyse_files(Entries, DirAbs, Ext, [], []),
if Recurse == false ->
- get_files(Dirs, Ext, Recurse, Files++Matches);
+ get_files(Dirs, RootDir, Ext, Recurse, Files++Matches);
true ->
- Files1 = get_files(SubDirs, Ext, Recurse, Files++Matches),
- get_files(Dirs, Ext, Recurse, Files1)
+ Files1 = get_files(SubDirs, RootDir, Ext, Recurse, Files++Matches),
+ get_files(Dirs, RootDir, Ext, Recurse, Files1)
end;
{error,Reason} ->
- {error,{Reason,Dir}}
+ {error,{Reason,DirAbs}}
end;
-get_files([], _Ext, _R, Files) ->
+get_files([], _RootDir, _Ext, _R, Files) ->
Files.
%% analyse_files(...)
diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl
index e8ea7992b4..498950c9d1 100644
--- a/lib/common_test/src/ct_framework.erl
+++ b/lib/common_test/src/ct_framework.erl
@@ -686,18 +686,21 @@ end_tc(Mod,Func,TCPid,Result,Args,Return) ->
undefined ->
%% send sync notification so that event handlers may print
%% in the log file before it gets closed
- ct_event:sync_notify(#event{name=tc_done,
- node=node(),
- data={Mod,FuncSpec,
- tag_cth(FinalNotify)}}),
+ Event = #event{name=tc_done,
+ node=node(),
+ data={Mod,FuncSpec,
+ tag(FinalNotify)}},
+ ct_event:sync_notify(Event),
Result1;
Fun ->
%% send sync notification so that event handlers may print
%% in the log file before it gets closed
- ct_event:sync_notify(#event{name=tc_done,
- node=node(),
- data={Mod,FuncSpec,
- tag(FinalNotify)}}),
+ Event = #event{name=tc_done,
+ node=node(),
+ data={Mod,FuncSpec,
+ tag({'$test_server_framework_test',
+ FinalNotify})}},
+ ct_event:sync_notify(Event),
Fun(end_tc, Return)
end,
@@ -770,44 +773,37 @@ end_tc(Mod,Func,TCPid,Result,Args,Return) ->
%% {error,Reason} | {skip,Reason} | {timetrap_timeout,TVal} |
%% {testcase_aborted,Reason} | testcase_aborted_or_killed |
-%% {'EXIT',Reason} | Other (ignored return value, e.g. 'ok')
-tag({STag,Reason}) when STag == skip; STag == skipped ->
- case Reason of
- {failed,{_,init_per_testcase,_}} -> {auto_skipped,Reason};
- _ -> {skipped,Reason}
- end;
-tag({auto_skip,Reason}) ->
- {auto_skipped,Reason};
-tag(E = {ETag,_}) when ETag == error; ETag == 'EXIT';
- ETag == timetrap_timeout;
- ETag == testcase_aborted ->
- {failed,E};
-tag(E = testcase_aborted_or_killed) ->
- {failed,E};
-tag(Other) ->
- Other.
-
-tag_cth({skipped,Reason={failed,{_,init_per_testcase,_}}}) ->
+%% {'EXIT',Reason} | {fail,Reason} | {failed,Reason} |
+%% {user_timetrap_error,Reason} |
+%% Other (ignored return value, e.g. 'ok')
+tag({'$test_server_framework_test',Result}) ->
+ case tag(Result) of
+ ok -> Result;
+ Failure -> Failure
+ end;
+tag({skipped,Reason={failed,{_,init_per_testcase,_}}}) ->
{auto_skipped,Reason};
-tag_cth({STag,Reason}) when STag == skip; STag == skipped ->
+tag({STag,Reason}) when STag == skip; STag == skipped ->
case Reason of
{failed,{_,init_per_testcase,_}} -> {auto_skipped,Reason};
_ -> {skipped,Reason}
end;
-tag_cth({auto_skip,Reason}) ->
+tag({auto_skip,Reason}) ->
{auto_skipped,Reason};
-tag_cth({fail,Reason}) ->
+tag({fail,Reason}) ->
{failed,{error,Reason}};
-tag_cth(E = {ETag,_}) when ETag == error; ETag == 'EXIT';
+tag(Failed = {failed,_Reason}) ->
+ Failed;
+tag(E = {ETag,_}) when ETag == error; ETag == 'EXIT';
ETag == timetrap_timeout;
ETag == testcase_aborted ->
{failed,E};
-tag_cth(E = testcase_aborted_or_killed) ->
+tag(E = testcase_aborted_or_killed) ->
{failed,E};
-tag_cth(List) when is_list(List) ->
- ok;
-tag_cth(Other) ->
- Other.
+tag(UserTimetrap = {user_timetrap_error,_Reason}) ->
+ UserTimetrap;
+tag(_Other) ->
+ ok.
%%%-----------------------------------------------------------------
%%% @spec error_notification(Mod,Func,Args,Error) -> ok
@@ -841,6 +837,8 @@ error_notification(Mod,Func,_Args,{Error,Loc}) ->
io_lib:format("{test_case_failed,~p}", [Reason]);
Result -> Result
end;
+ {'EXIT',_Reason} = EXIT ->
+ io_lib:format("~P", [EXIT,5]);
{Spec,_Reason} when is_atom(Spec) ->
io_lib:format("~w", [Spec]);
Other ->
@@ -1268,6 +1266,11 @@ report(What,Data) ->
Data1 = if GrName == undefined -> {Suite,Func,Result};
true -> Data
end,
+ %% Register the group leader for the process calling the report
+ %% function, making it possible for a hook function to print
+ %% in the test case log file
+ ReportingPid = self(),
+ ct_logs:register_groupleader(ReportingPid, group_leader()),
case Result of
{failed, _} ->
ct_hooks:on_tc_fail(What, Data1);
@@ -1282,6 +1285,7 @@ report(What,Data) ->
_Else ->
ok
end,
+ ct_logs:unregister_groupleader(ReportingPid),
case {Func,Result} of
{init_per_suite,_} ->
ok;
diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl
index 7037cdca73..dc118ed149 100644
--- a/lib/common_test/src/ct_logs.erl
+++ b/lib/common_test/src/ct_logs.erl
@@ -29,6 +29,7 @@
-module(ct_logs).
-export([init/2, close/2, init_tc/1, end_tc/1]).
+-export([register_groupleader/2, unregister_groupleader/1]).
-export([get_log_dir/0, get_log_dir/1]).
-export([log/3, start_log/1, cont_log/2, end_log/0]).
-export([set_stylesheet/2, clear_stylesheet/1]).
@@ -267,7 +268,7 @@ init_tc(RefreshLog) ->
ok.
%%%-----------------------------------------------------------------
-%%% @spec end_tc(TCPid) -> ok | {error,Reason}
+%%% @spec end_tc(TCPid) -> ok
%%%
%%% @doc Test case clean up (tool-internal use only).
%%%
@@ -278,6 +279,26 @@ end_tc(TCPid) ->
call({end_tc,TCPid}).
%%%-----------------------------------------------------------------
+%%% @spec register_groupleader(Pid,GroupLeader) -> ok
+%%%
+%%% @doc To enable logging to a group leader (tool-internal use only).
+%%%
+%%% <p>This function is called by ct_framework:report/2</p>
+register_groupleader(Pid,GroupLeader) ->
+ call({register_groupleader,Pid,GroupLeader}),
+ ok.
+
+%%%-----------------------------------------------------------------
+%%% @spec unregister_groupleader(Pid) -> ok
+%%%
+%%% @doc To disable logging to a group leader (tool-internal use only).
+%%%
+%%% <p>This function is called by ct_framework:report/2</p>
+unregister_groupleader(Pid) ->
+ call({unregister_groupleader,Pid}),
+ ok.
+
+%%%-----------------------------------------------------------------
%%% @spec log(Heading,Format,Args) -> ok
%%%
%%% @doc Log internal activity (tool-internal use only).
@@ -764,6 +785,14 @@ logger_loop(State) ->
return(From,ok),
logger_loop(State#logger_state{tc_groupleaders =
rm_tc_gl(TCPid,State)});
+ {{register_groupleader,Pid,GL},From} ->
+ GLs = add_tc_gl(Pid,GL,State),
+ return(From,ok),
+ logger_loop(State#logger_state{tc_groupleaders = GLs});
+ {{unregister_groupleader,Pid},From} ->
+ return(From,ok),
+ logger_loop(State#logger_state{tc_groupleaders =
+ rm_tc_gl(Pid,State)});
{{get_log_dir,true},From} ->
return(From,{ok,State#logger_state.log_dir}),
logger_loop(State);
@@ -1876,6 +1905,17 @@ sort_all_runs(Dirs) ->
{Date1,HH1,MM1,SS1} > {Date2,HH2,MM2,SS2}
end, Dirs).
+sort_ct_runs(Dirs) ->
+ %% Directory naming: <Prefix>.NodeName.Date_Time[/...]
+ %% Sort on Date_Time string: "YYYY-MM-DD_HH.MM.SS"
+ lists:sort(fun(Dir1,Dir2) ->
+ [_Prefix,_Node1,DateHH1,MM1,SS1] =
+ string:tokens(filename:dirname(Dir1),[$.]),
+ [_Prefix,_Node2,DateHH2,MM2,SS2] =
+ string:tokens(filename:dirname(Dir2),[$.]),
+ {DateHH1,MM1,SS1} =< {DateHH2,MM2,SS2}
+ end, Dirs).
+
dir_diff_all_runs(Dirs, LogCache) ->
case LogCache#log_cache.all_runs of
[] ->
@@ -2188,7 +2228,8 @@ make_all_suites_index(When) when is_atom(When) ->
end
end,
- LogDirs = filelib:wildcard(logdir_prefix()++".*/*"++?logdir_ext),
+ Wildcard = logdir_prefix()++".*/*"++?logdir_ext,
+ LogDirs = sort_ct_runs(filelib:wildcard(Wildcard)),
LogCacheInfo = get_cache_data(UseCache),
diff --git a/lib/common_test/src/ct_master.erl b/lib/common_test/src/ct_master.erl
index b42ff73846..2cdb259899 100644
--- a/lib/common_test/src/ct_master.erl
+++ b/lib/common_test/src/ct_master.erl
@@ -25,6 +25,7 @@
-export([run/1,run/3,run/4]).
-export([run_on_node/2,run_on_node/3]).
-export([run_test/1,run_test/2]).
+-export([get_event_mgr_ref/0]).
-export([basic_html/1]).
-export([abort/0,abort/1,progress/0]).
@@ -292,6 +293,18 @@ progress() ->
call(progress).
%%%-----------------------------------------------------------------
+%%% @spec get_event_mgr_ref() -> MasterEvMgrRef
+%%% MasterEvMgrRef = atom()
+%%%
+%%% @doc <p>Call this function in order to get a reference to the
+%%% CT master event manager. The reference can be used to e.g.
+%%% add a user specific event handler while tests are running.
+%%% Example:
+%%% <c>gen_event:add_handler(ct_master:get_event_mgr_ref(), my_ev_h, [])</c></p>
+get_event_mgr_ref() ->
+ ?CT_MEVMGR_REF.
+
+%%%-----------------------------------------------------------------
%%% @spec basic_html(Bool) -> ok
%%% Bool = true | false
%%%
diff --git a/lib/common_test/src/ct_netconfc.erl b/lib/common_test/src/ct_netconfc.erl
index bded5a15cb..85fb1ea8d2 100644
--- a/lib/common_test/src/ct_netconfc.erl
+++ b/lib/common_test/src/ct_netconfc.erl
@@ -759,8 +759,9 @@ action(Client,Action) ->
Client :: client(),
Action :: simple_xml(),
Timeout :: timeout(),
- Result :: {ok,[simple_xml()]} | {error,error_reason()}.
-%% @doc Execute an action.
+ Result :: ok | {ok,[simple_xml()]} | {error,error_reason()}.
+%% @doc Execute an action. If the return type is void, <c>ok</c> will
+%% be returned instead of <c>{ok,[simple_xml()]}</c>.
%%
%% @end
%%----------------------------------------------------------------------
@@ -1570,6 +1571,9 @@ decode_ok(Other) ->
decode_data([{Tag,Attrs,Content}]) ->
case get_local_name_atom(Tag) of
+ ok ->
+ %% when action has return type void
+ ok;
data ->
%% Since content of data has nothing from the netconf
%% namespace, we remove the parent's xmlns attribute here
diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl
index 00d0aab507..4a12481214 100644
--- a/lib/common_test/src/ct_run.erl
+++ b/lib/common_test/src/ct_run.erl
@@ -293,10 +293,10 @@ script_start1(Parent, Args) ->
application:set_env(common_test, auto_compile, true),
InclDirs =
case proplists:get_value(include, Args) of
- Incl when is_list(hd(Incl)) ->
- Incl;
+ Incls when is_list(hd(Incls)) ->
+ [filename:absname(IDir) || IDir <- Incls];
Incl when is_list(Incl) ->
- [Incl];
+ [filename:absname(Incl)];
undefined ->
[]
end,
@@ -774,7 +774,8 @@ script_usage() ->
"\n\t[-basic_html]\n\n"),
io:format("Run tests from command line:\n\n"
"\tct_run [-dir TestDir1 TestDir2 .. TestDirN] |"
- "\n\t[-suite Suite1 Suite2 .. SuiteN [-case Case1 Case2 .. CaseN]]"
+ "\n\t[[-dir TestDir] -suite Suite1 Suite2 .. SuiteN"
+ "\n\t [[-group Groups1 Groups2 .. GroupsN] [-case Case1 Case2 .. CaseN]]]"
"\n\t[-step [config | keep_inactive]]"
"\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]"
"\n\t[-userconfig CallbackModule ConfigFile1 .. ConfigFileN]"
@@ -1023,10 +1024,10 @@ run_test2(StartOpts) ->
case proplists:get_value(include, StartOpts) of
undefined ->
[];
- Incl when is_list(hd(Incl)) ->
- Incl;
+ Incls when is_list(hd(Incls)) ->
+ [filename:absname(IDir) || IDir <- Incls];
Incl when is_list(Incl) ->
- [Incl]
+ [filename:absname(Incl)]
end,
case os:getenv("CT_INCLUDE_PATH") of
false ->
@@ -1393,6 +1394,7 @@ run_testspec2(TestSpec) ->
EnvInclude++Opts#opts.include
end,
application:set_env(common_test, include, AllInclude),
+
LogDir1 = which(logdir,Opts#opts.logdir),
case check_and_install_configfiles(
Opts#opts.config, LogDir1, Opts) of
@@ -2134,6 +2136,14 @@ do_run_test(Tests, Skip, Opts0) ->
case check_and_add(Tests, [], []) of
{ok,AddedToPath} ->
ct_util:set_testdata({stats,{0,0,{0,0}}}),
+
+ %% test_server needs to know the include path too
+ InclPath = case application:get_env(common_test, include) of
+ {ok,Incls} -> Incls;
+ _ -> []
+ end,
+ application:set_env(test_server, include, InclPath),
+
test_server_ctrl:start_link(local),
%% let test_server expand the test tuples and count no of cases
diff --git a/lib/common_test/src/ct_telnet.erl b/lib/common_test/src/ct_telnet.erl
index babe73e575..4e03bf8630 100644
--- a/lib/common_test/src/ct_telnet.erl
+++ b/lib/common_test/src/ct_telnet.erl
@@ -1122,7 +1122,8 @@ teln_expect1(Name,Pid,Data,Pattern,Acc,EO=#eo{idle_timeout=IdleTO,
NotFinished ->
%% Get more data
Fun = fun() -> get_data1(EO#eo.teln_pid) end,
- case timer:tc(ct_gen_conn, do_within_time, [Fun, IdleTO]) of
+ BreakAfter = if TotalTO < IdleTO -> TotalTO; true -> IdleTO end,
+ case timer:tc(ct_gen_conn, do_within_time, [Fun, BreakAfter]) of
{_,{error,Reason}} ->
%% A timeout will occur when the telnet connection
%% is idle for EO#eo.idle_timeout milliseconds.
diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl
index 8ee12a2e4d..c2e06d866f 100644
--- a/lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl
+++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl
@@ -73,7 +73,7 @@ test_get_known_variable(_)->
test_localtime_update(_)->
Seconds = 5,
LT1 = ct:get_config(localtime),
- timer:sleep(Seconds*1000),
+ ct:sleep(Seconds*1000),
LT2 = ct:reload_config(localtime),
case is_diff_ok(LT1, LT2, Seconds) of
{false, Actual, Exp}->
diff --git a/lib/common_test/test/ct_cover_SUITE.erl b/lib/common_test/test/ct_cover_SUITE.erl
index 87ba4ae1b9..1dab425509 100644
--- a/lib/common_test/test/ct_cover_SUITE.erl
+++ b/lib/common_test/test/ct_cover_SUITE.erl
@@ -77,7 +77,11 @@ all() ->
ct_cover_add_remove_nodes,
otp_9956,
cross,
- export_import
+ export_import,
+ relative_incl_dirs,
+ absolute_incl_dirs,
+ relative_excl_dirs,
+ absolute_excl_dirs
].
%%--------------------------------------------------------------------
@@ -215,6 +219,45 @@ export_import(Config) ->
check_calls(Events2,2),
ok.
+relative_incl_dirs(Config) ->
+ false = check_cover(Config),
+ RelDir = rel_path(?config(priv_dir, Config), ?config(data_dir, Config)),
+ CoverSpec = [{incl_dirs, [RelDir]}],
+ CoverFile = create_cover_file(rel_incl_dirs, CoverSpec, Config),
+ Opts = [{cover, CoverFile}],
+ {ok, Events} = run_test(rel_incl_dirs, default, Opts, Config),
+ check_calls(Events, 1),
+ ok.
+
+absolute_incl_dirs(Config) ->
+ false = check_cover(Config),
+ CoverSpec = [{incl_dirs, [?config(data_dir, Config)]}],
+ CoverFile = create_cover_file(abs_incl_dirs, CoverSpec, Config),
+ Opts = [{cover, CoverFile}],
+ {ok, Events} = run_test(abs_incl_dirs, default, Opts, Config),
+ check_calls(Events, 1),
+ ok.
+
+relative_excl_dirs(Config) ->
+ false = check_cover(Config),
+ RelDir = rel_path(?config(priv_dir, Config), ?config(data_dir, Config)),
+ CoverSpec = default_cover_file_content() ++ [{excl_dirs, [RelDir]}],
+ CoverFile = create_cover_file(rel_excl_dirs, CoverSpec, Config),
+ Opts = [{cover, CoverFile}],
+ {ok, Events} = run_test(rel_excl_dirs, default_no_cover, Opts, Config),
+ check_no_cover_compiled(Events),
+ ok.
+
+absolute_excl_dirs(Config) ->
+ false = check_cover(Config),
+ AbsDir = ?config(data_dir, Config),
+ CoverSpec = default_cover_file_content() ++ [{excl_dirs, [AbsDir]}],
+ CoverFile = create_cover_file(abs_excl_dirs, CoverSpec, Config),
+ Opts = [{cover, CoverFile}],
+ {ok, Events} = run_test(abs_excl_dirs, default_no_cover, Opts, Config),
+ check_no_cover_compiled(Events),
+ ok.
+
%%%-----------------------------------------------------------------
%%% HELP FUNCTIONS
%%%-----------------------------------------------------------------
@@ -288,23 +331,36 @@ get_log_dirs(Events) ->
{ct_test_support_eh,
{event,start_logging,_Node,LogDir}} <- Events].
+%% Check if a module was compiled without cover
+check_no_cover_compiled(Events) ->
+ check_no_cover_compiled(Events, ?mod).
+check_no_cover_compiled(Events, Mod) ->
+ [ {error, {not_cover_compiled, Mod}} = analyse_log(CoverLog, Mod)
+ || CoverLog <- cover_logs(Events) ].
+
%% Check that each coverlog includes N calls to ?mod:foo/0
check_calls(Events,N) ->
check_calls(Events,{?mod,foo,0},N).
check_calls(Events,MFA,N) ->
- CoverLogs = [filename:join(D,"all.coverdata") || D <- get_log_dirs(Events)],
- do_check_logs(CoverLogs,MFA,N).
+ do_check_logs(cover_logs(Events),MFA,N).
do_check_logs([CoverLog|CoverLogs],{Mod,_,_} = MFA,N) ->
- {ok,_} = cover:start(),
- ok = cover:import(CoverLog),
- {ok,Calls} = cover:analyse(Mod,calls,function),
- ok = cover:stop(),
+ {ok, Calls} = analyse_log(CoverLog, Mod),
{MFA,N} = lists:keyfind(MFA,1,Calls),
do_check_logs(CoverLogs,MFA,N);
do_check_logs([],_,_) ->
ok.
+cover_logs(Events) ->
+ [filename:join(D,"all.coverdata") || D <- get_log_dirs(Events)].
+
+analyse_log(CoverLog, Mod) ->
+ {ok, _} = cover:start(),
+ ok = cover:import(CoverLog),
+ Result = cover:analyse(Mod, calls, function),
+ ok = cover:stop(),
+ Result.
+
fullname(Name) ->
{ok,Host} = inet:gethostname(),
list_to_atom(atom_to_list(Name) ++ "@" ++ Host).
@@ -333,3 +389,12 @@ start_slave(Name,Args) ->
{boot_timeout,10}, % extending some timers for slow test hosts
{init_timeout,10},
{startup_timeout,10}]).
+
+rel_path(From, To) ->
+ Segments = do_rel_path(filename:split(From), filename:split(To)),
+ filename:join(Segments).
+
+do_rel_path([Seg|RestA], [Seg|RestB]) ->
+ do_rel_path(RestA, RestB);
+do_rel_path(PathA, PathB) ->
+ lists:duplicate(length(PathA), "..") ++ PathB.
diff --git a/lib/common_test/test/ct_cover_SUITE_data/cover_SUITE.erl b/lib/common_test/test/ct_cover_SUITE_data/cover_SUITE.erl
index 83d368c53d..789e48bd96 100644
--- a/lib/common_test/test/ct_cover_SUITE_data/cover_SUITE.erl
+++ b/lib/common_test/test/ct_cover_SUITE_data/cover_SUITE.erl
@@ -71,6 +71,10 @@ default(_Config) ->
cover_test_mod:foo(),
ok.
+default_no_cover(_Config) ->
+ cover_test_mod:foo(),
+ ok.
+
slave(_Config) ->
cover_compiled = code:which(cover_test_mod),
cover_test_mod:foo(),
diff --git a/lib/common_test/test/ct_error_SUITE.erl b/lib/common_test/test/ct_error_SUITE.erl
index ecf231529a..8464225284 100644
--- a/lib/common_test/test/ct_error_SUITE.erl
+++ b/lib/common_test/test/ct_error_SUITE.erl
@@ -1466,7 +1466,8 @@ test_events(misc_errors) ->
{failed,{error,{suite_failed,this_is_expected}}}}},
{?eh,test_stats,{0,5,{0,0}}},
{?eh,tc_start,{misc_error_1_SUITE,killed_by_signal_1}},
- {?eh,tc_done,{misc_error_1_SUITE,killed_by_signal_1,i_die_now}},
+ {?eh,tc_done,{misc_error_1_SUITE,killed_by_signal_1,
+ {failed,{'EXIT',i_die_now}}}},
{?eh,test_stats,{0,6,{0,0}}},
{?eh,tc_start,{misc_error_1_SUITE,killed_by_signal_2}},
{?eh,tc_done,{misc_error_1_SUITE,killed_by_signal_2,
diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_12_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_12_SUITE.erl
index 806d3caf72..0ff8659269 100644
--- a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_12_SUITE.erl
+++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_12_SUITE.erl
@@ -26,10 +26,10 @@ init_per_testcase(_, Config) ->
Config.
end_per_testcase(tc2, _Config) ->
- timer:sleep(2000),
+ ct:sleep(2000),
exit(this_should_not_be_printed);
end_per_testcase(tc4, _Config) ->
- timer:sleep(2000),
+ ct:sleep(2000),
exit(this_should_not_be_printed);
end_per_testcase(_, _) ->
ok.
@@ -42,7 +42,7 @@ tc1() ->
put('$test_server_framework_test',
fun(init_tc, _Default) ->
ct:pal("init_tc(~p): Night time...",[self()]),
- timer:sleep(2000),
+ ct:sleep(2000),
ct:pal("init_tc(~p): Day time!",[self()]),
exit(this_should_not_be_printed);
(_, Default) -> Default
@@ -67,7 +67,7 @@ tc3(_) ->
put('$test_server_framework_test',
fun(end_tc, _Default) ->
ct:pal("end_tc(~p): Night time...",[self()]),
- timer:sleep(1000),
+ ct:sleep(1000),
ct:pal("end_tc(~p): Day time!",[self()]);
(_, Default) -> Default
end),
@@ -78,7 +78,7 @@ tc4() ->
put('$test_server_framework_test',
fun(end_tc, _Default) ->
ct:pal("end_tc(~p): Night time...",[self()]),
- timer:sleep(1000),
+ ct:sleep(1000),
ct:pal("end_tc(~p): Day time!",[self()]);
(_, Default) -> Default
end),
diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_13_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_13_SUITE.erl
index c8a3c1d15e..cfc0babb68 100644
--- a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_13_SUITE.erl
+++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_13_SUITE.erl
@@ -26,7 +26,7 @@ init_per_suite() ->
put('$test_server_framework_test',
fun(end_tc, _Default) ->
ct:pal("end_tc(~p): Night time...",[self()]),
- timer:sleep(1000),
+ ct:sleep(1000),
ct:pal("end_tc(~p): Day time!",[self()]);
(_, Default) -> Default
end),
diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_14_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_14_SUITE.erl
index 960d0f61b0..54b09e78c6 100644
--- a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_14_SUITE.erl
+++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_14_SUITE.erl
@@ -29,7 +29,7 @@ end_per_suite() ->
put('$test_server_framework_test',
fun(end_tc, _Default) ->
ct:pal("end_tc(~p): Night time...",[self()]),
- timer:sleep(1000),
+ ct:sleep(1000),
ct:pal("end_tc(~p): Day time!",[self()]);
(_, Default) -> Default
end),
diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_3_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_3_SUITE.erl
index 08c57887ef..0d93e46501 100644
--- a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_3_SUITE.erl
+++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_3_SUITE.erl
@@ -36,7 +36,7 @@ suite() ->
%% Reason = term()
%%--------------------------------------------------------------------
init_per_suite(Config) ->
- timer:sleep(5000),
+ ct:sleep(5000),
exit(shouldnt_happen).
% Config.
diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_7_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_7_SUITE.erl
index 9cd5b6ad29..d95f3b235b 100644
--- a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_7_SUITE.erl
+++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_7_SUITE.erl
@@ -43,7 +43,7 @@ init_per_suite(Config) ->
%% Config0 = Config1 = [tuple()]
%%--------------------------------------------------------------------
end_per_suite(Config) ->
- timer:sleep(5000),
+ ct:sleep(5000),
ok.
%%--------------------------------------------------------------------
diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_8_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_8_SUITE.erl
index 25993833d7..d8f0c48034 100644
--- a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_8_SUITE.erl
+++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_8_SUITE.erl
@@ -57,7 +57,7 @@ init_per_group(g1, Config) ->
Config;
init_per_group(g2, Config) ->
ct:comment("init_per_group(g2) timeout"),
- timer:sleep(5000),
+ ct:sleep(5000),
Config;
init_per_group(g3, _Config) ->
badmatch = 42;
@@ -80,7 +80,7 @@ end_per_group(g11, _Config) ->
ok;
end_per_group(g12, _Config) ->
ct:comment("end_per_group(g6) timeout"),
- timer:sleep(5000),
+ ct:sleep(5000),
ok;
end_per_group(_GroupName, _Config) ->
ok.
diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl
index a98382965f..1451a4119e 100644
--- a/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl
+++ b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl
@@ -111,7 +111,7 @@ end_per_testcase1(tc2, Config) ->
ct:pal("end_per_testcase(tc2): ~p", [Config]),
tc2 = ?config(tc, Config),
{failed,timetrap_timeout} = ?config(tc_status, Config),
- timer:sleep(2000);
+ ct:sleep(2000);
end_per_testcase1(tc3, Config) ->
ct:pal("end_per_testcase(tc3): ~p", [Config]),
@@ -123,7 +123,7 @@ end_per_testcase1(tc4, Config) ->
ct:pal("end_per_testcase(tc4): ~p", [Config]),
tc4 = ?config(tc, Config),
{failed,{testcase_aborted,testing_end_conf}} = ?config(tc_status, Config),
- timer:sleep(2000);
+ ct:sleep(2000);
end_per_testcase1(tc5, Config) ->
ct:pal("end_per_testcase(tc5): ~p", [Config]),
@@ -182,29 +182,29 @@ all() ->
[tc1, tc2, tc3, tc4, tc5, tc6, tc7, tc8, tc9].
tc1(_) ->
- timer:sleep(2000),
+ ct:sleep(2000),
ok.
tc2(_) ->
- timer:sleep(2000).
+ ct:sleep(2000).
tc3(_) ->
spawn(ct, abort_current_testcase, [testing_end_conf]),
- timer:sleep(2000),
+ ct:sleep(2000),
ok.
tc4(_) ->
spawn(ct, abort_current_testcase, [testing_end_conf]),
- timer:sleep(2000),
+ ct:sleep(2000),
ok.
tc5(_) ->
- timer:sleep(2000),
+ ct:sleep(2000),
ok.
tc6(_) ->
spawn(ct, abort_current_testcase, [testing_end_conf]),
- timer:sleep(2000).
+ ct:sleep(2000).
tc7(_) ->
sleep(2000),
@@ -220,5 +220,5 @@ tc9(_) ->
%%%-----------------------------------------------------------------
sleep(T) ->
- timer:sleep(T),
+ ct:sleep(T),
ok.
diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_helper.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_helper.erl
index 1389acca11..a9ea0be847 100644
--- a/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_helper.erl
+++ b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_helper.erl
@@ -3,5 +3,5 @@
-export([sleep/1]).
sleep(T) ->
- timer:sleep(T),
+ ct:sleep(T),
ok.
diff --git a/lib/common_test/test/ct_event_handler_SUITE.erl b/lib/common_test/test/ct_event_handler_SUITE.erl
index b534a7141d..b759424e46 100644
--- a/lib/common_test/test/ct_event_handler_SUITE.erl
+++ b/lib/common_test/test/ct_event_handler_SUITE.erl
@@ -29,6 +29,7 @@
-compile(export_all).
-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/src/ct_util.hrl").
%-include_lib("common_test/include/ct_event.hrl").
@@ -59,7 +60,7 @@ end_per_testcase(TestCase, Config) ->
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
- [start_stop, results].
+ [start_stop, results, event_mgrs].
groups() ->
[].
@@ -156,18 +157,28 @@ results(Config) when is_list(Config) ->
TestEvents =
[{eh_A,start_logging,{'DEF','RUNDIR'}},
{eh_A,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
- {eh_A,start_info,{1,1,3}},
+ {eh_A,start_info,{1,1,5}},
{eh_A,tc_start,{eh_11_SUITE,init_per_suite}},
{eh_A,tc_done,{eh_11_SUITE,init_per_suite,ok}},
- {eh_A,tc_start,{eh_11_SUITE,tc1}},
- {eh_A,tc_done,{eh_11_SUITE,tc1,ok}},
- {eh_A,test_stats,{1,0,{0,0}}},
- {eh_A,tc_start,{eh_11_SUITE,tc2}},
- {eh_A,tc_done,{eh_11_SUITE,tc2,{skipped,"Skipped"}}},
- {eh_A,test_stats,{1,0,{1,0}}},
- {eh_A,tc_start,{eh_11_SUITE,tc3}},
- {eh_A,tc_done,{eh_11_SUITE,tc3,{failed,{error,'Failing'}}}},
- {eh_A,test_stats,{1,1,{1,0}}},
+ [{eh_A,tc_start,{eh_11_SUITE,{init_per_group,g1,[]}}},
+ {eh_A,tc_done,{eh_11_SUITE,{init_per_group,g1,[]},ok}},
+ {eh_A,tc_start,{eh_11_SUITE,tc1}},
+ {eh_A,tc_done,{eh_11_SUITE,tc1,ok}},
+ {eh_A,test_stats,{1,0,{0,0}}},
+ {eh_A,tc_start,{eh_11_SUITE,tc2}},
+ {eh_A,tc_done,{eh_11_SUITE,tc2,ok}},
+ {eh_A,test_stats,{2,0,{0,0}}},
+ {eh_A,tc_start,{eh_11_SUITE,tc3}},
+ {eh_A,tc_done,{eh_11_SUITE,tc3,{skipped,"Skip"}}},
+ {eh_A,test_stats,{2,0,{1,0}}},
+ {eh_A,tc_start,{eh_11_SUITE,tc4}},
+ {eh_A,tc_done,{eh_11_SUITE,tc4,{skipped,"Skipped"}}},
+ {eh_A,test_stats,{2,0,{2,0}}},
+ {eh_A,tc_start,{eh_11_SUITE,tc5}},
+ {eh_A,tc_done,{eh_11_SUITE,tc5,{failed,{error,'Failing'}}}},
+ {eh_A,test_stats,{2,1,{2,0}}},
+ {eh_A,tc_start,{eh_11_SUITE,{end_per_group,g1,[]}}},
+ {eh_A,tc_done,{eh_11_SUITE,{end_per_group,g1,[]},ok}}],
{eh_A,tc_start,{eh_11_SUITE,end_per_suite}},
{eh_A,tc_done,{eh_11_SUITE,end_per_suite,ok}},
{eh_A,test_done,{'DEF','STOP_TIME'}},
@@ -176,5 +187,10 @@ results(Config) when is_list(Config) ->
ok = ct_test_support:verify_events(TestEvents++TestEvents, Events, Config).
+event_mgrs(_) ->
+ ?CT_EVMGR_REF = ct:get_event_mgr_ref(),
+ ?CT_MEVMGR_REF = ct_master:get_event_mgr_ref().
+
+
%%%-----------------------------------------------------------------
%%% HELP FUNCTIONS
diff --git a/lib/common_test/test/ct_event_handler_SUITE_data/event_handling_1/test/eh_11_SUITE.erl b/lib/common_test/test/ct_event_handler_SUITE_data/event_handling_1/test/eh_11_SUITE.erl
index 16b7129993..14ea12d579 100644
--- a/lib/common_test/test/ct_event_handler_SUITE_data/event_handling_1/test/eh_11_SUITE.erl
+++ b/lib/common_test/test/ct_event_handler_SUITE_data/event_handling_1/test/eh_11_SUITE.erl
@@ -32,100 +32,36 @@
%% COMMON TEST CALLBACK FUNCTIONS
%%--------------------------------------------------------------------
-%%--------------------------------------------------------------------
-%% Function: suite() -> Info
-%%
-%% Info = [tuple()]
-%% List of key/value pairs.
-%%
-%% Description: Returns list of tuples to set default properties
-%% for the suite.
-%%
-%% Note: The suite/0 function is only meant to be used to return
-%% default data values, not perform any other operations.
-%%--------------------------------------------------------------------
suite() ->
[
{timetrap,{seconds,10}}
].
-%%--------------------------------------------------------------------
-%% Function: init_per_suite(Config0) ->
-%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
-%%
-%% Config0 = Config1 = [tuple()]
-%% A list of key/value pairs, holding the test case configuration.
-%% Reason = term()
-%% The reason for skipping the suite.
-%%
-%% Description: Initialization before the suite.
-%%
-%% Note: This function is free to add any key/value pairs to the Config
-%% variable, but should NOT alter/remove any existing entries.
-%%--------------------------------------------------------------------
init_per_suite(Config) ->
Config.
-%%--------------------------------------------------------------------
-%% Function: end_per_suite(Config0) -> void() | {save_config,Config1}
-%%
-%% Config0 = Config1 = [tuple()]
-%% A list of key/value pairs, holding the test case configuration.
-%%
-%% Description: Cleanup after the suite.
-%%--------------------------------------------------------------------
end_per_suite(_Config) ->
- ok.
+ %% should report ok as result to event handler
+ done.
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ %% should report ok as result to event handler
+ void.
-%%--------------------------------------------------------------------
-%% Function: init_per_testcase(TestCase, Config0) ->
-%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
-%%
-%% TestCase = atom()
-%% Name of the test case that is about to run.
-%% Config0 = Config1 = [tuple()]
-%% A list of key/value pairs, holding the test case configuration.
-%% Reason = term()
-%% The reason for skipping the test case.
-%%
-%% Description: Initialization before each test case.
-%%
-%% Note: This function is free to add any key/value pairs to the Config
-%% variable, but should NOT alter/remove any existing entries.
-%%--------------------------------------------------------------------
init_per_testcase(_TestCase, Config) ->
Config.
-%%--------------------------------------------------------------------
-%% Function: end_per_testcase(TestCase, Config0) ->
-%% void() | {save_config,Config1}
-%%
-%% TestCase = atom()
-%% Name of the test case that is finished.
-%% Config0 = Config1 = [tuple()]
-%% A list of key/value pairs, holding the test case configuration.
-%%
-%% Description: Cleanup after each test case.
-%%--------------------------------------------------------------------
end_per_testcase(_TestCase, _Config) ->
- ok.
+ true.
-%%--------------------------------------------------------------------
-%% Function: all() -> TestCases | {skip,Reason}
-%%
-%% TestCases = [TestCase | {sequence,SeqName}]
-%% TestCase = atom()
-%% Name of a test case.
-%% SeqName = atom()
-%% Name of a test case sequence.
-%% Reason = term()
-%% The reason for skipping all test cases.
-%%
-%% Description: Returns the list of test cases that are to be executed.
-%%--------------------------------------------------------------------
-all() ->
- [tc1, tc2, tc3].
+groups() ->
+ [{g1, [], [tc1, tc2, tc3, tc4, tc5]}].
+all() ->
+ [{group,g1}].
%%--------------------------------------------------------------------
%% TEST CASES
@@ -134,8 +70,15 @@ all() ->
tc1(_Config) ->
ok.
-tc2(_Config) ->
- {skip,"Skipped"}.
+tc2(_Config) ->
+ %% should report ok as result to event handler
+ 42.
+
+tc3(_Config) ->
+ {skip,"Skip"}.
+
+tc4(_Config) ->
+ {skipped,"Skipped"}.
-tc3(_Config) ->
+tc5(_Config) ->
exit('Failing').
diff --git a/lib/common_test/test/ct_gen_conn_SUITE_data/conn_SUITE.erl b/lib/common_test/test/ct_gen_conn_SUITE_data/conn_SUITE.erl
index 1344878675..96dd80e4e8 100644
--- a/lib/common_test/test/ct_gen_conn_SUITE_data/conn_SUITE.erl
+++ b/lib/common_test/test/ct_gen_conn_SUITE_data/conn_SUITE.erl
@@ -73,23 +73,23 @@ handles_to_multi_conn_pids(_Config) ->
{true,true} = {is_process_alive(Handle3),is_process_alive(ConnPid3)},
ok = proto:close(Handle1),
- timer:sleep(100),
+ ct:sleep(100),
{false,false} = {is_process_alive(Handle1),is_process_alive(ConnPid1)},
{true,true} = {is_process_alive(Handle2),is_process_alive(ConnPid2)},
ok = proto:kill_conn_proc(Handle2),
- timer:sleep(100),
+ ct:sleep(100),
{true,false} = {is_process_alive(Handle2),is_process_alive(ConnPid2)},
ConnPid2x = ct_gen_conn:get_conn_pid(Handle2),
true = is_process_alive(ConnPid2x),
ok = proto:close(Handle2),
- timer:sleep(100),
+ ct:sleep(100),
{false,false} = {is_process_alive(Handle2),is_process_alive(ConnPid2x)},
application:set_env(ct_test, reconnect, false),
ok = proto:kill_conn_proc(Handle3),
- timer:sleep(100),
+ ct:sleep(100),
{false,false} = {is_process_alive(Handle3),is_process_alive(ConnPid3)},
ok.
@@ -116,23 +116,23 @@ handles_to_single_conn_pids(_Config) ->
ct:pal("CONNS = ~n~p", [Conns]),
ok = proto:close(Handle1),
- timer:sleep(100),
+ ct:sleep(100),
{false,true} = {is_process_alive(Handle1),is_process_alive(ConnPid)},
ok = proto:kill_conn_proc(Handle2),
- timer:sleep(100),
+ ct:sleep(100),
NewConnPid = ct_gen_conn:get_conn_pid(Handle2),
NewConnPid = ct_gen_conn:get_conn_pid(Handle3),
true = is_process_alive(Handle2),
true = is_process_alive(Handle3),
ok = proto:close(Handle2),
- timer:sleep(100),
+ ct:sleep(100),
{false,true} = {is_process_alive(Handle2),is_process_alive(NewConnPid)},
application:set_env(ct_test, reconnect, false),
ok = proto:kill_conn_proc(Handle3),
- timer:sleep(100),
+ ct:sleep(100),
{false,false} = {is_process_alive(Handle3),is_process_alive(NewConnPid)},
ok.
@@ -158,29 +158,29 @@ names_to_multi_conn_pids(_Config) ->
Handle1 = proto:open(mconn1),
ok = proto:close(mconn1),
- timer:sleep(100),
+ ct:sleep(100),
{false,false} = {is_process_alive(Handle1),is_process_alive(ConnPid1)},
ok = proto:kill_conn_proc(Handle2),
- timer:sleep(100),
+ ct:sleep(100),
Handle2 = proto:open(mconn2), % should've been reconnected already
{true,false} = {is_process_alive(Handle2),is_process_alive(ConnPid2)},
ConnPid2x = ct_gen_conn:get_conn_pid(Handle2),
true = is_process_alive(ConnPid2x),
ok = proto:close(mconn2),
- timer:sleep(100),
+ ct:sleep(100),
{false,false} = {is_process_alive(Handle2),is_process_alive(ConnPid2x)},
Handle2y = proto:open(mconn2),
ConnPid2y = ct_gen_conn:get_conn_pid(Handle2y),
{true,true} = {is_process_alive(Handle2y),is_process_alive(ConnPid2y)},
ok = proto:close(mconn2),
- timer:sleep(100),
+ ct:sleep(100),
{false,false} = {is_process_alive(Handle2y),is_process_alive(ConnPid2y)},
application:set_env(ct_test, reconnect, false),
ok = proto:kill_conn_proc(Handle3),
- timer:sleep(100),
+ ct:sleep(100),
{false,false} = {is_process_alive(Handle3),is_process_alive(ConnPid3)},
ok.
@@ -211,11 +211,11 @@ names_to_single_conn_pids(_Config) ->
ct:pal("CONNS on ~p = ~n~p", [ConnPid,Conns]),
ok = proto:close(sconn1),
- timer:sleep(100),
+ ct:sleep(100),
{false,true} = {is_process_alive(Handle1),is_process_alive(ConnPid)},
ok = proto:kill_conn_proc(Handle2),
- timer:sleep(100),
+ ct:sleep(100),
{true,false} = {is_process_alive(Handle2),is_process_alive(ConnPid)},
Handle2 = proto:open(sconn2), % should've been reconnected already
NewConnPid = ct_gen_conn:get_conn_pid(Handle2),
@@ -227,12 +227,12 @@ names_to_single_conn_pids(_Config) ->
ct:pal("CONNS on ~p = ~n~p", [NewConnPid,Conns1]),
ok = proto:close(sconn2),
- timer:sleep(100),
+ ct:sleep(100),
{false,true} = {is_process_alive(Handle2),is_process_alive(NewConnPid)},
application:set_env(ct_test, reconnect, false),
ok = proto:kill_conn_proc(Handle3),
- timer:sleep(100),
+ ct:sleep(100),
{false,false} = {is_process_alive(Handle3),is_process_alive(NewConnPid)},
ok.
diff --git a/lib/common_test/test/ct_group_leader_SUITE_data/group_leader_SUITE.erl b/lib/common_test/test/ct_group_leader_SUITE_data/group_leader_SUITE.erl
index 804f722081..bfdc78639e 100644
--- a/lib/common_test/test/ct_group_leader_SUITE_data/group_leader_SUITE.erl
+++ b/lib/common_test/test/ct_group_leader_SUITE_data/group_leader_SUITE.erl
@@ -258,14 +258,14 @@ gen_io(Label, N, Acc) ->
%% (via ct logging functions) from an external process which has a
%% different group leader than the test cases.
unexp1(Config) ->
- timer:sleep(1000),
+ ct:sleep(1000),
gen_unexp_io(),
- timer:sleep(1000),
+ ct:sleep(1000),
check_unexp_io(Config),
ok.
unexp2(_) ->
- timer:sleep(2000),
+ ct:sleep(2000),
ok.
gen_unexp_io() ->
diff --git a/lib/common_test/test/ct_groups_test_1_SUITE.erl b/lib/common_test/test/ct_groups_test_1_SUITE.erl
index e520a72227..d5de949554 100644
--- a/lib/common_test/test/ct_groups_test_1_SUITE.erl
+++ b/lib/common_test/test/ct_groups_test_1_SUITE.erl
@@ -302,7 +302,7 @@ test_events(groups_suite_1) ->
{?eh,tc_done,{groups_11_SUITE,{end_per_group,test_group_4,[]},ok}}],
{?eh,tc_start,{groups_11_SUITE,end_per_suite}},
- {?eh,tc_done,{groups_11_SUITE,end_per_suite,init}},
+ {?eh,tc_done,{groups_11_SUITE,end_per_suite,ok}},
{?eh,test_done,{'DEF','STOP_TIME'}},
{?eh,stop_logging,[]}];
@@ -410,7 +410,7 @@ test_events(groups_suite_2) ->
{?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_4,[]},ok}}],
{?eh,tc_start,{groups_12_SUITE,end_per_suite}},
- {?eh,tc_done,{groups_12_SUITE,end_per_suite,init}},
+ {?eh,tc_done,{groups_12_SUITE,end_per_suite,ok}},
{?eh,test_done,{'DEF','STOP_TIME'}},
{?eh,stop_logging,[]}];
@@ -505,7 +505,7 @@ test_events(groups_suites_1) ->
{?eh,tc_done,{groups_11_SUITE,{end_per_group,test_group_4,[]},ok}}],
{?eh,tc_start,{groups_11_SUITE,end_per_suite}},
- {?eh,tc_done,{groups_11_SUITE,end_per_suite,init}},
+ {?eh,tc_done,{groups_11_SUITE,end_per_suite,ok}},
{?eh,tc_start,{groups_12_SUITE,init_per_suite}},
{?eh,tc_done,{groups_12_SUITE,init_per_suite,ok}},
@@ -596,7 +596,7 @@ test_events(groups_suites_1) ->
{?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_4,[]},ok}}],
{?eh,tc_start,{groups_12_SUITE,end_per_suite}},
- {?eh,tc_done,{groups_12_SUITE,end_per_suite,init}},
+ {?eh,tc_done,{groups_12_SUITE,end_per_suite,ok}},
{?eh,test_done,{'DEF','STOP_TIME'}},
{?eh,stop_logging,[]}];
@@ -691,7 +691,7 @@ test_events(groups_dir_1) ->
{?eh,tc_done,{groups_11_SUITE,{end_per_group,test_group_4,[]},ok}}],
{?eh,tc_start,{groups_11_SUITE,end_per_suite}},
- {?eh,tc_done,{groups_11_SUITE,end_per_suite,init}},
+ {?eh,tc_done,{groups_11_SUITE,end_per_suite,ok}},
{?eh,tc_start,{groups_12_SUITE,init_per_suite}},
{?eh,tc_done,{groups_12_SUITE,init_per_suite,ok}},
@@ -782,7 +782,7 @@ test_events(groups_dir_1) ->
{?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_4,[]},ok}}],
{?eh,tc_start,{groups_12_SUITE,end_per_suite}},
- {?eh,tc_done,{groups_12_SUITE,end_per_suite,init}},
+ {?eh,tc_done,{groups_12_SUITE,end_per_suite,ok}},
{?eh,test_done,{'DEF','STOP_TIME'}},
{?eh,stop_logging,[]}];
@@ -878,7 +878,7 @@ test_events(groups_dirs_1) ->
{?eh,tc_done,{groups_11_SUITE,{end_per_group,test_group_4,[]},ok}}],
{?eh,tc_start,{groups_11_SUITE,end_per_suite}},
- {?eh,tc_done,{groups_11_SUITE,end_per_suite,init}},
+ {?eh,tc_done,{groups_11_SUITE,end_per_suite,ok}},
{?eh,tc_start,{groups_12_SUITE,init_per_suite}},
{?eh,tc_done,{groups_12_SUITE,init_per_suite,ok}},
@@ -969,7 +969,7 @@ test_events(groups_dirs_1) ->
{?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_4,[]},ok}}],
{?eh,tc_start,{groups_12_SUITE,end_per_suite}},
- {?eh,tc_done,{groups_12_SUITE,end_per_suite,init}},
+ {?eh,tc_done,{groups_12_SUITE,end_per_suite,ok}},
{?eh,tc_start,{groups_21_SUITE,init_per_suite}},
{?eh,tc_done,{groups_21_SUITE,init_per_suite,ok}},
@@ -1089,7 +1089,7 @@ test_events(groups_dirs_1) ->
{groups_21_SUITE,{end_per_group,test_group_4,[]},ok}}],
{?eh,tc_start,{groups_21_SUITE,end_per_suite}},
- {?eh,tc_done,{groups_21_SUITE,end_per_suite,init}},
+ {?eh,tc_done,{groups_21_SUITE,end_per_suite,ok}},
{?eh,tc_start,{groups_22_SUITE,init_per_suite}},
{?eh,tc_done,{groups_22_SUITE,init_per_suite,ok}},
@@ -1223,6 +1223,6 @@ test_events(groups_dirs_1) ->
{groups_22_SUITE,{end_per_group,test_group_4,[]},ok}}],
{?eh,tc_start,{groups_22_SUITE,end_per_suite}},
- {?eh,tc_done,{groups_22_SUITE,end_per_suite,init}},
+ {?eh,tc_done,{groups_22_SUITE,end_per_suite,ok}},
{?eh,test_done,{'DEF','STOP_TIME'}},
{?eh,stop_logging,[]}].
diff --git a/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_1/test/groups_12_SUITE.erl b/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_1/test/groups_12_SUITE.erl
index ec90ef95d1..6f49f9a957 100644
--- a/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_1/test/groups_12_SUITE.erl
+++ b/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_1/test/groups_12_SUITE.erl
@@ -278,7 +278,7 @@ testcase_5a(Config) ->
%% increase chance the done event will come
%% during execution of subgroup (could be
%% tricky to handle)
- timer:sleep(3),
+ ct:sleep(3),
ok.
testcase_5b() ->
[].
diff --git a/lib/common_test/test/ct_groups_test_2_SUITE.erl b/lib/common_test/test/ct_groups_test_2_SUITE.erl
index 8b0de98709..f41395e028 100644
--- a/lib/common_test/test/ct_groups_test_2_SUITE.erl
+++ b/lib/common_test/test/ct_groups_test_2_SUITE.erl
@@ -302,7 +302,7 @@ test_events(empty_group) ->
{?eh,tc_done,
{groups_22_SUITE,{end_per_group,test_group_8,[]},ok}}],
{?eh,tc_start,{groups_22_SUITE,end_per_suite}},
- {?eh,tc_done,{groups_22_SUITE,end_per_suite,init}},
+ {?eh,tc_done,{groups_22_SUITE,end_per_suite,ok}},
{?eh,test_done,{'DEF','STOP_TIME'}},
{?eh,stop_logging,[]}
].
diff --git a/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_22_SUITE.erl b/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_22_SUITE.erl
index 154c676d7e..80bb5ba69b 100644
--- a/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_22_SUITE.erl
+++ b/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_22_SUITE.erl
@@ -293,7 +293,7 @@ testcase_5a(Config) ->
%% increase chance the done event will come
%% during execution of subgroup (could be
%% tricky to handle)
- timer:sleep(3),
+ ct:sleep(3),
ok.
testcase_5b() ->
[].
diff --git a/lib/common_test/test/ct_hooks_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE.erl
index c8fc4bd59b..d5ad8312e6 100644
--- a/lib/common_test/test/ct_hooks_SUITE.erl
+++ b/lib/common_test/test/ct_hooks_SUITE.erl
@@ -1075,7 +1075,37 @@ test_events(fail_n_skip_with_minimal_cth) ->
{?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
{?eh,cth,{'_',init,['_',[]]}},
{?eh,tc_start,{'_',init_per_suite}},
-
+
+ {parallel,
+ [{?eh,tc_start,{ct_cth_fail_one_skip_one_SUITE,{init_per_group,
+ group1,[parallel]}}},
+ {?eh,tc_done,{ct_cth_fail_one_skip_one_SUITE,{init_per_group,
+ group1,[parallel]},ok}},
+ {parallel,
+ [{?eh,tc_start,{ct_cth_fail_one_skip_one_SUITE,{init_per_group,
+ group2,[parallel]}}},
+ {?eh,tc_done,{ct_cth_fail_one_skip_one_SUITE,{init_per_group,
+ group2,[parallel]},ok}},
+ %% Verify that 'skip' as well as 'skipped' works
+ {?eh,tc_start,{ct_cth_fail_one_skip_one_SUITE,test_case2}},
+ {?eh,tc_done,{ct_cth_fail_one_skip_one_SUITE,test_case2,{skipped,"skip it"}}},
+ {?eh,tc_start,{ct_cth_fail_one_skip_one_SUITE,test_case3}},
+ {?eh,tc_done,{ct_cth_fail_one_skip_one_SUITE,test_case3,{skipped,"skip it"}}},
+ {?eh,cth,{empty_cth,on_tc_skip,[{test_case2,group2},
+ {tc_user_skip,{skipped,"skip it"}},
+ []]}},
+ {?eh,cth,{empty_cth,on_tc_skip,[{test_case3,group2},
+ {tc_user_skip,{skipped,"skip it"}},
+ []]}},
+ {?eh,tc_start,{ct_cth_fail_one_skip_one_SUITE,{end_per_group,
+ group2,[parallel]}}},
+ {?eh,tc_done,{ct_cth_fail_one_skip_one_SUITE,{end_per_group,group2,
+ [parallel]},ok}}]},
+ {?eh,tc_start,{ct_cth_fail_one_skip_one_SUITE,{end_per_group,
+ group1,[parallel]}}},
+ {?eh,tc_done,{ct_cth_fail_one_skip_one_SUITE,{end_per_group,
+ group1,[parallel]},ok}}]},
+
{?eh,tc_done,{'_',end_per_suite,ok}},
{?eh,cth,{'_',terminate,[[]]}},
{?eh,stop_logging,[]}
diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_cth_fail_one_skip_one_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_cth_fail_one_skip_one_SUITE.erl
index b2f22d8257..7b84c246ca 100644
--- a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_cth_fail_one_skip_one_SUITE.erl
+++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_cth_fail_one_skip_one_SUITE.erl
@@ -41,6 +41,8 @@ end_per_group(_Group,_Config) ->
init_per_testcase(test_case2, Config) ->
{skip,"skip it"};
+init_per_testcase(test_case3, Config) ->
+ {skipped,"skip it"};
init_per_testcase(_TestCase, Config) ->
Config.
@@ -48,7 +50,9 @@ end_per_testcase(_TestCase, _Config) ->
ok.
groups() ->
- [{group1,[parallel],[{group2,[parallel],[test_case1,test_case2,test_case3]}]}].
+ [{group1,[parallel],
+ [{group2,[parallel],
+ [test_case1,test_case2,test_case3,test_case4]}]}].
all() ->
[{group,group1}].
@@ -62,3 +66,6 @@ test_case2(Config) ->
test_case3(Config) ->
ok.
+
+test_case4(Config) ->
+ ok.
diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_SUITE.erl
index 18dd07e87e..80ce248418 100644
--- a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_SUITE.erl
+++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/cth_log_SUITE.erl
@@ -50,7 +50,7 @@ init_per_suite(Config) ->
end_per_suite(Config) ->
Gen = proplists:get_value(gen, Config),
exit(Gen, kill),
- timer:sleep(100),
+ ct:sleep(100),
ok.
%%--------------------------------------------------------------------
diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/empty_cth.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/empty_cth.erl
index 6caac7e447..77783fccf5 100644
--- a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/empty_cth.erl
+++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/empty_cth.erl
@@ -75,6 +75,7 @@
init(Id, Opts) ->
gen_event:notify(?CT_EVMGR_REF, #event{ name = cth, node = node(),
data = {?MODULE, init, [Id, Opts]}}),
+ ct:log("~w:init called", [?MODULE]),
{ok,Opts}.
%% @doc The ID is used to uniquly identify an CTH instance, if two CTH's
@@ -85,6 +86,7 @@ init(Id, Opts) ->
id(Opts) ->
gen_event:notify(?CT_EVMGR_REF, #event{ name = cth, node = node(),
data = {?MODULE, id, [Opts]}}),
+ ct:log("~w:id called", [?MODULE]),
now().
%% @doc Called before init_per_suite is called. Note that this callback is
@@ -100,6 +102,7 @@ pre_init_per_suite(Suite,Config,State) ->
?CT_EVMGR_REF, #event{ name = cth, node = node(),
data = {?MODULE, pre_init_per_suite,
[Suite,Config,State]}}),
+ ct:log("~w:pre_init_per_suite(~w) called", [?MODULE,Suite]),
{Config, State}.
%% @doc Called after init_per_suite.
@@ -114,6 +117,7 @@ post_init_per_suite(Suite,Config,Return,State) ->
?CT_EVMGR_REF, #event{ name = cth, node = node(),
data = {?MODULE, post_init_per_suite,
[Suite,Config,Return,State]}}),
+ ct:log("~w:post_init_per_suite(~w) called", [?MODULE,Suite]),
{Return, State}.
%% @doc Called before end_per_suite. The config/state can be changed here,
@@ -127,6 +131,7 @@ pre_end_per_suite(Suite,Config,State) ->
?CT_EVMGR_REF, #event{ name = cth, node = node(),
data = {?MODULE, pre_end_per_suite,
[Suite,Config,State]}}),
+ ct:log("~w:pre_end_per_suite(~w) called", [?MODULE,Suite]),
{Config, State}.
%% @doc Called after end_per_suite. Note that the config cannot be
@@ -141,6 +146,7 @@ post_end_per_suite(Suite,Config,Return,State) ->
?CT_EVMGR_REF, #event{ name = cth, node = node(),
data = {?MODULE, post_end_per_suite,
[Suite,Config,Return,State]}}),
+ ct:log("~w:post_end_per_suite(~w) called", [?MODULE,Suite]),
{Return, State}.
%% @doc Called before each init_per_group.
@@ -154,6 +160,7 @@ pre_init_per_group(Group,Config,State) ->
?CT_EVMGR_REF, #event{ name = cth, node = node(),
data = {?MODULE, pre_init_per_group,
[Group,Config,State]}}),
+ ct:log("~w:pre_init_per_group(~w) called", [?MODULE,Group]),
{Config, State}.
%% @doc Called after each init_per_group.
@@ -168,6 +175,7 @@ post_init_per_group(Group,Config,Return,State) ->
?CT_EVMGR_REF, #event{ name = cth, node = node(),
data = {?MODULE, post_init_per_group,
[Group,Config,Return,State]}}),
+ ct:log("~w:post_init_per_group(~w) called", [?MODULE,Group]),
{Return, State}.
%% @doc Called after each end_per_group. The config/state can be changed here,
@@ -181,6 +189,7 @@ pre_end_per_group(Group,Config,State) ->
?CT_EVMGR_REF, #event{ name = cth, node = node(),
data = {?MODULE, pre_end_per_group,
[Group,Config,State]}}),
+ ct:log("~w:pre_end_per_group(~w) called", [?MODULE,Group]),
{Config, State}.
%% @doc Called after each end_per_group. Note that the config cannot be
@@ -195,6 +204,7 @@ post_end_per_group(Group,Config,Return,State) ->
?CT_EVMGR_REF, #event{ name = cth, node = node(),
data = {?MODULE, post_end_per_group,
[Group,Config,Return,State]}}),
+ ct:log("~w:post_end_per_group(~w) called", [?MODULE,Group]),
{Return, State}.
%% @doc Called before each test case.
@@ -208,6 +218,7 @@ pre_init_per_testcase(TC,Config,State) ->
?CT_EVMGR_REF, #event{ name = cth, node = node(),
data = {?MODULE, pre_init_per_testcase,
[TC,Config,State]}}),
+ ct:log("~w:pre_init_per_testcase(~w) called", [?MODULE,TC]),
{Config, State}.
%% @doc Called after each test case. Note that the config cannot be
@@ -222,6 +233,7 @@ post_end_per_testcase(TC,Config,Return,State) ->
?CT_EVMGR_REF, #event{ name = cth, node = node(),
data = {?MODULE, post_end_per_testcase,
[TC,Config,Return,State]}}),
+ ct:log("~w:post_end_per_testcase(~w) called", [?MODULE,TC]),
{Return, State}.
%% @doc Called after post_init_per_suite, post_end_per_suite, post_init_per_group,
@@ -237,6 +249,7 @@ on_tc_fail(TC, Reason, State) ->
?CT_EVMGR_REF, #event{ name = cth, node = node(),
data = {?MODULE, on_tc_fail,
[TC,Reason,State]}}),
+ ct:log("~w:on_tc_fail(~w) called", [?MODULE,TC]),
State.
%% @doc Called when a test case is skipped by either user action
@@ -253,6 +266,7 @@ on_tc_skip(TC, Reason, State) ->
?CT_EVMGR_REF, #event{ name = cth, node = node(),
data = {?MODULE, on_tc_skip,
[TC,Reason,State]}}),
+ ct:log("~w:on_tc_skip(~w) called", [?MODULE,TC]),
State.
%% @doc Called when the scope of the CTH is done, this depends on
@@ -274,4 +288,5 @@ terminate(State) ->
gen_event:notify(
?CT_EVMGR_REF, #event{ name = cth, node = node(),
data = {?MODULE, terminate, [State]}}),
+ ct:log("~w:terminate called", [?MODULE]),
ok.
diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/minimal_terminate_cth.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/minimal_terminate_cth.erl
index 30721a6b3a..436470f46d 100644
--- a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/minimal_terminate_cth.erl
+++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/minimal_terminate_cth.erl
@@ -28,10 +28,14 @@
%% CT Hooks
-export([init/2]).
-export([terminate/1]).
+-export([on_tc_skip/3]).
init(Id, Opts) ->
empty_cth:init(Id, Opts).
+on_tc_skip(TC, Reason, State) ->
+ empty_cth:on_tc_skip(TC,Reason,State).
+
terminate(State) ->
empty_cth:terminate(State).
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
index 2bcfeeec0c..a145d85b1d 100644
--- a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
@@ -218,7 +218,7 @@ hello_required_exists(Config) ->
?NS:expect_do_reply('close-session',close,ok),
?ok = ct_netconfc:close_session(my_named_connection),
- timer:sleep(500),
+ ct:sleep(500),
%% Then check that it can be used again after the first is closed
{ok,_Client2} = open_configured_success(my_named_connection,DataDir),
@@ -488,8 +488,15 @@ action(Config) ->
DataDir = ?config(data_dir,Config),
{ok,Client} = open_success(DataDir),
Data = [{myactionreturn,[{xmlns,"myns"}],["value"]}],
- ?NS:expect_reply(action,{data,Data}),
- {ok,Data} = ct_netconfc:action(Client,{myaction,[{xmlns,"myns"}],[]}),
+ %% test either to receive {data,Data} or {ok,Data},
+ %% both need to be handled
+ {Reply,RetVal} = case element(3, now()) rem 2 of
+ 0 -> {{data,Data},{ok,Data}};
+ 1 -> {{ok,Data},ok}
+ end,
+ ct:log("Client will receive {~w,Data}", [element(1,Reply)]),
+ ?NS:expect_reply(action,Reply),
+ RetVal = ct_netconfc:action(Client,{myaction,[{xmlns,"myns"}],[]}),
?NS:expect_do_reply('close-session',close,ok),
?ok = ct_netconfc:close_session(Client),
ok.
@@ -656,10 +663,10 @@ receive_chunked_data(Config) ->
%% Spawn a process which will wait a bit for the client to send
%% the request (below), then order the server to the chunks of the
%% rpc-reply one by one.
- spawn(fun() -> timer:sleep(500),?NS:hupp(send,Part1),
- timer:sleep(100),?NS:hupp(send,Part2),
- timer:sleep(100),?NS:hupp(send,Part3),
- timer:sleep(100),?NS:hupp(send,Part4)
+ spawn(fun() -> ct:sleep(500),?NS:hupp(send,Part1),
+ ct:sleep(100),?NS:hupp(send,Part2),
+ ct:sleep(100),?NS:hupp(send,Part3),
+ ct:sleep(100),?NS:hupp(send,Part4)
end),
%% Order server to expect a get - then the process above will make
@@ -704,8 +711,8 @@ timeout_receive_chunked_data(Config) ->
%% Spawn a process which will wait a bit for the client to send
%% the request (below), then order the server to the chunks of the
%% rpc-reply one by one.
- spawn(fun() -> timer:sleep(500),?NS:hupp(send,Part1),
- timer:sleep(100),?NS:hupp(send,Part2)
+ spawn(fun() -> ct:sleep(500),?NS:hupp(send,Part1),
+ ct:sleep(100),?NS:hupp(send,Part2)
end),
%% Order server to expect a get - then the process above will make
@@ -750,9 +757,9 @@ close_while_waiting_for_chunked_data(Config) ->
%% Spawn a process which will wait a bit for the client to send
%% the request (below), then order the server to the chunks of the
%% rpc-reply one by one.
- spawn(fun() -> timer:sleep(500),?NS:hupp(send,Part1),
- timer:sleep(100),?NS:hupp(send,Part2),
- timer:sleep(100),?NS:hupp(kill)
+ spawn(fun() -> ct:sleep(500),?NS:hupp(send,Part1),
+ ct:sleep(100),?NS:hupp(send,Part2),
+ ct:sleep(100),?NS:hupp(kill)
end),
%% Order server to expect a get - then the process above will make
@@ -768,7 +775,7 @@ connection_crash(Config) ->
%% Test that if the test survives killing the connection
%% process. Earlier this caused ct_util_server to terminate, and
%% this aborting the complete test run.
- spawn(fun() -> timer:sleep(500),exit(Client,kill) end),
+ spawn(fun() -> ct:sleep(500),exit(Client,kill) end),
?NS:expect(get),
{error,{closed,killed}}=ct_netconfc:get(Client,{server,[{xmlns,"myns"}],[]}),
ok.
diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl b/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl
index fb0734d48e..27da67bd1d 100644
--- a/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl
+++ b/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl
@@ -351,7 +351,7 @@ check_expected(SessionId,ConnRef,Msg) ->
do(ConnRef, Do),
reply(ConnRef,Reply);
error ->
- timer:sleep(1000),
+ ct:sleep(1000),
exit({error,{got_unexpected,SessionId,Msg,ets:tab2list(ns_tab)}})
end.
@@ -540,8 +540,13 @@ make_msg({hello,SessionId,Stuff}) ->
SessionIdXml/binary,"</hello>">>);
make_msg(ok) ->
xml(rpc_reply("<ok/>"));
+
+make_msg({ok,Data}) ->
+ xml(rpc_reply(from_simple({ok,Data})));
+
make_msg({data,Data}) ->
xml(rpc_reply(from_simple({data,Data})));
+
make_msg(event) ->
xml(<<"<notification xmlns=\"",?NETCONF_NOTIF_NAMESPACE,"\">"
"<eventTime>2012-06-14T14:50:54+02:00</eventTime>"
diff --git a/lib/common_test/test/ct_pre_post_test_io_SUITE.erl b/lib/common_test/test/ct_pre_post_test_io_SUITE.erl
index 5de1ecc2bd..1e6018f442 100644
--- a/lib/common_test/test/ct_pre_post_test_io_SUITE.erl
+++ b/lib/common_test/test/ct_pre_post_test_io_SUITE.erl
@@ -91,27 +91,27 @@ pre_post_io(Config) ->
spawn(fun() ->
ct:pal("CONTROLLER: Started!", []),
%% --- test run 1 ---
- timer:sleep(3000),
+ ct:sleep(3000),
ct:pal("CONTROLLER: Handle remote events = true", []),
ok = ct_test_support:ct_rpc({cth_log_redirect,
handle_remote_events,
[true]}, Config),
- timer:sleep(2000),
+ ct:sleep(2000),
ct:pal("CONTROLLER: Proceeding with test run #1!", []),
ok = ct_test_support:ct_rpc({cth_ctrl,proceed,[]}, Config),
- timer:sleep(6000),
+ ct:sleep(6000),
ct:pal("CONTROLLER: Proceeding with shutdown #1!", []),
ok = ct_test_support:ct_rpc({cth_ctrl,proceed,[]}, Config),
%% --- test run 2 ---
- timer:sleep(3000),
+ ct:sleep(3000),
ct:pal("CONTROLLER: Handle remote events = true", []),
ok = ct_test_support:ct_rpc({cth_log_redirect,
handle_remote_events,
[true]}, Config),
- timer:sleep(2000),
+ ct:sleep(2000),
ct:pal("CONTROLLER: Proceeding with test run #2!", []),
ok = ct_test_support:ct_rpc({cth_ctrl,proceed,[]}, Config),
- timer:sleep(6000),
+ ct:sleep(6000),
ct:pal("CONTROLLER: Proceeding with shutdown #2!", []),
ok = ct_test_support:ct_rpc({cth_ctrl,proceed,[]}, Config)
end),
diff --git a/lib/common_test/test/ct_repeat_1_SUITE.erl b/lib/common_test/test/ct_repeat_1_SUITE.erl
index e37aeb196c..50e07608f6 100644
--- a/lib/common_test/test/ct_repeat_1_SUITE.erl
+++ b/lib/common_test/test/ct_repeat_1_SUITE.erl
@@ -220,8 +220,7 @@ test_events(repeat_cs_and_grs) ->
{?eh,test_stats,{1,1,{0,0}}},
[{?eh,tc_done,{repeat_1_SUITE,{init_per_group,gr_fail_result,[]},ok}},
{?eh,test_stats,{2,1,{0,0}}},
- {?eh,tc_done,{repeat_1_SUITE,{end_per_group,gr_fail_result,[]},
- {return_group_result,failed}}}],
+ {?eh,tc_done,{repeat_1_SUITE,{end_per_group,gr_fail_result,[]},ok}}],
{?eh,test_stats,{3,1,{0,0}}},
[{?eh,tc_done,{repeat_1_SUITE,{init_per_group,gr_fail_init,[]},
{failed,{error,fails_on_purpose}}}},
@@ -242,8 +241,7 @@ test_events(repeat_cs_and_grs) ->
{?eh,test_stats,{5,2,{0,1}}},
[{?eh,tc_done,{repeat_1_SUITE,{init_per_group,gr_fail_result,[]},ok}},
{?eh,test_stats,{6,2,{0,1}}},
- {?eh,tc_done,{repeat_1_SUITE,{end_per_group,gr_fail_result,[]},
- {return_group_result,failed}}}],
+ {?eh,tc_done,{repeat_1_SUITE,{end_per_group,gr_fail_result,[]},ok}}],
{?eh,test_stats,{7,2,{0,1}}},
[{?eh,tc_done,{repeat_1_SUITE,{init_per_group,gr_fail_init,[]},
{failed,{error,fails_on_purpose}}}},
@@ -289,8 +287,7 @@ test_events(repeat_seq) ->
{init_per_group,gr_fail_result,[]},ok}},
{?eh,test_stats,{4,2,{0,2}}},
{?eh,tc_done,{repeat_1_SUITE,
- {end_per_group,gr_fail_result,[]},
- {return_group_result,failed}}}],
+ {end_per_group,gr_fail_result,[]},ok}}],
{?eh,tc_auto_skip,{repeat_1_SUITE,{tc_ok_2,repeat_seq_2},
{group_result,gr_fail_result,failed}}},
{?eh,test_stats,{4,2,{0,3}}},
@@ -402,8 +399,7 @@ test_events(repeat_gr_until_any_ok) ->
[{?eh,tc_done,{repeat_1_SUITE,
{init_per_group,gr_fail_result,[]},ok}},
{?eh,tc_done,{repeat_1_SUITE,
- {end_per_group,gr_fail_result,[]},
- {return_group_result,failed}}}],
+ {end_per_group,gr_fail_result,[]},ok}}],
{?eh,tc_done,{repeat_1_SUITE,tc_fail_1,
{failed,{error,{{badmatch,2},'_'}}}}},
{?eh,test_stats,{1,1,{0,0}}},
@@ -418,8 +414,7 @@ test_events(repeat_gr_until_any_ok) ->
[{?eh,tc_done,{repeat_1_SUITE,
{init_per_group,gr_fail_result_then_ok,[]},ok}},
{?eh,tc_done,{repeat_1_SUITE,
- {end_per_group,gr_fail_result_then_ok,[]},
- {return_group_result,failed}}}],
+ {end_per_group,gr_fail_result_then_ok,[]},ok}}],
{?eh,tc_done,{repeat_1_SUITE,
{end_per_group,repeat_gr_until_any_ok_1,
[{repeat_until_any_ok,3}]},ok}}],
@@ -441,8 +436,7 @@ test_events(repeat_gr_until_any_ok) ->
{init_per_group,repeat_gr_until_any_ok_2,
[{repeat_until_any_ok,3}]},ok}},
[{?eh,tc_done,{repeat_1_SUITE,
- {end_per_group,gr_fail_result,[]},
- {return_group_result,failed}}}],
+ {end_per_group,gr_fail_result,[]},ok}}],
{?eh,tc_done,{repeat_1_SUITE,tc_fail_1,
{failed,{error,{{badmatch,2},'_'}}}}},
{?eh,test_stats,{5,5,{0,2}}},
@@ -675,8 +669,7 @@ test_events(repeat_gr_until_any_fail) ->
{repeat_1_SUITE,{end_per_group,gr_ok_then_fail_result,[]}}},
{?eh,tc_done,
{repeat_1_SUITE,
- {end_per_group,gr_ok_then_fail_result,[]},
- {return_group_result,failed}}}],
+ {end_per_group,gr_ok_then_fail_result,[]},ok}}],
{?eh,tc_start,{repeat_1_SUITE,tc_ok_2}},
{?eh,tc_done,{repeat_1_SUITE,tc_ok_2,ok}},
{?eh,test_stats,{8,0,{0,0}}},
@@ -938,8 +931,7 @@ test_events(repeat_gr_until_all_ok) ->
{?eh,tc_done,{repeat_1_SUITE,tc_ok_1,ok}},
{?eh,test_stats,{3,1,{0,0}}},
{?eh,tc_done,{repeat_1_SUITE,
- {end_per_group,gr_fail_result_then_ok,[]},
- {return_group_result,failed}}}],
+ {end_per_group,gr_fail_result_then_ok,[]},ok}}],
{?eh,tc_done,{repeat_1_SUITE,
{end_per_group,repeat_gr_until_all_ok_1,
[{repeat_until_all_ok,3}]},ok}}],
@@ -1113,8 +1105,7 @@ test_events(repeat_gr_until_all_fail) ->
gr_ok_then_fail_result,[]},ok}},
{?eh,test_stats,{3,3,{0,2}}},
{?eh,tc_done,{repeat_1_SUITE,
- {end_per_group,gr_ok_then_fail_result,[]},
- {return_group_result,failed}}}],
+ {end_per_group,gr_ok_then_fail_result,[]},ok}}],
{?eh,tc_done,{repeat_1_SUITE,
{end_per_group,repeat_gr_until_all_fail_1,
[{repeat_until_all_fail,2}]},ok}}],
@@ -1148,8 +1139,7 @@ test_events(repeat_gr_until_all_fail) ->
{init_per_group,repeat_gr_until_all_fail_3,
[{repeat_until_all_fail,3}]},ok}},
[{?eh,tc_done,{repeat_1_SUITE,
- {end_per_group,gr_fail_result,[]},
- {return_group_result,failed}}}],
+ {end_per_group,gr_fail_result,[]},ok}}],
{?eh,tc_done,{repeat_1_SUITE,tc_ok_then_fail_1,ok}},
{?eh,test_stats,{6,5,{0,3}}},
{?eh,tc_done,{repeat_1_SUITE,
@@ -1159,8 +1149,7 @@ test_events(repeat_gr_until_all_fail) ->
{init_per_group,repeat_gr_until_all_fail_3,
[{repeat_until_all_fail,2}]},ok}},
[{?eh,tc_done,{repeat_1_SUITE,
- {end_per_group,gr_fail_result,[]},
- {return_group_result,failed}}}],
+ {end_per_group,gr_fail_result,[]},ok}}],
{?eh,tc_done,{repeat_1_SUITE,tc_ok_then_fail_1,
{failed,{error,failing_this_time}}}},
{?eh,test_stats,{7,6,{0,3}}},
@@ -1263,8 +1252,7 @@ test_events(repeat_seq_until_any_fail) ->
{init_per_group,repeat_seq_until_any_fail_4,
[{repeat_until_any_fail,2},sequence]},ok}},
[{?eh,tc_done,{repeat_1_SUITE,
- {end_per_group,gr_ok_then_fail_result,[]},
- {return_group_result,failed}}}],
+ {end_per_group,gr_ok_then_fail_result,[]},ok}}],
{?eh,tc_auto_skip,{repeat_1_SUITE,{tc_ok_1,gr_ok_1},
{group_result,gr_ok_then_fail_result,failed}}},
{?eh,test_stats,{19,1,{0,3}}},
@@ -1473,8 +1461,7 @@ test_events(repeat_shuffled_seq_until_any_fail) ->
[{?eh,tc_start,{repeat_1_SUITE,
{end_per_group,gr_ok_then_fail_result,[]}}},
{?eh,tc_done,{repeat_1_SUITE,
- {end_per_group,gr_ok_then_fail_result,[]},
- {return_group_result,failed}}}],
+ {end_per_group,gr_ok_then_fail_result,[]},ok}}],
{?eh,tc_start,{repeat_1_SUITE,
{end_per_group,repeat_shuffled_seq_until_any_fail_4,
[{shuffle,repeated},{repeat_until_any_fail,2},sequence]}}},
diff --git a/lib/common_test/test/ct_repeat_testrun_SUITE_data/a_test/r1_SUITE.erl b/lib/common_test/test/ct_repeat_testrun_SUITE_data/a_test/r1_SUITE.erl
index 3fd5943691..3d7049a9c4 100644
--- a/lib/common_test/test/ct_repeat_testrun_SUITE_data/a_test/r1_SUITE.erl
+++ b/lib/common_test/test/ct_repeat_testrun_SUITE_data/a_test/r1_SUITE.erl
@@ -68,7 +68,7 @@ end_per_testcase(_Case, Config) ->
%%%-----------------------------------------------------------------
%%% Test cases
tc1(_Config) ->
- timer:sleep(10000),
+ ct:sleep(10000),
ok.
tc2(_Config) ->
diff --git a/lib/common_test/test/ct_repeat_testrun_SUITE_data/b_test/r2_SUITE.erl b/lib/common_test/test/ct_repeat_testrun_SUITE_data/b_test/r2_SUITE.erl
index dc9abc2863..e4f6e7dcc1 100644
--- a/lib/common_test/test/ct_repeat_testrun_SUITE_data/b_test/r2_SUITE.erl
+++ b/lib/common_test/test/ct_repeat_testrun_SUITE_data/b_test/r2_SUITE.erl
@@ -68,7 +68,7 @@ end_per_testcase(_Case, Config) ->
%%%-----------------------------------------------------------------
%%% Test cases
tc1(_Config) ->
- %% timer:sleep(3000),
+ %% ct:sleep(3000),
ok.
tc2(_Config) ->
diff --git a/lib/common_test/test/ct_sequence_1_SUITE.erl b/lib/common_test/test/ct_sequence_1_SUITE.erl
index 5a775a1117..4055cd789e 100644
--- a/lib/common_test/test/ct_sequence_1_SUITE.erl
+++ b/lib/common_test/test/ct_sequence_1_SUITE.erl
@@ -182,8 +182,7 @@ test_events(subgroup_return_fail) ->
{?eh,test_stats,{0,1,{0,0}}},
{?eh,tc_start,
{subgroups_1_SUITE,{end_per_group,return_fail,[]}}},
- {?eh,tc_done,{subgroups_1_SUITE,{end_per_group,return_fail,[]},
- {return_group_result,failed}}}],
+ {?eh,tc_done,{subgroups_1_SUITE,{end_per_group,return_fail,[]},ok}}],
{?eh,tc_auto_skip,
{subgroups_1_SUITE,{ok_tc,ok_group},
{group_result,return_fail,failed}}},
@@ -191,8 +190,7 @@ test_events(subgroup_return_fail) ->
{?eh,tc_start,
{subgroups_1_SUITE,{end_per_group,subgroup_return_fail,[sequence]}}},
{?eh,tc_done,
- {subgroups_1_SUITE,{end_per_group,subgroup_return_fail,[sequence]},
- {return_group_result,failed}}}],
+ {subgroups_1_SUITE,{end_per_group,subgroup_return_fail,[sequence]},ok}}],
{?eh,test_done,{'DEF','STOP_TIME'}},
{?eh,stop_logging,[]}
];
@@ -221,8 +219,7 @@ test_events(subgroup_init_fail) ->
{?eh,test_stats,{0,0,{0,2}}},
{?eh,tc_start,{subgroups_1_SUITE,{end_per_group,subgroup_init_fail,[sequence]}}},
{?eh,tc_done,{subgroups_1_SUITE,
- {end_per_group,subgroup_init_fail,[sequence]},
- {return_group_result,failed}}}],
+ {end_per_group,subgroup_init_fail,[sequence]},ok}}],
{?eh,test_done,{'DEF','STOP_TIME'}},
{?eh,stop_logging,[]}
];
@@ -245,8 +242,7 @@ test_events(subgroup_after_failed_case) ->
{?eh,tc_start,{subgroups_1_SUITE,
{end_per_group,subgroup_after_failed_case,[sequence]}}},
{?eh,tc_done,{subgroups_1_SUITE,
- {end_per_group,subgroup_after_failed_case,[sequence]},
- {return_group_result,failed}}}],
+ {end_per_group,subgroup_after_failed_case,[sequence]},ok}}],
{?eh,test_done,{'DEF','STOP_TIME'}},
{?eh,stop_logging,[]}
];
@@ -266,16 +262,14 @@ test_events(case_after_subgroup_return_fail) ->
{?eh,tc_done,{subgroups_1_SUITE,failing_tc,{failed,{error,{{badmatch,3},'_'}}}}},
{?eh,test_stats,{0,1,{0,0}}},
{?eh,tc_start,{subgroups_1_SUITE,{end_per_group,return_fail,[]}}},
- {?eh,tc_done,{subgroups_1_SUITE,{end_per_group,return_fail,[]},
- {return_group_result,failed}}}],
+ {?eh,tc_done,{subgroups_1_SUITE,{end_per_group,return_fail,[]},ok}}],
{?eh,tc_auto_skip,{subgroups_1_SUITE,{ok_tc,case_after_subgroup_return_fail},
{group_result,return_fail,failed}}},
{?eh,test_stats,{0,1,{0,1}}},
{?eh,tc_start,{subgroups_1_SUITE,
{end_per_group,case_after_subgroup_return_fail,[sequence]}}},
{?eh,tc_done,{subgroups_1_SUITE,
- {end_per_group,case_after_subgroup_return_fail,[sequence]},
- {return_group_result,failed}}}],
+ {end_per_group,case_after_subgroup_return_fail,[sequence]},ok}}],
{?eh,test_done,{'DEF','STOP_TIME'}},
{?eh,stop_logging,[]}
];
@@ -310,8 +304,7 @@ test_events(case_after_subgroup_fail_init) ->
{?eh,tc_start,{subgroups_1_SUITE,
{end_per_group,case_after_subgroup_fail_init,[sequence]}}},
{?eh,tc_done,{subgroups_1_SUITE,
- {end_per_group,case_after_subgroup_fail_init,[sequence]},
- {return_group_result,failed}}}],
+ {end_per_group,case_after_subgroup_fail_init,[sequence]},ok}}],
{?eh,test_done,{'DEF','STOP_TIME'}},
{?eh,stop_logging,[]}
].
diff --git a/lib/common_test/test/ct_shell_SUITE.erl b/lib/common_test/test/ct_shell_SUITE.erl
index 4b8c43d800..70c0ab8127 100644
--- a/lib/common_test/test/ct_shell_SUITE.erl
+++ b/lib/common_test/test/ct_shell_SUITE.erl
@@ -93,7 +93,7 @@ start_interactive(Config) ->
test_server:format(Level,
"ct_util_server not stopped on ~p yet, waiting 5 s...~n",
[CTNode]),
- timer:sleep(5000),
+ ct:sleep(5000),
undefined = rpc:call(CTNode, erlang, whereis, [ct_util_server])
end,
Events = ct_test_support:get_events(ERPid, Config),
diff --git a/lib/common_test/test/ct_skip_SUITE_data/skip/test/auto_skip_4_SUITE.erl b/lib/common_test/test/ct_skip_SUITE_data/skip/test/auto_skip_4_SUITE.erl
index 825846cd55..89e202a404 100644
--- a/lib/common_test/test/ct_skip_SUITE_data/skip/test/auto_skip_4_SUITE.erl
+++ b/lib/common_test/test/ct_skip_SUITE_data/skip/test/auto_skip_4_SUITE.erl
@@ -72,7 +72,7 @@ end_per_group(_GroupName, _Config) ->
%% Reason = term()
%%--------------------------------------------------------------------
init_per_testcase(tc1, Config) ->
- timer:sleep(5000),
+ ct:sleep(5000),
Config;
init_per_testcase(_TestCase, Config) ->
Config.
diff --git a/lib/common_test/test/ct_smoke_test_SUITE.erl b/lib/common_test/test/ct_smoke_test_SUITE.erl
index 49b38361e2..6077946c33 100644
--- a/lib/common_test/test/ct_smoke_test_SUITE.erl
+++ b/lib/common_test/test/ct_smoke_test_SUITE.erl
@@ -480,7 +480,7 @@ events(Test) when Test == dir1 ; Test == dir2 ;
{Suite,tc4,{skipped,"Skipping this one"}}},
{?eh,test_stats,{7,0,{1,0}}},
{?eh,tc_start,{Suite,end_per_suite}},
- {?eh,tc_done,{Suite,end_per_suite,ips_data}},
+ {?eh,tc_done,{Suite,end_per_suite,ok}},
{?eh,test_done,{'DEF','STOP_TIME'}},
{?eh,stop_logging,[]}
];
@@ -517,7 +517,7 @@ events(Test) when Test == dir1_2 ; Test == suite11_21 ->
{happy_11_SUITE,tc4,{skipped,"Skipping this one"}}},
{?eh,test_stats,{7,0,{1,0}}},
{?eh,tc_start,{happy_11_SUITE,end_per_suite}},
- {?eh,tc_done,{happy_11_SUITE,end_per_suite,ips_data}},
+ {?eh,tc_done,{happy_11_SUITE,end_per_suite,ok}},
{?eh,tc_start,{happy_21_SUITE,init_per_suite}},
{?eh,tc_done,{happy_21_SUITE,init_per_suite,ok}},
{?eh,tc_start,{happy_21_SUITE,tc1}},
@@ -546,7 +546,7 @@ events(Test) when Test == dir1_2 ; Test == suite11_21 ->
{happy_21_SUITE,tc4,{skipped,"Skipping this one"}}},
{?eh,test_stats,{14,0,{2,0}}},
{?eh,tc_start,{happy_21_SUITE,end_per_suite}},
- {?eh,tc_done,{happy_21_SUITE,end_per_suite,ips_data}},
+ {?eh,tc_done,{happy_21_SUITE,end_per_suite,ok}},
{?eh,test_done,{'DEF','STOP_TIME'}},
{?eh,stop_logging,[]}
];
@@ -563,7 +563,7 @@ events(Test) when Test == tc111 ; Test == tc211 ->
{?eh,tc_done,{Suite,tc1,ok}},
{?eh,test_stats,{1,0,{0,0}}},
{?eh,tc_start,{Suite,end_per_suite}},
- {?eh,tc_done,{Suite,end_per_suite,ips_data}},
+ {?eh,tc_done,{Suite,end_per_suite,ok}},
{?eh,test_done,{'DEF','STOP_TIME'}},
{?eh,stop_logging,[]}
];
@@ -582,7 +582,7 @@ events(tc111_112) ->
{?eh,tc_done,{happy_11_SUITE,tc2,ok}},
{?eh,test_stats,{2,0,{0,0}}},
{?eh,tc_start,{happy_11_SUITE,end_per_suite}},
- {?eh,tc_done,{happy_11_SUITE,end_per_suite,ips_data}},
+ {?eh,tc_done,{happy_11_SUITE,end_per_suite,ok}},
{?eh,test_done,{'DEF','STOP_TIME'}},
{?eh,stop_logging,[]}
].
diff --git a/lib/common_test/test/ct_snmp_SUITE_data/snmp_SUITE.erl b/lib/common_test/test/ct_snmp_SUITE_data/snmp_SUITE.erl
index e20832e1e7..07f7bf02e4 100644
--- a/lib/common_test/test/ct_snmp_SUITE_data/snmp_SUITE.erl
+++ b/lib/common_test/test/ct_snmp_SUITE_data/snmp_SUITE.erl
@@ -117,12 +117,12 @@ break(_Config) ->
start_stop(Config) ->
ok = ct_snmp:start(Config,snmp1,snmp_app1),
- timer:sleep(1000),
+ ct:sleep(1000),
{snmp,_,_} = lists:keyfind(snmp,1,application:which_applications()),
[_|_] = filelib:wildcard("*/*.conf",?config(priv_dir,Config)),
ok = ct_snmp:stop(Config),
- timer:sleep(1000),
+ ct:sleep(1000),
false = lists:keyfind(snmp,1,application:which_applications()),
[] = filelib:wildcard("*/*.conf",?config(priv_dir,Config)),
ok.
diff --git a/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl b/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl
index 0ddb4e9b00..1d3f5918d2 100644
--- a/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl
+++ b/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl
@@ -44,6 +44,7 @@ all() ->
expect_error_timeout1,
expect_error_timeout2,
expect_error_timeout3,
+ total_timeout_less_than_idle,
no_prompt_check,
no_prompt_check_repeat,
no_prompt_check_sequence,
@@ -134,9 +135,32 @@ expect_error_timeout2(_) ->
expect_error_timeout3(_) ->
{ok, Handle} = ct_telnet:open(telnet_server_conn1),
ok = ct_telnet:send(Handle, "echo_loop 5000 xxx"),
+
+ T0 = now(),
{error,timeout} = ct_telnet:expect(Handle, ["yyy"],
[{idle_timeout,infinity},
- {total_timeout,3000}]),
+ {total_timeout,2001}]),
+ Diff = trunc(timer:now_diff(now(),T0)/1000),
+ {_,true} = {Diff, (Diff >= 2000) and (Diff =< 4000)},
+
+ ok = ct_telnet:send(Handle, "echo ayt"),
+ {ok,["ayt"]} = ct_telnet:expect(Handle, ["ayt"]),
+ ok = ct_telnet:close(Handle),
+ ok.
+
+%% OTP-12335: If total_timeout < idle_timeout, expect will never timeout
+%% until after idle_timeout, which is incorrect.
+total_timeout_less_than_idle(_) ->
+ {ok, Handle} = ct_telnet:open(telnet_server_conn1),
+ ok = ct_telnet:send(Handle, "echo_no_prompt xxx"),
+
+ T0 = now(),
+ {error,timeout} = ct_telnet:expect(Handle, ["yyy"],
+ [{idle_timeout,5000},
+ {total_timeout,2001}]),
+ Diff = trunc(timer:now_diff(now(),T0)/1000),
+ {_,true} = {Diff, (Diff >= 2000) and (Diff =< 4000)},
+
ok = ct_telnet:send(Handle, "echo ayt"),
{ok,["ayt"]} = ct_telnet:expect(Handle, ["ayt"]),
ok = ct_telnet:close(Handle),
@@ -259,14 +283,14 @@ large_string(_) ->
%% yield the same result as the single request case.
ok = ct_telnet:send(Handle, "echo_sep "++BigString),
- timer:sleep(1000),
+ ct:sleep(1000),
{ok,Data1} = ct_telnet:get_data(Handle),
ct:log("[GET DATA #1] Received ~w chars: ~s",
[length(lists:flatten(Data1)),Data1]),
VerifyStr = [C || C <- lists:flatten(Data1), C/=$ , C/=$\r, C/=$\n, C/=$>],
ok = ct_telnet:send(Handle, "echo_sep "++BigString),
- timer:sleep(50),
+ ct:sleep(50),
{ok,Data2} = ct_telnet:get_data(Handle),
ct:log("[GET DATA #2] Received ~w chars: ~s", [length(lists:flatten(Data2)),Data2]),
VerifyStr = [C || C <- lists:flatten(Data2), C/=$ , C/=$\r, C/=$\n, C/=$>],
@@ -292,7 +316,7 @@ server_speaks(_) ->
"echo_no_prompt This is the second message"),
%% Let ct_telnet_client get an idle timeout. This should print the
%% two messages to the log. Note that the buffers are not flushed here!
- timer:sleep(15000),
+ ct:sleep(15000),
ok = ct_telnet_client:send_data(Backdoor,
"echo_no_prompt This is the third message"),
{ok,_} = ct_telnet:expect(Handle, ["first.*second.*third"],
@@ -302,7 +326,7 @@ server_speaks(_) ->
ok = ct_telnet_client:send_data(Backdoor,
"echo_no_prompt This is the fourth message"),
%% give the server time to respond
- timer:sleep(2000),
+ ct:sleep(2000),
%% closing the connection should print last message in log
ok = ct_telnet:close(Handle),
ok.
@@ -314,11 +338,11 @@ server_disconnects(_) ->
ok = ct_telnet:send(Handle, "disconnect_after 1500"),
%% wait until the get_data operation (triggered by send/2) times out
%% before sending the msg
- timer:sleep(500),
+ ct:sleep(500),
ok = ct_telnet:send(Handle, "echo_no_prompt This is the message"),
%% when the server closes the connection, the last message should be
%% printed in the log
- timer:sleep(3000),
+ ct:sleep(3000),
_ = ct_telnet:close(Handle),
ok.
diff --git a/lib/common_test/test/ct_test_server_if_1_SUITE_data/test_server_if/test/ts_if_1_SUITE.erl b/lib/common_test/test/ct_test_server_if_1_SUITE_data/test_server_if/test/ts_if_1_SUITE.erl
index 06fa6ac638..d30feb0bd2 100644
--- a/lib/common_test/test/ct_test_server_if_1_SUITE_data/test_server_if/test/ts_if_1_SUITE.erl
+++ b/lib/common_test/test/ct_test_server_if_1_SUITE_data/test_server_if/test/ts_if_1_SUITE.erl
@@ -76,7 +76,7 @@ end_per_group(_GroupName, _Config) ->
%% Reason = term()
%%--------------------------------------------------------------------
init_per_testcase(tc1, Config) ->
- timer:sleep(5000),
+ ct:sleep(5000),
Config;
init_per_testcase(tc8, _Config) ->
{skip,"tc8 skipped"};
@@ -92,7 +92,7 @@ init_per_testcase(_TestCase, Config) ->
%% Config0 = Config1 = [tuple()]
%%--------------------------------------------------------------------
end_per_testcase(tc2, Config) ->
- timer:sleep(5000);
+ ct:sleep(5000);
end_per_testcase(tc12, Config) ->
ct:comment("end_per_testcase(tc12) called!"),
ct:pal("end_per_testcase(tc12) called!", []),
@@ -146,7 +146,7 @@ tc2(_) ->
timeout_in_end_per_testcase.
tc3(_) ->
- timer:sleep(5000).
+ ct:sleep(5000).
tc4(_) ->
exit(failed_on_purpose).
@@ -186,7 +186,7 @@ gtc2(_) ->
tc12(_) ->
F = fun() -> ct:abort_current_testcase('stopping tc12') end,
spawn(F),
- timer:sleep(1000),
+ ct:sleep(1000),
exit(should_have_been_aborted).
tc13(_) ->
diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl
index 746469584d..6abca08452 100644
--- a/lib/common_test/test/ct_test_support.erl
+++ b/lib/common_test/test/ct_test_support.erl
@@ -51,12 +51,12 @@ init_per_suite(Config) ->
init_per_suite(Config, 50).
init_per_suite(Config, Level) ->
+ ScaleFactor = test_server:timetrap_scale_factor(),
case os:type() of
{win32, _} ->
%% Extend timeout to 1 hour for windows as starting node
%% can take a long time there
- test_server:timetrap( 60*60*1000 *
- test_server:timetrap_scale_factor());
+ test_server:timetrap( 60*60*1000 * ScaleFactor );
_ ->
ok
end,
@@ -67,6 +67,16 @@ init_per_suite(Config, Level) ->
_ ->
ok
end,
+
+ {Mult,Scale} = test_server_ctrl:get_timetrap_parameters(),
+ test_server:format(Level, "Timetrap multiplier: ~w~n", [Mult]),
+ if Scale == true ->
+ test_server:format(Level, "Timetrap scale factor: ~w~n",
+ [ScaleFactor]);
+ true ->
+ ok
+ end,
+
start_slave(Config, Level).
start_slave(Config, Level) ->
diff --git a/lib/common_test/test/ct_testspec_1_SUITE.erl b/lib/common_test/test/ct_testspec_1_SUITE.erl
index c2670316b6..bc19283a47 100644
--- a/lib/common_test/test/ct_testspec_1_SUITE.erl
+++ b/lib/common_test/test/ct_testspec_1_SUITE.erl
@@ -795,7 +795,7 @@ test_events(skip_all_groups) ->
{?eh,test_stats,{0,0,{12,0}}},
{?eh,tc_user_skip,{groups_11_SUITE,{end_per_group,test_group_4},"SKIPPED!"}},
{?eh,tc_start,{groups_11_SUITE,end_per_suite}},
- {?eh,tc_done,{groups_11_SUITE,end_per_suite,init}},
+ {?eh,tc_done,{groups_11_SUITE,end_per_suite,ok}},
{negative,{?eh,tc_start,'_'},{?eh,stop_logging,'_'}}
];
@@ -840,7 +840,7 @@ test_events(skip_group) ->
{?eh,test_stats,{2,0,{6,0}}},
{?eh,tc_user_skip,{groups_11_SUITE,{end_per_group,test_group_2},
"SKIPPED!"}},
- {?eh,tc_done,{groups_11_SUITE,end_per_suite,init}},
+ {?eh,tc_done,{groups_11_SUITE,end_per_suite,ok}},
{negative,{?eh,tc_start,'_'},{?eh,stop_logging,'_'}}
];
@@ -876,7 +876,7 @@ test_events(skip_group_all_testcases) ->
{?eh,test_stats,{0,0,{4,0}}},
{?eh,tc_user_skip,{groups_11_SUITE,{end_per_group,test_group_1b},
"SKIPPED!"}},
- {?eh,tc_done,{groups_11_SUITE,end_per_suite,init}},
+ {?eh,tc_done,{groups_11_SUITE,end_per_suite,ok}},
{negative,{?eh,tc_start,'_'},{?eh,stop_logging,'_'}}
];
@@ -1065,7 +1065,7 @@ test_events(skip_subgroup) ->
{?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_4,[]},ok}}],
{?eh,tc_start,{groups_12_SUITE,end_per_suite}},
- {?eh,tc_done,{groups_12_SUITE,end_per_suite,init}},
+ {?eh,tc_done,{groups_12_SUITE,end_per_suite,ok}},
{negative,{?eh,tc_start,'_'},{?eh,stop_logging,'_'}}
];
diff --git a/lib/common_test/test/ct_testspec_1_SUITE_data/groups_1/groups_12_SUITE.erl b/lib/common_test/test/ct_testspec_1_SUITE_data/groups_1/groups_12_SUITE.erl
index 69c06f9b83..e5de5df1a8 100644
--- a/lib/common_test/test/ct_testspec_1_SUITE_data/groups_1/groups_12_SUITE.erl
+++ b/lib/common_test/test/ct_testspec_1_SUITE_data/groups_1/groups_12_SUITE.erl
@@ -285,7 +285,7 @@ testcase_5a(Config) ->
%% increase chance the done event will come
%% during execution of subgroup (could be
%% tricky to handle)
- timer:sleep(3),
+ ct:sleep(3),
ok.
testcase_5b() ->
[].
diff --git a/lib/common_test/test/ct_testspec_1_SUITE_data/groups_2/groups_22_SUITE.erl b/lib/common_test/test/ct_testspec_1_SUITE_data/groups_2/groups_22_SUITE.erl
index cd517876df..dcd361d658 100644
--- a/lib/common_test/test/ct_testspec_1_SUITE_data/groups_2/groups_22_SUITE.erl
+++ b/lib/common_test/test/ct_testspec_1_SUITE_data/groups_2/groups_22_SUITE.erl
@@ -278,7 +278,7 @@ testcase_5a(Config) ->
%% increase chance the done event will come
%% during execution of subgroup (could be
%% tricky to handle)
- timer:sleep(3),
+ ct:sleep(3),
ok.
testcase_5b() ->
[].
diff --git a/lib/common_test/test/telnet_server.erl b/lib/common_test/test/telnet_server.erl
index d25ee62d38..11959c3e12 100644
--- a/lib/common_test/test/telnet_server.erl
+++ b/lib/common_test/test/telnet_server.erl
@@ -59,7 +59,7 @@ init(Opts) ->
accept(State),
ok = gen_tcp:close(LSock),
dbg("telnet_server closed the listen socket ~p\n", [LSock]),
- timer:sleep(1000),
+ ct:sleep(1000),
ok.
listen(0, _Port, _Opts) ->
@@ -68,7 +68,7 @@ listen(Retries, Port, Opts) ->
case gen_tcp:listen(Port, Opts) of
{error,eaddrinuse} ->
dbg("Listen port not released, trying again..."),
- timer:sleep(5000),
+ ct:sleep(5000),
listen(Retries-1, Port, Opts);
Ok = {ok,_LSock} ->
Ok;
@@ -220,7 +220,7 @@ do_handle_data("echo_sep " ++ Data,State) ->
Msgs = string:tokens(Data," "),
lists:foreach(fun(Msg) ->
send(Msg,State),
- timer:sleep(10)
+ ct:sleep(10)
end, Msgs),
send("\r\n> ",State),
{ok,State};
@@ -292,7 +292,7 @@ send_loop(T0,T,Data,State) ->
ok;
true ->
send(Data,State),
- timer:sleep(500),
+ ct:sleep(500),
send_loop(T0,T,Data,State)
end.
diff --git a/lib/compiler/src/beam_bool.erl b/lib/compiler/src/beam_bool.erl
index 5a4621dc37..a452d30b61 100644
--- a/lib/compiler/src/beam_bool.erl
+++ b/lib/compiler/src/beam_bool.erl
@@ -126,44 +126,53 @@ bopt_block(Reg, Fail, OldIs, [{block,Bl0}|Acc0], St0) ->
%% There was a reference to a boolean expression
%% from inside a protected block (try/catch), to
%% a boolean expression outside.
- throw:protected_barrier ->
+ throw:protected_barrier ->
failed;
- %% The 'xor' operator was used. We currently don't
- %% find it worthwile to translate 'xor' operators
- %% (the code would be clumsy).
- throw:'xor' ->
+ %% The 'xor' operator was used. We currently don't
+ %% find it worthwile to translate 'xor' operators
+ %% (the code would be clumsy).
+ throw:'xor' ->
failed;
- %% The block does not contain a boolean expression,
- %% but only a call to a guard BIF.
- %% For instance: ... when element(1, T) ->
- throw:not_boolean_expr ->
+ %% The block does not contain a boolean expression,
+ %% but only a call to a guard BIF.
+ %% For instance: ... when element(1, T) ->
+ throw:not_boolean_expr ->
failed;
- %% The block contains a 'move' instruction that could
- %% not be handled.
- throw:move ->
+ %% The block contains a 'move' instruction that could
+ %% not be handled.
+ throw:move ->
failed;
- %% The optimization is not safe. (A register
- %% used by the instructions following the
- %% optimized code is either not assigned a
- %% value at all or assigned a different value.)
- throw:all_registers_not_killed ->
+ %% The optimization is not safe. (A register
+ %% used by the instructions following the
+ %% optimized code is either not assigned a
+ %% value at all or assigned a different value.)
+ throw:all_registers_not_killed ->
failed;
- throw:registers_used ->
+ throw:registers_used ->
failed;
- %% A protected block refered to the value
- %% returned by another protected block,
- %% probably because the Core Erlang code
- %% used nested try/catches in the guard.
- %% (v3_core never produces nested try/catches
- %% in guards, so it must have been another
- %% Core Erlang translator.)
- throw:protected_violation ->
+ %% A protected block refered to the value
+ %% returned by another protected block,
+ %% probably because the Core Erlang code
+ %% used nested try/catches in the guard.
+ %% (v3_core never produces nested try/catches
+ %% in guards, so it must have been another
+ %% Core Erlang translator.)
+ throw:protected_violation ->
+ failed;
+
+ %% Failed to work out the live registers for a GC
+ %% BIF. For example, if the number of live registers
+ %% needed to be 4 because {x,3} was a source register,
+ %% but {x,2} was not known to be initialized, this
+ %% exception would be thrown.
+ throw:gc_bif_alloc_failure ->
failed
+
end
end.
@@ -665,10 +674,16 @@ put_reg_1(V, [], I) -> [{I,V}].
fetch_reg(V, [{I,V}|_]) -> {x,I};
fetch_reg(V, [_|SRs]) -> fetch_reg(V, SRs).
-live_regs(Regs) ->
- foldl(fun ({I,_}, _) ->
- I
- end, -1, Regs)+1.
+live_regs([{_,reserved}|_]) ->
+ %% We are not sure that this register is initialized, so we must
+ %% abort the optimization.
+ throw(gc_bif_alloc_failure);
+live_regs([{I,_}]) ->
+ I+1;
+live_regs([{_,_}|Regs]) ->
+ live_regs(Regs);
+live_regs([]) ->
+ 0.
%%%
diff --git a/lib/compiler/src/core_lib.erl b/lib/compiler/src/core_lib.erl
index 2792fd8fa5..0d95971f91 100644
--- a/lib/compiler/src/core_lib.erl
+++ b/lib/compiler/src/core_lib.erl
@@ -212,6 +212,8 @@ vu_pattern(V, #c_tuple{es=Es}, St) ->
vu_pattern_list(V, Es, St);
vu_pattern(V, #c_binary{segments=Ss}, St) ->
vu_pat_seg_list(V, Ss, St);
+vu_pattern(V, #c_map{es=Es}, St) ->
+ vu_map_pairs(V, Es, St);
vu_pattern(V, #c_alias{var=Var,pat=P}, St0) ->
case vu_pattern(V, Var, St0) of
{true,_}=St1 -> St1;
@@ -234,6 +236,13 @@ vu_pat_seg_list(V, Ss, St) ->
end
end, St, Ss).
+vu_map_pairs(V, [#c_map_pair{val=Pat}|T], St0) ->
+ case vu_pattern(V, Pat, St0) of
+ {true,_}=St -> St;
+ St -> vu_map_pairs(V, T, St)
+ end;
+vu_map_pairs(_, [], St) -> St.
+
-spec vu_var_list(cerl:var_name(), [cerl:c_var()]) -> boolean().
vu_var_list(V, Vs) ->
diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl
index 82817a987a..ed8f609082 100644
--- a/lib/compiler/src/sys_core_fold.erl
+++ b/lib/compiler/src/sys_core_fold.erl
@@ -1229,6 +1229,11 @@ is_non_numeric([H|T]) ->
is_non_numeric(H) andalso is_non_numeric(T);
is_non_numeric(Tuple) when is_tuple(Tuple) ->
is_non_numeric_tuple(Tuple, tuple_size(Tuple));
+is_non_numeric(Map) when is_map(Map) ->
+ %% Note that 17.x and 18.x compare keys in different ways.
+ %% Be very conservative -- require that both keys and values
+ %% are non-numeric.
+ is_non_numeric(maps:to_list(Map));
is_non_numeric(Num) when is_number(Num) ->
false;
is_non_numeric(_) -> true.
@@ -1338,9 +1343,12 @@ eval_element(Call, #c_literal{val=Pos}, #c_var{name=V}, Types)
{ok,#c_tuple{es=Elements}} ->
if
1 =< Pos, Pos =< length(Elements) ->
- case lists:nth(Pos, Elements) of
- #c_alias{var=Alias} -> Alias;
- Res -> Res
+ El = lists:nth(Pos, Elements),
+ try
+ pat_to_expr(El)
+ catch
+ throw:impossible ->
+ Call
end;
true ->
eval_failure(Call, badarg)
@@ -2040,17 +2048,18 @@ case_opt_args([], Cs, _Sub, _LitExpr, Acc) ->
%% Try to expand one argument to several arguments (if tuple/list)
%% or to remove a literal argument.
%%
-case_opt_arg(E0, Sub, Cs, LitExpr) ->
+case_opt_arg(E0, Sub, Cs0, LitExpr) ->
E = maybe_replace_var(E0, Sub),
case cerl:is_data(E) of
false ->
- {error,Cs};
+ {error,Cs0};
true ->
+ Cs = case_opt_nomatch(E, Cs0, LitExpr),
case cerl:data_type(E) of
{atomic,_} ->
- case_opt_lit(E, Cs, LitExpr);
+ case_opt_lit(E, Cs);
_ ->
- case_opt_data(E, Cs, LitExpr)
+ case_opt_data(E, Cs)
end
end.
@@ -2072,19 +2081,67 @@ maybe_replace_var_1(E, #sub{t=Tdb}) ->
false ->
E;
true ->
- cerl_trees:map(fun(C) ->
- case cerl:is_c_alias(C) of
- false -> C;
- true -> cerl:alias_pat(C)
- end
- end, T0)
+ %% The pattern was a tuple. Now we must make sure
+ %% that the elements of the tuple are suitable. In
+ %% particular, we don't want binary or map
+ %% construction here, since that means that the
+ %% binary or map will be constructed in the 'case'
+ %% argument. That is wasteful for binaries. Even
+ %% worse is that any map pattern that use the ':='
+ %% operator will fail when used in map
+ %% construction (only the '=>' operator is allowed
+ %% when constructing a map from scratch).
+ ToData = fun coerce_to_data/1,
+ try
+ cerl_trees:map(ToData, T0)
+ catch
+ throw:impossible ->
+ %% Something unsuitable was found (map or
+ %% or binary). Keep the variable.
+ E
+ end
end;
error ->
E
end.
-%% case_opt_lit(Literal, Clauses0, LitExpr) ->
-%% {ok,[],Clauses} | error
+%% coerce_to_data(Core) -> Core'
+%% Coerce an element originally from a pattern to an data item or or
+%% variable. Throw an 'impossible' exception if non-data Core Erlang
+%% terms such as binary construction or map construction are
+%% encountered.
+
+coerce_to_data(C) ->
+ case cerl:is_c_alias(C) of
+ false ->
+ case cerl:is_data(C) orelse cerl:is_c_var(C) of
+ true -> C;
+ false -> throw(impossible)
+ end;
+ true ->
+ coerce_to_data(cerl:alias_pat(C))
+ end.
+
+%% case_opt_nomatch(E, Clauses, LitExpr) -> Clauses'
+%% Remove all clauses that cannot possibly match.
+
+case_opt_nomatch(E, [{[P|_],C,_,_}=Current|Cs], LitExpr) ->
+ case cerl_clauses:match(P, E) of
+ none ->
+ %% The pattern will not match the case expression. Remove
+ %% the clause. Unless the entire case expression is a
+ %% literal, also emit a warning.
+ case LitExpr of
+ false -> add_warning(C, nomatch_clause_type);
+ true -> ok
+ end,
+ case_opt_nomatch(E, Cs, LitExpr);
+ _ ->
+ [Current|case_opt_nomatch(E, Cs, LitExpr)]
+ end;
+case_opt_nomatch(_, [], _) -> [].
+
+%% case_opt_lit(Literal, Clauses0) -> {ok,[],Clauses} | error
%% The current part of the case expression is a literal. That
%% means that we will know at compile-time whether a clause
%% will match, and we can remove the corresponding pattern from
@@ -2093,68 +2150,48 @@ maybe_replace_var_1(E, #sub{t=Tdb}) ->
%% The only complication is if the literal is a binary. Binary
%% pattern matching is tricky, so we will give up in that case.
-case_opt_lit(Lit, Cs0, LitExpr) ->
- Cs1 = case_opt_lit_1(Lit, Cs0, LitExpr),
- try case_opt_lit_2(Lit, Cs1) of
+case_opt_lit(Lit, Cs0) ->
+ try case_opt_lit_1(Lit, Cs0) of
Cs ->
{ok,[],Cs}
catch
throw:impossible ->
- {error,Cs1}
+ {error,Cs0}
end.
-case_opt_lit_1(E, [{[P|_],C,_,_}=Current|Cs], LitExpr) ->
- case cerl_clauses:match(P, E) of
- none ->
- %% The pattern will not match the literal. Remove the clause.
- %% Unless the entire case expression is a literal, also
- %% emit a warning.
- case LitExpr of
- false -> add_warning(C, nomatch_clause_type);
- true -> ok
- end,
- case_opt_lit_1(E, Cs, LitExpr);
- _ ->
- [Current|case_opt_lit_1(E, Cs, LitExpr)]
- end;
-case_opt_lit_1(_, [], _) -> [].
-
-case_opt_lit_2(E, [{[P|Ps],C,PsAcc,Bs0}|Cs]) ->
- %% Non-matching clauses have already been removed in case_opt_lit_1/3.
+case_opt_lit_1(E, [{[P|Ps],C,PsAcc,Bs0}|Cs]) ->
+ %% Non-matching clauses have already been removed
+ %% in case_opt_nomatch/3.
case cerl_clauses:match(P, E) of
{true,Bs} ->
%% The pattern matches the literal. Remove the pattern
%% and update the bindings.
- [{Ps,C,PsAcc,Bs++Bs0}|case_opt_lit_2(E, Cs)];
+ [{Ps,C,PsAcc,Bs++Bs0}|case_opt_lit_1(E, Cs)];
{false,_} ->
%% Binary literal and pattern. We are not sure whether
%% the pattern will match.
throw(impossible)
end;
-case_opt_lit_2(_, []) -> [].
+case_opt_lit_1(_, []) -> [].
%% case_opt_data(Expr, Clauses0, LitExpr) -> {ok,Exprs,Clauses}
-case_opt_data(E, Cs0, LitExpr) ->
+case_opt_data(E, Cs0) ->
Es = cerl:data_es(E),
- Cs = case_opt_data_1(Cs0, Es,
- {cerl:data_type(E),cerl:data_arity(E)},
- LitExpr),
- {ok,Es,Cs}.
-
-case_opt_data_1([{[P|Ps0],C,PsAcc,Bs0}|Cs], Es, TypeSig, LitExpr) ->
- case case_data_pat(P, TypeSig) of
- {ok,Ps1,Bs1} ->
- [{Ps1++Ps0,C,PsAcc,Bs1++Bs0}|
- case_opt_data_1(Cs, Es, TypeSig,LitExpr)];
- error ->
- case LitExpr of
- false -> add_warning(C, nomatch_clause_type);
- true -> ok
- end,
- case_opt_data_1(Cs, Es, TypeSig, LitExpr)
- end;
-case_opt_data_1([], _, _, _) -> [].
+ TypeSig = {cerl:data_type(E),cerl:data_arity(E)},
+ try case_opt_data_1(Cs0, Es, TypeSig) of
+ Cs ->
+ {ok,Es,Cs}
+ catch
+ throw:impossible ->
+ {error,Cs0}
+ end.
+
+case_opt_data_1([{[P|Ps0],C,PsAcc,Bs0}|Cs], Es, TypeSig) ->
+ {ok,Ps1,Bs1} = case_data_pat(P, TypeSig),
+ [{Ps1++Ps0,C,PsAcc,Bs1++Bs0}|
+ case_opt_data_1(Cs, Es, TypeSig)];
+case_opt_data_1([], _, _) -> [].
%% case_data_pat(Pattern, Type, Arity) -> {ok,[Pattern],[{AliasVar,Pat}]} | error.
@@ -2163,12 +2200,7 @@ case_data_pat(P, TypeSig) ->
false ->
case_data_pat_var(P, TypeSig);
true ->
- case {cerl:data_type(P),cerl:data_arity(P)} of
- TypeSig ->
- {ok,cerl:data_es(P),[]};
- {_,_} ->
- error
- end
+ {ok,cerl:data_es(P),[]}
end.
%% case_data_pat_var(Pattern, {DataType,ArityType}) ->
@@ -2188,35 +2220,38 @@ case_data_pat_var(P, {Type,Arity}=TypeSig) ->
alias ->
V = cerl:alias_var(P),
Apat = cerl:alias_pat(P),
- case case_data_pat(Apat, TypeSig) of
- {ok,Ps,Bs} ->
- {ok,Ps,[{V,cerl:ann_make_data(Ann, Type, unalias_pat_list(Ps))}|Bs]};
- error ->
- error
- end;
- _ ->
- error
+ {ok,Ps,Bs} = case_data_pat(Apat, TypeSig),
+ {ok,Ps,[{V,cerl:ann_make_data(Ann, Type,
+ pat_to_expr_list(Ps))}|Bs]}
end.
-%% unalias_pat(Pattern) -> Pattern.
-%% Remove all the aliases in a pattern but using the alias variables
-%% instead of the values. We KNOW they will be bound.
+%% pat_to_expr(Pattern) -> Expression.
+%% Convert a pattern to an expression if possible. We KNOW that
+%% all variables in the pattern will be bound.
+%%
+%% Throw an 'impossible' exception if a map or (non-literal)
+%% binary is encountered. Trying to use a map pattern as an
+%% expression is incorrect, while rebuilding a potentially
+%% huge binary in an expression would be wasteful.
-unalias_pat(P) ->
- case cerl:is_c_alias(P) of
- true ->
+pat_to_expr(P) ->
+ case cerl:type(P) of
+ alias ->
cerl:alias_var(P);
- false ->
+ var ->
+ P;
+ _ ->
case cerl:is_data(P) of
false ->
- P;
+ %% Map or binary.
+ throw(impossible);
true ->
- Es = unalias_pat_list(cerl:data_es(P)),
+ Es = pat_to_expr_list(cerl:data_es(P)),
cerl:update_data(P, cerl:data_type(P), Es)
end
end.
-unalias_pat_list(Ps) -> [unalias_pat(P) || P <- Ps].
+pat_to_expr_list(Ps) -> [pat_to_expr(P) || P <- Ps].
make_vars(A, Max) ->
make_vars(A, 1, Max).
diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl
index 149b9bbb8f..10e3451e8f 100644
--- a/lib/compiler/test/bs_match_SUITE.erl
+++ b/lib/compiler/test/bs_match_SUITE.erl
@@ -34,7 +34,7 @@
otp_7188/1,otp_7233/1,otp_7240/1,otp_7498/1,
match_string/1,zero_width/1,bad_size/1,haystack/1,
cover_beam_bool/1,matched_out_size/1,follow_fail_branch/1,
- no_partition/1,calling_a_binary/1]).
+ no_partition/1,calling_a_binary/1,binary_in_map/1]).
-export([coverage_id/1,coverage_external_ignore/2]).
@@ -59,7 +59,7 @@ groups() ->
matching_and_andalso,otp_7188,otp_7233,otp_7240,
otp_7498,match_string,zero_width,bad_size,haystack,
cover_beam_bool,matched_out_size,follow_fail_branch,
- no_partition,calling_a_binary]}].
+ no_partition,calling_a_binary,binary_in_map]}].
init_per_suite(Config) ->
@@ -1189,6 +1189,26 @@ call_binary(<<>>, Acc) ->
call_binary(<<H,T/bits>>, Acc) ->
T(<<Acc/binary,H>>).
+binary_in_map(Config) when is_list(Config) ->
+ ok = match_binary_in_map(#{key => <<42:8>>}),
+ {'EXIT',{{badmatch,#{key := 1}},_}} =
+ (catch match_binary_in_map(#{key => 1})),
+ {'EXIT',{{badmatch,#{key := <<1023:16>>}},_}} =
+ (catch match_binary_in_map(#{key => <<1023:16>>})),
+ {'EXIT',{{badmatch,#{key := <<1:8>>}},_}} =
+ (catch match_binary_in_map(#{key => <<1:8>>})),
+ {'EXIT',{{badmatch,not_a_map},_}} =
+ (catch match_binary_in_map(not_a_map)),
+ ok.
+
+match_binary_in_map(Map) ->
+ case 8 of
+ N ->
+ #{key := <<42:N>>} = Map,
+ ok
+ end.
+
+
check(F, R) ->
R = F().
diff --git a/lib/compiler/test/core_fold_SUITE.erl b/lib/compiler/test/core_fold_SUITE.erl
index 6a7036d728..2de17e7653 100644
--- a/lib/compiler/test/core_fold_SUITE.erl
+++ b/lib/compiler/test/core_fold_SUITE.erl
@@ -60,6 +60,12 @@ t_element(Config) when is_list(Config) ->
X = make_ref(),
?line X = id(element(1, {X,y,z})),
?line b = id(element(2, {a,b,c,d})),
+ (fun() ->
+ case {a,#{k=>X}} of
+ {a,#{k:=X}}=Tuple ->
+ #{k:=X} = id(element(2, Tuple))
+ end
+ end)(),
%% No optimization, but should work.
Tuple = id({x,y,z}),
@@ -204,6 +210,16 @@ eq(Config) when is_list(Config) ->
?line ?CMP_DIFF(a, [a]),
?line ?CMP_DIFF(a, {1,2,3}),
+ ?CMP_SAME(#{a=>1.0,b=>2}, #{b=>2.0,a=>1}),
+ ?CMP_SAME(#{a=>[1.0],b=>[2]}, #{b=>[2.0],a=>[1]}),
+
+ %% The rule for comparing keys are different in 17.x and 18.x.
+ %% Just test that the results are consistent.
+ Bool = id(#{1=>a}) == id(#{1.0=>a}), %Unoptimizable.
+ Bool = id(#{1=>a}) == #{1.0=>a}, %Optimizable.
+ Bool = #{1=>a} == #{1.0=>a}, %Optimizable.
+ io:format("Bool = ~p\n", [Bool]),
+
ok.
%% OTP-7117.
diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl
index eb205d09a7..34bfdeb1e5 100644
--- a/lib/compiler/test/guard_SUITE.erl
+++ b/lib/compiler/test/guard_SUITE.erl
@@ -1556,6 +1556,24 @@ bad_constants(Config) when is_list(Config) ->
bad_guards(Config) when is_list(Config) ->
if erlang:float(self()); true -> ok end,
+
+ fc(catch bad_guards_1(1, [])),
+ fc(catch bad_guards_1(1, [2])),
+ fc(catch bad_guards_1(atom, [2])),
+
+ fc(catch bad_guards_2(#{a=>0,b=>0}, [])),
+ fc(catch bad_guards_2(#{a=>0,b=>0}, [x])),
+ fc(catch bad_guards_2(not_a_map, [x])),
+ fc(catch bad_guards_2(42, [x])),
+ ok.
+
+%% beam_bool used to produce GC BIF instructions whose
+%% Live operands included uninitialized registers.
+
+bad_guards_1(X, [_]) when {{X}}, -X ->
+ ok.
+
+bad_guards_2(M, [_]) when M#{a := 0, b => 0}, map_size(M) ->
ok.
%% Call this function to turn off constant propagation.
diff --git a/lib/compiler/test/match_SUITE.erl b/lib/compiler/test/match_SUITE.erl
index ae7d764535..1e778dca24 100644
--- a/lib/compiler/test/match_SUITE.erl
+++ b/lib/compiler/test/match_SUITE.erl
@@ -22,7 +22,7 @@
init_per_group/2,end_per_group/2,
pmatch/1,mixed/1,aliases/1,match_in_call/1,
untuplify/1,shortcut_boolean/1,letify_guard/1,
- selectify/1,underscore/1,coverage/1]).
+ selectify/1,underscore/1,match_map/1,coverage/1]).
-include_lib("test_server/include/test_server.hrl").
@@ -35,7 +35,8 @@ all() ->
groups() ->
[{p,test_lib:parallel(),
[pmatch,mixed,aliases,match_in_call,untuplify,
- shortcut_boolean,letify_guard,selectify,underscore,coverage]}].
+ shortcut_boolean,letify_guard,selectify,
+ underscore,match_map,coverage]}].
init_per_suite(Config) ->
@@ -400,6 +401,24 @@ underscore(Config) when is_list(Config) ->
_ = is_list(Config),
ok.
+-record(s, {map,t}).
+
+match_map(Config) when is_list(Config) ->
+ Map = #{key=>{x,y},ignore=>anything},
+ #s{map=Map,t={x,y}} = do_match_map(#s{map=Map}),
+ {a,#{k:={a,b,c}}} = do_match_map_2(#{k=>{a,b,c}}),
+ ok.
+
+do_match_map(#s{map=#{key:=Val}}=S) ->
+ %% Would crash with a 'badarg' exception.
+ S#s{t=Val}.
+
+do_match_map_2(Map) ->
+ case {a,Map} of
+ {a,#{k:=_}}=Tuple ->
+ Tuple
+ end.
+
coverage(Config) when is_list(Config) ->
%% Cover beam_dead.
ok = coverage_1(x, a),
diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c
index e7215eeb64..26e2486dc2 100644
--- a/lib/crypto/c_src/crypto.c
+++ b/lib/crypto/c_src/crypto.c
@@ -1644,14 +1644,15 @@ static ERL_NIF_TERM aes_cfb_8_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM
int new_ivlen = 0;
ERL_NIF_TERM ret;
- if (!enif_inspect_iolist_as_binary(env, argv[0], &key) || key.size != 16
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key)
+ || !(key.size == 16 || key.size == 24 || key.size == 32)
|| !enif_inspect_binary(env, argv[1], &ivec) || ivec.size != 16
|| !enif_inspect_iolist_as_binary(env, argv[2], &text)) {
return enif_make_badarg(env);
}
memcpy(ivec_clone, ivec.data, 16);
- AES_set_encrypt_key(key.data, 128, &aes_key);
+ AES_set_encrypt_key(key.data, key.size * 8, &aes_key);
AES_cfb8_encrypt((unsigned char *) text.data,
enif_make_new_binary(env, text.size, &ret),
text.size, &aes_key, ivec_clone, &new_ivlen,
@@ -1670,14 +1671,15 @@ static ERL_NIF_TERM aes_cfb_128_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TE
CHECK_OSE_CRYPTO();
- if (!enif_inspect_iolist_as_binary(env, argv[0], &key) || key.size != 16
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key)
+ || !(key.size == 16 || key.size == 24 || key.size == 32)
|| !enif_inspect_binary(env, argv[1], &ivec) || ivec.size != 16
|| !enif_inspect_iolist_as_binary(env, argv[2], &text)) {
return enif_make_badarg(env);
}
memcpy(ivec_clone, ivec.data, 16);
- AES_set_encrypt_key(key.data, 128, &aes_key);
+ AES_set_encrypt_key(key.data, key.size * 8, &aes_key);
AES_cfb128_encrypt((unsigned char *) text.data,
enif_make_new_binary(env, text.size, &ret),
text.size, &aes_key, ivec_clone, &new_ivlen,
diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl
index 03aa3964a5..53e29af338 100644
--- a/lib/crypto/test/crypto_SUITE.erl
+++ b/lib/crypto/test/crypto_SUITE.erl
@@ -1185,6 +1185,38 @@ aes_cfb8() ->
{aes_cfb8,
hexstr2bin("2b7e151628aed2a6abf7158809cf4f3c"),
hexstr2bin("26751F67A3CBB140B1808CF187A4F4DF"),
+ hexstr2bin("f69f2445df4f9b17ad2b417be66c3710")},
+ {aes_cfb8,
+ hexstr2bin("8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"),
+ hexstr2bin("000102030405060708090a0b0c0d0e0f"),
+ hexstr2bin("6bc1bee22e409f96e93d7e117393172a")},
+ {aes_cfb8,
+ hexstr2bin("8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"),
+ hexstr2bin("cdc80d6fddf18cab34c25909c99a4174"),
+ hexstr2bin("ae2d8a571e03ac9c9eb76fac45af8e51")},
+ {aes_cfb8,
+ hexstr2bin("8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"),
+ hexstr2bin("67ce7f7f81173621961a2b70171d3d7a"),
+ hexstr2bin("30c81c46a35ce411e5fbc1191a0a52ef")},
+ {aes_cfb8,
+ hexstr2bin("8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"),
+ hexstr2bin("2e1e8a1dd59b88b1c8e60fed1efac4c9"),
+ hexstr2bin("f69f2445df4f9b17ad2b417be66c3710")},
+ {aes_cfb8,
+ hexstr2bin("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"),
+ hexstr2bin("000102030405060708090a0b0c0d0e0f"),
+ hexstr2bin("6bc1bee22e409f96e93d7e117393172a")},
+ {aes_cfb8,
+ hexstr2bin("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"),
+ hexstr2bin("dc7e84bfda79164b7ecd8486985d3860"),
+ hexstr2bin("ae2d8a571e03ac9c9eb76fac45af8e51")},
+ {aes_cfb8,
+ hexstr2bin("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"),
+ hexstr2bin("39ffed143b28b1c832113c6331e5407b"),
+ hexstr2bin("30c81c46a35ce411e5fbc1191a0a52ef")},
+ {aes_cfb8,
+ hexstr2bin("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"),
+ hexstr2bin("df10132415e54b92a13ed0a8267ae2f9"),
hexstr2bin("f69f2445df4f9b17ad2b417be66c3710")}
].
@@ -1204,6 +1236,38 @@ aes_cfb128() ->
{aes_cfb128,
hexstr2bin("2b7e151628aed2a6abf7158809cf4f3c"),
hexstr2bin("26751F67A3CBB140B1808CF187A4F4DF"),
+ hexstr2bin("f69f2445df4f9b17ad2b417be66c3710")},
+ {aes_cfb128,
+ hexstr2bin("8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"),
+ hexstr2bin("000102030405060708090a0b0c0d0e0f"),
+ hexstr2bin("6bc1bee22e409f96e93d7e117393172a")},
+ {aes_cfb128,
+ hexstr2bin("8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"),
+ hexstr2bin("cdc80d6fddf18cab34c25909c99a4174"),
+ hexstr2bin("ae2d8a571e03ac9c9eb76fac45af8e51")},
+ {aes_cfb128,
+ hexstr2bin("8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"),
+ hexstr2bin("67ce7f7f81173621961a2b70171d3d7a"),
+ hexstr2bin("30c81c46a35ce411e5fbc1191a0a52ef")},
+ {aes_cfb128,
+ hexstr2bin("8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"),
+ hexstr2bin("2e1e8a1dd59b88b1c8e60fed1efac4c9"),
+ hexstr2bin("f69f2445df4f9b17ad2b417be66c3710")},
+ {aes_cfb128,
+ hexstr2bin("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"),
+ hexstr2bin("000102030405060708090a0b0c0d0e0f"),
+ hexstr2bin("6bc1bee22e409f96e93d7e117393172a")},
+ {aes_cfb128,
+ hexstr2bin("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"),
+ hexstr2bin("dc7e84bfda79164b7ecd8486985d3860"),
+ hexstr2bin("ae2d8a571e03ac9c9eb76fac45af8e51")},
+ {aes_cfb128,
+ hexstr2bin("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"),
+ hexstr2bin("39ffed143b28b1c832113c6331e5407b"),
+ hexstr2bin("30c81c46a35ce411e5fbc1191a0a52ef")},
+ {aes_cfb128,
+ hexstr2bin("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"),
+ hexstr2bin("df10132415e54b92a13ed0a8267ae2f9"),
hexstr2bin("f69f2445df4f9b17ad2b417be66c3710")}
].
diff --git a/lib/debugger/src/dbg_wx_settings.erl b/lib/debugger/src/dbg_wx_settings.erl
index 20aac74c3d..2c332c0a54 100644
--- a/lib/debugger/src/dbg_wx_settings.erl
+++ b/lib/debugger/src/dbg_wx_settings.erl
@@ -65,14 +65,8 @@ open_win(Win, Pos, SFile, Str, What) ->
{style,What}]),
case wxFileDialog:showModal(FD) of
?wxID_OK ->
- case wxFileDialog:getPaths(FD) of
- [NewFile] ->
- wxFileDialog:destroy(FD),
- {ok, NewFile};
- _ ->
- wxFileDialog:destroy(FD),
- cancel
- end;
+ NewFile = wxFileDialog:getPath(FD),
+ {ok, NewFile};
_ ->
wxFileDialog:destroy(FD),
cancel
diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl
index 03005e689f..3cdbf3569a 100644
--- a/lib/dialyzer/src/dialyzer_dataflow.erl
+++ b/lib/dialyzer/src/dialyzer_dataflow.erl
@@ -3477,6 +3477,7 @@ find_terminals(Tree) ->
'let' -> find_terminals(cerl:let_body(Tree));
letrec -> find_terminals(cerl:letrec_body(Tree));
literal -> {false, true};
+ map -> {false, true};
primop -> {false, false}; %% match_fail, etc. are not explicit exits.
'receive' ->
Timeout = cerl:receive_timeout(Tree),
diff --git a/lib/dialyzer/test/small_SUITE_data/src/maps_redef2.erl b/lib/dialyzer/test/small_SUITE_data/src/maps_redef2.erl
new file mode 100644
index 0000000000..945b2a9144
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/src/maps_redef2.erl
@@ -0,0 +1,23 @@
+%% In 17, the linter says that map(A) redefines 'type map', which is
+%% allowed until next release. However, Dialyzer used to replace
+%% map(A) with #{}, which resulted in warnings.
+
+-module(maps_redef2).
+
+-export([t/0]).
+
+-type map(_A) :: integer().
+
+t() ->
+ M = new(),
+ t1(M).
+
+-spec t1(map(_)) -> map(_).
+
+t1(A) ->
+ A + A.
+
+-spec new() -> map(_).
+
+new() ->
+ 3.
diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl
index bc25f7d472..8272904856 100644
--- a/lib/diameter/include/diameter_gen.hrl
+++ b/lib/diameter/include/diameter_gen.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2015. 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,9 @@
-define(THROW(T), throw({?MODULE, T})).
+%% Tag common to generated dictionaries.
+-define(TAG, diameter_gen).
+
%% Key to a value in the process dictionary that determines whether or
%% not an unrecognized AVP setting the M-bit should be regarded as an
%% error or not. See is_strict/0.
@@ -48,13 +51,20 @@
%% dictionary.
putr(K,V) ->
- put({?MODULE, K}, V).
+ put({?TAG, K}, V).
getr(K) ->
- get({?MODULE, K}).
+ case get({?TAG, K}) of
+ undefined ->
+ V = erase({?MODULE, K}), %% written in old code
+ V == undefined orelse putr(K,V),
+ V;
+ V ->
+ V
+ end.
eraser(K) ->
- erase({?MODULE, K}).
+ erase({?TAG, K}).
%% ---------------------------------------------------------------------------
%% # encode_avps/2
@@ -313,12 +323,20 @@ d(Name, Avp, Acc) ->
%% decode is packed into 'AVP'.
Mod = dict(Failed), %% Dictionary to decode in.
+ %% On decode, a Grouped AVP is represented as a #diameter_avp{}
+ %% list with AVP as head and component AVPs as tail. On encode,
+ %% data can be a list of component AVPs.
+
try Mod:avp(decode, Data, AvpName) of
V ->
{Avps, T} = Acc,
{H, A} = ungroup(V, Avp),
{[H | Avps], pack_avp(Name, A, T)}
catch
+ throw: {?TAG, {grouped, RC, ComponentAvps}} ->
+ {Avps, {Rec, Errors}} = Acc,
+ A = trim(Avp),
+ {[[A | trim(ComponentAvps)] | Avps], {Rec, [{RC, A} | Errors]}};
error: Reason ->
d(undefined == Failed orelse is_failed(),
Reason,
@@ -338,6 +356,10 @@ d(Name, Avp, Acc) ->
trim(#diameter_avp{data = <<0:1, Bin/binary>>} = Avp) ->
Avp#diameter_avp{data = Bin};
+trim(Avps)
+ when is_list(Avps) ->
+ lists:map(fun trim/1, Avps);
+
trim(Avp) ->
Avp.
@@ -582,22 +604,37 @@ value(_, Avp) ->
%% # grouped_avp/3
%% ---------------------------------------------------------------------------
--spec grouped_avp(decode, avp_name(), binary())
+-spec grouped_avp(decode, avp_name(), bitstring())
-> {avp_record(), [avp()]};
(encode, avp_name(), avp_record() | avp_values())
-> binary()
| no_return().
+%% Length error induced by diameter_codec:collect_avps/1.
+grouped_avp(decode, _Name, <<0:1, _/binary>>) ->
+ throw({?TAG, {grouped, 5014, []}});
+
grouped_avp(decode, Name, Data) ->
- {Rec, Avps, []} = decode_avps(Name, diameter_codec:collect_avps(Data)),
- {Rec, Avps};
-%% A failed match here will result in 5004. Note that this is the only
-%% AVP type that doesn't just return the decoded record, also
-%% returning the list of component AVP's.
+ grouped_decode(Name, diameter_codec:collect_avps(Data));
grouped_avp(encode, Name, Data) ->
encode_avps(Name, Data).
+%% grouped_decode/2
+%%
+%% Note that Grouped is the only AVP type that doesn't just return a
+%% decoded value, also returning the list of component diameter_avp
+%% records.
+
+grouped_decode(_Name, {Error, Acc}) ->
+ {RC, Avp} = Error,
+ throw({?TAG, {grouped, RC, [Avp | Acc]}});
+
+grouped_decode(Name, ComponentAvps) ->
+ {Rec, Avps, Es} = decode_avps(Name, ComponentAvps),
+ [] == Es orelse throw({?TAG, {grouped, 5004, Avps}}), %% decode failure
+ {Rec, Avps}.
+
%% ---------------------------------------------------------------------------
%% # empty_group/1
%% ---------------------------------------------------------------------------
diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl
index a2b04bfd63..b4ecb63961 100644
--- a/lib/diameter/src/base/diameter_codec.erl
+++ b/lib/diameter/src/base/diameter_codec.erl
@@ -390,6 +390,9 @@ sequence_numbers(#diameter_packet{bin = Bin})
sequence_numbers(#diameter_packet{header = #diameter_header{} = H}) ->
sequence_numbers(H);
+sequence_numbers(#diameter_packet{msg = [#diameter_header{} = H | _]}) ->
+ sequence_numbers(H);
+
sequence_numbers(#diameter_header{hop_by_hop_id = H,
end_to_end_id = E}) ->
{H,E};
@@ -561,14 +564,14 @@ split_data(Bin, Len) ->
<<Data:Len/binary, _:Pad/binary, Rest/binary>> ->
{Data, Rest};
_ ->
- %% 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.
+ %% Header length points past the end of the message, or
+ %% doesn't span the header. 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.
@@ -582,6 +585,8 @@ split_data(Bin, Len) ->
%% dictionary doesn't know about specific AVP's.
%% Grouped AVP whose components need packing ...
+pack_avp([#diameter_avp{} = A | Avps]) ->
+ pack_avp(A#diameter_avp{data = Avps});
pack_avp(#diameter_avp{data = [#diameter_avp{} | _] = Avps} = A) ->
pack_avp(A#diameter_avp{data = encode_avps(Avps)});
diff --git a/lib/diameter/src/base/diameter_lib.erl b/lib/diameter/src/base/diameter_lib.erl
index 4ffe69112d..3f327f3653 100644
--- a/lib/diameter/src/base/diameter_lib.erl
+++ b/lib/diameter/src/base/diameter_lib.erl
@@ -343,17 +343,19 @@ opts(HeapSize, Opts) ->
%% # wait/1
%% ---------------------------------------------------------------------------
--spec wait([pid()])
+-spec wait([pid() | reference()])
-> ok.
wait(L) ->
- down([erlang:monitor(process, P) || P <- L]).
+ lists:foreach(fun down/1, L).
-down([]) ->
- ok;
-down([MRef|T]) ->
- receive {'DOWN', MRef, process, _, _} -> ok end,
- down(T).
+down(Pid)
+ when is_pid(Pid) ->
+ down(monitor(process, Pid));
+
+down(MRef)
+ when is_reference(MRef) ->
+ receive {'DOWN', MRef, process, _, _} = T -> T end.
%% ---------------------------------------------------------------------------
%% # fold_tuple/3
diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl
index 694d64336c..04401a3d87 100644
--- a/lib/diameter/src/base/diameter_service.erl
+++ b/lib/diameter/src/base/diameter_service.erl
@@ -613,8 +613,9 @@ st(#watchdog{ref = Ref, pid = Pid}, Refs) ->
%% st/3
st(#watchdog{pid = Pid}, Reason, Acc) ->
+ MRef = monitor(process, Pid),
Pid ! {shutdown, self(), Reason},
- [Pid | Acc].
+ [MRef | Acc].
%% ---------------------------------------------------------------------------
%% # call_service/2
diff --git a/lib/diameter/src/base/diameter_service_sup.erl b/lib/diameter/src/base/diameter_service_sup.erl
index 153fff902f..e3177f0083 100644
--- a/lib/diameter/src/base/diameter_service_sup.erl
+++ b/lib/diameter/src/base/diameter_service_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2015. 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
@@ -58,7 +58,7 @@ init([]) ->
ChildSpec = {Mod,
{Mod, start_link, []},
temporary,
- 1000,
+ 5000,
worker,
[Mod]},
{ok, {Flags, [ChildSpec]}}.
diff --git a/lib/diameter/src/base/diameter_sup.erl b/lib/diameter/src/base/diameter_sup.erl
index e5afd23dcd..4ede4086d8 100644
--- a/lib/diameter/src/base/diameter_sup.erl
+++ b/lib/diameter/src/base/diameter_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2015. 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
@@ -64,7 +64,7 @@ spec(Mod) ->
{Mod,
{Mod, start_link, []},
permanent,
- 1000,
+ infinity,
supervisor,
[Mod]}.
diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl
index 3b62afca47..0b503338a6 100644
--- a/lib/diameter/src/base/diameter_traffic.erl
+++ b/lib/diameter/src/base/diameter_traffic.erl
@@ -162,24 +162,28 @@ incr_error(Dir, Id, TPid) ->
%% incr_rc/4
%% ---------------------------------------------------------------------------
--spec incr_rc(send|recv, Pkt, TPid, Dict0)
+-spec incr_rc(send|recv, Pkt, TPid, DictT)
-> {Counter, non_neg_integer()}
| Reason
when Pkt :: #diameter_packet{},
TPid :: pid(),
- Dict0 :: module(),
+ DictT :: module() | {module(), module(), module()},
Counter :: {'Result-Code', integer()}
| {'Experimental-Result', integer(), integer()},
Reason :: atom().
-incr_rc(Dir, Pkt, TPid, Dict0) ->
+incr_rc(Dir, Pkt, TPid, {Dict, _, _} = DictT) ->
try
- incr_result(Dir, Pkt, TPid, {Dict0, Dict0, Dict0})
+ incr_result(Dir, Pkt, TPid, DictT)
catch
exit: {E,_} when E == no_result_code;
E == invalid_error_bit ->
+ incr(TPid, {msg_id(Pkt#diameter_packet.header, Dict), Dir, E}),
E
- end.
+ end;
+
+incr_rc(Dir, Pkt, TPid, Dict0) ->
+ incr_rc(Dir, Pkt, TPid, {Dict0, Dict0, Dict0}).
%% ---------------------------------------------------------------------------
%% pending/1
@@ -678,7 +682,7 @@ local(Msg, TPid, {Dict, AppDict, Dict0} = DictT, Fs, ReqPkt) ->
reset(make_answer_packet(Msg, ReqPkt), Dict, Dict0),
Fs),
incr(send, Pkt, TPid, AppDict),
- incr_result(send, Pkt, TPid, DictT), %% count outgoing
+ incr_rc(send, Pkt, TPid, DictT), %% count outgoing
send(TPid, Pkt).
%% reset/3
@@ -1388,6 +1392,21 @@ make_request_packet(#diameter_packet{header = Hdr} = Pkt,
make_request_packet(Msg, Pkt) ->
Pkt#diameter_packet{msg = Msg}.
+%% make_retransmit_packet/2
+
+make_retransmit_packet(#diameter_packet{msg = [#diameter_header{} = Hdr
+ | Avps]}
+ = Pkt) ->
+ Pkt#diameter_packet{msg = [make_retransmit_header(Hdr) | Avps]};
+
+make_retransmit_packet(#diameter_packet{header = Hdr} = Pkt) ->
+ Pkt#diameter_packet{header = make_retransmit_header(Hdr)}.
+
+%% make_retransmit_header/1
+
+make_retransmit_header(Hdr) ->
+ Hdr#diameter_header{is_retransmitted = true}.
+
%% fold_record/2
fold_record(undefined, R) ->
@@ -1674,9 +1693,7 @@ retransmit({TPid, Caps, App}
have_request(Pkt0, TPid) %% Don't failover to a peer we've
andalso ?THROW(timeout), %% already sent to.
- #diameter_packet{header = Hdr0} = Pkt0,
- Hdr = Hdr0#diameter_header{is_retransmitted = true},
- Pkt = Pkt0#diameter_packet{header = Hdr},
+ Pkt = make_retransmit_packet(Pkt0),
retransmit(cb(App, prepare_retransmit, [Pkt, SvcName, {TPid, Caps}]),
Transport,
diff --git a/lib/diameter/src/base/diameter_types.erl b/lib/diameter/src/base/diameter_types.erl
index ca3338be5f..442d90c98b 100644
--- a/lib/diameter/src/base/diameter_types.erl
+++ b/lib/diameter/src/base/diameter_types.erl
@@ -75,7 +75,7 @@
%% message indicating this error MUST include the offending AVPs
%% within a Failed-AVP AVP.
%%
--define(INVALID_LENGTH(Bin), erlang:error({'DIAMETER', 5014, Bin})).
+-define(INVALID_LENGTH(Bitstr), erlang:error({'DIAMETER', 5014, Bitstr})).
%% -------------------------------------------------------------------------
%% 3588, 4.2. Basic AVP Data Formats
diff --git a/lib/diameter/src/transport/diameter_transport_sup.erl b/lib/diameter/src/transport/diameter_transport_sup.erl
index 6457ab78b0..284a41a752 100644
--- a/lib/diameter/src/transport/diameter_transport_sup.erl
+++ b/lib/diameter/src/transport/diameter_transport_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2015. 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
@@ -54,7 +54,7 @@ start_child(Name, Module) ->
Spec = {Name,
{Module, start_link, [Name]},
permanent,
- 1000,
+ infinity,
supervisor,
[Module]},
supervisor:start_child(?MODULE, Spec).
diff --git a/lib/diameter/test/diameter_app_SUITE.erl b/lib/diameter/test/diameter_app_SUITE.erl
index f68a18b5c2..cf34c762e1 100644
--- a/lib/diameter/test/diameter_app_SUITE.erl
+++ b/lib/diameter/test/diameter_app_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2015. 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
@@ -187,15 +187,14 @@ xref(Config) ->
xref:stop(XRef),
+ Rel = release(), %% otp_release-ish
+
%% Only care about calls from our own application.
- [] = lists:filter(fun({{F,_,_},{T,_,_}}) ->
+ [] = lists:filter(fun({{F,_,_} = From, {_,_,_} = To}) ->
lists:member(F, Mods)
- andalso {F,T} /= {diameter_tcp, ssl}
+ andalso not ignored(From, To, Rel)
end,
Undefs),
- %% diameter_tcp does call ssl despite the latter not being listed
- %% as a dependency in the app file since ssl is only required for
- %% TLS security: it's up to a client who wants TLS to start ssl.
%% Ensure that only runtime or info modules call runtime modules.
%% It's not strictly necessary that diameter compiler modules not
@@ -214,6 +213,38 @@ xref(Config) ->
[] = lists:filter(fun(M) -> not lists:member(app(M), Deps) end,
RTdeps -- Mods).
+ignored({FromMod,_,_}, {ToMod,_,_} = To, Rel)->
+ %% diameter_tcp does call ssl despite the latter not being listed
+ %% as a dependency in the app file since ssl is only required for
+ %% TLS security: it's up to a client who wants TLS to start ssl.
+ %% The OTP 18 time api is also called if it exists, so that the
+ %% same code can be run on older releases.
+ {FromMod, ToMod} == {diameter_tcp, ssl}
+ orelse (FromMod == diameter_lib
+ andalso Rel < 18
+ andalso lists:member(To, time_api())).
+
+%% New time api in OTP 18.
+time_api() ->
+ [{erlang, F, A} || {F,A} <- [{convert_time_resolution,3},
+ {monotonic_time,0},
+ {monotonic_time,1},
+ {time_offset,0},
+ {time_offset,1},
+ {time_resolution,0},
+ {timestamp,0},
+ {unique_integer,0},
+ {unique_integer,1}]].
+
+release() ->
+ Rel = erlang:system_info(otp_release),
+ try list_to_integer(Rel) of
+ N -> N
+ catch
+ error:_ ->
+ 0 %% aka < 17
+ end.
+
unversion(App) ->
T = lists:dropwhile(fun is_vsn_ch/1, lists:reverse(App)),
lists:reverse(case T of [$-|TT] -> TT; _ -> T end).
diff --git a/lib/diameter/test/diameter_codec_SUITE.erl b/lib/diameter/test/diameter_codec_SUITE.erl
index cd8ca41f66..64ea90554d 100644
--- a/lib/diameter/test/diameter_codec_SUITE.erl
+++ b/lib/diameter/test/diameter_codec_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2015. 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
@@ -29,6 +29,9 @@
-export([suite/0,
all/0,
+ groups/0,
+ init_per_group/2,
+ end_per_group/2,
init_per_testcase/2,
end_per_testcase/2]).
@@ -36,9 +39,13 @@
-export([base/1,
gen/1,
lib/1,
- unknown/1]).
+ unknown/1,
+ success/1,
+ grouped_error/1,
+ failed_error/1]).
-include("diameter_ct.hrl").
+-include("diameter.hrl").
-define(L, atom_to_list).
@@ -48,7 +55,19 @@ suite() ->
[{timetrap, {seconds, 10}}].
all() ->
- [base, gen, lib, unknown].
+ [base, gen, lib, unknown, {group, recode}].
+
+groups() ->
+ [{recode, [], [success,
+ grouped_error,
+ failed_error]}].
+
+init_per_group(recode, Config) ->
+ ok = diameter:start(),
+ Config.
+
+end_per_group(_, _) ->
+ ok = diameter:stop().
init_per_testcase(gen, Config) ->
[{application, ?APP, App}] = diameter_util:consult(?APP, app),
@@ -98,3 +117,166 @@ compile(File) ->
compile(File, Opts) ->
compile:file(File, [return | Opts]).
+
+%% ===========================================================================
+
+%% Ensure a Grouped AVP is represented by a list in the avps field.
+success(_) ->
+ Avps = [{295, <<1:32>>}, %% Termination-Cause
+ {284, [{280, "Proxy-Host"}, %% Proxy-Info
+ {33, "Proxy-State"}, %%
+ {295, <<2:32>>}]}], %% Termination-Cause
+ #diameter_packet{avps = [#diameter_avp{code = 295,
+ value = 1,
+ data = <<1:32>>},
+ [#diameter_avp{code = 284},
+ #diameter_avp{code = 280},
+ #diameter_avp{code = 33},
+ #diameter_avp{code = 295,
+ value = 2,
+ data = <<2:32>>}]],
+ errors = []}
+ = str(recode(str(Avps))).
+
+%% ===========================================================================
+
+%% Ensure a Grouped AVP is represented by a list in the avps field
+%% even in the case of a decode error on a component AVP.
+grouped_error(_) ->
+ Avps = [{295, <<1:32>>}, %% Termination-Cause
+ {284, [{295, <<0:32>>}, %% Proxy-Info, Termination-Cause
+ {280, "Proxy-Host"},
+ {33, "Proxy-State"}]}],
+ #diameter_packet{avps = [#diameter_avp{code = 295,
+ value = 1,
+ data = <<1:32>>},
+ [#diameter_avp{code = 284},
+ #diameter_avp{code = 295,
+ value = undefined,
+ data = <<0:32>>},
+ #diameter_avp{code = 280},
+ #diameter_avp{code = 33}]],
+ errors = [{5004, #diameter_avp{code = 284}}]}
+ = str(recode(str(Avps))).
+
+%% ===========================================================================
+
+%% Ensure that a failed decode in Failed-AVP is acceptable, and that
+%% the component AVPs are decoded if possible.
+failed_error(_) ->
+ Avps = [{279, [{295, <<0:32>>}, %% Failed-AVP, Termination-Cause
+ {258, <<1:32>>}, %% Auth-Application-Id
+ {284, [{280, "Proxy-Host"}, %% Proxy-Info
+ {33, "Proxy-State"},
+ {295, <<0:32>>}, %% Termination-Cause, invalid
+ {258, <<2:32>>}]}]}], %% Auth-Application-Id
+ #diameter_packet{avps = [[#diameter_avp{code = 279},
+ #diameter_avp{code = 295,
+ value = undefined,
+ data = <<0:32>>},
+ #diameter_avp{code = 258,
+ value = 1,
+ data = <<1:32>>},
+ [#diameter_avp{code = 284},
+ #diameter_avp{code = 280},
+ #diameter_avp{code = 33},
+ #diameter_avp{code = 295,
+ value = undefined},
+ #diameter_avp{code = 258,
+ value = 2,
+ data = <<2:32>>}]]],
+ errors = []}
+ = sta(recode(sta(Avps))).
+
+%% ===========================================================================
+
+%% str/1
+
+str(#diameter_packet{avps = [#diameter_avp{code = 263},
+ #diameter_avp{code = 264},
+ #diameter_avp{code = 296},
+ #diameter_avp{code = 283},
+ #diameter_avp{code = 258,
+ value = 0}
+ | T]}
+ = Pkt) ->
+ Pkt#diameter_packet{avps = T};
+
+str(Avps) ->
+ OH = "diameter.erlang.org",
+ OR = "erlang.org",
+ DR = "example.com",
+ Sid = "diameter.erlang.org;123;456",
+
+ [#diameter_header{version = 1,
+ cmd_code = 275, %% STR
+ is_request = true,
+ application_id = 0,
+ hop_by_hop_id = 17,
+ end_to_end_id = 42,
+ is_proxiable = false,
+ is_error = false,
+ is_retransmitted = false}
+ | avp([{263, Sid}, %% Session-Id
+ {264, OH}, %% Origin-Host
+ {296, OR}, %% Origin-Realm
+ {283, DR}, %% Destination-Realm
+ {258, <<0:32>>}] %% Auth-Application-Id
+ ++ Avps)].
+
+%% sta/1
+
+sta(#diameter_packet{avps = [#diameter_avp{code = 263},
+ #diameter_avp{code = 268},
+ #diameter_avp{code = 264},
+ #diameter_avp{code = 296},
+ #diameter_avp{code = 278,
+ value = 4}
+ | T]}
+ = Pkt) ->
+ Pkt#diameter_packet{avps = T};
+
+sta(Avps) ->
+ OH = "diameter.erlang.org",
+ OR = "erlang.org",
+ Sid = "diameter.erlang.org;123;456",
+
+ [#diameter_header{version = 1,
+ cmd_code = 275, %% STA
+ is_request = false,
+ application_id = 0,
+ hop_by_hop_id = 17,
+ end_to_end_id = 42,
+ is_proxiable = false,
+ is_error = false,
+ is_retransmitted = false}
+ | avp([{263, Sid}, %% Session-Id
+ {268, <<2002:32>>}, %% Result-Code
+ {264, OH}, %% Origin-Host
+ {296, OR}, %% Origin-Realm
+ {278, <<4:32>>}] %% Origin-State-Id
+ ++ Avps)].
+
+avp({Code, Data}) ->
+ #diameter_avp{code = Code,
+ data = avp(Data)};
+
+avp(#diameter_avp{} = A) ->
+ A;
+
+avp([{_,_} | _] = Avps) ->
+ lists:map(fun avp/1, Avps);
+
+avp(V) ->
+ V.
+
+%% recode/1
+
+recode(Msg) ->
+ recode(Msg, diameter_gen_base_rfc6733).
+
+recode(#diameter_packet{} = Pkt, Dict) ->
+ diameter_codec:decode(Dict, diameter_codec:encode(Dict, Pkt));
+
+recode(Msg, Dict) ->
+ recode(#diameter_packet{msg = Msg}, Dict).
diff --git a/lib/diameter/test/diameter_config_SUITE.erl b/lib/diameter/test/diameter_config_SUITE.erl
index ad5b3f9420..a4bba42b2b 100644
--- a/lib/diameter/test/diameter_config_SUITE.erl
+++ b/lib/diameter/test/diameter_config_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2015. 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
@@ -171,6 +171,9 @@
[[{suspect, 2}]]],
[[x],
[[{open, 0}]]]},
+ {pool_size,
+ [[1], [100]],
+ [[0], [infinity], [-1], [x]]},
{private,
[[x]],
[]},
diff --git a/lib/diameter/test/diameter_gen_sctp_SUITE.erl b/lib/diameter/test/diameter_gen_sctp_SUITE.erl
index 1e2baa79b3..4ea5e80095 100644
--- a/lib/diameter/test/diameter_gen_sctp_SUITE.erl
+++ b/lib/diameter/test/diameter_gen_sctp_SUITE.erl
@@ -119,10 +119,10 @@ send_not_from_controlling_process(_) ->
send_not_from_controlling_process() ->
FPid = self(),
- {L, MRef} = spawn_monitor(fun() -> listen(FPid) end),%% listening process
+ {L, MRef} = spawn_monitor(fun() -> listen(FPid) end),
receive
{?MODULE, C, S} ->
- erlang:demonitor(MRef, [flush]),
+ demonitor(MRef, [flush]),
[L,C,S];
{'DOWN', MRef, process, _, _} = T ->
error(T)
@@ -137,13 +137,7 @@ listen(FPid) ->
LPid = self(),
spawn(fun() -> connect1(PortNr, FPid, LPid) end), %% connecting process
Id = assoc(Sock),
- ?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = Id}], _Bin})
- = recv(). %% Waits with this as current_function.
-
-%% recv/0
-
-recv() ->
- receive T -> T end.
+ recv(Sock, Id).
%% connect1/3
@@ -154,7 +148,7 @@ connect1(PortNr, FPid, LPid) ->
FPid ! {?MODULE,
self(),
spawn(fun() -> send(Sock, Id) end)}, %% sending process
- MRef = erlang:monitor(process, LPid),
+ MRef = monitor(process, LPid),
down(MRef). %% Waits with this as current_function.
%% down/1
@@ -277,7 +271,8 @@ acc(N, Acc) ->
loop(Sock, MRef, Bin) ->
receive
- ?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = Id}], B}) ->
+ ?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = Id}], B})
+ when is_binary(B) ->
Sz = size(Bin),
{Sz, Bin} = {size(B), B}, %% assert
ok = send(Sock, Id, mark(Bin)),
@@ -291,7 +286,7 @@ loop(Sock, MRef, Bin) ->
%% connect2/3
connect2(Pid, PortNr, Bin) ->
- erlang:monitor(process, Pid),
+ monitor(process, Pid),
{ok, Sock} = open(),
ok = gen_sctp:connect_init(Sock, ?ADDR, PortNr, []),
@@ -312,9 +307,14 @@ connect2(Pid, PortNr, Bin) ->
recv(Sock, Id) ->
receive
- ?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = Id}], Bin}) ->
+ ?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = I}], Bin})
+ when is_binary(Bin) ->
+ Id = I, %% assert
Bin;
- T -> %% eg. 'DOWN'
+ ?SCTP(S, _) ->
+ Sock = S, %% assert
+ recv(Sock, Id);
+ T ->
exit(T)
end.
diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl
index 318057b1b2..9822b95301 100644
--- a/lib/diameter/test/diameter_traffic_SUITE.erl
+++ b/lib/diameter/test/diameter_traffic_SUITE.erl
@@ -414,12 +414,13 @@ send_eval(Config) ->
= call(Config, Req).
%% Send an accounting ACR that the server tries to answer with an
-%% inappropriate header, resulting in no answer being sent and the
-%% request timing out.
+%% inappropriate header. That the error is detected is coded in
+%% handle_answer.
send_bad_answer(Config) ->
Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD},
{'Accounting-Record-Number', 2}],
- {timeout, _} = call(Config, Req).
+ ?answer_message(?SUCCESS)
+ = call(Config, Req).
%% Send an ACR that the server callback answers explicitly with a
%% protocol error.
@@ -1057,15 +1058,12 @@ answer(Pkt, Req, _Peer, Name, #group{client_dict0 = Dict0}) ->
[R | Vs] = Dict:'#get-'(answer(Ans, Es, Name)),
[Dict:rec2msg(R) | Vs].
-answer(Rec, [_|_], N)
- when N == send_long_avp_length;
- N == send_short_avp_length;
- N == send_zero_avp_length;
- N == send_invalid_avp_length;
- N == send_invalid_reject;
- N == send_unknown_short_mandatory;
- N == send_unexpected_mandatory_decode ->
+%% An inappropriate E-bit results in a decode error ...
+answer(Rec, Es, send_bad_answer) ->
+ [{5004, #diameter_avp{name = 'Result-Code'}} | _] = Es,
Rec;
+
+%% ... while other errors are reflected in Failed-AVP.
answer(Rec, [], _) ->
Rec.
diff --git a/lib/diameter/test/diameter_transport_SUITE.erl b/lib/diameter/test/diameter_transport_SUITE.erl
index 1e61bbdbf7..f098851bea 100644
--- a/lib/diameter/test/diameter_transport_SUITE.erl
+++ b/lib/diameter/test/diameter_transport_SUITE.erl
@@ -424,7 +424,11 @@ gen_send(tcp, Sock, Bin) ->
gen_recv(sctp, Sock) ->
{_OS, _IS, Id} = getr(assoc),
- ?RECV(?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = Id}], Bin}), Bin);
+ receive
+ ?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = Id}], Bin})
+ when is_binary(Bin) ->
+ Bin
+ end;
gen_recv(tcp, Sock) ->
tcp_recv(Sock, <<>>).
diff --git a/lib/eldap/doc/src/eldap.xml b/lib/eldap/doc/src/eldap.xml
index 4417551aa8..945379d5d2 100644
--- a/lib/eldap/doc/src/eldap.xml
+++ b/lib/eldap/doc/src/eldap.xml
@@ -103,23 +103,27 @@ filter() See present/1, substrings/2,
<type>
<v>Handle = handle()</v>
<v>Options = ssl:ssl_options()</v>
- <v>Timeout = inifinity | positive_integer()</v>
+ <v>Timeout = infinity | positive_integer()</v>
</type>
<desc>
<p>Upgrade the connection associated with <c>Handle</c> to a tls connection if possible.</p>
- <p>The upgrade is done in two phases: first the server is asked for permission to upgrade. Second, if the request is acknowledged, the upgrade is performed.</p>
- <p>Error responese from phase one will not affect the current encryption state of the connection. Those responses are:</p>
+ <p>The upgrade is done in two phases: first the server is asked for permission to upgrade. Second, if the request is acknowledged, the upgrade to tls is performed.</p>
+ <p>Error responses from phase one will not affect the current encryption state of the connection. Those responses are:</p>
<taglist>
<tag><c>tls_already_started</c></tag>
<item>The connection is already encrypted. The connection is not affected.</item>
<tag><c>{response,ResponseFromServer}</c></tag>
<item>The upgrade was refused by the LDAP server. The <c>ResponseFromServer</c> is an atom delivered byt the LDAP server explained in section 2.3 of rfc 2830. The connection is not affected, so it is still un-encrypted.</item>
</taglist>
- <p>Errors in the seconde phase will however end the connection:</p>
+ <p>Errors in the second phase will however end the connection:</p>
<taglist>
<tag><c>Error</c></tag>
<item>Any error responded from ssl:connect/3</item>
</taglist>
+ <p>The <c>Timeout</c> parameter is for the actual tls upgrade (phase 2) while the timeout in
+ <seealso marker="#open/2">erl_tar:open/2</seealso> is used for the initial negotiation about
+ upgrade (phase 1).
+ </p>
</desc>
</func>
<func>
@@ -224,9 +228,9 @@ filter() See present/1, substrings/2,
</type>
<desc>
<p> Modify the DN of an entry. <c>DeleteOldRDN</c> indicates
- whether the current RDN should be removed after operation.
- <c>NewSupDN</c> should be "" if the RDN should not be moved or the new parent which
- the RDN will be moved to.</p>
+ whether the current RDN should be removed from the attribute list after the after operation.
+ <c>NewSupDN</c> is the new parent that the RDN shall be moved to. If the old parent should
+ remain as parent, <c>NewSupDN</c> shall be "".</p>
<pre>
modify_dn(Handle, "cn=Bill Valentine, ou=people, o=Example Org, dc=example, dc=com ",
"cn=Bill Jr Valentine", true, "")
@@ -253,6 +257,10 @@ filter() See present/1, substrings/2,
Filter = eldap:substrings("cn", [{any,"V"}]),
search(Handle, [{base, "dc=example, dc=com"}, {filter, Filter}, {attributes, ["cn"]}]),
</pre>
+ <p>The <c>timeout</c> option in the <c>SearchOptions</c> is for the ldap server, while
+ the timeout in <seealso marker="#open/2">erl_tar:open/2</seealso> is used for each
+ individual request in the search operation.
+ </p>
</desc>
</func>
diff --git a/lib/eldap/doc/src/notes.xml b/lib/eldap/doc/src/notes.xml
index f92d100757..e5cbcb26ff 100644
--- a/lib/eldap/doc/src/notes.xml
+++ b/lib/eldap/doc/src/notes.xml
@@ -30,6 +30,35 @@
</header>
<p>This document describes the changes made to the Eldap application.</p>
+<section><title>Eldap 1.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed that eldap:open did not use the Timeout parameter
+ when calling ssl:connect. (Thanks Wiesław Bieniek for
+ reporting)</p>
+ <p>
+ Own Id: OTP-12311</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Added the LDAP filter <c>extensibleMatch</c>.</p>
+ <p>
+ Own Id: OTP-12174</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Eldap 1.0.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/eldap/src/eldap.erl b/lib/eldap/src/eldap.erl
index 9f7aca287b..80718bc106 100644
--- a/lib/eldap/src/eldap.erl
+++ b/lib/eldap/src/eldap.erl
@@ -107,7 +107,8 @@ getopts(Handle, OptNames) when is_pid(Handle), is_list(OptNames) ->
%%% --------------------------------------------------------------------
close(Handle) when is_pid(Handle) ->
- send(Handle, close).
+ send(Handle, close),
+ ok.
%%% --------------------------------------------------------------------
%%% Set who we should link ourselves to
@@ -394,7 +395,7 @@ parse_args([{port, Port}|T], Cpid, Data) when is_integer(Port) ->
parse_args([{timeout, Timeout}|T], Cpid, Data) when is_integer(Timeout),Timeout>0 ->
parse_args(T, Cpid, Data#eldap{timeout = Timeout});
parse_args([{anon_auth, true}|T], Cpid, Data) ->
- parse_args(T, Cpid, Data#eldap{anon_auth = false});
+ parse_args(T, Cpid, Data#eldap{anon_auth = true});
parse_args([{anon_auth, _}|T], Cpid, Data) ->
parse_args(T, Cpid, Data);
parse_args([{ssl, true}|T], Cpid, Data) ->
diff --git a/lib/eldap/test/Makefile b/lib/eldap/test/Makefile
index 24e71cebaa..28a7a107e1 100644
--- a/lib/eldap/test/Makefile
+++ b/lib/eldap/test/Makefile
@@ -28,8 +28,9 @@ INCLUDES= -I. -I ../include
# ----------------------------------------------------
MODULES= \
- eldap_connections_SUITE \
- eldap_basic_SUITE
+ eldap_basic_SUITE \
+ make_certs
+
ERL_FILES= $(MODULES:%=%.erl)
diff --git a/lib/eldap/test/eldap_basic_SUITE.erl b/lib/eldap/test/eldap_basic_SUITE.erl
index d87f3ac4ac..137c61b2d9 100644
--- a/lib/eldap/test/eldap_basic_SUITE.erl
+++ b/lib/eldap/test/eldap_basic_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012-2013. 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
@@ -24,305 +24,919 @@
%%-include_lib("common_test/include/ct.hrl").
-include_lib("test_server/include/test_server.hrl").
-include_lib("eldap/include/eldap.hrl").
+-include_lib("eldap/ebin/ELDAPv3.hrl").
+
-define(TIMEOUT, 120000). % 2 min
+all() ->
+ [app,
+ appup,
+ {group, encode_decode},
+ {group, return_values},
+ {group, v4_connections},
+ {group, v6_connections},
+ {group, plain_api},
+ {group, ssl_api},
+ {group, start_tls_api}
+ ].
+
+groups() ->
+ [{encode_decode, [], [encode,
+ decode
+ ]},
+ {plain_api, [], [{group,api}]},
+ {ssl_api, [], [{group,api}, start_tls_on_ssl_should_fail]},
+ {start_tls_api, [], [{group,api}, start_tls_twice_should_fail]},
+
+ {api, [], [{group,api_not_bound},
+ {group,api_bound}]},
+
+ {api_not_bound, [], [elementary_search, search_non_existant,
+ add_when_not_bound,
+ bind]},
+ {api_bound, [], [add_when_bound,
+ add_already_exists,
+ more_add,
+ search_filter_equalityMatch,
+ search_filter_substring_any,
+ search_filter_initial,
+ search_filter_final,
+ search_filter_and,
+ search_filter_or,
+ search_filter_and_not,
+ search_two_hits,
+ modify,
+ delete,
+ modify_dn_delete_old,
+ modify_dn_keep_old]},
+ {v4_connections, [], connection_tests()},
+ {v6_connections, [], connection_tests()},
+ {return_values, [], [open_ret_val_success,
+ open_ret_val_error,
+ close_ret_val]}
+ ].
+
+connection_tests() ->
+ [tcp_connection,
+ tcp_connection_option,
+ ssl_connection,
+ client_side_start_tls_timeout,
+ client_side_bind_timeout,
+ client_side_add_timeout,
+ client_side_search_timeout
+ ].
+
+
+
init_per_suite(Config) ->
- StartSsl = try ssl:start()
- catch
- Error:Reason ->
- {skip, lists:flatten(io_lib:format("eldap init_per_suite failed to start ssl Error=~p Reason=~p", [Error, Reason]))}
- end,
- case StartSsl of
- ok ->
- chk_config(ldap_server, {"localhost",9876},
- chk_config(ldaps_server, {"localhost",9877},
- Config));
- _ ->
- StartSsl
- end.
+ SSL_available = init_ssl_certs_et_al(Config),
+ LDAP_server = find_first_server(false, [{config,eldap_server}, {config,ldap_server}, {"localhost",9876}]),
+ LDAPS_server =
+ case SSL_available of
+ true ->
+ find_first_server(true, [{config,ldaps_server}, {"localhost",9877}]);
+ false ->
+ undefined
+ end,
+ [{ssl_available, SSL_available},
+ {ldap_server, LDAP_server},
+ {ldaps_server, LDAPS_server} | Config].
end_per_suite(_Config) ->
- ok.
-
-init_per_testcase(_TestCase, Config0) ->
- {EldapHost,Port} = proplists:get_value(ldap_server,Config0),
- try
- {ok, Handle} = eldap:open([EldapHost], [{port,Port}]),
- ok = eldap:simple_bind(Handle, "cn=Manager,dc=ericsson,dc=se", "hejsan"),
- {ok, MyHost} = inet:gethostname(),
- Path = "dc="++MyHost++",dc=ericsson,dc=se",
- eldap:add(Handle,"dc=ericsson,dc=se",
- [{"objectclass", ["dcObject", "organization"]},
- {"dc", ["ericsson"]}, {"o", ["Testing"]}]),
- eldap:add(Handle,Path,
- [{"objectclass", ["dcObject", "organization"]},
- {"dc", [MyHost]}, {"o", ["Test machine"]}]),
- [{eldap_path,Path}|Config0]
- catch error:{badmatch,Error} ->
- io:format("Eldap init error ~p~n ~p~n",[Error, erlang:get_stacktrace()]),
- {skip, lists:flatten(io_lib:format("Ldap init failed with host ~p:~p. Error=~p", [EldapHost,Port,Error]))}
+ ssl:stop().
+
+
+init_per_group(return_values, Config) ->
+ case ?config(ldap_server,Config) of
+ undefined ->
+ {skip, "LDAP server not availble"};
+ {Host,Port} ->
+ ct:comment("ldap://~s:~p",[Host,Port]),
+ Config
+ end;
+init_per_group(plain_api, Config0) ->
+ case ?config(ldap_server,Config0) of
+ undefined ->
+ {skip, "LDAP server not availble"};
+ Server = {Host,Port} ->
+ ct:comment("ldap://~s:~p",[Host,Port]),
+ initialize_db([{server,Server}, {ssl_flag,false}, {start_tls,false} | Config0])
+ end;
+init_per_group(ssl_api, Config0) ->
+ case ?config(ldaps_server,Config0) of
+ undefined ->
+ {skip, "LDAPS server not availble"};
+ Server = {Host,Port} ->
+ ct:comment("ldaps://~s:~p",[Host,Port]),
+ initialize_db([{server,Server}, {ssl_flag,true}, {start_tls,false} | Config0])
+ end;
+init_per_group(start_tls_api, Config0) ->
+ case {?config(ldap_server,Config0), ?config(ssl_available,Config0)} of
+ {undefined,true} ->
+ {skip, "LDAP server not availble"};
+ {_,false} ->
+ {skip, "TLS not availble"};
+ {Server={Host,Port}, true} ->
+ ct:comment("ldap://~s:~p + start_tls",[Host,Port]),
+ Config = [{server,Server}, {ssl_flag,false} | Config0],
+ case supported_extension("1.3.6.1.4.1.1466.20037", Config) of
+ true -> initialize_db([{start_tls,true} | Config]);
+ false -> {skip, "start_tls not supported according to the server"}
+ end
+ end;
+init_per_group(v4_connections, Config) ->
+ [{tcp_listen_opts, [{reuseaddr, true}]},
+ {listen_host, "localhost"},
+ {tcp_connect_opts, []}
+ | Config];
+init_per_group(v6_connections, Config) ->
+ {ok, Hostname} = inet:gethostname(),
+ case lists:member(list_to_atom(Hostname), ct:get_config(ipv6_hosts,[])) of
+ true ->
+ [{tcp_listen_opts, [inet6,{reuseaddr, true}]},
+ {listen_host, "::"},
+ {tcp_connect_opts, [{tcpopts,[inet6]}]}
+ | Config];
+ false ->
+ {skip, io_lib:format("~p is not an ipv6_host",[Hostname])}
+ end;
+init_per_group(_, Config) ->
+ Config.
+
+end_per_group(plain_api, Config) -> clear_db(Config);
+end_per_group(ssl_api, Config) -> clear_db(Config);
+end_per_group(start_tls_api, Config) -> clear_db(Config);
+end_per_group(_Group, Config) -> Config.
+
+
+init_per_testcase(ssl_connection, Config) ->
+ case ?config(ssl_available,Config) of
+ true ->
+ SSL_Port = 9999,
+ CertFile = filename:join(?config(data_dir,Config), "certs/server/cert.pem"),
+ KeyFile = filename:join(?config(data_dir,Config), "certs/server/key.pem"),
+
+ Parent = self(),
+ Listener = spawn_link(
+ fun() ->
+ case ssl:listen(SSL_Port, [{certfile, CertFile},
+ {keyfile, KeyFile}
+ | ?config(tcp_listen_opts,Config)
+ ]) of
+ {ok,SSL_LSock} ->
+ Parent ! {ok,self()},
+ (fun L() ->
+ ct:log("ssl server waiting for connections...",[]),
+ {ok, S} = ssl:transport_accept(SSL_LSock),
+ ct:log("ssl:transport_accept/1 ok",[]),
+ ok = ssl:ssl_accept(S),
+ ct:log("ssl:ssl_accept/1 ok",[]),
+ L()
+ end)();
+ Other ->
+ Parent ! {not_ok,Other,self()}
+ end
+ end),
+ receive
+ {ok,Listener} ->
+ ct:log("SSL listening to port ~p (process ~p)",[SSL_Port, Listener]),
+ [{ssl_listener,Listener},
+ {ssl_listen_port,SSL_Port},
+ {ssl_connect_opts,[]}
+ | Config];
+ {no_ok,SSL_Other,Listener} ->
+ ct:log("ssl:listen on port ~p failed: ~p",[SSL_Port,SSL_Other]),
+ {fail, "ssl:listen/2 failed"}
+ after 5000 ->
+ {fail, "Waiting for ssl:listen timeout"}
+ end;
+ false ->
+ {skip, "ssl not available"}
+ end;
+
+init_per_testcase(TC, Config) ->
+ case lists:member(TC,connection_tests()) of
+ true ->
+ case gen_tcp:listen(0, proplists:get_value(tcp_listen_opts,Config)) of
+ {ok,LSock} ->
+ {ok,{_,Port}} = inet:sockname(LSock),
+ [{listen_socket,LSock},
+ {listen_port,Port}
+ | Config];
+ Other ->
+ {fail, Other}
+ end;
+
+ false ->
+ case proplists:get_value(name,?config(tc_group_properties, Config)) of
+ api_not_bound ->
+ {ok,H} = open(Config),
+ [{handle,H} | Config];
+ api_bound ->
+ {ok,H} = open(Config),
+ ok = eldap:simple_bind(H,
+ "cn=Manager,dc=ericsson,dc=se",
+ "hejsan"),
+ [{handle,H} | Config];
+ _Name ->
+ Config
+ end
end.
-end_per_testcase(_TestCase, Config) ->
- {EHost, Port} = proplists:get_value(ldap_server, Config),
- Path = proplists:get_value(eldap_path, Config),
- {ok, H} = eldap:open([EHost], [{port, Port}]),
- ok = eldap:simple_bind(H, "cn=Manager,dc=ericsson,dc=se", "hejsan"),
- case eldap:search(H, [{base, Path},
- {filter, eldap:present("objectclass")},
- {scope, eldap:wholeSubtree()}])
- of
- {ok, {eldap_search_result, Entries, _}} ->
- [ok = eldap:delete(H, Entry) || {eldap_entry, Entry, _} <- Entries];
- _ -> ignore
- end,
+end_per_testcase(_, Config) ->
+ catch gen_tcp:close( proplists:get_value(listen_socket, Config) ),
+ catch eldap:close( proplists:get_value(handle,Config) ).
- ok.
-%% suite() ->
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%
+%%% Test cases
+%%%
-all() ->
- [app,
- appup,
- api,
- ssl_api,
- start_tls,
- tls_operations,
- start_tls_twice,
- start_tls_on_ssl
- ].
-
-app(doc) -> "Test that the eldap app file is ok";
-app(suite) -> [];
+%%%----------------------------------------------------------------
+%%% Test that the eldap app file is ok
app(Config) when is_list(Config) ->
ok = test_server:app_test(eldap).
-%% Test that the eldap appup file is ok
+%%%----------------------------------------------------------------
+%%% Test that the eldap appup file is ok
appup(Config) when is_list(Config) ->
ok = test_server:appup_test(eldap).
-api(doc) -> "Basic test that all api functions works as expected";
-api(suite) -> [];
-api(Config) ->
- {Host,Port} = proplists:get_value(ldap_server, Config),
- {ok, H} = eldap:open([Host], [{port,Port}
- ,{log,fun(Lvl,Fmt,Args)-> io:format("~p: ~s",[Lvl,io_lib:format(Fmt,Args)]) end}
- ]),
- %% {ok, H} = eldap:open([Host], [{port,Port+1}, {ssl, true}]),
- do_api_checks(H, Config),
- eldap:close(H),
- ok.
+%%%----------------------------------------------------------------
+open_ret_val_success(Config) ->
+ {Host,Port} = ?config(ldap_server,Config),
+ {ok,H} = eldap:open([Host], [{port,Port}]),
+ catch eldap:close(H).
+
+%%%----------------------------------------------------------------
+open_ret_val_error(_Config) ->
+ {error,_} = eldap:open(["nohost.example.com"], [{port,65535}]).
+
+%%%----------------------------------------------------------------
+close_ret_val(Config) ->
+ {Host,Port} = ?config(ldap_server,Config),
+ {ok,H} = eldap:open([Host], [{port,Port}]),
+ ok = eldap:close(H).
+
+%%%----------------------------------------------------------------
+tcp_connection(Config) ->
+ Host = proplists:get_value(listen_host, Config),
+ Port = proplists:get_value(listen_port, Config),
+ Opts = proplists:get_value(tcp_connect_opts, Config),
+ case eldap:open([Host], [{port,Port}|Opts]) of
+ {ok,_H} ->
+ Sl = proplists:get_value(listen_socket, Config),
+ case gen_tcp:accept(Sl,1000) of
+ {ok,_S} -> ok;
+ {error,timeout} -> ct:fail("server side accept timeout",[]);
+ Other -> ct:fail("gen_tdp:accept failed: ~p",[Other])
+ end;
+ Other -> ct:fail("eldap:open failed: ~p",[Other])
+ end.
+%%%----------------------------------------------------------------
+ssl_connection(Config) ->
+ Host = proplists:get_value(listen_host, Config),
+ Port = proplists:get_value(ssl_listen_port, Config),
+ Opts = proplists:get_value(tcp_connect_opts, Config),
+ SSLOpts = proplists:get_value(ssl_connect_opts, Config),
+ case eldap:open([Host], [{port,Port},
+ {ssl,true},
+ {timeout,5000},
+ {sslopts,SSLOpts}|Opts]) of
+ {ok,_H} -> ok;
+ Other -> ct:fail("eldap:open failed: ~p",[Other])
+ end.
-ssl_api(doc) -> "Basic test that all api functions works as expected";
-ssl_api(suite) -> [];
-ssl_api(Config) ->
- {Host,Port} = proplists:get_value(ldaps_server, Config),
- {ok, H} = eldap:open([Host], [{port,Port}, {ssl,true}]),
- do_api_checks(H, Config),
- eldap:close(H),
- ok.
+%%%----------------------------------------------------------------
+client_side_add_timeout(Config) ->
+ client_timeout(
+ fun(H) ->
+ eldap:add(H, "cn=Foo Bar,dc=host,dc=ericsson,dc=se",
+ [{"objectclass", ["person"]},
+ {"cn", ["Foo Bar"]},
+ {"sn", ["Bar"]},
+ {"telephoneNumber", ["555-1232", "555-5432"]}])
+ end, Config).
+
+%%%----------------------------------------------------------------
+client_side_bind_timeout(Config) ->
+ client_timeout(
+ fun(H) ->
+ eldap:simple_bind(H, anon, anon)
+ end, Config).
+
+%%%----------------------------------------------------------------
+client_side_search_timeout(Config) ->
+ client_timeout(
+ fun(H) ->
+ eldap:search(H, [{base,"dc=host,dc=ericsson,dc=se"},
+ {filter, eldap:present("objectclass")},
+ {scope, eldap:wholeSubtree()}])
+ end, Config).
+
+%%%----------------------------------------------------------------
+client_side_start_tls_timeout(Config) ->
+ client_timeout(
+ fun(H) ->
+ eldap:start_tls(H, [])
+ end, Config).
+
+%%%----------------------------------------------------------------
+tcp_connection_option(Config) ->
+ Host = proplists:get_value(listen_host, Config),
+ Port = proplists:get_value(listen_port, Config),
+ Opts = proplists:get_value(tcp_connect_opts, Config),
+ Sl = proplists:get_value(listen_socket, Config),
+
+ %% Make an option value to test. The option must be implemented on all
+ %% platforms that we test on. Must check what the default value is
+ %% so we don't happen to choose that particular value.
+ {ok,[{linger,DefaultLinger}]} = inet:getopts(Sl, [linger]),
+ TestLinger = case DefaultLinger of
+ {false,_} -> {true,5};
+ {true,_} -> {false,0}
+ end,
+
+ case catch eldap:open([Host],
+ [{port,Port},{tcpopts,[{linger,TestLinger}]}|Opts]) of
+ {ok,H} ->
+ case gen_tcp:accept(Sl,1000) of
+ {ok,_} ->
+ case eldap:getopts(H, [{tcpopts,[linger]}]) of
+ {ok,[{tcpopts,[{linger,ActualLinger}]}]} ->
+ case ActualLinger of
+ TestLinger ->
+ ok;
+ DefaultLinger ->
+ ct:fail("eldap:getopts: 'linger' didn't change,"
+ " got ~p (=default) expected ~p",
+ [ActualLinger,TestLinger]);
+ _ ->
+ ct:fail("eldap:getopts: bad 'linger', got ~p expected ~p",
+ [ActualLinger,TestLinger])
+ end;
+ Other ->
+ ct:fail("eldap:getopts: bad result ~p",[Other])
+ end;
+ {error,timeout} ->
+ ct:fail("server side accept timeout",[])
+ end;
+
+ Other ->
+ ct:fail("eldap:open failed: ~p",[Other])
+ end.
-start_tls(doc) -> "Test that an existing (tcp) connection can be upgraded to tls";
-start_tls(suite) -> [];
-start_tls(Config) ->
- {Host,Port} = proplists:get_value(ldap_server, Config),
- {ok, H} = eldap:open([Host], [{port,Port}]),
- ok = eldap:start_tls(H, [
- {keyfile, filename:join([proplists:get_value(data_dir,Config),
- "certs/client/key.pem"])}
- ]),
- eldap:close(H).
+%%%----------------------------------------------------------------
+%%% Basic test that all api functions works as expected
+
+%%%----------------------------------------------------------------
+elementary_search(Config) ->
+ {ok, #eldap_search_result{entries=[_]}} =
+ eldap:search(?config(handle,Config),
+ #eldap_search{base = ?config(eldap_path, Config),
+ filter= eldap:present("objectclass"),
+ scope = eldap:wholeSubtree()}).
+
+%%%----------------------------------------------------------------
+search_non_existant(Config) ->
+ {error, noSuchObject} =
+ eldap:search(?config(handle,Config),
+ #eldap_search{base = "cn=Bar," ++ ?config(eldap_path, Config),
+ filter= eldap:present("objectclass"),
+ scope = eldap:wholeSubtree()}).
+
+%%%----------------------------------------------------------------
+add_when_not_bound(Config) ->
+ {error, _} = eldap:add(?config(handle,Config),
+ "cn=Jonas Jonsson," ++ ?config(eldap_path, Config),
+ [{"objectclass", ["person"]},
+ {"cn", ["Jonas Jonsson"]},
+ {"sn", ["Jonsson"]}]).
+
+%%%----------------------------------------------------------------
+bind(Config) ->
+ ok = eldap:simple_bind(?config(handle,Config),
+ "cn=Manager,dc=ericsson,dc=se",
+ "hejsan").
+
+%%%----------------------------------------------------------------
+add_when_bound(Config) ->
+ ok = eldap:add(?config(handle, Config),
+ "cn=Jonas Jonsson," ++ ?config(eldap_path, Config),
+ [{"objectclass", ["person"]},
+ {"cn", ["Jonas Jonsson"]},
+ {"sn", ["Jonsson"]}]).
+
+%%%----------------------------------------------------------------
+add_already_exists(Config) ->
+ {error, entryAlreadyExists} =
+ eldap:add(?config(handle, Config),
+ "cn=Jonas Jonsson," ++ ?config(eldap_path, Config),
+ [{"objectclass", ["person"]},
+ {"cn", ["Jonas Jonsson"]},
+ {"sn", ["Jonsson"]}]).
+
+%%%----------------------------------------------------------------
+more_add(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ ok = eldap:add(H, "cn=Foo Bar," ++ BasePath,
+ [{"objectclass", ["person"]},
+ {"cn", ["Foo Bar"]},
+ {"sn", ["Bar"]},
+ {"telephoneNumber", ["555-1232", "555-5432"]}]),
+ ok = eldap:add(H, "ou=Team," ++ BasePath,
+ [{"objectclass", ["organizationalUnit"]},
+ {"ou", ["Team"]}]).
-tls_operations(doc) -> "Test that an upgraded connection is usable for ldap stuff";
-tls_operations(suite) -> [];
-tls_operations(Config) ->
- {Host,Port} = proplists:get_value(ldap_server, Config),
- {ok, H} = eldap:open([Host], [{port,Port}]),
- ok = eldap:start_tls(H, [
- {keyfile, filename:join([proplists:get_value(data_dir,Config),
- "certs/client/key.pem"])}
- ]),
- do_api_checks(H, Config),
+%%%----------------------------------------------------------------
+search_filter_equalityMatch(Config) ->
+ BasePath = ?config(eldap_path, Config),
+ ExpectedDN = "cn=Jonas Jonsson," ++ BasePath,
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=ExpectedDN}]}} =
+ eldap:search(?config(handle, Config),
+ #eldap_search{base = BasePath,
+ filter = eldap:equalityMatch("sn", "Jonsson"),
+ scope=eldap:singleLevel()}).
+
+%%%----------------------------------------------------------------
+search_filter_substring_any(Config) ->
+ BasePath = ?config(eldap_path, Config),
+ ExpectedDN = "cn=Jonas Jonsson," ++ BasePath,
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=ExpectedDN}]}} =
+ eldap:search(?config(handle, Config),
+ #eldap_search{base = BasePath,
+ filter = eldap:substrings("sn", [{any, "ss"}]),
+ scope=eldap:singleLevel()}).
+
+%%%----------------------------------------------------------------
+search_filter_initial(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ ExpectedDN = "cn=Foo Bar," ++ BasePath,
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=ExpectedDN}]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:substrings("sn", [{initial, "B"}]),
+ scope=eldap:singleLevel()}).
+
+%%%----------------------------------------------------------------
+search_filter_final(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ ExpectedDN = "cn=Foo Bar," ++ BasePath,
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=ExpectedDN}]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:substrings("sn", [{final, "r"}]),
+ scope=eldap:singleLevel()}).
+
+%%%----------------------------------------------------------------
+search_filter_and(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ ExpectedDN = "cn=Foo Bar," ++ BasePath,
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=ExpectedDN}]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:'and'([eldap:substrings("sn", [{any, "a"}]),
+ eldap:equalityMatch("cn","Foo Bar")]),
+ scope=eldap:singleLevel()}).
+
+%%%----------------------------------------------------------------
+search_filter_or(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ ExpectedDNs = lists:sort(["cn=Foo Bar," ++ BasePath,
+ "ou=Team," ++ BasePath]),
+ {ok, #eldap_search_result{entries=Es}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:'or'([eldap:substrings("sn", [{any, "a"}]),
+ eldap:equalityMatch("ou","Team")]),
+ scope=eldap:singleLevel()}),
+ ExpectedDNs = lists:sort([DN || #eldap_entry{object_name=DN} <- Es]).
+
+%%%----------------------------------------------------------------
+search_filter_and_not(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ {ok, #eldap_search_result{entries=[]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:'and'([eldap:substrings("sn", [{any, "a"}]),
+ eldap:'not'(
+ eldap:equalityMatch("cn","Foo Bar")
+ )]),
+ scope=eldap:singleLevel()}).
+
+%%%----------------------------------------------------------------
+search_two_hits(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ DN1 = "cn=Santa Claus," ++ BasePath,
+ DN2 = "cn=Jultomten," ++ BasePath,
+ %% Add two objects:
+ ok = eldap:add(H, DN1,
+ [{"objectclass", ["person"]},
+ {"cn", ["Santa Claus"]},
+ {"sn", ["Santa"]},
+ {"description", ["USA"]}]),
+ ok = eldap:add(H, DN2,
+ [{"objectclass", ["person"]},
+ {"cn", ["Jultomten"]},
+ {"sn", ["Tomten"]},
+ {"description", ["Sweden"]}]),
+
+ %% Search for them:
+ {ok, #eldap_search_result{entries=Es}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:present("description"),
+ scope=eldap:singleLevel()}),
+
+ %% And check that they are the expected ones:
+ ExpectedDNs = lists:sort([DN1, DN2]),
+ ExpectedDNs = lists:sort([D || #eldap_entry{object_name=D} <- Es]),
+
+ %% Restore the database:
+ [ok=eldap:delete(H,DN) || DN <- ExpectedDNs].
+
+%%%----------------------------------------------------------------
+modify(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ %% The object to modify
+ DN = "cn=Foo Bar," ++ BasePath,
+
+ %% Save a copy to restore later:
+ {ok,OriginalAttrs} = attributes(H, DN),
+
+ %% Do a change
+ Mod = [eldap:mod_replace("telephoneNumber", ["555-12345"]),
+ eldap:mod_add("description", ["Nice guy"])],
+ ok = eldap:modify(H, DN, Mod),
+
+ %% Check that the object was changed
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=DN}]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:equalityMatch("telephoneNumber", "555-12345"),
+ scope=eldap:singleLevel()}),
+
+ %% Do another type of change
+ ok = eldap:modify(H, DN, [eldap:mod_delete("telephoneNumber", [])]),
+ %% and check that it worked by repeating the test above
+ {ok, #eldap_search_result{entries=[]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:equalityMatch("telephoneNumber", "555-12345"),
+ scope=eldap:singleLevel()}),
+ %% restore the orignal version:
+ restore_original_object(H, DN, OriginalAttrs).
+
+%%%----------------------------------------------------------------
+delete(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ %% The element to play with:
+ DN = "cn=Jonas Jonsson," ++ BasePath,
+
+ %% Prove that the element is present before deletion
+ {ok,OriginalAttrs} = attributes(H, DN),
+
+ %% Do what the test has to do:
+ ok = eldap:delete(H, DN),
+ %% check that it really was deleted:
+ {error, noSuchObject} = eldap:delete(H, DN),
+
+ %% And restore the object for subsequent tests
+ restore_original_object(H, DN, OriginalAttrs).
+
+%%%----------------------------------------------------------------
+modify_dn_delete_old(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ OrigCN = "Foo Bar",
+ OriginalRDN = "cn="++OrigCN,
+ DN = OriginalRDN ++ "," ++ BasePath,
+ NewCN = "Niclas Andre",
+ NewRDN = "cn="++NewCN,
+ NewDN = NewRDN ++ "," ++BasePath,
+
+ %% Check that the object to modify_dn of exists:
+ {ok,OriginalAttrs} = attributes(H, DN),
+ CN_orig = lists:sort(proplists:get_value("cn",OriginalAttrs)),
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=DN}]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:substrings("sn", [{any, "a"}]),
+ scope = eldap:singleLevel()}),
+
+ %% Modify and delete the old one:
+ ok = eldap:modify_dn(H, DN, NewRDN, true, ""),
+
+ %% Check that DN was modified and the old one was deleted:
+ {ok,NewAttrs} = attributes(H, NewDN),
+ CN_new = lists:sort(proplists:get_value("cn",NewAttrs)),
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=NewDN}]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:substrings("sn", [{any, "a"}]),
+ scope = eldap:singleLevel()}),
+ %% What we expect:
+ CN_new = lists:sort([NewCN | CN_orig -- [OrigCN]]),
+
+ %% Change back:
+ ok = eldap:modify_dn(H, NewDN, OriginalRDN, true, ""),
+
+ %% Check that DN was modified and the new one was deleted:
+ {ok,SameAsOriginalAttrs} = attributes(H, DN),
+ CN_orig = lists:sort(proplists:get_value("cn",SameAsOriginalAttrs)),
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=DN}]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:substrings("sn", [{any, "a"}]),
+ scope = eldap:singleLevel()}).
+
+%%%----------------------------------------------------------------
+modify_dn_keep_old(Config) ->
+ H = ?config(handle, Config),
+ BasePath = ?config(eldap_path, Config),
+ OriginalRDN = "cn=Foo Bar",
+ DN = OriginalRDN ++ "," ++ BasePath,
+ NewCN = "Niclas Andre",
+ NewRDN = "cn="++NewCN,
+ NewDN = NewRDN ++ "," ++BasePath,
+
+ %% Check that the object to modify_dn of exists but the new one does not:
+ {ok,OriginalAttrs} = attributes(H, DN),
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=DN}]}} =
+ eldap:search(H,
+ #eldap_search{base = BasePath,
+ filter = eldap:substrings("sn", [{any, "a"}]),
+ scope = eldap:singleLevel()}),
+
+ %% Modify but keep the old "cn" attr:
+ ok = eldap:modify_dn(H, DN, NewRDN, false, ""),
+
+ %% Check that DN was modified and the old CN entry is not deleted:
+ {ok,NewAttrs} = attributes(H, NewDN),
+ CN_orig = proplists:get_value("cn",OriginalAttrs),
+ CN_new = proplists:get_value("cn",NewAttrs),
+ Expected = lists:sort([NewCN|CN_orig]),
+ Expected = lists:sort(CN_new),
+
+ %% Restore db:
+ ok = eldap:delete(H, NewDN),
+ restore_original_object(H, DN, OriginalAttrs).
+
+%%%----------------------------------------------------------------
+%%% Test that start_tls on an already upgraded connection makes no noise
+start_tls_twice_should_fail(Config) ->
+ {ok,H} = open_bind(Config),
+ {error,tls_already_started} = eldap:start_tls(H, []),
eldap:close(H).
-start_tls_twice(doc) -> "Test that start_tls on an already upgraded connection fails";
-start_tls_twice(suite) -> [];
-start_tls_twice(Config) ->
- {Host,Port} = proplists:get_value(ldap_server, Config),
- {ok, H} = eldap:open([Host], [{port,Port}]),
- ok = eldap:start_tls(H, []),
+%%%----------------------------------------------------------------
+%%% Test that start_tls on an ldaps connection fails
+start_tls_on_ssl_should_fail(Config) ->
+ {ok,H} = open_bind(Config),
{error,tls_already_started} = eldap:start_tls(H, []),
- do_api_checks(H, Config),
eldap:close(H).
+%%%----------------------------------------------------------------
+encode(_Config) ->
+ {ok,Bin} = 'ELDAPv3':encode('AddRequest', #'AddRequest'{entry="hejHopp" ,attributes=[]} ),
+ Expected = <<104,11,4,7,104,101,106,72,111,112,112,48,0>>,
+ case Bin of
+ Expected -> ok;
+ _ -> ct:log("Encoded erroneously to:~n~p~nExpected:~n~p",[Bin,Expected]),
+ {fail, "Bad encode"}
+ end.
+
+%%%----------------------------------------------------------------
+decode(_Config) ->
+ {ok,Res} = 'ELDAPv3':decode('AddRequest', <<104,11,4,7,104,101,106,72,111,112,112,48,0>>),
+ ct:log("Res = ~p", [Res]),
+ Expected = #'AddRequest'{entry = "hejHopp",attributes = []},
+ case Res of
+ Expected -> ok;
+ #'AddRequest'{entry= <<"hejHopp">>, attributes=[]} ->
+ {fail, "decoded to (correct) binary!!"};
+ _ ->
+ {fail, "Bad decode"}
+ end.
-start_tls_on_ssl(doc) -> "Test that start_tls on an ldaps connection fails";
-start_tls_on_ssl(suite) -> [];
-start_tls_on_ssl(Config) ->
- {Host,Port} = proplists:get_value(ldaps_server, Config),
- {ok, H} = eldap:open([Host], [{port,Port}, {ssl,true}]),
- {error,tls_already_started} = eldap:start_tls(H, []),
- do_api_checks(H, Config),
- eldap:close(H).
-%%%--------------------------------------------------------------------------------
-chk_config(Key, Default, Config) ->
- case catch ct:get_config(ldap_server, undefined) of
- undefined -> [{Key,Default} | Config ];
- {'EXIT',_} -> [{Key,Default} | Config ];
- Value -> [{Key,Value} | Config]
+%%%****************************************************************
+%%% Private
+
+attributes(H, DN) ->
+ case eldap:search(H,
+ #eldap_search{base = DN,
+ filter= eldap:present("objectclass"),
+ scope = eldap:wholeSubtree()}) of
+ {ok, #eldap_search_result{entries=[#eldap_entry{object_name=DN,
+ attributes=OriginalAttrs}]}} ->
+ {ok, OriginalAttrs};
+ Other ->
+ Other
end.
+restore_original_object(H, DN, Attrs) ->
+ eldap:delete(H, DN),
+ ok = eldap:add(H, DN, Attrs).
+
+
+find_first_server(UseSSL, [{config,Key}|Ss]) ->
+ case ct:get_config(Key) of
+ {Host,Port} ->
+ ct:log("find_first_server config ~p -> ~p",[Key,{Host,Port}]),
+ find_first_server(UseSSL, [{Host,Port}|Ss]);
+ undefined ->
+ ct:log("find_first_server config ~p is undefined",[Key]),
+ find_first_server(UseSSL, Ss)
+ end;
+find_first_server(UseSSL, [{Host,Port}|Ss]) ->
+ case eldap:open([Host],[{port,Port},{ssl,UseSSL}]) of
+ {ok,H} when UseSSL==false, Ss=/=[] ->
+ case eldap:start_tls(H,[]) of
+ ok ->
+ ct:log("find_first_server ~p UseSSL=~p -> ok",[{Host,Port},UseSSL]),
+ eldap:close(H),
+ {Host,Port};
+ Res ->
+ ct:log("find_first_server ~p UseSSL=~p failed with~n~p~nSave as spare host.",[{Host,Port},UseSSL,Res]),
+ eldap:close(H),
+ find_first_server(UseSSL, Ss++[{spare_host,Host,Port}])
+ end;
+ {ok,H} ->
+ ct:log("find_first_server ~p UseSSL=~p -> ok",[{Host,Port},UseSSL]),
+ eldap:close(H),
+ {Host,Port};
+ Res ->
+ ct:log("find_first_server ~p UseSSL=~p failed with~n~p",[{Host,Port},UseSSL,Res]),
+ find_first_server(UseSSL, Ss)
+ end;
+find_first_server(false, [{spare_host,Host,Port}|_]) ->
+ ct:log("find_first_server can't find start_tls host, use the spare non-start_tls host for plain ldap: ~p",[{Host,Port}]),
+ {Host,Port};
+find_first_server(_, []) ->
+ ct:log("find_first_server, nothing left to try",[]),
+ undefined.
+
+initialize_db(Config) ->
+ case {open_bind(Config), inet:gethostname()} of
+ {{ok,H}, {ok,MyHost}} ->
+ Path = "dc="++MyHost++",dc=ericsson,dc=se",
+ delete_old_contents(H, Path),
+ add_new_contents(H, Path, MyHost),
+ eldap:close(H),
+ [{eldap_path,Path}|Config];
+ Other ->
+ ct:fail("initialize_db failed: ~p",[Other])
+ end.
+clear_db(Config) ->
+ {ok,H} = open_bind(Config),
+ Path = ?config(eldap_path, Config),
+ delete_old_contents(H, Path),
+ eldap:close(H),
+ Config.
-do_api_checks(H, Config) ->
- BasePath = proplists:get_value(eldap_path, Config),
+delete_old_contents(H, Path) ->
+ case eldap:search(H, [{base, Path},
+ {filter, eldap:present("objectclass")},
+ {scope, eldap:wholeSubtree()}])
+ of
+ {ok, #eldap_search_result{entries=Entries}} ->
+ [ok = eldap:delete(H,DN) || #eldap_entry{object_name=DN} <- Entries];
+ _Res ->
+ ignore
+ end.
- All = fun(Where) ->
- eldap:search(H, #eldap_search{base=Where,
- filter=eldap:present("objectclass"),
- scope= eldap:wholeSubtree()})
- end,
- {ok, #eldap_search_result{entries=[_XYZ]}} = All(BasePath),
-%% ct:log("XYZ=~p",[_XYZ]),
- {error, noSuchObject} = All("cn=Bar,"++BasePath),
+add_new_contents(H, Path, MyHost) ->
+ ok(eldap:add(H,"dc=ericsson,dc=se",
+ [{"objectclass", ["dcObject", "organization"]},
+ {"dc", ["ericsson"]},
+ {"o", ["Testing"]}])),
+ ok(eldap:add(H,Path,
+ [{"objectclass", ["dcObject", "organization"]},
+ {"dc", [MyHost]},
+ {"o", ["Test machine"]}])).
- {error, _} = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath,
- [{"objectclass", ["person"]},
- {"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]),
- eldap:simple_bind(H, "cn=Manager,dc=ericsson,dc=se", "hejsan"),
- chk_add(H, BasePath),
- {ok,FB} = chk_search(H, BasePath),
- chk_modify(H, FB),
- chk_delete(H, BasePath),
- chk_modify_dn(H, FB).
+ok({error,entryAlreadyExists}) -> ok;
+ok(X) -> ok=X.
-chk_add(H, BasePath) ->
- ok = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath,
- [{"objectclass", ["person"]},
- {"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]),
- {error, entryAlreadyExists} = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath,
- [{"objectclass", ["person"]},
- {"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]),
- ok = eldap:add(H, "cn=Foo Bar," ++ BasePath,
- [{"objectclass", ["person"]},
- {"cn", ["Foo Bar"]}, {"sn", ["Bar"]}, {"telephoneNumber", ["555-1232", "555-5432"]}]),
- ok = eldap:add(H, "ou=Team," ++ BasePath,
- [{"objectclass", ["organizationalUnit"]},
- {"ou", ["Team"]}]).
-chk_search(H, BasePath) ->
- Search = fun(Filter) ->
- eldap:search(H, #eldap_search{base=BasePath,
- filter=Filter,
- scope=eldap:singleLevel()})
- end,
- JJSR = {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:equalityMatch("sn", "Jonsson")),
- JJSR = Search(eldap:substrings("sn", [{any, "ss"}])),
- FBSR = {ok, #eldap_search_result{entries=[#eldap_entry{object_name=FB}]}} =
- Search(eldap:substrings("sn", [{any, "a"}])),
- FBSR = Search(eldap:substrings("sn", [{initial, "B"}])),
- FBSR = Search(eldap:substrings("sn", [{final, "r"}])),
- F_AND = eldap:'and'([eldap:present("objectclass"), eldap:present("ou")]),
- {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(F_AND),
- F_NOT = eldap:'and'([eldap:present("objectclass"), eldap:'not'(eldap:present("ou"))]),
- {ok, #eldap_search_result{entries=[#eldap_entry{}, #eldap_entry{}]}} = Search(F_NOT),
- {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:extensibleMatch("Bar",[{type,"sn"},{matchingRule,"caseExactMatch"}])),
- {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:extensibleMatch("Bar",[{type,"sn"},{matchingRule,"2.5.13.5"}])),
- {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:extensibleMatch("Bar",[{type,"sn"},{matchingRule,"caseIgnoreMatch"}])),
- {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:extensibleMatch("bar",[{type,"sn"},{matchingRule,"caseIgnoreMatch"}])),
- {ok, #eldap_search_result{entries=[]}} = Search(eldap:extensibleMatch("bar",[{type,"sn"},{matchingRule,"gluffgluff"}])),
- {ok, #eldap_search_result{entries=[]}} = Search(eldap:extensibleMatch("bar",[{type,"sn"},{matchingRule,"caseExactMatch"}])),
- {ok,FB}. %% FIXME
-
-chk_modify(H, FB) ->
- Mod = [eldap:mod_replace("telephoneNumber", ["555-12345"]),
- eldap:mod_add("description", ["Nice guy"])],
- %% io:format("MOD ~p ~p ~n",[FB, Mod]),
- ok = eldap:modify(H, FB, Mod),
- %% DELETE ATTR
- ok = eldap:modify(H, FB, [eldap:mod_delete("telephoneNumber", [])]).
-
-
-chk_delete(H, BasePath) ->
- {error, entryAlreadyExists} = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath,
- [{"objectclass", ["person"]},
- {"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]),
- ok = eldap:delete(H, "cn=Jonas Jonsson," ++ BasePath),
- {error, noSuchObject} = eldap:delete(H, "cn=Jonas Jonsson," ++ BasePath).
-
-chk_modify_dn(H, FB) ->
- ok = eldap:modify_dn(H, FB, "cn=Niclas Andre", true, "").
- %%io:format("Res ~p~n ~p~n",[R, All(BasePath)]).
-
-
-%%%----------------
-add(H, Attr, Value, Path0, Attrs, Class) ->
- Path = case Path0 of
- [] -> Attr ++ "=" ++ Value;
- _ -> Attr ++ "=" ++ Value ++ "," ++ Path0
- end,
- case eldap:add(H, Path, [{"objectclass", Class}, {Attr, [Value]}] ++ Attrs)
- of
- ok -> {ok, Path};
- {error, E = entryAlreadyExists} -> {E, Path};
- R = {error, Reason} ->
- io:format("~p:~p: ~s,~s =>~n ~p~n",
- [?MODULE,?LINE, Attr, Value, R]),
- exit({ldap, add, Reason})
+cond_start_tls(H, Config) ->
+ case ?config(start_tls,Config) of
+ true -> start_tls(H,Config);
+ _ -> Config
end.
+start_tls(H, Config) ->
+ KeyFile = filename:join([?config(data_dir,Config),
+ "certs/client/key.pem"
+ ]),
+ case eldap:start_tls(H, [{keyfile, KeyFile}]) of
+ ok ->
+ [{start_tls_success,true} | Config];
+ Error ->
+ ct:log("Start_tls on ~p failed: ~p",[?config(url,Config) ,Error]),
+ ct:fail("start_tls failed")
+ end.
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% Develop
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-test() ->
- run().
-
-run() ->
- Cases = all(),
- run(Cases).
-
-run(Case) when is_atom(Case) ->
- run([Case]);
-run(Cases) when is_list(Cases) ->
- Run = fun(Test, Config0) ->
- Config = init_per_testcase(Test, Config0),
- try
- io:format("~nTest ~p ... ",[Test]),
- ?MODULE:Test(Config),
- end_per_testcase(Test, Config),
- io:format("ok~n",[])
- catch _:Reason ->
- io:format("~n FAIL (~p): ~p~n ~p~n",
- [Test, Reason, erlang:get_stacktrace()])
- end
- end,
- process_flag(trap_exit, true),
- Pid = spawn_link(fun() ->
- case init_per_suite([]) of
- {skip, Reason} -> io:format("Skip ~s~n",[Reason]);
- Config ->
- try
- [Run(Test, Config) || Test <- Cases]
- catch _:Err ->
- io:format("Error ~p in ~p~n",[Err, erlang:get_stacktrace()])
- end,
- end_per_suite(Config)
- end
- end),
- receive
- {'EXIT', Pid, normal} -> ok;
- Msg -> io:format("Received ~p (~p)~n",[Msg, Pid])
- after 100 -> ok end,
- process_flag(trap_exit, false),
- ok.
+%%%----------------------------------------------------------------
+open_bind(Config) ->
+ {ok,H} = open(Config),
+ ok = eldap:simple_bind(H, "cn=Manager,dc=ericsson,dc=se", "hejsan"),
+ {ok,H}.
+
+open(Config) ->
+ {Host,Port} = ?config(server,Config),
+ SSLflag = ?config(ssl_flag,Config),
+ {ok,H} = eldap:open([Host], [{port,Port},{ssl,SSLflag}]),
+ cond_start_tls(H, Config),
+ {ok,H}.
+
+%%%----------------------------------------------------------------
+supported_extension(OID, Config) ->
+ {ok,H} = open_bind(Config),
+ case eldap:search(H, [{scope, eldap:baseObject()},
+ {filter, eldap:present("objectclass")},
+ {deref, eldap:neverDerefAliases()},
+ {attributes, ["+"]}]) of
+ {ok,R=#eldap_search_result{}} ->
+ eldap:close(H),
+ lists:member(OID,
+ [SE || EE <- R#eldap_search_result.entries,
+ {"supportedExtension",SEs} <- EE#eldap_entry.attributes,
+ SE<-SEs]);
+ _ ->
+ eldap:close(H),
+ false
+ end.
+
+%%%----------------------------------------------------------------
+client_timeout(Fun, Config) ->
+ Host = proplists:get_value(listen_host, Config),
+ Port = proplists:get_value(listen_port, Config),
+ Opts = proplists:get_value(tcp_connect_opts, Config),
+ T = 1000,
+ case eldap:open([Host], [{timeout,T},{port,Port}|Opts]) of
+ {ok,H} ->
+ T0 = now(),
+ {error,{gen_tcp_error,timeout}} = Fun(H),
+ T_op = diff(T0,now()),
+ ct:log("Time = ~p, Timeout spec = ~p",[T_op,T]),
+ if
+ T_op < T ->
+ {fail, "Timeout too early"};
+ true ->
+ ok
+ end;
+
+ Other -> ct:fail("eldap:open failed: ~p",[Other])
+ end.
+
+diff({M1,S1,U1},{M2,S2,U2}) ->
+ ( ((M2-M1)*1000 + (S2-S1))*1000 + (U2-U1) ).
+
+%%%----------------------------------------------------------------
+init_ssl_certs_et_al(Config) ->
+ try ssl:start()
+ of
+ R when R==ok ; R=={error,{already_started,ssl}} ->
+ try make_certs:all("/dev/null",
+ filename:join(?config(data_dir,Config), "certs"))
+ of
+ {ok,_} -> true;
+ Other ->
+ ct:comment("make_certs failed"),
+ ct:log("make_certs failed ~p", [Other]),
+ false
+ catch
+ C:E ->
+ ct:comment("make_certs crashed"),
+ ct:log("make_certs failed ~p:~p", [C,E]),
+ false
+ end;
+ _ ->
+ false
+ catch
+ Error:Reason ->
+ ct:comment("ssl failed to start"),
+ ct:log("init_per_suite failed to start ssl Error=~p Reason=~p", [Error, Reason]),
+ false
+ end.
diff --git a/lib/eldap/test/eldap_basic_SUITE_data/RAND b/lib/eldap/test/eldap_basic_SUITE_data/RAND
new file mode 100644
index 0000000000..70997bd01f
--- /dev/null
+++ b/lib/eldap/test/eldap_basic_SUITE_data/RAND
Binary files differ
diff --git a/lib/eldap/test/eldap_connections_SUITE.erl b/lib/eldap/test/eldap_connections_SUITE.erl
deleted file mode 100644
index c5460fef09..0000000000
--- a/lib/eldap/test/eldap_connections_SUITE.erl
+++ /dev/null
@@ -1,147 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% 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
-%% 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%
-%%
-
--module(eldap_connections_SUITE).
-
--compile(export_all).
-
--include_lib("common_test/include/ct.hrl").
-%-include_lib("eldap/include/eldap.hrl").
-
-
-all() ->
- [
- {group, v4},
- {group, v6}
- ].
-
-
-init_per_group(v4, Config) ->
- [{listen_opts, []},
- {listen_host, "localhost"},
- {connect_opts, []}
- | Config];
-init_per_group(v6, Config) ->
- {ok, Hostname} = inet:gethostname(),
- case lists:member(list_to_atom(Hostname), ct:get_config(ipv6_hosts,[])) of
- true ->
- [{listen_opts, [inet6]},
- {listen_host, "::"},
- {connect_opts, [{tcpopts,[inet6]}]}
- | Config];
- false ->
- {skip, io_lib:format("~p is not an ipv6_host",[Hostname])}
- end.
-
-
-end_per_group(_GroupName, Config) ->
- Config.
-
-
-groups() ->
- [{v4, [], [tcp_connection, tcp_connection_option]},
- {v6, [], [tcp_connection, tcp_connection_option]}
- ].
-
-
-init_per_suite(Config) -> Config.
-
-
-end_per_suite(_Config) -> ok.
-
-
-init_per_testcase(_TestCase, Config) ->
- case gen_tcp:listen(0, proplists:get_value(listen_opts,Config)) of
- {ok,LSock} ->
- {ok,{_,Port}} = inet:sockname(LSock),
- [{listen_socket,LSock},
- {listen_port,Port}
- | Config];
- Other ->
- {fail, Other}
- end.
-
-
-end_per_testcase(_TestCase, Config) ->
- catch gen_tcp:close( proplists:get_value(listen_socket, Config) ).
-
-%%%================================================================
-%%%
-%%% Test cases
-%%%
-%%%----------------------------------------------------------------
-tcp_connection(Config) ->
- Host = proplists:get_value(listen_host, Config),
- Port = proplists:get_value(listen_port, Config),
- Opts = proplists:get_value(connect_opts, Config),
- case eldap:open([Host], [{port,Port}|Opts]) of
- {ok,_H} ->
- Sl = proplists:get_value(listen_socket, Config),
- case gen_tcp:accept(Sl,1000) of
- {ok,_S} -> ok;
- {error,timeout} -> ct:fail("server side accept timeout",[])
- end;
- Other -> ct:fail("eldap:open failed: ~p",[Other])
- end.
-
-
-%%%----------------------------------------------------------------
-tcp_connection_option(Config) ->
- Host = proplists:get_value(listen_host, Config),
- Port = proplists:get_value(listen_port, Config),
- Opts = proplists:get_value(connect_opts, Config),
- Sl = proplists:get_value(listen_socket, Config),
-
- %% Make an option value to test. The option must be implemented on all
- %% platforms that we test on. Must check what the default value is
- %% so we don't happen to choose that particular value.
- {ok,[{linger,DefaultLinger}]} = inet:getopts(Sl, [linger]),
- TestLinger = case DefaultLinger of
- {false,_} -> {true,5};
- {true,_} -> {false,0}
- end,
-
- case catch eldap:open([Host],
- [{port,Port},{tcpopts,[{linger,TestLinger}]}|Opts]) of
- {ok,H} ->
- case gen_tcp:accept(Sl,1000) of
- {ok,_} ->
- case eldap:getopts(H, [{tcpopts,[linger]}]) of
- {ok,[{tcpopts,[{linger,ActualLinger}]}]} ->
- case ActualLinger of
- TestLinger ->
- ok;
- DefaultLinger ->
- ct:fail("eldap:getopts: 'linger' didn't change,"
- " got ~p (=default) expected ~p",
- [ActualLinger,TestLinger]);
- _ ->
- ct:fail("eldap:getopts: bad 'linger', got ~p expected ~p",
- [ActualLinger,TestLinger])
- end;
- Other ->
- ct:fail("eldap:getopts: bad result ~p",[Other])
- end;
- {error,timeout} ->
- ct:fail("server side accept timeout",[])
- end;
-
- Other ->
- ct:fail("eldap:open failed: ~p",[Other])
- end.
diff --git a/lib/eldap/test/eldap_misc_SUITE.erl b/lib/eldap/test/eldap_misc_SUITE.erl
deleted file mode 100644
index ca810ee33c..0000000000
--- a/lib/eldap/test/eldap_misc_SUITE.erl
+++ /dev/null
@@ -1,51 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% 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
-%% 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%
-%%
-
--module(eldap_misc_SUITE).
-
--compile(export_all). %% Use this only in test suites...
-
--include_lib("common_test/include/ct.hrl").
--include_lib("eldap/include/eldap.hrl").
--include_lib("eldap/ebin/ELDAPv3.hrl").
-
-all() ->
- [
- encode,
- decode
- ].
-
-
-encode(_Config) ->
- {ok,Bin} = 'ELDAPv3':encode('AddRequest', #'AddRequest'{entry="hejHopp" ,attributes=[]} ),
- Expected = <<104,11,4,7,104,101,106,72,111,112,112,48,0>>,
- Expected = Bin.
-
-decode(_Config) ->
- {ok,Res} = 'ELDAPv3':decode('AddRequest', <<104,11,4,7,104,101,106,72,111,112,112,48,0>>),
- ct:log("Res = ~p", [Res]),
- Expected = #'AddRequest'{entry = "hejHopp",attributes = []},
- case Res of
- Expected -> ok;
- #'AddRequest'{entry= <<"hejHopp">>, attributes=[]} ->
- {fail, "decoded to (correct) binary!!"};
- _ ->
- {fail, "Bad decode"}
- end.
-
diff --git a/lib/eldap/test/make_certs.erl b/lib/eldap/test/make_certs.erl
index f963af180d..15a7e118ff 100644
--- a/lib/eldap/test/make_certs.erl
+++ b/lib/eldap/test/make_certs.erl
@@ -1,41 +1,89 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2013. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2007-2012. 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%
%%
-module(make_certs).
+-compile([export_all]).
--export([all/2]).
+%-export([all/1, all/2, rootCA/2, intermediateCA/3, endusers/3, enduser/3, revoke/3, gencrl/2, verify/3]).
--record(dn, {commonName,
+-record(config, {commonName,
organizationalUnitName = "Erlang OTP",
organizationName = "Ericsson AB",
localityName = "Stockholm",
countryName = "SE",
- emailAddress = "[email protected]"}).
+ emailAddress = "[email protected]",
+ default_bits = 2048,
+ v2_crls = true,
+ ecc_certs = false,
+ issuing_distribution_point = false,
+ crl_port = 8000,
+ openssl_cmd = "openssl"}).
+
+
+default_config() ->
+ #config{}.
+
+make_config(Args) ->
+ make_config(Args, #config{}).
+
+make_config([], C) ->
+ C;
+make_config([{organizationalUnitName, Name}|T], C) when is_list(Name) ->
+ make_config(T, C#config{organizationalUnitName = Name});
+make_config([{organizationName, Name}|T], C) when is_list(Name) ->
+ make_config(T, C#config{organizationName = Name});
+make_config([{localityName, Name}|T], C) when is_list(Name) ->
+ make_config(T, C#config{localityName = Name});
+make_config([{countryName, Name}|T], C) when is_list(Name) ->
+ make_config(T, C#config{countryName = Name});
+make_config([{emailAddress, Name}|T], C) when is_list(Name) ->
+ make_config(T, C#config{emailAddress = Name});
+make_config([{default_bits, Bits}|T], C) when is_integer(Bits) ->
+ make_config(T, C#config{default_bits = Bits});
+make_config([{v2_crls, Bool}|T], C) when is_boolean(Bool) ->
+ make_config(T, C#config{v2_crls = Bool});
+make_config([{crl_port, Port}|T], C) when is_integer(Port) ->
+ make_config(T, C#config{crl_port = Port});
+make_config([{ecc_certs, Bool}|T], C) when is_boolean(Bool) ->
+ make_config(T, C#config{ecc_certs = Bool});
+make_config([{issuing_distribution_point, Bool}|T], C) when is_boolean(Bool) ->
+ make_config(T, C#config{issuing_distribution_point = Bool});
+make_config([{openssl_cmd, Cmd}|T], C) when is_list(Cmd) ->
+ make_config(T, C#config{openssl_cmd = Cmd}).
+
+
+all([DataDir, PrivDir]) ->
+ all(DataDir, PrivDir).
all(DataDir, PrivDir) ->
- OpenSSLCmd = "openssl",
+ all(DataDir, PrivDir, #config{}).
+
+all(DataDir, PrivDir, C) when is_list(C) ->
+ all(DataDir, PrivDir, make_config(C));
+all(DataDir, PrivDir, C = #config{}) ->
+ ok = filelib:ensure_dir(filename:join(PrivDir, "erlangCA")),
create_rnd(DataDir, PrivDir), % For all requests
- rootCA(PrivDir, OpenSSLCmd, "erlangCA"),
- intermediateCA(PrivDir, OpenSSLCmd, "otpCA", "erlangCA"),
- endusers(PrivDir, OpenSSLCmd, "otpCA", ["client", "server"]),
- collect_certs(PrivDir, ["erlangCA", "otpCA"], ["client", "server"]),
- %% Create keycert files
+ rootCA(PrivDir, "erlangCA", C),
+ intermediateCA(PrivDir, "otpCA", "erlangCA", C),
+ endusers(PrivDir, "otpCA", ["client", "server", "revoked"], C),
+ endusers(PrivDir, "erlangCA", ["localhost"], C),
+ %% Create keycert files
SDir = filename:join([PrivDir, "server"]),
SC = filename:join([SDir, "cert.pem"]),
SK = filename:join([SDir, "key.pem"]),
@@ -46,7 +94,14 @@ all(DataDir, PrivDir) ->
CK = filename:join([CDir, "key.pem"]),
CKC = filename:join([CDir, "keycert.pem"]),
append_files([CK, CC], CKC),
- remove_rnd(PrivDir).
+ RDir = filename:join([PrivDir, "revoked"]),
+ RC = filename:join([RDir, "cert.pem"]),
+ RK = filename:join([RDir, "key.pem"]),
+ RKC = filename:join([RDir, "keycert.pem"]),
+ revoke(PrivDir, "otpCA", "revoked", C),
+ append_files([RK, RC], RKC),
+ remove_rnd(PrivDir),
+ {ok, C}.
append_files(FileNames, ResultFileName) ->
{ok, ResultFile} = file:open(ResultFileName, [write]),
@@ -59,117 +114,182 @@ do_append_files([F|Fs], RF) ->
ok = file:write(RF, Data),
do_append_files(Fs, RF).
-rootCA(Root, OpenSSLCmd, Name) ->
- create_ca_dir(Root, Name, ca_cnf(Name)),
- DN = #dn{commonName = Name},
- create_self_signed_cert(Root, OpenSSLCmd, Name, req_cnf(DN)),
- ok.
+rootCA(Root, Name, C) ->
+ create_ca_dir(Root, Name, ca_cnf(C#config{commonName = Name})),
+ create_self_signed_cert(Root, Name, req_cnf(C#config{commonName = Name}), C),
+ file:copy(filename:join([Root, Name, "cert.pem"]), filename:join([Root, Name, "cacerts.pem"])),
+ gencrl(Root, Name, C).
-intermediateCA(Root, OpenSSLCmd, CA, ParentCA) ->
- CA = "otpCA",
- create_ca_dir(Root, CA, ca_cnf(CA)),
+intermediateCA(Root, CA, ParentCA, C) ->
+ create_ca_dir(Root, CA, ca_cnf(C#config{commonName = CA})),
CARoot = filename:join([Root, CA]),
- DN = #dn{commonName = CA},
CnfFile = filename:join([CARoot, "req.cnf"]),
- file:write_file(CnfFile, req_cnf(DN)),
- KeyFile = filename:join([CARoot, "private", "key.pem"]),
- ReqFile = filename:join([CARoot, "req.pem"]),
- create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile),
+ file:write_file(CnfFile, req_cnf(C#config{commonName = CA})),
+ KeyFile = filename:join([CARoot, "private", "key.pem"]),
+ ReqFile = filename:join([CARoot, "req.pem"]),
+ create_req(Root, CnfFile, KeyFile, ReqFile, C),
CertFile = filename:join([CARoot, "cert.pem"]),
- sign_req(Root, OpenSSLCmd, ParentCA, "ca_cert", ReqFile, CertFile).
-
-endusers(Root, OpenSSLCmd, CA, Users) ->
- lists:foreach(fun(User) -> enduser(Root, OpenSSLCmd, CA, User) end, Users).
-
-enduser(Root, OpenSSLCmd, CA, User) ->
+ sign_req(Root, ParentCA, "ca_cert", ReqFile, CertFile, C),
+ CACertsFile = filename:join(CARoot, "cacerts.pem"),
+ file:copy(filename:join([Root, ParentCA, "cacerts.pem"]), CACertsFile),
+ %% append this CA's cert to the cacerts file
+ {ok, Bin} = file:read_file(CertFile),
+ {ok, FD} = file:open(CACertsFile, [append]),
+ file:write(FD, ["\n", Bin]),
+ file:close(FD),
+ gencrl(Root, CA, C).
+
+endusers(Root, CA, Users, C) ->
+ [enduser(Root, CA, User, C) || User <- Users].
+
+enduser(Root, CA, User, C) ->
UsrRoot = filename:join([Root, User]),
file:make_dir(UsrRoot),
CnfFile = filename:join([UsrRoot, "req.cnf"]),
- DN = #dn{commonName = User},
- file:write_file(CnfFile, req_cnf(DN)),
- KeyFile = filename:join([UsrRoot, "key.pem"]),
- ReqFile = filename:join([UsrRoot, "req.pem"]),
- create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile),
+ file:write_file(CnfFile, req_cnf(C#config{commonName = User})),
+ KeyFile = filename:join([UsrRoot, "key.pem"]),
+ ReqFile = filename:join([UsrRoot, "req.pem"]),
+ create_req(Root, CnfFile, KeyFile, ReqFile, C),
+ %create_req(Root, CnfFile, KeyFile, ReqFile),
CertFileAllUsage = filename:join([UsrRoot, "cert.pem"]),
- sign_req(Root, OpenSSLCmd, CA, "user_cert", ReqFile, CertFileAllUsage),
+ sign_req(Root, CA, "user_cert", ReqFile, CertFileAllUsage, C),
CertFileDigitalSigOnly = filename:join([UsrRoot, "digital_signature_only_cert.pem"]),
- sign_req(Root, OpenSSLCmd, CA, "user_cert_digital_signature_only", ReqFile, CertFileDigitalSigOnly).
-
-collect_certs(Root, CAs, Users) ->
- Bins = lists:foldr(
- fun(CA, Acc) ->
- File = filename:join([Root, CA, "cert.pem"]),
- {ok, Bin} = file:read_file(File),
- [Bin, "\n" | Acc]
- end, [], CAs),
- lists:foreach(
- fun(User) ->
- File = filename:join([Root, User, "cacerts.pem"]),
- file:write_file(File, Bins)
- end, Users).
+ sign_req(Root, CA, "user_cert_digital_signature_only", ReqFile, CertFileDigitalSigOnly, C),
+ CACertsFile = filename:join(UsrRoot, "cacerts.pem"),
+ file:copy(filename:join([Root, CA, "cacerts.pem"]), CACertsFile),
+ ok.
-create_self_signed_cert(Root, OpenSSLCmd, CAName, Cnf) ->
+revoke(Root, CA, User, C) ->
+ UsrCert = filename:join([Root, User, "cert.pem"]),
+ CACnfFile = filename:join([Root, CA, "ca.cnf"]),
+ Cmd = [C#config.openssl_cmd, " ca"
+ " -revoke ", UsrCert,
+ [" -crl_reason keyCompromise" || C#config.v2_crls ],
+ " -config ", CACnfFile],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env),
+ gencrl(Root, CA, C).
+
+gencrl(Root, CA, C) ->
+ CACnfFile = filename:join([Root, CA, "ca.cnf"]),
+ CACRLFile = filename:join([Root, CA, "crl.pem"]),
+ Cmd = [C#config.openssl_cmd, " ca"
+ " -gencrl ",
+ " -crlhours 24",
+ " -out ", CACRLFile,
+ " -config ", CACnfFile],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env).
+
+verify(Root, CA, User, C) ->
+ CAFile = filename:join([Root, User, "cacerts.pem"]),
+ CACRLFile = filename:join([Root, CA, "crl.pem"]),
+ CertFile = filename:join([Root, User, "cert.pem"]),
+ Cmd = [C#config.openssl_cmd, " verify"
+ " -CAfile ", CAFile,
+ " -CRLfile ", CACRLFile, %% this is undocumented, but seems to work
+ " -crl_check ",
+ CertFile],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ try cmd(Cmd, Env) catch
+ exit:{eval_cmd, _, _} ->
+ invalid
+ end.
+
+create_self_signed_cert(Root, CAName, Cnf, C = #config{ecc_certs = true}) ->
CARoot = filename:join([Root, CAName]),
CnfFile = filename:join([CARoot, "req.cnf"]),
file:write_file(CnfFile, Cnf),
- KeyFile = filename:join([CARoot, "private", "key.pem"]),
- CertFile = filename:join([CARoot, "cert.pem"]),
- Cmd = [OpenSSLCmd, " req"
+ KeyFile = filename:join([CARoot, "private", "key.pem"]),
+ CertFile = filename:join([CARoot, "cert.pem"]),
+ Cmd = [C#config.openssl_cmd, " ecparam"
+ " -out ", KeyFile,
+ " -name secp521r1 ",
+ %" -name sect283k1 ",
+ " -genkey "],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env),
+
+ Cmd2 = [C#config.openssl_cmd, " req"
+ " -new"
+ " -x509"
+ " -config ", CnfFile,
+ " -key ", KeyFile,
+ " -outform PEM ",
+ " -out ", CertFile],
+ cmd(Cmd2, Env);
+create_self_signed_cert(Root, CAName, Cnf, C) ->
+ CARoot = filename:join([Root, CAName]),
+ CnfFile = filename:join([CARoot, "req.cnf"]),
+ file:write_file(CnfFile, Cnf),
+ KeyFile = filename:join([CARoot, "private", "key.pem"]),
+ CertFile = filename:join([CARoot, "cert.pem"]),
+ Cmd = [C#config.openssl_cmd, " req"
" -new"
" -x509"
" -config ", CnfFile,
" -keyout ", KeyFile,
- " -out ", CertFile],
- Env = [{"ROOTDIR", Root}],
- cmd(Cmd, Env),
- fix_key_file(OpenSSLCmd, KeyFile).
-
-% openssl 1.0 generates key files in pkcs8 format by default and we don't handle this format
-fix_key_file(OpenSSLCmd, KeyFile) ->
- KeyFileTmp = KeyFile ++ ".tmp",
- Cmd = [OpenSSLCmd, " rsa",
- " -in ",
- KeyFile,
- " -out ",
- KeyFileTmp],
- cmd(Cmd, []),
- ok = file:rename(KeyFileTmp, KeyFile).
+ " -outform PEM",
+ " -out ", CertFile],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env).
+
create_ca_dir(Root, CAName, Cnf) ->
CARoot = filename:join([Root, CAName]),
+ ok = filelib:ensure_dir(CARoot),
file:make_dir(CARoot),
create_dirs(CARoot, ["certs", "crl", "newcerts", "private"]),
create_rnd(Root, filename:join([CAName, "private"])),
create_files(CARoot, [{"serial", "01\n"},
+ {"crlnumber", "01"},
{"index.txt", ""},
{"ca.cnf", Cnf}]).
-create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile) ->
- Cmd = [OpenSSLCmd, " req"
+create_req(Root, CnfFile, KeyFile, ReqFile, C = #config{ecc_certs = true}) ->
+ Cmd = [C#config.openssl_cmd, " ecparam"
+ " -out ", KeyFile,
+ " -name secp521r1 ",
+ %" -name sect283k1 ",
+ " -genkey "],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env),
+ Cmd2 = [C#config.openssl_cmd, " req"
+ " -new ",
+ " -key ", KeyFile,
+ " -outform PEM ",
+ " -out ", ReqFile,
+ " -config ", CnfFile],
+ cmd(Cmd2, Env);
+ %fix_key_file(KeyFile).
+create_req(Root, CnfFile, KeyFile, ReqFile, C) ->
+ Cmd = [C#config.openssl_cmd, " req"
" -new"
" -config ", CnfFile,
- " -keyout ", KeyFile,
- " -out ", ReqFile],
- Env = [{"ROOTDIR", Root}],
- cmd(Cmd, Env),
- fix_key_file(OpenSSLCmd, KeyFile).
+ " -outform PEM ",
+ " -keyout ", KeyFile,
+ " -out ", ReqFile],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env).
+ %fix_key_file(KeyFile).
+
-sign_req(Root, OpenSSLCmd, CA, CertType, ReqFile, CertFile) ->
+sign_req(Root, CA, CertType, ReqFile, CertFile, C) ->
CACnfFile = filename:join([Root, CA, "ca.cnf"]),
- Cmd = [OpenSSLCmd, " ca"
+ Cmd = [C#config.openssl_cmd, " ca"
" -batch"
" -notext"
- " -config ", CACnfFile,
+ " -config ", CACnfFile,
" -extensions ", CertType,
- " -in ", ReqFile,
+ " -in ", ReqFile,
" -out ", CertFile],
- Env = [{"ROOTDIR", Root}],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
cmd(Cmd, Env).
-
+
%%
%% Misc
%%
-
+
create_dirs(Root, Dirs) ->
lists:foreach(fun(Dir) ->
file:make_dir(filename:join([Root, Dir])) end,
@@ -192,30 +312,30 @@ remove_rnd(Dir) ->
cmd(Cmd, Env) ->
FCmd = lists:flatten(Cmd),
- Port = open_port({spawn, FCmd}, [stream, eof, exit_status, stderr_to_stdout,
+ Port = open_port({spawn, FCmd}, [stream, eof, exit_status, stderr_to_stdout,
{env, Env}]),
- eval_cmd(Port).
+ eval_cmd(Port, FCmd).
-eval_cmd(Port) ->
- receive
+eval_cmd(Port, Cmd) ->
+ receive
{Port, {data, _}} ->
- eval_cmd(Port);
+ eval_cmd(Port, Cmd);
{Port, eof} ->
ok
end,
receive
{Port, {exit_status, Status}} when Status /= 0 ->
%% io:fwrite("exit status: ~w~n", [Status]),
- exit({eval_cmd, Status})
+ exit({eval_cmd, Cmd, Status})
after 0 ->
ok
end.
%%
-%% Contents of configuration files
+%% Contents of configuration files
%%
-req_cnf(DN) ->
+req_cnf(C) ->
["# Purpose: Configuration for requests (end users and CAs)."
"\n"
"ROOTDIR = $ENV::ROOTDIR\n"
@@ -224,10 +344,10 @@ req_cnf(DN) ->
"[req]\n"
"input_password = secret\n"
"output_password = secret\n"
- "default_bits = 1024\n"
+ "default_bits = ", integer_to_list(C#config.default_bits), "\n"
"RANDFILE = $ROOTDIR/RAND\n"
"encrypt_key = no\n"
- "default_md = sha1\n"
+ "default_md = md5\n"
"#string_mask = pkix\n"
"x509_extensions = ca_ext\n"
"prompt = no\n"
@@ -235,12 +355,12 @@ req_cnf(DN) ->
"\n"
"[name]\n"
- "commonName = ", DN#dn.commonName, "\n"
- "organizationalUnitName = ", DN#dn.organizationalUnitName, "\n"
- "organizationName = ", DN#dn.organizationName, "\n"
- "localityName = ", DN#dn.localityName, "\n"
- "countryName = ", DN#dn.countryName, "\n"
- "emailAddress = ", DN#dn.emailAddress, "\n"
+ "commonName = ", C#config.commonName, "\n"
+ "organizationalUnitName = ", C#config.organizationalUnitName, "\n"
+ "organizationName = ", C#config.organizationName, "\n"
+ "localityName = ", C#config.localityName, "\n"
+ "countryName = ", C#config.countryName, "\n"
+ "emailAddress = ", C#config.emailAddress, "\n"
"\n"
"[ca_ext]\n"
@@ -249,8 +369,7 @@ req_cnf(DN) ->
"subjectKeyIdentifier = hash\n"
"subjectAltName = email:copy\n"].
-
-ca_cnf(CA) ->
+ca_cnf(C) ->
["# Purpose: Configuration for CAs.\n"
"\n"
"ROOTDIR = $ENV::ROOTDIR\n"
@@ -258,21 +377,23 @@ ca_cnf(CA) ->
"\n"
"[ca]\n"
- "dir = $ROOTDIR/", CA, "\n"
+ "dir = $ROOTDIR/", C#config.commonName, "\n"
"certs = $dir/certs\n"
"crl_dir = $dir/crl\n"
"database = $dir/index.txt\n"
"new_certs_dir = $dir/newcerts\n"
"certificate = $dir/cert.pem\n"
"serial = $dir/serial\n"
- "crl = $dir/crl.pem\n"
+ "crl = $dir/crl.pem\n",
+ ["crlnumber = $dir/crlnumber\n" || C#config.v2_crls],
"private_key = $dir/private/key.pem\n"
"RANDFILE = $dir/private/RAND\n"
"\n"
- "x509_extensions = user_cert\n"
+ "x509_extensions = user_cert\n",
+ ["crl_extensions = crl_ext\n" || C#config.v2_crls],
"unique_subject = no\n"
"default_days = 3600\n"
- "default_md = sha1\n"
+ "default_md = md5\n"
"preserve = no\n"
"policy = policy_match\n"
"\n"
@@ -286,6 +407,13 @@ ca_cnf(CA) ->
"emailAddress = supplied\n"
"\n"
+ "[crl_ext]\n"
+ "authorityKeyIdentifier=keyid:always,issuer:always\n",
+ ["issuingDistributionPoint=critical, @idpsec\n" || C#config.issuing_distribution_point],
+
+ "[idpsec]\n"
+ "fullname=URI:http://localhost:8000/",C#config.commonName,"/crl.pem\n"
+
"[user_cert]\n"
"basicConstraints = CA:false\n"
"keyUsage = nonRepudiation, digitalSignature, keyEncipherment\n"
@@ -293,6 +421,12 @@ ca_cnf(CA) ->
"authorityKeyIdentifier = keyid,issuer:always\n"
"subjectAltName = email:copy\n"
"issuerAltName = issuer:copy\n"
+ "crlDistributionPoints=@crl_section\n"
+
+ "[crl_section]\n"
+ %% intentionally invalid
+ "URI.1=http://localhost/",C#config.commonName,"/crl.pem\n"
+ "URI.2=http://localhost:",integer_to_list(C#config.crl_port),"/",C#config.commonName,"/crl.pem\n"
"\n"
"[user_cert_digital_signature_only]\n"
@@ -310,4 +444,7 @@ ca_cnf(CA) ->
"subjectKeyIdentifier = hash\n"
"authorityKeyIdentifier = keyid:always,issuer:always\n"
"subjectAltName = email:copy\n"
- "issuerAltName = issuer:copy\n"].
+ "issuerAltName = issuer:copy\n"
+ "crlDistributionPoints=@crl_section\n"
+ ].
+
diff --git a/lib/eldap/vsn.mk b/lib/eldap/vsn.mk
index 432ba2e742..adca41ed63 100644
--- a/lib/eldap/vsn.mk
+++ b/lib/eldap/vsn.mk
@@ -1 +1 @@
-ELDAP_VSN = 1.1
+ELDAP_VSN = 1.1.1
diff --git a/lib/hipe/cerl/erl_types.erl b/lib/hipe/cerl/erl_types.erl
index 4b2bec5fa8..4215448c61 100644
--- a/lib/hipe/cerl/erl_types.erl
+++ b/lib/hipe/cerl/erl_types.erl
@@ -2,7 +2,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2015. 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
@@ -4230,7 +4230,7 @@ t_from_form({type, _L, any, []}, _TypeNames, _RecDict, _VarDict) ->
t_from_form({type, _L, arity, []}, _TypeNames, _RecDict, _VarDict) ->
{t_arity(), []};
t_from_form({type, _L, array, []}, TypeNames, RecDict, VarDict) ->
- builtin_type(array, t_array(), TypeNames, RecDict, VarDict);
+ builtin_type(array, t_array(), [], TypeNames, RecDict, VarDict);
t_from_form({type, _L, atom, []}, _TypeNames, _RecDict, _VarDict) ->
{t_atom(), []};
t_from_form({type, _L, binary, []}, _TypeNames, _RecDict, _VarDict) ->
@@ -4253,9 +4253,9 @@ t_from_form({type, _L, byte, []}, _TypeNames, _RecDict, _VarDict) ->
t_from_form({type, _L, char, []}, _TypeNames, _RecDict, _VarDict) ->
{t_char(), []};
t_from_form({type, _L, dict, []}, TypeNames, RecDict, VarDict) ->
- builtin_type(dict, t_dict(), TypeNames, RecDict, VarDict);
+ builtin_type(dict, t_dict(), [], TypeNames, RecDict, VarDict);
t_from_form({type, _L, digraph, []}, TypeNames, RecDict, VarDict) ->
- builtin_type(digraph, t_digraph(), TypeNames, RecDict, VarDict);
+ builtin_type(digraph, t_digraph(), [], TypeNames, RecDict, VarDict);
t_from_form({type, _L, float, []}, _TypeNames, _RecDict, _VarDict) ->
{t_float(), []};
t_from_form({type, _L, function, []}, _TypeNames, _RecDict, _VarDict) ->
@@ -4272,9 +4272,9 @@ t_from_form({type, _L, 'fun', [{type, _, product, Domain}, Range]},
{T, R2} = t_from_form(Range, TypeNames, RecDict, VarDict),
{t_fun(L, T), R1 ++ R2};
t_from_form({type, _L, gb_set, []}, TypeNames, RecDict, VarDict) ->
- builtin_type(gb_set, t_gb_set(), TypeNames, RecDict, VarDict);
+ builtin_type(gb_set, t_gb_set(), [], TypeNames, RecDict, VarDict);
t_from_form({type, _L, gb_tree, []}, TypeNames, RecDict, VarDict) ->
- builtin_type(gb_tree, t_gb_tree(), TypeNames, RecDict, VarDict);
+ builtin_type(gb_tree, t_gb_tree(), [], TypeNames, RecDict, VarDict);
t_from_form({type, _L, identifier, []}, _TypeNames, _RecDict, _VarDict) ->
{t_identifier(), []};
t_from_form({type, _L, integer, []}, _TypeNames, _RecDict, _VarDict) ->
@@ -4288,8 +4288,12 @@ t_from_form({type, _L, list, []}, _TypeNames, _RecDict, _VarDict) ->
t_from_form({type, _L, list, [Type]}, TypeNames, RecDict, VarDict) ->
{T, R} = t_from_form(Type, TypeNames, RecDict, VarDict),
{t_list(T), R};
-t_from_form({type, _L, map, _}, TypeNames, RecDict, VarDict) ->
- builtin_type(map, t_map([]), TypeNames, RecDict, VarDict);
+t_from_form({type, _L, map, As0}, TypeNames, RecDict, VarDict) ->
+ As = case is_list(As0) of
+ true -> As0;
+ false -> []
+ end,
+ builtin_type(map, t_map([]), As, TypeNames, RecDict, VarDict);
t_from_form({type, _L, mfa, []}, _TypeNames, _RecDict, _VarDict) ->
{t_mfa(), []};
t_from_form({type, _L, module, []}, _TypeNames, _RecDict, _VarDict) ->
@@ -4348,7 +4352,7 @@ t_from_form({type, _L, product, Elements}, TypeNames, RecDict, VarDict) ->
{L, R} = list_from_form(Elements, TypeNames, RecDict, VarDict),
{t_product(L), R};
t_from_form({type, _L, queue, []}, TypeNames, RecDict, VarDict) ->
- builtin_type(queue, t_queue(), TypeNames, RecDict, VarDict);
+ builtin_type(queue, t_queue(), [], TypeNames, RecDict, VarDict);
t_from_form({type, _L, range, [From, To]} = Type,
_TypeNames, _RecDict, _VarDict) ->
case {erl_eval:partial_eval(From), erl_eval:partial_eval(To)} of
@@ -4361,13 +4365,13 @@ t_from_form({type, _L, record, [Name|Fields]}, TypeNames, RecDict, VarDict) ->
t_from_form({type, _L, reference, []}, _TypeNames, _RecDict, _VarDict) ->
{t_reference(), []};
t_from_form({type, _L, set, []}, TypeNames, RecDict, VarDict) ->
- builtin_type(set, t_set(), TypeNames, RecDict, VarDict);
+ builtin_type(set, t_set(), [], TypeNames, RecDict, VarDict);
t_from_form({type, _L, string, []}, _TypeNames, _RecDict, _VarDict) ->
{t_string(), []};
t_from_form({type, _L, term, []}, _TypeNames, _RecDict, _VarDict) ->
{t_any(), []};
t_from_form({type, _L, tid, []}, TypeNames, RecDict, VarDict) ->
- builtin_type(tid, t_tid(), TypeNames, RecDict, VarDict);
+ builtin_type(tid, t_tid(), [], TypeNames, RecDict, VarDict);
t_from_form({type, _L, timeout, []}, _TypeNames, _RecDict, _VarDict) ->
{t_timeout(), []};
t_from_form({type, _L, tuple, any}, _TypeNames, _RecDict, _VarDict) ->
@@ -4384,10 +4388,10 @@ t_from_form({opaque, _L, Name, {Mod, Args, Rep}}, _TypeNames,
_RecDict, _VarDict) ->
{t_opaque(Mod, Name, Args, Rep), []}.
-builtin_type(Name, Type, TypeNames, RecDict, VarDict) ->
- case lookup_type(Name, 0, RecDict) of
+builtin_type(Name, Type, Args, TypeNames, RecDict, VarDict) ->
+ case lookup_type(Name, length(Args), RecDict) of
{_, {_M, _T, _A}} ->
- type_from_form(Name, [], TypeNames, RecDict, VarDict);
+ type_from_form(Name, Args, TypeNames, RecDict, VarDict);
error ->
{Type, []}
end.
@@ -4588,7 +4592,7 @@ t_form_to_string({type, _L, iodata, []}) -> "iodata()";
t_form_to_string({type, _L, iolist, []}) -> "iolist()";
t_form_to_string({type, _L, list, [Type]}) ->
"[" ++ t_form_to_string(Type) ++ "]";
-t_form_to_string({type, _L, map, _}) ->
+t_form_to_string({type, _L, map, Args}) when not is_list(Args) ->
"#{}";
t_form_to_string({type, _L, mfa, []}) -> "mfa()";
t_form_to_string({type, _L, module, []}) -> "module()";
diff --git a/lib/hipe/ppc/hipe_rtl_to_ppc.erl b/lib/hipe/ppc/hipe_rtl_to_ppc.erl
index 7dfa56df29..a55fc137c3 100644
--- a/lib/hipe/ppc/hipe_rtl_to_ppc.erl
+++ b/lib/hipe/ppc/hipe_rtl_to_ppc.erl
@@ -102,10 +102,18 @@ conv_insn(I, Map, Data) ->
end.
conv_fconv(I, Map, Data) ->
- %% Dst := (double)Src, where Dst is FP reg and Src is int reg
+ %% Dst := (double)Src, where Dst is FP reg and Src is GP reg or imm
{Dst, Map0} = conv_fpreg(hipe_rtl:fconv_dst(I), Map),
- {Src, Map1} = conv_src(hipe_rtl:fconv_src(I), Map0), % exclude imm src
- I2 = mk_fconv(Dst, Src),
+ {Src, Map1} = conv_src(hipe_rtl:fconv_src(I), Map0),
+ I2 =
+ case hipe_ppc:is_temp(Src) of
+ true ->
+ mk_fconv(Dst, Src);
+ false ->
+ Tmp = new_untagged_temp(),
+ mk_li(Tmp, Src,
+ mk_fconv(Dst, Tmp))
+ end,
{I2, Map1, Data}.
mk_fconv(Dst, Src) ->
diff --git a/lib/hipe/rtl/hipe_rtl.erl b/lib/hipe/rtl/hipe_rtl.erl
index bc61bec0bd..2f62dd79ad 100644
--- a/lib/hipe/rtl/hipe_rtl.erl
+++ b/lib/hipe/rtl/hipe_rtl.erl
@@ -413,11 +413,11 @@ rtl_info_update(Rtl, Info) -> Rtl#rtl{info=Info}.
%% move
%%
-mk_move(Dst, Src) -> #move{dst=Dst, src=Src}.
+mk_move(Dst, Src) -> false = is_fpreg(Dst), false = is_fpreg(Src), #move{dst=Dst, src=Src}.
move_dst(#move{dst=Dst}) -> Dst.
-move_dst_update(M, NewDst) -> M#move{dst=NewDst}.
+move_dst_update(M, NewDst) -> false = is_fpreg(NewDst), M#move{dst=NewDst}.
move_src(#move{src=Src}) -> Src.
-move_src_update(M, NewSrc) -> M#move{src=NewSrc}.
+move_src_update(M, NewSrc) -> false = is_fpreg(NewSrc), M#move{src=NewSrc}.
%% is_move(#move{}) -> true;
%% is_move(_) -> false.
@@ -469,7 +469,11 @@ phi_remove_pred(Phi, Pred) ->
case NewArgList of
[Arg] -> %% the phi should be turned into a move instruction
{_Label,Var} = Arg,
- mk_move(phi_dst(Phi), Var);
+ Dst = phi_dst(Phi),
+ case {is_fpreg(Dst), is_fpreg(Var)} of
+ {true, true} -> mk_fmove(Dst, Var);
+ {false, false} -> mk_move(Dst, Var)
+ end;
%% io:format("~nPhi (~w) turned into move (~w) when removing pred ~w~n",[Phi,Move,Pred]),
[_|_] ->
Phi#phi{arglist=NewArgList}
@@ -836,11 +840,11 @@ fp_unop_op(#fp_unop{op=Op}) -> Op.
%% fmove
%%
-mk_fmove(X, Y) -> #fmove{dst=X, src=Y}.
+mk_fmove(X, Y) -> true = is_fpreg(X), true = is_fpreg(Y), #fmove{dst=X, src=Y}.
fmove_dst(#fmove{dst=Dst}) -> Dst.
-fmove_dst_update(M, NewDst) -> M#fmove{dst=NewDst}.
+fmove_dst_update(M, NewDst) -> true = is_fpreg(NewDst), M#fmove{dst=NewDst}.
fmove_src(#fmove{src=Src}) -> Src.
-fmove_src_update(M, NewSrc) -> M#fmove{src=NewSrc}.
+fmove_src_update(M, NewSrc) -> true = is_fpreg(NewSrc), M#fmove{src=NewSrc}.
%%
%% fconv
diff --git a/lib/hipe/rtl/hipe_rtl_binary_match.erl b/lib/hipe/rtl/hipe_rtl_binary_match.erl
index 8831199244..af8903904b 100644
--- a/lib/hipe/rtl/hipe_rtl_binary_match.erl
+++ b/lib/hipe/rtl/hipe_rtl_binary_match.erl
@@ -990,19 +990,19 @@ unsigned_bignum(Dst1, Src, TrueLblName) ->
hipe_tagscheme:unsafe_mk_big(Dst1, Src, unsigned),
hipe_rtl:mk_goto(TrueLblName)].
-load_bytes(Dst, Base, Offset, {Signedness, _Endianess},1) ->
+load_bytes(Dst, Base, Offset, {Signedness, _Endianness},1) ->
[hipe_rtl:mk_load(Dst, Base, Offset, byte, Signedness),
hipe_rtl:mk_alu(Offset, Offset, add, hipe_rtl:mk_imm(1))];
-load_bytes(Dst, Base, Offset, {Signedness, Endianess},2) ->
- case Endianess of
+load_bytes(Dst, Base, Offset, {Signedness, Endianness},2) ->
+ case Endianness of
big ->
hipe_rtl_arch:load_big_2(Dst, Base, Offset, Signedness);
little ->
hipe_rtl_arch:load_little_2(Dst, Base, Offset, Signedness)
end;
-load_bytes(Dst, Base, Offset, {Signedness, Endianess},3) ->
+load_bytes(Dst, Base, Offset, {Signedness, Endianness},3) ->
Tmp1 = hipe_rtl:mk_new_reg(),
- case Endianess of
+ case Endianness of
big ->
[hipe_rtl:mk_load(Dst, Base, Offset, byte, Signedness),
hipe_rtl:mk_alu(Offset, Offset, add, hipe_rtl:mk_imm(1)),
@@ -1026,18 +1026,18 @@ load_bytes(Dst, Base, Offset, {Signedness, Endianess},3) ->
hipe_rtl:mk_alu(Dst, Dst, 'or', Tmp1),
hipe_rtl:mk_alu(Offset, Offset, add, hipe_rtl:mk_imm(1))]
end;
-load_bytes(Dst, Base, Offset, {Signedness, Endianess}, 4) ->
- case Endianess of
+load_bytes(Dst, Base, Offset, {Signedness, Endianness}, 4) ->
+ case Endianness of
big ->
hipe_rtl_arch:load_big_4(Dst, Base, Offset, Signedness);
little ->
hipe_rtl_arch:load_little_4(Dst, Base, Offset, Signedness)
end;
-load_bytes(Dst, Base, Offset, {Signedness, Endianess}, X) when X > 1 ->
+load_bytes(Dst, Base, Offset, {Signedness, Endianness}, X) when X > 1 ->
[LoopLbl, EndLbl] = create_lbls(2),
[Tmp1, Limit, TmpOffset] = create_regs(3),
- case Endianess of
+ case Endianness of
big ->
[hipe_rtl:mk_alu(Limit, Offset, add, hipe_rtl:mk_imm(X)),
hipe_rtl:mk_load(Dst, Base, Offset, byte, Signedness),
diff --git a/lib/hipe/sparc/hipe_rtl_to_sparc.erl b/lib/hipe/sparc/hipe_rtl_to_sparc.erl
index dc001f865e..fd21be3ae7 100644
--- a/lib/hipe/sparc/hipe_rtl_to_sparc.erl
+++ b/lib/hipe/sparc/hipe_rtl_to_sparc.erl
@@ -85,17 +85,17 @@ conv_insn(I, Map, Data) ->
end.
conv_fconv(I, Map, Data) ->
- %% Dst := (double)Src, where Dst is FP reg and Src is int reg
- {Src, Map1} = conv_src(hipe_rtl:fconv_src(I), Map), % exclude imm src
+ %% Dst := (double)Src, where Dst is FP reg and Src is GP reg or imm
+ {Src, Map1} = conv_src(hipe_rtl:fconv_src(I), Map),
{Dst, Map2} = conv_fpreg(hipe_rtl:fconv_dst(I), Map1),
I2 = mk_fconv(Src, Dst),
{I2, Map2, Data}.
mk_fconv(Src, Dst) ->
CSP = hipe_sparc:mk_temp(14, 'untagged'), % o6
- Disp = hipe_sparc:mk_simm13(100),
- [hipe_sparc:mk_store('stw', Src, CSP, Disp),
- hipe_sparc:mk_pseudo_fload(CSP, Disp, Dst, true),
+ Offset = 100,
+ mk_store('stw', Src, CSP, Offset) ++
+ [hipe_sparc:mk_pseudo_fload(CSP, hipe_sparc:mk_simm13(Offset), Dst, true),
hipe_sparc:mk_fp_unary('fitod', Dst, Dst)].
conv_fmove(I, Map, Data) ->
diff --git a/lib/hipe/x86/hipe_rtl_to_x86.erl b/lib/hipe/x86/hipe_rtl_to_x86.erl
index d77e4fed3b..36da2f4d44 100644
--- a/lib/hipe/x86/hipe_rtl_to_x86.erl
+++ b/lib/hipe/x86/hipe_rtl_to_x86.erl
@@ -236,7 +236,7 @@ conv_insn(I, Map, Data) ->
#fconv{} ->
{Dst, Map0} = conv_dst(hipe_rtl:fconv_dst(I), Map),
{[], Src, Map1} = conv_src(hipe_rtl:fconv_src(I), Map0),
- I2 = [hipe_x86:mk_fmove(Src, Dst)],
+ I2 = conv_fconv(Dst, Src),
{I2, Map1, Data};
X ->
%% gctest??
@@ -712,6 +712,19 @@ vmap_lookup(Map, Key) ->
vmap_bind(Map, Key, Val) ->
gb_trees:insert(Key, Val, Map).
+%%% Finalise the conversion of an Integer-to-Float operation.
+
+conv_fconv(Dst, Src) ->
+ case hipe_x86:is_imm(Src) of
+ false ->
+ [hipe_x86:mk_fmove(Src, Dst)];
+ true ->
+ %% cvtsi2sd does not allow src to be an immediate
+ Tmp = new_untagged_temp(),
+ [hipe_x86:mk_move(Src, Tmp),
+ hipe_x86:mk_fmove(Tmp, Dst)]
+ end.
+
%%% Finalise the conversion of a 2-address FP operation.
conv_fp_unary(Dst, Src, FpUnOp) ->
diff --git a/lib/inets/doc/src/http_uri.xml b/lib/inets/doc/src/http_uri.xml
index e64c375bba..acbd79b201 100644
--- a/lib/inets/doc/src/http_uri.xml
+++ b/lib/inets/doc/src/http_uri.xml
@@ -63,6 +63,7 @@ host() = string()
port() = pos_integer()
path() = string() - Representing a file path or directory path
query() = string()
+fragment() = string()
]]></code>
<marker id="scheme_defaults"></marker>
@@ -92,13 +93,16 @@ query() = string()
<v>URI = uri() </v>
<v>Options = [Option] </v>
<v>Option = {ipv6_host_with_brackets, boolean()} |
- {scheme_defaults, scheme_defaults()}]</v>
- <v>Result = {Scheme, UserInfo, Host, Port, Path, Query}</v>
+ {scheme_defaults, scheme_defaults()} |
+ {fragment, boolean()}]</v>
+ <v>Result = {Scheme, UserInfo, Host, Port, Path, Query} |
+ {Scheme, UserInfo, Host, Port, Path, Query, Fragment}</v>
<v>UserInfo = user_info()</v>
<v>Host = host()</v>
<v>Port = pos_integer()</v>
<v>Path = path()</v>
<v>Query = query()</v>
+ <v>Fragment = fragment()</v>
<v>Reason = term() </v>
</type>
<desc>
@@ -111,6 +115,9 @@ query() = string()
a scheme not found in the scheme defaults) a port number must be
provided or else the parsing will fail. </p>
+ <p>If the fragment option is true, the URI fragment will be returned as
+ part of the parsing result, otherwise it is completely ignored.</p>
+
<marker id="encode"></marker>
</desc>
</func>
diff --git a/lib/inets/doc/src/httpd.xml b/lib/inets/doc/src/httpd.xml
index 4ca038cc99..20c8a6b1b1 100644
--- a/lib/inets/doc/src/httpd.xml
+++ b/lib/inets/doc/src/httpd.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1997</year><year>2013</year>
+ <year>1997</year><year>2015</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -249,7 +249,16 @@
<p>Limits the size of the message header of HTTP request.
Defaults to 10240. </p>
</item>
-
+
+ <marker id="prop_max_content_length"></marker>
+ <tag>{max_content_length, integer()}</tag>
+ <item>
+ <p>Maximum Content-Length in an incoming request, in bytes. Requests
+ with content larger than this are answered with Status 413.
+ Defaults to 100000000 (100 MB).
+ </p>
+ </item>
+
<marker id="prop_max_uri"></marker>
<tag>{max_uri_size, integer()}</tag>
<item>
diff --git a/lib/inets/doc/src/httpd_conf.xml b/lib/inets/doc/src/httpd_conf.xml
index 3ef03966a7..60fc2f135e 100644
--- a/lib/inets/doc/src/httpd_conf.xml
+++ b/lib/inets/doc/src/httpd_conf.xml
@@ -97,7 +97,7 @@
<v>FilePath = string()</v>
<v>Result = {ok,Directory} | {error,Reason}</v>
<v>Directory = string()</v>
- <v>Reason = string() | enoent | eaccess | enotdir | FileInfo</v>
+ <v>Reason = string() | enoent | eacces | enotdir | FileInfo</v>
<v>FileInfo = File info record</v>
</type>
<desc>
@@ -105,7 +105,7 @@
<p><c>is_directory/1</c> checks if <c>FilePath</c> is a
directory in which case it is returned. Please read
<c>file(3)</c> for a description of <c>enoent</c>,
- <c>eaccess</c> and <c>enotdir</c>. The definition of
+ <c>eacces</c> and <c>enotdir</c>. The definition of
the file info record can be found by including <c>file.hrl</c>
from the kernel application, see file(3).</p>
@@ -120,14 +120,14 @@
<v>FilePath = string()</v>
<v>Result = {ok,File} | {error,Reason}</v>
<v>File = string()</v>
- <v>Reason = string() | enoent | eaccess | enotdir | FileInfo</v>
+ <v>Reason = string() | enoent | eacces | enotdir | FileInfo</v>
<v>FileInfo = File info record</v>
</type>
<desc>
<marker id="is_file"></marker>
<p><c>is_file/1</c> checks if <c>FilePath</c> is a regular
file in which case it is returned. Read <c>file(3)</c> for a
- description of <c>enoent</c>, <c>eaccess</c> and
+ description of <c>enoent</c>, <c>eacces</c> and
<c>enotdir</c>. The definition of the file info record can be
found by including <c>file.hrl</c> from the kernel application,
see file(3).</p>
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index fb7034498c..7f73aa5e7b 100644
--- a/lib/inets/doc/src/notes.xml
+++ b/lib/inets/doc/src/notes.xml
@@ -32,7 +32,40 @@
<file>notes.xml</file>
</header>
- <section><title>Inets 5.10.4</title>
+ <section><title>Inets 5.10.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ mod_alias now handles https-URIs properly</p>
+ <p>
+ Consistent view of configuration parameter
+ keep_alive_timeout, should be presented in the
+ httpd:info/[1,2] function in the same unit as it is
+ inputted.</p>
+ <p>
+ Own Id: OTP-12436 Aux Id: seq12786 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Gracefully handle invalid content-lenght headers instead
+ of crashing in list_to_integer.</p>
+ <p>
+ Own Id: OTP-12429</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Inets 5.10.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/inets/examples/httpd_load_test/hdlt_slave.erl b/lib/inets/examples/httpd_load_test/hdlt_slave.erl
index 52af9b5b90..41361418bc 100644
--- a/lib/inets/examples/httpd_load_test/hdlt_slave.erl
+++ b/lib/inets/examples/httpd_load_test/hdlt_slave.erl
@@ -180,7 +180,7 @@ ssh_slave_start(Host, ErlCmd) ->
?DEBUG("ssh_exec_erl -> done", []),
{ok, Connection, Channel};
Error3 ->
- ?LOG("failed exec comand: ~p", [Error3]),
+ ?LOG("failed exec command: ~p", [Error3]),
throw({error, {ssh_exec_failed, Error3}})
end.
diff --git a/lib/inets/src/http_client/httpc_cookie.erl b/lib/inets/src/http_client/httpc_cookie.erl
index 134115bdfa..35778d3ed5 100644
--- a/lib/inets/src/http_client/httpc_cookie.erl
+++ b/lib/inets/src/http_client/httpc_cookie.erl
@@ -115,8 +115,8 @@ maybe_dets_close(Db) ->
%%--------------------------------------------------------------------
-%% Func: insert(CookieDb) -> ok
-%% Purpose: Close the cookie db
+%% Func: insert(CookieDb, Cookie) -> ok
+%% Purpose: insert cookies into the cookie db
%%--------------------------------------------------------------------
%% If no persistent cookie database is defined we
@@ -334,9 +334,23 @@ add_domain(Str, #http_cookie{domain_default = true}) ->
add_domain(Str, #http_cookie{domain = Domain}) ->
Str ++ "; $Domain=" ++ Domain.
+is_set_cookie_valid("") ->
+ %% an empty Set-Cookie header is not valid
+ false;
+is_set_cookie_valid([$=|_]) ->
+ %% a Set-Cookie header without name is not valid
+ false;
+is_set_cookie_valid(SetCookieHeader) ->
+ %% a Set-Cookie header without name/value is not valid
+ case string:chr(SetCookieHeader, $=) of
+ 0 -> false;
+ _ -> true
+ end.
+
parse_set_cookies(CookieHeaders, DefaultPathDomain) ->
- %% empty Set-Cookie header is invalid according to RFC but some sites violate it
- SetCookieHeaders = [Value || {"set-cookie", Value} <- CookieHeaders, Value /= ""],
+ %% filter invalid Set-Cookie headers
+ SetCookieHeaders = [Value || {"set-cookie", Value} <- CookieHeaders,
+ is_set_cookie_valid(Value)],
Cookies = [parse_set_cookie(SetCookieHeader, DefaultPathDomain) ||
SetCookieHeader <- SetCookieHeaders],
%% print_cookies("Parsed Cookies", Cookies),
@@ -348,6 +362,8 @@ parse_set_cookie(CookieHeader, {DefaultPath, DefaultDomain}) ->
Name = string:substr(CookieHeader, 1, Pos - 1),
{Value, Attrs} =
case string:substr(CookieHeader, Pos + 1) of
+ [] ->
+ {"", ""};
[$;|ValueAndAttrs] ->
{"", string:tokens(ValueAndAttrs, ";")};
ValueAndAttrs ->
diff --git a/lib/inets/src/http_lib/http_internal.hrl b/lib/inets/src/http_lib/http_internal.hrl
index 53b776c4e7..54425740b5 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-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2015. 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
@@ -28,6 +28,7 @@
-define(HTTP_MAX_URI_SIZE, nolimit).
-define(HTTP_MAX_VERSION_STRING, 8).
-define(HTTP_MAX_METHOD_STRING, 20).
+-define(HTTP_MAX_CONTENT_LENGTH, 100000000).
-ifndef(HTTP_DEFAULT_SSL_KIND).
-define(HTTP_DEFAULT_SSL_KIND, essl).
diff --git a/lib/inets/src/http_lib/http_request.erl b/lib/inets/src/http_lib/http_request.erl
index f295453bdd..a0833ddf01 100644
--- a/lib/inets/src/http_lib/http_request.erl
+++ b/lib/inets/src/http_lib/http_request.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2015. 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
@@ -21,8 +21,16 @@
-include("http_internal.hrl").
--export([headers/2, http_headers/1, is_absolut_uri/1]).
+-export([headers/2, http_headers/1, is_absolut_uri/1, key_value/1]).
+
+key_value(KeyValueStr) ->
+ case lists:splitwith(fun($:) -> false; (_) -> true end, KeyValueStr) of
+ {Key, [$: | Value]} ->
+ {http_util:to_lower(string:strip(Key)), string:strip(Value)};
+ {_, []} ->
+ undefined
+ end.
%%-------------------------------------------------------------------------
%% headers(HeaderList, #http_request_h{}) -> #http_request_h{}
%% HeaderList - ["HeaderField:Value"]
@@ -34,14 +42,12 @@
%%-------------------------------------------------------------------------
headers([], Headers) ->
Headers;
-headers([Header | Tail], Headers) ->
- case lists:splitwith(fun($:) -> false; (_) -> true end, Header) of
- {Key, [$: | Value]} ->
- headers(Tail, headers(http_util:to_lower(string:strip(Key)),
- string:strip(Value), Headers));
- {_, []} ->
- headers(Tail, Headers)
- end.
+headers([{Key, Value} | Tail], Headers) ->
+ headers(Tail, headers(Key, Value, Headers));
+headers([undefined], Headers) ->
+ Headers;
+headers(KeyValues, Headers) ->
+ headers([key_value(KeyValue) || KeyValue <- KeyValues], Headers).
%%-------------------------------------------------------------------------
%% headers(#http_request_h{}) -> HeaderList
diff --git a/lib/inets/src/http_lib/http_uri.erl b/lib/inets/src/http_lib/http_uri.erl
index 5962001c3a..350a4bc169 100644
--- a/lib/inets/src/http_lib/http_uri.erl
+++ b/lib/inets/src/http_lib/http_uri.erl
@@ -90,8 +90,8 @@ parse(AbsURI, Opts) ->
{error, Reason};
{Scheme, DefaultPort, Rest} ->
case (catch parse_uri_rest(Scheme, DefaultPort, Rest, Opts)) of
- {ok, {UserInfo, Host, Port, Path, Query}} ->
- {ok, {Scheme, UserInfo, Host, Port, Path, Query}};
+ {ok, Result} ->
+ {ok, Result};
{error, Reason} ->
{error, {Reason, Scheme, AbsURI}};
_ ->
@@ -148,27 +148,22 @@ parse_scheme(AbsURI, Opts) ->
end.
parse_uri_rest(Scheme, DefaultPort, "//" ++ URIPart, Opts) ->
- {Authority, PathQuery} =
- case split_uri(URIPart, "/", URIPart, 1, 0) of
- Split = {_, _} ->
- Split;
- URIPart ->
- case split_uri(URIPart, "\\?", URIPart, 1, 0) of
- Split = {_, _} ->
- Split;
- URIPart ->
- {URIPart,""}
- end
- end,
+ {Authority, PathQueryFragment} =
+ split_uri(URIPart, "[/?#]", {URIPart, ""}, 1, 0),
+ {RawPath, QueryFragment} =
+ split_uri(PathQueryFragment, "[?#]", {PathQueryFragment, ""}, 1, 0),
+ {Query, Fragment} =
+ split_uri(QueryFragment, "#", {QueryFragment, ""}, 1, 0),
{UserInfo, HostPort} = split_uri(Authority, "@", {"", Authority}, 1, 1),
{Host, Port} = parse_host_port(Scheme, DefaultPort, HostPort, Opts),
- {Path, Query} = parse_path_query(PathQuery),
- {ok, {UserInfo, Host, Port, Path, Query}}.
-
+ Path = path(RawPath),
+ case lists:keyfind(fragment, 1, Opts) of
+ {fragment, true} ->
+ {ok, {Scheme, UserInfo, Host, Port, Path, Query, Fragment}};
+ _ ->
+ {ok, {Scheme, UserInfo, Host, Port, Path, Query}}
+ end.
-parse_path_query(PathQuery) ->
- {Path, Query} = split_uri(PathQuery, "\\?", {PathQuery, ""}, 1, 0),
- {path(Path), Query}.
%% In this version of the function, we no longer need
%% the Scheme argument, but just in case...
diff --git a/lib/inets/src/http_server/httpd_conf.erl b/lib/inets/src/http_server/httpd_conf.erl
index 27446ca7fe..78dda794db 100644
--- a/lib/inets/src/http_server/httpd_conf.erl
+++ b/lib/inets/src/http_server/httpd_conf.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2013. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2015. 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,7 +44,7 @@
%% FilePath = string()
%% Result = {ok,Directory} | {error,Reason}
%% Directory = string()
-%% Reason = string() | enoent | eaccess | enotdir | FileInfo
+%% Reason = string() | enoent | eacces | enotdir | FileInfo
%% FileInfo = File info record
%%
%% Description: Checks if FilePath is a directory in which case it is
@@ -71,7 +71,7 @@ is_directory(_Type,_Access,FileInfo,_Directory) ->
%% FilePath = string()
%% Result = {ok,File} | {error,Reason}
%% File = string()
-%% Reason = string() | enoent | eaccess | enotdir | FileInfo
+%% Reason = string() | enoent | eacces | enotdir | FileInfo
%% FileInfo = File info record
%%
%% Description: Checks if FilePath is a regular file in which case it
@@ -205,13 +205,13 @@ load("MaxURISize " ++ MaxHeaderSize, []) ->
" is an invalid number of MaxHeaderSize")}
end;
-load("MaxBodySize " ++ MaxBodySize, []) ->
- case make_integer(MaxBodySize) of
+load("MaxContentLength " ++ Max, []) ->
+ case make_integer(Max) of
{ok, Integer} ->
- {ok, [], {max_body_size,Integer}};
+ {ok, [], {max_content_length, Integer}};
{error, _} ->
- {error, ?NICE(clean(MaxBodySize) ++
- " is an invalid number of MaxBodySize")}
+ {error, ?NICE(clean(Max) ++
+ " is an invalid number of MaxContentLength")}
end;
load("ServerName " ++ ServerName, []) ->
@@ -337,7 +337,7 @@ load("MaxKeepAliveRequest " ++ MaxRequests, []) ->
load("KeepAliveTimeout " ++ Timeout, []) ->
case make_integer(Timeout) of
{ok, Integer} ->
- {ok, [], {keep_alive_timeout, Integer*1000}};
+ {ok, [], {keep_alive_timeout, Integer}};
{error, _} ->
{error, ?NICE(clean(Timeout)++" is an invalid KeepAliveTimeout")}
end;
@@ -569,6 +569,12 @@ validate_config_params([{max_body_size, Value} | Rest])
validate_config_params([{max_body_size, Value} | _]) ->
throw({max_body_size, Value});
+validate_config_params([{max_content_length, Value} | Rest])
+ when is_integer(Value) andalso (Value > 0) ->
+ validate_config_params(Rest);
+validate_config_params([{max_content_length, Value} | _]) ->
+ throw({max_content_length, Value});
+
validate_config_params([{server_name, Value} | Rest])
when is_list(Value) ->
validate_config_params(Rest);
@@ -635,7 +641,7 @@ validate_config_params([{max_keep_alive_request, Value} | Rest])
when is_integer(Value) andalso (Value > 0) ->
validate_config_params(Rest);
validate_config_params([{max_keep_alive_request, Value} | _]) ->
- throw({max_header_size, Value});
+ throw({max_keep_alive_request, Value});
validate_config_params([{keep_alive_timeout, Value} | Rest])
when is_integer(Value) andalso (Value >= 0) ->
@@ -799,7 +805,7 @@ store({server_tokens, ServerTokens} = Entry, _ConfigList) ->
Server = server(ServerTokens),
{ok, [Entry, {server, Server}]};
store({keep_alive_timeout, KeepAliveTimeout}, _ConfigList) ->
- {ok, {keep_alive_timeout, KeepAliveTimeout * 1000}};
+ {ok, {keep_alive_timeout, KeepAliveTimeout}};
store(ConfigListEntry, _ConfigList) ->
{ok, ConfigListEntry}.
diff --git a/lib/inets/src/http_server/httpd_request.erl b/lib/inets/src/http_server/httpd_request.erl
index 712c73599f..6985065c3e 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-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2015. 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
@@ -118,18 +118,17 @@ validate(Method, Uri, Version) ->
%% create it.
%% ----------------------------------------------------------------------
update_mod_data(ModData, Method, RequestURI, HTTPVersion, Headers)->
- ParsedHeaders = tagup_header(Headers),
- PersistentConn = get_persistens(HTTPVersion, ParsedHeaders,
+ PersistentConn = get_persistens(HTTPVersion, Headers,
ModData#mod.config_db),
{ok, ModData#mod{data = [],
method = Method,
absolute_uri = format_absolute_uri(RequestURI,
- ParsedHeaders),
+ Headers),
request_uri = format_request_uri(RequestURI),
http_version = HTTPVersion,
request_line = Method ++ " " ++ RequestURI ++
" " ++ HTTPVersion,
- parsed_header = ParsedHeaders,
+ parsed_header = Headers,
connection = PersistentConn}}.
%%%========================================================================
@@ -146,14 +145,14 @@ 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()}.
+ {error, {size_error, 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.
- {error, {too_long, MaxURI, 414, "URI unreasonably long"},lowest_version()};
+ {error, {size_error, 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) ->
@@ -179,12 +178,12 @@ parse_version(<<?CR>> = 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()}.
+ {error, {size_error, 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, {too_long, Max, 413, "Headers unreasonably long"}, HttpVersion};
+ {error, {size_error, Max, 413, "Headers unreasonably long"}, HttpVersion};
parse_headers(<<>>, Header, Headers, Current, Max, MaxSizes, Result) ->
{?MODULE, parse_headers, [<<>>, Header, Headers, Current, Max,
@@ -204,14 +203,22 @@ parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], _, _, _, Result) ->
Result])),
{ok, NewResult};
parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, Header, Headers, _, _,
- _, Result) ->
- HTTPHeaders = [lists:reverse(Header) | Headers],
- RequestHeaderRcord =
- http_request:headers(HTTPHeaders, #http_request_h{}),
- NewResult =
- list_to_tuple(lists:reverse([Body, {RequestHeaderRcord,
- HTTPHeaders} | Result])),
- {ok, NewResult};
+ MaxSizes, Result) ->
+ case http_request:key_value(lists:reverse(Header)) of
+ undefined -> %% Skip headers with missing :
+ {ok, list_to_tuple(lists:reverse([Body, {http_request:headers(Headers, #http_request_h{}), Headers} | Result]))};
+ NewHeader ->
+ case check_header(NewHeader, MaxSizes) of
+ ok ->
+ {ok, list_to_tuple(lists:reverse([Body, {http_request:headers([NewHeader | Headers],
+ #http_request_h{}),
+ [NewHeader | Headers]} | Result]))};
+
+ {error, Reason} ->
+ HttpVersion = lists:nth(3, lists:reverse(Result)),
+ {error, Reason, HttpVersion}
+ end
+ end;
parse_headers(<<?CR,?LF,?CR>> = Data, Header, Headers, Current, Max,
MaxSizes, Result) ->
@@ -243,8 +250,21 @@ parse_headers(<<?LF, Octet, Rest/binary>>, Header, Headers, Current, Max,
MaxSizes, Result);
parse_headers(<<?CR,?LF, Octet, Rest/binary>>, Header, Headers, _, Max,
MaxSizes, Result) ->
- parse_headers(Rest, [Octet], [lists:reverse(Header) | Headers],
- 0, Max, MaxSizes, Result);
+ case http_request:key_value(lists:reverse(Header)) of
+ undefined -> %% Skip headers with missing :
+ parse_headers(Rest, [Octet], Headers,
+ 0, Max, MaxSizes, Result);
+ NewHeader ->
+ case check_header(NewHeader, MaxSizes) of
+ ok ->
+ parse_headers(Rest, [Octet], [NewHeader | Headers],
+ 0, Max, MaxSizes, Result);
+ {error, Reason} ->
+ HttpVersion = lists:nth(3, lists:reverse(Result)),
+ {error, Reason, HttpVersion}
+ end
+ end;
+
parse_headers(<<?CR>> = Data, Header, Headers, Current, Max,
MaxSizes, Result) ->
{?MODULE, parse_headers, [Data, Header, Headers, Current, Max,
@@ -388,29 +408,25 @@ get_persistens(HTTPVersion,ParsedHeader,ConfigDB)->
false
end.
-
-%%----------------------------------------------------------------------
-%% tagup_header
-%%
-%% Parses the header of a HTTP request and returns a key,value tuple
-%% list containing Name and Value of each header directive as of:
-%%
-%% Content-Type: multipart/mixed -> {"Content-Type", "multipart/mixed"}
-%%
-%% But in http/1.1 the field-names are case insencitive so now it must be
-%% Content-Type: multipart/mixed -> {"content-type", "multipart/mixed"}
-%% The standard furthermore says that leading and traling white space
-%% is not a part of the fieldvalue and shall therefore be removed.
-%%----------------------------------------------------------------------
-tagup_header([]) -> [];
-tagup_header([Line|Rest]) -> [tag(Line, [])|tagup_header(Rest)].
-
-tag([], Tag) ->
- {http_util:to_lower(lists:reverse(Tag)), ""};
-tag([$:|Rest], Tag) ->
- {http_util:to_lower(lists:reverse(Tag)), string:strip(Rest)};
-tag([Chr|Rest], Tag) ->
- tag(Rest, [Chr|Tag]).
-
lowest_version()->
"HTTP/0.9".
+
+check_header({"content-length", Value}, Maxsizes) ->
+ Max = proplists:get_value(max_content_length, Maxsizes),
+ MaxLen = length(integer_to_list(Max)),
+ case length(Value) =< MaxLen of
+ true ->
+ try
+ _ = list_to_integer(Value),
+ ok
+ catch _:_ ->
+ {error, {size_error, Max, 411, "content-length not an integer"}}
+ end;
+ false ->
+ {error, {size_error, Max, 413, "content-length unreasonably long"}}
+ end;
+check_header(_, _) ->
+ ok.
+
+
+
diff --git a/lib/inets/src/http_server/httpd_request_handler.erl b/lib/inets/src/http_server/httpd_request_handler.erl
index 9bea58cc9e..f7a9fe5d49 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-2014. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2015. 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
@@ -96,8 +96,9 @@ init([Manager, ConfigDB, AcceptTimeout]) ->
proc_lib:init_ack({ok, self()}),
{SocketType, Socket} = await_socket_ownership_transfer(AcceptTimeout),
-
- KeepAliveTimeOut = httpd_util:lookup(ConfigDB, keep_alive_timeout, 150000),
+
+ %%Timeout value is in seconds we want it in milliseconds
+ KeepAliveTimeOut = 1000 * httpd_util:lookup(ConfigDB, keep_alive_timeout, 150),
case http_transport:negotiate(SocketType, Socket, ?HANDSHAKE_TIMEOUT) of
{error, _Error} ->
@@ -119,11 +120,15 @@ continue_init(Manager, ConfigDB, SocketType, Socket, TimeOut) ->
MaxHeaderSize = max_header_size(ConfigDB),
MaxURISize = max_uri_size(ConfigDB),
NrOfRequest = max_keep_alive_request(ConfigDB),
-
+ MaxContentLen = max_content_length(ConfigDB),
+
{_, Status} = httpd_manager:new_connection(Manager),
MFA = {httpd_request, parse, [[{max_uri, MaxURISize}, {max_header, MaxHeaderSize},
- {max_version, ?HTTP_MAX_VERSION_STRING}, {max_method, ?HTTP_MAX_METHOD_STRING}]]},
+ {max_version, ?HTTP_MAX_VERSION_STRING},
+ {max_method, ?HTTP_MAX_METHOD_STRING},
+ {max_content_length, MaxContentLen}
+ ]]},
State = #state{mod = Mod,
manager = Manager,
@@ -207,7 +212,7 @@ handle_info({Proto, Socket, Data},
set_new_data_size(cancel_request_timeout(State), NewDataSize)
end,
handle_http_msg(Result, NewState);
- {error, {too_long, MaxSize, ErrCode, ErrStr}, Version} ->
+ {error, {size_error, MaxSize, ErrCode, ErrStr}, Version} ->
NewModData = ModData#mod{http_version = Version},
httpd_response:send_status(NewModData, ErrCode, ErrStr),
Reason = io_lib:format("~p: ~p max size is ~p~n",
@@ -444,8 +449,7 @@ handle_body(#state{headers = Headers, body = Body, mod = ModData} = State,
error_log(Reason, ModData),
{stop, normal, State#state{response_sent = true}};
_ ->
- Length =
- list_to_integer(Headers#http_request_h.'content-length'),
+ Length = list_to_integer(Headers#http_request_h.'content-length'),
case ((Length =< MaxBodySize) or (MaxBodySize == nolimit)) of
true ->
case httpd_request:whole_body(Body, Length) of
@@ -454,7 +458,7 @@ handle_body(#state{headers = Headers, body = Body, mod = ModData} = State,
ModData#mod.socket,
[{active, once}]),
{noreply, State#state{mfa =
- {Module, Function, Args}}};
+ {Module, Function, Args}}};
{ok, NewBody} ->
handle_response(
@@ -471,7 +475,7 @@ handle_body(#state{headers = Headers, body = Body, mod = ModData} = State,
handle_expect(#state{headers = Headers, mod =
#mod{config_db = ConfigDB} = ModData} = State,
MaxBodySize) ->
- Length = Headers#http_request_h.'content-length',
+ Length = list_to_integer(Headers#http_request_h.'content-length'),
case expect(Headers, ModData#mod.http_version, ConfigDB) of
continue when (MaxBodySize > Length) orelse (MaxBodySize =:= nolimit) ->
httpd_response:send_status(ModData, 100, ""),
@@ -545,9 +549,13 @@ handle_next_request(#state{mod = #mod{connection = true} = ModData,
init_data = ModData#mod.init_data},
MaxHeaderSize = max_header_size(ModData#mod.config_db),
MaxURISize = max_uri_size(ModData#mod.config_db),
+ MaxContentLen = max_content_length(ModData#mod.config_db),
MFA = {httpd_request, parse, [[{max_uri, MaxURISize}, {max_header, MaxHeaderSize},
- {max_version, ?HTTP_MAX_VERSION_STRING}, {max_method, ?HTTP_MAX_METHOD_STRING}]]},
+ {max_version, ?HTTP_MAX_VERSION_STRING},
+ {max_method, ?HTTP_MAX_METHOD_STRING},
+ {max_content_length, MaxContentLen}
+ ]]},
TmpState = State#state{mod = NewModData,
mfa = MFA,
max_keep_alive_request = decrease(Max),
@@ -630,3 +638,5 @@ max_body_size(ConfigDB) ->
max_keep_alive_request(ConfigDB) ->
httpd_util:lookup(ConfigDB, max_keep_alive_request, infinity).
+max_content_length(ConfigDB) ->
+ httpd_util:lookup(ConfigDB, max_content_length, ?HTTP_MAX_CONTENT_LENGTH).
diff --git a/lib/inets/src/http_server/mod_alias.erl b/lib/inets/src/http_server/mod_alias.erl
index 0b9fe4cfe0..5039cd56b5 100644
--- a/lib/inets/src/http_server/mod_alias.erl
+++ b/lib/inets/src/http_server/mod_alias.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2010. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2015. 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
@@ -55,6 +55,7 @@ do(#mod{data = Data} = Info) ->
do_alias(#mod{config_db = ConfigDB,
request_uri = ReqURI,
+ socket_type = SocketType,
data = Data}) ->
{ShortPath, Path, AfterPath} =
real_name(ConfigDB, ReqURI, which_alias(ConfigDB)),
@@ -70,8 +71,9 @@ do_alias(#mod{config_db = ConfigDB,
(LastChar =/= $/)) ->
?hdrt("directory and last-char is a /", []),
ServerName = which_server_name(ConfigDB),
- Port = port_string( which_port(ConfigDB) ),
- URL = "http://" ++ ServerName ++ Port ++ ReqURI ++ "/",
+ Port = port_string(which_port(ConfigDB)),
+ Protocol = get_protocol(SocketType),
+ URL = Protocol ++ ServerName ++ Port ++ ReqURI ++ "/",
ReasonPhrase = httpd_util:reason_phrase(301),
Message = httpd_util:message(301, URL, ConfigDB),
{proceed,
@@ -94,6 +96,12 @@ port_string(80) ->
port_string(Port) ->
":" ++ integer_to_list(Port).
+get_protocol(ip_comm) ->
+ "http://";
+get_protocol(_) ->
+ %% Should clean up to have only one ssl type essl vs ssl is not relevant any more
+ "https://".
+
%% real_name
real_name(ConfigDB, RequestURI, []) ->
diff --git a/lib/inets/test/http_format_SUITE.erl b/lib/inets/test/http_format_SUITE.erl
index d4a3f28f38..5952e9fd6e 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-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2015. 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
@@ -355,10 +355,12 @@ http_request(Config) when is_list(Config) ->
"http://www.erlang.org",
"HTTP/1.1",
{#http_request_h{host = "www.erlang.org", te = []},
- ["te: ","host:www.erlang.org"]}, <<>>} =
+ [{"te", []}, {"host", "www.erlang.org"}]}, <<>>} =
parse(httpd_request, parse, [[{max_header, ?HTTP_MAX_HEADER_SIZE},
{max_version, ?HTTP_MAX_VERSION_STRING},
- {max_method, ?HTTP_MAX_METHOD_STRING}]],
+ {max_method, ?HTTP_MAX_METHOD_STRING},
+ {max_content_length, ?HTTP_MAX_CONTENT_LENGTH}
+ ]],
HttpHead),
HttpHead1 = ["GET http://www.erlang.org HTTP/1.1" ++
@@ -369,7 +371,9 @@ http_request(Config) when is_list(Config) ->
{#http_request_h{}, []}, <<>>} =
parse(httpd_request, parse, [[{max_header, ?HTTP_MAX_HEADER_SIZE},
{max_version, ?HTTP_MAX_VERSION_STRING},
- {max_method, ?HTTP_MAX_METHOD_STRING}]], HttpHead1),
+ {max_method, ?HTTP_MAX_METHOD_STRING},
+ {max_content_length, ?HTTP_MAX_CONTENT_LENGTH}
+ ]], HttpHead1),
HttpHead2 = ["GET http://www.erlang.org HTTP/1.1" ++
@@ -380,7 +384,9 @@ http_request(Config) when is_list(Config) ->
{#http_request_h{}, []}, <<>>} =
parse(httpd_request, parse, [[{max_header, ?HTTP_MAX_HEADER_SIZE},
{max_version, ?HTTP_MAX_VERSION_STRING},
- {max_method, ?HTTP_MAX_METHOD_STRING}]], HttpHead2),
+ {max_method, ?HTTP_MAX_METHOD_STRING},
+ {max_content_length, ?HTTP_MAX_CONTENT_LENGTH}
+ ]], 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 c535d59b9f..21be7862cb 100644
--- a/lib/inets/test/httpc_SUITE.erl
+++ b/lib/inets/test/httpc_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2015. 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
@@ -91,6 +91,7 @@ only_simulated() ->
cookie,
cookie_profile,
empty_set_cookie,
+ invalid_set_cookie,
trace,
stream_once,
stream_single_chunk,
@@ -568,6 +569,18 @@ empty_set_cookie(Config) when is_list(Config) ->
ok = httpc:set_options([{cookies, disabled}]).
%%-------------------------------------------------------------------------
+invalid_set_cookie(doc) ->
+ ["Test ignoring invalid Set-Cookie header"];
+invalid_set_cookie(Config) when is_list(Config) ->
+ ok = httpc:set_options([{cookies, enabled}]),
+
+ URL = url(group_name(Config), "/invalid_set_cookie.html", Config),
+ {ok, {{_,200,_}, [_|_], [_|_]}} =
+ httpc:request(get, {URL, []}, [], []),
+
+ ok = httpc:set_options([{cookies, disabled}]).
+
+%%-------------------------------------------------------------------------
headers_as_is(doc) ->
["Test the option headers_as_is"];
headers_as_is(Config) when is_list(Config) ->
@@ -1246,8 +1259,9 @@ dummy_server_init(Caller, ip_comm, Inet, _) ->
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);
+ {max_method, ?HTTP_MAX_METHOD_STRING},
+ {max_content_length, ?HTTP_MAX_CONTENT_LENGTH}]]},
+ [], ListenSocket);
dummy_server_init(Caller, ssl, Inet, SSLOptions) ->
BaseOpts = [binary, {reuseaddr,true}, {active, false} |
@@ -1261,7 +1275,9 @@ dummy_ssl_server_init(Caller, BaseOpts, Inet) ->
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}]]},
+ {max_method, ?HTTP_MAX_METHOD_STRING},
+ {max_content_length, ?HTTP_MAX_CONTENT_LENGTH}
+ ]]},
[], ListenSocket).
dummy_ipcomm_server_loop(MFA, Handlers, ListenSocket) ->
@@ -1338,16 +1354,20 @@ handle_request(Module, Function, Args, Socket) ->
stop ->
stop;
<<>> ->
- {httpd_request, parse, [[<<>>, [{max_uri, ?HTTP_MAX_URI_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}]]]};
+ {max_method, ?HTTP_MAX_METHOD_STRING},
+ {max_content_length, ?HTTP_MAX_CONTENT_LENGTH}
+ ]]};
Data ->
handle_request(httpd_request, parse,
[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)
+ {max_header, ?HTTP_MAX_HEADER_SIZE},
+ {max_version,?HTTP_MAX_VERSION_STRING},
+ {max_method, ?HTTP_MAX_METHOD_STRING},
+ {max_content_length, ?HTTP_MAX_CONTENT_LENGTH}
+ ]], Socket)
end;
NewMFA ->
NewMFA
@@ -1437,7 +1457,7 @@ dummy_ssl_server_hang_loop(_) ->
ensure_host_header_with_port([]) ->
false;
-ensure_host_header_with_port(["host: " ++ Host| _]) ->
+ensure_host_header_with_port([{"host", Host}| _]) ->
case string:tokens(Host, [$:]) of
[_ActualHost, _Port] ->
true;
@@ -1449,7 +1469,7 @@ ensure_host_header_with_port([_|T]) ->
auth_header([]) ->
auth_header_not_found;
-auth_header(["authorization:" ++ Value | _]) ->
+auth_header([{"authorization", Value} | _]) ->
{ok, string:strip(Value)};
auth_header([_ | Tail]) ->
auth_header(Tail).
@@ -1466,7 +1486,7 @@ handle_auth("Basic " ++ UserInfo, Challange, DefaultResponse) ->
check_cookie([]) ->
ct:fail(no_cookie_header);
-check_cookie(["cookie:" ++ _Value | _]) ->
+check_cookie([{"cookie", _} | _]) ->
ok;
check_cookie([_Head | Tail]) ->
check_cookie(Tail).
@@ -1686,6 +1706,14 @@ handle_uri(_,"/empty_set_cookie.html",_,_,_,_) ->
"Content-Length:32\r\n\r\n"++
"<HTML><BODY>foobar</BODY></HTML>";
+handle_uri(_,"/invalid_set_cookie.html",_,_,_,_) ->
+ "HTTP/1.1 200 ok\r\n" ++
+ "set-cookie: =\r\n" ++
+ "set-cookie: name=\r\n" ++
+ "set-cookie: name-or-value\r\n" ++
+ "Content-Length:32\r\n\r\n"++
+ "<HTML><BODY>foobar</BODY></HTML>";
+
handle_uri(_,"/missing_crlf.html",_,_,_,_) ->
"HTTP/1.1 200 ok" ++
"Content-Length:32\r\n" ++
diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl
index 4010597657..342004f19b 100644
--- a/lib/inets/test/httpd_SUITE.erl
+++ b/lib/inets/test/httpd_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2015. 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
@@ -132,6 +132,7 @@ http_get() ->
bad_hex,
missing_CR,
max_header,
+ max_content_length,
ipv6
].
@@ -979,13 +980,22 @@ max_header(Config) when is_list(Config) ->
Host = ?config(host, Config),
case Version of
"HTTP/0.9" ->
- {skip, no_implemented};
+ {skip, not_implemented};
_ ->
dos_hostname(?config(type, Config), ?config(port, Config), Host,
?config(node, Config), Version, ?MAX_HEADER_SIZE)
end.
%%-------------------------------------------------------------------------
+max_content_length() ->
+ ["Denial Of Service (DOS) attack, prevented by max_content_length"].
+max_content_length(Config) when is_list(Config) ->
+ Version = ?config(http_version, Config),
+ Host = ?config(host, Config),
+ garbage_content_length(?config(type, Config), ?config(port, Config), Host,
+ ?config(node, Config), Version).
+
+%%-------------------------------------------------------------------------
security_1_1(Config) when is_list(Config) ->
security([{http_version, "HTTP/1.1"} | Config]).
@@ -1368,7 +1378,9 @@ server_config(http_reload, Config) ->
server_config(https_reload, Config) ->
[{keep_alive_timeout, 2}] ++ server_config(https, Config);
server_config(http_limit, Config) ->
- [{max_clients, 1}] ++ server_config(http, Config);
+ [{max_clients, 1},
+ %% Make sure option checking code is run
+ {max_content_length, 100000002}] ++ server_config(http, Config);
server_config(https_limit, Config) ->
[{max_clients, 1}] ++ server_config(https, Config);
server_config(http_basic_auth, Config) ->
@@ -1814,7 +1826,7 @@ dos_hostname(Type, Port, Host, Node, Version, Max) ->
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
dos_hostname_request(TooLongHeader, Version),
- [{statuscode, dos_code(Version)},
+ [{statuscode, request_entity_too_large_code(Version)},
{version, Version}]).
dos_hostname_request(Host, Version) ->
dos_http_request("GET / ", Version, Host).
@@ -1824,11 +1836,32 @@ dos_http_request(Request, "HTTP/1.1" = Version, Host) ->
dos_http_request(Request, Version, Host) ->
Request ++ Version ++ "\r\nhost:" ++ Host ++ "\r\n\r\n".
-dos_code("HTTP/1.0") ->
+request_entity_too_large_code("HTTP/1.0") ->
403; %% 413 not defined in HTTP/1.0
-dos_code(_) ->
+request_entity_too_large_code(_) ->
413.
+length_required_code("HTTP/1.0") ->
+ 403; %% 411 not defined in HTTP/1.0
+length_required_code(_) ->
+ 411.
+
+garbage_content_length(Type, Port, Host, Node, Version) ->
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ garbage_content_length_request("GET / ", Version, Host, "aaaa"),
+ [{statuscode, length_required_code(Version)},
+ {version, Version}]),
+ ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
+ garbage_content_length_request("GET / ", Version, Host,
+ lists:duplicate($a, 100)),
+ [{statuscode, request_entity_too_large_code(Version)},
+ {version, Version}]).
+
+garbage_content_length_request(Request, Version, Host, Garbage) ->
+ http_request(Request, Version, Host,
+ {"content-length:" ++ Garbage, "Body with garbage content length indicator"}).
+
+
update_password(Node, ServerRoot, _Address, Port, AuthPrefix, Dir, Old, New)->
Directory = filename:join([ServerRoot, "htdocs", AuthPrefix ++ Dir]),
rpc:call(Node, mod_auth, update_password,
diff --git a/lib/inets/test/uri_SUITE.erl b/lib/inets/test/uri_SUITE.erl
index 9ba09e1474..f75e347d0c 100644
--- a/lib/inets/test/uri_SUITE.erl
+++ b/lib/inets/test/uri_SUITE.erl
@@ -46,6 +46,7 @@ all() ->
userinfo,
scheme,
queries,
+ fragments,
escaped,
hexed_query
].
@@ -105,6 +106,42 @@ queries(Config) when is_list(Config) ->
{ok, {http,[],"localhost",8888,"/foobar.html","?foo=bar&foobar=42"}} =
http_uri:parse("http://localhost:8888/foobar.html?foo=bar&foobar=42").
+fragments(Config) when is_list(Config) ->
+ {ok, {http,[],"localhost",80,"/",""}} =
+ http_uri:parse("http://localhost#fragment"),
+ {ok, {http,[],"localhost",80,"/path",""}} =
+ http_uri:parse("http://localhost/path#fragment"),
+ {ok, {http,[],"localhost",80,"/","?query"}} =
+ http_uri:parse("http://localhost?query#fragment"),
+ {ok, {http,[],"localhost",80,"/path","?query"}} =
+ http_uri:parse("http://localhost/path?query#fragment"),
+ {ok, {http,[],"localhost",80,"/","","#fragment"}} =
+ http_uri:parse("http://localhost#fragment", [{fragment,true}]),
+ {ok, {http,[],"localhost",80,"/path","","#fragment"}} =
+ http_uri:parse("http://localhost/path#fragment", [{fragment,true}]),
+ {ok, {http,[],"localhost",80,"/","?query","#fragment"}} =
+ http_uri:parse("http://localhost?query#fragment", [{fragment,true}]),
+ {ok, {http,[],"localhost",80,"/path","?query","#fragment"}} =
+ http_uri:parse("http://localhost/path?query#fragment",
+ [{fragment,true}]),
+ {ok, {http,[],"localhost",80,"/","",""}} =
+ http_uri:parse("http://localhost", [{fragment,true}]),
+ {ok, {http,[],"localhost",80,"/path","",""}} =
+ http_uri:parse("http://localhost/path", [{fragment,true}]),
+ {ok, {http,[],"localhost",80,"/","?query",""}} =
+ http_uri:parse("http://localhost?query", [{fragment,true}]),
+ {ok, {http,[],"localhost",80,"/path","?query",""}} =
+ http_uri:parse("http://localhost/path?query", [{fragment,true}]),
+ {ok, {http,[],"localhost",80,"/","","#"}} =
+ http_uri:parse("http://localhost#", [{fragment,true}]),
+ {ok, {http,[],"localhost",80,"/path","","#"}} =
+ http_uri:parse("http://localhost/path#", [{fragment,true}]),
+ {ok, {http,[],"localhost",80,"/","?query","#"}} =
+ http_uri:parse("http://localhost?query#", [{fragment,true}]),
+ {ok, {http,[],"localhost",80,"/path","?query","#"}} =
+ http_uri:parse("http://localhost/path?query#", [{fragment,true}]),
+ ok.
+
escaped(Config) when is_list(Config) ->
{ok, {http,[],"www.somedomain.com",80,"/%2Eabc",[]}} =
http_uri:parse("http://www.somedomain.com/%2Eabc"),
diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk
index dbae5e4b3c..7d11916454 100644
--- a/lib/inets/vsn.mk
+++ b/lib/inets/vsn.mk
@@ -18,6 +18,6 @@
# %CopyrightEnd%
APPLICATION = inets
-INETS_VSN = 5.10.4
+INETS_VSN = 5.10.5
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)"
diff --git a/lib/kernel/doc/src/gen_sctp.xml b/lib/kernel/doc/src/gen_sctp.xml
index dc9e4766a9..ee8cd441d4 100644
--- a/lib/kernel/doc/src/gen_sctp.xml
+++ b/lib/kernel/doc/src/gen_sctp.xml
@@ -961,7 +961,7 @@
<pre> #sctp_paddrinfo{
assoc_id = assoc_id(),
address = {IP, Port},
- state = inactive | active,
+ state = inactive | active | unconfirmed,
cwnd = integer(),
srtt = integer(),
rto = integer(),
diff --git a/lib/kernel/doc/src/inet.xml b/lib/kernel/doc/src/inet.xml
index 8dd311e5cd..77a8caaaf6 100644
--- a/lib/kernel/doc/src/inet.xml
+++ b/lib/kernel/doc/src/inet.xml
@@ -332,23 +332,23 @@ fe80::204:acff:fe17:bf38
<taglist>
<tag><c>recv_avg</c></tag>
<item>
- <p>Average size of packets in bytes received to the socket.</p>
+ <p>Average size of packets in bytes received by the socket.</p>
</item>
<tag><c>recv_cnt</c></tag>
<item>
- <p>Number of packets received to the socket.</p>
+ <p>Number of packets received by the socket.</p>
</item>
<tag><c>recv_dvi</c></tag>
<item>
- <p>Average packet size deviation in bytes received to the socket.</p>
+ <p>Average packet size deviation in bytes received by the socket.</p>
</item>
<tag><c>recv_max</c></tag>
<item>
- <p>The size of the largest packet in bytes received to the socket.</p>
+ <p>The size of the largest packet in bytes received by the socket.</p>
</item>
<tag><c>recv_oct</c></tag>
<item>
- <p>Number of bytes received to the socket.</p>
+ <p>Number of bytes received by the socket.</p>
</item>
<tag><c>send_avg</c></tag>
diff --git a/lib/kernel/doc/src/kernel_app.xml b/lib/kernel/doc/src/kernel_app.xml
index 00c6bc33d6..96e3651140 100644
--- a/lib/kernel/doc/src/kernel_app.xml
+++ b/lib/kernel/doc/src/kernel_app.xml
@@ -4,7 +4,7 @@
<appref>
<header>
<copyright>
- <year>1996</year><year>2014</year>
+ <year>1996</year><year>2015</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -188,6 +188,18 @@
<p>Define the <c>First..Last</c> port range for the listener
socket of a distributed Erlang node.</p>
</item>
+ <tag><c>{inet_dist_listen_options, Opts}</c></tag>
+ <item>
+ <p>Define a list of extra socket options to be used when opening the
+ listening socket for a distributed Erlang node.
+ See <seealso marker="gen_tcp#listen/2">gen_tcp:listen/2</seealso></p>
+ </item>
+ <tag><c>{inet_dist_connect_options, Opts}</c></tag>
+ <item>
+ <p>Define a list of extra socket options to be used when connecting to
+ other distributed Erlang nodes.
+ See <seealso marker="gen_tcp#connect/4">gen_tcp:connect/4</seealso></p>
+ </item>
<tag><c>inet_parse_error_log = silent</c></tag>
<item>
<p>If this configuration parameter is set, no
diff --git a/lib/kernel/src/application_controller.erl b/lib/kernel/src/application_controller.erl
index daad45b6c2..6635885aaf 100644
--- a/lib/kernel/src/application_controller.erl
+++ b/lib/kernel/src/application_controller.erl
@@ -1615,7 +1615,6 @@ conv([Key, Val | T]) ->
[{make_term(Key), make_term(Val)} | conv(T)];
conv(_) -> [].
-%%% Fix some day: eliminate the duplicated code here
make_term(Str) ->
case erl_scan:string(Str) of
{ok, Tokens, _} ->
@@ -1623,16 +1622,17 @@ make_term(Str) ->
{ok, Term} ->
Term;
{error, {_,M,Reason}} ->
- error_logger:format("application_controller: ~ts: ~ts~n",
- [M:format_error(Reason), Str]),
- throw({error, {bad_environment_value, Str}})
+ handle_make_term_error(M, Reason, Str)
end;
{error, {_,M,Reason}, _} ->
- error_logger:format("application_controller: ~ts: ~ts~n",
- [M:format_error(Reason), Str]),
- throw({error, {bad_environment_value, Str}})
+ handle_make_term_error(M, Reason, Str)
end.
+handle_make_term_error(Mod, Reason, Str) ->
+ error_logger:format("application_controller: ~ts: ~ts~n",
+ [Mod:format_error(Reason), Str]),
+ throw({error, {bad_environment_value, Str}}).
+
get_env_i(Name, #state{conf_data = ConfData}) when is_list(ConfData) ->
case lists:keyfind(Name, 1, ConfData) of
{_Name, Env} -> Env;
diff --git a/lib/kernel/src/erl_distribution.erl b/lib/kernel/src/erl_distribution.erl
index 25ad34357a..3c4429129e 100644
--- a/lib/kernel/src/erl_distribution.erl
+++ b/lib/kernel/src/erl_distribution.erl
@@ -22,7 +22,6 @@
-export([start_link/0,start_link/1,init/1,start/1,stop/0]).
-%-define(DBG,io:format("~p:~p~n",[?MODULE,?LINE])).
-define(DBG,erlang:display([?MODULE,?LINE])).
start_link() ->
diff --git a/lib/kernel/src/gen_udp.erl b/lib/kernel/src/gen_udp.erl
index 70dceb3679..860eec10a0 100644
--- a/lib/kernel/src/gen_udp.erl
+++ b/lib/kernel/src/gen_udp.erl
@@ -78,7 +78,7 @@
ipv6_v6only.
-type socket() :: port().
--export_type([option/0, option_name/0]).
+-export_type([option/0, option_name/0, socket/0]).
-spec open(Port) -> {ok, Socket} | {error, Reason} when
Port :: inet:port_number(),
diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl
index 43bab8bcf0..ec2c350931 100644
--- a/lib/kernel/src/inet.erl
+++ b/lib/kernel/src/inet.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2014. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2015. 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
@@ -1070,7 +1070,7 @@ gethostbyname_tm(Name, Type, Timer, [wins|_]=Opts) ->
gethostbyname_tm_native(Name, Type, Timer, Opts);
gethostbyname_tm(Name, Type, Timer, [native|_]=Opts) ->
gethostbyname_tm_native(Name, Type, Timer, Opts);
-gethostbyname_tm(Name, Type, Timer, [_|_]=Opts) ->
+gethostbyname_tm(Name, Type, Timer, [_|Opts]) ->
gethostbyname_tm(Name, Type, Timer, Opts);
%% Make sure we always can look up our own hostname.
gethostbyname_tm(Name, Type, Timer, []) ->
diff --git a/lib/kernel/src/inet_tcp_dist.erl b/lib/kernel/src/inet_tcp_dist.erl
index 63f236b069..835dcf2705 100644
--- a/lib/kernel/src/inet_tcp_dist.erl
+++ b/lib/kernel/src/inet_tcp_dist.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2013. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2015. 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,7 +77,7 @@ listen(Name) ->
Error
end.
-do_listen(Options0) ->
+do_listen(Options) ->
{First,Last} = case application:get_env(kernel,inet_dist_listen_min) of
{ok,N} when is_integer(N) ->
case application:get_env(kernel,
@@ -90,13 +90,7 @@ do_listen(Options0) ->
_ ->
{0,0}
end,
- Options = case application:get_env(kernel, inet_dist_use_interface) of
- {ok, Ip} ->
- [{ip, Ip} | Options0];
- _ ->
- Options0
- end,
- do_listen(First, Last, [{backlog,128}|Options]).
+ do_listen(First, Last, listen_options([{backlog,128}|Options])).
do_listen(First,Last,_) when First > Last ->
{error,eaddrinuse};
@@ -108,6 +102,23 @@ do_listen(First,Last,Options) ->
Other
end.
+listen_options(Opts0) ->
+ Opts1 =
+ case application:get_env(kernel, inet_dist_use_interface) of
+ {ok, Ip} ->
+ [{ip, Ip} | Opts0];
+ _ ->
+ Opts0
+ end,
+ case application:get_env(kernel, inet_dist_listen_options) of
+ {ok,ListenOpts} ->
+ erlang:display({inet_dist_listen_options, ListenOpts}),
+ ListenOpts ++ Opts1;
+ _ ->
+ Opts1
+ end.
+
+
%% ------------------------------------------------------------
%% Accepts new connection attempts from other Erlang nodes.
%% ------------------------------------------------------------
@@ -219,7 +230,7 @@ nodelay() ->
_ ->
{nodelay, true}
end.
-
+
%% ------------------------------------------------------------
%% Get remote information about a Socket.
@@ -260,9 +271,11 @@ do_setup(Kernel, Node, Type, MyNode, LongOrShortNames,SetupTime) ->
?trace("port_please(~p) -> version ~p~n",
[Node,Version]),
dist_util:reset_timer(Timer),
- case inet_tcp:connect(Ip, TcpPort,
- [{active, false},
- {packet,2}]) of
+ case
+ inet_tcp:connect(
+ Ip, TcpPort,
+ connect_options([{active, false}, {packet, 2}]))
+ of
{ok, Socket} ->
HSData = #hs_data{
kernel_pid = Kernel,
@@ -324,6 +337,15 @@ do_setup(Kernel, Node, Type, MyNode, LongOrShortNames,SetupTime) ->
?shutdown(Node)
end.
+connect_options(Opts) ->
+ case application:get_env(kernel, inet_dist_connect_options) of
+ {ok,ConnectOpts} ->
+ erlang:display({inet_dist_listen_options, ConnectOpts}),
+ ConnectOpts ++ Opts;
+ _ ->
+ Opts
+ end.
+
%%
%% Close a socket.
%%
diff --git a/lib/kernel/src/standard_error.erl b/lib/kernel/src/standard_error.erl
index 10cf77e0d4..1c43063937 100644
--- a/lib/kernel/src/standard_error.erl
+++ b/lib/kernel/src/standard_error.erl
@@ -63,7 +63,7 @@ server(PortName,PortSettings) ->
run(Port).
run(P) ->
- put(unicode,false),
+ put(encoding, latin1),
server_loop(P).
server_loop(Port) ->
@@ -95,25 +95,47 @@ do_io_request(Req, From, ReplyAs, Port) ->
io_reply(From, ReplyAs, Reply).
%% New in R13B
-% Wide characters (Unicode)
-io_request({put_chars,Encoding,Chars}, Port) -> % Binary new in R9C
- put_chars(wrap_characters_to_binary(Chars,Encoding,
- case get(unicode) of
- true -> unicode;
- _ -> latin1
- end), Port);
-io_request({put_chars,Encoding,Mod,Func,Args}, Port) ->
- Result = case catch apply(Mod,Func,Args) of
- Data when is_list(Data); is_binary(Data) ->
- wrap_characters_to_binary(Data,Encoding,
- case get(unicode) of
- true -> unicode;
- _ -> latin1
- end);
- Undef ->
- Undef
- end,
- put_chars(Result, Port);
+%% Encoding option (unicode/latin1)
+io_request({put_chars,unicode,Chars}, Port) ->
+ case wrap_characters_to_binary(Chars, unicode, get(encoding)) of
+ error ->
+ {error,{error,put_chars}};
+ Bin ->
+ put_chars(Bin, Port)
+ end;
+io_request({put_chars,unicode,Mod,Func,Args}, Port) ->
+ case catch apply(Mod, Func, Args) of
+ Data when is_list(Data); is_binary(Data) ->
+ case wrap_characters_to_binary(Data, unicode, get(encoding)) of
+ Bin when is_binary(Bin) ->
+ put_chars(Bin, Port);
+ error ->
+ {error,{error,put_chars}}
+ end;
+ _ ->
+ {error,{error,put_chars}}
+ end;
+io_request({put_chars,latin1,Chars}, Port) ->
+ case catch unicode:characters_to_binary(Chars, latin1, get(encoding)) of
+ Data when is_binary(Data) ->
+ put_chars(Data, Port);
+ _ ->
+ {error,{error,put_chars}}
+ end;
+io_request({put_chars,latin1,Mod,Func,Args}, Port) ->
+ case catch apply(Mod, Func, Args) of
+ Data when is_list(Data); is_binary(Data) ->
+ case
+ catch unicode:characters_to_binary(Data, latin1, get(encoding))
+ of
+ Bin when is_binary(Bin) ->
+ put_chars(Bin, Port);
+ _ ->
+ {error,{error,put_chars}}
+ end;
+ _ ->
+ {error,{error,put_chars}}
+ end;
%% BC if called from pre-R13 node
io_request({put_chars,Chars}, Port) ->
io_request({put_chars,latin1,Chars}, Port);
@@ -134,10 +156,10 @@ io_request({get_geometry,rows},Port) ->
_ ->
{error,{error,enotsup}}
end;
-io_request({getopts,[]}, Port) ->
- getopts(Port);
-io_request({setopts,Opts}, Port) when is_list(Opts) ->
- setopts(Opts, Port);
+io_request(getopts, _Port) ->
+ getopts();
+io_request({setopts,Opts}, _Port) when is_list(Opts) ->
+ setopts(Opts);
io_request({requests,Reqs}, Port) ->
io_requests(Reqs, {ok,ok}, Port);
io_request(R, _Port) -> %Unknown request
@@ -176,47 +198,48 @@ io_reply(From, ReplyAs, Reply) ->
%% put_chars
put_chars(Chars, Port) when is_binary(Chars) ->
_ = put_port(Chars, Port),
- {ok,ok};
-put_chars(Chars, Port) ->
- case catch list_to_binary(Chars) of
- Binary when is_binary(Binary) ->
- put_chars(Binary, Port);
- _ ->
- {error,{error,put_chars}}
- end.
+ {ok,ok}.
%% setopts
-setopts(Opts0,Port) ->
- Opts = proplists:unfold(
- proplists:substitute_negations(
- [{latin1,unicode}],
- Opts0)),
+setopts(Opts0) ->
+ Opts = expand_encoding(Opts0),
case check_valid_opts(Opts) of
- true ->
- do_setopts(Opts,Port);
- false ->
- {error,{error,enotsup}}
+ true ->
+ do_setopts(Opts);
+ false ->
+ {error,{error,enotsup}}
end.
+
check_valid_opts([]) ->
true;
-check_valid_opts([{unicode,Valid}|T]) when Valid =:= true; Valid =:= utf8; Valid =:= false ->
+check_valid_opts([{encoding,Valid}|T]) when Valid =:= unicode;
+ Valid =:= utf8; Valid =:= latin1 ->
check_valid_opts(T);
check_valid_opts(_) ->
false.
-do_setopts(Opts, _Port) ->
- case proplists:get_value(unicode,Opts) of
- Valid when Valid =:= true; Valid =:= utf8 ->
- put(unicode,true);
- false ->
- put(unicode,false);
- undefined ->
- ok
+expand_encoding([]) ->
+ [];
+expand_encoding([latin1 | T]) ->
+ [{encoding,latin1} | expand_encoding(T)];
+expand_encoding([unicode | T]) ->
+ [{encoding,unicode} | expand_encoding(T)];
+expand_encoding([H|T]) ->
+ [H|expand_encoding(T)].
+
+do_setopts(Opts) ->
+ case proplists:get_value(encoding, Opts) of
+ Valid when Valid =:= unicode; Valid =:= utf8 ->
+ put(encoding, unicode);
+ latin1 ->
+ put(encoding, latin1);
+ undefined ->
+ ok
end,
{ok,ok}.
-getopts(_Port) ->
- Uni = {unicode, get(unicode) =:= true},
+getopts() ->
+ Uni = {encoding,get(encoding)},
{ok,[Uni]}.
wrap_characters_to_binary(Chars,From,To) ->
@@ -227,17 +250,17 @@ wrap_characters_to_binary(Chars,From,To) ->
_Else ->
16#10ffff
end,
- unicode:characters_to_binary(
- [ case X of
- $\n ->
- if
- TrNl ->
- "\r\n";
- true ->
- $\n
- end;
- High when High > Limit ->
- ["\\x{",erlang:integer_to_list(X, 16),$}];
- Ordinary ->
- Ordinary
- end || X <- unicode:characters_to_list(Chars,From) ],unicode,To).
+ case catch unicode:characters_to_list(Chars, From) of
+ L when is_list(L) ->
+ unicode:characters_to_binary(
+ [ case X of
+ $\n when TrNl ->
+ "\r\n";
+ High when High > Limit ->
+ ["\\x{",erlang:integer_to_list(X, 16),$}];
+ Low ->
+ Low
+ end || X <- L ], unicode, To);
+ _ ->
+ error
+ end.
diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile
index f1b8a105ed..ef351a25fb 100644
--- a/lib/kernel/test/Makefile
+++ b/lib/kernel/test/Makefile
@@ -77,7 +77,8 @@ MODULES= \
ignore_cores \
zlib_SUITE \
loose_node \
- sendfile_SUITE
+ sendfile_SUITE \
+ standard_error_SUITE
APP_FILES = \
appinc.app \
diff --git a/lib/kernel/test/erl_distribution_SUITE.erl b/lib/kernel/test/erl_distribution_SUITE.erl
index 9cccdab76b..15c2adc957 100644
--- a/lib/kernel/test/erl_distribution_SUITE.erl
+++ b/lib/kernel/test/erl_distribution_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2011. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2015. 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,7 +26,8 @@
-export([tick/1, tick_change/1, illegal_nodenames/1, hidden_node/1,
table_waste/1, net_setuptime/1,
-
+ inet_dist_options_options/1,
+
monitor_nodes_nodedown_reason/1,
monitor_nodes_complex_nodedown_reason/1,
monitor_nodes_node_type/1,
@@ -38,7 +39,8 @@
monitor_nodes_many/1]).
%% Performs the test at another node.
--export([tick_cli_test/1, tick_cli_test1/1,
+-export([get_socket_priorities/0,
+ tick_cli_test/1, tick_cli_test1/1,
tick_serv_test/2, tick_serv_test1/1,
keep_conn/1, time_ping/1]).
@@ -62,7 +64,8 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[tick, tick_change, illegal_nodenames, hidden_node,
- table_waste, net_setuptime, {group, monitor_nodes}].
+ table_waste, net_setuptime, inet_dist_options_options,
+ {group, monitor_nodes}].
groups() ->
[{monitor_nodes, [],
@@ -554,6 +557,71 @@ check_monitor_nodes_res(Pid, Node) ->
end.
+
+inet_dist_options_options(suite) -> [];
+inet_dist_options_options(doc) ->
+ ["Check the kernel inet_dist_{listen,connect}_options options"];
+inet_dist_options_options(Config) when is_list(Config) ->
+ Prio = 1,
+ case gen_udp:open(0, [{priority,Prio}]) of
+ {ok,Socket} ->
+ case inet:getopts(Socket, [priority]) of
+ {ok,[{priority,Prio}]} ->
+ ok = gen_udp:close(Socket),
+ do_inet_dist_options_options(Prio);
+ _ ->
+ ok = gen_udp:close(Socket),
+ {skip,
+ "Can not set priority "++integer_to_list(Prio)++
+ " on socket"}
+ end;
+ {error,_} ->
+ {skip, "Can not set priority on socket"}
+ end.
+
+do_inet_dist_options_options(Prio) ->
+ PriorityString0 = "[{priority,"++integer_to_list(Prio)++"}]",
+ PriorityString =
+ case os:cmd("echo [{a,1}]") of
+ "[{a,1}]"++_ ->
+ PriorityString0;
+ _ ->
+ %% Some shells need quoting of [{}]
+ "'"++PriorityString0++"'"
+ end,
+ InetDistOptions =
+ "-hidden "
+ "-kernel inet_dist_connect_options "++PriorityString++" "
+ "-kernel inet_dist_listen_options "++PriorityString,
+ ?line {ok,Node1} =
+ start_node(inet_dist_options_1, InetDistOptions),
+ ?line {ok,Node2} =
+ start_node(inet_dist_options_2, InetDistOptions),
+ %%
+ ?line pong =
+ rpc:call(Node1, net_adm, ping, [Node2]),
+ ?line PrioritiesNode1 =
+ rpc:call(Node1, ?MODULE, get_socket_priorities, []),
+ ?line PrioritiesNode2 =
+ rpc:call(Node2, ?MODULE, get_socket_priorities, []),
+ ?line ?t:format("PrioritiesNode1 = ~p", [PrioritiesNode1]),
+ ?line ?t:format("PrioritiesNode2 = ~p", [PrioritiesNode2]),
+ ?line Elevated = [P || P <- PrioritiesNode1, P =:= Prio],
+ ?line Elevated = [P || P <- PrioritiesNode2, P =:= Prio],
+ ?line [_|_] = Elevated,
+ %%
+ ?line stop_node(Node2),
+ ?line stop_node(Node1),
+ ok.
+
+get_socket_priorities() ->
+ [Priority ||
+ {ok,[{priority,Priority}]} <-
+ [inet:getopts(Port, [priority]) ||
+ Port <- erlang:ports(),
+ element(2, erlang:port_info(Port, name)) =:= "tcp_inet"]].
+
+
%%
%% Testcase:
diff --git a/lib/kernel/test/file_SUITE.erl b/lib/kernel/test/file_SUITE.erl
index 56c35678b6..2ce2303ba3 100644
--- a/lib/kernel/test/file_SUITE.erl
+++ b/lib/kernel/test/file_SUITE.erl
@@ -424,7 +424,7 @@ make_del_dir(Config) when is_list(Config) ->
?line ok = ?FILE_MODULE:del_dir(NewDir),
?line {error, enoent} = ?FILE_MODULE:del_dir(NewDir),
% Make sure we are not in a directory directly under test_server
- % as that would result in eacess errors when trying to delere '..',
+ % as that would result in eacces errors when trying to delete '..',
% because there are processes having that directory as current.
?line ok = ?FILE_MODULE:make_dir(NewDir),
?line {ok,CurrentDir} = file:get_cwd(),
diff --git a/lib/kernel/test/inet_SUITE.erl b/lib/kernel/test/inet_SUITE.erl
index d45dfc2173..44a32fc1ec 100644
--- a/lib/kernel/test/inet_SUITE.erl
+++ b/lib/kernel/test/inet_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2014. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2015. 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
@@ -36,6 +36,7 @@
gethostnative_parallell/1, cname_loop/1,
gethostnative_soft_restart/0, gethostnative_soft_restart/1,
gethostnative_debug_level/0, gethostnative_debug_level/1,
+ lookup_bad_search_option/1,
getif/1,
getif_ifr_name_overflow/1,getservbyname_overflow/1, getifaddrs/1,
parse_strict_address/1, simple_netns/1, simple_netns_open/1]).
@@ -52,6 +53,7 @@ all() ->
ipv4_to_ipv6, host_and_addr, {group, parse},
t_gethostnative, gethostnative_parallell, cname_loop,
gethostnative_debug_level, gethostnative_soft_restart,
+ lookup_bad_search_option,
getif, getif_ifr_name_overflow, getservbyname_overflow,
getifaddrs, parse_strict_address, simple_netns, simple_netns_open].
@@ -86,10 +88,30 @@ init_per_group(_GroupName, Config) ->
end_per_group(_GroupName, Config) ->
Config.
+init_per_testcase(lookup_bad_search_option, Config) ->
+ Db = inet_db,
+ Key = res_lookup,
+ %% The bad option can not enter through inet_db:set_lookup/1,
+ %% but through e.g .inetrc.
+ Prev = ets:lookup(Db, Key),
+ ets:delete(Db, Key),
+ ets:insert(Db, {Key,[lookup_bad_search_option]}),
+ ?t:format("Misconfigured resolver lookup order", []),
+ Dog = test_server:timetrap(test_server:seconds(60)),
+ [{Key,Prev},{watchdog,Dog}|Config];
init_per_testcase(_Func, Config) ->
Dog = test_server:timetrap(test_server:seconds(60)),
[{watchdog,Dog}|Config].
+end_per_testcase(lookup_bad_search_option, Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ Db = inet_db,
+ Key = res_lookup,
+ Prev = ?config(Key, Config),
+ ets:delete(Db, Key),
+ ets:insert(Db, Prev),
+ ?t:format("Restored resolver lookup order", []);
end_per_testcase(_Func, Config) ->
Dog = ?config(watchdog, Config),
test_server:timetrap_cancel(Dog).
@@ -908,6 +930,19 @@ lookup_loop([H|Hs], Delay, Tag, Parent, Cnt, Hosts) ->
+lookup_bad_search_option(suite) ->
+ [];
+lookup_bad_search_option(doc) ->
+ ["Test lookup with erroneously configured lookup option (OTP-12133)"];
+lookup_bad_search_option(Config) when is_list(Config) ->
+ %% Manipulation of resolver config is done in init_per_testcase
+ %% and end_per_testcase to ensure cleanup.
+ {ok,Hostname} = inet:gethostname(),
+ {ok,_Hent} = inet:gethostbyname(Hostname), % Will hang loop for this bug
+ ok.
+
+
+
getif(suite) ->
[];
getif(doc) ->
diff --git a/lib/kernel/test/prim_file_SUITE.erl b/lib/kernel/test/prim_file_SUITE.erl
index 05bd5b3a3d..f55716cbec 100644
--- a/lib/kernel/test/prim_file_SUITE.erl
+++ b/lib/kernel/test/prim_file_SUITE.erl
@@ -262,7 +262,7 @@ make_del_dir(Config, Handle, Suffix) ->
?line {error, enoent} = ?PRIM_FILE_call(del_dir, Handle, [NewDir]),
% Make sure we are not in a directory directly under test_server
- % as that would result in eacess errors when trying to delere '..',
+ % as that would result in eacces errors when trying to delete '..',
% because there are processes having that directory as current.
?line ok = ?PRIM_FILE_call(make_dir, Handle, [NewDir]),
?line {ok, CurrentDir} = ?PRIM_FILE_call(get_cwd, Handle, []),
diff --git a/lib/kernel/test/standard_error_SUITE.erl b/lib/kernel/test/standard_error_SUITE.erl
new file mode 100644
index 0000000000..b290454b40
--- /dev/null
+++ b/lib/kernel/test/standard_error_SUITE.erl
@@ -0,0 +1,38 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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%
+%%
+
+-module(standard_error_SUITE).
+
+-export([all/0,suite/0]).
+-export([badarg/1,getopts/1]).
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [badarg,getopts].
+
+badarg(Config) when is_list(Config) ->
+ {'EXIT',{badarg,_}} = (catch io:put_chars(standard_error, [oops])),
+ true = erlang:is_process_alive(whereis(standard_error)),
+ ok.
+
+getopts(Config) when is_list(Config) ->
+ [{encoding,latin1}] = io:getopts(standard_error),
+ ok.
diff --git a/lib/mnesia/doc/src/Mnesia_chap5.xmlsrc b/lib/mnesia/doc/src/Mnesia_chap5.xmlsrc
index 65b950bd46..127c23e0f7 100644
--- a/lib/mnesia/doc/src/Mnesia_chap5.xmlsrc
+++ b/lib/mnesia/doc/src/Mnesia_chap5.xmlsrc
@@ -867,6 +867,7 @@ ok
</section>
<section>
+ <marker id="event_handling"></marker>
<title>Mnesia Event Handling</title>
<p>System events and table events are the two categories of events
that Mnesia will generate in various situations.
diff --git a/lib/mnesia/doc/src/mnesia.xml b/lib/mnesia/doc/src/mnesia.xml
index 268dc18e65..ed5b879f7f 100644
--- a/lib/mnesia/doc/src/mnesia.xml
+++ b/lib/mnesia/doc/src/mnesia.xml
@@ -151,9 +151,9 @@ If a new item is inserted with the same key as
</item>
<item>
<p><c>local_content</c> When an application requires
- tables whose contents is local to each node,
+ tables whose contents are local to each node,
<c>local_content</c> tables may be used. The name of the
- table is known to all Mnesia nodes, but its contents is
+ table is known to all Mnesia nodes, but its contents are
unique on each node. This means that access to such a table
must be done locally. Set the <c>local_content</c> field to
<c>true</c> if you want to enable the <c>local_content</c>
@@ -579,7 +579,7 @@ mnesia:add_table_index(person, age)
<desc>
<p>The tables are backed up to external media using the backup
module <c>BackupMod</c>. Tables with the local contents
- property is being backed up as they exist on the current
+ property are backed up as they exist on the current
node. <c>BackupMod</c> is the default backup callback
module obtained by
<c>mnesia:system_info(backup_module)</c>. See the User's
@@ -863,7 +863,7 @@ mnesia:create_table(person,
{attributes, record_info(fields,person)}]).
</code>
<p>The specification of <c>index</c> and <c>attributes</c> may be
- hard coded as <c>{index, [2]}</c> and
+ hard coded as <c>{index, [4]}</c> and
<c>{attributes, [name, age, address, salary, children]}</c>
respectively.
</p>
@@ -2188,12 +2188,13 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>subscribe(EventCategory)</name>
+ <name>subscribe(EventCategory) -> {ok, Node} | {error, Reason} </name>
<fsummary>Subscribe to events of type <c>EventCategory</c>.</fsummary>
<desc>
<p>Ensures that a copy of all events of type
<c>EventCategory</c> are sent to the caller. The event
- types available are described in the Mnesia User's Guide.</p>
+ types available are described in the Mnesia User's Guide at <seealso marker="Mnesia_chap5#event_handling">Mnesia Event Handling</seealso>.</p>
+ <p><c>Node</c> is the local node. For table events to be subscribed, mnesia must have a readable local copy of the table on the node.</p>
</desc>
</func>
<func>
@@ -2861,11 +2862,12 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>unsubscribe(EventCategory)</name>
+ <name>unsubscribe(EventCategory) -> {ok, Node} | {error, Reason} </name>
<fsummary>Subscribe to events of type <c>EventCategory</c>.</fsummary>
<desc>
<p>Stops sending events of type
<c>EventCategory</c> to the caller.</p>
+ <p><c>Node</c> is the local node.</p>
</desc>
</func>
<func>
diff --git a/lib/mnesia/src/mnesia_locker.erl b/lib/mnesia/src/mnesia_locker.erl
index e27396731f..1efb939e00 100644
--- a/lib/mnesia/src/mnesia_locker.erl
+++ b/lib/mnesia/src/mnesia_locker.erl
@@ -982,8 +982,14 @@ sticky_flush(Ns=[Node | Tail], Store) ->
flush_remaining([], _SkipNode, Res) ->
del_debug(),
exit(Res);
-flush_remaining([SkipNode | Tail ], SkipNode, Res) ->
- flush_remaining(Tail, SkipNode, Res);
+flush_remaining(Ns=[SkipNode | Tail ], SkipNode, Res) ->
+ add_debug(Ns),
+ receive
+ {?MODULE, SkipNode, _} ->
+ flush_remaining(Tail, SkipNode, Res)
+ after 0 ->
+ flush_remaining(Tail, SkipNode, Res)
+ end;
flush_remaining(Ns=[Node | Tail], SkipNode, Res) ->
add_debug(Ns),
receive
diff --git a/lib/mnesia/src/mnesia_recover.erl b/lib/mnesia/src/mnesia_recover.erl
index b6492707e2..eeb4fa0ced 100644
--- a/lib/mnesia/src/mnesia_recover.erl
+++ b/lib/mnesia/src/mnesia_recover.erl
@@ -689,12 +689,29 @@ handle_call({connect_nodes, Ns}, From, State) ->
%% called from handle_info
gen_server:reply(From, {[], AlreadyConnected}),
{noreply, State};
- GoodNodes ->
+ ProbablyGoodNodes ->
%% Now we have agreed upon a protocol with some new nodes
- %% and we may use them when we recover transactions
+ %% and we may use them when we recover transactions.
+ %%
+ %% Just in case Mnesia was stopped on some of those nodes
+ %% between the protocol negotiation and now, we check one
+ %% more time the state of Mnesia.
+ %%
+ %% Of course, there is still a chance that mnesia_down
+ %% events occur during this check and we miss them. To
+ %% prevent it, handle_cast({mnesia_down, ...}, ...) removes
+ %% the down node again, in addition to mnesia_down/1.
+ %%
+ %% See a comment in handle_cast({mnesia_down, ...}, ...).
+ Verify = fun(N) ->
+ Run = mnesia_lib:is_running(N),
+ Run =:= yes orelse Run =:= starting
+ end,
+ GoodNodes = [N || N <- ProbablyGoodNodes, Verify(N)],
+
mnesia_lib:add_list(recover_nodes, GoodNodes),
cast({announce_all, GoodNodes}),
- case get_master_nodes(schema) of
+ case get_master_nodes(schema) of
[] ->
Context = starting_partitioned_network,
mnesia_monitor:detect_inconcistency(GoodNodes, Context);
@@ -842,6 +859,14 @@ handle_cast({what_decision, Node, OtherD}, State) ->
{noreply, State};
handle_cast({mnesia_down, Node}, State) ->
+ %% The node was already removed from recover_nodes in mnesia_down/1,
+ %% but we do it again here in the mnesia_recover process, in case
+ %% another event incorrectly added it back. This can happen during
+ %% Mnesia startup which takes time betweenthe connection, the
+ %% protocol negotiation and the merge of the schema.
+ %%
+ %% See a comment in handle_call({connect_nodes, ...), ...).
+ mnesia_lib:del(recover_nodes, Node),
case State#state.unclear_decision of
undefined ->
{noreply, State};
diff --git a/lib/mnesia/test/mnesia_recovery_test.erl b/lib/mnesia/test/mnesia_recovery_test.erl
index 0d0ad32fb0..946a9f97ba 100644
--- a/lib/mnesia/test/mnesia_recovery_test.erl
+++ b/lib/mnesia/test/mnesia_recovery_test.erl
@@ -320,7 +320,9 @@ read_during_down(Op, Config) when is_list(Config) ->
?log("W2R ~p~n", [W2R]),
loop_and_kill_mnesia(10, hd(W2R), Tabs),
[Pid ! self() || Pid <- Readers],
- ?match([ok, ok, ok], [receive ok -> ok after 1000 -> {Pid, mnesia_lib:dist_coredump()} end || Pid <- Readers]),
+ ?match([ok, ok, ok],
+ [receive ok -> ok after 5000 -> {Pid, mnesia_lib:dist_coredump()} end
+ || Pid <- Readers]),
?verify_mnesia(Ns, []).
reader(Tab, OP) ->
@@ -338,8 +340,12 @@ reader(Tab, OP) ->
?error("Expected ~p Got ~p ~n", [[{Tab, key, val}], Else]),
erlang:error(test_failed)
end,
- receive Pid ->
- Pid ! ok
+ receive
+ Pid when is_pid(Pid) ->
+ Pid ! ok;
+ Other ->
+ io:format("Msg: ~p~n", [Other]),
+ error(Other)
after 50 ->
reader(Tab, OP)
end.
@@ -1537,6 +1543,7 @@ disc_less(Config) when is_list(Config) ->
timer:sleep(500),
?match(ok, rpc:call(Node3, mnesia, start, [[{extra_db_nodes, [Node1, Node2]}]])),
?match(ok, rpc:call(Node3, mnesia, wait_for_tables, [[Tab1, Tab2, Tab3], 20000])),
+ ?match(ok, rpc:call(Node1, mnesia, wait_for_tables, [[Tab1, Tab2, Tab3], 20000])),
?match(ok, rpc:call(Node3, ?MODULE, verify_data, [Tab1, 100])),
?match(ok, rpc:call(Node3, ?MODULE, verify_data, [Tab2, 100])),
diff --git a/lib/mnesia/test/mnesia_test_lib.hrl b/lib/mnesia/test/mnesia_test_lib.hrl
index 94a195f01f..cd76377df6 100644
--- a/lib/mnesia/test/mnesia_test_lib.hrl
+++ b/lib/mnesia/test/mnesia_test_lib.hrl
@@ -66,12 +66,14 @@
?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]),
+ ?error("Not Matching Actual result was:~n ~p~n ~p~n",
+ [_AR_2, erlang:get_stacktrace()]),
{fail,_AR_2}
end;
- _:_AR_1 ->
- ?error("Not Matching Actual result was:~n ~p~n", [_AR_1]),
- {fail,_AR_1}
+ _T1_:_AR_1 ->
+ ?error("Not Matching Actual result was:~n ~p~n ~p~n",
+ [{_T1_,_AR_1}, erlang:get_stacktrace()]),
+ {fail,{_T1_,_AR_1}}
end
end()).
diff --git a/lib/orber/src/cdr_decode.erl b/lib/orber/src/cdr_decode.erl
index 36ef6ce02f..9aec64892e 100644
--- a/lib/orber/src/cdr_decode.erl
+++ b/lib/orber/src/cdr_decode.erl
@@ -193,7 +193,7 @@ dec_message_header(TypeCodes, Message, Bytes) ->
%% Args:
%% The message as a byte sequence.
%% Returns:
-%% A tuple {Endianess, Rest} where Endianess is big or little.
+%% A tuple {Endianness, Rest} where Endianness is big or little.
%% Rest is the remaining message byte sequence.
%%-----------------------------------------------------------------
dec_byte_order(<<0:8,T/binary>>) ->
@@ -206,7 +206,7 @@ dec_byte_order(<<1:8,T/binary>>) ->
%% Args:
%% The message as a byte sequence.
%% Returns:
-%% A tuple {Endianess, Rest} where Endianess is big or little.
+%% A tuple {Endianness, Rest} where Endianness is big or little.
%% Rest is the remaining message byte sequence.
%%-----------------------------------------------------------------
dec_byte_order_list([0|T]) ->
diff --git a/lib/os_mon/c_src/memsup.c b/lib/os_mon/c_src/memsup.c
index 409db84aa7..5dcab07dd8 100644
--- a/lib/os_mon/c_src/memsup.c
+++ b/lib/os_mon/c_src/memsup.c
@@ -104,7 +104,7 @@
#if !defined (__OpenBSD__) && !defined (__NetBSD__)
#include <vm/vm_param.h>
#endif
-#if defined (__FreeBSD__) || defined(__DragonFly__) || defined (__NetBSD__)
+#if defined (__FreeBSD__) || defined(__DragonFly__) || defined (__NetBSD__) || defined(__OpenBSD__)
#include <sys/vmmeter.h>
#endif
#endif
diff --git a/lib/os_mon/src/cpu_sup.erl b/lib/os_mon/src/cpu_sup.erl
index 1f088ecbde..66e7973e7e 100644
--- a/lib/os_mon/src/cpu_sup.erl
+++ b/lib/os_mon/src/cpu_sup.erl
@@ -221,7 +221,7 @@ get_uint32_measurement(Request, #internal{port = P, os_type = {unix, sunos}}) ->
port_server_call(P, Request);
get_uint32_measurement(Request, #internal{os_type = {unix, linux}}) ->
{ok,F} = file:open("/proc/loadavg",[read,raw]),
- {ok,D} = file:read(F,24),
+ {ok,D} = file:read_line(F),
ok = file:close(F),
{ok,[Load1,Load5,Load15,_PRun,PTotal],_} = io_lib:fread("~f ~f ~f ~d/~d", D),
case Request of
diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml
index e3473f80d7..b86d0fe0ab 100644
--- a/lib/public_key/doc/src/public_key.xml
+++ b/lib/public_key/doc/src/public_key.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>2008</year>
- <year>2014</year>
+ <year>2015</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
@@ -127,6 +127,8 @@
affiliationChanged | superseded | cessationOfOperation |
certificateHold | privilegeWithdrawn | aACompromise</code></p>
+ <p><code>issuer_name() = {rdnSequence,[#'AttributeTypeAndValue'{}]} </code> </p>
+
<p><code>ssh_file() = openssh_public_key | rfc4716_public_key | known_hosts |
auth_keys</code></p>
@@ -368,8 +370,8 @@
<name>pkix_is_issuer(Cert, IssuerCert) -> boolean()</name>
<fsummary> Checks if <c>IssuerCert</c> issued <c>Cert</c> </fsummary>
<type>
- <v>Cert = der_encode() | #'OTPCertificate'{}</v>
- <v>IssuerCert = der_encode() | #'OTPCertificate'{}</v>
+ <v>Cert = der_encoded() | #'OTPCertificate'{}</v>
+ <v>IssuerCert = der_encoded() | #'OTPCertificate'{}</v>
</type>
<desc>
<p> Checks if <c>IssuerCert</c> issued <c>Cert</c> </p>
@@ -380,7 +382,7 @@
<name>pkix_is_fixed_dh_cert(Cert) -> boolean()</name>
<fsummary> Checks if a Certificate is a fixed Diffie-Hellman Cert.</fsummary>
<type>
- <v>Cert = der_encode() | #'OTPCertificate'{}</v>
+ <v>Cert = der_encoded() | #'OTPCertificate'{}</v>
</type>
<desc>
<p> Checks if a Certificate is a fixed Diffie-Hellman Cert.</p>
@@ -391,7 +393,7 @@
<name>pkix_is_self_signed(Cert) -> boolean()</name>
<fsummary> Checks if a Certificate is self signed.</fsummary>
<type>
- <v>Cert = der_encode() | #'OTPCertificate'{}</v>
+ <v>Cert = der_encoded() | #'OTPCertificate'{}</v>
</type>
<desc>
<p> Checks if a Certificate is self signed.</p>
@@ -402,24 +404,25 @@
<name>pkix_issuer_id(Cert, IssuedBy) -> {ok, IssuerID} | {error, Reason}</name>
<fsummary> Returns the issuer id.</fsummary>
<type>
- <v>Cert = der_encode() | #'OTPCertificate'{}</v>
+ <v>Cert = der_encoded() | #'OTPCertificate'{}</v>
<v>IssuedBy = self | other</v>
- <v>IssuerID = {integer(), {rdnSequence, [#'AttributeTypeAndValue'{}]}}</v>
+ <v>IssuerID = {integer(), issuer_name()}</v>
<d>The issuer id consists of the serial number and the issuers name.</d>
<v>Reason = term()</v>
- </type>
- <desc>
- <p> Returns the issuer id.</p>
- </desc>
+ </type>
+ <desc>
+ <p> Returns the issuer id.</p>
+ </desc>
</func>
-
+
+
<func>
<name>pkix_normalize_name(Issuer) -> Normalized</name>
<fsummary>Normalizes a issuer name so that it can be easily
compared to another issuer name. </fsummary>
<type>
- <v>Issuer = {rdnSequence,[#'AttributeTypeAndValue'{}]}</v>
- <v>Normalized = {rdnSequence, [#'AttributeTypeAndValue'{}]}</v>
+ <v>Issuer = issuer_name()</v>
+ <v>Normalized = issuer_name()</v>
</type>
<desc>
<p>Normalizes a issuer name so that it can be easily
@@ -431,13 +434,13 @@
<name>pkix_path_validation(TrustedCert, CertChain, Options) -> {ok, {PublicKeyInfo, PolicyTree}} | {error, {bad_cert, Reason}} </name>
<fsummary> Performs a basic path validation according to RFC 5280.</fsummary>
<type>
- <v> TrustedCert = #'OTPCertificate'{} | der_encode() | atom() </v>
+ <v> TrustedCert = #'OTPCertificate'{} | der_encoded() | atom() </v>
<d>Normally a trusted certificate but it can also be a path validation
error that can be discovered while
constructing the input to this function and that should be run through the <c>verify_fun</c>.
For example <c>unknown_ca </c> or <c>selfsigned_peer </c>
</d>
- <v> CertChain = [der_encode()]</v>
+ <v> CertChain = [der_encoded()]</v>
<d>A list of DER encoded certificates in trust order ending with the peer certificate.</d>
<v> Options = proplists:proplist()</v>
<v>PublicKeyInfo = {?'rsaEncryption' | ?'id-dsa',
@@ -527,6 +530,17 @@ fun(OtpCert :: #'OTPCertificate'{},
</desc>
</func>
+ <func>
+ <name>pkix_crl_issuer(CRL) -> issuer_name()</name>
+ <fsummary>Returns the issuer of the <c>CRL</c>.</fsummary>
+ <type>
+ <v>CRL = der_encoded() | #'CertificateList'{} </v>
+ </type>
+ <desc>
+ <p>Returns the issuer of the <c>CRL</c>.</p>
+ </desc>
+ </func>
+
<func>
<name>pkix_crls_validate(OTPCertificate, DPAndCRLs, Options) -> CRLStatus()</name>
<fsummary> Performs CRL validation.</fsummary>
@@ -574,9 +588,48 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</taglist>
</desc>
</func>
+
+ <func>
+ <name>pkix_crl_verify(CRL, Cert) -> boolean()</name>
+ <fsummary> Verify that <c>Cert</c> is the <c> CRL</c> signer. </fsummary>
+ <type>
+ <v>CRL = der_encoded() | #'CertificateList'{} </v>
+ <v>Cert = der_encoded() | #'OTPCertificate'{} </v>
+ </type>
+ <desc>
+ <p>Verify that <c>Cert</c> is the <c>CRL</c> signer.</p>
+ </desc>
+ </func>
+ <func>
+ <name>pkix_dist_point(Cert) -> DistPoint</name>
+ <fsummary>Creates a distribution point for CRLs issued by the same issuer as <c>Cert</c>.</fsummary>
+ <type>
+ <v> Cert = der_encoded() | #'OTPCertificate'{} </v>
+ <v> DistPoint = #'DistributionPoint'{}</v>
+ </type>
+ <desc>
+ <p>Creates a distribution point for CRLs issued by the same issuer as <c>Cert</c>.
+ Can be used as input to <seealso
+ marker="#pkix_crls_validate-3">pkix_crls_validate/3 </seealso>
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name>pkix_dist_points(Cert) -> DistPoints</name>
+ <fsummary> Extracts distribution points from the certificates extensions.</fsummary>
+ <type>
+ <v> Cert = der_encoded() | #'OTPCertificate'{} </v>
+ <v> DistPoints = [#'DistributionPoint'{}]</v>
+ </type>
+ <desc>
+ <p> Extracts distribution points from the certificates extensions.</p>
+ </desc>
+ </func>
+
<func>
- <name>pkix_sign(#'OTPTBSCertificate'{}, Key) -> der_encode()</name>
+ <name>pkix_sign(#'OTPTBSCertificate'{}, Key) -> der_encoded()</name>
<fsummary>Signs certificate.</fsummary>
<type>
<v>Key = rsa_public_key() | dsa_public_key()</v>
@@ -606,7 +659,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
<name>pkix_verify(Cert, Key) -> boolean()</name>
<fsummary> Verify pkix x.509 certificate signature.</fsummary>
<type>
- <v>Cert = der_encode()</v>
+ <v>Cert = der_encoded()</v>
<v>Key = rsa_public_key() | dsa_public_key()</v>
</type>
<desc>
diff --git a/lib/public_key/src/pubkey_cert.erl b/lib/public_key/src/pubkey_cert.erl
index ae517ca642..8b11538499 100644
--- a/lib/public_key/src/pubkey_cert.erl
+++ b/lib/public_key/src/pubkey_cert.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-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
@@ -28,8 +28,9 @@
validate_issuer/4, validate_names/6,
validate_extensions/4,
normalize_general_name/1, is_self_signed/1,
- is_issuer/2, issuer_id/2, is_fixed_dh_cert/1,
- verify_data/1, verify_fun/4, select_extension/2, match_name/3,
+ is_issuer/2, issuer_id/2, distribution_points/1,
+ is_fixed_dh_cert/1, verify_data/1, verify_fun/4,
+ select_extension/2, match_name/3,
extensions_list/1, cert_auth_key_id/1, time_str_2_gregorian_sec/1]).
-define(NULL, 0).
@@ -272,6 +273,16 @@ issuer_id(Otpcert, self) ->
SerialNr = TBSCert#'OTPTBSCertificate'.serialNumber,
{ok, {SerialNr, normalize_general_name(Issuer)}}.
+distribution_points(Otpcert) ->
+ TBSCert = Otpcert#'OTPCertificate'.tbsCertificate,
+ Extensions = extensions_list(TBSCert#'OTPTBSCertificate'.extensions),
+ case select_extension(?'id-ce-cRLDistributionPoints', Extensions) of
+ undefined ->
+ [];
+ #'Extension'{extnValue = Value} ->
+ Value
+ end.
+
%%--------------------------------------------------------------------
-spec is_fixed_dh_cert(#'OTPCertificate'{}) -> boolean().
%%
@@ -296,7 +307,9 @@ is_fixed_dh_cert(#'OTPCertificate'{tbsCertificate =
%% --------------------------------------------------------------------
verify_fun(Otpcert, Result, UserState0, VerifyFun) ->
case VerifyFun(Otpcert, Result, UserState0) of
- {valid,UserState} ->
+ {valid, UserState} ->
+ UserState;
+ {valid_peer, UserState} ->
UserState;
{fail, Reason} ->
case Reason of
diff --git a/lib/public_key/src/pubkey_crl.erl b/lib/public_key/src/pubkey_crl.erl
index f0df4bc3f2..488cc97c70 100644
--- a/lib/public_key/src/pubkey_crl.erl
+++ b/lib/public_key/src/pubkey_crl.erl
@@ -41,10 +41,10 @@ validate(OtpCert, OtherDPCRLs, DP, {DerCRL, CRL}, {DerDeltaCRL, DeltaCRL},
CRLIssuer = TBSCRL#'TBSCertList'.issuer,
AltNames = case pubkey_cert:select_extension(?'id-ce-subjectAltName',
TBSCert#'OTPTBSCertificate'.extensions) of
- undefined ->
- [];
- Ext ->
- Ext#'Extension'.extnValue
+ #'Extension'{extnValue = Value} ->
+ Value;
+ _ ->
+ []
end,
revoked_status(DP, IDP, {directoryName, CRLIssuer},
[ {directoryName, CertIssuer} | AltNames], SerialNumber, Revoked,
diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl
index 1bbf4ef416..e8ff965982 100644
--- a/lib/public_key/src/public_key.erl
+++ b/lib/public_key/src/public_key.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2015. 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
@@ -46,7 +46,11 @@
pkix_normalize_name/1,
pkix_path_validation/3,
ssh_decode/2, ssh_encode/2,
- pkix_crls_validate/3
+ pkix_crls_validate/3,
+ pkix_dist_point/1,
+ pkix_dist_points/1,
+ pkix_crl_verify/2,
+ pkix_crl_issuer/1
]).
-export_type([public_key/0, private_key/0, pem_entry/0,
@@ -110,7 +114,7 @@ pem_encode(PemEntries) when is_list(PemEntries) ->
iolist_to_binary(pubkey_pem:encode(PemEntries)).
%%--------------------------------------------------------------------
--spec pem_entry_decode(pem_entry(), [string()]) -> term().
+-spec pem_entry_decode(pem_entry(), string()) -> term().
%
%% Description: Decodes a pem entry. pem_decode/1 returns a list of
%% pem entries.
@@ -142,14 +146,16 @@ pem_entry_decode({Asn1Type, CryptDer, {Cipher, #'PBES2-params'{}}} = PemEntry,
pem_entry_decode({Asn1Type, CryptDer, {Cipher, {#'PBEParameter'{},_}}} = PemEntry,
Password) when is_atom(Asn1Type) andalso
is_binary(CryptDer) andalso
- is_list(Cipher) ->
+ is_list(Cipher) andalso
+ is_list(Password) ->
do_pem_entry_decode(PemEntry, Password);
pem_entry_decode({Asn1Type, CryptDer, {Cipher, Salt}} = PemEntry,
Password) when is_atom(Asn1Type) andalso
is_binary(CryptDer) andalso
is_list(Cipher) andalso
is_binary(Salt) andalso
- ((erlang:byte_size(Salt) == 8) or (erlang:byte_size(Salt) == 16)) ->
+ ((erlang:byte_size(Salt) == 8) or (erlang:byte_size(Salt) == 16)) andalso
+ is_list(Password) ->
do_pem_entry_decode(PemEntry, Password).
@@ -470,6 +476,45 @@ verify(DigestOrPlainText, sha = DigestType, Signature, {Key, #'Dss-Parms'{p = P
crypto:verify(dss, DigestType, DigestOrPlainText, Signature, [P, Q, G, Key]).
%%--------------------------------------------------------------------
+-spec pkix_dist_point(der_encoded() | #'OTPCertificate'{}) ->
+ #'DistributionPoint'{}.
+%% Description: Creates a distribution point for CRLs issued by the same issuer as <c>Cert</c>.
+%%--------------------------------------------------------------------
+pkix_dist_point(OtpCert) when is_binary(OtpCert) ->
+ pkix_dist_point(pkix_decode_cert(OtpCert, otp));
+pkix_dist_point(OtpCert) ->
+ Issuer = public_key:pkix_normalize_name(
+ pubkey_cert_records:transform(
+ OtpCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.issuer, encode)),
+
+ TBSCert = OtpCert#'OTPCertificate'.tbsCertificate,
+ Extensions = pubkey_cert:extensions_list(TBSCert#'OTPTBSCertificate'.extensions),
+ AltNames = case pubkey_cert:select_extension(?'id-ce-issuerAltName', Extensions) of
+ undefined ->
+ [];
+ #'Extension'{extnValue = Value} ->
+ Value
+ end,
+ Point = {fullName, [{directoryName, Issuer} | AltNames]},
+ #'DistributionPoint'{cRLIssuer = asn1_NOVALUE,
+ reasons = asn1_NOVALUE,
+ distributionPoint = Point}.
+%%--------------------------------------------------------------------
+-spec pkix_dist_points(der_encoded() | #'OTPCertificate'{}) ->
+ [#'DistributionPoint'{}].
+%% Description: Extracts distributionpoints specified in the certificates extensions.
+%%--------------------------------------------------------------------
+pkix_dist_points(OtpCert) when is_binary(OtpCert) ->
+ pkix_dist_points(pkix_decode_cert(OtpCert, otp));
+pkix_dist_points(OtpCert) ->
+ Value = pubkey_cert:distribution_points(OtpCert),
+ lists:foldl(fun(Point, Acc0) ->
+ DistPoint = pubkey_cert_records:transform(Point, decode),
+ [DistPoint | Acc0]
+ end,
+ [], Value).
+
+%%--------------------------------------------------------------------
-spec pkix_sign(#'OTPTBSCertificate'{},
rsa_private_key() | dsa_private_key()) -> Der::binary().
%%
@@ -511,6 +556,25 @@ pkix_verify(DerCert, Key = {#'ECPoint'{}, _})
verify(PlainText, DigestType, Signature, Key).
%%--------------------------------------------------------------------
+-spec pkix_crl_verify(CRL::binary() | #'CertificateList'{}, Cert::binary() | #'OTPCertificate'{}) -> boolean().
+%%
+%% Description: Verify that Cert is the CRL signer.
+%%--------------------------------------------------------------------
+pkix_crl_verify(CRL, Cert) when is_binary(CRL) ->
+ pkix_crl_verify(der_decode('CertificateList', CRL), Cert);
+pkix_crl_verify(CRL, Cert) when is_binary(Cert) ->
+ pkix_crl_verify(CRL, pkix_decode_cert(Cert, otp));
+pkix_crl_verify(#'CertificateList'{} = CRL, #'OTPCertificate'{} = Cert) ->
+ TBSCert = Cert#'OTPCertificate'.tbsCertificate,
+ PublicKeyInfo = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
+ PublicKey = PublicKeyInfo#'OTPSubjectPublicKeyInfo'.subjectPublicKey,
+ AlgInfo = PublicKeyInfo#'OTPSubjectPublicKeyInfo'.algorithm,
+ PublicKeyParams = AlgInfo#'PublicKeyAlgorithm'.parameters,
+ pubkey_crl:verify_crl_signature(CRL,
+ der_encode('CertificateList', CRL),
+ PublicKey, PublicKeyParams).
+
+%%--------------------------------------------------------------------
-spec pkix_is_issuer(Cert :: der_encoded()| #'OTPCertificate'{} | #'CertificateList'{},
IssuerCert :: der_encoded()|
#'OTPCertificate'{}) -> boolean().
@@ -564,17 +628,27 @@ pkix_is_fixed_dh_cert(Cert) when is_binary(Cert) ->
%
%% Description: Returns the issuer id.
%%--------------------------------------------------------------------
-pkix_issuer_id(#'OTPCertificate'{} = OtpCert, self) ->
- pubkey_cert:issuer_id(OtpCert, self);
-
-pkix_issuer_id(#'OTPCertificate'{} = OtpCert, other) ->
- pubkey_cert:issuer_id(OtpCert, other);
-
-pkix_issuer_id(Cert, Signed) when is_binary(Cert) ->
+pkix_issuer_id(#'OTPCertificate'{} = OtpCert, Signed) when (Signed == self) or
+ (Signed == other) ->
+ pubkey_cert:issuer_id(OtpCert, Signed);
+pkix_issuer_id(Cert, Signed) when is_binary(Cert) ->
OtpCert = pkix_decode_cert(Cert, otp),
pkix_issuer_id(OtpCert, Signed).
%%--------------------------------------------------------------------
+-spec pkix_crl_issuer(CRL::binary()| #'CertificateList'{}) ->
+ {rdnSequence,
+ [#'AttributeTypeAndValue'{}]}.
+%
+%% Description: Returns the issuer.
+%%--------------------------------------------------------------------
+pkix_crl_issuer(CRL) when is_binary(CRL) ->
+ pkix_crl_issuer(der_decode('CertificateList', CRL));
+pkix_crl_issuer(#'CertificateList'{} = CRL) ->
+ pubkey_cert_records:transform(
+ CRL#'CertificateList'.tbsCertList#'TBSCertList'.issuer, decode).
+
+%%--------------------------------------------------------------------
-spec pkix_normalize_name({rdnSequence,
[#'AttributeTypeAndValue'{}]}) ->
{rdnSequence,
@@ -921,3 +995,4 @@ ec_key({PubKey, PrivateKey}, Params) ->
privateKey = binary_to_list(PrivateKey),
parameters = Params,
publicKey = {0, PubKey}}.
+
diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl
index 163f5f4413..40c28e86b3 100644
--- a/lib/public_key/test/public_key_SUITE.erl
+++ b/lib/public_key/test/public_key_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2015. 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
@@ -42,7 +42,7 @@ all() ->
encrypt_decrypt,
{group, sign_verify},
pkix, pkix_countryname, pkix_emailaddress, pkix_path_validation,
- pkix_iso_rsa_oid, pkix_iso_dsa_oid].
+ pkix_iso_rsa_oid, pkix_iso_dsa_oid, pkix_crl].
groups() ->
[{pem_decode_encode, [], [dsa_pem, rsa_pem, encrypted_pem,
@@ -712,6 +712,42 @@ pkix_iso_dsa_oid(Config) when is_list(Config) ->
{_, dsa} = public_key:pkix_sign_types(SigAlg#'SignatureAlgorithm'.algorithm).
%%--------------------------------------------------------------------
+
+pkix_crl() ->
+ [{doc, "test pkix_crl_* functions"}].
+
+pkix_crl(Config) when is_list(Config) ->
+ Datadir = ?config(data_dir, Config),
+ {ok, PemCRL} = file:read_file(filename:join(Datadir, "idp_crl.pem")),
+ [{_, CRL, _}] = public_key:pem_decode(PemCRL),
+
+ {ok, IDPPemCert} = file:read_file(filename:join(Datadir, "idp_cert.pem")),
+ [{_, IDPCert, _}] = public_key:pem_decode(IDPPemCert),
+
+ {ok, SignPemCert} = file:read_file(filename:join(Datadir, "crl_signer.pem")),
+ [{_, SignCert, _}] = public_key:pem_decode(SignPemCert),
+
+ OTPIDPCert = public_key:pkix_decode_cert(IDPCert, otp),
+ OTPSignCert = public_key:pkix_decode_cert(SignCert, otp),
+ ERLCRL = public_key:der_decode('CertificateList',CRL),
+
+ {rdnSequence,_} = public_key:pkix_crl_issuer(CRL),
+ {rdnSequence,_} = public_key:pkix_crl_issuer(ERLCRL),
+
+ true = public_key:pkix_crl_verify(CRL, SignCert),
+ true = public_key:pkix_crl_verify(ERLCRL, OTPSignCert),
+
+ [#'DistributionPoint'{}|_] = public_key:pkix_dist_points(IDPCert),
+ [#'DistributionPoint'{}|_] = public_key:pkix_dist_points(OTPIDPCert),
+
+ #'DistributionPoint'{cRLIssuer = asn1_NOVALUE,
+ reasons = asn1_NOVALUE,
+ distributionPoint = Point} = public_key:pkix_dist_point(IDPCert),
+ #'DistributionPoint'{cRLIssuer = asn1_NOVALUE,
+ reasons = asn1_NOVALUE,
+ distributionPoint = Point} = public_key:pkix_dist_point(OTPIDPCert).
+
+%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
asn1_encode_decode({Asn1Type, Der, not_encrypted} = Entry) ->
diff --git a/lib/public_key/test/public_key_SUITE_data/crl_signer.pem b/lib/public_key/test/public_key_SUITE_data/crl_signer.pem
new file mode 100644
index 0000000000..d77f86b45d
--- /dev/null
+++ b/lib/public_key/test/public_key_SUITE_data/crl_signer.pem
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIID8zCCAtugAwIBAgIJAKU8w89SmyPyMA0GCSqGSIb3DQEBBAUAMIGGMREwDwYD
+VQQDEwhlcmxhbmdDQTETMBEGA1UECxMKRXJsYW5nIE9UUDEUMBIGA1UEChMLRXJp
+Y3Nzb24gQUIxEjAQBgNVBAcTCVN0b2NraG9sbTELMAkGA1UEBhMCU0UxJTAjBgkq
+hkiG9w0BCQEWFnBldGVyQGVyaXguZXJpY3Nzb24uc2UwHhcNMTUwMjIzMTMyNTMx
+WhcNMTUwMzI1MTMyNTMxWjCBhjERMA8GA1UEAxMIZXJsYW5nQ0ExEzARBgNVBAsT
+CkVybGFuZyBPVFAxFDASBgNVBAoTC0VyaWNzc29uIEFCMRIwEAYDVQQHEwlTdG9j
+a2hvbG0xCzAJBgNVBAYTAlNFMSUwIwYJKoZIhvcNAQkBFhZwZXRlckBlcml4LmVy
+aWNzc29uLnNlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyzwkmKzy
+WTLOafHmgqZVENdt3OYECPA4BamVKyEdi8zgXI0S71wzPZ+XvuGbHDTBzsTHf71L
+xRQgoG30tv5jqWSlfh8iyS6fO+FHxBKd+xg6hLJXk5PCUa5X1D4BO8B4aapEzev+
+T8+pTaOLeVPdfGfKp0yWF50eCpdSF/kMCCIIA8QNSahfcwuLbEEzUNZof6YPZBNm
+e+XUMXCjpb/mU7krfu8nLaspG1HgxQqErEEBzGJE7mguqSVETK/xpGXEMTNIuj8N
+ziFrfqAezDob3z48xHUaHKZRBb9NIxWIjVxkTYaqOtf9UNCT96CHeZ7rk9iNscQu
+USabMIamFY8cNQIDAQABo2IwYDAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIB
+BjAdBgNVHQ4EFgQUm2M3f6UBEIsHI1HIvphbBz60RsAwIQYDVR0RBBowGIEWcGV0
+ZXJAZXJpeC5lcmljc3Nvbi5zZTANBgkqhkiG9w0BAQQFAAOCAQEAPmm0V36HZySF
+BoV03DGyeFUSeMtO0DO058NaXXv2VNPpUXT72Mt1ovXNvVFcReggb01polF7TFFI
+4NRb6qbsLPxny29Clf/9WKY4zDhbb2MIy8yueoOyyeNQtrzY+iQjo4q9U+Aa6xj1
+pxmG1URDfOmCgX33ItCrZXFGa4ic0HrbWgJMDNo4lSOiio8bl3IYN4vBcobRfhDs
+pw5jochE5ZpPh4i76Pg6D99EFkNaLyQioWEu4n2OxR0EBSFLJkVJQ0alUx18AKio
+bje+h5nzRgTm5HApYzcorF57KfUKPDaW1Q6tRckRyHApueDuK8p49ITQE71lmkLc
+ywxoJMrNnA==
+-----END CERTIFICATE-----
+
diff --git a/lib/public_key/test/public_key_SUITE_data/idp_cert.pem b/lib/public_key/test/public_key_SUITE_data/idp_cert.pem
new file mode 100644
index 0000000000..c2afc56a3a
--- /dev/null
+++ b/lib/public_key/test/public_key_SUITE_data/idp_cert.pem
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFGjCCBAKgAwIBAgIBAzANBgkqhkiG9w0BAQQFADCBgzEOMAwGA1UEAxMFb3Rw
+Q0ExEzARBgNVBAsTCkVybGFuZyBPVFAxFDASBgNVBAoTC0VyaWNzc29uIEFCMQsw
+CQYDVQQGEwJTRTESMBAGA1UEBxMJU3RvY2tob2xtMSUwIwYJKoZIhvcNAQkBFhZw
+ZXRlckBlcml4LmVyaWNzc29uLnNlMB4XDTE1MDIyMzEzMjUzMVoXDTI1MDEwMTEz
+MjUzMVowgYQxDzANBgNVBAMTBnNlcnZlcjETMBEGA1UECxMKRXJsYW5nIE9UUDEU
+MBIGA1UEChMLRXJpY3Nzb24gQUIxCzAJBgNVBAYTAlNFMRIwEAYDVQQHEwlTdG9j
+a2hvbG0xJTAjBgkqhkiG9w0BCQEWFnBldGVyQGVyaXguZXJpY3Nzb24uc2UwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDK8EDdNZEebdfxb57e3UA8uTCq
+TsFtJv5tyjnZtSFsGDrwrZYjRMOCJFh8Yv6Ddq4mZiAvUCJxMzW4zVzraMmmQC8z
+Hi3xQyuIq2UCW3ESxLvchCcuSjNOWke0z+rXHzA8Yz9y1fqhhO6AF8q5lLwGo+VQ
+sJkVV8QwB9UXZN4pAc3zTeqZkGCrNY/ZIgtCrk4jw7sY/gumS8BjhXCYGyFZRDvX
+jzIXQx6jn7/2huNbEAiBXbYYAMd7OEwhpHHAWOVA6g+/TNydgRO3W4xVmlEhDpYs
+bnMV/Tq570E1bhz1XWb642K2MnxI74g8FXmhN6x6P8d4zU/eFcs+gxO0X6KzAgMB
+AAGjggGUMIIBkDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAdBgNVHQ4EFgQUo8dr
+DDQXK25dB6qMY8dNIjAKIPEwgbMGA1UdIwSBqzCBqIAU5YMIq7A5eYQhQsHsc/XC
+7GeZ+kuhgYykgYkwgYYxETAPBgNVBAMTCGVybGFuZ0NBMRMwEQYDVQQLEwpFcmxh
+bmcgT1RQMRQwEgYDVQQKEwtFcmljc3NvbiBBQjESMBAGA1UEBxMJU3RvY2tob2xt
+MQswCQYDVQQGEwJTRTElMCMGCSqGSIb3DQEJARYWcGV0ZXJAZXJpeC5lcmljc3Nv
+bi5zZYIBATAhBgNVHREEGjAYgRZwZXRlckBlcml4LmVyaWNzc29uLnNlMCEGA1Ud
+EgQaMBiBFnBldGVyQGVyaXguZXJpY3Nzb24uc2UwWwYDVR0fBFQwUjAkoCKgIIYe
+aHR0cDovL2xvY2FsaG9zdC9vdHBDQS9jcmwucGVtMCqgKKAmhiRodHRwOi8vbG9j
+YWxob3N0OjM3ODEzL290cENBL2NybC5wZW0wDQYJKoZIhvcNAQEEBQADggEBACwq
+o4nQTTereSIL8ZLQHweJKXYstTaZrRrAaoRUe9oClY7H++zXmMa8iZvUqqdT3fXW
+4KMXXyoB1o+cLxLnAPKOiFFL9rcbaeAMxZMIrTaFDQsOXAPVqJLSWWS5I5LsNvS6
+MlB6O6+0binTyilDKg683VV9nKNiNdL8WzGa5ig+HvK6xUpJwpOTmDmfdg09zQ+8
+aCbJrthXg0tNnGIorttAd2wFvmLUezoJrlfwLChB0M/qa+RVRCFMiPvkWupo5eVK
+Malwpz2xp2rAUlb6qQY7eI6lV8JsVK06QxBmUHP68Y9kYT5/gy5ketjOB0Ypin05
+6+3VrZKFxrkqKaEoL50=
+-----END CERTIFICATE-----
diff --git a/lib/public_key/test/public_key_SUITE_data/idp_crl.pem b/lib/public_key/test/public_key_SUITE_data/idp_crl.pem
new file mode 100644
index 0000000000..0872279501
--- /dev/null
+++ b/lib/public_key/test/public_key_SUITE_data/idp_crl.pem
@@ -0,0 +1,18 @@
+-----BEGIN X509 CRL-----
+MIIC3TCCAcUCAQEwDQYJKoZIhvcNAQEEBQAwgYYxETAPBgNVBAMTCGVybGFuZ0NB
+MRMwEQYDVQQLEwpFcmxhbmcgT1RQMRQwEgYDVQQKEwtFcmljc3NvbiBBQjESMBAG
+A1UEBxMJU3RvY2tob2xtMQswCQYDVQQGEwJTRTElMCMGCSqGSIb3DQEJARYWcGV0
+ZXJAZXJpeC5lcmljc3Nvbi5zZRcNMTUwMjIzMTMyNTMxWhcNMTUwMjI0MTMyNTMx
+WqCCAQgwggEEMIG7BgNVHSMEgbMwgbCAFJtjN3+lARCLByNRyL6YWwc+tEbAoYGM
+pIGJMIGGMREwDwYDVQQDEwhlcmxhbmdDQTETMBEGA1UECxMKRXJsYW5nIE9UUDEU
+MBIGA1UEChMLRXJpY3Nzb24gQUIxEjAQBgNVBAcTCVN0b2NraG9sbTELMAkGA1UE
+BhMCU0UxJTAjBgkqhkiG9w0BCQEWFnBldGVyQGVyaXguZXJpY3Nzb24uc2WCCQCl
+PMPPUpsj8jA4BgNVHRwBAf8ELjAsoCqgKIYmaHR0cDovL2xvY2FsaG9zdDo4MDAw
+L2VybGFuZ0NBL2NybC5wZW0wCgYDVR0UBAMCAQEwDQYJKoZIhvcNAQEEBQADggEB
+AE9WKJhW1oivBEE91akeDcYCtSVp98F7DxzQyJTBLQJGMEXSg8G/oAp64F4qs3oV
+LXS5YFYwxjD9tXByGVEJoIUUMtfMeCvZMgd2V8mBlAJiyHkTrFFA8PgBv+htrJji
+nrheAhrEedqZbqwmrcU34h9fWHp0Zl6UDYyF3I/S0/5ilIz3DvNZ9SBfKKt3DYeW
+hon7qpNo6YrtEzbXyOaa2mFX9c1w39LBZ1FdY0jEzUfh2eImBLxnBjZArNxzYuU8
+a+lNMjc6JUAJwITS6C1YfI4ECsqXe0K/n90pMcm/jgiGFCZhVbXq+Nrm/24qPKBA
+zqoNos7aV7LEYLYOjknaIhY=
+-----END X509 CRL-----
diff --git a/lib/runtime_tools/src/dbg.erl b/lib/runtime_tools/src/dbg.erl
index 186563ab74..c2de57d40b 100644
--- a/lib/runtime_tools/src/dbg.erl
+++ b/lib/runtime_tools/src/dbg.erl
@@ -778,50 +778,50 @@ tracer_init(Handler, HandlerData) ->
tracer_loop(Handler, HandlerData).
tracer_loop(Handler, Hdata) ->
- receive
- Msg ->
- %% Don't match in receive to avoid giving EXIT message higher
- %% priority than the trace messages.
- case Msg of
- {'EXIT',_Pid,_Reason} ->
- ok;
- Trace ->
- NewData = recv_all_traces(Trace, Handler, Hdata),
- tracer_loop(Handler, NewData)
- end
+ {State, Suspended, Traces} = recv_all_traces(),
+ NewHdata = handle_traces(Suspended, Traces, Handler, Hdata),
+ case State of
+ done ->
+ exit(normal);
+ loop ->
+ tracer_loop(Handler, NewHdata)
end.
-
-recv_all_traces(Trace, Handler, Hdata) ->
- Suspended = suspend(Trace, []),
- recv_all_traces(Suspended, Handler, Hdata, [Trace]).
-recv_all_traces(Suspended0, Handler, Hdata, Traces) ->
+recv_all_traces() ->
+ recv_all_traces([], [], infinity).
+
+recv_all_traces(Suspended0, Traces, Timeout) ->
receive
Trace when is_tuple(Trace), element(1, Trace) == trace ->
Suspended = suspend(Trace, Suspended0),
- recv_all_traces(Suspended, Handler, Hdata, [Trace|Traces]);
+ recv_all_traces(Suspended, [Trace|Traces], 0);
Trace when is_tuple(Trace), element(1, Trace) == trace_ts ->
Suspended = suspend(Trace, Suspended0),
- recv_all_traces(Suspended, Handler, Hdata, [Trace|Traces]);
+ recv_all_traces(Suspended, [Trace|Traces], 0);
Trace when is_tuple(Trace), element(1, Trace) == seq_trace ->
Suspended = suspend(Trace, Suspended0),
- recv_all_traces(Suspended, Handler, Hdata, [Trace|Traces]);
+ recv_all_traces(Suspended, [Trace|Traces], 0);
Trace when is_tuple(Trace), element(1, Trace) == drop ->
Suspended = suspend(Trace, Suspended0),
- recv_all_traces(Suspended, Handler, Hdata, [Trace|Traces]);
+ recv_all_traces(Suspended, [Trace|Traces], 0);
+ {'EXIT', _Pid, _Reason} ->
+ {done, Suspended0, Traces};
Other ->
%%% Is this really a good idea?
io:format(user,"** tracer received garbage: ~p~n", [Other]),
- recv_all_traces(Suspended0, Handler, Hdata, Traces)
- after 0 ->
- case catch invoke_handler(Traces, Handler, Hdata) of
- {'EXIT',Reason} ->
- resume(Suspended0),
- exit({trace_handler_crashed,Reason});
- NewHdata ->
- resume(Suspended0),
- NewHdata
- end
+ recv_all_traces(Suspended0, Traces, Timeout)
+ after Timeout ->
+ {loop, Suspended0, Traces}
+ end.
+
+handle_traces(Suspended, Traces, Handler, Hdata) ->
+ case catch invoke_handler(Traces, Handler, Hdata) of
+ {'EXIT',Reason} ->
+ resume(Suspended),
+ exit({trace_handler_crashed,Reason});
+ NewHdata ->
+ resume(Suspended),
+ NewHdata
end.
invoke_handler([Tr|Traces], Handler, Hdata0) ->
diff --git a/lib/runtime_tools/test/dbg_SUITE.erl b/lib/runtime_tools/test/dbg_SUITE.erl
index dfae52ed1d..0bcbd67d05 100644
--- a/lib/runtime_tools/test/dbg_SUITE.erl
+++ b/lib/runtime_tools/test/dbg_SUITE.erl
@@ -25,7 +25,7 @@
ip_port/1, file_port/1, file_port2/1, file_port_schedfix/1,
ip_port_busy/1, wrap_port/1, wrap_port_time/1,
with_seq_trace/1, dead_suspend/1, local_trace/1,
- saved_patterns/1]).
+ saved_patterns/1, tracer_exit_on_stop/1]).
-export([init_per_testcase/2, end_per_testcase/2]).
-export([tracee1/1, tracee2/1]).
-export([dummy/0, exported/1]).
@@ -47,7 +47,7 @@ all() ->
[big, tiny, simple, message, distributed, ip_port,
file_port, file_port2, file_port_schedfix, ip_port_busy,
wrap_port, wrap_port_time, with_seq_trace, dead_suspend,
- local_trace, saved_patterns].
+ local_trace, saved_patterns, tracer_exit_on_stop].
groups() ->
[].
@@ -742,6 +742,38 @@ run_dead_suspend() ->
dummy() ->
ok.
+%% Test that a tracer process does not ignore an exit signal message when it has
+%% received (but not handled) trace messages
+tracer_exit_on_stop(_) ->
+ %% Tracer blocks waiting for fun to complete so that the trace message and
+ %% the exit signal message from the dbg process are in its message queue.
+ Fun = fun() ->
+ ?MODULE:dummy(),
+ Ref = erlang:trace_delivered(self()),
+ receive {trace_delivered, _, Ref} -> stop() end
+ end,
+ {ok, _} = dbg:tracer(process, {fun spawn_once_handler/2, {self(), Fun}}),
+ {ok, Tracer} = dbg:get_tracer(),
+ MRef = monitor(process, Tracer),
+ {ok, _} = dbg:p(self(), [call]),
+ {ok, _} = dbg:p(new, [call]),
+ {ok, _} = dbg:tp(?MODULE, dummy, []),
+ ?MODULE:dummy(),
+ receive {'DOWN', MRef, _, _, normal} -> ok end,
+ [{trace,_,call,{?MODULE, dummy,[]}},
+ {trace,_,call,{?MODULE, dummy,[]}}] = flush(),
+ ok.
+
+spawn_once_handler(Event, {Pid, done} = State) ->
+ Pid ! Event,
+ State;
+spawn_once_handler(Event, {Pid, Fun}) ->
+ {_, Ref} = spawn_monitor(Fun),
+ receive
+ {'DOWN', Ref, _, _, _} ->
+ Pid ! Event,
+ {Pid, done}
+ end.
%%
%% Support functions
diff --git a/lib/sasl/test/release_handler_SUITE.erl b/lib/sasl/test/release_handler_SUITE.erl
index bd7414fbb4..b7c5f34f58 100644
--- a/lib/sasl/test/release_handler_SUITE.erl
+++ b/lib/sasl/test/release_handler_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2015. 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
@@ -1802,11 +1802,17 @@ upgrade_gg(cleanup,Config) ->
%%%-----------------------------------------------------------------
%%% OTP-10463, Bug - release_handler could not handle regexp in appup
%%% files.
-otp_10463_upgrade_script_regexp(_Config) ->
- %% Assuming that kernel always has a regexp in it's appup
- KernelVsn = vsn(kernel,current),
- {ok,KernelVsn,_} =
- release_handler:upgrade_script(kernel,code:lib_dir(kernel)),
+otp_10463_upgrade_script_regexp(Config) ->
+ DataDir = ?config(data_dir,Config),
+ code:add_path(filename:join([DataDir,regexp_appup,app1,ebin])),
+ application:start(app1),
+ {ok,"1.1",_} = release_handler:upgrade_script(app1,code:lib_dir(app1)),
+ ok.
+
+otp_10463_upgrade_script_regexp(cleanup,Config) ->
+ DataDir = ?config(data_dir,Config),
+ application:stop(app1),
+ code:del_path(filename:join([DataDir,regexp_appup,app1,ebin])),
ok.
no_dot_erlang(Conf) ->
diff --git a/lib/sasl/test/release_handler_SUITE_data/regexp_appup/app1/ebin/app1.app b/lib/sasl/test/release_handler_SUITE_data/regexp_appup/app1/ebin/app1.app
new file mode 100644
index 0000000000..ba6d09cd42
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/regexp_appup/app1/ebin/app1.app
@@ -0,0 +1,29 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2015. 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%
+%%
+%% This is an -*- erlang -*- file.
+%%
+{application, app1,
+ [
+ {description, "Test that release_handler can read appup with regexp"},
+ {vsn, "1.1"},
+ {modules, []},
+ {registered, []},
+ {applications, []}
+ ]
+}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/regexp_appup/app1/ebin/app1.appup b/lib/sasl/test/release_handler_SUITE_data/regexp_appup/app1/ebin/app1.appup
new file mode 100644
index 0000000000..9c657232d0
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/regexp_appup/app1/ebin/app1.appup
@@ -0,0 +1,23 @@
+%% -*- erlang -*-
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2015. 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%
+{"1.1",
+ %% Up from
+ [{<<"1(\\.[0-9]+)*">>,[{restart_application,app1}]}],
+ %% Down to
+ [{<<"1(\\.[0-9]+)*">>,[{restart_application,app1}]}]
+}.
diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml
index 9f5d1c003d..d481a75c9a 100644
--- a/lib/ssh/doc/src/ssh.xml
+++ b/lib/ssh/doc/src/ssh.xml
@@ -234,11 +234,11 @@
<taglist>
<tag><c><![CDATA[{inet, inet | inet6}]]></c></tag>
<item> IP version to use when the host address is specified as <c>any</c>. </item>
- <tag><c><![CDATA[{subsystems, [subsystem_spec()]]]></c></tag>
+ <tag><c><![CDATA[{subsystems, [subsystem_spec()]}]]></c></tag>
<item>
Provides specifications for handling of subsystems. The
"sftp" subsystem spec can be retrieved by calling
- ssh_sftpd:subsystem_spec/1. If the subsystems option in
+ ssh_sftpd:subsystem_spec/1. If the subsystems option is
not present the value of
<c>[ssh_sftpd:subsystem_spec([])]</c> will be used. It is
of course possible to set the option to the empty list if
diff --git a/lib/ssh/doc/src/ssh_connection.xml b/lib/ssh/doc/src/ssh_connection.xml
index ff72cf7ee0..5e2926dfa6 100644
--- a/lib/ssh/doc/src/ssh_connection.xml
+++ b/lib/ssh/doc/src/ssh_connection.xml
@@ -62,6 +62,7 @@
<p><c>ssh_request_status() = success | failure</c></p>
<p><c>event() = {ssh_cm, ssh_connection_ref(), ssh_event_msg()} </c></p>
<p><c>ssh_event_msg() = data_events() | status_events() | terminal_events() </c></p>
+ <p><c>reason() = timeout | closed </c></p>
<taglist>
<tag><b>data_events()</b></tag>
@@ -218,7 +219,7 @@
</func>
<func>
- <name>exec(ConnectionRef, ChannelId, Command, TimeOut) -> ssh_request_status() </name>
+ <name>exec(ConnectionRef, ChannelId, Command, TimeOut) -> ssh_request_status() | {error, reason()} </name>
<fsummary>Request that the server start the execution of the given command. </fsummary>
<type>
<v> ConnectionRef = ssh_connection_ref() </v>
@@ -274,7 +275,8 @@
</func>
<func>
- <name>ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> success | failure</name>
+ <name>ptty_alloc(ConnectionRef, ChannelId, Options) -> </name>
+ <name>ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> > ssh_request_status() | {error, reason()} </name>
<fsummary>Send status replies to requests that want such replies. </fsummary>
<type>
<v> ConnectionRef = ssh_connection_ref() </v>
@@ -374,7 +376,7 @@
<func>
<name>session_channel(ConnectionRef, Timeout) -> </name>
<name>session_channel(ConnectionRef, InitialWindowSize,
- MaxPacketSize, Timeout) -> {ok, ssh_channel_id()} | {error, Reason}</name>
+ MaxPacketSize, Timeout) -> {ok, ssh_channel_id()} | {error, reason()}</name>
<fsummary>Opens a channel for a ssh session. </fsummary>
<type>
<v> ConnectionRef = ssh_connection_ref()</v>
@@ -391,7 +393,7 @@
</func>
<func>
- <name>setenv(ConnectionRef, ChannelId, Var, Value, TimeOut) -> ssh_request_status()</name>
+ <name>setenv(ConnectionRef, ChannelId, Var, Value, TimeOut) -> ssh_request_status() | {error, reason()} </name>
<fsummary> Environment variables may be passed to the
shell/command to be started later.</fsummary>
<type>
@@ -409,7 +411,7 @@
</func>
<func>
- <name>shell(ConnectionRef, ChannelId) -> ssh_request_status()
+ <name>shell(ConnectionRef, ChannelId) -> ssh_request_status() | {error, closed}
</name>
<fsummary> Requests that the user's default shell (typically
defined in /etc/passwd in UNIX systems) shall be executed at the server
@@ -426,7 +428,7 @@
</func>
<func>
- <name>subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> ssh_request_status()</name>
+ <name>subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> ssh_request_status() | {error, reason()} </name>
<fsummary> </fsummary>
<type>
<v> ConnectionRef = ssh_connection_ref() </v>
diff --git a/lib/ssh/doc/src/using_ssh.xml b/lib/ssh/doc/src/using_ssh.xml
index 9ab71260d3..46178d4018 100644
--- a/lib/ssh/doc/src/using_ssh.xml
+++ b/lib/ssh/doc/src/using_ssh.xml
@@ -79,7 +79,7 @@
<p> The option user_dir defaults to the users ~/.ssh directory</p>
<p>In the following example we generate new keys and host keys as
- to be able to run the example without having root privilages</p>
+ to be able to run the example without having root privileges</p>
<code>
$bash> ssh-keygen -t rsa -f /tmp/ssh_daemon/ssh_host_rsa_key
diff --git a/lib/ssh/examples/Makefile b/lib/ssh/examples/Makefile
index de019f75b5..9280c42076 100644
--- a/lib/ssh/examples/Makefile
+++ b/lib/ssh/examples/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2005-2012. All Rights Reserved.
+# Copyright Ericsson AB 2005-2015. 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,7 +38,8 @@ RELSYSDIR = $(RELEASE_PATH)/lib/ssh-$(VSN)
MODULES = \
- ssh_sample_cli
+ ssh_sample_cli \
+ ssh_device.erl
ERL_FILES= $(MODULES:=.erl)
diff --git a/lib/ssh/examples/ssh_device.erl b/lib/ssh/examples/ssh_device.erl
new file mode 100644
index 0000000000..f6be812915
--- /dev/null
+++ b/lib/ssh/examples/ssh_device.erl
@@ -0,0 +1,62 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2015. 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%
+%%
+
+-module(ssh_device).
+
+%% api
+-export([ssh_device/5]).
+
+%%% I wrote this because of i think a fully ssh client sample will be easy to start the ssh module better than
+%%% go though each function file.
+ssh_device(Host, Port, User, Pass, Cmd) ->
+ ssh:start(),
+ case ssh:connect(Host, Port,
+ [{user, User}, {password, Pass},
+ {silently_accept_hosts, true}, {quiet_mode, true}])
+ of
+ {ok, Conn} ->
+ {ok, ChannelId} = ssh_connection:session_channel(Conn,
+ infinity),
+ ssh_connection:exec(Conn, ChannelId, Cmd, infinity),
+ Init_rep = <<>>,
+ wait_for_response(Conn, Host, Init_rep),
+ ssh:close(Conn);
+ {error, nxdomain} ->
+ {error,nxdomain}
+ end.
+
+%%--------------------------------------------------------------------
+%%% Internal application API
+%%--------------------------------------------------------------------
+wait_for_response(Conn, Host, Acc) ->
+ receive
+ {ssh_cm, Conn, Msg} ->
+ case Msg of
+ {closed, _ChannelId} ->
+ {ok,Acc};
+ {data, _, _, A} ->
+ Acc2 = <<Acc/binary, A/binary>>,
+ wait_for_response(Conn, Host, Acc2);
+ _ ->
+ wait_for_response(Conn, Host, Acc)
+ end
+ after
+ 5000 ->
+ {error,timeout}
+ end.
diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl
index 01141622d6..c66f810948 100644
--- a/lib/ssh/src/ssh_connection.erl
+++ b/lib/ssh/src/ssh_connection.erl
@@ -56,8 +56,8 @@
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec session_channel(pid(), timeout()) -> {ok, channel_id()} | {error, term()}.
--spec session_channel(pid(), integer(), integer(), timeout()) -> {ok, channel_id()} | {error, term()}.
+-spec session_channel(pid(), timeout()) -> {ok, channel_id()} | {error, timeout | closed}.
+-spec session_channel(pid(), integer(), integer(), timeout()) -> {ok, channel_id()} | {error, timeout | closed}.
%% Description: Opens a channel for a ssh session. A session is a
%% remote execution of a program. The program may be a shell, an
@@ -81,7 +81,8 @@ session_channel(ConnectionHandler, InitialWindowSize,
end.
%%--------------------------------------------------------------------
--spec exec(pid(), channel_id(), string(), timeout()) -> success | failure.
+-spec exec(pid(), channel_id(), string(), timeout()) ->
+ success | failure | {error, timeout | closed}.
%% Description: Will request that the server start the
%% execution of the given command.
@@ -101,8 +102,8 @@ shell(ConnectionHandler, ChannelId) ->
ssh_connection_handler:request(ConnectionHandler, self(), ChannelId,
"shell", false, <<>>, 0).
%%--------------------------------------------------------------------
--spec subsystem(pid(), channel_id(), string(), timeout()) ->
- success | failure | {error, timeout}.
+-spec subsystem(pid(), channel_id(), string(), timeout()) ->
+ success | failure | {error, timeout | closed}.
%%
%% Description: Executes a predefined subsystem.
%%--------------------------------------------------------------------
@@ -142,7 +143,7 @@ send_eof(ConnectionHandler, Channel) ->
ssh_connection_handler:send_eof(ConnectionHandler, Channel).
%%--------------------------------------------------------------------
--spec adjust_window(pid(), channel_id(), integer()) -> ok.
+-spec adjust_window(pid(), channel_id(), integer()) -> ok | {error, closed}.
%%
%%
%% Description: Adjusts the ssh flowcontrol window.
@@ -151,7 +152,8 @@ adjust_window(ConnectionHandler, Channel, Bytes) ->
ssh_connection_handler:adjust_window(ConnectionHandler, Channel, Bytes).
%%--------------------------------------------------------------------
--spec setenv(pid(), channel_id(), string(), string(), timeout()) -> success | failure.
+-spec setenv(pid(), channel_id(), string(), string(), timeout()) ->
+ success | failure | {error, timeout | closed}.
%%
%%
%% Description: Environment variables may be passed to the shell/command to be
@@ -183,7 +185,11 @@ reply_request(_,false, _, _) ->
ok.
%%--------------------------------------------------------------------
--spec ptty_alloc(pid(), channel_id(), proplists:proplist()) -> success | failiure.
+-spec ptty_alloc(pid(), channel_id(), proplists:proplist()) ->
+ success | failiure | {error, closed}.
+-spec ptty_alloc(pid(), channel_id(), proplists:proplist(), timeout()) ->
+ success | failiure | {error, timeout} | {error, closed}.
+
%%
%%
%% Description: Sends a ssh connection protocol pty_req.
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
index fdb9d3b3e6..68523aa72b 100644
--- a/lib/ssh/src/ssh_connection_handler.erl
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -289,8 +289,13 @@ renegotiate_data(ConnectionHandler) ->
-spec close(pid(), channel_id()) -> ok.
%%--------------------------------------------------------------------
close(ConnectionHandler, ChannelId) ->
- sync_send_all_state_event(ConnectionHandler, {close, ChannelId}).
-
+ case sync_send_all_state_event(ConnectionHandler, {close, ChannelId}) of
+ ok ->
+ ok;
+ {error, closed} ->
+ ok
+ end.
+
%%--------------------------------------------------------------------
-spec stop(pid()) -> ok | {error, term()}.
%%--------------------------------------------------------------------
@@ -1204,7 +1209,11 @@ sync_send_all_state_event(FsmPid, Event) ->
sync_send_all_state_event(FsmPid, Event, infinity).
sync_send_all_state_event(FsmPid, Event, Timeout) ->
- try gen_fsm:sync_send_all_state_event(FsmPid, Event, Timeout)
+ try gen_fsm:sync_send_all_state_event(FsmPid, Event, Timeout) of
+ {closed, _Channel} ->
+ {error, closed};
+ Result ->
+ Result
catch
exit:{noproc, _} ->
{error, closed};
@@ -1702,7 +1711,7 @@ handshake(Pid, Ref, Timeout) ->
{error, Reason}
after Timeout ->
stop(Pid),
- {error, Timeout}
+ {error, timeout}
end.
start_timeout(_,_, infinity) ->
diff --git a/lib/ssh/src/ssh_info.erl b/lib/ssh/src/ssh_info.erl
index 9ed598b3ab..e5a8666af0 100644
--- a/lib/ssh/src/ssh_info.erl
+++ b/lib/ssh/src/ssh_info.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2015. 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
@@ -179,7 +179,14 @@ line(Len, Char) ->
datetime() ->
- {{YYYY,MM,DD}, {H,M,S}} = calendar:now_to_universal_time(now()),
+ %% Adapt to new OTP 18 erlang time API and be back-compatible
+ TimeStamp = try
+ erlang:timestamp()
+ catch
+ error:undef ->
+ erlang:now()
+ end,
+ {{YYYY,MM,DD}, {H,M,S}} = calendar:now_to_universal_time(TimeStamp),
lists:flatten(io_lib:format('~4w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w UTC',[YYYY,MM,DD, H,M,S])).
diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl
index 52665635f0..04ae6b11e2 100644
--- a/lib/ssh/src/ssh_sftpd.erl
+++ b/lib/ssh/src/ssh_sftpd.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2015. 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
@@ -559,56 +559,73 @@ stat(ReqId, RelPath, State0=#state{file_handler=FileMod,
send_status({error, E}, ReqId, State1)
end.
-decode_4_open_flag(create_new) ->
- [write];
-decode_4_open_flag(create_truncate) ->
- [write];
-decode_4_open_flag(truncate_existing) ->
- [write];
-decode_4_open_flag(open_existing) ->
- [read].
-
-decode_4_flags([OpenFlag | Flags]) ->
- decode_4_flags(Flags, decode_4_open_flag(OpenFlag)).
-
-decode_4_flags([], Flags) ->
- Flags;
-decode_4_flags([append_data|R], _Flags) ->
- decode_4_flags(R, [append]);
-decode_4_flags([append_data_atomic|R], _Flags) ->
- decode_4_flags(R, [append]);
-decode_4_flags([_|R], Flags) ->
- decode_4_flags(R, Flags).
-
-decode_4_access_flag(read_data) ->
- [read];
-decode_4_access_flag(list_directory) ->
- [read];
-decode_4_access_flag(write_data) ->
- [write];
-decode_4_access_flag(add_file) ->
- [write];
-decode_4_access_flag(add_subdirectory) ->
- [read];
-decode_4_access_flag(append_data) ->
- [append];
-decode_4_access_flag(write_attributes) ->
- [write];
-decode_4_access_flag(_) ->
- [read].
-
-decode_4_acess([_ | _] = Flags) ->
+sftp_to_erlang_flag(read, Vsn) when Vsn == 3;
+ Vsn == 4 ->
+ read;
+sftp_to_erlang_flag(write, Vsn) when Vsn == 3;
+ Vsn == 4 ->
+ write;
+sftp_to_erlang_flag(append, Vsn) when Vsn == 3;
+ Vsn == 4 ->
+ append;
+sftp_to_erlang_flag(creat, Vsn) when Vsn == 3;
+ Vsn == 4 ->
+ write;
+sftp_to_erlang_flag(trunc, Vsn) when Vsn == 3;
+ Vsn == 4 ->
+ write;
+sftp_to_erlang_flag(excl, Vsn) when Vsn == 3;
+ Vsn == 4 ->
+ read;
+sftp_to_erlang_flag(create_new, Vsn) when Vsn > 4 ->
+ write;
+sftp_to_erlang_flag(create_truncate, Vsn) when Vsn > 4 ->
+ write;
+sftp_to_erlang_flag(open_existing, Vsn) when Vsn > 4 ->
+ read;
+sftp_to_erlang_flag(open_or_create, Vsn) when Vsn > 4 ->
+ write;
+sftp_to_erlang_flag(truncate_existing, Vsn) when Vsn > 4 ->
+ write;
+sftp_to_erlang_flag(append_data, Vsn) when Vsn > 4 ->
+ append;
+sftp_to_erlang_flag(append_data_atomic, Vsn) when Vsn > 4 ->
+ append;
+sftp_to_erlang_flag(_, _) ->
+ read.
+
+sftp_to_erlang_flags(Flags, Vsn) ->
lists:map(fun(Flag) ->
- [decode_4_access_flag(Flag)]
- end, Flags);
-decode_4_acess([]) ->
- [].
+ sftp_to_erlang_flag(Flag, Vsn)
+ end, Flags).
+
+sftp_to_erlang_access_flag(read_data, _) ->
+ read;
+sftp_to_erlang_access_flag(list_directory, _) ->
+ read;
+sftp_to_erlang_access_flag(write_data, _) ->
+ write;
+sftp_to_erlang_access_flag(append_data, _) ->
+ append;
+sftp_to_erlang_access_flag(add_subdirectory, _) ->
+ read;
+sftp_to_erlang_access_flag(add_file, _) ->
+ write;
+sftp_to_erlang_access_flag(write_attributes, _) ->
+ write;
+sftp_to_erlang_access_flag(_, _) ->
+ read.
+sftp_to_erlang_access_flags(Flags, Vsn) ->
+ lists:map(fun(Flag) ->
+ sftp_to_erlang_access_flag(Flag, Vsn)
+ end, Flags).
open(Vsn, ReqId, Data, State) when Vsn =< 3 ->
<<?UINT32(BLen), BPath:BLen/binary, ?UINT32(PFlags),
_Attrs/binary>> = Data,
Path = unicode:characters_to_list(BPath),
- Flags = ssh_xfer:decode_open_flags(Vsn, PFlags),
+ FlagBits = ssh_xfer:decode_open_flags(Vsn, PFlags),
+ Flags = lists:usort(sftp_to_erlang_flags(FlagBits, Vsn)),
do_open(ReqId, State, Path, Flags);
open(Vsn, ReqId, Data, State) when Vsn >= 4 ->
<<?UINT32(BLen), BPath:BLen/binary, ?UINT32(Access),
@@ -616,15 +633,12 @@ open(Vsn, ReqId, Data, State) when Vsn >= 4 ->
Path = unicode:characters_to_list(BPath),
FlagBits = ssh_xfer:decode_open_flags(Vsn, PFlags),
AcessBits = ssh_xfer:decode_ace_mask(Access),
- %% TODO: This is to make sure the Access flags are not ignored
- %% but this should be thought through better. This solution should
- %% be considered a hack in order to buy some time. At least
- %% it works better than when the Access flags where totally ignored.
- %% A better solution may need some code refactoring that we do
- %% not have time for right now.
- AcessFlags = decode_4_acess(AcessBits),
- Flags = lists:append(lists:umerge(
- [[decode_4_flags(FlagBits)] | AcessFlags])),
+ %% TODO: There are still flags that are not
+ %% fully handled as SSH_FXF_ACCESS_TEXT_MODE and
+ %% a lot a ACE flags, the later we may not need
+ %% to understand as they are NFS flags
+ AcessFlags = sftp_to_erlang_access_flags(AcessBits, Vsn),
+ Flags = lists:usort(sftp_to_erlang_flags(FlagBits, Vsn) ++ AcessFlags),
do_open(ReqId, State, Path, Flags).
do_open(ReqId, State0, Path, Flags) ->
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl
index 415cb9fc9c..b449012ffc 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-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2015. 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
@@ -715,7 +715,14 @@ ssh_connect_arg4_timeout(_Config) ->
%% try to connect with a timeout, but "supervise" it
Client = spawn(fun() ->
- T0 = now(),
+ %% Adapt to OTP 18 erlang time API and be back-compatible
+ T0 = try
+ erlang:monotonic_time()
+ catch
+ error:undef ->
+ %% Use Erlang system time as monotonic time
+ erlang:now()
+ end,
Rc = ssh:connect("localhost",Port,[],Timeout),
ct:log("Client ssh:connect got ~p",[Rc]),
Parent ! {done,self(),Rc,T0}
@@ -723,16 +730,22 @@ ssh_connect_arg4_timeout(_Config) ->
%% Wait for client reaction on the connection try:
receive
- {done, Client, {error,_E}, T0} ->
- Msp = ms_passed(T0, now()),
+ {done, Client, {error,timeout}, T0} ->
+ Msp = ms_passed(T0),
exit(Server,hasta_la_vista___baby),
Low = 0.9*Timeout,
High = 1.1*Timeout,
- ct:log("Timeout limits: ~p--~p, timeout was ~p, expected ~p",[Low,High,Msp,Timeout]),
+ ct:log("Timeout limits: ~.4f - ~.4f ms, timeout "
+ "was ~.4f ms, expected ~p ms",[Low,High,Msp,Timeout]),
if
Low<Msp, Msp<High -> ok;
true -> {fail, "timeout not within limits"}
end;
+
+ {done, Client, {error,Other}, _T0} ->
+ ct:log("Error message \"~p\" from the client is unexpected.",[{error,Other}]),
+ {fail, "Unexpected error message"};
+
{done, Client, {ok,_Ref}, _T0} ->
{fail,"ssh-connected ???"}
after
@@ -743,12 +756,16 @@ ssh_connect_arg4_timeout(_Config) ->
end.
-%% Help function
-%% N2-N1
-ms_passed(N1={_,_,M1}, N2={_,_,M2}) ->
- {0,{0,Min,Sec}} = calendar:time_difference(calendar:now_to_local_time(N1),
- calendar:now_to_local_time(N2)),
- 1000 * (Min*60 + Sec + (M2-M1)/1000000).
+%% Help function, elapsed milliseconds since T0
+ms_passed({_,_,_} = T0 ) ->
+ %% OTP 17 and earlier
+ timer:now_diff(erlang:now(), T0)/1000;
+
+ms_passed(T0) ->
+ %% OTP 18
+ erlang:convert_time_resolution(erlang:monotonic_time() - T0,
+ erlang:time_resolution(),
+ 1000000)/1000.
%%--------------------------------------------------------------------
ssh_connect_negtimeout_parallel(Config) -> ssh_connect_negtimeout(Config,true).
diff --git a/lib/ssh/test/ssh_connection_SUITE.erl b/lib/ssh/test/ssh_connection_SUITE.erl
index 85bd2c75d4..e3871b3feb 100644
--- a/lib/ssh/test/ssh_connection_SUITE.erl
+++ b/lib/ssh/test/ssh_connection_SUITE.erl
@@ -37,7 +37,6 @@
all() ->
[
{group, openssh},
- {group, openssh_payload},
interrupted_send,
start_shell,
start_shell_exec,
@@ -46,7 +45,8 @@ all() ->
gracefull_invalid_start,
gracefull_invalid_long_start,
gracefull_invalid_long_start_no_nl,
- stop_listener
+ stop_listener,
+ start_subsystem_on_closed_channel
].
groups() ->
[{openssh, [], payload() ++ ptty()}].
@@ -576,6 +576,31 @@ stop_listener(Config) when is_list(Config) ->
ct:fail({unexpected, Error})
end.
+start_subsystem_on_closed_channel(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDir),
+ SysDir = ?config(data_dir, Config),
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {password, "morot"},
+ {subsystems, [{"echo_n", {ssh_echo_server, [4000000]}}]}]),
+
+ ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+
+ {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
+
+ ok = ssh_connection:close(ConnectionRef, ChannelId),
+
+ {error, closed} = ssh_connection:subsystem(ConnectionRef, ChannelId, "echo_n", infinity),
+
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
diff --git a/lib/ssh/test/ssh_sftpd_SUITE.erl b/lib/ssh/test/ssh_sftpd_SUITE.erl
index 7b22e45d5e..0ce8eec906 100644
--- a/lib/ssh/test/ssh_sftpd_SUITE.erl
+++ b/lib/ssh/test/ssh_sftpd_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2015. 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
@@ -56,7 +56,8 @@ all() ->
retrieve_attributes,
set_attributes,
links,
- ver3_rename,
+ ver3_rename,
+ ver3_open_flags,
relpath,
sshd_read_file,
ver6_basic].
@@ -193,6 +194,39 @@ open_close_file(Config) when is_list(Config) ->
?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
?SSH_FXF_OPEN_EXISTING).
+ver3_open_flags() ->
+ [{doc, "Test open flags"}].
+ver3_open_flags(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "not_exist.txt"),
+ {Cm, Channel} = ?config(sftp, Config),
+ ReqId = 0,
+
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(ReqId), Handle/binary>>, _} =
+ open_file_v3(FileName, Cm, Channel, ReqId,
+ ?SSH_FXF_CREAT bor ?SSH_FXF_TRUNC),
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId),
+ ?UINT32(?SSH_FX_OK), _/binary>>, _} = close(Handle, ReqId,
+ Cm, Channel),
+
+ NewFileName = filename:join(PrivDir, "not_exist2.txt"),
+ NewReqId = ReqId + 1,
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(NewReqId), NewHandle/binary>>, _} =
+ open_file_v3(NewFileName, Cm, Channel, NewReqId,
+ ?SSH_FXF_CREAT bor ?SSH_FXF_EXCL),
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId),
+ ?UINT32(?SSH_FX_OK), _/binary>>, _} = close(NewHandle, NewReqId,
+ Cm, Channel),
+
+ NewFileName1 = filename:join(PrivDir, "test.txt"),
+ NewReqId1 = NewReqId + 1,
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(NewReqId1), NewHandle1/binary>>, _} =
+ open_file_v3(NewFileName1, Cm, Channel, NewReqId1,
+ ?SSH_FXF_READ bor ?SSH_FXF_WRITE bor ?SSH_FXF_APPEND),
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId1),
+ ?UINT32(?SSH_FX_OK), _/binary>>, _} = close(NewHandle1, NewReqId1,
+ Cm, Channel).
+
%%--------------------------------------------------------------------
open_close_dir() ->
[{doc,"Test SSH_FXP_OPENDIR and SSH_FXP_CLOSE commands"}].
@@ -662,6 +696,16 @@ open_file(File, Cm, Channel, ReqId, Access, Flags) ->
?SSH_FXP_OPEN, Data/binary>>),
reply(Cm, Channel).
+open_file_v3(File, Cm, Channel, ReqId, Flags) ->
+
+ Data = list_to_binary([?uint32(ReqId),
+ ?binary(list_to_binary(File)),
+ ?uint32(Flags),
+ ?REG_ATTERS]),
+ Size = 1 + size(Data),
+ ssh_connection:send(Cm, Channel, <<?UINT32(Size),
+ ?SSH_FXP_OPEN, Data/binary>>),
+ reply(Cm, Channel).
close(Handle, ReqId, Cm , Channel) ->
diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk
index c8cac3e852..bfebe2c60b 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.1.1
APP_VSN = "ssh-$(SSH_VSN)"
diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml
index b53344e381..0c042f8571 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1999</year><year>2014</year>
+ <year>1999</year><year>2015</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -163,7 +163,7 @@
is supplied it will override the certfile option.</item>
<tag>{certfile, path()}</tag>
- <item>Path to a file containing the user's certificate.</item>
+ <item>Path to a file containing the user's PEM encoded certificate.</item>
<tag>{key, {'RSAPrivateKey'| 'DSAPrivateKey' | 'ECPrivateKey' |'PrivateKeyInfo', der_encoded()}}</tag>
<item> The DER encoded users private key. If this option
@@ -348,11 +348,23 @@ fun(srp, Username :: string(), UserState :: term()) ->
</p>
</item>
+ <tag>{padding_check, boolean()}</tag>
+ <item>
+ <p> This option only affects TLS-1.0 connections.
+ If set to false it disables the block cipher padding check
+ to be able to interoperate with legacy software.
+ </p>
+
+ <warning><p> Using this option makes TLS vulnerable to
+ the Poodle attack</p></warning>
+
+ </item>
+
</taglist>
-
+
</section>
-
- <section>
+
+ <section>
<title>SSL OPTION DESCRIPTIONS - CLIENT SIDE</title>
<p>Options described here are client specific or has a slightly different
@@ -413,6 +425,23 @@ fun(srp, Username :: string(), UserState :: term()) ->
Indication extension will be sent if possible, this option may also be
used to disable that behavior.</p>
</item>
+ <tag>{fallback, boolean()}</tag>
+ <item>
+ <p> Send special cipher suite TLS_FALLBACK_SCSV to avoid undesired TLS version downgrade.
+ Defaults to false</p>
+ <warning><p>Note this option is not needed in normal TLS usage and should not be used
+ to implement new clients. But legacy clients that that retries connections in the following manner</p>
+
+ <p><c> ssl:connect(Host, Port, [...{versions, ['tlsv2', 'tlsv1.1', 'tlsv1', 'sslv3']}])</c></p>
+ <p><c> ssl:connect(Host, Port, [...{versions, [tlsv1.1', 'tlsv1', 'sslv3']}, {fallback, true}])</c></p>
+ <p><c> ssl:connect(Host, Port, [...{versions, ['tlsv1', 'sslv3']}, {fallback, true}]) </c></p>
+ <p><c> ssl:connect(Host, Port, [...{versions, ['sslv3']}, {fallback, true}]) </c></p>
+
+ <p>may use it to avoid undesired TLS version downgrade. Note that TLS_FALLBACK_SCSV must also
+ be supported by the server for the prevention to work.
+ </p></warning>
+ </item>
+
</taglist>
</section>
@@ -538,7 +567,19 @@ fun(srp, Username :: string(), UserState :: term()) ->
</p>
</desc>
</func>
-
+
+ <func>
+ <name>clear_pem_cache() -> ok </name>
+ <fsummary> Clears the pem cache</fsummary>
+
+ <desc><p>PEM files, used by ssl API-functions, are cached. The
+ cache is regularly checked to see if any cache entries should be
+ invalidated, however this function provides a way to
+ unconditionally clear the whole cache.
+ </p>
+ </desc>
+ </func>
+
<func>
<name>connect(Socket, SslOptions) -> </name>
<name>connect(Socket, SslOptions, Timeout) -> {ok, SslSocket}
diff --git a/lib/ssl/doc/src/ssl_app.xml b/lib/ssl/doc/src/ssl_app.xml
index 43cb3934f7..f1377cabda 100644
--- a/lib/ssl/doc/src/ssl_app.xml
+++ b/lib/ssl/doc/src/ssl_app.xml
@@ -4,7 +4,7 @@
<appref>
<header>
<copyright>
- <year>1999</year><year>2013</year>
+ <year>1999</year><year>2015</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -82,7 +82,16 @@
callback module, defaults to [].
</p>
</item>
-
+
+ <tag><c><![CDATA[ssl_pem_cache_clean = integer() <optional>]]></c></tag>
+ <item>
+ <p>
+ Number of milliseconds between PEM cache validations.
+ </p>
+ <seealso
+ marker="ssl#clear_pem_cache-0">ssl:clear_pem_cache/0</seealso>
+
+ </item>
</taglist>
</section>
diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl
index a7bbb6bc40..ae35dd7ea4 100644
--- a/lib/ssl/src/dtls_record.erl
+++ b/lib/ssl/src/dtls_record.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2015. 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
@@ -146,7 +146,7 @@ decode_cipher_text(#ssl_tls{type = Type, version = Version,
= ConnnectionStates0) ->
CompressAlg = SecParams#security_parameters.compression_algorithm,
{PlainFragment, Mac, ReadState1} = ssl_record:decipher(dtls_v1:corresponding_tls_version(Version),
- CipherFragment, ReadState0),
+ CipherFragment, ReadState0, true),
MacHash = calc_mac_hash(ReadState1, Type, Version, Epoch, Seq, PlainFragment),
case ssl_record:is_correct_mac(Mac, MacHash) of
true ->
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index b4bea25942..5f4ad7f013 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2014. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2015. 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
@@ -656,7 +656,9 @@ handle_options(Opts0) ->
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),
- protocol = proplists:get_value(protocol, Opts, tls)
+ protocol = proplists:get_value(protocol, Opts, tls),
+ padding_check = proplists:get_value(padding_check, Opts, true),
+ fallback = proplists:get_value(fallback, Opts, false)
},
CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}),
@@ -669,7 +671,8 @@ handle_options(Opts0) ->
cb_info, renegotiate_at, secure_renegotiate, hibernate_after,
erl_dist, next_protocols_advertised,
client_preferred_next_protocols, log_alert,
- server_name_indication, honor_cipher_order],
+ server_name_indication, honor_cipher_order, padding_check,
+ fallback],
SockOpts = lists:foldl(fun(Key, PropList) ->
proplists:delete(Key, PropList)
@@ -847,6 +850,10 @@ validate_option(server_name_indication, undefined) ->
undefined;
validate_option(honor_cipher_order, Value) when is_boolean(Value) ->
Value;
+validate_option(padding_check, Value) when is_boolean(Value) ->
+ Value;
+validate_option(fallback, Value) when is_boolean(Value) ->
+ Value;
validate_option(Opt, Value) ->
throw({error, {options, {Opt, Value}}}).
diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl
index 78dc98bc25..9e372f739a 100644
--- a/lib/ssl/src/ssl_alert.erl
+++ b/lib/ssl/src/ssl_alert.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2015. 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
@@ -161,5 +161,7 @@ description_txt(?BAD_CERTIFICATE_HASH_VALUE) ->
"bad certificate hash value";
description_txt(?UNKNOWN_PSK_IDENTITY) ->
"unknown psk identity";
+description_txt(?INAPPROPRIATE_FALLBACK) ->
+ "inappropriate fallback";
description_txt(Enum) ->
lists:flatten(io_lib:format("unsupported/unknown alert: ~p", [Enum])).
diff --git a/lib/ssl/src/ssl_alert.hrl b/lib/ssl/src/ssl_alert.hrl
index f4f1d74264..a3619e4a35 100644
--- a/lib/ssl/src/ssl_alert.hrl
+++ b/lib/ssl/src/ssl_alert.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2015. 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
@@ -58,6 +58,7 @@
%% protocol_version(70),
%% insufficient_security(71),
%% internal_error(80),
+%% inappropriate_fallback(86),
%% user_canceled(90),
%% no_renegotiation(100),
%% RFC 4366
@@ -93,6 +94,7 @@
-define(PROTOCOL_VERSION, 70).
-define(INSUFFICIENT_SECURITY, 71).
-define(INTERNAL_ERROR, 80).
+-define(INAPPROPRIATE_FALLBACK, 86).
-define(USER_CANCELED, 90).
-define(NO_RENEGOTIATION, 100).
-define(UNSUPPORTED_EXTENSION, 110).
diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl
index 9c0ed181fe..30d224fee2 100644
--- a/lib/ssl/src/ssl_certificate.erl
+++ b/lib/ssl/src/ssl_certificate.erl
@@ -282,7 +282,7 @@ other_issuer(OtpCert, CertDbHandle) ->
handle_path({BinCert, OTPCert}, Path, PartialChainHandler) ->
case public_key:pkix_is_self_signed(OTPCert) of
true ->
- {BinCert, Path};
+ {BinCert, lists:delete(BinCert, Path)};
false ->
handle_incomplete_chain(Path, PartialChainHandler)
end.
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index 72467ea2a0..bec0055353 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2015. 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
@@ -33,11 +33,10 @@
-include_lib("public_key/include/public_key.hrl").
-export([security_parameters/2, security_parameters/3, suite_definition/1,
- decipher/5, cipher/5,
- suite/1, suites/1, all_suites/1,
+ decipher/6, cipher/5, suite/1, suites/1, all_suites/1,
ec_keyed_suites/0, anonymous_suites/0, psk_suites/1, srp_suites/0,
openssl_suite/1, openssl_suite_name/1, filter/2, filter_suites/1,
- hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2]).
+ hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1]).
-export_type([cipher_suite/0,
erl_cipher_suite/0, openssl_cipher_suite/0,
@@ -143,17 +142,18 @@ block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0,
{T, CS0#cipher_state{iv=NextIV}}.
%%--------------------------------------------------------------------
--spec decipher(cipher_enum(), integer(), #cipher_state{}, binary(), ssl_record:ssl_version()) ->
+-spec decipher(cipher_enum(), integer(), #cipher_state{}, binary(),
+ ssl_record:ssl_version(), boolean()) ->
{binary(), binary(), #cipher_state{}} | #alert{}.
%%
%% Description: Decrypts the data and the MAC using cipher described
%% by cipher_enum() and updating the cipher state.
%%-------------------------------------------------------------------
-decipher(?NULL, _HashSz, CipherState, Fragment, _) ->
+decipher(?NULL, _HashSz, CipherState, Fragment, _, _) ->
{Fragment, <<>>, CipherState};
-decipher(?RC4, HashSz, CipherState, Fragment, _) ->
+decipher(?RC4, HashSz, CipherState, Fragment, _, _) ->
State0 = case CipherState#cipher_state.state of
- undefined -> crypto:stream_init(rc4, CipherState#cipher_state.key);
+ undefined -> crypto:stream_init(rc4, CipherState#cipher_state.key);
S -> S
end,
try crypto:stream_decrypt(State0, Fragment) of
@@ -171,23 +171,23 @@ decipher(?RC4, HashSz, CipherState, Fragment, _) ->
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
end;
-decipher(?DES, HashSz, CipherState, Fragment, Version) ->
+decipher(?DES, HashSz, CipherState, Fragment, Version, PaddingCheck) ->
block_decipher(fun(Key, IV, T) ->
crypto:block_decrypt(des_cbc, Key, IV, T)
- end, CipherState, HashSz, Fragment, Version);
-decipher(?'3DES', HashSz, CipherState, Fragment, Version) ->
+ end, CipherState, HashSz, Fragment, Version, PaddingCheck);
+decipher(?'3DES', HashSz, CipherState, Fragment, Version, PaddingCheck) ->
block_decipher(fun(<<K1:8/binary, K2:8/binary, K3:8/binary>>, IV, T) ->
crypto:block_decrypt(des3_cbc, [K1, K2, K3], IV, T)
- end, CipherState, HashSz, Fragment, Version);
-decipher(?AES, HashSz, CipherState, Fragment, Version) ->
+ end, CipherState, HashSz, Fragment, Version, PaddingCheck);
+decipher(?AES, HashSz, CipherState, Fragment, Version, PaddingCheck) ->
block_decipher(fun(Key, IV, T) when byte_size(Key) =:= 16 ->
crypto:block_decrypt(aes_cbc128, Key, IV, T);
(Key, IV, T) when byte_size(Key) =:= 32 ->
crypto:block_decrypt(aes_cbc256, Key, IV, T)
- end, CipherState, HashSz, Fragment, Version).
+ end, CipherState, HashSz, Fragment, Version, PaddingCheck).
block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0,
- HashSz, Fragment, Version) ->
+ HashSz, Fragment, Version, PaddingCheck) ->
try
Text = Fun(Key, IV, Fragment),
NextIV = next_iv(Fragment, IV),
@@ -195,7 +195,7 @@ block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0,
Content = GBC#generic_block_cipher.content,
Mac = GBC#generic_block_cipher.mac,
CipherState1 = CipherState0#cipher_state{iv=GBC#generic_block_cipher.next_iv},
- case is_correct_padding(GBC, Version) of
+ case is_correct_padding(GBC, Version, PaddingCheck) of
true ->
{Content, Mac, CipherState1};
false ->
@@ -1108,6 +1108,9 @@ is_acceptable_prf(default_prf, _) ->
is_acceptable_prf(Prf, Algos) ->
proplists:get_bool(Prf, Algos).
+is_fallback(CipherSuites)->
+ lists:member(?TLS_FALLBACK_SCSV, CipherSuites).
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
@@ -1288,16 +1291,18 @@ generic_stream_cipher_from_bin(T, HashSz) ->
#generic_stream_cipher{content=Content,
mac=Mac}.
-%% For interoperability reasons we do not check the padding content in
-%% SSL 3.0 and TLS 1.0 as it is not strictly required and breaks
-%% interopability with for instance Google.
is_correct_padding(#generic_block_cipher{padding_length = Len,
- padding = Padding}, {3, N})
- when N == 0; N == 1 ->
- Len == byte_size(Padding);
-%% Padding must be check in TLS 1.1 and after
+ padding = Padding}, {3, 0}, _) ->
+ Len == byte_size(Padding); %% Only length check is done in SSL 3.0 spec
+%% For interoperability reasons it is possible to disable
+%% the padding check when using TLS 1.0, as it is not strictly required
+%% in the spec (only recommended), howerver this makes TLS 1.0 vunrable to the Poodle attack
+%% so by default this clause will not match
+is_correct_padding(GenBlockCipher, {3, 1}, false) ->
+ is_correct_padding(GenBlockCipher, {3, 0}, false);
+%% Padding must be checked in TLS 1.1 and after
is_correct_padding(#generic_block_cipher{padding_length = Len,
- padding = Padding}, _) ->
+ padding = Padding}, _, _) ->
Len == byte_size(Padding) andalso
list_to_binary(lists:duplicate(Len, Len)) == Padding.
diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl
index 3ce9c19aa9..3e50563f0a 100644
--- a/lib/ssl/src/ssl_cipher.hrl
+++ b/lib/ssl/src/ssl_cipher.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2015. 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
@@ -355,6 +355,10 @@
%% hello extension data as they should.
-define(TLS_EMPTY_RENEGOTIATION_INFO_SCSV, <<?BYTE(16#00), ?BYTE(16#FF)>>).
+%% TLS Fallback Signaling Cipher Suite Value (SCSV) for Preventing Protocol
+%% Downgrade Attacks
+-define(TLS_FALLBACK_SCSV, <<?BYTE(16#56), ?BYTE(16#00)>>).
+
%%% PSK Cipher Suites RFC 4279
%% TLS_PSK_WITH_RC4_128_SHA = { 0x00, 0x8A };
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index 75efb64e3f..88105cac5a 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2015. 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
@@ -117,7 +117,9 @@
server_name_indication = undefined,
%% Should the server prefer its own cipher order over the one provided by
%% the client?
- honor_cipher_order = false
+ honor_cipher_order = false,
+ padding_check = true,
+ fallback = false
}).
-record(socket_options,
diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl
index d6e5064c39..c4f1f7f193 100644
--- a/lib/ssl/src/ssl_manager.erl
+++ b/lib/ssl/src/ssl_manager.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2015. 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
@@ -30,10 +30,10 @@
lookup_trusted_cert/4,
new_session_id/1, clean_cert_db/2,
register_session/2, register_session/3, invalidate_session/2,
- invalidate_session/3, clear_pem_cache/0, manager_name/1]).
+ invalidate_session/3, invalidate_pem/1, clear_pem_cache/0, manager_name/1]).
% Spawn export
--export([init_session_validator/1]).
+-export([init_session_validator/1, init_pem_cache_validator/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@@ -49,7 +49,9 @@
session_lifetime,
certificate_db,
session_validation_timer,
- last_delay_timer = {undefined, undefined}%% Keep for testing purposes
+ last_delay_timer = {undefined, undefined},%% Keep for testing purposes
+ last_pem_check,
+ clear_pem_cache
}).
-define('24H_in_msec', 86400000).
@@ -117,14 +119,13 @@ connection_init(Trustedcerts, Role) ->
%% Description: Cache a pem file and return its content.
%%--------------------------------------------------------------------
cache_pem_file(File, DbHandle) ->
- MD5 = crypto:hash(md5, File),
- case ssl_pkix_db:lookup_cached_pem(DbHandle, MD5) of
+ case ssl_pkix_db:lookup_cached_pem(DbHandle, File) of
[{Content,_}] ->
{ok, Content};
[Content] ->
{ok, Content};
undefined ->
- call({cache_pem, {MD5, File}})
+ call({cache_pem, File})
end.
%%--------------------------------------------------------------------
@@ -191,6 +192,11 @@ invalidate_session(Host, Port, Session) ->
invalidate_session(Port, Session) ->
cast({invalidate_session, Port, Session}).
+
+-spec invalidate_pem(File::binary()) -> ok.
+invalidate_pem(File) ->
+ cast({invalidate_pem, File}).
+
%%====================================================================
%% gen_server callbacks
%%====================================================================
@@ -212,12 +218,16 @@ init([Name, Opts]) ->
SessionCache = CacheCb:init(proplists:get_value(session_cb_init_args, Opts, [])),
Timer = erlang:send_after(SessionLifeTime * 1000 + 5000,
self(), validate_sessions),
- erlang:send_after(?CLEAR_PEM_CACHE, self(), clear_pem_cache),
+ Interval = pem_check_interval(),
+ erlang:send_after(Interval, self(), clear_pem_cache),
{ok, #state{certificate_db = CertDb,
session_cache = SessionCache,
session_cache_cb = CacheCb,
session_lifetime = SessionLifeTime,
- session_validation_timer = Timer}}.
+ session_validation_timer = Timer,
+ last_pem_check = os:timestamp(),
+ clear_pem_cache = Interval
+ }}.
%%--------------------------------------------------------------------
-spec handle_call(msg(), from(), #state{}) -> {reply, reply(), #state{}}.
@@ -256,7 +266,7 @@ handle_call({{new_session_id,Port}, _},
{reply, Id, State};
-handle_call({{cache_pem, File}, _Pid}, _,
+handle_call({{cache_pem,File}, _Pid}, _,
#state{certificate_db = Db} = State) ->
try ssl_pkix_db:cache_pem_file(File, Db) of
Result ->
@@ -303,7 +313,12 @@ handle_cast({invalidate_session, Host, Port,
handle_cast({invalidate_session, Port, #session{session_id = ID} = Session},
#state{session_cache = Cache,
session_cache_cb = CacheCb} = State) ->
- invalidate_session(Cache, CacheCb, {Port, ID}, Session, State).
+ invalidate_session(Cache, CacheCb, {Port, ID}, Session, State);
+
+handle_cast({invalidate_pem, File},
+ #state{certificate_db = [_, _, PemCache]} = State) ->
+ ssl_pkix_db:remove(File, PemCache),
+ {noreply, State}.
%%--------------------------------------------------------------------
-spec handle_info(msg(), #state{}) -> {noreply, #state{}}.
@@ -328,15 +343,13 @@ handle_info({delayed_clean_session, Key}, #state{session_cache = Cache,
CacheCb:delete(Cache, Key),
{noreply, State};
-handle_info(clear_pem_cache, #state{certificate_db = [_,_,PemChace]} = State) ->
- case ssl_pkix_db:db_size(PemChace) of
- N when N < ?NOT_TO_BIG ->
- ok;
- _ ->
- ssl_pkix_db:clear(PemChace)
- end,
- erlang:send_after(?CLEAR_PEM_CACHE, self(), clear_pem_cache),
- {noreply, State};
+handle_info(clear_pem_cache, #state{certificate_db = [_,_,PemChace],
+ clear_pem_cache = Interval,
+ last_pem_check = CheckPoint} = State) ->
+ NewCheckPoint = os:timestamp(),
+ start_pem_cache_validator(PemChace, CheckPoint),
+ erlang:send_after(Interval, self(), clear_pem_cache),
+ {noreply, State#state{last_pem_check = NewCheckPoint}};
handle_info({clean_cert_db, Ref, File},
@@ -482,10 +495,9 @@ new_id(Port, Tries, Cache, CacheCb) ->
clean_cert_db(Ref, CertDb, RefDb, PemCache, File) ->
case ssl_pkix_db:ref_count(Ref, RefDb, 0) of
0 ->
- MD5 = crypto:hash(md5, File),
- case ssl_pkix_db:lookup_cached_pem(PemCache, MD5) of
+ case ssl_pkix_db:lookup_cached_pem(PemCache, File) of
[{Content, Ref}] ->
- ssl_pkix_db:insert(MD5, Content, PemCache);
+ ssl_pkix_db:insert(File, Content, PemCache);
_ ->
ok
end,
@@ -494,3 +506,39 @@ clean_cert_db(Ref, CertDb, RefDb, PemCache, File) ->
_ ->
ok
end.
+
+start_pem_cache_validator(PemCache, CheckPoint) ->
+ spawn_link(?MODULE, init_pem_cache_validator,
+ [[get(ssl_manager), PemCache, CheckPoint]]).
+
+init_pem_cache_validator([SslManagerName, PemCache, CheckPoint]) ->
+ put(ssl_manager, SslManagerName),
+ ssl_pkix_db:foldl(fun pem_cache_validate/2,
+ CheckPoint, PemCache).
+
+pem_cache_validate({File, _}, CheckPoint) ->
+ case file:read_file_info(File, []) of
+ {ok, #file_info{mtime = Time}} ->
+ case is_before_checkpoint(Time, CheckPoint) of
+ true ->
+ ok;
+ false ->
+ invalidate_pem(File)
+ end;
+ _ ->
+ invalidate_pem(File)
+ end,
+ CheckPoint.
+
+pem_check_interval() ->
+ case application:get_env(ssl, ssl_pem_cache_clean) of
+ {ok, Interval} when is_integer(Interval) ->
+ Interval;
+ _ ->
+ ?CLEAR_PEM_CACHE
+ end.
+
+is_before_checkpoint(Time, CheckPoint) ->
+ calendar:datetime_to_gregorian_seconds(calendar:now_to_datetime(CheckPoint)) -
+ calendar:datetime_to_gregorian_seconds(Time) > 0.
+
diff --git a/lib/ssl/src/ssl_pkix_db.erl b/lib/ssl/src/ssl_pkix_db.erl
index e59aba0618..8531445ba4 100644
--- a/lib/ssl/src/ssl_pkix_db.erl
+++ b/lib/ssl/src/ssl_pkix_db.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2015. 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
@@ -81,10 +81,10 @@ lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) ->
{ok, Certs}
end.
-lookup_cached_pem([_, _, PemChache], MD5) ->
- lookup_cached_pem(PemChache, MD5);
-lookup_cached_pem(PemChache, MD5) ->
- lookup(MD5, PemChache).
+lookup_cached_pem([_, _, PemChache], File) ->
+ lookup_cached_pem(PemChache, File);
+lookup_cached_pem(PemChache, File) ->
+ lookup(File, PemChache).
%%--------------------------------------------------------------------
-spec add_trusted_certs(pid(), {erlang:timestamp(), string()} |
@@ -100,36 +100,35 @@ add_trusted_certs(_Pid, {der, DerList}, [CerDb, _,_]) ->
{ok, NewRef};
add_trusted_certs(_Pid, File, [CertsDb, RefDb, PemChache] = Db) ->
- MD5 = crypto:hash(md5, File),
- case lookup_cached_pem(Db, MD5) of
+ case lookup_cached_pem(Db, File) of
[{_Content, Ref}] ->
ref_count(Ref, RefDb, 1),
{ok, Ref};
[Content] ->
Ref = make_ref(),
update_counter(Ref, 1, RefDb),
- insert(MD5, {Content, Ref}, PemChache),
+ insert(File, {Content, Ref}, PemChache),
add_certs_from_pem(Content, Ref, CertsDb),
{ok, Ref};
undefined ->
- new_trusted_cert_entry({MD5, File}, Db)
+ new_trusted_cert_entry(File, Db)
end.
%%--------------------------------------------------------------------
%%
%% Description: Cache file as binary in DB
%%--------------------------------------------------------------------
--spec cache_pem_file({binary(), binary()}, [db_handle()]) -> {ok, term()}.
-cache_pem_file({MD5, File}, [_CertsDb, _RefDb, PemChache]) ->
+-spec cache_pem_file(binary(), [db_handle()]) -> {ok, term()}.
+cache_pem_file(File, [_CertsDb, _RefDb, PemChache]) ->
{ok, PemBin} = file:read_file(File),
Content = public_key:pem_decode(PemBin),
- insert(MD5, Content, PemChache),
+ insert(File, Content, PemChache),
{ok, Content}.
--spec cache_pem_file(reference(), {binary(), binary()}, [db_handle()]) -> {ok, term()}.
-cache_pem_file(Ref, {MD5, File}, [_CertsDb, _RefDb, PemChache]) ->
+-spec cache_pem_file(reference(), binary(), [db_handle()]) -> {ok, term()}.
+cache_pem_file(Ref, File, [_CertsDb, _RefDb, PemChache]) ->
{ok, PemBin} = file:read_file(File),
Content = public_key:pem_decode(PemBin),
- insert(MD5, {Content, Ref}, PemChache),
+ insert(File, {Content, Ref}, PemChache),
{ok, Content}.
%%--------------------------------------------------------------------
@@ -245,9 +244,9 @@ add_certs(Cert, Ref, CertsDb) ->
error_logger:info_report(Report)
end.
-new_trusted_cert_entry(FileRef, [CertsDb, RefDb, _] = Db) ->
+new_trusted_cert_entry(File, [CertsDb, RefDb, _] = Db) ->
Ref = make_ref(),
update_counter(Ref, 1, RefDb),
- {ok, Content} = cache_pem_file(Ref, FileRef, Db),
+ {ok, Content} = cache_pem_file(Ref, File, Db),
add_certs_from_pem(Content, Ref, CertsDb),
{ok, Ref}.
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl
index 7337225bc4..025a46bf65 100644
--- a/lib/ssl/src/ssl_record.erl
+++ b/lib/ssl/src/ssl_record.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2015. 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
@@ -48,7 +48,7 @@
-export([compress/3, uncompress/3, compressions/0]).
%% Payload encryption/decryption
--export([cipher/4, decipher/3, is_correct_mac/2]).
+-export([cipher/4, decipher/4, is_correct_mac/2]).
-export_type([ssl_version/0, ssl_atom_version/0]).
@@ -376,8 +376,9 @@ cipher(Version, Fragment,
{CipherFragment, CipherS1} =
ssl_cipher:cipher(BulkCipherAlgo, CipherS0, MacHash, Fragment, Version),
{CipherFragment, WriteState0#connection_state{cipher_state = CipherS1}}.
+
%%--------------------------------------------------------------------
--spec decipher(ssl_version(), binary(), #connection_state{}) -> {binary(), binary(), #connection_state{}} | #alert{}.
+-spec decipher(ssl_version(), binary(), #connection_state{}, boolean()) -> {binary(), binary(), #connection_state{}} | #alert{}.
%%
%% Description: Payload decryption
%%--------------------------------------------------------------------
@@ -387,8 +388,8 @@ decipher(Version, CipherFragment,
BulkCipherAlgo,
hash_size = HashSz},
cipher_state = CipherS0
- } = ReadState) ->
- case ssl_cipher:decipher(BulkCipherAlgo, HashSz, CipherS0, CipherFragment, Version) of
+ } = ReadState, PaddingCheck) ->
+ case ssl_cipher:decipher(BulkCipherAlgo, HashSz, CipherS0, CipherFragment, Version, PaddingCheck) of
{PlainFragment, Mac, CipherS1} ->
CS1 = ReadState#connection_state{cipher_state = CipherS1},
{PlainFragment, Mac, CS1};
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 7df73fb581..77d3aa7889 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2015. 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
@@ -482,8 +482,9 @@ next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_ci
next_record(#state{protocol_buffers =
#protocol_buffers{tls_packets = [], tls_cipher_texts = [CT | Rest]}
= Buffers,
- connection_states = ConnStates0} = State) ->
- case tls_record:decode_cipher_text(CT, ConnStates0) of
+ connection_states = ConnStates0,
+ ssl_options = #ssl_options{padding_check = Check}} = State) ->
+ case tls_record:decode_cipher_text(CT, ConnStates0, Check) of
{Plain, ConnStates} ->
{Plain, State#state{protocol_buffers =
Buffers#protocol_buffers{tls_cipher_texts = Rest},
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index 183cabcfcd..b0b6d5a8e3 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2015. 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
@@ -28,6 +28,7 @@
-include("tls_record.hrl").
-include("ssl_alert.hrl").
-include("ssl_internal.hrl").
+-include("ssl_cipher.hrl").
-include_lib("public_key/include/public_key.hrl").
-export([client_hello/8, hello/4,
@@ -47,22 +48,28 @@
%%--------------------------------------------------------------------
client_hello(Host, Port, ConnectionStates,
#ssl_options{versions = Versions,
- ciphers = UserSuites
+ ciphers = UserSuites,
+ fallback = Fallback
} = SslOpts,
Cache, CacheCb, Renegotiation, OwnCert) ->
Version = tls_record:highest_protocol_version(Versions),
Pending = ssl_record:pending_connection_state(ConnectionStates, read),
SecParams = Pending#connection_state.security_parameters,
- CipherSuites = ssl_handshake:available_suites(UserSuites, Version),
+ AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version),
Extensions = ssl_handshake:client_hello_extensions(Host, Version,
- CipherSuites,
+ AvailableCipherSuites,
SslOpts, ConnectionStates, Renegotiation),
-
- Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert),
-
+ CipherSuites =
+ case Fallback of
+ true ->
+ [?TLS_FALLBACK_SCSV | ssl_handshake:cipher_suites(AvailableCipherSuites, Renegotiation)];
+ false ->
+ ssl_handshake:cipher_suites(AvailableCipherSuites, Renegotiation)
+ end,
+ Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert),
#client_hello{session_id = Id,
client_version = Version,
- cipher_suites = ssl_handshake:cipher_suites(CipherSuites, Renegotiation),
+ cipher_suites = CipherSuites,
compression_methods = ssl_record:compressions(),
random = SecParams#security_parameters.client_random,
extensions = Extensions
@@ -96,33 +103,22 @@ hello(#server_hello{server_version = Version, random = Random,
end;
hello(#client_hello{client_version = ClientVersion,
- session_id = SugesstedId,
- cipher_suites = CipherSuites,
- compression_methods = Compressions,
- random = Random,
- extensions = #hello_extensions{elliptic_curves = Curves} = HelloExt},
+ cipher_suites = CipherSuites} = Hello,
#ssl_options{versions = Versions} = SslOpts,
- {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert}, Renegotiation) ->
+ Info, Renegotiation) ->
Version = ssl_handshake:select_version(tls_record, ClientVersion, Versions),
- case tls_record:is_acceptable_version(Version, Versions) of
- true ->
- ECCCurve = ssl_handshake:select_curve(Curves, ssl_handshake:supported_ecc(Version)),
- {Type, #session{cipher_suite = CipherSuite} = Session1}
- = ssl_handshake:select_session(SugesstedId, CipherSuites, Compressions,
- Port, Session0#session{ecc = ECCCurve}, Version,
- SslOpts, Cache, CacheCb, Cert),
- case CipherSuite of
- no_suite ->
- ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY);
- _ ->
- handle_client_hello_extensions(Version, Type, Random, CipherSuites, HelloExt,
- SslOpts, Session1, ConnectionStates0,
- Renegotiation)
+ case ssl_cipher:is_fallback(CipherSuites) of
+ true ->
+ Highest = tls_record:highest_protocol_version(Versions),
+ case tls_record:is_higher(Highest, Version) of
+ true ->
+ ?ALERT_REC(?FATAL, ?INAPPROPRIATE_FALLBACK);
+ false ->
+ handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation)
end;
false ->
- ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)
+ handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation)
end.
-
%%--------------------------------------------------------------------
-spec encode_handshake(tls_handshake(), tls_record:tls_version()) -> iolist().
%%
@@ -149,6 +145,32 @@ get_tls_handshake(Version, Data, Buffer) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+handle_client_hello(Version, #client_hello{session_id = SugesstedId,
+ cipher_suites = CipherSuites,
+ compression_methods = Compressions,
+ random = Random,
+ extensions = #hello_extensions{elliptic_curves = Curves} = HelloExt},
+ #ssl_options{versions = Versions} = SslOpts,
+ {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert}, Renegotiation) ->
+ case tls_record:is_acceptable_version(Version, Versions) of
+ true ->
+ ECCCurve = ssl_handshake:select_curve(Curves, ssl_handshake:supported_ecc(Version)),
+ {Type, #session{cipher_suite = CipherSuite} = Session1}
+ = ssl_handshake:select_session(SugesstedId, CipherSuites, Compressions,
+ Port, Session0#session{ecc = ECCCurve}, Version,
+ SslOpts, Cache, CacheCb, Cert),
+ case CipherSuite of
+ no_suite ->
+ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY);
+ _ ->
+ handle_client_hello_extensions(Version, Type, Random, CipherSuites, HelloExt,
+ SslOpts, Session1, ConnectionStates0,
+ Renegotiation)
+ end;
+ false ->
+ ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)
+ end.
+
get_tls_handshake_aux(Version, <<?BYTE(Type), ?UINT24(Length),
Body:Length/binary,Rest/binary>>, Acc) ->
Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>,
diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl
index f50ea22f39..168b2c8fd3 100644
--- a/lib/ssl/src/tls_record.erl
+++ b/lib/ssl/src/tls_record.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2015. 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,14 +34,14 @@
-export([get_tls_records/2]).
%% Decoding
--export([decode_cipher_text/2]).
+-export([decode_cipher_text/3]).
%% Encoding
-export([encode_plain_text/4]).
%% Protocol version handling
-export([protocol_version/1, lowest_protocol_version/2,
- highest_protocol_version/1, supported_protocol_versions/0,
+ highest_protocol_version/1, is_higher/2, supported_protocol_versions/0,
is_acceptable_version/1, is_acceptable_version/2]).
-export_type([tls_version/0, tls_atom_version/0]).
@@ -142,19 +142,21 @@ encode_plain_text(Type, Version, Data,
{CipherText, ConnectionStates#connection_states{current_write = WriteState#connection_state{sequence_number = Seq +1}}}.
%%--------------------------------------------------------------------
--spec decode_cipher_text(#ssl_tls{}, #connection_states{}) ->
+-spec decode_cipher_text(#ssl_tls{}, #connection_states{}, boolean()) ->
{#ssl_tls{}, #connection_states{}}| #alert{}.
%%
%% Description: Decode cipher text
%%--------------------------------------------------------------------
decode_cipher_text(#ssl_tls{type = Type, version = Version,
- fragment = CipherFragment} = CipherText, ConnnectionStates0) ->
- ReadState0 = ConnnectionStates0#connection_states.current_read,
- #connection_state{compression_state = CompressionS0,
- sequence_number = Seq,
- security_parameters = SecParams} = ReadState0,
- CompressAlg = SecParams#security_parameters.compression_algorithm,
- case ssl_record:decipher(Version, CipherFragment, ReadState0) of
+ fragment = CipherFragment} = CipherText,
+ #connection_states{current_read =
+ #connection_state{
+ compression_state = CompressionS0,
+ sequence_number = Seq,
+ security_parameters=
+ #security_parameters{compression_algorithm = CompressAlg}
+ } = ReadState0} = ConnnectionStates0, PaddingCheck) ->
+ case ssl_record:decipher(Version, CipherFragment, ReadState0, PaddingCheck) of
{PlainFragment, Mac, ReadState1} ->
MacHash = calc_mac_hash(Type, Version, PlainFragment, ReadState1),
case ssl_record:is_correct_mac(Mac, MacHash) of
@@ -234,6 +236,13 @@ highest_protocol_version(Version = {M,_}, [{N,_} | Rest]) when M > N ->
highest_protocol_version(_, [Version | Rest]) ->
highest_protocol_version(Version, Rest).
+is_higher({M, N}, {M, O}) when N > O ->
+ true;
+is_higher({M, _}, {N, _}) when M > N ->
+ true;
+is_higher(_, _) ->
+ false.
+
%%--------------------------------------------------------------------
-spec supported_protocol_versions() -> [tls_version()].
%%
diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile
index 2f8ff6f04e..0d241707d9 100644
--- a/lib/ssl/test/Makefile
+++ b/lib/ssl/test/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1999-2013. All Rights Reserved.
+# Copyright Ericsson AB 1999-2015. 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
@@ -46,6 +46,7 @@ MODULES = \
ssl_npn_handshake_SUITE \
ssl_packet_SUITE \
ssl_payload_SUITE \
+ ssl_pem_cache_SUITE \
ssl_session_cache_SUITE \
ssl_to_openssl_SUITE \
ssl_ECC_SUITE \
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index 1da4e88077..df9432a43b 100644
--- a/lib/ssl/test/ssl_basic_SUITE.erl
+++ b/lib/ssl/test/ssl_basic_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2015. 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
@@ -90,7 +90,8 @@ basic_tests() ->
version_option,
connect_twice,
connect_dist,
- clear_pem_cache
+ clear_pem_cache,
+ fallback
].
options_tests() ->
@@ -256,11 +257,6 @@ init_per_testcase(Case, Config) when Case == unordered_protocol_versions_client
_ ->
{skip, "TLS 1.2 need but not supported on this platform"}
end;
-init_per_testcase(no_authority_key_identifier, Config) ->
- %% Clear cach so that root cert will not
- %% be found.
- ssl:clear_pem_cache(),
- Config;
init_per_testcase(protocol_versions, Config) ->
ssl:stop(),
@@ -286,6 +282,14 @@ init_per_testcase(empty_protocol_versions, Config) ->
ssl:start(),
Config;
+init_per_testcase(fallback, Config) ->
+ case tls_record:highest_protocol_version([]) of
+ {3, N} when N > 1 ->
+ Config;
+ _ ->
+ {skip, "Not relevant if highest supported version is less than 3.2"}
+ end;
+
%% init_per_testcase(different_ca_peer_sign, Config0) ->
%% ssl_test_lib:make_mix_cert(Config0);
@@ -648,6 +652,34 @@ clear_pem_cache(Config) when is_list(Config) ->
0 = ets:info(FilRefDb, size).
%%--------------------------------------------------------------------
+
+fallback() ->
+ [{doc, "Test TLS_FALLBACK_SCSV downgrade prevention"}].
+
+fallback(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server =
+ ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {options, ServerOpts}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client =
+ ssl_test_lib:start_client_error([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {from, self()}, {options,
+ [{fallback, true},
+ {versions, ['tlsv1']}
+ | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, {error,{tls_alert,"inappropriate fallback"}},
+ Client, {error,{tls_alert,"inappropriate fallback"}}).
+
+%%--------------------------------------------------------------------
peername() ->
[{doc,"Test API function peername/1"}].
diff --git a/lib/ssl/test/ssl_certificate_verify_SUITE.erl b/lib/ssl/test/ssl_certificate_verify_SUITE.erl
index b7864ba6e7..dab7a941db 100644
--- a/lib/ssl/test/ssl_certificate_verify_SUITE.erl
+++ b/lib/ssl/test/ssl_certificate_verify_SUITE.erl
@@ -443,7 +443,7 @@ verify_fun_always_run_client(Config) when is_list(Config) ->
{unknown, UserState};
(_, valid, [ChainLen]) ->
{valid, [ChainLen + 1]};
- (_, valid_peer, [2]) ->
+ (_, valid_peer, [1]) ->
{fail, "verify_fun_was_always_run"};
(_, valid_peer, UserState) ->
{valid, UserState}
@@ -482,7 +482,7 @@ verify_fun_always_run_server(Config) when is_list(Config) ->
{unknown, UserState};
(_, valid, [ChainLen]) ->
{valid, [ChainLen + 1]};
- (_, valid_peer, [2]) ->
+ (_, valid_peer, [1]) ->
{fail, "verify_fun_was_always_run"};
(_, valid_peer, UserState) ->
{valid, UserState}
diff --git a/lib/ssl/test/ssl_cipher_SUITE.erl b/lib/ssl/test/ssl_cipher_SUITE.erl
index 45e91786d4..0e48b674e0 100644
--- a/lib/ssl/test/ssl_cipher_SUITE.erl
+++ b/lib/ssl/test/ssl_cipher_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2015. 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,7 +38,7 @@
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
- [aes_decipher_good, aes_decipher_good_tls11, aes_decipher_fail, aes_decipher_fail_tls11].
+ [aes_decipher_good, aes_decipher_fail, padding_test].
groups() ->
[].
@@ -73,93 +73,123 @@ end_per_testcase(_TestCase, Config) ->
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
aes_decipher_good() ->
- [{doc,"Decipher a known cryptotext."}].
+ [{doc,"Decipher a known cryptotext using a correct key"}].
aes_decipher_good(Config) when is_list(Config) ->
HashSz = 32,
- CipherState = #cipher_state{iv = <<59,201,85,117,188,206,224,136,5,109,46,70,104,79,4,9>>,
- key = <<72,196,247,97,62,213,222,109,210,204,217,186,172,184,197,148>>},
- Fragment = <<220,193,179,139,171,33,143,245,202,47,123,251,13,232,114,8,
- 190,162,74,31,186,227,119,155,94,74,119,79,169,193,240,160,
- 198,181,81,19,98,162,213,228,74,224,253,168,156,59,195,122,
- 108,101,107,242,20,15,169,150,163,107,101,94,93,104,241,165>>,
- Content = <<183,139,16,132,10,209,67,86,168,100,61,217,145,57,36,56, "HELLO\n">>,
- Mac = <<71,136,212,107,223,200,70,232,127,116,148,205,232,35,158,113,237,174,15,217,192,168,35,8,6,107,107,233,25,174,90,111>>,
- Version = {3,0},
- {Content, Mac, _} = ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version),
- Version1 = {3,1},
- {Content, Mac, _} = ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version1),
- ok.
-
-%%--------------------------------------------------------------------
-
-aes_decipher_good_tls11() ->
- [{doc,"Decipher a known TLS 1.1 cryptotext."}].
-
-%% the fragment is actuall a TLS 1.1 record, with
-%% Version = TLS 1.1, we get the correct NextIV in #cipher_state
-aes_decipher_good_tls11(Config) when is_list(Config) ->
- HashSz = 32,
- CipherState = #cipher_state{iv = <<59,201,85,117,188,206,224,136,5,109,46,70,104,79,4,9>>,
- key = <<72,196,247,97,62,213,222,109,210,204,217,186,172,184,197,148>>},
- Fragment = <<220,193,179,139,171,33,143,245,202,47,123,251,13,232,114,8,
- 190,162,74,31,186,227,119,155,94,74,119,79,169,193,240,160,
- 198,181,81,19,98,162,213,228,74,224,253,168,156,59,195,122,
- 108,101,107,242,20,15,169,150,163,107,101,94,93,104,241,165>>,
- Content = <<"HELLO\n">>,
- NextIV = <<183,139,16,132,10,209,67,86,168,100,61,217,145,57,36,56>>,
- Mac = <<71,136,212,107,223,200,70,232,127,116,148,205,232,35,158,113,237,174,15,217,192,168,35,8,6,107,107,233,25,174,90,111>>,
- Version = {3,2},
- {Content, Mac, #cipher_state{iv = NextIV}} = ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version),
- Version1 = {3,2},
- {Content, Mac, #cipher_state{iv = NextIV}} = ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version1),
- ok.
+ CipherState = correct_cipher_state(),
+ decipher_check_good(HashSz, CipherState, {3,0}),
+ decipher_check_good(HashSz, CipherState, {3,1}),
+ decipher_check_good(HashSz, CipherState, {3,2}),
+ decipher_check_good(HashSz, CipherState, {3,3}).
%%--------------------------------------------------------------------
aes_decipher_fail() ->
- [{doc,"Decipher a known cryptotext."}].
+ [{doc,"Decipher a known cryptotext using a incorrect key"}].
-%% same as above, last byte of key replaced
aes_decipher_fail(Config) when is_list(Config) ->
HashSz = 32,
- CipherState = #cipher_state{iv = <<59,201,85,117,188,206,224,136,5,109,46,70,104,79,4,9>>,
- key = <<72,196,247,97,62,213,222,109,210,204,217,186,172,184,197,254>>},
- Fragment = <<220,193,179,139,171,33,143,245,202,47,123,251,13,232,114,8,
- 190,162,74,31,186,227,119,155,94,74,119,79,169,193,240,160,
- 198,181,81,19,98,162,213,228,74,224,253,168,156,59,195,122,
- 108,101,107,242,20,15,169,150,163,107,101,94,93,104,241,165>>,
- Version = {3,0},
- {Content, Mac, _} = ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version),
- 32 = byte_size(Content),
- 32 = byte_size(Mac),
- Version1 = {3,1},
- {Content1, Mac1, _} = ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version1),
- 32 = byte_size(Content1),
- 32 = byte_size(Mac1),
- ok.
-%%--------------------------------------------------------------------
-
-aes_decipher_fail_tls11() ->
- [{doc,"Decipher a known TLS 1.1 cryptotext."}].
-
-%% same as above, last byte of key replaced
-%% stricter padding checks in TLS 1.1 mean we get an alert instead
-aes_decipher_fail_tls11(Config) when is_list(Config) ->
- HashSz = 32,
- CipherState = #cipher_state{iv = <<59,201,85,117,188,206,224,136,5,109,46,70,104,79,4,9>>,
- key = <<72,196,247,97,62,213,222,109,210,204,217,186,172,184,197,254>>},
- Fragment = <<220,193,179,139,171,33,143,245,202,47,123,251,13,232,114,8,
- 190,162,74,31,186,227,119,155,94,74,119,79,169,193,240,160,
- 198,181,81,19,98,162,213,228,74,224,253,168,156,59,195,122,
- 108,101,107,242,20,15,169,150,163,107,101,94,93,104,241,165>>,
- Version = {3,2},
- #alert{level = ?FATAL, description = ?BAD_RECORD_MAC} =
- ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version),
- Version1 = {3,3},
- #alert{level = ?FATAL, description = ?BAD_RECORD_MAC} =
- ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version1),
- ok.
+ CipherState = incorrect_cipher_state(),
+ decipher_check_fail(HashSz, CipherState, {3,0}),
+ decipher_check_fail(HashSz, CipherState, {3,1}),
+ decipher_check_fail(HashSz, CipherState, {3,2}),
+ decipher_check_fail(HashSz, CipherState, {3,3}).
%%--------------------------------------------------------------------
+padding_test(Config) when is_list(Config) ->
+ HashSz = 16,
+ CipherState = correct_cipher_state(),
+ pad_test(HashSz, CipherState, {3,0}),
+ pad_test(HashSz, CipherState, {3,1}),
+ pad_test(HashSz, CipherState, {3,2}),
+ pad_test(HashSz, CipherState, {3,3}).
+
+%%--------------------------------------------------------------------
+% Internal functions --------------------------------------------------------
+%%--------------------------------------------------------------------
+decipher_check_good(HashSz, CipherState, Version) ->
+ {Content, NextIV, Mac} = content_nextiv_mac(Version),
+ {Content, Mac, #cipher_state{iv = NextIV}} =
+ ssl_cipher:decipher(?AES, HashSz, CipherState, aes_fragment(Version), Version, true).
+
+decipher_check_fail(HashSz, CipherState, Version) ->
+ {Content, NextIV, Mac} = content_nextiv_mac(Version),
+ true = {Content, Mac, #cipher_state{iv = NextIV}} =/=
+ ssl_cipher:decipher(?AES, HashSz, CipherState, aes_fragment(Version), Version, true).
+
+pad_test(HashSz, CipherState, {3,0} = Version) ->
+ %% 3.0 does not have padding test
+ {Content, NextIV, Mac} = badpad_content_nextiv_mac(Version),
+ {Content, Mac, #cipher_state{iv = NextIV}} =
+ ssl_cipher:decipher(?AES, HashSz, CipherState, badpad_aes_fragment({3,0}), {3,0}, true),
+ {Content, Mac, #cipher_state{iv = NextIV}} =
+ ssl_cipher:decipher(?AES, HashSz, CipherState, badpad_aes_fragment({3,0}), {3,0}, false);
+pad_test(HashSz, CipherState, {3,1} = Version) ->
+ %% 3.1 should have padding test, but may be disabled
+ {Content, NextIV, Mac} = badpad_content_nextiv_mac(Version),
+ BadCont = badpad_content(Content),
+ {Content, Mac, #cipher_state{iv = NextIV}} =
+ ssl_cipher:decipher(?AES, HashSz, CipherState, badpad_aes_fragment({3,1}) , {3,1}, false),
+ {BadCont, Mac, #cipher_state{iv = NextIV}} =
+ ssl_cipher:decipher(?AES, HashSz, CipherState, badpad_aes_fragment({3,1}), {3,1}, true);
+pad_test(HashSz, CipherState, Version) ->
+ %% 3.2 and 3.3 must have padding test
+ {Content, NextIV, Mac} = badpad_content_nextiv_mac(Version),
+ BadCont = badpad_content(Content),
+ {BadCont, Mac, #cipher_state{iv = NextIV}} = ssl_cipher:decipher(?AES, HashSz, CipherState,
+ badpad_aes_fragment(Version), Version, false),
+ {BadCont, Mac, #cipher_state{iv = NextIV}} = ssl_cipher:decipher(?AES, HashSz, CipherState,
+ badpad_aes_fragment(Version), Version, true).
+
+aes_fragment({3,N}) when N == 0; N == 1->
+ <<197,9,6,109,242,87,80,154,85,250,110,81,119,95,65,185,53,206,216,153,246,169,
+ 119,177,178,238,248,174,253,220,242,81,33,0,177,251,91,44,247,53,183,198,165,
+ 63,20,194,159,107>>;
+
+aes_fragment(_) ->
+ <<220,193,179,139,171,33,143,245,202,47,123,251,13,232,114,8,
+ 190,162,74,31,186,227,119,155,94,74,119,79,169,193,240,160,
+ 198,181,81,19,98,162,213,228,74,224,253,168,156,59,195,122,
+ 108,101,107,242,20,15,169,150,163,107,101,94,93,104,241,165>>.
+
+badpad_aes_fragment({3,N}) when N == 0; N == 1 ->
+ <<186,139,125,10,118,21,26,248,120,108,193,104,87,118,145,79,225,55,228,10,105,
+ 30,190,37,1,88,139,243,210,99,65,41>>;
+badpad_aes_fragment(_) ->
+ <<137,31,14,77,228,80,76,103,183,125,55,250,68,190,123,131,117,23,229,180,207,
+ 94,121,137,117,157,109,99,113,61,190,138,131,229,201,120,142,179,172,48,77,
+ 234,19,240,33,38,91,93>>.
+
+content_nextiv_mac({3,N}) when N == 0; N == 1 ->
+ {<<"HELLO\n">>,
+ <<33,0, 177,251, 91,44, 247,53, 183,198, 165,63, 20,194, 159,107>>,
+ <<71,136,212,107,223,200,70,232,127,116,148,205,232,35,158,113,237,174,15,217,192,168,35,8,6,107,107,233,25,174,90,111>>};
+content_nextiv_mac(_) ->
+ {<<"HELLO\n">>,
+ <<183,139,16,132,10,209,67,86,168,100,61,217,145,57,36,56>>,
+ <<71,136,212,107,223,200,70,232,127,116,148,205,232,35,158,113,237,174,15,217,192,168,35,8,6,107,107,233,25,174,90,111>>}.
+
+badpad_content_nextiv_mac({3,N}) when N == 0; N == 1 ->
+ {<<"HELLO\n">>,
+ <<225,55,228,10,105,30,190,37,1,88,139,243,210,99,65,41>>,
+ <<183,139,16,132,10,209,67,86,168,100,61,217,145,57,36,56>>
+ };
+badpad_content_nextiv_mac(_) ->
+ {<<"HELLO\n">>,
+ <<133,211,45,189,179,229,56,86,11,178,239,159,14,160,253,140>>,
+ <<183,139,16,132,10,209,67,86,168,100,61,217,145,57,36,56>>
+ }.
+
+badpad_content(Content) ->
+ %% BadContent will fail mac test
+ <<16#F0, Content/binary>>.
+
+correct_cipher_state() ->
+ #cipher_state{iv = <<59,201,85,117,188,206,224,136,5,109,46,70,104,79,4,9>>,
+ key = <<72,196,247,97,62,213,222,109,210,204,217,186,172,184,197,148>>}.
+
+incorrect_cipher_state() ->
+ #cipher_state{iv = <<59,201,85,117,188,206,224,136,5,109,46,70,104,79,4,9>>,
+ key = <<72,196,247,97,62,213,222,109,210,204,217,186,172,184,197,254>>}.
diff --git a/lib/ssl/test/ssl_pem_cache_SUITE.erl b/lib/ssl/test/ssl_pem_cache_SUITE.erl
new file mode 100644
index 0000000000..843079e2fe
--- /dev/null
+++ b/lib/ssl/test/ssl_pem_cache_SUITE.erl
@@ -0,0 +1,127 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2015-2015. 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/.2
+%%
+%% 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%
+%%
+
+%%
+
+-module(ssl_pem_cache_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("kernel/include/file.hrl").
+
+-define(CLEANUP_INTERVAL, 5000).
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+all() ->
+ [pem_cleanup].
+
+groups() ->
+ [].
+
+init_per_suite(Config0) ->
+ catch crypto:stop(),
+ try crypto:start() of
+ ok ->
+ ssl:start(),
+ %% make rsa certs using oppenssl
+ Result =
+ (catch make_certs:all(?config(data_dir, Config0),
+ ?config(priv_dir, Config0))),
+ ct:log("Make certs ~p~n", [Result]),
+
+ Config1 = ssl_test_lib:make_dsa_cert(Config0),
+ ssl_test_lib:cert_options(Config1)
+ catch _:_ ->
+ {skip, "Crypto did not start"}
+ end.
+
+end_per_suite(_Config) ->
+ application:stop(crypto).
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+init_per_testcase(pem_cleanup, Config) ->
+ ssl:stop(),
+ application:load(ssl),
+ application:set_env(ssl, ssl_pem_cache_clean, ?CLEANUP_INTERVAL),
+ ssl:start(),
+ Config.
+
+end_per_testcase(_TestCase, Config) ->
+ %%ssl:stop(),
+ Config.
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+pem_cleanup() ->
+ [{doc, "Test pem cache invalidate mechanism"}].
+pem_cleanup(Config)when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {from, self()}, {options, ClientOpts}]),
+
+ Size = ssl_pkix_db:db_size(get_pem_cache()),
+ Certfile = proplists:get_value(certfile, ServerOpts),
+ {ok, FileInfo} = file:read_file_info(Certfile),
+ Time = later(),
+ ok = file:write_file_info(Certfile, FileInfo#file_info{mtime = Time}),
+ ct:sleep(2 * ?CLEANUP_INTERVAL),
+ Size1 = ssl_pkix_db:db_size(get_pem_cache()),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client),
+ false = Size == Size1.
+
+get_pem_cache() ->
+ {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
+ [_, _,_, _, Prop] = StatusInfo,
+ State = ssl_test_lib:state(Prop),
+ case element(5, State) of
+ [_CertDb, _FileRefDb, PemChace] ->
+ PemChace;
+ _ ->
+ undefined
+ end.
+
+later()->
+ DateTime = calendar:now_to_local_time(os:timestamp()),
+ Gregorian = calendar:datetime_to_gregorian_seconds(DateTime),
+ calendar:gregorian_seconds_to_datetime(Gregorian + (2 * ?CLEANUP_INTERVAL)).
+
diff --git a/lib/stdlib/doc/src/proc_lib.xml b/lib/stdlib/doc/src/proc_lib.xml
index 5bf5744622..b0bad9d27b 100644
--- a/lib/stdlib/doc/src/proc_lib.xml
+++ b/lib/stdlib/doc/src/proc_lib.xml
@@ -173,7 +173,7 @@
<name name="init_ack" arity="2"/>
<fsummary>Used by a process when it has started.</fsummary>
<desc>
- <p>This function must used by a process that has been started by
+ <p>This function must be used by a process that has been started by
a <seealso marker="#start/3">start[_link]/3,4,5</seealso>
function. It tells <c><anno>Parent</anno></c> that the process has
initialized itself, has started, or has failed to initialize
diff --git a/lib/stdlib/doc/src/re.xml b/lib/stdlib/doc/src/re.xml
index a1833f6a51..5af1468e9b 100644
--- a/lib/stdlib/doc/src/re.xml
+++ b/lib/stdlib/doc/src/re.xml
@@ -150,7 +150,11 @@ This option makes it possible to include comments inside complicated patterns. N
<tag><c>no_start_optimize</c></tag>
<item>This option disables optimization that may malfunction if "Special start-of-pattern items" are present in the regular expression. A typical example would be when matching "DEFABC" against "(*COMMIT)ABC", where the start optimization of PCRE would skip the subject up to the "A" and would never realize that the (*COMMIT) instruction should have made the matching fail. This option is only relevant if you use "start-of-pattern items", as discussed in the section "PCRE regular expression details" below.</item>
<tag><c>ucp</c></tag>
- <item>Specifies that Unicode Character Properties should be used when resolving \B, \b, \D, \d, \S, \s, \Wand \w. Without this flag, only ISO-Latin-1 properties are used. Using Unicode properties hurts performance, but is semantically correct when working with Unicode characters beyond the ISO-Latin-1 range.</item>
+ <item>Specifies that Unicode Character Properties should be used when
+ resolving \B, \b, \D, \d, \S, \s, \W and \w. Without this flag, only
+ ISO-Latin-1 properties are used. Using Unicode properties hurts
+ performance, but is semantically correct when working with Unicode
+ characters beyond the ISO-Latin-1 range.</item>
<tag><c>never_utf</c></tag>
<item>Specifies that the (*UTF) and/or (*UTF8) "start-of-pattern items" are forbidden. This flag can not be combined with <c>unicode</c>. Useful if ISO-Latin-1 patterns from an external source are to be compiled.</item>
</taglist>
@@ -966,7 +970,7 @@ appearance causes an error.
</quote>
<p>This has the same effect as setting the <c>ucp</c> option: it causes sequences
such as \d and \w to use Unicode properties to determine character types,
-instead of recognizing only characters with codes less than 128 via a lookup
+instead of recognizing only characters with codes less than 256 via a lookup
table.
</p>
@@ -1307,7 +1311,8 @@ By default, the definition of letters and digits is controlled by PCRE's
low-valued character tables, in Erlang's case (and without the <c>unicode</c> option),
the ISO-Latin-1 character set.</p>
-<p>By default, in <c>unicode</c> mode, characters with values greater than 128 never match
+<p>By default, in <c>unicode</c> mode, characters with values greater than 255,
+i.e. all characters outside the ISO-Latin-1 character set, never match
\d, \s, or \w, and always match \D, \S, and \W. These sequences retain
their original meanings from before UTF support was available, mainly for
efficiency reasons. However, if the <c>ucp</c> option is set, the behaviour is changed so that Unicode
@@ -1954,10 +1959,10 @@ can be included in a class as a literal string of data units, or by using the
upper case and lower case versions, so for example, a caseless [aeiou] matches
"A" as well as "a", and a caseless [^aeiou] does not match "A", whereas a
caseful version would. In a UTF mode, PCRE always understands the concept of
-case for characters whose values are less than 128, so caseless matching is
+case for characters whose values are less than 256, so caseless matching is
always possible. For characters with higher values, the concept of case is
supported if PCRE is compiled with Unicode property support, but not otherwise.
-If you want to use caseless matching in a UTF mode for characters 128 and
+If you want to use caseless matching in a UTF mode for characters 256 and
above, you must ensure that PCRE is compiled with Unicode property support as
well as with UTF support.</p>
@@ -1989,7 +1994,7 @@ matches the letters in either case. For example, [W-c] is equivalent to
[][\\^_`wxyzabc], matched caselessly, and in a non-UTF mode, if character
tables for a French locale are in use, [\xc8-\xcb] matches accented E
characters in both cases. In UTF modes, PCRE supports the concept of case for
-characters with values greater than 128 only when it is compiled with Unicode
+characters with values greater than 255 only when it is compiled with Unicode
property support.</p>
<p>The character escape sequences \d, \D, \h, \H, \p, \P, \s, \S, \v,
@@ -2062,7 +2067,7 @@ by a ^ character after the colon. For example,</p>
syntax [.ch.] and [=ch=] where "ch" is a "collating element", but these are not
supported, and an error is given if they are encountered.</p>
-<p>By default, in UTF modes, characters with values greater than 128 do not match
+<p>By default, in UTF modes, characters with values greater than 255 do not match
any of the POSIX character classes. However, if the PCRE_UCP option is passed
to <b>pcre_compile()</b>, some of the classes are changed so that Unicode
character properties are used. This is achieved by replacing the POSIX classes
@@ -2081,7 +2086,7 @@ by other sequences, as follows:</p>
<p>Negated versions, such as [:^alpha:] use \P instead of \p. The other POSIX
classes are unchanged, and match only characters with code points less than
-128.</p>
+256.</p>
</section>
diff --git a/lib/stdlib/src/binary.erl b/lib/stdlib/src/binary.erl
index 4850a59eb6..8d07a356dd 100644
--- a/lib/stdlib/src/binary.erl
+++ b/lib/stdlib/src/binary.erl
@@ -89,9 +89,9 @@ copy(_, _) ->
decode_unsigned(_) ->
erlang:nif_error(undef).
--spec decode_unsigned(Subject, Endianess) -> Unsigned when
+-spec decode_unsigned(Subject, Endianness) -> Unsigned when
Subject :: binary(),
- Endianess :: big | little,
+ Endianness :: big | little,
Unsigned :: non_neg_integer().
decode_unsigned(_, _) ->
@@ -103,9 +103,9 @@ decode_unsigned(_, _) ->
encode_unsigned(_) ->
erlang:nif_error(undef).
--spec encode_unsigned(Unsigned, Endianess) -> binary() when
+-spec encode_unsigned(Unsigned, Endianness) -> binary() when
Unsigned :: non_neg_integer(),
- Endianess :: big | little.
+ Endianness :: big | little.
encode_unsigned(_, _) ->
erlang:nif_error(undef).
diff --git a/lib/stdlib/src/c.erl b/lib/stdlib/src/c.erl
index c2256c0cf9..9860adf04d 100644
--- a/lib/stdlib/src/c.erl
+++ b/lib/stdlib/src/c.erl
@@ -509,9 +509,12 @@ m(M) ->
{exports,E} = lists:keyfind(exports, 1, L),
Time = get_compile_time(L),
COpts = get_compile_options(L),
- format("Module ~w compiled: ",[M]), print_time(Time),
- format("Compiler options: ~p~n", [COpts]),
+ format("Module: ~w~n", [M]),
+ print_md5(L),
+ format("Compiled: "),
+ print_time(Time),
print_object_file(M),
+ format("Compiler options: ~p~n", [COpts]),
format("Exports: ~n",[]), print_exports(keysort(1, E)).
print_object_file(Mod) ->
@@ -522,6 +525,12 @@ print_object_file(Mod) ->
ignore
end.
+print_md5(L) ->
+ case lists:keyfind(md5, 1, L) of
+ {md5,<<MD5:128>>} -> io:format("MD5: ~.16b~n",[MD5]);
+ _ -> ok
+ end.
+
get_compile_time(L) ->
case get_compile_info(L, time) of
{ok,Val} -> Val;
@@ -569,8 +578,8 @@ split_print_exports([{F1, A1}|T1], [{F2, A2} | T2]) ->
split_print_exports([], []) -> ok.
print_time({Year,Month,Day,Hour,Min,_Secs}) ->
- format("Date: ~s ~w ~w, ", [month(Month),Day,Year]),
- format("Time: ~.2.0w.~.2.0w~n", [Hour,Min]);
+ format("~s ~w ~w, ", [month(Month),Day,Year]),
+ format("~.2.0w:~.2.0w~n", [Hour,Min]);
print_time(notime) ->
format("No compile time info available~n",[]).
diff --git a/lib/stdlib/src/ets.erl b/lib/stdlib/src/ets.erl
index 42b11a97e2..93c4f59896 100644
--- a/lib/stdlib/src/ets.erl
+++ b/lib/stdlib/src/ets.erl
@@ -1613,13 +1613,18 @@ choice(Height, Width, P, Mode, Tab, Key, Turn, Opos) ->
end.
get_line(P, Default) ->
- case io:get_line(P) of
+ case line_string(io:get_line(P)) of
"\n" ->
Default;
L ->
L
end.
+%% If the standard input is set to binary mode
+%% convert it to a list so we can properly match.
+line_string(Binary) when is_binary(Binary) -> unicode:characters_to_list(Binary);
+line_string(Other) -> Other.
+
nonl(S) -> string:strip(S, right, $\n).
print_number(Tab, Key, Num) ->
diff --git a/lib/syntax_tools/src/epp_dodger.erl b/lib/syntax_tools/src/epp_dodger.erl
index 7e12eab1b5..3ca7a8197e 100644
--- a/lib/syntax_tools/src/epp_dodger.erl
+++ b/lib/syntax_tools/src/epp_dodger.erl
@@ -88,7 +88,7 @@
%% This is a so-called Erlang I/O ErrorInfo structure; see the {@link
%% //stdlib/io} module for details.
--type errorinfo() :: term(). % {integer(), atom(), term()}.
+-type errorinfo() :: {integer(), atom(), term()}.
-type option() :: atom() | {atom(), term()}.
@@ -208,8 +208,8 @@ do_parse_file(DefEncoding, File, Parser, Options) ->
try Parser(Dev, 1, Options)
after ok = file:close(Dev)
end;
- {error, _} = Error ->
- Error
+ {error, Error} ->
+ {error, {0, file, Error}} % defer to file:format_error/1
end.
find_invalid_unicode([H|T]) ->
diff --git a/lib/test_server/src/erl2html2.erl b/lib/test_server/src/erl2html2.erl
index b9b45cda25..7cfaa2c325 100644
--- a/lib/test_server/src/erl2html2.erl
+++ b/lib/test_server/src/erl2html2.erl
@@ -22,11 +22,11 @@
%%%------------------------------------------------------------------
-module(erl2html2).
--export([convert/2, convert/3]).
+-export([convert/3, convert/4]).
-convert([], _Dest) -> % Fake clause.
+convert([], _Dest, _InclPath) -> % Fake clause.
ok;
-convert(File, Dest) ->
+convert(File, Dest, InclPath) ->
%% The generated code uses the BGCOLOR attribute in the
%% BODY tag, which wasn't valid until HTML 3.2. Also,
%% good HTML should either override all colour attributes
@@ -48,12 +48,12 @@ convert(File, Dest) ->
"</head>\n\n"
"<body bgcolor=\"white\" text=\"black\""
" link=\"blue\" vlink=\"purple\" alink=\"red\">\n"],
- convert(File, Dest, Header).
+ convert(File, Dest, InclPath, Header).
-convert(File, Dest, Header) ->
+convert(File, Dest, InclPath, Header) ->
%% statistics(runtime),
- case parse_file(File) of
+ case parse_file(File, InclPath) of
{ok,Functions} ->
%% {_, Time1} = statistics(runtime),
%% io:format("Parsed file in ~.2f Seconds.~n",[Time1/1000]),
@@ -92,8 +92,8 @@ convert(File, Dest, Header) ->
%%% Use expanded preprocessor directives if possible (epp). Only if
%%% this fails, fall back on using non-expanded code (epp_dodger).
-parse_file(File) ->
- case epp:open(File, [], []) of
+parse_file(File, InclPath) ->
+ case epp:open(File, InclPath, []) of
{ok,Epp} ->
try parse_preprocessed_file(Epp,File,false) of
Forms ->
@@ -145,13 +145,15 @@ parse_non_preprocessed_file(File) ->
parse_non_preprocessed_file(Epp, File, Location) ->
case epp_dodger:parse_form(Epp, Location) of
{ok,Tree,Location1} ->
- case erl_syntax:revert(Tree) of
+ try erl_syntax:revert(Tree) of
{function,L,F,A,[_|C]} ->
Clauses = [{clause,CL} || {clause,CL,_,_,_} <- C],
[{atom_to_list(F),A,L} | Clauses] ++
parse_non_preprocessed_file(Epp, File, Location1);
_ ->
parse_non_preprocessed_file(Epp, File, Location1)
+ catch
+ _:_ -> parse_non_preprocessed_file(Epp, File, Location1)
end;
{error,_E,Location1} ->
parse_non_preprocessed_file(Epp, File, Location1);
diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl
index 9192a76a17..8d91778cbb 100644
--- a/lib/test_server/src/test_server.erl
+++ b/lib/test_server/src/test_server.erl
@@ -130,7 +130,8 @@ cover_compile(CoverInfo=#cover{app=App,excl=all,incl=Include,cross=Cross}) ->
io:fwrite("done\n\n",[]),
{ok,CoverInfo#cover{mods=Include}}
end;
-cover_compile(CoverInfo=#cover{app=App,excl=Exclude,incl=Include,cross=Cross}) ->
+cover_compile(CoverInfo=#cover{app=App,excl=Exclude,
+ incl=Include,cross=Cross}) ->
CrossMods = lists:flatmap(fun({_,M}) -> M end,Cross),
case code:lib_dir(App) of
{error,bad_name} ->
@@ -445,15 +446,6 @@ run_test_case_apply({CaseNum,Mod,Func,Args,Name,
}).
run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData) ->
- {ok,Cwd} = file:get_cwd(),
- Args2Print = case Args of
- [Args1] when is_list(Args1) ->
- lists:keydelete(tc_group_result, 1, Args1);
- _ ->
- Args
- end,
- print(minor, "Test case started with:\n~w:~w(~tp)\n", [Mod,Func,Args2Print]),
- print(minor, "Current directory is ~tp\n", [Cwd]),
print_timestamp(minor,"Started at "),
print(minor, "", [], internal_raw),
TCCallback = get(test_server_testcase_callback),
@@ -728,7 +720,7 @@ handle_tc_exit(Reason, #st{status=tc,config=Config0,mf={Mod,Func},pid=Pid}=St)
Msg = {E,AbortReason},
{Msg,Loc0,Msg};
Other ->
- {Other,unknown,Other}
+ {{'EXIT',Other},unknown,Other}
end,
Timeout = end_conf_timeout(Reason, St),
Config = [{tc_status,{failed,F}}|Config0],
@@ -742,7 +734,7 @@ handle_tc_exit(Reason, #st{config=Config,mf={Mod,Func0},pid=Pid,
{testcase_aborted=E,AbortReason,Loc0} ->
{{E,AbortReason},Loc0};
Other ->
- {Other,St#st.last_known_loc}
+ {{'EXIT',Other},St#st.last_known_loc}
end,
Func = case Status of
init_per_testcase=F -> {F,Func0};
@@ -779,16 +771,16 @@ do_call_end_conf(Starter,Mod,Func,Data,Conf,TVal) ->
EndConfApply =
fun() ->
timetrap(TVal),
- case catch apply(Mod,end_per_testcase,[Func,Conf]) of
- {'EXIT',Why} ->
+ try apply(Mod,end_per_testcase,[Func,Conf]) of
+ _ -> ok
+ catch
+ _:Why ->
timer:sleep(1),
group_leader() ! {printout,12,
"WARNING! "
"~w:end_per_testcase(~w, ~p)"
" crashed!\n\tReason: ~p\n",
- [Mod,Func,Conf,Why]};
- _ ->
- ok
+ [Mod,Func,Conf,Why]}
end,
Supervisor ! {self(),end_conf}
end,
@@ -817,11 +809,11 @@ spawn_fw_call(Mod,{init_per_testcase,Func},CurrConf,Pid,
Skip = {skip,{failed,{Mod,init_per_testcase,Why}}},
%% if init_per_testcase fails, the test case
%% should be skipped
- case catch do_end_tc_call(Mod,Func, {Pid,Skip,[CurrConf]}, Why) of
- {'EXIT',FwEndTCErr} ->
- exit({fw_notify_done,end_tc,FwEndTCErr});
- _ ->
- ok
+ try do_end_tc_call(Mod,Func, {Pid,Skip,[CurrConf]}, Why) of
+ _ -> ok
+ catch
+ _:FwEndTCErr ->
+ exit({fw_notify_done,end_tc,FwEndTCErr})
end,
%% finished, report back
SendTo ! {self(),fw_notify_done,
@@ -849,12 +841,12 @@ spawn_fw_call(Mod,{end_per_testcase,Func},EndConf,Pid,
" failed!\n\tReason: timetrap timeout"
" after ~w ms!\n", [Mod,Func,EndConf,TVal]},
FailLoc = proplists:get_value(tc_fail_loc, EndConf),
- case catch do_end_tc_call(Mod,Func,
+ try do_end_tc_call(Mod,Func,
{Pid,Report,[EndConf]}, Why) of
- {'EXIT',FwEndTCErr} ->
- exit({fw_notify_done,end_tc,FwEndTCErr});
- _ ->
- ok
+ _ -> ok
+ catch
+ _:FwEndTCErr ->
+ exit({fw_notify_done,end_tc,FwEndTCErr})
end,
Warn = "<font color=\"red\">"
"WARNING: end_per_testcase timed out!</font>",
@@ -890,21 +882,21 @@ spawn_fw_call(Mod,Func,CurrConf,Pid,Error,Loc,SendTo) ->
end,
FwCall =
fun() ->
- case catch fw_error_notify(Mod,Func1,[],
- Error,Loc) of
- {'EXIT',FwErrorNotifyErr} ->
+ try fw_error_notify(Mod,Func1,[],
+ Error,Loc) of
+ _ -> ok
+ catch
+ _:FwErrorNotifyErr ->
exit({fw_notify_done,error_notification,
- FwErrorNotifyErr});
- _ ->
- ok
+ FwErrorNotifyErr})
end,
Conf = [{tc_status,{failed,Error}}|CurrConf],
- case catch do_end_tc_call(Mod,Func1,
- {Pid,Error,[Conf]},Error) of
- {'EXIT',FwEndTCErr} ->
- exit({fw_notify_done,end_tc,FwEndTCErr});
- _ ->
- ok
+ try do_end_tc_call(Mod,Func1,
+ {Pid,Error,[Conf]},Error) of
+ _ -> ok
+ catch
+ _:FwEndTCErr ->
+ exit({fw_notify_done,end_tc,FwEndTCErr})
end,
%% finished, report back
SendTo ! {self(),fw_notify_done,{died,Error,Loc,[],undefined}}
@@ -984,12 +976,15 @@ run_test_case_eval(Mod, Func, Args0, Name, Ref, RunInit,
NewResult = do_end_tc_call(Mod,Func, {{error,Reason},[Conf]},
{fail,Reason}),
{{0,NewResult},Where,[]};
- Skip = {skip,_Reason} ->
- NewResult = do_end_tc_call(Mod,Func, {Skip,Args0}, Skip),
+ Skip = {SkipType,_Reason} when SkipType == skip;
+ SkipType == skipped ->
+ NewResult = do_end_tc_call(Mod,Func,
+ {Skip,Args0}, Skip),
{{0,NewResult},Where,[]};
AutoSkip = {auto_skip,_Reason} ->
%% special case where a conf case "pretends" to be skipped
- NewResult = do_end_tc_call(Mod,Func, {AutoSkip,Args0}, AutoSkip),
+ NewResult =
+ do_end_tc_call(Mod,Func, {AutoSkip,Args0}, AutoSkip),
{{0,NewResult},Where,[]}
end,
exit({Ref,Time,Value,Loc,Opts}).
@@ -1000,10 +995,12 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) ->
set_tc_state(init_per_testcase, hd(Args)),
ensure_timetrap(Args),
case init_per_testcase(Mod, Func, Args) of
- Skip = {skip,Reason} ->
+ Skip = {SkipType,Reason} when SkipType == skip;
+ SkipType == skipped ->
Line = get_loc(),
Conf = [{tc_status,{skipped,Reason}}|hd(Args)],
- NewRes = do_end_tc_call(Mod,Func, {Skip,[Conf]}, Skip),
+ NewRes = do_end_tc_call(Mod,Func,
+ {Skip,[Conf]}, Skip),
{{0,NewRes},Line,[]};
{skip_and_save,Reason,SaveCfg} ->
Line = get_loc(),
@@ -1021,11 +1018,12 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) ->
{{0,NewRes},[{Mod,Func}],[]};
{ok,NewConf} ->
%% call user callback function if defined
- NewConf1 = user_callback(TCCallback, Mod, Func, init, NewConf),
+ NewConf1 =
+ user_callback(TCCallback, Mod, Func, init, NewConf),
%% save current state in controller loop
set_tc_state(tc, NewConf1),
%% execute the test case
- {{T,Return},Loc} = {ts_tc(Mod, Func, [NewConf1]),get_loc()},
+ {{T,Return},Loc} = {ts_tc(Mod,Func,[NewConf1]), get_loc()},
{EndConf,TSReturn,FWReturn} =
case Return of
{E,TCError} when E=='EXIT' ; E==failed ->
@@ -1041,30 +1039,39 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) ->
{[{tc_status,{skipped,Why}},
{save_config,SaveCfg}|NewConf1],
Skip,Skip};
- {skip,Why} ->
- {[{tc_status,{skipped,Why}}|NewConf1],Return,Return};
+ {SkipType,Why} when SkipType == skip;
+ SkipType == skipped ->
+ {[{tc_status,{skipped,Why}}|NewConf1],Return,
+ Return};
_ ->
{[{tc_status,ok}|NewConf1],Return,ok}
end,
%% call user callback function if defined
- EndConf1 = user_callback(TCCallback, Mod, Func, 'end', EndConf),
+ EndConf1 =
+ user_callback(TCCallback, Mod, Func, 'end', EndConf),
%% update current state in controller loop
{FWReturn1,TSReturn1,EndConf2} =
case end_per_testcase(Mod, Func, EndConf1) of
SaveCfg1={save_config,_} ->
- {FWReturn,TSReturn,[SaveCfg1|lists:keydelete(save_config,1,
- EndConf1)]};
+ {FWReturn,TSReturn,
+ [SaveCfg1|lists:keydelete(save_config,1,
+ EndConf1)]};
{fail,ReasonToFail} ->
%% user has failed the testcase
- fw_error_notify(Mod, Func, EndConf1, ReasonToFail),
- {{error,ReasonToFail},{failed,ReasonToFail},EndConf1};
- {failed,{_,end_per_testcase,_}} = Failure when FWReturn == ok ->
+ fw_error_notify(Mod, Func, EndConf1,
+ ReasonToFail),
+ {{error,ReasonToFail},
+ {failed,ReasonToFail},
+ EndConf1};
+ {failed,{_,end_per_testcase,_}} = Failure when
+ FWReturn == ok ->
%% unexpected termination in end_per_testcase
%% report this as the result to the framework
{Failure,TSReturn,EndConf1};
_ ->
- %% test case result should be reported to framework
- %% no matter the status of end_per_testcase
+ %% test case result should be reported to
+ %% framework no matter the status of
+ %% end_per_testcase
{FWReturn,TSReturn,EndConf1}
end,
%% clear current state in controller loop
@@ -1131,7 +1138,8 @@ process_return_val([Return], M,F,A, Loc, Final) when is_list(Return) ->
ReturnTags = [skip,skip_and_save,save_config,comment,return_group_result],
%% check if all elements in the list are valid end conf return value tuples
case lists:all(fun(Val) when is_tuple(Val) ->
- lists:any(fun(T) -> T == element(1, Val) end, ReturnTags);
+ lists:any(fun(T) -> T == element(1, Val) end,
+ ReturnTags);
(ok) ->
true;
(_) ->
@@ -1165,14 +1173,19 @@ process_return_val1([Failed={E,TCError}|_], M,F,A=[Args], Loc, _, SaveOpts)
NewReturn ->
{NewReturn,SaveOpts}
end;
-process_return_val1([SaveCfg={save_config,_}|Opts], M,F,[Args], Loc, Final, SaveOpts) ->
+process_return_val1([SaveCfg={save_config,_}|Opts], M,F,[Args],
+ Loc, Final, SaveOpts) ->
process_return_val1(Opts, M,F,[[SaveCfg|Args]], Loc, Final, SaveOpts);
-process_return_val1([{skip_and_save,Why,SaveCfg}|Opts], M,F,[Args], Loc, _, SaveOpts) ->
- process_return_val1(Opts, M,F,[[{save_config,SaveCfg}|Args]], Loc, {skip,Why}, SaveOpts);
-process_return_val1([GR={return_group_result,_}|Opts], M,F,A, Loc, Final, SaveOpts) ->
+process_return_val1([{skip_and_save,Why,SaveCfg}|Opts], M,F,[Args],
+ Loc, _, SaveOpts) ->
+ process_return_val1(Opts, M,F,[[{save_config,SaveCfg}|Args]],
+ Loc, {skip,Why}, SaveOpts);
+process_return_val1([GR={return_group_result,_}|Opts], M,F,A,
+ Loc, Final, SaveOpts) ->
process_return_val1(Opts, M,F,A, Loc, Final, [GR|SaveOpts]);
-process_return_val1([RetVal={Tag,_}|Opts], M,F,A, Loc, _, SaveOpts) when Tag==skip;
- Tag==comment ->
+process_return_val1([RetVal={Tag,_}|Opts], M,F,A,
+ Loc, _, SaveOpts) when Tag==skip;
+ Tag==comment ->
process_return_val1(Opts, M,F,A, Loc, RetVal, SaveOpts);
process_return_val1([_|Opts], M,F,A, Loc, Final, SaveOpts) ->
process_return_val1(Opts, M,F,A, Loc, Final, SaveOpts);
@@ -1186,7 +1199,8 @@ process_return_val1([], M,F,A, _Loc, Final, SaveOpts) ->
user_callback(undefined, _, _, _, Args) ->
Args;
-user_callback({CBMod,CBFunc}, Mod, Func, InitOrEnd, [Args]) when is_list(Args) ->
+user_callback({CBMod,CBFunc}, Mod, Func, InitOrEnd,
+ [Args]) when is_list(Args) ->
case catch apply(CBMod, CBFunc, [InitOrEnd,Mod,Func,Args]) of
Args1 when is_list(Args1) ->
[Args1];
@@ -1373,8 +1387,8 @@ fw_error_notify(Mod, Func, Args, Error, Loc) ->
%% Just like io:format, except that depending on the Detail value, the output
%% is directed to console, major and/or minor log files.
-print(Detail,Format,Args) ->
- test_server_ctrl:print(Detail, Format, Args).
+%% print(Detail,Format,Args) ->
+%% test_server_ctrl:print(Detail, Format, Args).
print(Detail,Format,Args,Printer) ->
test_server_ctrl:print(Detail, Format, Args, Printer).
@@ -1778,7 +1792,8 @@ timetrap(Timeout0, TimeToReport0, TCPid, MultAndScale = {Multiplier,Scale}) ->
put(test_server_timetraps,[{Handle,TCPid,{TimeToReport,Scale}}]);
List ->
List1 = lists:delete({infinity,TCPid,{infinity,false}}, List),
- put(test_server_timetraps,[{Handle,TCPid,{TimeToReport,Scale}}|List1])
+ put(test_server_timetraps,[{Handle,TCPid,
+ {TimeToReport,Scale}}|List1])
end,
Handle.
@@ -1837,7 +1852,9 @@ time_ms(Ms, _, _) when is_integer(Ms) -> Ms;
time_ms(infinity, _, _) -> infinity;
time_ms(Fun, TCPid, MultAndScale) when is_function(Fun) ->
time_ms_apply(Fun, TCPid, MultAndScale);
-time_ms({M,F,A}=MFA, TCPid, MultAndScale) when is_atom(M), is_atom(F), is_list(A) ->
+time_ms({M,F,A}=MFA, TCPid, MultAndScale) when is_atom(M),
+ is_atom(F),
+ is_list(A) ->
time_ms_apply(MFA, TCPid, MultAndScale);
time_ms(Other, _, _) -> exit({invalid_time_format,Other}).
diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl
index af8921fe75..68b03a5987 100644
--- a/lib/test_server/src/test_server_ctrl.erl
+++ b/lib/test_server/src/test_server_ctrl.erl
@@ -1808,20 +1808,32 @@ start_minor_log_file1(Mod, Func, LogDir, AbsName, MFA) ->
put(test_server_minor_footer, Footer),
io:put_chars(Fd, Header),
+ io:put_chars(Fd, "<a name=\"top\"></a>"),
+ io:put_chars(Fd, "<pre>\n"),
+
SrcListing = downcase(atom_to_list(Mod)) ++ ?src_listing_ext,
+
+ {Info,Arity} =
+ if Func == init_per_suite; Func == end_per_suite ->
+ {"Config function: ", 1};
+ Func == init_per_group; Func == end_per_group ->
+ {"Config function: ", 2};
+ true ->
+ {"Test case: ", 1}
+ end,
+
case {filelib:is_file(filename:join(LogDir, SrcListing)),
lists:member(no_src, get(test_server_logopts))} of
{true,false} ->
- print(Lev, "<a href=\"~ts#~ts\">source code for ~w:~w/1</a>\n",
+ print(Lev, Info ++ "<a href=\"~ts#~ts\">~w:~w/~w</a> "
+ "(click for source code)\n",
[uri_encode(SrcListing),
uri_encode(atom_to_list(Func)++"-1",utf8),
- Mod,Func]);
+ Mod,Func,Arity]);
_ ->
- ok
+ print(Lev, Info ++ "~w:~w/~w\n", [Mod,Func,Arity])
end,
- io:put_chars(Fd, "<pre>\n"),
-
AbsName.
stop_minor_log_file() ->
@@ -1927,15 +1939,20 @@ html_possibly_convert(Src, SrcInfo, Dest) ->
{ok,DestInfo} when DestInfo#file_info.mtime >= SrcInfo#file_info.mtime ->
ok; % dest file up to date
_ ->
+ InclPath = case application:get_env(test_server, include) of
+ {ok,Incls} -> Incls;
+ _ -> []
+ end,
+
OutDir = get(test_server_log_dir_base),
case test_server_sup:framework_call(get_html_wrapper,
["Module "++Src,false,
OutDir,undefined,
encoding(Src)], "") of
Empty when (Empty == "") ; (element(2,Empty) == "") ->
- erl2html2:convert(Src, Dest);
+ erl2html2:convert(Src, Dest, InclPath);
{_,Header,_} ->
- erl2html2:convert(Src, Dest, Header)
+ erl2html2:convert(Src, Dest, InclPath, Header)
end
end.
@@ -3071,13 +3088,11 @@ print_conf_time(ConfTime) ->
print(major, "=group_time ~.3fs", [ConfTime]),
print(minor, "~n=== Total execution time of group: ~.3fs~n", [ConfTime]).
-print_props(_, []) ->
+print_props([]) ->
ok;
-print_props(true, Props) ->
+print_props(Props) ->
print(major, "=group_props ~p", [Props]),
- print(minor, "Group properties: ~p~n", [Props]);
-print_props(_, _) ->
- ok.
+ print(minor, "Group properties: ~p~n", [Props]).
%% repeat N times: {repeat,N}
%% repeat N times or until all successful: {repeat_until_all_ok,N}
@@ -3682,7 +3697,6 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit,
print(major, "=case ~w:~w", [Mod, Func]),
MinorName = start_minor_log_file(Mod, Func, self() /= Main),
- print(minor, "<a name=\"top\"></a>", [], internal_raw),
MinorBase = filename:basename(MinorName),
print(major, "=logfile ~ts", [filename:basename(MinorName)]),
@@ -3715,7 +3729,20 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit,
[tc_start,{{Mod,{Func,GrName}},
MinorName}]),
- print_props((RunInit==skip_init), get_props(Mode)),
+ {ok,Cwd} = file:get_cwd(),
+ Args2Print = if is_list(UpdatedArgs) ->
+ lists:keydelete(tc_group_result, 1, UpdatedArgs);
+ true ->
+ UpdatedArgs
+ end,
+ if RunInit == skip_init ->
+ print_props(get_props(Mode));
+ true ->
+ ok
+ end,
+ print(minor, "Config value:\n\n ~tp\n", [Args2Print]),
+ print(minor, "Current directory is ~tp\n", [Cwd]),
+
GrNameStr = case GrName of
undefined -> "";
Name -> cast_to_list(Name)
diff --git a/lib/test_server/src/ts_install.erl b/lib/test_server/src/ts_install.erl
index bc62015ac3..594e619fbc 100644
--- a/lib/test_server/src/ts_install.erl
+++ b/lib/test_server/src/ts_install.erl
@@ -18,7 +18,6 @@
%%
-module(ts_install).
-
-export([install/2, platform_id/1]).
-include("ts.hrl").
@@ -135,15 +134,63 @@ unix_autoconf(XConf) ->
case filelib:is_file(Configure) of
true ->
OSXEnv = macosx_cflags(),
+ UnQuotedEnv = assign_vars(unquote(Env++OSXEnv)),
io:format("Running ~s~nEnv: ~p~n",
- [lists:flatten(Configure ++ Args),Env++OSXEnv]),
+ [lists:flatten(Configure ++ Args),UnQuotedEnv]),
Port = open_port({spawn, lists:flatten(["\"",Configure,"\"",Args])},
- [stream, eof, {env,Env++OSXEnv}]),
+ [stream, eof, {env,UnQuotedEnv}]),
ts_lib:print_data(Port);
false ->
{error, no_configure_script}
end.
+unquote([{Var,Val}|T]) ->
+ [{Var,unquote(Val)}|unquote(T)];
+unquote([]) ->
+ [];
+unquote("\""++Rest) ->
+ lists:reverse(tl(lists:reverse(Rest)));
+unquote(String) ->
+ String.
+
+assign_vars([]) ->
+ [];
+assign_vars([{VAR,FlagsStr} | VARs]) ->
+ [{VAR,assign_vars(FlagsStr)} | assign_vars(VARs)];
+assign_vars(FlagsStr) ->
+ Flags = [assign_all_vars(Str,[]) || Str <- string:tokens(FlagsStr, [$ ])],
+ string:strip(lists:flatten(lists:map(fun(Flag) ->
+ Flag ++ " "
+ end, Flags)), right).
+
+assign_all_vars([$$ | Rest], FlagSoFar) ->
+ {VarName,Rest1} = get_var_name(Rest, []),
+ assign_all_vars(Rest1, FlagSoFar ++ assign_var(VarName));
+assign_all_vars([Char | Rest], FlagSoFar) ->
+ assign_all_vars(Rest, FlagSoFar ++ [Char]);
+assign_all_vars([], Flag) ->
+ Flag.
+
+get_var_name([Ch | Rest] = Str, VarR) ->
+ case valid_char(Ch) of
+ true -> get_var_name(Rest, [Ch | VarR]);
+ false -> {lists:reverse(VarR),Str}
+ end;
+get_var_name([], VarR) ->
+ {lists:reverse(VarR),[]}.
+
+assign_var(VarName) ->
+ case os:getenv(VarName) of
+ false -> "";
+ Val -> Val
+ end.
+
+valid_char(Ch) when Ch >= $a, Ch =< $z -> true;
+valid_char(Ch) when Ch >= $A, Ch =< $Z -> true;
+valid_char(Ch) when Ch >= $0, Ch =< $9 -> true;
+valid_char($_) -> true;
+valid_char(_) -> false.
+
get_xcomp_flag(Flag, Flags) ->
get_xcomp_flag(Flag, Flag, Flags).
get_xcomp_flag(Flag, Tag, Flags) ->
diff --git a/lib/test_server/src/ts_make.erl b/lib/test_server/src/ts_make.erl
index 8727f7ebfe..9cb77ecb12 100644
--- a/lib/test_server/src/ts_make.erl
+++ b/lib/test_server/src/ts_make.erl
@@ -67,7 +67,17 @@ get_port_data(Port, Last0, Complete0) ->
end.
update_last([C|Rest], Line, true) ->
- io:put_chars(list_to_binary(Line)), %% Utf-8 list to utf-8 binary
+ try
+ %% Utf-8 list to utf-8 binary
+ %% (e.g. we assume utf-8 bytes from port)
+ io:put_chars(list_to_binary(Line))
+ catch
+ error:badarg ->
+ %% io:put_chars/1 badarged
+ %% this likely means we had unicode code points
+ %% in our bytes buffer (e.g warning from gcc with åäö)
+ io:put_chars(unicode:characters_to_binary(Line))
+ end,
io:nl(),
update_last([C|Rest], [], false);
update_last([$\r|Rest], Result, Complete) ->
diff --git a/lib/test_server/test/erl2html2_SUITE.erl b/lib/test_server/test/erl2html2_SUITE.erl
index 37c2b74d8e..908985c879 100644
--- a/lib/test_server/test/erl2html2_SUITE.erl
+++ b/lib/test_server/test/erl2html2_SUITE.erl
@@ -161,7 +161,7 @@ convert_module(Mod,Config) ->
Src = filename:join(DataDir,Mod++".erl"),
Dst = filename:join(PrivDir,Mod++".erl.html"),
io:format("<a href=\"~s\">~s</a>\n",[Src,filename:basename(Src)]),
- ok = erl2html2:convert(Src, Dst, "<html><body>"),
+ ok = erl2html2:convert(Src, Dst, [], "<html><body>"),
io:format("<a href=\"~s\">~s</a>\n",[Dst,filename:basename(Dst)]),
{Src,Dst}.
diff --git a/lib/tools/emacs/erlang.el b/lib/tools/emacs/erlang.el
index c56759ebb9..0c003bab39 100644
--- a/lib/tools/emacs/erlang.el
+++ b/lib/tools/emacs/erlang.el
@@ -4743,6 +4743,23 @@ for a tag on the form `module:tag'."
;;; `module:tag'.
+(when (and (fboundp 'etags-tags-completion-table)
+ (fboundp 'tags-lazy-completion-table)) ; Emacs 23.1+
+ (if (fboundp 'advice-add)
+ ;; Emacs 24.4+
+ (advice-add 'etags-tags-completion-table :around
+ (lambda (oldfun)
+ (if (eq find-tag-default-function 'erlang-find-tag-for-completion)
+ (erlang-etags-tags-completion-table)
+ (funcall oldfun)))
+ (list :name 'erlang-replace-tags-table))
+ ;; Emacs 23.1-24.3
+ (defadvice etags-tags-completion-table (around erlang-replace-tags-table activate)
+ (if (eq find-tag-default-function 'erlang-find-tag-for-completion)
+ (setq ad-return-value (erlang-etags-tags-completion-table))
+ ad-do-it))))
+
+
(defun erlang-complete-tag ()
"Perform tags completion on the text around point.
Completes to the set of names listed in the current tags table.
@@ -4754,7 +4771,17 @@ about Erlang modules."
(require 'etags)
(error nil))
(cond ((and erlang-tags-installed
- (fboundp 'complete-tag)) ; Emacs 19
+ (fboundp 'etags-tags-completion-table)
+ (fboundp 'tags-lazy-completion-table)) ; Emacs 23.1+
+ ;; This depends on the advice called erlang-replace-tags-table
+ ;; above. It is not enough to let-bind
+ ;; tags-completion-table-function since that will not override
+ ;; the buffer-local value in the TAGS buffer.
+ (let ((find-tag-default-function 'erlang-find-tag-for-completion))
+ (complete-tag)))
+ ((and erlang-tags-installed
+ (fboundp 'complete-tag)
+ (fboundp 'tags-complete-tag)) ; Emacs 19
(let ((orig-tags-complete-tag (symbol-function 'tags-complete-tag)))
(fset 'tags-complete-tag
(symbol-function 'erlang-tags-complete-tag))
@@ -4769,6 +4796,15 @@ about Erlang modules."
(error "This version of Emacs can't complete tags"))))
+(defun erlang-find-tag-for-completion ()
+ (let ((start (save-excursion
+ (skip-chars-backward "[:word:][:digit:]_:'")
+ (point))))
+ (unless (eq start (point))
+ (buffer-substring-no-properties start (point)))))
+
+
+
;; Based on `tags-complete-tag', but this one uses
;; `erlang-tags-completion-table' instead of `tags-completion-table'.
;;
@@ -4816,7 +4852,12 @@ about Erlang modules."
;; the only format supported by Emacs, so far.)
(defun erlang-etags-tags-completion-table ()
(let ((table (make-vector 511 0))
- (file nil))
+ (file nil)
+ (progress-reporter
+ (when (fboundp 'make-progress-reporter)
+ (make-progress-reporter
+ (format "Making erlang tags completion table for %s..." buffer-file-name)
+ (point-min) (point-max)))))
(save-excursion
(goto-char (point-min))
;; This monster regexp matches an etags tag line.
@@ -4828,31 +4869,33 @@ about Erlang modules."
;; \6 is the line to start searching at;
;; \7 is the char to start searching at.
(while (progn
- (while (and
- (eq (following-char) ?\f)
- (looking-at "\f\n\\([^,\n]*\\),.*\n"))
- (setq file (buffer-substring
- (match-beginning 1) (match-end 1)))
- (goto-char (match-end 0)))
- (re-search-forward
- "\
+ (while (and
+ (eq (following-char) ?\f)
+ (looking-at "\f\n\\([^,\n]*\\),.*\n"))
+ (setq file (buffer-substring
+ (match-beginning 1) (match-end 1)))
+ (goto-char (match-end 0)))
+ (re-search-forward
+ "\
^\\(\\([^\177]+[^-a-zA-Z0-9_$\177]+\\)?\\([-a-zA-Z0-9_$?:]+\\)\
\[^-a-zA-Z0-9_$?:\177]*\\)\177\\(\\([^\n\001]+\\)\001\\)?\
\\([0-9]+\\)?,\\([0-9]+\\)?\n"
- nil t))
- (let ((tag (if (match-beginning 5)
- ;; There is an explicit tag name.
- (buffer-substring (match-beginning 5) (match-end 5))
- ;; No explicit tag name. Best guess.
- (buffer-substring (match-beginning 3) (match-end 3))))
- (module (and file
- (erlang-get-module-from-file-name file))))
- (intern tag table)
- (if (stringp module)
- (progn
- (intern (concat module ":" tag) table)
- ;; Only the first one will be stored in the table.
- (intern (concat module ":") table))))))
+ nil t))
+ (let ((tag (if (match-beginning 5)
+ ;; There is an explicit tag name.
+ (buffer-substring (match-beginning 5) (match-end 5))
+ ;; No explicit tag name. Best guess.
+ (buffer-substring (match-beginning 3) (match-end 3))))
+ (module (and file
+ (erlang-get-module-from-file-name file))))
+ (intern tag table)
+ (when (stringp module)
+ (intern (concat module ":" tag) table)
+ ;; Only the first ones will be stored in the table.
+ (intern (concat module ":") table)
+ (intern (concat module ":module_info") table))
+ (when progress-reporter
+ (progress-reporter-update progress-reporter (point))))))
table))
;;;
diff --git a/lib/tools/src/lcnt.erl b/lib/tools/src/lcnt.erl
index f1251fddab..d5ba8aa52f 100644
--- a/lib/tools/src/lcnt.erl
+++ b/lib/tools/src/lcnt.erl
@@ -305,7 +305,7 @@ handle_call({inspect, Lockname, InOpts}, _From, #state{ duration=Duration, locks
{true, true} -> locks_ids(Filtered);
_ -> []
end,
- Combos = combine_classes(Filtered, proplists:get_value(combine, Opts)),
+ Combos = combine_classes(Filtered, proplists:get_value(combine, Opts)),
case proplists:get_value(locations, Opts) of
true ->
lists:foreach(fun
@@ -329,9 +329,8 @@ handle_call({inspect, Lockname, InOpts}, _From, #state{ duration=Duration, locks
end
end, Combos);
_ ->
- Print1 = locks2print(Combos, Duration),
- Print2 = filter_print(Print1, Opts),
- print_lock_information(Print2, proplists:get_value(print, Opts))
+ Print = filter_print(locks2print(Combos, Duration), Opts),
+ print_lock_information(Print, proplists:get_value(print, Opts))
end,
{reply, ok, State};
@@ -357,8 +356,7 @@ handle_call({histogram, Lockname, InOpts}, _From, #state{ duration=Duration, loc
{thresholds, [{tries, -1}, {colls, -1}, {time, -1}]}], Opts),
Prints = locks2print([L], Duration),
print_lock_information(Prints, proplists:get_value(print, Opts1)),
- print_full_histogram(SumStats#stats.hist),
- io:format("~n")
+ print_full_histogram(SumStats#stats.hist)
end, Combos),
{reply, ok, State};
@@ -509,20 +507,23 @@ filter_locks(Locks, Lockname) ->
% 4. max length of locks
filter_print(PLs, Opts) ->
- TLs = threshold_locks(PLs, proplists:get_value(thresholds, Opts, [])),
- SLs = sort_locks(TLs, proplists:get_value(sort, Opts, time)),
- CLs = cut_locks(SLs, proplists:get_value(max_locks, Opts, none)),
- reverse_locks(CLs, not proplists:get_value(reverse,Opts, false)).
-
-sort_locks(Locks, name) -> lists:keysort(#print.name, Locks);
-sort_locks(Locks, id) -> lists:keysort(#print.id, Locks);
-sort_locks(Locks, type) -> lists:keysort(#print.type, Locks);
-sort_locks(Locks, tries) -> lists:keysort(#print.tries, Locks);
-sort_locks(Locks, colls) -> lists:keysort(#print.colls, Locks);
-sort_locks(Locks, ratio) -> lists:keysort(#print.cr, Locks);
-sort_locks(Locks, time) -> lists:keysort(#print.time, Locks);
+ TLs = threshold_locks(PLs, proplists:get_value(thresholds, Opts, [])),
+ SLs = sort_locks(TLs, proplists:get_value(sort, Opts, time)),
+ CLs = cut_locks(SLs, proplists:get_value(max_locks, Opts, none)),
+ reverse_locks(CLs, proplists:get_value(reverse, Opts, false)).
+
+sort_locks(Locks, name) -> reverse_sort_locks(#print.name, Locks);
+sort_locks(Locks, id) -> reverse_sort_locks(#print.id, Locks);
+sort_locks(Locks, type) -> reverse_sort_locks(#print.type, Locks);
+sort_locks(Locks, tries) -> reverse_sort_locks(#print.tries, Locks);
+sort_locks(Locks, colls) -> reverse_sort_locks(#print.colls, Locks);
+sort_locks(Locks, ratio) -> reverse_sort_locks(#print.cr, Locks);
+sort_locks(Locks, time) -> reverse_sort_locks(#print.time, Locks);
sort_locks(Locks, _) -> sort_locks(Locks, time).
+reverse_sort_locks(Ix, Locks) ->
+ lists:reverse(lists:keysort(Ix, Locks)).
+
% cut locks not above certain thresholds
threshold_locks(Locks, Thresholds) ->
Tries = proplists:get_value(tries, Thresholds, -1),
@@ -647,15 +648,19 @@ format_histogram(Tup) when is_tuple(Tup) ->
_ -> string_histogram([case V of 0 -> 0; _ -> V/Max end || V <- Vs])
end.
-string_histogram([0|Vs]) ->
- [$\s|string_histogram(Vs)];
-string_histogram([V|Vs]) when V > 0.66 ->
- [$X|string_histogram(Vs)];
-string_histogram([V|Vs]) when V > 0.33 ->
- [$x|string_histogram(Vs)];
-string_histogram([_|Vs]) ->
- [$.|string_histogram(Vs)];
-string_histogram([]) -> [].
+string_histogram(Vs) ->
+ [$||histogram_values_to_string(Vs,$|)].
+
+histogram_values_to_string([0|Vs],End) ->
+ [$\s|histogram_values_to_string(Vs,End)];
+histogram_values_to_string([V|Vs],End) when V > 0.66 ->
+ [$X|histogram_values_to_string(Vs,End)];
+histogram_values_to_string([V|Vs],End) when V > 0.33 ->
+ [$x|histogram_values_to_string(Vs,End)];
+histogram_values_to_string([_|Vs],End) ->
+ [$.|histogram_values_to_string(Vs,End)];
+histogram_values_to_string([],End) ->
+ [End].
%% state making
@@ -778,7 +783,7 @@ auto_print_width(Locks, Print) ->
({print,print}, Out) -> [print|Out];
({Str, Len}, Out) -> [erlang:min(erlang:max(length(s(Str))+1,Len),80)|Out]
end, [], lists:zip(tuple_to_list(L), tuple_to_list(Max)))))
- end, #print{ id = 4, type = 5, entry = 5, name = 6, tries = 8, colls = 13, cr = 16, time = 11, dtr = 14, hist=20 },
+ end, #print{ id=4, type=5, entry=5, name=6, tries=8, colls=13, cr=16, time=11, dtr=14, hist=20 },
Locks),
% Setup the offsets for later pruning
Offsets = [
@@ -820,7 +825,7 @@ print_header(Opts) ->
cr = "collisions [%]",
time = "time [us]",
dtr = "duration [%]",
- hist = "histogram"
+ hist = "histogram [log2(us)]"
},
Divider = #print{
name = lists:duplicate(1 + length(Header#print.name), 45),
@@ -863,9 +868,9 @@ format_lock(L, [Opt|Opts]) ->
{time, W} -> [{space, W, s(L#print.time) } | format_lock(L, Opts)];
duration -> [{space, 20, s(L#print.dtr) } | format_lock(L, Opts)];
{duration, W} -> [{space, W, s(L#print.dtr) } | format_lock(L, Opts)];
- histogram -> [{space, 0, s(L#print.hist) } | format_lock(L, Opts)];
- {histogram, W} -> [{space, W, s(L#print.hist) } | format_lock(L, Opts)];
- _ -> format_lock(L, Opts)
+ histogram -> [{space, 20, s(L#print.hist) } | format_lock(L, Opts)];
+ {histogram, W} -> [{left, W - length(s(L#print.hist)) - 1, s(L#print.hist)} | format_lock(L, Opts)];
+ _ -> format_lock(L, Opts)
end.
print_state_information(#state{locks = Locks} = State) ->
@@ -926,6 +931,7 @@ s(T) -> term2string(T).
strings(Strings) -> strings(Strings, []).
strings([], Out) -> Out;
strings([{space, N, S} | Ss], Out) -> strings(Ss, Out ++ term2string(term2string("~~~ws", [N]), [S]));
+strings([{left, N, S} | Ss], Out) -> strings(Ss, Out ++ term2string(term2string(" ~~s~~~ws", [N]), [S,""]));
strings([{format, Format, S} | Ss], Out) -> strings(Ss, Out ++ term2string(Format, [S]));
strings([S|Ss], Out) -> strings(Ss, Out ++ term2string("~ts", [S])).
diff --git a/lib/tools/src/tags.erl b/lib/tools/src/tags.erl
index e3cc51cdb2..e25db2eb1b 100644
--- a/lib/tools/src/tags.erl
+++ b/lib/tools/src/tags.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2013. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2015. 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
@@ -297,15 +297,16 @@ word_char(_) -> false.
%% Check the options `outfile' and `outdir'.
open_out(Options) ->
+ Opts = [write, {encoding, unicode}],
case lists:keysearch(outfile, 1, Options) of
{value, {outfile, File}} ->
- file:open(File, [write]);
+ file:open(File, Opts);
_ ->
case lists:keysearch(outdir, 1, Options) of
{value, {outdir, Dir}} ->
- file:open(filename:join(Dir, "TAGS"), [write]);
+ file:open(filename:join(Dir, "TAGS"), Opts);
_ ->
- file:open("TAGS", [write])
+ file:open("TAGS", Opts)
end
end.
diff --git a/lib/wx/src/wxe_server.erl b/lib/wx/src/wxe_server.erl
index 465b9da2e0..153e2475ba 100644
--- a/lib/wx/src/wxe_server.erl
+++ b/lib/wx/src/wxe_server.erl
@@ -223,14 +223,18 @@ handle_connect(Object, #evh{handler=undefined, cb=Callback} = EvData0,
Error ->
{reply, Error, State0}
end;
-handle_connect(Object, EvData=#evh{handler=Handler},
+handle_connect(Object, EvData=#evh{handler=Handler},
From, State0 = #state{users=Users}) ->
%% Correct process is already listening just register it
put(Handler, From),
- User0 = #user{events=Listeners0} = gb_trees:get(From, Users),
- User = User0#user{events=[{Object,EvData}|Listeners0]},
- State = State0#state{users=gb_trees:update(From, User, Users)},
- {reply, ok, State}.
+ case gb_trees:lookup(From, Users) of
+ {value, User0 = #user{events=Listeners0}} ->
+ User = User0#user{events=[{Object,EvData}|Listeners0]},
+ State = State0#state{users=gb_trees:update(From, User, Users)},
+ {reply, ok, State};
+ none -> %% We are closing up the shop
+ {reply, {error, terminating}, State0}
+ end.
invoke_cb({{Ev=#wx{}, Ref=#wx_ref{}}, FunId,_}, _S) ->
%% Event callbacks
diff --git a/lib/wx/test/wx_event_SUITE.erl b/lib/wx/test/wx_event_SUITE.erl
index 076f16ba16..f9f8788d8f 100644
--- a/lib/wx/test/wx_event_SUITE.erl
+++ b/lib/wx/test/wx_event_SUITE.erl
@@ -336,12 +336,14 @@ connect_in_callback(Config) ->
end}]),
wxWindow:show(F1),
receive
- {continue, F1} -> Tester ! {continue, F1}
+ {continue, F1} ->
+ true = wxFrame:disconnect(F1, size),
+ Tester ! {continue, F1}
end
end,
- wxFrame:connect(Frame,size,
+ wxFrame:connect(Frame,show,
[{callback,
- fun(#wx{event=#wxSize{}},_SizeEv) ->
+ fun(#wx{event=#wxShow{}},_SizeEv) ->
io:format("Frame got size~n",[]),
spawn(TestWindow)
end}]),